diff --git a/document/development/2026-06-26/dev-logs/bugs/multi-task-reimbursement-not-starting.md b/document/development/2026-06-26/dev-logs/bugs/multi-task-reimbursement-not-starting.md index 5501b84..64b0e7d 100644 --- a/document/development/2026-06-26/dev-logs/bugs/multi-task-reimbursement-not-starting.md +++ b/document/development/2026-06-26/dev-logs/bugs/multi-task-reimbursement-not-starting.md @@ -28,3 +28,10 @@ - 操作:先补红灯断言,要求工作台入口向申请预览流传 `onApplicationActionCompleted`,且申请预览流在保存/提交成功分支调用该回调;然后按同一条 `steward_continue_next_task` 路由复用原有报销启动逻辑。 - 验证:红灯阶段 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 失败在缺少 `onApplicationActionCompleted`;修复后 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过,`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。 - 影响:复合任务里用户保存第一张出差申请草稿后,系统会立即进入第二个业务招待费报销任务,不再要求用户手动点击“继续处理费用报销”;低置信确认后再保存草稿也保持同样行为。 + +- 22:45:继续修复同一 bug:真实页面已经显示“申请草稿已保存”,但保存动作结束后没有继续拉起第二个业务招待费报销 task。 + - Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地领先 13 个提交:`6bdaeed6 chore: 忽略 .zcode 本地目录并更新规则表与开发日志`、`d5a8f847 refactor(web): 应用外壳/差旅详情/报销创建视图适配主题与多 task`、`c4b5fcc0 feat(web): AI 工作台多 task 串行推进与会话适配`、`5753899e feat(web): 主题皮肤系统与 LLM 设置面板重构`、`9c3fa80d feat(server): 设置持久化新增 LLM 模型表与主题字段`,以及前面记录过的 8 个本地 ahead 提交。 + - 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在保存草稿/提交申请成功分支新增 `actionCompletedHandler`,优先使用本次 `executeInlineApplicationPreviewAction` / `startAiApplicationPreview` options 传入的 `onApplicationActionCompleted`,没有时再回落到 composable 初始化回调;自动保存草稿时同步把 `options.onApplicationActionCompleted` 透传给保存动作。`workbench-ai-application-context-submit.test.mjs` 新增“自动保存申请草稿后继续剩余 steward task”的行为测试;`workbench-ai-intent-planner-model.test.mjs` 更新结构断言,锁住 options 回调优先和自动保存透传契约。 + - 操作:先用新增测试复现红灯:自动保存时确实只触发初始化回调,`options` 里的续跑回调没有被使用;再做最小修复,让保存动作按本次调用上下文续跑下一任务。 + - 验证:红灯阶段 `node --test web/tests/workbench-ai-application-context-submit.test.mjs` 失败在 `fromOptions` 为 `undefined`;修复后该测试 2/2 通过;`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过;`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;真实 `http://127.0.0.1:5173/api/v1/steward/plans` 采样确认该用户句子仍返回 `expense_application` + `reimbursement` 两个 task;`npm --prefix web run build` 通过;`git diff --check` 通过。 + - 影响:保存草稿结果消息到达后,前端会使用当前任务链路传下来的续跑回调立即处理剩余 task;用户截图里的“申请草稿已保存”不再是终点,后续业务招待费报销会自动进入现有报销流程。 diff --git a/document/development/2026-06-27/work-logs.med b/document/development/2026-06-27/work-logs.med new file mode 100644 index 0000000..184f2a7 --- /dev/null +++ b/document/development/2026-06-27/work-logs.med @@ -0,0 +1,17 @@ +# 2026-06-27 综合工作日志 + +生成时间:2026-06-27 17:43:24 CST +来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录 + +## 今日功能点 + +- 今日未发现功能点文档。 + +## 今日 Bugs + +- 今日未发现 bug 修复记录。 + +## 综合分析 + +- 今日目录下暂无功能点或 bug 记录。 +- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。 diff --git a/document/development/2026-06-28/work-logs.med b/document/development/2026-06-28/work-logs.med new file mode 100644 index 0000000..078844a --- /dev/null +++ b/document/development/2026-06-28/work-logs.med @@ -0,0 +1,17 @@ +# 2026-06-28 综合工作日志 + +生成时间:2026-06-28 18:30:35 CST +来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录 + +## 今日功能点 + +- 今日未发现功能点文档。 + +## 今日 Bugs + +- 今日未发现 bug 修复记录。 + +## 综合分析 + +- 今日目录下暂无功能点或 bug 记录。 +- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。 diff --git a/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js b/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js index cee1efa..74ea402 100644 --- a/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js +++ b/web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js @@ -463,9 +463,12 @@ export function useWorkbenchAiApplicationPreviewFlow({ targetMessage.suggestedActions = [] const detailActions = buildInlineApplicationDetailAction(draftPayload) const nextTaskAction = buildApplicationPreviewNextTaskAction(targetMessage) + const actionCompletedHandler = typeof options.onApplicationActionCompleted === 'function' + ? options.onApplicationActionCompleted + : onApplicationActionCompleted const shouldAutoContinueNextTask = Boolean( nextTaskAction && - typeof onApplicationActionCompleted === 'function' && + typeof actionCompletedHandler === 'function' && Array.isArray(targetMessage.stewardRemainingTasks) && targetMessage.stewardRemainingTasks.length ) @@ -485,7 +488,7 @@ export function useWorkbenchAiApplicationPreviewFlow({ persistCurrentConversation() scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value }) if (shouldAutoContinueNextTask) { - onApplicationActionCompleted(targetMessage.stewardRemainingTasks, targetMessage) + actionCompletedHandler(targetMessage.stewardRemainingTasks, targetMessage) } return true } catch (error) { @@ -613,7 +616,8 @@ export function useWorkbenchAiApplicationPreviewFlow({ if (options.autoSaveDraft) { await executeInlineApplicationPreviewAction(AI_APPLICATION_ACTION_SAVE_DRAFT, previewMessage, { skipUserMessage: true, - userText: options.userMessage || '保存草稿' + userText: options.userMessage || '保存草稿', + onApplicationActionCompleted: options.onApplicationActionCompleted }) } else if ( typeof options.onPreviewReadyForNextTask === 'function' && diff --git a/web/tests/workbench-ai-application-context-submit.test.mjs b/web/tests/workbench-ai-application-context-submit.test.mjs index 8cb726a..9f3a2a0 100644 --- a/web/tests/workbench-ai-application-context-submit.test.mjs +++ b/web/tests/workbench-ai-application-context-submit.test.mjs @@ -19,7 +19,7 @@ function createInlineMessage(role, content, options = {}) { } } -function buildApplicationPreviewFlowHarness(messages) { +function buildApplicationPreviewFlowHarness(messages, options = {}) { const conversationMessages = createRef(messages) const applicationSubmitConfirmOpen = createRef(false) const applicationSubmitConfirmContext = createRef(null) @@ -69,7 +69,8 @@ function buildApplicationPreviewFlowHarness(messages) { resolveLatestInlineUserPrompt: () => '2026-02-20 至 2026-02-23,去上海出差,交通火车,保存草稿', scrollInlineConversationToBottom: () => {}, sending: createRef(false), - toast: () => {} + toast: () => {}, + onApplicationActionCompleted: options.onApplicationActionCompleted }) return { @@ -80,6 +81,72 @@ function buildApplicationPreviewFlowHarness(messages) { } } +test('workbench auto-saved application draft continues remaining steward task', async () => { + const originalFetch = globalThis.fetch + const requests = [] + globalThis.fetch = async (url, options = {}) => { + const normalizedUrl = String(url) + if (normalizedUrl.includes('/reimbursements/application-preview-action')) { + const body = JSON.parse(String(options.body || '{}')) + requests.push({ url: normalizedUrl, body }) + return { + ok: true, + async json() { + return { + status: 'succeeded', + result: { + draft_payload: { + claim_id: 'claim-auto-saved-draft', + claim_no: 'AEW2DDAFL', + status: 'draft' + } + } + } + } + } + } + throw new Error(`unexpected request: ${normalizedUrl}`) + } + + 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-25', + reason: '业务招待费报销' + } + }] + const harness = buildApplicationPreviewFlowHarness([], { + onApplicationActionCompleted: (tasks, sourceMessage) => { + continuedTasks.push({ tasks, sourceMessage }) + } + }) + + await harness.flow.startAiApplicationPreview('travel', '差旅费', '2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元', { + autoSaveDraft: true, + stewardRemainingTasks: remainingTasks, + onApplicationActionCompleted: (tasks, sourceMessage) => { + continuedTasks.push({ tasks, sourceMessage, fromOptions: true }) + } + }) + + assert.equal(requests.length, 1) + assert.equal(continuedTasks.length, 1) + assert.deepEqual(continuedTasks[0].tasks, remainingTasks) + assert.equal(continuedTasks[0].sourceMessage.stewardRemainingTasks, remainingTasks) + assert.equal(continuedTasks[0].fromOptions, true) + 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 cf501aa..aeb4a0f 100644 --- a/web/tests/workbench-ai-intent-planner-model.test.mjs +++ b/web/tests/workbench-ai-intent-planner-model.test.mjs @@ -351,7 +351,9 @@ test('workbench AI mode asks steward model plan before fallback execution', () = assert.match(personalWorkbenchAiModeScript, /onApplicationActionCompleted:\s*startModelPlannedNextTask/) assert.match(applicationPreviewFlowScript, /options\.autoSaveDraft/) assert.match(applicationPreviewFlowScript, /options\.onPreviewReadyForNextTask/) - assert.match(applicationPreviewFlowScript, /onApplicationActionCompleted\(\s*targetMessage\.stewardRemainingTasks/) + assert.match(applicationPreviewFlowScript, /const actionCompletedHandler = typeof options\.onApplicationActionCompleted === 'function'/) + assert.match(applicationPreviewFlowScript, /actionCompletedHandler\(targetMessage\.stewardRemainingTasks/) + assert.match(applicationPreviewFlowScript, /onApplicationActionCompleted:\s*options\.onApplicationActionCompleted/) assert.doesNotMatch(applicationPreviewFlowScript, /options\.autoSubmit && normalizeApplicationPreview\(preview\)\.readyToSubmit/) assert.match(applicationPreviewFlowScript, /ontologyFields:\s*options\.ontologyFields/) assert.match(applicationPreviewFlowScript, /executeInlineApplicationPreviewAction\(AI_APPLICATION_ACTION_SAVE_DRAFT/)