Commit 48d00eba authored by AI-甘富林's avatar AI-甘富林

fix(desktop): route workspace handoff to chat fallback

parent bfda0c3a
...@@ -1525,6 +1525,26 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -1525,6 +1525,26 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
attachments: preparedExecution.attachments, attachments: preparedExecution.attachments,
extraEnv: projectModelEnv extraEnv: projectModelEnv
}); });
if ("handoff" in result) {
const fallbackExecutionPolicy = await resolveExecutionPolicy(
preparedExecution.sessionState.projectId,
undefined,
"chat-fallback"
);
const fallbackResult = await runGatewayChatRequestWithRecovery(chatGatewayRecoveryCoordinator, {
reason: "chat-send",
execute: () => gatewayClient.sendPrompt(executionSessionId, result.handoff.content)
});
await projectStore.appendSessionMessage(fallbackResult.sessionId, fallbackResult.reply);
await projectStore.updateSessionLastActive(fallbackResult.sessionId).catch(() => undefined);
runtimeCloudSupervisor.noteMessageSent(
fallbackResult.sessionId,
fallbackResult.reply.content,
fallbackExecutionPolicy.modelId,
executionSkillId
);
return { ...fallbackResult, executionPolicy: fallbackExecutionPolicy };
}
await projectStore.appendSessionMessage(executionSessionId, result.reply); await projectStore.appendSessionMessage(executionSessionId, result.reply);
await projectStore.updateSessionLastActive(executionSessionId).catch(() => undefined); await projectStore.updateSessionLastActive(executionSessionId).catch(() => undefined);
runtimeCloudSupervisor.noteMessageSent(executionSessionId, result.reply.content, preparedExecution.executionPolicy.modelId, executionSkillId); runtimeCloudSupervisor.noteMessageSent(executionSessionId, result.reply.content, preparedExecution.executionPolicy.modelId, executionSkillId);
...@@ -1750,6 +1770,133 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -1750,6 +1770,133 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
}); });
} }
}); });
if ("handoff" in result) {
executionPolicy = await resolveExecutionPolicy(
preparedExecution.sessionState.projectId,
undefined,
"chat-fallback"
);
await updateAssistantTranscript((current) => ({
...current,
streamState: "streaming",
statusLabel: "Routing to chat model",
statusDetail: undefined
}));
queueOrSend({
type: "status",
requestId,
sessionId: executionSessionId,
runId: result.runId,
stage: "chat-fallback",
label: "Routing to chat model"
});
await runGatewayChatRequestWithRecovery(chatGatewayRecoveryCoordinator, {
reason: "chat-stream",
execute: () => gatewayClient.streamPrompt(executionSessionId, result.handoff.content, {
onStarted: ({ sessionId: nextSessionId, runId }) => {
executionSessionId = nextSessionId;
queueOrSend({
type: "started",
requestId,
sessionId: nextSessionId,
runId,
executionPolicy: executionPolicy ?? undefined
});
},
onStatus: ({ sessionId: nextSessionId, runId, stage, label, detail }) => {
executionSessionId = nextSessionId;
void updateAssistantTranscript((current) => ({
...current,
streamState: "streaming",
statusLabel: label,
statusDetail: detail
}));
queueOrSend({
type: "status",
requestId,
sessionId: nextSessionId,
runId,
stage,
label,
detail
});
},
onDelta: ({ sessionId: nextSessionId, runId, textDelta, fullText }) => {
executionSessionId = nextSessionId;
void updateAssistantTranscript((current) => ({
...current,
content: fullText && fullText.length >= current.content.length
? fullText
: current.content + textDelta,
streamState: "streaming",
statusLabel: undefined,
statusDetail: undefined
}));
queueOrSend({
type: "delta",
requestId,
sessionId: nextSessionId,
runId,
textDelta,
fullText
});
},
onCompleted: ({ sessionId: nextSessionId, runId, reply }) => {
executionSessionId = nextSessionId;
settled = true;
void (async () => {
await updateAssistantTranscript((current) => ({
...current,
content: reply.content,
createdAt: reply.createdAt,
streamState: undefined,
statusLabel: undefined,
statusDetail: undefined
}));
await projectStore.updateSessionLastActive(nextSessionId).catch(() => undefined);
})().catch(() => undefined);
runtimeCloudSupervisor.noteMessageSent(nextSessionId, reply.content, executionPolicy?.modelId, executionSkillId);
queueOrSend({
type: "completed",
requestId,
sessionId: nextSessionId,
runId,
reply,
executionPolicy: executionPolicy ?? undefined
});
queueProjectContextRefresh();
},
onError: ({ sessionId: nextSessionId, runId, error }) => {
executionSessionId = nextSessionId;
settled = true;
const errorCategory = typeof (error as Error & { errorCategory?: unknown }).errorCategory === "string"
? String((error as Error & { errorCategory?: unknown }).errorCategory).trim()
: "";
void updateAssistantTranscript((current) => ({
...current,
content: current.content.trim() ? current.content : error.message,
streamState: "error",
statusLabel: undefined,
statusDetail: undefined
}));
runtimeCloudSupervisor.noteError("chat_stream_failed", error.message, {
modelId: executionPolicy?.modelId,
sessionId: nextSessionId
});
queueOrSend({
type: "error",
requestId,
sessionId: nextSessionId,
runId,
message: error.message,
errorCategory: errorCategory || undefined
});
queueProjectContextRefresh();
}
})
});
return;
}
settled = true; settled = true;
await updateAssistantTranscript((current) => ({ await updateAssistantTranscript((current) => ({
...current, ...current,
...@@ -2215,5 +2362,3 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -2215,5 +2362,3 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
...@@ -24,6 +24,7 @@ interface ProjectWorkspaceExecutionCallbacks { ...@@ -24,6 +24,7 @@ interface ProjectWorkspaceExecutionCallbacks {
interface RunnerEvent { interface RunnerEvent {
type?: string; type?: string;
target?: string;
runId?: string; runId?: string;
stage?: string; stage?: string;
label?: string; label?: string;
...@@ -36,6 +37,21 @@ interface RunnerEvent { ...@@ -36,6 +37,21 @@ interface RunnerEvent {
result?: unknown; result?: unknown;
} }
export interface ProjectWorkspaceHandoffResult {
runId: string;
handoff: {
target: "chat-fallback";
content: string;
};
}
export interface ProjectWorkspaceReplyResult {
runId: string;
reply: ChatMessage;
}
export type ProjectWorkspaceExecutionResult = ProjectWorkspaceReplyResult | ProjectWorkspaceHandoffResult;
interface ProjectAutomationCommandConfig { interface ProjectAutomationCommandConfig {
runtime?: unknown; runtime?: unknown;
script?: unknown; script?: unknown;
...@@ -298,7 +314,7 @@ export class ProjectWorkspaceExecutorService { ...@@ -298,7 +314,7 @@ export class ProjectWorkspaceExecutorService {
async execute( async execute(
input: ProjectWorkspaceExecutionInput, input: ProjectWorkspaceExecutionInput,
callbacks: ProjectWorkspaceExecutionCallbacks = {} callbacks: ProjectWorkspaceExecutionCallbacks = {}
): Promise<{ runId: string; reply: ChatMessage }> { ): Promise<ProjectWorkspaceExecutionResult> {
const runtimeStatus = await this.runtimeManager.status(); const runtimeStatus = await this.runtimeManager.status();
if (runtimeStatus.payloadState !== "ready") { if (runtimeStatus.payloadState !== "ready") {
throw new Error("Bundled runtime payload is not ready for project workspace execution."); throw new Error("Bundled runtime payload is not ready for project workspace execution.");
...@@ -317,7 +333,7 @@ export class ProjectWorkspaceExecutorService { ...@@ -317,7 +333,7 @@ export class ProjectWorkspaceExecutorService {
automationCommand ? "Launching project workspace automation" : "Launching project workspace agent" automationCommand ? "Launching project workspace automation" : "Launching project workspace agent"
); );
return await new Promise<{ runId: string; reply: ChatMessage }>((resolve, reject) => { return await new Promise<ProjectWorkspaceExecutionResult>((resolve, reject) => {
let settled = false; let settled = false;
let stderr = ""; let stderr = "";
let stdoutBuffer = ""; let stdoutBuffer = "";
...@@ -453,6 +469,23 @@ export class ProjectWorkspaceExecutorService { ...@@ -453,6 +469,23 @@ export class ProjectWorkspaceExecutorService {
return; return;
} }
if (event.type === "handoff" && event.target === "chat-fallback") {
if (settled) {
return;
}
settled = true;
resolve({
runId: activeRunId,
handoff: {
target: "chat-fallback",
content: typeof event.content === "string" && event.content.trim()
? event.content
: input.userPrompt?.trim() || input.prompt
}
});
return;
}
if (event.type === "error") { if (event.type === "error") {
finishWithError( finishWithError(
event.message?.trim() || "Project workspace execution failed.", event.message?.trim() || "Project workspace execution failed.",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment