From 43779f8f2c631a35ed3f450e1f43a245fca12740 Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Tue, 30 Jun 2026 11:40:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E5=A4=9A=20task=20=E4=B8=B2?= =?UTF-8?q?=E8=A1=8C=E6=8E=A8=E8=BF=9B=20task2=20=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E8=A2=AB=E9=A2=84=E8=A7=88=E7=94=9F=E6=88=90=E6=97=B6=E6=8F=90?= =?UTF-8?q?=E5=89=8D=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onPreviewReadyForNextTask 在 task1 申请核对表刚生成、用户还没操作时就 提前拉起 task2,与用户后续在 task1 上的保存草稿/提交操作互相打架,导致 task2 完全无反应。移除该提前推进回调,统一由 onApplicationActionCompleted 在 task1 真正完成后再推进 task2。 - useWorkbenchAiApplicationPreviewFlow: 删除预览生成时的提前推进分支 - usePersonalWorkbenchAiMode: startModelPlannedApplicationPreview 不再传 onPreviewReadyForNextTask - useWorkbenchAiActionRouter: 低置信确认按钮分支同步删除该回调 - 新增时序回归测试:预览生成不提前推进、保存草稿后才推进 - 更新两处源码正则断言为 doesNotMatch --- .../usePersonalWorkbenchAiMode.js | 1 - .../useWorkbenchAiActionRouter.js | 6 -- .../useWorkbenchAiApplicationPreviewFlow.js | 9 +-- web/tests/workbench-ai-action-router.test.mjs | 3 +- ...nch-ai-application-context-submit.test.mjs | 57 +++++++++++++++++++ ...workbench-ai-intent-planner-model.test.mjs | 6 +- 6 files changed, 66 insertions(+), 16 deletions(-) diff --git a/web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js b/web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js index ba026d0..ee96e6a 100644 --- a/web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js +++ b/web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js @@ -766,7 +766,6 @@ export function usePersonalWorkbenchAiMode(props, emit) { requestedSubmit: travelApplicationRequest.requestedSubmit, submitRequiresConfirmation: travelApplicationRequest.submitRequiresConfirmation, stewardRemainingTasks: travelApplicationRequest.stewardRemainingTasks, - onPreviewReadyForNextTask: startModelPlannedNextTask, onApplicationActionCompleted: startModelPlannedNextTask } ) diff --git a/web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js b/web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js index 76f06ce..f0d06e1 100644 --- a/web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js +++ b/web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js @@ -96,12 +96,6 @@ export function useWorkbenchAiActionRouter({ requestedSubmit: Boolean(actionPayload.requestedSubmit), submitRequiresConfirmation: Boolean(actionPayload.submitRequiresConfirmation), stewardRemainingTasks, - onPreviewReadyForNextTask: (remainingTasks = []) => { - const nextTaskAction = buildNextTaskSuggestedAction({ steward_remaining_tasks: remainingTasks }) - if (nextTaskAction) { - handleInlineSuggestedAction(nextTaskAction) - } - }, onApplicationActionCompleted: (remainingTasks = []) => { const nextTaskAction = buildNextTaskSuggestedAction({ steward_remaining_tasks: remainingTasks }) if (nextTaskAction) { diff --git a/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js b/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js index 74ea402..fc53e63 100644 --- a/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js +++ b/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js @@ -619,13 +619,10 @@ export function useWorkbenchAiApplicationPreviewFlow({ userText: options.userMessage || '保存草稿', onApplicationActionCompleted: options.onApplicationActionCompleted }) - } else if ( - typeof options.onPreviewReadyForNextTask === 'function' && - Array.isArray(previewMessage.stewardRemainingTasks) && - previewMessage.stewardRemainingTasks.length - ) { - options.onPreviewReadyForNextTask(previewMessage.stewardRemainingTasks, previewMessage) } + // 多 task 串行推进:预览生成后不提前拉起下一个 task(避免和用户在 task1 核对表上的 + // 保存草稿/提交操作互相打架,导致 task2 状态错乱)。task2 的推进统一交给 + // onApplicationActionCompleted,在 task1 真正完成(保存草稿/提交成功)后再触发。 } catch (error) { replaceInlineMessage(pendingMessage.id, createInlineMessage('assistant', error?.message || '申请核对表生成失败,请稍后重试。', { id: pendingMessage.id, diff --git a/web/tests/workbench-ai-action-router.test.mjs b/web/tests/workbench-ai-action-router.test.mjs index 4e22669..e732785 100644 --- a/web/tests/workbench-ai-action-router.test.mjs +++ b/web/tests/workbench-ai-action-router.test.mjs @@ -148,7 +148,8 @@ test('workbench low-confidence application confirmation forwards remaining tasks assert.ok(previewCall, 'startAiApplicationPreview 应被调用') assert.deepEqual(previewCall[3].stewardRemainingTasks, remainingTasks) - assert.equal(typeof previewCall[3].onPreviewReadyForNextTask, 'function') + // 低置信确认按钮只在 task1 完成后推进 task2,不再在预览生成时提前推进。 + assert.equal(previewCall[3].onPreviewReadyForNextTask, undefined) assert.equal(typeof previewCall[3].onApplicationActionCompleted, 'function') }) diff --git a/web/tests/workbench-ai-application-context-submit.test.mjs b/web/tests/workbench-ai-application-context-submit.test.mjs index 9f3a2a0..ed1c3d7 100644 --- a/web/tests/workbench-ai-application-context-submit.test.mjs +++ b/web/tests/workbench-ai-application-context-submit.test.mjs @@ -147,6 +147,63 @@ test('workbench auto-saved application draft continues remaining steward task', } }) +test('workbench application preview does not continue next task until draft is saved or submitted', async () => { + // 时序回归:task1 申请核对表刚生成、用户还没点保存草稿/提交时, + // 不能提前拉起 task2(会导致两条流程消息和状态互相打架,最终 task2 无反应)。 + // task2 的推进必须等 task1 真正完成(onApplicationActionCompleted)后再触发。 + const originalFetch = globalThis.fetch + globalThis.fetch = async (url) => { + if (String(url).includes('/reimbursements/application-preview-action')) { + return { + ok: true, + async json() { + return { status: 'succeeded', result: { draft_payload: { claim_id: 'c1', claim_no: 'AEW2', status: 'draft' } } } + } + } + } + throw new Error(`unexpected request: ${url}`) + } + + try { + const continuedTasks = [] + const remainingTasks = [{ + task_id: 'task-reimbursement-2', + task_type: 'reimbursement', + assigned_agent: 'reimbursement_assistant', + summary: '报销昨天的业务招待费 2000 元', + ontology_fields: { expense_type: 'entertainment', amount: '2000元', time_range: '2026-06-29', reason: '业务招待费报销' } + }] + const harness = buildApplicationPreviewFlowHarness([], { + onApplicationActionCompleted: (tasks) => { continuedTasks.push({ tasks, phase: 'module' }) } + }) + + // 第一步:生成申请核对表(不传 autoSaveDraft,模拟用户需要手动操作 task1) + await harness.flow.startAiApplicationPreview('travel', '差旅费', '2月20-23去上海出差,并且报销昨天招待费2000元', { + stewardRemainingTasks: remainingTasks, + onApplicationActionCompleted: (tasks) => { continuedTasks.push({ tasks, phase: 'options' }) } + }) + + // 预览生成后,task2 不应被提前拉起 + assert.equal(continuedTasks.length, 0, '预览生成时不应触发 task2 推进回调') + const previewMessage = harness.conversationMessages.value.find((m) => m.applicationPreview) + assert.equal(previewMessage?.stewardRemainingTasks?.length, 1, 'task2 应仍挂在核对表消息上等待用户完成 task1') + + // 第二步:用户手动点击"保存草稿"(走 actionRouter,不传 options.onApplicationActionCompleted), + // 此时回落到模块级 onApplicationActionCompleted 触发 task2,这正是真实运行时的续跑路径。 + await harness.flow.executeInlineApplicationPreviewAction('save_draft', previewMessage, { + userText: '保存草稿', + draftPayload: null + }) + + assert.equal(continuedTasks.length, 1, '保存草稿完成后应推进 task2') + assert.deepEqual(continuedTasks[0].tasks, remainingTasks) + assert.equal(continuedTasks[0].phase, 'module', '手动保存草稿走模块级续跑回调') + assert.doesNotMatch(harness.conversationMessages.value.at(-1).content, /继续处理费用报销/, '自动续跑时不展示重复的继续处理按钮') + } finally { + globalThis.fetch = originalFetch + } +}) + test('workbench saved application draft can be submitted by contextual text without re-planning', async () => { const originalFetch = globalThis.fetch const requests = [] diff --git a/web/tests/workbench-ai-intent-planner-model.test.mjs b/web/tests/workbench-ai-intent-planner-model.test.mjs index aeb4a0f..96f8ce9 100644 --- a/web/tests/workbench-ai-intent-planner-model.test.mjs +++ b/web/tests/workbench-ai-intent-planner-model.test.mjs @@ -347,10 +347,12 @@ test('workbench AI mode asks steward model plan before fallback execution', () = assert.match(personalWorkbenchAiModeScript, /submitRequiresConfirmation:\s*travelApplicationRequest\.submitRequiresConfirmation/) assert.match(personalWorkbenchAiModeScript, /ontologyFields:\s*travelApplicationRequest\.ontologyFields/) assert.match(personalWorkbenchAiModeScript, /stewardRemainingTasks:\s*travelApplicationRequest\.stewardRemainingTasks/) - assert.match(personalWorkbenchAiModeScript, /onPreviewReadyForNextTask:\s*startModelPlannedNextTask/) assert.match(personalWorkbenchAiModeScript, /onApplicationActionCompleted:\s*startModelPlannedNextTask/) + // 多 task 串行推进:预览生成时不再提前拉起下一个 task(会与用户在 task1 上的操作互相打架), + // 改为只在 task1 完成(保存草稿/提交)后通过 onApplicationActionCompleted 推进 task2。 + assert.doesNotMatch(personalWorkbenchAiModeScript, /onPreviewReadyForNextTask/) assert.match(applicationPreviewFlowScript, /options\.autoSaveDraft/) - assert.match(applicationPreviewFlowScript, /options\.onPreviewReadyForNextTask/) + assert.doesNotMatch(applicationPreviewFlowScript, /onPreviewReadyForNextTask/) assert.match(applicationPreviewFlowScript, /const actionCompletedHandler = typeof options\.onApplicationActionCompleted === 'function'/) assert.match(applicationPreviewFlowScript, /actionCompletedHandler\(targetMessage\.stewardRemainingTasks/) assert.match(applicationPreviewFlowScript, /onApplicationActionCompleted:\s*options\.onApplicationActionCompleted/)