Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qjclaw-dmg
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AI-甘富林
qjclaw-dmg
Commits
48d00eba
Commit
48d00eba
authored
Apr 28, 2026
by
AI-甘富林
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(desktop): route workspace handoff to chat fallback
parent
bfda0c3a
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
182 additions
and
4 deletions
+182
-4
ipc.ts
apps/desktop/src/main/ipc.ts
+147
-2
project-workspace-executor.ts
apps/desktop/src/main/services/project-workspace-executor.ts
+35
-2
No files found.
apps/desktop/src/main/ipc.ts
View file @
48d00eba
...
@@ -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
apps/desktop/src/main/services/project-workspace-executor.ts
View file @
48d00eba
...
@@ -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."
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment