Skip to content

Commit 16022b0

Browse files
committed
fix(task-orchestration): make plan preview a chat command
1 parent ba2ceb3 commit 16022b0

13 files changed

Lines changed: 198 additions & 195 deletions

tests/unit/config-tabs-ui.test.mjs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ test('config template keeps expected config tabs in top and side navigation', ()
9494
assert.match(orchestrationPanel, /t\('orchestration\.hero\.kicker'\)/);
9595
assert.match(orchestrationPanel, /t\('orchestration\.hero\.title'\)/);
9696
assert.doesNotMatch(orchestrationPanel, /@click="previewTaskPlan\(\)"/);
97-
assert.match(orchestrationPanel, /@click="planAndRunTaskOrchestration\(\)"/);
97+
assert.doesNotMatch(orchestrationPanel, /@click="planAndRunTaskOrchestration\(\)"/);
98+
assert.doesNotMatch(orchestrationPanel, /orchestration\.actions\.generatePlan/);
9899
assert.match(orchestrationPanel, /@click="queueTaskOrchestrationAndStart\(\)"/);
99100
assert.match(orchestrationPanel, /@click="startTaskQueueRunner\(\)"/);
100101
assert.match(orchestrationPanel, /@click="retryTaskRunFromUi\(taskOrchestration.selectedRunId\)"/);
@@ -107,13 +108,14 @@ test('config template keeps expected config tabs in top and side navigation', ()
107108
assert.match(orchestrationPanel, /class="[^\"]*task-thread-message-card[^\"]*task-thread-workbench-card[^\"]*"/);
108109
assert.match(orchestrationPanel, /class="task-thread-card-label">AI · \{\{ t\('orchestration\.plan\.title'\) \}\}/);
109110
assert.match(orchestrationPanel, /class="task-chat-bubble-row is-user task-thread-plan-request"/);
110-
assert.match(orchestrationPanel, /You · latest request/);
111+
assert.match(orchestrationPanel, /You · \/plan/);
112+
assert.match(orchestrationPanel, /\/plan \{\{ taskOrchestration\.target \}\}/);
111113
assert.match(orchestrationPanel, /v-for="message in taskOrchestrationConversationMessages"/);
112114
assert.match(orchestrationPanel, /message\.role === 'user' \? 'is-user' : 'is-assistant'/);
113115
assert.match(orchestrationPanel, /class="[^"]*task-chat-composer[^"]*"/);
114116
assert.match(orchestrationPanel, /v-model="taskOrchestration\.chatDraft"/);
115-
assert.match(orchestrationPanel, /@keydown\.enter\.exact\.prevent="appendTaskChatMessage\(\)"/);
116-
assert.match(orchestrationPanel, /@click="appendTaskChatMessage\(\)"/);
117+
assert.match(orchestrationPanel, /@keydown\.enter\.exact\.prevent="submitTaskOrchestrationChatMessage\(\)"/);
118+
assert.match(orchestrationPanel, /@click="submitTaskOrchestrationChatMessage\(\)"/);
117119
assert.match(orchestrationPanel, /t\('orchestration\.chat\.input\.label'\)/);
118120
assert.match(orchestrationPanel, /t\('orchestration\.chat\.input\.placeholder'\)/);
119121
assert.match(orchestrationPanel, /t\('orchestration\.chat\.context\.workspace\.auto'\)/);
@@ -140,7 +142,7 @@ test('config template keeps expected config tabs in top and side navigation', ()
140142
assert.match(orchestrationPanel, /t\('orchestration\.stage\.title'\)/);
141143
assert.doesNotMatch(orchestrationPanel, /class="btn-tool task-action-preview" @click="previewTaskPlan\(\)"/);
142144
assert.doesNotMatch(orchestrationPanel, /orchestration\.actions\.previewOnly/);
143-
assert.match(orchestrationPanel, /class="task-action-row-right task-action-row-right-prominent"/);
145+
assert.doesNotMatch(orchestrationPanel, /class="task-action-row-right task-action-row-right-prominent"/);
144146
assert.match(orchestrationPanel, /class="task-action-caption"/);
145147
assert.match(orchestrationPanel, /class="task-empty-state"/);
146148
assert.match(orchestrationPanel, /taskOrchestration.selectedRunError/);

tests/unit/web-ui-behavior-parity.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus
635635
'syncTaskOrchestrationPolling',
636636
'resetTaskOrchestrationDraft',
637637
'appendTaskChatMessage',
638+
'submitTaskOrchestrationChatMessage',
638639
'appendTaskWorkflowId',
639640
'openClaudeMdEditor',
640641
'switchPromptsSubTab',

tests/unit/web-ui-logic.test.mjs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,49 @@ test('appendTaskChatMessage records sequential requests and invalidates stale pl
12981298
assert.deepStrictEqual(req.followUps, ['Then finish requirement 2 with the prior context']);
12991299
});
13001300

1301+
test('submitTaskOrchestrationChatMessage treats /plan as chat preview command', async () => {
1302+
const methods = createTaskOrchestrationMethods({ api: async () => ({}) });
1303+
const previewCalls = [];
1304+
const context = {
1305+
ensureTaskOrchestrationState: methods.ensureTaskOrchestrationState,
1306+
appendTaskChatMessage: methods.appendTaskChatMessage,
1307+
previewTaskPlan(options) {
1308+
previewCalls.push(options);
1309+
return Promise.resolve({ ok: true });
1310+
},
1311+
showMessage(message, tone) {
1312+
throw new Error(`unexpected message: ${tone}:${message}`);
1313+
},
1314+
taskOrchestration: {
1315+
chatDraft: '/plan Finish requirement 1',
1316+
target: '',
1317+
followUpsText: '',
1318+
selectedEngine: 'openai-chat',
1319+
runMode: 'write',
1320+
plan: null,
1321+
planFingerprint: '',
1322+
planIssues: [],
1323+
planWarnings: [],
1324+
lastError: ''
1325+
}
1326+
};
1327+
1328+
const result = await methods.submitTaskOrchestrationChatMessage.call(context);
1329+
1330+
assert.deepStrictEqual(result, { ok: true });
1331+
assert.strictEqual(context.taskOrchestration.target, 'Finish requirement 1');
1332+
assert.strictEqual(context.taskOrchestration.followUpsText, '');
1333+
assert.strictEqual(context.taskOrchestration.chatDraft, '');
1334+
assert.deepStrictEqual(previewCalls, [{ silent: false }]);
1335+
1336+
context.taskOrchestration.chatDraft = '/plan';
1337+
const secondResult = await methods.submitTaskOrchestrationChatMessage.call(context);
1338+
assert.deepStrictEqual(secondResult, { ok: true });
1339+
assert.strictEqual(context.taskOrchestration.target, 'Finish requirement 1');
1340+
assert.strictEqual(context.taskOrchestration.followUpsText, '');
1341+
assert.deepStrictEqual(previewCalls, [{ silent: false }, { silent: false }]);
1342+
});
1343+
13011344
test('taskOrchestrationConversationMessages renders assistant-left and user-right sequence model', () => {
13021345
const computed = createMainTabsComputed();
13031346
const translations = {

web-ui/modules/app.computed.main-tabs.mjs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,7 @@ function createTaskConversationMessages(taskOrchestration, t = null) {
100100
meta: translateTaskText(t, 'orchestration.chat.meta.afterPrevious', '等待前一条完成后继续')
101101
});
102102
});
103-
if (state.plan && typeof state.plan === 'object') {
104-
const nodeCount = Array.isArray(state.plan.nodes) ? state.plan.nodes.length : 0;
105-
const waveCount = Array.isArray(state.plan.waves) ? state.plan.waves.length : 0;
106-
messages.push({
107-
id: 'assistant-plan',
108-
role: 'assistant',
109-
label: translateTaskText(t, 'orchestration.chat.assistant.planLabel', '计划预览'),
110-
text: translateTaskText(t, 'orchestration.chat.assistant.planSummary', '计划已生成:{nodes} 个节点,{waves} 个批次。', { nodes: nodeCount, waves: waveCount }),
111-
meta: translateTaskText(t, 'orchestration.chat.meta.contextKept', '上下文会随线程保留')
112-
});
113-
} else if (target) {
103+
if (!state.plan && target) {
114104
messages.push({
115105
id: 'assistant-next',
116106
role: 'assistant',
@@ -172,7 +162,7 @@ function createTaskDraftChecklist(metrics, t = null) {
172162
label: translateTaskText(t, 'orchestration.readiness.preview.label', '预览'),
173163
done: previewReady,
174164
detail: !metrics.hasPlan
175-
? translateTaskText(t, 'orchestration.readiness.preview.missing', '还没生成计划')
165+
? translateTaskText(t, 'orchestration.readiness.preview.missing', '还没发送 /plan')
176166
: (metrics.planIssues.length > 0 ? translateTaskText(t, 'orchestration.readiness.preview.blocked', `有 ${metrics.planIssues.length} 个阻塞项`, { count: metrics.planIssues.length }) : translateTaskText(t, 'orchestration.readiness.preview.ready', `计划可用,${metrics.planNodeCount} 个节点`, { count: metrics.planNodeCount }))
177167
}
178168
];
@@ -199,7 +189,7 @@ function createTaskDraftReadiness(metrics, t = null) {
199189
title: translateTaskText(t, 'orchestration.readiness.preview.title', '建议先预览'),
200190
summary: metrics.hasSequentialFollowUps
201191
? translateTaskText(t, 'orchestration.readiness.preview.sequenceSummary', '草稿已成形,已锁定 {count} 条顺序需求:先完成需求 1,再继续需求 2。', { count: metrics.requestCount })
202-
: translateTaskText(t, 'orchestration.readiness.preview.summary', '草稿已成形,先生成一次计划,确认节点和依赖再执行。')
192+
: translateTaskText(t, 'orchestration.readiness.preview.summary', '草稿已成形,发送 /plan 预览方案,确认节点和依赖再执行。')
203193
};
204194
}
205195
if (metrics.planIssues.length > 0) {
@@ -213,7 +203,7 @@ function createTaskDraftReadiness(metrics, t = null) {
213203
return {
214204
tone: 'warn',
215205
title: translateTaskText(t, 'orchestration.readiness.warn.title', '可以执行,但有提醒'),
216-
summary: translateTaskText(t, 'orchestration.readiness.warn.summary', `计划已生成,但还有 ${metrics.planWarnings.length} 条提醒值得先看一眼。`, { count: metrics.planWarnings.length })
206+
summary: translateTaskText(t, 'orchestration.readiness.warn.summary', `/plan 方案已生成,但还有 ${metrics.planWarnings.length} 条提醒值得先看一眼。`, { count: metrics.planWarnings.length })
217207
};
218208
}
219209
if (metrics.dryRun) {

web-ui/modules/app.methods.task-orchestration.mjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,31 @@ export function createTaskOrchestrationMethods(options = {}) {
361361
return true;
362362
},
363363

364+
async submitTaskOrchestrationChatMessage() {
365+
const state = this.ensureTaskOrchestrationState();
366+
const rawMessage = String(state.chatDraft || '').trim();
367+
if (!rawMessage) {
368+
return false;
369+
}
370+
const planCommand = rawMessage.match(/^\/plan(?:\s+([\s\S]*))?$/i);
371+
if (!planCommand) {
372+
return this.appendTaskChatMessage();
373+
}
374+
const planTarget = String(planCommand[1] || '').trim();
375+
if (planTarget) {
376+
state.chatDraft = planTarget;
377+
if (!this.appendTaskChatMessage()) {
378+
return false;
379+
}
380+
} else if (!String(state.target || '').trim()) {
381+
this.showMessage('先输入任务需求,再发送 /plan', 'error');
382+
return false;
383+
} else {
384+
state.chatDraft = '';
385+
}
386+
return this.previewTaskPlan({ silent: false });
387+
},
388+
364389
async previewTaskPlan(options = {}) {
365390
const state = this.ensureTaskOrchestrationState();
366391
if (state.planning) {

web-ui/modules/i18n/locales/en.mjs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ const en = Object.freeze({
202202
'title.config': 'Local Configuration Console',
203203
'title.sessions': 'Sessions & Export',
204204
'title.usage': 'Local Usage & Trends',
205-
'title.orchestration': 'Task Orchestration',
205+
'title.orchestration': 'Task Thread',
206206
'title.market': 'Skills Install & Sync',
207207
'title.plugins': 'Plugins & Templates',
208208
'title.docs': 'CLI Install & Docs',
@@ -213,7 +213,7 @@ const en = Object.freeze({
213213
'subtitle.config': 'Manage local configs and models.',
214214
'subtitle.sessions': 'Browse and export sessions.',
215215
'subtitle.usage': 'View usage for the last 7/30 days.',
216-
'subtitle.orchestration': 'Plan, queue, run, and review local tasks.',
216+
'subtitle.orchestration': 'Chat with Codexmate, then send /plan when the task is ready.',
217217
'subtitle.market': 'Manage local skills.',
218218
'subtitle.plugins': 'Manage reusable prompt templates and plugins.',
219219
'subtitle.docs': 'CLI install commands and troubleshooting.',
@@ -958,19 +958,19 @@ const en = Object.freeze({
958958
'usage.range.7d.short': 'Last 7 days',
959959
'usage.range.30d.short': 'Last 30 days',
960960
'orchestration.queueStats': 'Queue: {running} running · {queued} queued',
961-
'orchestration.hero.kicker': 'Task orchestration',
962-
'orchestration.hero.title': 'Turn goals into executable steps',
963-
'orchestration.hero.subtitle': 'Send a task message, preview a plan, then run.',
961+
'orchestration.hero.kicker': 'Task thread',
962+
'orchestration.hero.title': 'Chat the task into shape',
963+
'orchestration.hero.subtitle': 'Send task messages, then use /plan in the thread when you want Codexmate to draft the execution plan.',
964964
'orchestration.quick.kicker': 'Quick task',
965965
'orchestration.quick.title': 'Tell Codexmate what to finish',
966-
'orchestration.quick.subtitle': 'Start with the outcome. Codexmate will preview a plan, run it, and keep advanced options out of the way until needed.',
967-
'orchestration.quick.caption': 'The primary action previews the plan before running. Use queue mode for batches or long-running work.',
966+
'orchestration.quick.subtitle': 'Start with the outcome. Keep it conversational; send /plan when the draft should become an execution plan.',
967+
'orchestration.quick.caption': 'Need a plan preview? Send /plan as a chat request. Queue mode stays in Advanced for batches or long-running work.',
968968
'orchestration.quick.templates.reviewFix.meta': 'Fix review feedback and validate regressions.',
969969
'orchestration.quick.templates.planOnly.meta': 'Investigate first without writing files.',
970970
'orchestration.quick.templates.workflowBatch.meta': 'Reuse local workflows for repeatable checks.',
971971
'orchestration.chat.input.label': 'Task message',
972-
'orchestration.chat.input.placeholder': 'Tell Codexmate what to finish, e.g. fix current PR review comments, add regression tests, and attach verification screenshots',
973-
'orchestration.chat.input.hint': 'Write it like a chat message; workspace, thread, and run mode are inferred by default and stay in Advanced when needed.',
972+
'orchestration.chat.input.placeholder': 'Tell Codexmate what to finish. Send /plan to turn the current request into an execution plan.',
973+
'orchestration.chat.input.hint': 'Write it like a chat message. Use /plan for planning; workspace, thread, and run mode stay in Advanced when needed.',
974974
'orchestration.chat.context.aria': 'Task context',
975975
'orchestration.chat.context.workspace.auto': 'Workspace auto-detected',
976976
'orchestration.chat.context.workspace.value': 'Workspace {value}',
@@ -989,15 +989,15 @@ const en = Object.freeze({
989989
'orchestration.chat.assistant.planLabel': 'Plan preview',
990990
'orchestration.chat.assistant.planSummary': 'Plan ready: {nodes} nodes across {waves} waves.',
991991
'orchestration.chat.assistant.sequenceReady': 'Multiple requests received. Codexmate will finish request 1 first, then continue with the remaining context.',
992-
'orchestration.chat.assistant.singleReady': 'First request received. Add request 2 or preview and run now.',
992+
'orchestration.chat.assistant.singleReady': 'First request received. Add request 2 or send /plan when you want the execution plan.',
993993
'orchestration.chat.user.step': 'Request {count}',
994994
'orchestration.chat.meta.thread': 'Thread {value}',
995995
'orchestration.chat.meta.workspace': 'Workspace {value}',
996996
'orchestration.chat.meta.order': 'Sequential execution · context kept',
997997
'orchestration.chat.meta.first': 'Finish this first',
998998
'orchestration.chat.meta.afterPrevious': 'Continue after the previous request completes',
999999
'orchestration.chat.meta.contextKept': 'Context is kept on the thread',
1000-
'orchestration.chat.meta.previewNext': 'Next: preview the plan',
1000+
'orchestration.chat.meta.previewNext': 'Next: send /plan',
10011001
'orchestration.quick.checklist.title': 'Ready check',
10021002
'orchestration.quick.checklist.subtitle': 'Only the blockers that affect execution stay visible.',
10031003
'orchestration.quick.status.title': 'Current draft',
@@ -1098,8 +1098,8 @@ const en = Object.freeze({
10981098
'orchestration.actions.planning': 'Planning...',
10991099
'orchestration.actions.previewOnly': 'Preview only',
11001100
'orchestration.actions.preparing': 'Preparing...',
1101-
'orchestration.actions.generatePlan': 'Generate plan',
1102-
'orchestration.actions.planAndRun': 'Plan & run',
1101+
'orchestration.actions.generatePlan': 'Send /plan',
1102+
'orchestration.actions.planAndRun': 'Preview /plan and run',
11031103
'orchestration.actions.processing': 'Working...',
11041104
'orchestration.actions.queueAndStart': 'Queue & start',
11051105
'orchestration.actions.caption': '“Plan & run” refreshes the plan when needed; use “Queue & start” for batch runs.',

0 commit comments

Comments
 (0)