feat: add omx orchestration backend selection
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { requireRequestSession } from "@/lib/boss-auth";
|
||||
import {
|
||||
getProject,
|
||||
getProjectOrchestrationBackendState,
|
||||
updateProjectOrchestrationBackend,
|
||||
} from "@/lib/boss-data";
|
||||
|
||||
function normalizeRequestedBackendId(value: unknown) {
|
||||
return value === "omx-team" ? "omx-team" : "boss-native-orchestrator";
|
||||
}
|
||||
|
||||
async function readGroupProjectOrNotFound(projectId: string) {
|
||||
const project = await getProject(projectId);
|
||||
if (!project) {
|
||||
return { ok: false as const, response: NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 }) };
|
||||
}
|
||||
if (!project.isGroup) {
|
||||
return {
|
||||
ok: false as const,
|
||||
response: NextResponse.json({ ok: false, message: "PROJECT_NOT_GROUP_CHAT" }, { status: 400 }),
|
||||
};
|
||||
}
|
||||
return { ok: true as const, project };
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ projectId: string }> },
|
||||
) {
|
||||
const session = await requireRequestSession(request);
|
||||
if (!session) {
|
||||
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { projectId } = await context.params;
|
||||
const projectCheck = await readGroupProjectOrNotFound(projectId);
|
||||
if (!projectCheck.ok) {
|
||||
return projectCheck.response;
|
||||
}
|
||||
|
||||
const state = await getProjectOrchestrationBackendState(projectId);
|
||||
if (!state) {
|
||||
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
...state,
|
||||
requestedBackendId: projectCheck.project.orchestrationBackendOverride ?? null,
|
||||
requestedBackendLabel: projectCheck.project.orchestrationBackendOverride
|
||||
? state.requestedBackendLabel
|
||||
: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ projectId: string }> },
|
||||
) {
|
||||
const session = await requireRequestSession(request);
|
||||
if (!session) {
|
||||
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { projectId } = await context.params;
|
||||
const projectCheck = await readGroupProjectOrNotFound(projectId);
|
||||
if (!projectCheck.ok) {
|
||||
return projectCheck.response;
|
||||
}
|
||||
|
||||
const rawBody = await request.text().catch(() => "");
|
||||
let body: unknown;
|
||||
try {
|
||||
body = JSON.parse(rawBody);
|
||||
} catch {
|
||||
return NextResponse.json({ ok: false, message: "INVALID_JSON_PAYLOAD" }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
||||
return NextResponse.json({ ok: false, message: "INVALID_ORCHESTRATION_BACKEND_PAYLOAD" }, { status: 400 });
|
||||
}
|
||||
|
||||
const payload = body as {
|
||||
orchestrationBackendOverride?: unknown;
|
||||
backendId?: unknown;
|
||||
requestedBackendId?: unknown;
|
||||
};
|
||||
const hasOrchestrationBackendOverride = Object.prototype.hasOwnProperty.call(
|
||||
payload,
|
||||
"orchestrationBackendOverride",
|
||||
);
|
||||
const hasBackendId = Object.prototype.hasOwnProperty.call(payload, "backendId");
|
||||
const hasRequestedBackendId = Object.prototype.hasOwnProperty.call(payload, "requestedBackendId");
|
||||
if (!hasOrchestrationBackendOverride && !hasBackendId && !hasRequestedBackendId) {
|
||||
return NextResponse.json({ ok: false, message: "INVALID_ORCHESTRATION_BACKEND_PAYLOAD" }, { status: 400 });
|
||||
}
|
||||
|
||||
const requestedBackendId = normalizeRequestedBackendId(
|
||||
hasOrchestrationBackendOverride
|
||||
? payload.orchestrationBackendOverride
|
||||
: hasRequestedBackendId
|
||||
? payload.requestedBackendId
|
||||
: payload.backendId,
|
||||
);
|
||||
|
||||
try {
|
||||
await updateProjectOrchestrationBackend(projectId, requestedBackendId);
|
||||
const state = await getProjectOrchestrationBackendState(projectId);
|
||||
if (!state) {
|
||||
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true, ...state });
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
|
||||
{ status: error instanceof Error && error.message === "PROJECT_NOT_FOUND" ? 404 : 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,15 @@ import {
|
||||
MasterIdentityPill,
|
||||
PageNav,
|
||||
ProjectHeaderActions,
|
||||
ProjectOrchestrationBackendCard,
|
||||
StatusBar,
|
||||
} from "@/components/app-ui";
|
||||
import { requirePageSession } from "@/lib/boss-auth";
|
||||
import { listDispatchPlansByProject, readState } from "@/lib/boss-data";
|
||||
import {
|
||||
getProjectOrchestrationBackendState,
|
||||
listDispatchPlansByProject,
|
||||
readState,
|
||||
} from "@/lib/boss-data";
|
||||
import { resolveDispatchPlanComposerState } from "@/lib/dispatch-plan-ui";
|
||||
import { formatTimestampLabel, getProjectDetailView } from "@/lib/boss-projections";
|
||||
|
||||
@@ -30,6 +35,9 @@ export default async function ProjectChatPage({
|
||||
const dispatchPlanState = detail?.project.isGroup
|
||||
? resolveDispatchPlanComposerState(await listDispatchPlansByProject(projectId))
|
||||
: resolveDispatchPlanComposerState([]);
|
||||
const orchestrationBackendState = detail?.project.isGroup
|
||||
? await getProjectOrchestrationBackendState(projectId)
|
||||
: null;
|
||||
|
||||
if (!detail) notFound();
|
||||
|
||||
@@ -76,6 +84,14 @@ export default async function ProjectChatPage({
|
||||
<div className="pt-3">
|
||||
<ProjectHeaderActions projectId={detail.project.id} />
|
||||
</div>
|
||||
{detail.project.isGroup && orchestrationBackendState ? (
|
||||
<div className="mt-3">
|
||||
<ProjectOrchestrationBackendCard
|
||||
projectId={detail.project.id}
|
||||
initialState={orchestrationBackendState}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-4 space-y-3">
|
||||
<div className="rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
|
||||
<div className="text-[14px] font-semibold text-[#111111]">主 Agent 调度结论</div>
|
||||
|
||||
@@ -29,6 +29,8 @@ import type {
|
||||
OtaUpdateLog,
|
||||
OpsRepairTicket,
|
||||
OpsRepairVerification,
|
||||
ProjectOrchestrationBackendState,
|
||||
OrchestrationBackendId,
|
||||
ThreadContextSnapshot,
|
||||
UserProfile,
|
||||
UserSettings,
|
||||
@@ -781,6 +783,172 @@ export function ProjectHeaderActions({ projectId }: { projectId: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function orchestrationBackendChoiceLabel(choice: ProjectOrchestrationBackendState["availableChoices"][number]) {
|
||||
return choice.backendId === "boss-native-orchestrator"
|
||||
? "Boss Native Orchestrator"
|
||||
: "OMX Team Runtime";
|
||||
}
|
||||
|
||||
function normalizeOrchestrationReasonLabel(value: string) {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.endsWith("。") || trimmed.endsWith(".")) {
|
||||
return trimmed.slice(0, -1);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function orchestrationBackendAvailabilityCopy(
|
||||
state: ProjectOrchestrationBackendState,
|
||||
fallbackActive: boolean,
|
||||
) {
|
||||
if (state.omxAvailability.selectable) {
|
||||
return {
|
||||
badge: "正常",
|
||||
summary: "OMX Team Runtime 当前可用,当前可切换到该后端。",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
badge: fallbackActive ? "已回退" : "OMX 受限",
|
||||
summary: fallbackActive
|
||||
? `${normalizeOrchestrationReasonLabel(state.omxAvailability.reasonLabel)},当前已自动回退到 Boss Native Orchestrator。`
|
||||
: `${normalizeOrchestrationReasonLabel(state.omxAvailability.reasonLabel)},切换后会自动回退到 Boss Native Orchestrator。`,
|
||||
};
|
||||
}
|
||||
|
||||
export function ProjectOrchestrationBackendCard({
|
||||
projectId,
|
||||
initialState,
|
||||
}: {
|
||||
projectId: string;
|
||||
initialState: ProjectOrchestrationBackendState;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [state, setState] = useState(initialState);
|
||||
const [savingBackendId, setSavingBackendId] = useState<OrchestrationBackendId | null>(null);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const fallbackActive = state.requestedBackendId !== state.currentBackendId;
|
||||
const availabilityCopy = orchestrationBackendAvailabilityCopy(state, fallbackActive);
|
||||
|
||||
async function saveBackend(requestedBackendId: OrchestrationBackendId) {
|
||||
setSavingBackendId(requestedBackendId);
|
||||
const response = await fetch(`/api/v1/projects/${projectId}/orchestration-backend`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ requestedBackendId }),
|
||||
});
|
||||
const result = (await response.json()) as {
|
||||
ok: boolean;
|
||||
message?: string;
|
||||
currentBackendId?: OrchestrationBackendId;
|
||||
currentBackendLabel?: string;
|
||||
requestedBackendId?: OrchestrationBackendId;
|
||||
requestedBackendLabel?: string;
|
||||
availableChoices?: ProjectOrchestrationBackendState["availableChoices"];
|
||||
omxAvailability?: ProjectOrchestrationBackendState["omxAvailability"];
|
||||
};
|
||||
setSavingBackendId(null);
|
||||
if (
|
||||
!result.ok ||
|
||||
!result.currentBackendId ||
|
||||
!result.currentBackendLabel ||
|
||||
!result.requestedBackendId ||
|
||||
!result.requestedBackendLabel ||
|
||||
!result.availableChoices ||
|
||||
!result.omxAvailability
|
||||
) {
|
||||
setMessage(result.message ?? "保存失败");
|
||||
return;
|
||||
}
|
||||
setState({
|
||||
projectId,
|
||||
currentBackendId: result.currentBackendId,
|
||||
currentBackendLabel: result.currentBackendLabel,
|
||||
requestedBackendId: result.requestedBackendId,
|
||||
requestedBackendLabel: result.requestedBackendLabel,
|
||||
availableChoices: result.availableChoices,
|
||||
omxAvailability: result.omxAvailability,
|
||||
});
|
||||
setMessage(
|
||||
requestedBackendId === "omx-team"
|
||||
? "已切换到 OMX Team Runtime。"
|
||||
: "已切换回 Boss Native Orchestrator。",
|
||||
);
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-[#E5E5EA] bg-white px-4 py-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-[14px] font-semibold text-[#111111]">编排后端</div>
|
||||
<div className="mt-1 text-[12px] leading-5 text-[#57606A]">
|
||||
当前生效:{state.currentBackendLabel}
|
||||
<br />
|
||||
当前请求:{state.requestedBackendLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"rounded-full px-3 py-1 text-[11px] font-semibold",
|
||||
fallbackActive || !state.omxAvailability.selectable
|
||||
? "bg-[#FFF7E6] text-[#D46B08]"
|
||||
: "bg-[#EAF7F0] text-[#215B39]",
|
||||
)}
|
||||
>
|
||||
{availabilityCopy.badge}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-2">
|
||||
{state.availableChoices.map((choice) => {
|
||||
const active = choice.current;
|
||||
const selectable = choice.selectable && savingBackendId !== choice.backendId;
|
||||
return (
|
||||
<button
|
||||
key={choice.backendId}
|
||||
type="button"
|
||||
onClick={() => void saveBackend(choice.backendId)}
|
||||
disabled={!selectable}
|
||||
className={clsx(
|
||||
"flex items-center justify-between rounded-2xl border px-4 py-3 text-left",
|
||||
active ? "border-[#07C160] bg-[#F5FFF8]" : "border-[#E5E5EA] bg-[#F7F8FA]",
|
||||
!choice.selectable ? "opacity-70" : "",
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<div className="text-[14px] font-semibold text-[#111111]">
|
||||
{orchestrationBackendChoiceLabel(choice)}
|
||||
</div>
|
||||
<div className="mt-1 text-[12px] leading-5 text-[#57606A]">{choice.label}</div>
|
||||
</div>
|
||||
<div className="text-right text-[11px] text-[#8C8C8C]">
|
||||
<div>{active ? "当前" : "切换"}</div>
|
||||
{!choice.selectable ? <div>不可选</div> : null}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"mt-3 rounded-2xl px-4 py-3 text-[12px] leading-6",
|
||||
state.omxAvailability.selectable
|
||||
? "bg-[#EAF7F0] text-[#215B39]"
|
||||
: "bg-[#FFF7E6] text-[#8D5D00]",
|
||||
)}
|
||||
>
|
||||
{availabilityCopy.summary}
|
||||
</div>
|
||||
{message ? (
|
||||
<div className="mt-3 rounded-2xl bg-[#F7F8FA] px-4 py-3 text-[12px] text-[#57606A]">
|
||||
{message}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function masterIdentityPillClasses(role: MasterIdentitySummary["role"]) {
|
||||
switch (role) {
|
||||
case "primary":
|
||||
|
||||
@@ -6,6 +6,13 @@ import { publishBossEvent } from "@/lib/boss-events";
|
||||
import type { VerificationDeliveryMode } from "@/lib/boss-mail";
|
||||
import { getFixedVerificationCode, getVerificationDeliveryMode } from "@/lib/boss-mail";
|
||||
import { getPublishedOtaAsset } from "@/lib/boss-ota";
|
||||
import { BOSS_NATIVE_ORCHESTRATOR } from "@/lib/execution/backends/boss-native-orchestrator";
|
||||
import {
|
||||
OMX_TEAM_BACKEND,
|
||||
getOmxTeamBackendSelectionState,
|
||||
type OmxTeamBackendSelectionState,
|
||||
} from "@/lib/execution/backends/omx-team-backend";
|
||||
import { selectOrchestrationBackend } from "@/lib/execution/orchestration-backend-selector";
|
||||
|
||||
export type DeviceStatus = "online" | "abnormal" | "offline";
|
||||
export type DeviceSource = "production" | "demo";
|
||||
@@ -144,6 +151,8 @@ export type DispatchPlanStatus =
|
||||
| "dispatched";
|
||||
export type DispatchExecutionStatus = "queued" | "running" | "completed" | "failed";
|
||||
export type ReasoningEffort = "low" | "medium" | "high";
|
||||
export type OrchestrationBackendId = import("@/lib/execution/orchestration-backend").OrchestrationBackendId;
|
||||
export type OrchestrationBackendOverride = "omx-team";
|
||||
|
||||
export interface UserSettings {
|
||||
liveUpdates: boolean;
|
||||
@@ -305,6 +314,7 @@ export interface Project {
|
||||
createdByAgent: boolean;
|
||||
collaborationMode: "development" | "approval_required";
|
||||
approvalState: "not_required" | "pending_agent" | "pending_user" | "approved" | "rejected";
|
||||
orchestrationBackendOverride?: OrchestrationBackendOverride;
|
||||
agentControls?: ProjectAgentControls;
|
||||
unreadCount: number;
|
||||
riskLevel: RiskLevel;
|
||||
@@ -334,6 +344,10 @@ export interface DispatchPlan {
|
||||
status: DispatchPlanStatus;
|
||||
targets: DispatchPlanTarget[];
|
||||
summary: string;
|
||||
requestedOrchestrationBackendId?: OrchestrationBackendOverride;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
orchestrationFallbackReason?: string;
|
||||
createdAt: string;
|
||||
confirmedAt?: string;
|
||||
confirmedBy?: string;
|
||||
@@ -347,6 +361,8 @@ export interface DispatchExecution {
|
||||
targetProjectId: string;
|
||||
targetThreadId: string;
|
||||
deviceId: string;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
status: DispatchExecutionStatus;
|
||||
createdAt: string;
|
||||
completedAt?: string;
|
||||
@@ -382,6 +398,23 @@ export interface ProjectAgentControls {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ProjectOrchestrationBackendChoice {
|
||||
backendId: OrchestrationBackendId;
|
||||
label: string;
|
||||
selectable: boolean;
|
||||
current: boolean;
|
||||
}
|
||||
|
||||
export interface ProjectOrchestrationBackendState {
|
||||
projectId: string;
|
||||
requestedBackendId: OrchestrationBackendId;
|
||||
currentBackendId: OrchestrationBackendId;
|
||||
currentBackendLabel: string;
|
||||
requestedBackendLabel: string;
|
||||
availableChoices: ProjectOrchestrationBackendChoice[];
|
||||
omxAvailability: OmxTeamBackendSelectionState["availability"];
|
||||
}
|
||||
|
||||
export interface UserProjectAgentControls {
|
||||
account: string;
|
||||
projectId: string;
|
||||
@@ -607,6 +640,8 @@ export interface MasterAgentTask {
|
||||
targetThreadDisplayName?: string;
|
||||
targetCodexThreadRef?: string;
|
||||
targetCodexFolderRef?: string;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
deviceImportDraftId?: string;
|
||||
status: MasterAgentTaskStatus;
|
||||
requestedAt: string;
|
||||
@@ -1726,6 +1761,12 @@ function parseBackendOverride(value: unknown) {
|
||||
return { kind: "set" as const, value: "claw-runtime" as const };
|
||||
}
|
||||
|
||||
function normalizeOrchestrationBackendOverride(
|
||||
value: unknown,
|
||||
): OrchestrationBackendOverride | undefined {
|
||||
return value === "omx-team" ? "omx-team" : undefined;
|
||||
}
|
||||
|
||||
function normalizeStringSet(values: string[]) {
|
||||
return dedupeStrings(values.map((value) => value.trim()).filter(Boolean)).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
@@ -1913,6 +1954,20 @@ function normalizeDispatchPlan(raw: Partial<DispatchPlan>, fallback?: DispatchPl
|
||||
status: raw.status ?? fallback?.status ?? "pending_user_confirmation",
|
||||
targets,
|
||||
summary: raw.summary ?? fallback?.summary ?? "",
|
||||
requestedOrchestrationBackendId: normalizeOrchestrationBackendOverride(
|
||||
raw.requestedOrchestrationBackendId ?? fallback?.requestedOrchestrationBackendId,
|
||||
),
|
||||
orchestrationBackendId:
|
||||
raw.orchestrationBackendId === "omx-team" || raw.orchestrationBackendId === "boss-native-orchestrator"
|
||||
? raw.orchestrationBackendId
|
||||
: fallback?.orchestrationBackendId ?? "boss-native-orchestrator",
|
||||
orchestrationBackendLabel:
|
||||
trimToDefined(raw.orchestrationBackendLabel) ??
|
||||
trimToDefined(fallback?.orchestrationBackendLabel) ??
|
||||
(raw.orchestrationBackendId === "omx-team" ? "OMX Team Runtime" : "Boss Native Orchestrator"),
|
||||
orchestrationFallbackReason:
|
||||
trimToDefined(raw.orchestrationFallbackReason) ??
|
||||
trimToDefined(fallback?.orchestrationFallbackReason),
|
||||
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
|
||||
confirmedAt: raw.confirmedAt ?? fallback?.confirmedAt,
|
||||
confirmedBy: raw.confirmedBy ?? fallback?.confirmedBy,
|
||||
@@ -1936,6 +1991,14 @@ function normalizeDispatchExecution(
|
||||
targetProjectId: raw.targetProjectId ?? fallback?.targetProjectId ?? "",
|
||||
targetThreadId: raw.targetThreadId ?? fallback?.targetThreadId ?? "",
|
||||
deviceId: raw.deviceId ?? fallback?.deviceId ?? "",
|
||||
orchestrationBackendId:
|
||||
raw.orchestrationBackendId === "omx-team" || raw.orchestrationBackendId === "boss-native-orchestrator"
|
||||
? raw.orchestrationBackendId
|
||||
: fallback?.orchestrationBackendId ?? "boss-native-orchestrator",
|
||||
orchestrationBackendLabel:
|
||||
trimToDefined(raw.orchestrationBackendLabel) ??
|
||||
trimToDefined(fallback?.orchestrationBackendLabel) ??
|
||||
(raw.orchestrationBackendId === "omx-team" ? "OMX Team Runtime" : "Boss Native Orchestrator"),
|
||||
status: raw.status ?? fallback?.status ?? "queued",
|
||||
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
|
||||
completedAt: raw.completedAt ?? fallback?.completedAt,
|
||||
@@ -2714,6 +2777,7 @@ function normalizeProject(raw: Partial<Project>, fallback?: Project): Project {
|
||||
createdByAgent: raw.createdByAgent ?? false,
|
||||
collaborationMode: raw.collaborationMode ?? "development",
|
||||
approvalState: raw.approvalState ?? "not_required",
|
||||
orchestrationBackendOverride: normalizeOrchestrationBackendOverride(raw.orchestrationBackendOverride),
|
||||
agentControls: normalizeProjectAgentControls(raw.agentControls),
|
||||
};
|
||||
project.groupMembers = ensureArray(raw.groupMembers, []).map((member) =>
|
||||
@@ -2846,6 +2910,11 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
|
||||
targetProjectId: task.targetProjectId,
|
||||
targetThreadId: task.targetThreadId,
|
||||
targetThreadDisplayName: task.targetThreadDisplayName,
|
||||
orchestrationBackendId:
|
||||
task.orchestrationBackendId === "omx-team" || task.orchestrationBackendId === "boss-native-orchestrator"
|
||||
? task.orchestrationBackendId
|
||||
: undefined,
|
||||
orchestrationBackendLabel: task.orchestrationBackendLabel,
|
||||
deviceImportDraftId: task.deviceImportDraftId,
|
||||
status: task.status ?? "queued",
|
||||
requestedAt: task.requestedAt ?? nowIso(),
|
||||
@@ -3584,6 +3653,33 @@ export async function getProject(projectId: string) {
|
||||
return state.projects.find((project) => project.id === projectId) ?? null;
|
||||
}
|
||||
|
||||
export async function updateProjectOrchestrationBackendOverride(input: {
|
||||
projectId: string;
|
||||
requestedBy: string;
|
||||
orchestrationBackendOverride?: OrchestrationBackendOverride;
|
||||
}) {
|
||||
return mutateState((state) => {
|
||||
const project = state.projects.find((item) => item.id === input.projectId);
|
||||
if (!project) {
|
||||
throw new Error("PROJECT_NOT_FOUND");
|
||||
}
|
||||
if (!project.isGroup) {
|
||||
throw new Error("PROJECT_NOT_GROUP_CHAT");
|
||||
}
|
||||
requireDispatchActorSession(state, input.requestedBy);
|
||||
|
||||
const nextOverride = input.orchestrationBackendOverride;
|
||||
if (project.orchestrationBackendOverride === nextOverride) {
|
||||
return project;
|
||||
}
|
||||
|
||||
project.orchestrationBackendOverride = nextOverride;
|
||||
project.updatedAt = nowIso();
|
||||
project.threadMeta.updatedAt = project.updatedAt;
|
||||
return project;
|
||||
});
|
||||
}
|
||||
|
||||
export async function hasPersistedProject(projectId: string) {
|
||||
const rawState = await loadPersistedStateRaw();
|
||||
return Array.isArray(rawState.projects) && rawState.projects.some((project) => project?.id === projectId);
|
||||
@@ -3731,6 +3827,87 @@ export async function updateProjectAgentControls(
|
||||
});
|
||||
}
|
||||
|
||||
function projectOrchestrationRequestedBackendId(project: Project): OrchestrationBackendId {
|
||||
return project.orchestrationBackendOverride ?? "boss-native-orchestrator";
|
||||
}
|
||||
|
||||
async function buildProjectOrchestrationBackendState(
|
||||
project: Project,
|
||||
): Promise<ProjectOrchestrationBackendState> {
|
||||
const requestedBackendId = projectOrchestrationRequestedBackendId(project);
|
||||
const omxSelection = await getOmxTeamBackendSelectionState();
|
||||
const currentBackend = await selectOrchestrationBackend({
|
||||
requestedBackendId,
|
||||
omx: omxSelection,
|
||||
});
|
||||
const nativeBackend = await BOSS_NATIVE_ORCHESTRATOR.describe();
|
||||
const omxBackend = await OMX_TEAM_BACKEND.describe();
|
||||
const availableChoices: ProjectOrchestrationBackendChoice[] = [
|
||||
{
|
||||
backendId: nativeBackend.backendId as OrchestrationBackendId,
|
||||
label: nativeBackend.label,
|
||||
selectable: true,
|
||||
current: currentBackend.backendId === nativeBackend.backendId,
|
||||
},
|
||||
{
|
||||
backendId: omxBackend.backendId as OrchestrationBackendId,
|
||||
label: omxBackend.label,
|
||||
selectable: omxSelection.selectable,
|
||||
current: currentBackend.backendId === omxBackend.backendId,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
projectId: project.id,
|
||||
requestedBackendId,
|
||||
currentBackendId: currentBackend.backendId as OrchestrationBackendId,
|
||||
currentBackendLabel:
|
||||
availableChoices.find((choice) => choice.backendId === currentBackend.backendId)?.label ??
|
||||
nativeBackend.label,
|
||||
requestedBackendLabel:
|
||||
availableChoices.find((choice) => choice.backendId === requestedBackendId)?.label ??
|
||||
nativeBackend.label,
|
||||
availableChoices,
|
||||
omxAvailability: omxSelection.availability,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getProjectOrchestrationBackendState(
|
||||
projectId: string,
|
||||
): Promise<ProjectOrchestrationBackendState | null> {
|
||||
const state = await readState();
|
||||
const project = state.projects.find((item) => item.id === projectId);
|
||||
if (!project) {
|
||||
return null;
|
||||
}
|
||||
return buildProjectOrchestrationBackendState(project);
|
||||
}
|
||||
|
||||
export async function updateProjectOrchestrationBackend(
|
||||
projectId: string,
|
||||
requestedBackendId: OrchestrationBackendId,
|
||||
) {
|
||||
return mutateStateIfChanged(async (state) => {
|
||||
const project = state.projects.find((item) => item.id === projectId);
|
||||
if (!project) {
|
||||
throw new Error("PROJECT_NOT_FOUND");
|
||||
}
|
||||
|
||||
const nextOverride =
|
||||
requestedBackendId === "boss-native-orchestrator" ? undefined : "omx-team";
|
||||
const currentRequestedBackendId = projectOrchestrationRequestedBackendId(project);
|
||||
if (currentRequestedBackendId === requestedBackendId && project.orchestrationBackendOverride === nextOverride) {
|
||||
return { result: project.orchestrationBackendOverride ?? null, changed: false };
|
||||
}
|
||||
|
||||
project.orchestrationBackendOverride = nextOverride;
|
||||
const updatedAt = nowIso();
|
||||
project.updatedAt = updatedAt;
|
||||
project.threadMeta.updatedAt = updatedAt;
|
||||
return { result: project.orchestrationBackendOverride ?? null, changed: true };
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDevice(deviceId: string) {
|
||||
const state = await readState();
|
||||
return state.devices.find((device) => device.id === deviceId) ?? null;
|
||||
@@ -4914,6 +5091,8 @@ export async function queueMasterAgentTask(payload: {
|
||||
targetThreadDisplayName?: string;
|
||||
targetCodexThreadRef?: string;
|
||||
targetCodexFolderRef?: string;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
}) {
|
||||
const task = await mutateState((state) => {
|
||||
const task: MasterAgentTask = {
|
||||
@@ -4941,6 +5120,8 @@ export async function queueMasterAgentTask(payload: {
|
||||
targetThreadDisplayName: payload.targetThreadDisplayName,
|
||||
targetCodexThreadRef: payload.targetCodexThreadRef,
|
||||
targetCodexFolderRef: payload.targetCodexFolderRef,
|
||||
orchestrationBackendId: payload.orchestrationBackendId,
|
||||
orchestrationBackendLabel: payload.orchestrationBackendLabel,
|
||||
status: "queued",
|
||||
requestedAt: nowIso(),
|
||||
};
|
||||
@@ -4961,6 +5142,10 @@ export async function createDispatchPlan(input: {
|
||||
requestedBy: string;
|
||||
summary?: string;
|
||||
targets: DispatchPlanTarget[];
|
||||
requestedOrchestrationBackendId?: OrchestrationBackendOverride;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
orchestrationFallbackReason?: string;
|
||||
}) {
|
||||
return mutateState((state) => {
|
||||
return upsertDispatchPlanInState(state, input);
|
||||
@@ -4975,12 +5160,22 @@ function upsertDispatchPlanInState(
|
||||
requestedBy: string;
|
||||
summary?: string;
|
||||
targets: DispatchPlanTarget[];
|
||||
requestedOrchestrationBackendId?: OrchestrationBackendOverride;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
orchestrationFallbackReason?: string;
|
||||
},
|
||||
) {
|
||||
const groupProjectId = input.groupProjectId.trim();
|
||||
const requestMessageId = input.requestMessageId.trim();
|
||||
const requestedBy = input.requestedBy.trim();
|
||||
const summary = input.summary?.trim() ?? "";
|
||||
const requestedOrchestrationBackendId = input.requestedOrchestrationBackendId;
|
||||
const orchestrationBackendId = input.orchestrationBackendId ?? "boss-native-orchestrator";
|
||||
const orchestrationBackendLabel =
|
||||
trimToDefined(input.orchestrationBackendLabel) ??
|
||||
(orchestrationBackendId === "omx-team" ? "OMX Team Runtime" : "Boss Native Orchestrator");
|
||||
const orchestrationFallbackReason = trimToDefined(input.orchestrationFallbackReason);
|
||||
|
||||
if (!groupProjectId) throw new Error("DISPATCH_PLAN_GROUP_PROJECT_REQUIRED");
|
||||
if (!requestMessageId) throw new Error("DISPATCH_PLAN_REQUEST_MESSAGE_REQUIRED");
|
||||
@@ -4997,7 +5192,11 @@ function upsertDispatchPlanInState(
|
||||
const payloadMatches =
|
||||
existing.requestedBy === requestedBy &&
|
||||
existing.summary === summary &&
|
||||
sameDispatchPlanTargets(existing.targets, validatedTargets);
|
||||
sameDispatchPlanTargets(existing.targets, validatedTargets) &&
|
||||
existing.requestedOrchestrationBackendId === requestedOrchestrationBackendId &&
|
||||
existing.orchestrationBackendId === orchestrationBackendId &&
|
||||
existing.orchestrationBackendLabel === orchestrationBackendLabel &&
|
||||
existing.orchestrationFallbackReason === orchestrationFallbackReason;
|
||||
if (!payloadMatches) {
|
||||
throw new Error("DISPATCH_PLAN_RETRY_MISMATCH");
|
||||
}
|
||||
@@ -5015,6 +5214,10 @@ function upsertDispatchPlanInState(
|
||||
status: "pending_user_confirmation",
|
||||
targets: validatedTargets,
|
||||
summary,
|
||||
requestedOrchestrationBackendId,
|
||||
orchestrationBackendId,
|
||||
orchestrationBackendLabel,
|
||||
orchestrationFallbackReason,
|
||||
createdAt: nowIso(),
|
||||
};
|
||||
state.dispatchPlans.unshift(plan);
|
||||
@@ -5168,6 +5371,10 @@ export async function createDispatchExecutionsFromPlan(input: {
|
||||
if (!sameStringSet(existingTargetIds, canonicalTargetProjectIds)) {
|
||||
throw new Error("DISPATCH_EXECUTION_SET_MISMATCH");
|
||||
}
|
||||
for (const execution of existingExecutions) {
|
||||
execution.orchestrationBackendId = execution.orchestrationBackendId ?? plan.orchestrationBackendId;
|
||||
execution.orchestrationBackendLabel = execution.orchestrationBackendLabel ?? plan.orchestrationBackendLabel;
|
||||
}
|
||||
if (plan.status !== "dispatched") {
|
||||
plan.status = "dispatched";
|
||||
}
|
||||
@@ -5192,6 +5399,8 @@ export async function createDispatchExecutionsFromPlan(input: {
|
||||
targetProjectId: target.projectId,
|
||||
targetThreadId: target.threadId,
|
||||
deviceId: target.deviceId,
|
||||
orchestrationBackendId: plan.orchestrationBackendId,
|
||||
orchestrationBackendLabel: plan.orchestrationBackendLabel,
|
||||
status: "queued",
|
||||
createdAt,
|
||||
};
|
||||
@@ -5267,6 +5476,8 @@ function ensureDispatchExecutionTaskInState(
|
||||
existing.targetThreadDisplayName = existing.targetThreadDisplayName ?? target.threadDisplayName;
|
||||
existing.targetCodexThreadRef = existing.targetCodexThreadRef ?? target.codexThreadRef;
|
||||
existing.targetCodexFolderRef = existing.targetCodexFolderRef ?? target.codexFolderRef;
|
||||
existing.orchestrationBackendId = existing.orchestrationBackendId ?? execution.orchestrationBackendId;
|
||||
existing.orchestrationBackendLabel = existing.orchestrationBackendLabel ?? execution.orchestrationBackendLabel;
|
||||
existing.executionPrompt =
|
||||
existing.executionPrompt ||
|
||||
buildDispatchExecutionPrompt({
|
||||
@@ -5299,6 +5510,8 @@ function ensureDispatchExecutionTaskInState(
|
||||
targetThreadDisplayName: target.threadDisplayName,
|
||||
targetCodexThreadRef: target.codexThreadRef,
|
||||
targetCodexFolderRef: target.codexFolderRef,
|
||||
orchestrationBackendId: execution.orchestrationBackendId,
|
||||
orchestrationBackendLabel: execution.orchestrationBackendLabel,
|
||||
status: "queued",
|
||||
requestedAt: nowIso(),
|
||||
};
|
||||
@@ -5346,6 +5559,10 @@ export async function confirmDispatchPlanAndCreateExecutions(input: {
|
||||
if (!sameStringSet(existingTargetIds, canonicalTargetProjectIds)) {
|
||||
throw new Error("DISPATCH_EXECUTION_SET_MISMATCH");
|
||||
}
|
||||
for (const execution of existingExecutions) {
|
||||
execution.orchestrationBackendId = execution.orchestrationBackendId ?? plan.orchestrationBackendId;
|
||||
execution.orchestrationBackendLabel = execution.orchestrationBackendLabel ?? plan.orchestrationBackendLabel;
|
||||
}
|
||||
if (plan.status !== "dispatched") {
|
||||
plan.status = "dispatched";
|
||||
}
|
||||
@@ -5368,6 +5585,8 @@ export async function confirmDispatchPlanAndCreateExecutions(input: {
|
||||
targetProjectId: target.projectId,
|
||||
targetThreadId: target.threadId,
|
||||
deviceId: target.deviceId,
|
||||
orchestrationBackendId: plan.orchestrationBackendId,
|
||||
orchestrationBackendLabel: plan.orchestrationBackendLabel,
|
||||
status: "queued",
|
||||
createdAt,
|
||||
};
|
||||
@@ -5684,6 +5903,10 @@ export async function completeMasterAgentTask(payload: {
|
||||
dispatchPlan?: {
|
||||
summary?: string;
|
||||
targets: DispatchPlanTarget[];
|
||||
requestedOrchestrationBackendId?: OrchestrationBackendOverride;
|
||||
orchestrationBackendId?: OrchestrationBackendId;
|
||||
orchestrationBackendLabel?: string;
|
||||
orchestrationFallbackReason?: string;
|
||||
};
|
||||
}) {
|
||||
const result = await mutateState((state) => {
|
||||
@@ -5776,6 +5999,10 @@ export async function completeMasterAgentTask(payload: {
|
||||
requestedBy: task.requestedByAccount,
|
||||
summary: payload.dispatchPlan.summary,
|
||||
targets: payload.dispatchPlan.targets,
|
||||
requestedOrchestrationBackendId: payload.dispatchPlan.requestedOrchestrationBackendId,
|
||||
orchestrationBackendId: payload.dispatchPlan.orchestrationBackendId,
|
||||
orchestrationBackendLabel: payload.dispatchPlan.orchestrationBackendLabel,
|
||||
orchestrationFallbackReason: payload.dispatchPlan.orchestrationFallbackReason,
|
||||
});
|
||||
}
|
||||
} else if (task.taskType === "device_import_resolution") {
|
||||
|
||||
@@ -34,7 +34,9 @@ import {
|
||||
createClawBackend,
|
||||
getClawBackendSelectionState,
|
||||
} from "@/lib/execution/backends/claw-backend";
|
||||
import { getOmxTeamBackendSelectionState } from "@/lib/execution/backends/omx-team-backend";
|
||||
import { listExecutionBackendChoices, selectExecutionBackend } from "@/lib/execution/backend-selector";
|
||||
import { selectOrchestrationBackend } from "@/lib/execution/orchestration-backend-selector";
|
||||
import { resolveRuntimeRelevantMemories } from "@/lib/execution/memory-resolver";
|
||||
import type { RelevantMemory } from "@/lib/execution/memory-resolver";
|
||||
import { buildExecutionPrompt } from "@/lib/execution/prompt-assembler";
|
||||
@@ -1223,6 +1225,25 @@ type GroupDispatchRecommendationResult =
|
||||
error: string;
|
||||
};
|
||||
|
||||
async function resolveGroupOrchestrationBackend(project: Project) {
|
||||
const requestedBackendId = project.orchestrationBackendOverride;
|
||||
const omx = await getOmxTeamBackendSelectionState();
|
||||
const selectedBackend = await selectOrchestrationBackend({
|
||||
requestedBackendId,
|
||||
omx,
|
||||
});
|
||||
const description = await selectedBackend.describe();
|
||||
return {
|
||||
requestedBackendId,
|
||||
orchestrationBackendId: description.backendId,
|
||||
orchestrationBackendLabel: description.label,
|
||||
orchestrationFallbackReason:
|
||||
requestedBackendId === "omx-team" && description.backendId !== "omx-team"
|
||||
? omx.availability.reasonLabel
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveGroupDispatchPlanTask(taskId: string): Promise<GroupDispatchRecommendationResult> {
|
||||
const task = await getMasterAgentTask(taskId);
|
||||
if (!task) {
|
||||
@@ -1246,6 +1267,7 @@ async function resolveGroupDispatchPlanTask(taskId: string): Promise<GroupDispat
|
||||
if (targets.length === 0) {
|
||||
throw new Error("GROUP_DISPATCH_TARGETS_REQUIRED");
|
||||
}
|
||||
const orchestrationBackend = await resolveGroupOrchestrationBackend(project);
|
||||
|
||||
const completedTask = await completeMasterAgentTask({
|
||||
taskId: task.taskId,
|
||||
@@ -1254,6 +1276,10 @@ async function resolveGroupDispatchPlanTask(taskId: string): Promise<GroupDispat
|
||||
dispatchPlan: {
|
||||
summary: summarizeGroupDispatchPlan(task.requestText, targets),
|
||||
targets,
|
||||
requestedOrchestrationBackendId: orchestrationBackend.requestedBackendId,
|
||||
orchestrationBackendId: orchestrationBackend.orchestrationBackendId,
|
||||
orchestrationBackendLabel: orchestrationBackend.orchestrationBackendLabel,
|
||||
orchestrationFallbackReason: orchestrationBackend.orchestrationFallbackReason,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1296,6 +1322,8 @@ export async function queueGroupDispatchPlan(params: {
|
||||
throw new Error("PROJECT_NOT_GROUP_CHAT");
|
||||
}
|
||||
|
||||
const orchestrationBackend = await resolveGroupOrchestrationBackend(project);
|
||||
|
||||
const task = await queueMasterAgentTask({
|
||||
projectId: project.id,
|
||||
taskType: "group_dispatch_plan",
|
||||
@@ -1305,6 +1333,8 @@ export async function queueGroupDispatchPlan(params: {
|
||||
requestedBy: params.requestedBy,
|
||||
requestedByAccount: params.requestedBy,
|
||||
deviceId: state.user.boundDeviceId || "mac-studio",
|
||||
orchestrationBackendId: orchestrationBackend.orchestrationBackendId,
|
||||
orchestrationBackendLabel: orchestrationBackend.orchestrationBackendLabel,
|
||||
});
|
||||
|
||||
return resolveGroupDispatchPlanTask(task.taskId);
|
||||
|
||||
@@ -3,7 +3,13 @@ import {
|
||||
OMX_TEAM_BACKEND,
|
||||
type OmxTeamBackendSelectionState,
|
||||
} from "@/lib/execution/backends/omx-team-backend";
|
||||
import type { OrchestrationBackend } from "@/lib/execution/orchestration-backend";
|
||||
import {
|
||||
labelForOrchestrationBackend,
|
||||
normalizeOrchestrationBackendId,
|
||||
type OrchestrationBackend,
|
||||
type OrchestrationBackendChoiceView,
|
||||
type OrchestrationBackendSelectionState,
|
||||
} from "@/lib/execution/orchestration-backend";
|
||||
|
||||
export interface OrchestrationBackendSelectionInput {
|
||||
requestedBackendId?: string;
|
||||
@@ -24,10 +30,27 @@ function isReadyBackend(
|
||||
return true;
|
||||
}
|
||||
|
||||
function getRequestedBackendId(input: OrchestrationBackendSelectionInput) {
|
||||
return normalizeOrchestrationBackendId(input.requestedBackendId);
|
||||
}
|
||||
|
||||
function isSelectableBackend(
|
||||
backendId: string,
|
||||
input: OrchestrationBackendSelectionInput,
|
||||
) {
|
||||
if (backendId === OMX_TEAM_BACKEND.backendId) {
|
||||
return input.omx?.selectable ?? false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function selectOrchestrationBackend(
|
||||
input: OrchestrationBackendSelectionInput = {},
|
||||
): Promise<OrchestrationBackendChoice> {
|
||||
return (await listOrchestrationBackendChoices(input))[0] ?? BOSS_NATIVE_ORCHESTRATOR;
|
||||
const resolution = await resolveOrchestrationBackendSelection(input);
|
||||
return resolution.currentBackendId === OMX_TEAM_BACKEND.backendId
|
||||
? OMX_TEAM_BACKEND
|
||||
: BOSS_NATIVE_ORCHESTRATOR;
|
||||
}
|
||||
|
||||
export async function listOrchestrationBackendChoices(
|
||||
@@ -60,4 +83,45 @@ export async function listOrchestrationBackendChoices(
|
||||
return ordered;
|
||||
}
|
||||
|
||||
export async function listOrchestrationBackendChoiceViews(
|
||||
input: OrchestrationBackendSelectionInput = {},
|
||||
): Promise<OrchestrationBackendChoiceView[]> {
|
||||
const requestedBackendId = getRequestedBackendId(input);
|
||||
const omxSelectable = isSelectableBackend(OMX_TEAM_BACKEND.backendId, input);
|
||||
const choices = await listOrchestrationBackendChoices(input);
|
||||
return choices.map((backend) => ({
|
||||
backendId: backend.backendId,
|
||||
label: labelForOrchestrationBackend(backend.backendId),
|
||||
selectable: backend.backendId === OMX_TEAM_BACKEND.backendId ? omxSelectable : true,
|
||||
current: backend.backendId === requestedBackendId && (!omxSelectable || backend.backendId === requestedBackendId)
|
||||
? true
|
||||
: backend.backendId === BOSS_NATIVE_ORCHESTRATOR.backendId && requestedBackendId !== OMX_TEAM_BACKEND.backendId,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function resolveOrchestrationBackendSelection(
|
||||
input: OrchestrationBackendSelectionInput = {},
|
||||
): Promise<OrchestrationBackendSelectionState> {
|
||||
const requestedBackendId = getRequestedBackendId(input);
|
||||
const omxAvailability = input.omx?.availability;
|
||||
const omxSelectable = input.omx?.selectable ?? false;
|
||||
const omxRequested = requestedBackendId === OMX_TEAM_BACKEND.backendId;
|
||||
const currentBackendId =
|
||||
omxRequested && omxSelectable ? OMX_TEAM_BACKEND.backendId : BOSS_NATIVE_ORCHESTRATOR.backendId;
|
||||
return {
|
||||
requestedBackendId,
|
||||
currentBackendId,
|
||||
resolvedAt: new Date().toISOString(),
|
||||
...(omxAvailability ? { omxAvailability } : {}),
|
||||
...(omxRequested && !omxSelectable
|
||||
? {
|
||||
fallbackReason: "omx-team unavailable",
|
||||
fallbackReasonLabel:
|
||||
omxAvailability?.reasonLabel ??
|
||||
"OMX Team Runtime 当前不可用,已自动回退到 Boss Native Orchestrator。",
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
export const selectOrchestrationBackendForTesting = selectOrchestrationBackend;
|
||||
|
||||
@@ -1,4 +1,34 @@
|
||||
import type { OmxTeamBackendAvailability } from "@/lib/execution/backends/omx-team-config";
|
||||
|
||||
export type OrchestrationBackendId = "boss-native-orchestrator" | "omx-team";
|
||||
|
||||
export interface OrchestrationBackend {
|
||||
backendId: string;
|
||||
describe(): Promise<{ backendId: string; label: string }>;
|
||||
backendId: OrchestrationBackendId;
|
||||
describe(): Promise<{ backendId: OrchestrationBackendId; label: string }>;
|
||||
}
|
||||
|
||||
export interface OrchestrationBackendChoiceView {
|
||||
backendId: OrchestrationBackendId;
|
||||
label: string;
|
||||
selectable: boolean;
|
||||
current: boolean;
|
||||
}
|
||||
|
||||
export interface OrchestrationBackendSelectionState {
|
||||
requestedBackendId: OrchestrationBackendId;
|
||||
currentBackendId: OrchestrationBackendId;
|
||||
resolvedAt: string;
|
||||
fallbackReason?: string;
|
||||
fallbackReasonLabel?: string;
|
||||
omxAvailability?: OmxTeamBackendAvailability;
|
||||
}
|
||||
|
||||
export function normalizeOrchestrationBackendId(
|
||||
backendId?: string | null,
|
||||
): OrchestrationBackendId {
|
||||
return backendId?.trim() === "omx-team" ? "omx-team" : "boss-native-orchestrator";
|
||||
}
|
||||
|
||||
export function labelForOrchestrationBackend(backendId: OrchestrationBackendId) {
|
||||
return backendId === "omx-team" ? "OMX Team Runtime" : "Boss Native Orchestrator";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user