fix(chat): heal task bubble pending after completed run
This commit is contained in:
@@ -573,6 +573,82 @@ export function useTaskRunAttempts(input: TaskRunAttemptsInput) {
|
||||
}
|
||||
};
|
||||
|
||||
const settleFromRemoteIfTerminal = async (runId: string): Promise<boolean> => {
|
||||
if (settled || controller.signal.aborted) return settled;
|
||||
const remote = await getTaskRun(runId).catch(() => null);
|
||||
if (!remote) return false;
|
||||
const view = readRemoteTaskRun(remote);
|
||||
if (view.status === 'quality_failed') {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'quality_failed',
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
return true;
|
||||
}
|
||||
if (isBlockedGovernanceStatus(view.status)) {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'blocked_pending_governance',
|
||||
content: GOVERNANCE_BLOCKED_HINT,
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
return true;
|
||||
}
|
||||
if (view.status === 'completed' || view.status === 'pending_author_confirmation') {
|
||||
settled = true;
|
||||
const mapped = mapTaskStatusToMessageStatus(view.status, 'assistant') || 'completed';
|
||||
patchAsst({
|
||||
status: mapped,
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
await syncControlledDocsAfterTask(task.taskKey || 'intake');
|
||||
await chatWindow.refresh().catch(() => undefined);
|
||||
return true;
|
||||
}
|
||||
if (view.status === 'failed' || view.status === 'finalizing_failed') {
|
||||
failOrRetry(buildFailedTaskErrorInfo(view.errorMessage));
|
||||
return true;
|
||||
}
|
||||
if (view.status === 'interrupted') {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'failed',
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
errorInfo: buildInterruptedTaskErrorInfo(view.errorMessage, view.workspaceId),
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
return true;
|
||||
}
|
||||
if (view.status === 'cancelled') {
|
||||
failOrRetry(buildCancelledTaskErrorInfo());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const subscribeWithGapHeal = async (runId: string) => {
|
||||
await subscribeTaskRunEvents(
|
||||
runId,
|
||||
{
|
||||
onEvent,
|
||||
onGap: async () => settleFromRemoteIfTerminal(runId),
|
||||
},
|
||||
controller.signal,
|
||||
);
|
||||
};
|
||||
|
||||
const startObserver = async () => {
|
||||
if (mode === 'resume') {
|
||||
const runId = opts?.reconnectRunId?.trim();
|
||||
@@ -624,7 +700,7 @@ export function useTaskRunAttempts(input: TaskRunAttemptsInput) {
|
||||
taskWorkspaceId: resumed.workspace_id || workspaceId,
|
||||
livePhaseLabel: '正在继续任务…',
|
||||
});
|
||||
await subscribeTaskRunEvents(retainedRunId, { onEvent }, controller.signal);
|
||||
await subscribeWithGapHeal(retainedRunId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -643,7 +719,7 @@ export function useTaskRunAttempts(input: TaskRunAttemptsInput) {
|
||||
retainedRunId = existingRunId;
|
||||
taskRunStream.setRunId(sessionId, existingRunId);
|
||||
patchAsst({ taskRunId: existingRunId, livePhaseLabel: '正在重连任务…' });
|
||||
await subscribeTaskRunEvents(existingRunId, { onEvent }, controller.signal);
|
||||
await subscribeWithGapHeal(existingRunId);
|
||||
return;
|
||||
}
|
||||
if (remote && isRemoteTaskTerminal(view.status)) {
|
||||
@@ -700,7 +776,7 @@ export function useTaskRunAttempts(input: TaskRunAttemptsInput) {
|
||||
await chatWindow.refresh().catch(() => undefined);
|
||||
return;
|
||||
}
|
||||
await subscribeTaskRunEvents(created.run_id, { onEvent }, controller.signal);
|
||||
await subscribeWithGapHeal(created.run_id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -717,82 +793,17 @@ export function useTaskRunAttempts(input: TaskRunAttemptsInput) {
|
||||
const runId = retainedRunId || taskRunStream.activeTaskRunId || undefined;
|
||||
if (runId) {
|
||||
try {
|
||||
if (await settleFromRemoteIfTerminal(runId)) {
|
||||
return;
|
||||
}
|
||||
const remote = await getTaskRun(runId);
|
||||
const view = readRemoteTaskRun(remote);
|
||||
if (view.status === 'quality_failed') {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'quality_failed',
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
return;
|
||||
}
|
||||
if (isBlockedGovernanceStatus(view.status)) {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'blocked_pending_governance',
|
||||
content: GOVERNANCE_BLOCKED_HINT,
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
return;
|
||||
}
|
||||
if (view.status === 'completed') {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'completed',
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
await syncControlledDocsAfterTask(task.taskKey || 'intake');
|
||||
return;
|
||||
}
|
||||
if (view.status === 'pending_author_confirmation') {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'pending_author_confirmation',
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
await syncControlledDocsAfterTask(task.taskKey || 'intake');
|
||||
await chatWindow.refresh().catch(() => undefined);
|
||||
return;
|
||||
}
|
||||
if (shouldSubscribeExistingRun(view.status)) {
|
||||
// 原 run 仍在跑:重订阅,不 POST 新 run
|
||||
settled = false;
|
||||
retainedRunId = runId;
|
||||
patchAsst({ livePhaseLabel: '正在重连任务…', taskRunId: runId });
|
||||
await subscribeTaskRunEvents(runId, { onEvent }, controller.signal);
|
||||
return;
|
||||
}
|
||||
if (view.status === 'failed' || view.status === 'finalizing_failed') {
|
||||
failOrRetry(buildFailedTaskErrorInfo(view.errorMessage));
|
||||
return;
|
||||
}
|
||||
if (view.status === 'interrupted') {
|
||||
settled = true;
|
||||
patchAsst({
|
||||
status: 'failed',
|
||||
livePhaseLabel: undefined,
|
||||
taskWorkspaceId: view.workspaceId,
|
||||
taskRunId: runId,
|
||||
errorInfo: buildInterruptedTaskErrorInfo(view.errorMessage, view.workspaceId),
|
||||
});
|
||||
taskRunStream.setThinking(sessionId, false);
|
||||
return;
|
||||
}
|
||||
if (view.status === 'cancelled') {
|
||||
failOrRetry(buildCancelledTaskErrorInfo());
|
||||
await subscribeWithGapHeal(runId);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user