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, normalizeWorkbenchAiIntentPlan, resolveExecutableTravelApplicationPlan, shouldRequestWorkbenchAiIntentPlan } from '../src/composables/workbenchAiMode/workbenchAiIntentPlannerModel.js' import { buildInlineApplicationPreview } from '../src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.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 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 }) }) 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 }) }) 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 }) }) 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 resolves executable application request', () => { assert.equal(buildRuleFallbackWorkbenchAiIntentPlan('帮我查询上海差旅标准'), null) const request = resolveExecutableTravelApplicationPlan( buildRuleFallbackWorkbenchAiIntentPlan('去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交') ) assert.deepEqual(request, { expenseType: 'travel', expenseTypeLabel: '差旅费', sourceText: '去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交', ontologyFields: {}, autoSubmit: true, autoSaveDraft: false }) }) 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, /ontologyFields:\s*travelApplicationRequest\.ontologyFields/) assert.match(applicationPreviewFlowScript, /options\.autoSaveDraft/) 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\)/) })