diff --git a/web/src/engine/templateMatch.ts b/web/src/engine/templateMatch.ts index d589e9f2..67ed3d67 100644 --- a/web/src/engine/templateMatch.ts +++ b/web/src/engine/templateMatch.ts @@ -3,6 +3,7 @@ import { DeliverableItem } from '../types/workbench'; const READ_ONLY_INTENT = /(?:总结|概括|查看|看看|展示|读取|介绍|解释|分析|评价|检查|查询|当前状态|进度|谁在编辑|是否在编辑)/; const WRITE_INTENT = + // 弱写动词(整理/梳理/汇总等)故意不含:走聊天通道,由后端阶段感知 + 确认门禁引导。 /(?:开始|编写|编制|生成|起草|创建|撰写|重写|更新|修改|完善|补充|刷新|重做|改写)/; const CURRENT_DOC_DEIXIS = /当前受控文档|当前文档|本文件|这份文档|本文档|本受控文档/; diff --git a/web/src/hooks/useTaskRunAttempts.test.ts b/web/src/hooks/useTaskRunAttempts.test.ts index 580dae47..d4fdf54e 100644 --- a/web/src/hooks/useTaskRunAttempts.test.ts +++ b/web/src/hooks/useTaskRunAttempts.test.ts @@ -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', () => { diff --git a/web/src/hooks/useTaskRunAttempts.ts b/web/src/hooks/useTaskRunAttempts.ts index 82bcc5b6..c09ec1a6 100644 --- a/web/src/hooks/useTaskRunAttempts.ts +++ b/web/src/hooks/useTaskRunAttempts.ts @@ -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, diff --git a/web/src/lib/taskRunErrorInfo.ts b/web/src/lib/taskRunErrorInfo.ts index ece1444c..328f14c3 100644 --- a/web/src/lib/taskRunErrorInfo.ts +++ b/web/src/lib/taskRunErrorInfo.ts @@ -35,6 +35,11 @@ const fatalDenialCopy: Record = { 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 },