feat(web): degrade stage-blocked task runs to chat confirmation

Keep weak write verbs off the task shortcut path, and once-only fall back
to chat when TASK_SCOPE_UNRESOLVED or DOC_TASK_NOT_IN_ACTIVE_STAGE.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-21 08:27:49 +00:00
co-authored by Cursor
parent 5ba9bbbe11
commit 568408cced
4 changed files with 38 additions and 0 deletions
+1
View File
@@ -3,6 +3,7 @@ import { DeliverableItem } from '../types/workbench';
const READ_ONLY_INTENT =
/(?:总结|概括|查看|看看|展示|读取|介绍|解释|分析|评价|检查|查询|当前状态|进度|谁在编辑|是否在编辑)/;
const WRITE_INTENT =
// 弱写动词(整理/梳理/汇总等)故意不含:走聊天通道,由后端阶段感知 + 确认门禁引导。
/(?:开始|编写|编制|生成|起草|创建|撰写|重写|更新|修改|完善|补充|刷新|重做|改写)/;
const CURRENT_DOC_DEIXIS = /当前受控文档|当前文档|本文件|这份文档|本文档|本受控文档/;
+10
View File
@@ -5,6 +5,7 @@ import { isBlockedGovernanceStatus } from '../lib/blockedGovernanceCopy';
import {
buildFailedTaskErrorInfo,
buildNetworkErrorInfo,
isTaskStageDegradeCode,
readRemoteTaskRun,
} from '../lib/taskRunErrorInfo';
@@ -22,6 +23,15 @@ test('chat and task attempts live in one hook', () => {
assert.equal(hook.includes('chatWindow.allMessages.find'), true);
assert.equal(hook.includes('buildFailedTaskErrorInfo'), true);
assert.equal(hook.includes('buildCancelledTaskErrorInfo'), true);
assert.equal(hook.includes('isTaskStageDegradeCode'), true);
assert.equal(hook.includes('改由对话确认引导'), true);
});
test('stage unresolved and not-in-active-stage degrade to chat once', () => {
assert.equal(isTaskStageDegradeCode('TASK_SCOPE_UNRESOLVED'), true);
assert.equal(isTaskStageDegradeCode('DOC_TASK_NOT_IN_ACTIVE_STAGE'), true);
assert.equal(isTaskStageDegradeCode('DOC_BUSY'), false);
assert.equal(isTaskStageDegradeCode(''), false);
});
test('failed run with error_message surfaces TASK_FAILED not NETWORK_ERROR', () => {
+22
View File
@@ -29,6 +29,7 @@ import {
buildFailedTaskErrorInfo,
buildInterruptedTaskErrorInfo,
buildNetworkErrorInfo,
isTaskStageDegradeCode,
readRemoteTaskRun,
} from '../lib/taskRunErrorInfo';
import {
@@ -790,6 +791,27 @@ export function useTaskRunAttempts(input: TaskRunAttemptsInput) {
? Number((_err as { httpStatus?: number }).httpStatus)
: undefined;
const errMeta = _err as { errorCode?: string; detail?: string; message?: string };
// 阶段/作用域未解析:降级聊天一次,由后端意图确认引导,替代仅弹错误气泡。
if (
mode === 'run' &&
attemptIndex === 0 &&
isTaskStageDegradeCode(errMeta.errorCode) &&
task.userMessage?.trim()
) {
settled = true;
patchAsst({
status: 'pending',
livePhaseLabel: '改由对话确认引导…',
errorInfo: undefined,
taskRequest: undefined,
content: '',
});
taskRunStream.setThinking(sessionId, false);
runStreamAttempt(task.userMessage.trim(), asstId, 0, {
clientTurnId: task.clientTurnId,
});
return;
}
failOrRetry(
buildNetworkErrorInfo(httpStatus, {
errorCode: errMeta.errorCode,
+5
View File
@@ -35,6 +35,11 @@ const fatalDenialCopy: Record<string, string> = {
TASK_SCOPE_UNRESOLVED: '无法定位该文档任务的工作流阶段,请检查任务名或联系管理员',
};
/** 任务直通道失败后应降级到聊天确认引导的错误码(仅降级一次,防循环)。 */
export function isTaskStageDegradeCode(code: string | undefined | null): boolean {
return code === 'TASK_SCOPE_UNRESOLVED' || code === 'DOC_TASK_NOT_IN_ACTIVE_STAGE';
}
export function buildNetworkErrorInfo(
httpStatus?: number,
opts?: { errorCode?: string; detail?: string },