Skip to content

Commit ee201a2

Browse files
committed
fix(task-orchestration): hide stale failed runs from plan previews
1 parent 712b3a6 commit ee201a2

6 files changed

Lines changed: 116 additions & 5 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus
889889
'taskOrchestrationSelectedRun',
890890
'taskOrchestrationSelectedRunNodes',
891891
'taskOrchestrationActiveQueue',
892+
'taskOrchestrationWorkbenchVisible',
892893
'taskOrchestrationQueueStats',
893894
'taskOrchestrationDraftMetrics',
894895
'taskOrchestrationDraftChecklist',

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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,46 @@ test('submitTaskOrchestrationChatMessage treats /plan as chat preview command',
13411341
assert.deepStrictEqual(previewCalls, [{ silent: false }, { silent: false }]);
13421342
});
13431343

1344+
test('submitTaskOrchestrationChatMessage clears stale failed run detail before /plan preview', async () => {
1345+
const methods = createTaskOrchestrationMethods({ api: async () => ({}) });
1346+
const context = {
1347+
ensureTaskOrchestrationState: methods.ensureTaskOrchestrationState,
1348+
appendTaskChatMessage: methods.appendTaskChatMessage,
1349+
previewTaskPlan(options) {
1350+
const state = this.ensureTaskOrchestrationState();
1351+
assert.strictEqual(state.selectedRunId, '');
1352+
assert.strictEqual(state.selectedRunDetail, null);
1353+
assert.strictEqual(state.selectedRunError, '');
1354+
assert.strictEqual(state.workspaceTab, 'queue');
1355+
return Promise.resolve({ ok: true, options });
1356+
},
1357+
showMessage(message, tone) {
1358+
throw new Error(`unexpected message: ${tone}:${message}`);
1359+
},
1360+
taskOrchestration: {
1361+
chatDraft: '/plan Fix the orchestration preview',
1362+
target: '',
1363+
followUpsText: '',
1364+
selectedEngine: 'openai-chat',
1365+
runMode: 'write',
1366+
plan: null,
1367+
planFingerprint: '',
1368+
planIssues: [],
1369+
planWarnings: [],
1370+
selectedRunId: 'run-failed',
1371+
selectedRunDetail: { run: { status: 'failed', summary: '前置节点失败,已阻塞' } },
1372+
selectedRunError: '前置节点失败,已阻塞',
1373+
workspaceTab: 'detail',
1374+
lastError: ''
1375+
}
1376+
};
1377+
1378+
const result = await methods.submitTaskOrchestrationChatMessage.call(context);
1379+
1380+
assert.deepStrictEqual(result, { ok: true, options: { silent: false } });
1381+
assert.strictEqual(context.taskOrchestration.target, 'Fix the orchestration preview');
1382+
});
1383+
13441384
test('previewTaskPlan sends previewOnly without mutating normal run semantics', async () => {
13451385
const apiCalls = [];
13461386
const methods = createTaskOrchestrationMethods({
@@ -1564,6 +1604,55 @@ test('taskOrchestrationActiveQueue hides completed and failed queue history from
15641604
assert.deepStrictEqual(activeQueue, [queued, running, runStatusOnly]);
15651605
});
15661606

1607+
test('taskOrchestrationWorkbenchVisible ignores passive failed run history', () => {
1608+
const computed = createMainTabsComputed();
1609+
const context = {
1610+
taskOrchestrationActiveQueue: [],
1611+
taskOrchestration: {
1612+
runs: [
1613+
{ runId: 'run-failed', status: 'failed', summary: '前置节点失败,已阻塞' }
1614+
],
1615+
selectedRunId: '',
1616+
selectedRunError: ''
1617+
}
1618+
};
1619+
1620+
assert.strictEqual(computed.taskOrchestrationWorkbenchVisible.call(context), false);
1621+
1622+
context.taskOrchestration.selectedRunId = 'run-failed';
1623+
assert.strictEqual(computed.taskOrchestrationWorkbenchVisible.call(context), true);
1624+
});
1625+
1626+
test('loadTaskOrchestrationOverview does not auto-select latest failed run history', async () => {
1627+
const methods = createTaskOrchestrationMethods({
1628+
api: async (name) => {
1629+
assert.strictEqual(name, 'task-overview');
1630+
return {
1631+
runs: [{ runId: 'run-failed', status: 'failed', summary: '前置节点失败,已阻塞' }],
1632+
queue: [],
1633+
workflows: [],
1634+
warnings: []
1635+
};
1636+
}
1637+
});
1638+
const context = {
1639+
ensureTaskOrchestrationState: methods.ensureTaskOrchestrationState,
1640+
loadTaskRunDetail() {
1641+
throw new Error('should not load detail for passive history');
1642+
},
1643+
isTaskRunActive: () => false,
1644+
showMessage() {},
1645+
syncTaskOrchestrationPolling() {}
1646+
};
1647+
context.taskOrchestration = methods.ensureTaskOrchestrationState.call(context);
1648+
1649+
await methods.loadTaskOrchestrationOverview.call(context, { silent: true, includeDetail: false });
1650+
1651+
assert.strictEqual(context.taskOrchestration.runs.length, 1);
1652+
assert.strictEqual(context.taskOrchestration.selectedRunId, '');
1653+
assert.strictEqual(context.taskOrchestration.selectedRunDetail, null);
1654+
});
1655+
15671656
test('startTaskQueueRunner surfaces already-running queue state distinctly', async () => {
15681657
const api = async (name) => {
15691658
if (name === 'task-queue-start') {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,17 @@ export function createMainTabsComputed() {
283283
return status === 'queued' || status === 'running';
284284
});
285285
},
286+
taskOrchestrationWorkbenchVisible() {
287+
const state = this.taskOrchestration && typeof this.taskOrchestration === 'object'
288+
? this.taskOrchestration
289+
: {};
290+
const activeQueue = Array.isArray(this.taskOrchestrationActiveQueue)
291+
? this.taskOrchestrationActiveQueue
292+
: [];
293+
return activeQueue.length > 0
294+
|| !!String(state.selectedRunId || '').trim()
295+
|| !!String(state.selectedRunError || '').trim();
296+
},
286297
taskOrchestrationQueueStats() {
287298
const queue = this.taskOrchestration && Array.isArray(this.taskOrchestration.queue)
288299
? this.taskOrchestration.queue

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,17 @@ function buildRunModeFlags(runMode) {
163163
};
164164
}
165165

166+
function clearTaskRunSelectionForDraft(state) {
167+
if (!state || typeof state !== 'object') {
168+
return;
169+
}
170+
state.selectedRunId = '';
171+
state.selectedRunDetail = null;
172+
state.selectedRunError = '';
173+
state.selectedRunLoading = false;
174+
state.workspaceTab = 'queue';
175+
}
176+
166177
export function createTaskOrchestrationMethods(options = {}) {
167178
const { api } = options;
168179

@@ -316,9 +327,6 @@ export function createTaskOrchestrationMethods(options = {}) {
316327
: null;
317328
state.overviewWarnings = Array.isArray(res && res.warnings) ? res.warnings : [];
318329
state.lastLoadedAt = new Date().toISOString();
319-
if (!state.selectedRunId && state.runs.length > 0) {
320-
state.selectedRunId = state.runs[0].runId || '';
321-
}
322330
const shouldRefreshSelectedDetail = !!state.selectedRunId
323331
&& (options.includeDetail !== false
324332
|| (state.selectedRunDetail && this.isTaskRunActive(state.selectedRunDetail && state.selectedRunDetail.run && state.selectedRunDetail.run.status)));
@@ -357,6 +365,7 @@ export function createTaskOrchestrationMethods(options = {}) {
357365
state.planIssues = [];
358366
state.planWarnings = [];
359367
state.lastError = '';
368+
clearTaskRunSelectionForDraft(state);
360369
scrollTaskOrchestrationThreadToEnd();
361370
return true;
362371
},
@@ -391,6 +400,7 @@ export function createTaskOrchestrationMethods(options = {}) {
391400
if (state.planning) {
392401
return null;
393402
}
403+
clearTaskRunSelectionForDraft(state);
394404
state.planning = true;
395405
try {
396406
const res = await api('task-plan', {

web-ui/partials/index/panel-orchestration.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@
242242
</template>
243243
</section>
244244
<section
245-
v-if="taskOrchestrationActiveQueue.length || taskOrchestration.runs.length || taskOrchestration.selectedRunId || taskOrchestration.selectedRunError"
245+
v-if="taskOrchestrationWorkbenchVisible"
246246
class="selector-section task-workbench-card task-thread-message-card task-thread-workbench-card">
247247
<div class="task-thread-card-label">AI · {{ t('orchestration.workbench.title') }}</div>
248248
<div class="task-thread-run-summary">

web-ui/res/web-ui-render.precompiled.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4446,7 +4446,7 @@ return function render(_ctx, _cache) {
44464446
: _createCommentVNode("v-if", true)
44474447
]))
44484448
: _createCommentVNode("v-if", true),
4449-
(_ctx.taskOrchestrationActiveQueue.length || _ctx.taskOrchestration.runs.length || _ctx.taskOrchestration.selectedRunId || _ctx.taskOrchestration.selectedRunError)
4449+
(_ctx.taskOrchestrationWorkbenchVisible)
44504450
? (_openBlock(), _createElementBlock("section", {
44514451
key: 4,
44524452
class: "selector-section task-workbench-card task-thread-message-card task-thread-workbench-card"

0 commit comments

Comments
 (0)