import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' import { WORKBENCH_AI_INTENT_SOURCE_MODEL, WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK, WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW, WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT, WORKBENCH_AI_STEP_RUN_DUPLICATE_PRECHECK, WORKBENCH_AI_STEP_SUBMIT_APPLICATION, WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS, buildRuleFallbackWorkbenchAiIntentPlan, isLowConfidenceTravelApplicationPlan, normalizeWorkbenchAiIntentPlan, resolveExecutableTravelApplicationPlan, shouldRequestWorkbenchAiIntentPlan } from '../src/composables/workbenchAiMode/workbenchAiIntentPlannerModel.js' import { buildInlineApplicationPreview } from '../src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js' import { createWorkbenchAiMessageRuntime } from '../src/composables/workbenchAiMode/workbenchAiMessageModel.js' const personalWorkbenchAiModeScript = readFileSync( fileURLToPath(new URL('../src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js', import.meta.url)), 'utf8' ) const stewardFlowScript = readFileSync( fileURLToPath(new URL('../src/composables/workbenchAiMode/useWorkbenchAiStewardFlow.js', import.meta.url)), 'utf8' ) const applicationPreviewFlowScript = readFileSync( fileURLToPath(new URL('../src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js', import.meta.url)), 'utf8' ) const planningThinkingModelScript = readFileSync( fileURLToPath(new URL('../src/composables/workbenchAiMode/workbenchAiPlanningThinkingModel.js', import.meta.url)), 'utf8' ) test('workbench AI intent planner normalizes model travel application submit plan into executable steps', () => { const plan = normalizeWorkbenchAiIntentPlan({ planning_source: 'llm_function_call', tasks: [{ task_type: 'expense_application', assigned_agent: 'application_assistant', requested_action: 'submit', confidence: 0.91, ontology_fields: { time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', transport_mode: '火车' } }] }, { prompt: '去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交' }) assert.equal(plan.source, WORKBENCH_AI_INTENT_SOURCE_MODEL) assert.equal(plan.intent, 'create_travel_application') assert.equal(plan.requestedAction, 'submit') assert.deepEqual(plan.steps, [ WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW, WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS, WORKBENCH_AI_STEP_RUN_DUPLICATE_PRECHECK, WORKBENCH_AI_STEP_SUBMIT_APPLICATION ]) assert.deepEqual(plan.ontologyFields, { time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', transport_mode: '火车' }) assert.deepEqual(plan.slots, { timeRange: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', transportMode: '火车' }) }) test('workbench AI intent planner keeps reimbursement task after first application task', () => { const reimbursementTask = { task_id: 'task-reimbursement-2', task_type: 'reimbursement', assigned_agent: 'reimbursement_assistant', title: '业务招待费报销', summary: '报销昨天的业务招待费 2000 元', requested_action: 'preview', confidence: 0.9, ontology_fields: { expense_type: 'entertainment', expense_type_label: '业务招待费', time_range: '2026-06-25', amount: '2000元', reason: '业务招待' }, missing_fields: [] } const plan = normalizeWorkbenchAiIntentPlan({ planning_source: 'llm_function_call', tasks: [{ task_id: 'task-application-1', task_type: 'expense_application', assigned_agent: 'application_assistant', requested_action: 'preview', confidence: 0.93, ontology_fields: { expense_type: 'travel', time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '服务国网服务器部署' }, missing_fields: ['transport_mode'] }, reimbursementTask] }, { prompt: '2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元' }) assert.deepEqual(plan.stewardRemainingTasks, [reimbursementTask]) assert.deepEqual(resolveExecutableTravelApplicationPlan(plan).stewardRemainingTasks, [reimbursementTask]) }) test('workbench AI intent planner prefers server action steps when present', () => { const plan = normalizeWorkbenchAiIntentPlan({ planning_source: 'llm_function_call', tasks: [{ task_type: 'expense_application', assigned_agent: 'application_assistant', requested_action: 'submit', confidence: 0.91, ontology_fields: { time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', transport_mode: '火车' }, action_steps: [ { action_type: 'fill_application_fields' }, { action_type: 'build_application_preview' }, { action_type: 'validate_required_fields' }, { action_type: 'save_application_draft' } ] }] }, { prompt: '2026-02-20 至 2026-02-23,去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交' }) assert.deepEqual(plan.steps, [ WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW, WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS, WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT ]) }) test('workbench AI intent planner falls back to rule plan for compact travel direct submit', () => { const plan = buildRuleFallbackWorkbenchAiIntentPlan('去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交') assert.equal(plan.source, WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK) assert.equal(plan.intent, 'create_travel_application') assert.equal(plan.requestedAction, 'submit') assert.deepEqual(plan.steps, [ WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW, WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS, WORKBENCH_AI_STEP_RUN_DUPLICATE_PRECHECK, WORKBENCH_AI_STEP_SUBMIT_APPLICATION ]) }) test('workbench AI intent planner detects compact travel save-draft variant before rules are enough', () => { const prompt = '2026-02-20 至 2026-02-23,上海出差,国网仿生产服务器部署,火车,保存草稿。' const plan = buildRuleFallbackWorkbenchAiIntentPlan(prompt) assert.equal(shouldRequestWorkbenchAiIntentPlan(prompt), true) assert.equal(shouldRequestWorkbenchAiIntentPlan('帮我查询上海差旅标准'), true) assert.equal(shouldRequestWorkbenchAiIntentPlan('1'), false) assert.equal(plan.source, WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK) assert.equal(plan.intent, 'create_travel_application') assert.equal(plan.requestedAction, 'save_draft') assert.deepEqual(plan.steps, [ WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW, WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS, WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT ]) assert.deepEqual(resolveExecutableTravelApplicationPlan(plan), { expenseType: 'travel', expenseTypeLabel: '差旅费', sourceText: prompt, ontologyFields: {}, autoSubmit: false, autoSaveDraft: true, requestedSubmit: false, submitRequiresConfirmation: false }) }) test('workbench AI intent planner turns model fields and action into executable application preview payload', () => { const prompt = '2026-02-20 至 2026-02-23,上海出差,国网仿生产服务器部署,火车,保存草稿。' const plan = normalizeWorkbenchAiIntentPlan({ planning_source: 'llm_function_call', tasks: [{ task_type: 'expense_application', assigned_agent: 'application_assistant', requested_action: 'save_draft', confidence: 0.95, ontology_fields: { time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '国网仿生产服务器部署', transport_mode: '火车' }, missing_fields: [] }] }, { prompt }) assert.deepEqual(resolveExecutableTravelApplicationPlan(plan), { expenseType: 'travel', expenseTypeLabel: '差旅费', sourceText: prompt, ontologyFields: { time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '国网仿生产服务器部署', transport_mode: '火车' }, autoSubmit: false, autoSaveDraft: true, requestedSubmit: false, submitRequiresConfirmation: false }) }) test('workbench AI intent planner turns single application candidate flow into executable preview payload', () => { const prompt = '2026-02-20 至 2026-02-23,去上海出差,辅助国网仿生产服务器部署,交通火车' const plan = normalizeWorkbenchAiIntentPlan({ planning_source: 'rule_fallback', plan_status: 'needs_flow_confirmation', pending_flow_confirmation: { status: 'pending', candidate_flows: [{ flow_id: 'travel_application', label: '先发起出差申请', confidence: 0.86, ontology_fields: { expense_type: 'travel', time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', transport_mode: '火车' }, missing_fields: [] }] } }, { prompt }) assert.equal(plan.source, WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK) assert.equal(plan.intent, 'create_travel_application') assert.equal(plan.requestedAction, 'preview') assert.deepEqual(resolveExecutableTravelApplicationPlan(plan), { expenseType: 'travel', expenseTypeLabel: '差旅费', sourceText: prompt, ontologyFields: { expense_type: 'travel', time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', transport_mode: '火车' }, autoSubmit: false, autoSaveDraft: false, requestedSubmit: false, submitRequiresConfirmation: false }) }) test('workbench AI application preview prefers model ontology fields over local text guesses', () => { const preview = buildInlineApplicationPreview( '差旅费', '2026-02-20 至 2026-02-23,上海出差,国网仿生产服务器部署,火车,保存草稿。', { name: '李文静', grade: 'P5', location: '武汉' }, { ontologyFields: { time_range: '2026-02-20 至 2026-02-23', location: '上海', reason: '国网仿生产服务器部署', transport_mode: '火车' } } ) assert.equal(preview.fields.time, '2026-02-20 至 2026-02-23') assert.equal(preview.fields.location, '上海') assert.equal(preview.fields.reason, '国网仿生产服务器部署') assert.equal(preview.fields.transportMode, '火车') }) test('workbench AI intent planner rejects policy question and requires confirmation for direct-submit request', () => { assert.equal(buildRuleFallbackWorkbenchAiIntentPlan('帮我查询上海差旅标准'), null) const request = resolveExecutableTravelApplicationPlan( buildRuleFallbackWorkbenchAiIntentPlan('去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交') ) assert.deepEqual(request, { expenseType: 'travel', expenseTypeLabel: '差旅费', sourceText: '去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交', ontologyFields: {}, autoSubmit: false, autoSaveDraft: false, requestedSubmit: true, submitRequiresConfirmation: true }) }) test('workbench AI message runtime persists direct-submit confirmation metadata', () => { const { createInlineMessage, normalizeRuntimeMessage, serializeRuntimeMessage } = createWorkbenchAiMessageRuntime() const message = createInlineMessage('assistant', '申请核对表', { applicationPreview: { fields: { location: '上海' } }, requestedSubmit: true, submitRequiresConfirmation: true }) const serialized = serializeRuntimeMessage(message) assert.equal(serialized.requestedSubmit, true) assert.equal(serialized.submitRequiresConfirmation, true) const normalized = normalizeRuntimeMessage(serialized) assert.equal(normalized.requestedSubmit, true) assert.equal(normalized.submitRequiresConfirmation, true) }) test('workbench AI mode asks steward model plan before fallback execution', () => { assert.match(stewardFlowScript, /async function resolveInlineExecutionPlan\(prompt, entry = \{\}, files = \[\], options = \{\}\)/) assert.match(stewardFlowScript, /fetchStewardPlan\(planRequest/) assert.match(stewardFlowScript, /timeoutMs:\s*35000/) assert.match(personalWorkbenchAiModeScript, /async function executeModelPlannedWorkbenchIntent\(cleanPrompt, entry = \{\}, files = \[\]\)/) assert.match(personalWorkbenchAiModeScript, /await stewardFlow\.resolveInlineExecutionPlan\(cleanPrompt, entry, files,\s*\{/) assert.match(personalWorkbenchAiModeScript, /normalizeWorkbenchAiIntentPlan\(modelPlan,\s*\{\s*prompt:\s*cleanPrompt/) assert.match(personalWorkbenchAiModeScript, /buildRuleFallbackWorkbenchAiIntentPlan\(cleanPrompt\)/) assert.match(personalWorkbenchAiModeScript, /shouldRequestWorkbenchAiIntentPlan\(cleanPrompt\)/) assert.match(personalWorkbenchAiModeScript, /resolveExecutableTravelApplicationPlan\(intentPlan\)/) assert.doesNotMatch(personalWorkbenchAiModeScript, /fallbackIntentPlan/) assert.match(personalWorkbenchAiModeScript, /autoSaveDraft:\s*travelApplicationRequest\.autoSaveDraft/) assert.match(personalWorkbenchAiModeScript, /requestedSubmit:\s*travelApplicationRequest\.requestedSubmit/) 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/) assert.match(applicationPreviewFlowScript, /options\.autoSaveDraft/) assert.match(applicationPreviewFlowScript, /options\.onPreviewReadyForNextTask/) 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/) assert.match(applicationPreviewFlowScript, /buildInlineApplicationPreview\([\s\S]*ontologyFields:\s*options\.ontologyFields/) assert.doesNotMatch(personalWorkbenchAiModeScript, /const travelApplicationRequest = resolveInlineTravelApplicationRequest\(cleanPrompt\)/) }) test('workbench AI mode shows a visible planning response before waiting for steward model plan', () => { assert.match(personalWorkbenchAiModeScript, /function startModelPlanningConversation\(cleanPrompt, entry = \{\}\)/) assert.match(personalWorkbenchAiModeScript, /conversationMessages\.value\.push\(createInlineMessage\('user', cleanPrompt\)\)/) assert.match(personalWorkbenchAiModeScript, /正在识别意图,准备拆解申请、报销和附件任务/) assert.match( personalWorkbenchAiModeScript, /const plannerPendingMessage = startModelPlanningConversation\(cleanPrompt, entry\)[\s\S]*await stewardFlow\.resolveInlineExecutionPlan\(cleanPrompt, entry, files,\s*\{/ ) assert.match( personalWorkbenchAiModeScript, /pendingMessageId:\s*plannerPendingMessage\?\.id/ ) assert.match(applicationPreviewFlowScript, /options\.pendingMessageId/) }) test('workbench AI mode streams planning thinking into the pending message', () => { assert.match(planningThinkingModelScript, /buildModelPlanningProgressSchedule/) assert.match(planningThinkingModelScript, /判断办理意图/) assert.match(planningThinkingModelScript, /抽取关键信息/) assert.match(planningThinkingModelScript, /规划执行步骤/) assert.match(planningThinkingModelScript, /准备兜底策略/) assert.match(personalWorkbenchAiModeScript, /function startModelPlanningProgressUpdates\(messageId\)/) assert.match(personalWorkbenchAiModeScript, /globalThis\.setTimeout\(\(\) => \{\s*updateModelPlanningThinkingEvent\(messageId, event\)/) assert.match(personalWorkbenchAiModeScript, /const stopPlanningProgressUpdates = startModelPlanningProgressUpdates\(plannerPendingMessage\.id\)/) assert.match(personalWorkbenchAiModeScript, /stopPlanningProgressUpdates\(\)/) assert.match( personalWorkbenchAiModeScript, /await stewardFlow\.resolveInlineExecutionPlan\(cleanPrompt, entry, files,\s*\{\s*pendingMessageId:\s*plannerPendingMessage\.id\s*\}\)/ ) assert.match(stewardFlowScript, /async function resolveInlineExecutionPlan\(prompt, entry = \{\}, files = \[\], options = \{\}\)/) assert.match(stewardFlowScript, /fetchInlineStewardPlan\(planningMessageId, planRequest,\s*\{[\s\S]*includeAnswerDelta:\s*false/) assert.match(applicationPreviewFlowScript, /mergeWorkbenchAiThinkingEvents\(previousThinkingEvents,\s*\[/) }) test('workbench AI mode reuses planning pending message for regular steward replies', () => { assert.match( personalWorkbenchAiModeScript, /stewardFlow\.requestInlineAssistantReply\(cleanPrompt, entry, files,\s*\{\s*pendingMessageId:\s*plannerPendingMessage\.id\s*\}\)/ ) assert.doesNotMatch( personalWorkbenchAiModeScript, /replaceInlineMessage\(plannerPendingMessage\.id,\s*createInlineMessage\('assistant', '已完成意图识别,继续为您整理回复。'/ ) assert.match(stewardFlowScript, /async function requestInlineAssistantReply\(prompt, entry = \{\}, files = \[\], options = \{\}\)/) assert.match(stewardFlowScript, /const reusablePendingMessageId = String\(options\.pendingMessageId \|\| ''\)\.trim\(\)/) assert.match(stewardFlowScript, /reusablePendingMessageId \? replaceInlineMessage\(reusablePendingMessageId, pendingMessage\) : conversationMessages\.value\.push\(pendingMessage\)/) }) test('isLowConfidenceTravelApplicationPlan gates preview behind confirmation when model confidence is low', () => { const lowConfidenceModelPlan = normalizeWorkbenchAiIntentPlan({ planning_source: 'llm_function_call', tasks: [{ task_type: 'expense_application', assigned_agent: 'application_assistant', requested_action: 'preview', confidence: 0.4, ontology_fields: { location: '上海', reason: '部署' } }] }, { prompt: '上海那边好像要过去一趟搞部署' }) assert.equal(lowConfidenceModelPlan.source, WORKBENCH_AI_INTENT_SOURCE_MODEL) assert.equal(isLowConfidenceTravelApplicationPlan(lowConfidenceModelPlan), true) const highConfidenceModelPlan = normalizeWorkbenchAiIntentPlan({ planning_source: 'llm_function_call', tasks: [{ task_type: 'expense_application', assigned_agent: 'application_assistant', requested_action: 'preview', confidence: 0.9, ontology_fields: { location: '上海' } }] }, { prompt: '去上海出差' }) assert.equal(isLowConfidenceTravelApplicationPlan(highConfidenceModelPlan), false) const ruleFallbackPlan = buildRuleFallbackWorkbenchAiIntentPlan('去上海出差,辅助部署,交通火车') assert.equal(ruleFallbackPlan.source, WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK) assert.equal(isLowConfidenceTravelApplicationPlan(ruleFallbackPlan), false) const explicitSubmitLowConfidence = normalizeWorkbenchAiIntentPlan({ planning_source: 'llm_function_call', tasks: [{ task_type: 'expense_application', assigned_agent: 'application_assistant', requested_action: 'submit', confidence: 0.3, ontology_fields: { location: '上海' } }] }, { prompt: '直接提交' }) assert.equal(explicitSubmitLowConfidence.requestedAction, 'submit') assert.equal(isLowConfidenceTravelApplicationPlan(explicitSubmitLowConfidence), false) assert.equal(isLowConfidenceTravelApplicationPlan(null), false) }) test('workbench AI mode routes low confidence travel application plan to confirmation prompt', () => { assert.match(personalWorkbenchAiModeScript, /isLowConfidenceTravelApplicationPlan\(intentPlan\)/) assert.match(personalWorkbenchAiModeScript, /function startModelPlannedTravelApplicationConfirmation\(/) assert.match(personalWorkbenchAiModeScript, /action_type:\s*'ai_application_confirm_intent'/) assert.match(personalWorkbenchAiModeScript, /需要确认:您是要发起出差申请吗/) }) test('shouldRequestWorkbenchAiIntentPlan skips chitchat and only triggers on business keywords', () => { assert.equal(shouldRequestWorkbenchAiIntentPlan('你好'), false) assert.equal(shouldRequestWorkbenchAiIntentPlan('谢谢'), false) assert.equal(shouldRequestWorkbenchAiIntentPlan('嗯'), false) assert.equal(shouldRequestWorkbenchAiIntentPlan('ok'), false) assert.equal(shouldRequestWorkbenchAiIntentPlan('1'), false) assert.equal(shouldRequestWorkbenchAiIntentPlan('帮我查报销'), true) assert.equal(shouldRequestWorkbenchAiIntentPlan('我要出差'), true) assert.equal(shouldRequestWorkbenchAiIntentPlan('帮我查询上海差旅标准'), true) assert.equal(shouldRequestWorkbenchAiIntentPlan('删除3天前的草稿'), true) })