codexmate 0.0.56 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/cli.js +680 -129
  2. package/lib/task-orchestrator.js +90 -21
  3. package/lib/task-workspace-chat.js +292 -0
  4. package/package.json +2 -2
  5. package/web-ui/app.js +55 -129
  6. package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
  7. package/web-ui/modules/app.methods.agents.mjs +3 -0
  8. package/web-ui/modules/app.methods.claude-config.mjs +65 -25
  9. package/web-ui/modules/app.methods.navigation.mjs +67 -83
  10. package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
  11. package/web-ui/modules/app.methods.session-actions.mjs +1 -12
  12. package/web-ui/modules/app.methods.session-browser.mjs +23 -54
  13. package/web-ui/modules/app.methods.session-trash.mjs +0 -1
  14. package/web-ui/modules/app.methods.startup-claude.mjs +16 -31
  15. package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
  16. package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
  17. package/web-ui/modules/app.methods.web-ui-preferences.mjs +415 -68
  18. package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
  19. package/web-ui/modules/i18n/locales/en.mjs +146 -26
  20. package/web-ui/modules/i18n/locales/ja.mjs +143 -23
  21. package/web-ui/modules/i18n/locales/vi.mjs +145 -25
  22. package/web-ui/modules/i18n/locales/zh-tw.mjs +146 -26
  23. package/web-ui/modules/i18n/locales/zh.mjs +148 -28
  24. package/web-ui/modules/i18n.mjs +5 -12
  25. package/web-ui/modules/sessions-filters-url.mjs +1 -2
  26. package/web-ui/partials/index/layout-header.html +37 -14
  27. package/web-ui/partials/index/panel-orchestration.html +489 -282
  28. package/web-ui/res/web-ui-render.precompiled.js +1049 -610
  29. package/web-ui/styles/layout-shell.css +157 -1
  30. package/web-ui/styles/navigation-panels.css +11 -0
  31. package/web-ui/styles/task-orchestration.css +2161 -4
@@ -3,6 +3,7 @@
3
3
  v-show="mainTab === 'orchestration'"
4
4
  class="mode-content"
5
5
  id="panel-orchestration"
6
+ :data-active="mainTab === 'orchestration' ? 'true' : 'false'"
6
7
  role="tabpanel"
7
8
  aria-labelledby="tab-orchestration">
8
9
  <section class="selector-section task-hero-card">
@@ -24,97 +25,476 @@
24
25
  </button>
25
26
  </div>
26
27
  </div>
27
- <div v-if="taskOrchestrationQueueStats.running || taskOrchestrationQueueStats.queued || taskOrchestration.runs.length" class="task-hero-meta-strip" :aria-label="t('orchestration.summary.aria')">
28
- <div class="task-hero-meta">{{ t('orchestration.summary.running') }} <strong>{{ taskOrchestrationQueueStats.running }}</strong></div>
29
- <div class="task-hero-meta">{{ t('orchestration.summary.queued') }} <strong>{{ taskOrchestrationQueueStats.queued }}</strong></div>
30
- <div class="task-hero-meta">{{ t('orchestration.summary.runs') }} <strong>{{ taskOrchestration.runs.length }}</strong></div>
31
- </div>
32
28
  </section>
33
29
 
34
- <div class="task-layout-grid task-layout-grid-primary">
35
- <section class="selector-section task-compose-flow-card">
36
- <div class="task-flow-section task-flow-section-compact">
37
- <div class="task-flow-head">
38
- <div class="task-flow-step">1</div>
30
+ <div class="task-layout-grid task-layout-grid-primary task-quick-layout">
31
+ <section class="selector-section task-compose-flow-card task-quick-card">
32
+ <aside class="task-project-sidebar" :aria-label="t('orchestration.workspace.aria')">
33
+ <div class="task-project-sidebar-head">
39
34
  <div>
40
- <div class="task-flow-title">{{ t('orchestration.step1.title') }}</div>
41
- <div class="task-flow-copy">{{ t('orchestration.step1.subtitle') }}</div>
35
+ <div class="task-thread-card-label">{{ t('orchestration.workspace.title') }}</div>
36
+ <div class="skills-panel-note">{{ t('orchestration.workspace.subtitle') }}</div>
37
+ </div>
38
+ <button type="button" class="btn-mini" @click="loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })" :disabled="taskOrchestration.loading">
39
+ {{ taskOrchestration.loading ? t('common.refreshing') : t('common.refresh') }}
40
+ </button>
41
+ </div>
42
+ <div class="task-project-list" :aria-label="t('orchestration.workspace.selector')">
43
+ <button
44
+ v-for="workspace in taskOrchestrationWorkspaceItems"
45
+ :key="workspace.key"
46
+ type="button"
47
+ :class="['task-project-item', { active: workspace.active }]"
48
+ :aria-selected="workspace.active ? 'true' : 'false'"
49
+ :title="workspace.path ? t('orchestration.workspace.pathHidden') : workspace.meta"
50
+ @click="selectTaskWorkspace(workspace.path)">
51
+ <span class="task-project-item-title">{{ workspace.label }}</span>
52
+ <span class="task-project-item-meta">{{ workspace.path ? t('orchestration.workspace.pathHidden') : workspace.meta }}</span>
53
+ <span class="task-project-item-stats">{{ t('orchestration.workspace.counts', { runs: workspace.runCount, queue: workspace.queueCount }) }}</span>
54
+ </button>
55
+ </div>
56
+ <button type="button" class="btn-tool btn-primary task-project-new-session" @click="startNewTaskWorkspaceSession()">
57
+ {{ t('orchestration.workspace.newSession') }}
58
+ </button>
59
+ <div class="task-session-inbox">
60
+ <div class="task-session-inbox-head">
61
+ <span class="task-readiness-title">{{ t('orchestration.workspace.sessions.title') }}</span>
62
+ <span class="pill neutral">{{ taskOrchestrationWorkspaceSessions.length }}</span>
63
+ </div>
64
+ <div v-if="!taskOrchestrationWorkspaceSessions.length" class="task-empty-state task-session-empty">
65
+ <div class="task-empty-title">{{ t('orchestration.workspace.sessions.empty.title') }}</div>
66
+ <div class="task-empty-copy">{{ t('orchestration.workspace.sessions.empty.subtitle') }}</div>
42
67
  </div>
68
+ <button
69
+ v-for="session in taskOrchestrationWorkspaceSessions.slice(0, 8)"
70
+ :key="session.id"
71
+ type="button"
72
+ class="task-session-inbox-item"
73
+ @click="continueTaskWorkspaceSession(session)">
74
+ <span class="task-session-inbox-main">
75
+ <span class="task-session-inbox-title">{{ session.type === 'queue' ? t('orchestration.workspace.sessions.queueTitle') : t('orchestration.workspace.sessions.runTitle') }}</span>
76
+ <span class="task-session-inbox-meta">{{ t('orchestration.workspace.sessions.detailsHidden') }}</span>
77
+ </span>
78
+ <span :class="['pill', taskRunStatusTone(session.status)]">{{ session.status }}</span>
79
+ </button>
80
+ </div>
81
+ </aside>
82
+ <div class="task-quick-main">
83
+ <div class="task-quick-copy">
84
+ <div class="task-hero-kicker">{{ t('orchestration.quick.kicker') }}</div>
85
+ <div class="selector-title task-quick-title">{{ t('orchestration.quick.title') }}</div>
86
+ <div class="skills-panel-note task-hero-copy">{{ t('orchestration.quick.subtitle') }}</div>
43
87
  </div>
44
88
 
45
- <details v-if="!taskOrchestration.target.trim()" class="task-template-panel">
46
- <summary class="task-advanced-summary">{{ t('orchestration.templates.title') }}</summary>
47
- <div class="task-template-block">
48
- <div class="task-template-chip-group">
49
- <button type="button" class="task-template-chip" @click="taskOrchestration.target = t('orchestration.templates.reviewFix.target'); taskOrchestration.selectedEngine = 'codex'; taskOrchestration.workflowIdsText = ''; taskOrchestration.notes = t('orchestration.templates.reviewFix.notes'); taskOrchestration.followUpsText = t('orchestration.templates.reviewFix.followUps')">{{ t('orchestration.templates.reviewFix.label') }}</button>
50
- <button type="button" class="task-template-chip" @click="taskOrchestration.target = t('orchestration.templates.planOnly.target'); taskOrchestration.selectedEngine = 'codex'; taskOrchestration.workflowIdsText = ''; taskOrchestration.notes = t('orchestration.templates.planOnly.notes'); taskOrchestration.followUpsText = ''">{{ t('orchestration.templates.planOnly.label') }}</button>
51
- <button type="button" class="task-template-chip" @click="taskOrchestration.target = t('orchestration.templates.workflowBatch.target'); taskOrchestration.selectedEngine = 'workflow'; taskOrchestration.workflowIdsText = t('orchestration.templates.workflowBatch.workflowIds'); taskOrchestration.notes = t('orchestration.templates.workflowBatch.notes'); taskOrchestration.followUpsText = ''">{{ t('orchestration.templates.workflowBatch.label') }}</button>
89
+ <section class="task-agent-cockpit" :aria-label="t('orchestration.agent.aria')">
90
+ <div class="task-agent-cockpit-head">
91
+ <div>
92
+ <div class="task-hero-kicker">{{ t('orchestration.agent.kicker') }}</div>
93
+ <div class="selector-title task-agent-title">{{ t('orchestration.agent.title') }}</div>
94
+ <div class="skills-panel-note task-agent-copy">{{ t('orchestration.agent.subtitle') }}</div>
52
95
  </div>
96
+ <span :class="['pill', taskOrchestration.running || taskOrchestration.planning ? 'warn' : (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.run ? taskRunStatusTone(taskOrchestrationSelectedRun.run.status) : 'neutral')]">
97
+ {{ taskOrchestration.running ? t('orchestration.agent.state.running') : (taskOrchestration.planning ? t('orchestration.agent.state.planning') : (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.run ? taskOrchestrationSelectedRun.run.status : t('orchestration.agent.state.ready'))) }}
98
+ </span>
53
99
  </div>
54
- </details>
100
+ <div class="task-agent-surface-grid">
101
+ <div class="task-agent-surface-card">
102
+ <span>{{ t('orchestration.agent.surface.workspace') }}</span>
103
+ <strong>{{ taskOrchestrationWorkspacePath ? t('orchestration.privacy.workspace.selected') : t('orchestration.chat.context.workspace.auto') }}</strong>
104
+ </div>
105
+ <div class="task-agent-surface-card">
106
+ <span>{{ t('orchestration.agent.surface.session') }}</span>
107
+ <strong>{{ (taskOrchestration.threadId.trim() || (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.threadId)) ? t('orchestration.privacy.thread.selected') : t('orchestration.chat.context.thread.auto') }}</strong>
108
+ </div>
109
+ <div class="task-agent-surface-card">
110
+ <span>{{ t('orchestration.agent.surface.mode') }}</span>
111
+ <strong>{{ taskOrchestration.selectedEngine === 'workflow' ? t('orchestration.engine.workflow') : t('orchestration.engine.openaiChat') }} · {{ taskOrchestration.runMode === 'dry-run' ? t('orchestration.runMode.dryRun') : (taskOrchestration.runMode === 'read' ? t('orchestration.runMode.readOnly') : t('orchestration.runMode.write')) }}</strong>
112
+ </div>
113
+ <div class="task-agent-surface-card">
114
+ <span>{{ t('orchestration.agent.surface.trace') }}</span>
115
+ <strong>{{ taskOrchestrationWorkspaceRuns.length }} {{ t('orchestration.agent.surface.runs') }} · {{ taskOrchestrationWorkspaceQueue.length }} {{ t('orchestration.agent.surface.queue') }}</strong>
116
+ </div>
117
+ </div>
118
+ </section>
55
119
 
56
- <div class="selector-grid task-composer-grid task-composer-grid-primary">
57
- <label class="selector-field task-field task-field-wide task-goal-field">
58
- <span class="selector-label">{{ t('orchestration.fields.target') }}</span>
59
- <textarea v-model="taskOrchestration.target" class="task-textarea task-textarea-goal" rows="5" :placeholder="t('orchestration.fields.target.placeholder')"></textarea>
60
- <span class="task-field-hint">{{ t('orchestration.fields.target.hint') }}</span>
61
- </label>
62
- </div>
120
+ <div class="task-quick-input-card task-chat-panel">
121
+ <div class="task-chat-thread" role="log" :aria-label="t('orchestration.chat.thread.aria')">
122
+ <div
123
+ v-for="message in taskOrchestrationConversationMessages"
124
+ :key="message.id"
125
+ :class="['task-chat-bubble-row', message.role === 'user' ? 'is-user' : 'is-assistant']">
126
+ <div class="task-chat-bubble">
127
+ <div class="task-chat-bubble-label">{{ message.label }}</div>
128
+ <div class="task-chat-bubble-text">{{ message.text }}</div>
129
+ <div v-if="message.meta" class="task-chat-bubble-meta">{{ message.meta }}</div>
130
+ </div>
131
+ </div>
132
+ <div
133
+ v-if="taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError"
134
+ class="task-chat-bubble-row is-user task-thread-plan-request">
135
+ <div class="task-chat-bubble">
136
+ <div class="task-chat-bubble-label">You · /plan</div>
137
+ <div class="task-chat-bubble-text">/plan {{ taskOrchestration.target }}</div>
138
+ <div v-if="taskOrchestrationDraftMetrics.followUpCount" class="task-chat-bubble-meta">{{ t('orchestration.chat.input.sequenceHint', { count: taskOrchestrationDraftMetrics.followUpCount + 1 }) }}</div>
139
+ </div>
140
+ </div>
141
+ <section
142
+ v-if="taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError"
143
+ class="selector-section task-plan-card task-thread-message-card task-thread-plan-card">
144
+ <div class="task-thread-card-label">AI · {{ t('orchestration.plan.title') }}</div>
145
+ <div v-if="taskOrchestration.lastError" class="task-issue-item">{{ taskOrchestration.lastError }}</div>
146
+ <div v-if="taskOrchestration.plan" class="task-thread-run-summary task-thread-plan-summary">
147
+ <span class="pill configured">/plan</span>
148
+ <span class="task-thread-run-summary-copy">{{ t('orchestration.chat.assistant.planSummary', { nodes: taskOrchestration.plan.nodes.length, waves: taskOrchestration.plan.waves.length }) }}</span>
149
+ </div>
150
+ <div v-if="taskOrchestration.planIssues.length" class="task-issues-list">
151
+ <div v-for="issue in taskOrchestration.planIssues" :key="issue.code + issue.message" class="task-issue-item">
152
+ {{ issue.message }}
153
+ </div>
154
+ </div>
155
+ <div v-if="taskOrchestration.planWarnings.length" class="task-warning-list">
156
+ <div v-for="warning in taskOrchestration.planWarnings" :key="warning" class="task-warning-item">
157
+ {{ warning }}
158
+ </div>
159
+ </div>
160
+ <template v-if="taskOrchestration.plan">
161
+ <details class="task-thread-run-details task-thread-plan-details">
162
+ <summary>Plan details</summary>
163
+ <div class="task-plan-summary-strip">
164
+ <div class="task-plan-summary-item">
165
+ <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.nodes') }}</span>
166
+ <strong>{{ taskOrchestration.plan.nodes.length }}</strong>
167
+ </div>
168
+ <div class="task-plan-summary-item">
169
+ <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.waves') }}</span>
170
+ <strong>{{ taskOrchestration.plan.waves.length }}</strong>
171
+ </div>
172
+ <div class="task-plan-summary-item">
173
+ <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.engine') }}</span>
174
+ <strong>{{ taskOrchestration.plan.engine }}</strong>
175
+ </div>
176
+ <div v-if="taskOrchestration.plan.threadId" class="task-plan-summary-item">
177
+ <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.threadId') }}</span>
178
+ <strong>{{ taskOrchestration.plan.threadId }}</strong>
179
+ </div>
180
+ <div v-if="taskOrchestration.plan.cwd" class="task-plan-summary-item">
181
+ <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.cwd') }}</span>
182
+ <strong>{{ taskOrchestration.plan.cwd }}</strong>
183
+ </div>
184
+ </div>
185
+ <div class="task-wave-list">
186
+ <div v-for="wave in taskOrchestration.plan.waves" :key="wave.label" class="task-wave-card">
187
+ <div class="task-wave-title">{{ wave.label }}</div>
188
+ <div class="task-wave-nodes">{{ wave.nodeIds.join(', ') }}</div>
189
+ </div>
190
+ </div>
191
+ <div class="task-node-list">
192
+ <div v-for="node in taskOrchestration.plan.nodes" :key="node.id" class="task-node-card">
193
+ <div class="task-node-head">
194
+ <div>
195
+ <div class="task-node-title">{{ node.title || node.id }}</div>
196
+ <div class="task-node-meta">{{ node.id }} · {{ node.kind }}<span v-if="node.workflowId"> · {{ node.workflowId }}</span></div>
197
+ </div>
198
+ <span :class="['pill', node.write ? 'configured' : 'empty']">{{ node.write ? t('orchestration.plan.node.write') : t('orchestration.plan.node.readOnly') }}</span>
199
+ </div>
200
+ <div class="task-node-deps">{{ t('orchestration.labels.dependencies') }}{{ formatTaskNodeDependencies(node) }}</div>
201
+ </div>
202
+ </div>
203
+ </details>
204
+ </template>
205
+ </section>
63
206
 
64
- <div class="task-draft-overview task-draft-inline">
65
- <div class="task-draft-inline-head">
66
- <span :class="['pill', taskOrchestrationDraftReadiness.tone]">{{ taskOrchestrationDraftReadiness.title }}</span>
67
- <div class="task-readiness-copy task-draft-inline-copy">{{ taskOrchestrationDraftReadiness.summary }}</div>
68
207
  </div>
69
- <div class="task-config-strip">
70
- <div class="task-config-pill">{{ taskOrchestration.selectedEngine === 'workflow' ? t('orchestration.engine.workflow') : t('orchestration.engine.codex') }}</div>
71
- <div class="task-config-pill">{{ taskOrchestration.runMode === 'dry-run' ? t('orchestration.runMode.dryRun') : (taskOrchestration.runMode === 'read' ? t('orchestration.runMode.readOnly') : t('orchestration.runMode.write')) }}</div>
72
- <div v-if="taskOrchestration.title.trim()" class="task-config-pill">{{ t('orchestration.pills.hasTitle') }}</div>
73
- <div v-if="taskOrchestration.selectedEngine === 'workflow' && taskOrchestrationDraftMetrics.workflowCount > 0" class="task-config-pill">{{ t('orchestration.pills.workflowCount', { count: taskOrchestrationDraftMetrics.workflowCount }) }}</div>
74
- <div v-if="taskOrchestration.plan" class="task-config-pill">{{ t('orchestration.pills.planNodes', { count: taskOrchestrationDraftMetrics.planNodeCount }) }}</div>
208
+ <div class="task-thread-composer">
209
+ <label class="task-quick-target-field task-chat-composer">
210
+ <span class="selector-label">{{ t('orchestration.chat.input.label') }}</span>
211
+ <span class="task-composer-prompt-glyph" aria-hidden="true">›</span>
212
+ <textarea
213
+ v-model="taskOrchestration.chatDraft"
214
+ class="task-textarea task-textarea-goal task-quick-target"
215
+ rows="3"
216
+ :placeholder="t('orchestration.chat.input.placeholder')"
217
+ @keydown.enter.exact.prevent="submitTaskOrchestrationChatMessage()"></textarea>
218
+ <span class="task-field-hint">{{ t('orchestration.chat.input.hint') }}</span>
219
+ </label>
220
+ <div class="task-chat-send-row">
221
+ <div class="task-chat-action-buttons task-chat-primary-action">
222
+ <button type="button" class="btn-tool btn-primary task-chat-primary-button" @click="planAndRunTaskOrchestrationFromChat()" :disabled="taskOrchestration.running || taskOrchestration.planning || (!taskOrchestration.target.trim() && !taskOrchestration.chatDraft.trim())">
223
+ {{ taskOrchestration.running ? t('orchestration.actions.processing') : (taskOrchestration.planning ? t('orchestration.actions.planning') : t('orchestration.chat.input.work')) }}
224
+ </button>
225
+ </div>
226
+ <span class="task-field-hint">{{ taskOrchestrationDraftMetrics.hasTarget ? t('orchestration.chat.input.sequenceHint', { count: taskOrchestrationDraftMetrics.followUpCount + 2 }) : t('orchestration.chat.input.workHint') }}</span>
227
+ </div>
228
+ <div class="task-chat-execute-caption">
229
+ {{ t('orchestration.chat.input.workCaption') }}
230
+ </div>
231
+ <div class="task-chat-context-row task-chat-context-row-primary" role="group" :aria-label="t('orchestration.chat.context.aria')">
232
+ <span class="task-chat-context-chip task-chat-context-chip-strong">
233
+ <small>{{ t('orchestration.plan.summary.cwd') }}</small>
234
+ <strong>{{ (taskOrchestrationWorkspacePath || taskOrchestration.workspacePath.trim()) ? t('orchestration.privacy.workspace.selected') : t('orchestration.chat.context.workspace.auto') }}</strong>
235
+ </span>
236
+ <span class="task-chat-context-chip task-chat-context-chip-strong">
237
+ <small>{{ t('orchestration.plan.summary.threadId') }}</small>
238
+ <strong>{{ (taskOrchestration.threadId.trim() || (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.threadId)) ? t('orchestration.privacy.thread.selected') : t('orchestration.chat.context.thread.auto') }}</strong>
239
+ </span>
240
+ <span class="task-chat-context-chip">{{ taskOrchestrationDraftMetrics.hasTarget ? t('orchestration.chat.context.sequence.value', { count: taskOrchestrationDraftMetrics.requestCount }) : t('orchestration.chat.context.sequence.empty') }}</span>
241
+ <span class="task-chat-context-chip">{{ taskOrchestration.runMode === 'dry-run' ? t('orchestration.runMode.dryRun') : (taskOrchestration.runMode === 'read' ? t('orchestration.runMode.readOnly') : t('orchestration.runMode.write') ) }}</span>
242
+ <button type="button" class="task-chat-context-chip task-chat-context-action" @click="taskOrchestration.settingsOpen = true">{{ t('orchestration.advanced.open') }}</button>
243
+ </div>
244
+
245
+ <div class="task-action-caption">{{ t('orchestration.quick.caption') }}</div>
75
246
  </div>
76
247
  </div>
77
248
  </div>
78
249
 
79
- <div class="task-flow-section task-flow-section-compact">
80
- <div class="task-flow-head">
81
- <div class="task-flow-step">2</div>
82
- <div>
83
- <div class="task-flow-title">{{ t('orchestration.step2.title') }}</div>
84
- <div class="task-flow-copy">{{ t('orchestration.step2.subtitle') }}</div>
85
- </div>
86
- </div>
250
+ <aside class="task-quick-side-card">
251
+ <section
252
+ class="selector-section task-workbench-card task-side-workbench-card">
253
+ <div class="task-thread-card-label">{{ t('orchestration.workbench.title') }}</div>
254
+ <div class="task-thread-run-summary">
255
+ <span v-if="taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.run" :class="['pill', taskRunStatusTone(taskOrchestrationSelectedRun.run.status)]">{{ taskOrchestrationSelectedRun.run.status }}</span>
256
+ <span v-else-if="taskOrchestrationWorkspaceRuns.length" :class="['pill', taskRunStatusTone(taskOrchestrationWorkspaceRuns[0].status)]">{{ taskOrchestrationWorkspaceRuns[0].status }}</span>
257
+ <span v-else-if="taskOrchestrationWorkspaceQueue.length" class="pill neutral">{{ t('orchestration.workbench.queueCount', { count: taskOrchestrationWorkspaceQueue.length }) }}</span>
258
+ <span v-else class="pill empty">{{ t('orchestration.workbench.ready') }}</span>
259
+ <span v-if="taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.run" class="task-thread-run-summary-copy">{{ taskOrchestrationSelectedRun.run.summary || taskOrchestrationSelectedRun.run.runId || taskOrchestration.selectedRunId }}</span>
260
+ <span v-else-if="taskOrchestrationWorkspaceRuns.length" class="task-thread-run-summary-copy">{{ taskOrchestrationWorkspaceRuns[0].summary || taskOrchestrationWorkspaceRuns[0].runId }}</span>
261
+ <span v-else-if="taskOrchestrationWorkspaceQueue.length" class="task-thread-run-summary-copy">{{ taskOrchestrationWorkspaceQueue[0].title || taskOrchestrationWorkspaceQueue[0].target || taskOrchestrationWorkspaceQueue[0].taskId }}</span>
262
+ <span v-else class="task-thread-run-summary-copy">{{ t('orchestration.workbench.subtitle') }}</span>
263
+ </div>
264
+ <details class="task-thread-run-details" open>
265
+ <summary>{{ t('orchestration.agent.trace.title') }}</summary>
87
266
 
88
- <div class="selector-grid task-composer-grid task-composer-grid-compact task-composer-grid-inline">
89
- <label class="selector-field">
90
- <span class="selector-label">{{ t('orchestration.fields.engine') }}</span>
91
- <select v-model="taskOrchestration.selectedEngine" class="provider-fast-switch-select" disabled>
92
- <option value="codex">{{ t('orchestration.engine.codex') }}</option>
93
- <option value="workflow">{{ t('orchestration.engine.workflow') }}</option>
94
- </select>
95
- </label>
96
- <label class="selector-field">
97
- <span class="selector-label">{{ t('orchestration.fields.runMode') }}</span>
98
- <select v-model="taskOrchestration.runMode" class="provider-fast-switch-select" disabled>
99
- <option value="write">{{ t('orchestration.runMode.write') }}</option>
100
- <option value="read">{{ t('orchestration.runMode.readOnly') }}</option>
101
- <option value="dry-run">{{ t('orchestration.runMode.dryRun') }}</option>
102
- </select>
103
- </label>
104
- </div>
267
+ <div class="settings-tab-actions task-header-actions task-thread-detail-actions">
268
+ <button type="button" class="btn-tool btn-tool-compact" @click="loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })" :disabled="taskOrchestration.loading">
269
+ {{ taskOrchestration.loading ? t('common.refreshing') : t('common.refresh') }}
270
+ </button>
271
+ <button v-if="taskOrchestrationWorkspaceQueue.length" type="button" class="btn-tool btn-tool-compact" @click="startTaskQueueRunner()" :disabled="taskOrchestration.queueStarting">
272
+ {{ taskOrchestration.queueStarting ? t('orchestration.queue.starting') : t('orchestration.queue.start') }}
273
+ </button>
274
+ </div>
275
+
276
+ <div class="task-agent-trace-grid">
277
+ <div class="task-agent-trace-card">
278
+ <span>{{ t('orchestration.agent.trace.plan') }}</span>
279
+ <strong>{{ taskOrchestration.plan ? taskOrchestration.plan.nodes.length : 0 }}</strong>
280
+ </div>
281
+ <div class="task-agent-trace-card">
282
+ <span>{{ t('orchestration.agent.trace.queue') }}</span>
283
+ <strong>{{ taskOrchestrationWorkspaceQueue.length }}</strong>
284
+ </div>
285
+ <div class="task-agent-trace-card">
286
+ <span>{{ t('orchestration.agent.trace.runs') }}</span>
287
+ <strong>{{ taskOrchestrationWorkspaceRuns.length }}</strong>
288
+ </div>
289
+ <div class="task-agent-trace-card">
290
+ <span>{{ t('orchestration.agent.trace.nodes') }}</span>
291
+ <strong>{{ taskOrchestrationSelectedRunNodes.length }}</strong>
292
+ </div>
293
+ </div>
105
294
 
106
- <details class="task-advanced-panel">
107
- <summary class="task-advanced-summary">{{ t('orchestration.advanced.title') }}</summary>
295
+ <div
296
+ v-if="(taskOrchestrationWorkspaceQueue.length ? 1 : 0) + (taskOrchestrationWorkspaceRuns.length ? 1 : 0) + ((taskOrchestration.selectedRunId || taskOrchestration.selectedRunError) ? 1 : 0) > 1"
297
+ class="task-workbench-tabs"
298
+ role="group"
299
+ :aria-label="t('orchestration.workbench.tabs.aria')">
300
+ <button v-if="taskOrchestrationWorkspaceQueue.length" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'queue' }" @click="taskOrchestration.workspaceTab = 'queue'">{{ t('orchestration.workbench.tabs.queue', { count: taskOrchestrationWorkspaceQueue.length }) }}</button>
301
+ <button v-if="taskOrchestrationWorkspaceRuns.length" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'runs' }" @click="taskOrchestration.workspaceTab = 'runs'">{{ t('orchestration.workbench.tabs.runs', { count: taskOrchestrationWorkspaceRuns.length }) }}</button>
302
+ <button v-if="taskOrchestration.selectedRunId || taskOrchestration.selectedRunError" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'detail' }" @click="taskOrchestration.workspaceTab = 'detail'">{{ t('orchestration.workbench.tabs.detail') }}</button>
303
+ </div>
304
+
305
+ <div v-if="(taskOrchestrationWorkspaceQueue.length && taskOrchestration.workspaceTab === 'queue') || (!taskOrchestrationWorkspaceRuns.length && !taskOrchestration.selectedRunId && !taskOrchestration.selectedRunError)" class="task-workbench-panel">
306
+ <div v-if="!taskOrchestrationWorkspaceQueue.length" class="task-empty-state">
307
+ <div class="task-empty-title">{{ t('orchestration.queue.empty.title') }}</div>
308
+ <div class="task-empty-copy">{{ t('orchestration.queue.empty.subtitle') }}</div>
309
+ </div>
310
+ <div v-else class="task-runtime-list">
311
+ <div
312
+ v-for="item in taskOrchestrationWorkspaceQueue"
313
+ :key="item.taskId"
314
+ :class="['task-runtime-item', { active: item.lastRunId && taskOrchestration.selectedRunId === item.lastRunId, clickable: !!item.lastRunId }]"
315
+ :role="item.lastRunId ? 'button' : null"
316
+ :tabindex="item.lastRunId ? 0 : -1"
317
+ :aria-disabled="item.lastRunId ? null : 'true'"
318
+ @click="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null"
319
+ @keydown.enter.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null"
320
+ @keydown.space.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null">
321
+ <div class="task-runtime-item-main">
322
+ <div class="task-runtime-item-title">{{ item.title || item.target || item.taskId }}</div>
323
+ <div class="task-runtime-item-meta">{{ item.taskId }} · {{ item.updatedAt || item.createdAt }}</div>
324
+ <div v-if="item.threadId || item.cwd" class="task-runtime-item-meta">
325
+ <span v-if="item.threadId">{{ t('orchestration.plan.summary.threadId') }}: {{ item.threadId }}</span>
326
+ <span v-if="item.threadId && item.cwd"> · </span>
327
+ <span v-if="item.cwd">{{ t('orchestration.plan.summary.cwd') }}: {{ item.cwd }}</span>
328
+ </div>
329
+ <div v-if="item.lastSummary" class="task-runtime-item-summary">{{ item.lastSummary }}</div>
330
+ </div>
331
+ <div class="task-runtime-item-actions">
332
+ <span :class="['pill', taskRunStatusTone(item.status || item.runStatus)]">{{ item.status || item.runStatus }}</span>
333
+ <button v-if="isTaskRunActive(item.status || item.runStatus)" type="button" class="btn-mini" @click.stop="cancelTaskRunFromUi(item.taskId)">{{ t('common.cancel') }}</button>
334
+ </div>
335
+ </div>
336
+ </div>
337
+ </div>
338
+
339
+ <div v-else-if="taskOrchestration.workspaceTab === 'runs' || (!taskOrchestrationWorkspaceQueue.length && taskOrchestrationWorkspaceRuns.length && !taskOrchestration.selectedRunId && !taskOrchestration.selectedRunError)" class="task-workbench-panel">
340
+ <div v-if="!taskOrchestrationWorkspaceRuns.length" class="task-empty-state">
341
+ <div class="task-empty-title">{{ t('orchestration.runs.empty.title') }}</div>
342
+ <div class="task-empty-copy">{{ t('orchestration.runs.empty.subtitle') }}</div>
343
+ </div>
344
+ <div v-else class="task-runtime-list">
345
+ <button
346
+ v-for="item in taskOrchestrationWorkspaceRuns"
347
+ :key="item.runId"
348
+ type="button"
349
+ :class="['task-runtime-item', { active: taskOrchestration.selectedRunId === item.runId }]"
350
+ @click="taskOrchestration.workspaceTab = 'detail'; selectTaskRun(item.runId)">
351
+ <div class="task-runtime-item-main">
352
+ <div class="task-runtime-item-title">{{ item.title || item.taskId || item.runId }}</div>
353
+ <div class="task-runtime-item-meta">{{ item.runId }} · {{ item.durationMs || 0 }}ms</div>
354
+ <div v-if="item.threadId || item.cwd" class="task-runtime-item-meta">
355
+ <span v-if="item.threadId">{{ t('orchestration.plan.summary.threadId') }}: {{ item.threadId }}</span>
356
+ <span v-if="item.threadId && item.cwd"> · </span>
357
+ <span v-if="item.cwd">{{ t('orchestration.plan.summary.cwd') }}: {{ item.cwd }}</span>
358
+ </div>
359
+ <div v-if="item.summary" class="task-runtime-item-summary">{{ item.summary }}</div>
360
+ </div>
361
+ <div class="task-runtime-item-actions">
362
+ <span :class="['pill', taskRunStatusTone(item.status)]">{{ item.status }}</span>
363
+ </div>
364
+ </button>
365
+ </div>
366
+ </div>
367
+
368
+ <div v-else class="task-workbench-panel">
369
+ <div class="task-detail-toolbar settings-tab-actions">
370
+ <button type="button" class="btn-tool btn-tool-compact" @click="taskOrchestration.selectedRunId ? loadTaskRunDetail(taskOrchestration.selectedRunId) : null" :disabled="!taskOrchestration.selectedRunId || taskOrchestration.selectedRunLoading">
371
+ {{ taskOrchestration.selectedRunLoading ? t('common.refreshing') : t('orchestration.detail.refresh') }}
372
+ </button>
373
+ <button type="button" class="btn-tool btn-tool-compact" @click="retryTaskRunFromUi(taskOrchestration.selectedRunId)" :disabled="!taskOrchestration.selectedRunId || taskOrchestration.retrying">
374
+ {{ taskOrchestration.retrying ? t('orchestration.detail.retrying') : t('orchestration.detail.retry') }}
375
+ </button>
376
+ <button type="button" class="btn-tool btn-tool-compact" @click="continueTaskThreadFromUi" :disabled="!taskOrchestrationSelectedRun">
377
+ {{ t('orchestration.detail.continueThread') }}
378
+ </button>
379
+ <button v-if="taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.run && isTaskRunActive(taskOrchestrationSelectedRun.run.status)" type="button" class="btn-tool btn-tool-compact" @click="cancelTaskRunFromUi(taskOrchestration.selectedRunId)">
380
+ {{ t('common.cancel') }}
381
+ </button>
382
+ </div>
383
+
384
+ <div v-if="taskOrchestration.selectedRunError" class="task-issue-item">{{ taskOrchestration.selectedRunError }}</div>
385
+ <div v-if="!taskOrchestrationSelectedRun" class="task-empty-state">
386
+ <div class="task-empty-title">{{ t('orchestration.detail.empty.title') }}</div>
387
+ <div class="task-empty-copy">{{ t('orchestration.detail.empty.subtitle') }}</div>
388
+ </div>
389
+ <template v-else>
390
+ <div class="task-detail-summary-strip">
391
+ <div class="task-plan-summary-item">
392
+ <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.status') }}</span>
393
+ <strong>{{ taskOrchestrationSelectedRun.run.status }}</strong>
394
+ </div>
395
+ <div class="task-plan-summary-item">
396
+ <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.duration') }}</span>
397
+ <strong>{{ taskOrchestrationSelectedRun.run.durationMs || 0 }}ms</strong>
398
+ </div>
399
+ <div class="task-plan-summary-item">
400
+ <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.nodes') }}</span>
401
+ <strong>{{ taskOrchestrationSelectedRunNodes.length }}</strong>
402
+ </div>
403
+ <div class="task-plan-summary-item">
404
+ <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.summary') }}</span>
405
+ <strong>{{ taskOrchestrationSelectedRun.run.summary || t('common.none') }}</strong>
406
+ </div>
407
+ <div v-if="taskOrchestrationSelectedRun.threadId" class="task-plan-summary-item">
408
+ <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.threadId') }}</span>
409
+ <strong>{{ taskOrchestrationSelectedRun.threadId }}</strong>
410
+ </div>
411
+ <div v-if="taskOrchestrationSelectedRun.cwd" class="task-plan-summary-item">
412
+ <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.cwd') }}</span>
413
+ <strong>{{ taskOrchestrationSelectedRun.cwd }}</strong>
414
+ </div>
415
+ </div>
416
+ <div v-if="taskOrchestrationSelectedRun.run.error" class="task-issue-item">{{ taskOrchestrationSelectedRun.run.error }}</div>
417
+ <div class="task-node-list">
418
+ <div v-for="node in taskOrchestrationSelectedRunNodes" :key="node.id" class="task-node-card task-node-card-detail">
419
+ <div class="task-node-head">
420
+ <div>
421
+ <div class="task-node-title">{{ node.title || node.id }}</div>
422
+ <div class="task-node-meta">{{ t('orchestration.detail.node.meta', { id: node.id, attempts: (node.attemptCount || 0), autoFix: (node.autoFixRounds || 0) }) }}</div>
423
+ </div>
424
+ <span :class="['pill', taskRunStatusTone(node.status)]">{{ node.status }}</span>
425
+ </div>
426
+ <div v-if="node.summary" class="task-runtime-item-summary">{{ node.summary }}</div>
427
+ <div v-if="node.error && node.error !== node.summary" class="task-node-deps">{{ t('orchestration.labels.error') }}{{ node.error }}</div>
428
+ <div class="task-node-deps">{{ t('orchestration.labels.dependencies') }}{{ formatTaskNodeDependencies(node) }}</div>
429
+ <div v-if="node.output && typeof node.output === 'object'" class="task-node-output-card">
430
+ <div class="task-node-output-head">
431
+ <strong>{{ t('orchestration.detail.node.output') }}</strong>
432
+ <span v-if="node.output.provider || node.output.model" class="task-node-output-meta">{{ node.output.provider || '' }}<template v-if="node.output.provider && node.output.model"> · </template>{{ node.output.model || '' }}</span>
433
+ </div>
434
+ <div class="task-node-output-facts">
435
+ <span v-if="node.output.endpoint">{{ t('orchestration.detail.node.endpoint') }}{{ node.output.endpoint }}</span>
436
+ <span v-if="node.output.status">HTTP {{ node.output.status }}</span>
437
+ <span v-if="node.output.durationMs">{{ node.output.durationMs }}ms</span>
438
+ </div>
439
+ <pre class="task-log-block task-output-block">{{ formatTaskNodeOutputText(node) }}</pre>
440
+ <div v-if="node.output.materializedFiles && node.output.materializedFiles.length" class="task-materialized-files">
441
+ <div class="task-node-deps">{{ t('orchestration.detail.node.materializedFiles') }}</div>
442
+ <div v-for="file in node.output.materializedFiles" :key="file.path || file.relativePath" class="task-materialized-file">{{ file.relativePath || file.path }} · {{ file.bytes || 0 }} bytes</div>
443
+ </div>
444
+ <div v-if="node.output.workspaceFiles && node.output.workspaceFiles.length" class="task-materialized-files task-workspace-files">
445
+ <div class="task-node-deps">{{ t('orchestration.detail.node.workspaceFiles') }}</div>
446
+ <div v-for="file in node.output.workspaceFiles" :key="file.path || file.relativePath" class="task-materialized-file">{{ [String(file.operation || '').trim().toUpperCase(), String(file.relativePath || file.path || '').trim(), Number.isFinite(Number(file.bytes)) ? (Number(file.bytes) + ' bytes') : '', typeof file.existed === 'boolean' ? (file.existed ? 'existed' : 'new') : ''].filter(Boolean).join(' · ') }}</div>
447
+ </div>
448
+ </div>
449
+ <pre class="task-log-block">{{ formatTaskNodeLogs(node.logs) }}</pre>
450
+ </div>
451
+ </div>
452
+ </template>
453
+ </div>
454
+ </details>
455
+ </section>
456
+ <details class="selector-section task-side-settings-card task-advanced-panel task-quick-advanced" :open="taskOrchestration.settingsOpen" @toggle="taskOrchestration.settingsOpen = $event.target.open">
457
+ <summary class="task-advanced-summary">{{ t('orchestration.advanced.title') }}</summary>
458
+ <div class="task-action-row-right task-action-row-right-prominent task-codex-queue-action">
459
+ <button type="button" class="btn-tool" @click="queueTaskOrchestrationAndStart()" :disabled="taskOrchestration.queueAdding || taskOrchestration.queueStarting || taskOrchestration.planning || !taskOrchestration.target.trim()">
460
+ {{ (taskOrchestration.queueAdding || taskOrchestration.queueStarting) ? t('orchestration.actions.processing') : t('orchestration.actions.queueAndStart') }}
461
+ </button>
462
+ </div>
108
463
  <div class="selector-grid task-composer-grid task-composer-grid-secondary">
109
464
  <label class="selector-field task-field-wide">
110
465
  <span class="selector-label">{{ t('orchestration.fields.title') }}</span>
111
466
  <input v-model="taskOrchestration.title" class="model-input" type="text" :placeholder="t('orchestration.fields.title.placeholder')">
112
467
  </label>
468
+ <label class="selector-field">
469
+ <span class="selector-label">{{ t('orchestration.fields.engine') }}</span>
470
+ <select v-model="taskOrchestration.selectedEngine" class="provider-fast-switch-select" @change="taskOrchestration.selectedEngine === 'workflow' ? null : taskOrchestration.workflowIdsText = ''">
471
+ <option value="openai-chat">{{ t('orchestration.engine.openaiChat') }}</option>
472
+ <option value="workflow">{{ t('orchestration.engine.workflow') }}</option>
473
+ </select>
474
+ </label>
475
+ <label class="selector-field">
476
+ <span class="selector-label">{{ t('orchestration.fields.runMode') }}</span>
477
+ <select v-model="taskOrchestration.runMode" class="provider-fast-switch-select">
478
+ <option value="write">{{ t('orchestration.runMode.write') }}</option>
479
+ <option value="read">{{ t('orchestration.runMode.readOnly') }}</option>
480
+ <option value="dry-run">{{ t('orchestration.runMode.dryRun') }}</option>
481
+ </select>
482
+ </label>
113
483
  <label class="selector-field task-field-wide">
114
484
  <span class="selector-label">{{ t('orchestration.fields.notes') }}</span>
115
485
  <textarea v-model="taskOrchestration.notes" class="task-textarea" rows="3" :placeholder="t('orchestration.fields.notes.placeholder')"></textarea>
116
486
  <span class="task-field-hint">{{ t('orchestration.fields.notes.hint') }}</span>
117
487
  </label>
488
+ <label class="selector-field task-field-wide">
489
+ <span class="selector-label">{{ t('orchestration.fields.workspacePath') }}</span>
490
+ <input v-model="taskOrchestration.workspacePath" class="model-input" type="text" :placeholder="t('orchestration.fields.workspacePath.placeholder')">
491
+ <span class="task-field-hint">{{ t('orchestration.fields.workspacePath.hint') }}</span>
492
+ </label>
493
+ <label class="selector-field">
494
+ <span class="selector-label">{{ t('orchestration.fields.threadId') }}</span>
495
+ <input v-model="taskOrchestration.threadId" class="model-input" type="text" :placeholder="t('orchestration.fields.threadId.placeholder')">
496
+ <span class="task-field-hint">{{ t('orchestration.fields.threadId.hint') }}</span>
497
+ </label>
118
498
  <label class="selector-field task-field-wide">
119
499
  <span class="selector-label">{{ t('orchestration.fields.followUps') }}</span>
120
500
  <textarea v-model="taskOrchestration.followUpsText" class="task-textarea" rows="3" :placeholder="t('orchestration.fields.followUps.placeholder')"></textarea>
@@ -147,36 +527,60 @@
147
527
  </label>
148
528
  </div>
149
529
  </details>
150
- </div>
151
-
152
- <div class="task-flow-section task-flow-section-actions task-flow-section-compact">
153
- <div class="task-flow-head">
154
- <div class="task-flow-step">3</div>
530
+ <div class="task-provider-status-card">
531
+ <div class="task-readiness-title">{{ t('orchestration.openai.status.title') }}</div>
532
+ <div class="task-readiness-copy">{{ t('orchestration.openai.status.subtitle') }}</div>
533
+ <div v-if="taskOrchestration.openAiChatStatus" class="task-provider-status-grid">
534
+ <div class="task-provider-status-row">
535
+ <span>{{ t('orchestration.openai.status.provider') }}</span>
536
+ <strong>{{ taskOrchestration.openAiChatStatus.providerName ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }}</strong>
537
+ </div>
538
+ <div class="task-provider-status-row">
539
+ <span>{{ t('orchestration.openai.status.model') }}</span>
540
+ <strong>{{ taskOrchestration.openAiChatStatus.model ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }}</strong>
541
+ </div>
542
+ <div class="task-provider-status-row task-provider-status-row-wide">
543
+ <span>{{ t('orchestration.openai.status.endpoint') }}</span>
544
+ <strong>{{ taskOrchestration.openAiChatStatus.endpoint ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }}</strong>
545
+ </div>
546
+ <div class="task-provider-status-row">
547
+ <span>{{ t('orchestration.openai.status.apiKey') }}</span>
548
+ <strong>{{ taskOrchestration.openAiChatStatus.hasApiKey ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.missing') }}</strong>
549
+ </div>
550
+ <div class="task-provider-status-row">
551
+ <span>{{ t('orchestration.openai.status.headers') }}</span>
552
+ <strong>{{ taskOrchestration.openAiChatStatus.hasExtraHeaders ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }}</strong>
553
+ </div>
554
+ </div>
555
+ <div v-else class="task-readiness-copy">{{ t('orchestration.openai.status.notLoaded') }}</div>
556
+ <div v-if="taskOrchestration.openAiChatStatus && taskOrchestration.openAiChatStatus.error" class="task-issue-item task-provider-status-error">{{ taskOrchestration.openAiChatStatus.error }}</div>
557
+ <button type="button" class="btn-tool btn-tool-compact task-provider-config-button" @click="openTaskOpenAiChatConfig()">
558
+ {{ t('orchestration.openai.status.configure') }}
559
+ </button>
560
+ </div>
561
+ <div class="task-readiness-head">
155
562
  <div>
156
- <div class="task-flow-title">{{ t('orchestration.step3.title') }}</div>
157
- <div class="task-flow-copy">{{ t('orchestration.step3.subtitle') }}</div>
563
+ <div class="task-readiness-title">{{ t('orchestration.quick.checklist.title') }}</div>
564
+ <div class="task-readiness-copy">{{ t('orchestration.quick.checklist.subtitle') }}</div>
158
565
  </div>
159
566
  </div>
160
- <div class="task-action-row task-action-row-prominent">
161
- <button type="button" class="btn-tool task-action-preview" @click="previewTaskPlan()" :disabled="taskOrchestration.planning || taskOrchestration.running || !taskOrchestration.target.trim()">
162
- {{ taskOrchestration.planning ? t('orchestration.actions.planning') : t('orchestration.actions.previewOnly') }}
163
- </button>
164
- <div class="task-action-row-right task-action-row-right-prominent">
165
- <button type="button" class="btn-tool btn-primary" @click="planAndRunTaskOrchestration()" :disabled="taskOrchestration.running || taskOrchestration.planning || !taskOrchestration.target.trim()">
166
- {{ (taskOrchestration.running || taskOrchestration.planning) ? t('orchestration.actions.preparing') : (taskOrchestration.runMode === 'dry-run' ? t('orchestration.actions.generatePlan') : t('orchestration.actions.planAndRun')) }}
167
- </button>
168
- <button type="button" class="btn-tool" @click="queueTaskOrchestrationAndStart()" :disabled="taskOrchestration.queueAdding || taskOrchestration.queueStarting || taskOrchestration.planning || !taskOrchestration.target.trim()">
169
- {{ (taskOrchestration.queueAdding || taskOrchestration.queueStarting) ? t('orchestration.actions.processing') : t('orchestration.actions.queueAndStart') }}
170
- </button>
567
+ <div class="task-readiness-grid task-quick-checklist">
568
+ <div v-for="item in taskOrchestrationDraftChecklist" :key="item.key" :class="['task-readiness-item', { done: item.done }]">
569
+ <strong>{{ item.label }}</strong>
570
+ <span>{{ item.detail }}</span>
171
571
  </div>
172
572
  </div>
173
- <div class="task-action-caption">{{ t('orchestration.actions.caption') }}</div>
174
- </div>
573
+ <div class="task-quick-status-card">
574
+ <div class="task-plan-summary-label">{{ t('orchestration.quick.status.title') }}</div>
575
+ <strong>{{ taskOrchestrationDraftReadiness.title }}</strong>
576
+ <span>{{ taskOrchestrationDraftReadiness.summary }}</span>
577
+ </div>
578
+ </aside>
175
579
  </section>
176
580
  </div>
177
581
 
178
582
  <section
179
- v-if="!(taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError || taskOrchestration.queue.length || taskOrchestration.runs.length || taskOrchestration.selectedRunId || taskOrchestration.selectedRunError)"
583
+ v-if="!(taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError || taskOrchestrationWorkspaceQueue.length || taskOrchestrationWorkspaceRuns.length || taskOrchestration.selectedRunId || taskOrchestration.selectedRunError)"
180
584
  class="selector-section task-stage-card">
181
585
  <div class="task-stage-empty">
182
586
  <div>
@@ -191,201 +595,4 @@
191
595
  </div>
192
596
  </section>
193
597
 
194
- <div
195
- v-else
196
- class="task-layout-grid task-layout-grid-secondary">
197
- <section
198
- v-if="taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError"
199
- class="selector-section task-plan-card">
200
- <div v-if="taskOrchestration.lastError" class="task-issue-item">{{ taskOrchestration.lastError }}</div>
201
- <div class="selector-header task-section-header">
202
- <div>
203
- <span class="selector-title">{{ t('orchestration.plan.title') }}</span>
204
- <div class="skills-panel-note">{{ t('orchestration.plan.subtitle') }}</div>
205
- </div>
206
- </div>
207
- <div v-if="taskOrchestration.planIssues.length" class="task-issues-list">
208
- <div v-for="issue in taskOrchestration.planIssues" :key="issue.code + issue.message" class="task-issue-item">
209
- {{ issue.message }}
210
- </div>
211
- </div>
212
- <div v-if="taskOrchestration.planWarnings.length" class="task-warning-list">
213
- <div v-for="warning in taskOrchestration.planWarnings" :key="warning" class="task-warning-item">
214
- {{ warning }}
215
- </div>
216
- </div>
217
- <template v-if="taskOrchestration.plan">
218
- <div class="task-plan-summary-strip">
219
- <div class="task-plan-summary-item">
220
- <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.nodes') }}</span>
221
- <strong>{{ taskOrchestration.plan.nodes.length }}</strong>
222
- </div>
223
- <div class="task-plan-summary-item">
224
- <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.waves') }}</span>
225
- <strong>{{ taskOrchestration.plan.waves.length }}</strong>
226
- </div>
227
- <div class="task-plan-summary-item">
228
- <span class="task-plan-summary-label">{{ t('orchestration.plan.summary.engine') }}</span>
229
- <strong>{{ taskOrchestration.plan.engine }}</strong>
230
- </div>
231
- </div>
232
- <div class="task-wave-list">
233
- <div v-for="wave in taskOrchestration.plan.waves" :key="wave.label" class="task-wave-card">
234
- <div class="task-wave-title">{{ wave.label }}</div>
235
- <div class="task-wave-nodes">{{ wave.nodeIds.join(', ') }}</div>
236
- </div>
237
- </div>
238
- <div class="task-node-list">
239
- <div v-for="node in taskOrchestration.plan.nodes" :key="node.id" class="task-node-card">
240
- <div class="task-node-head">
241
- <div>
242
- <div class="task-node-title">{{ node.title || node.id }}</div>
243
- <div class="task-node-meta">{{ node.id }} · {{ node.kind }}<span v-if="node.workflowId"> · {{ node.workflowId }}</span></div>
244
- </div>
245
- <span :class="['pill', node.write ? 'configured' : 'empty']">{{ node.write ? t('orchestration.plan.node.write') : t('orchestration.plan.node.readOnly') }}</span>
246
- </div>
247
- <div class="task-node-deps">{{ t('orchestration.labels.dependencies') }}{{ formatTaskNodeDependencies(node) }}</div>
248
- </div>
249
- </div>
250
- </template>
251
- </section>
252
-
253
- <section
254
- v-if="taskOrchestration.queue.length || taskOrchestration.runs.length || taskOrchestration.selectedRunId || taskOrchestration.selectedRunError"
255
- class="selector-section task-workbench-card">
256
- <div class="selector-header task-section-header">
257
- <div>
258
- <span class="selector-title">{{ t('orchestration.workbench.title') }}</span>
259
- <div class="skills-panel-note">{{ t('orchestration.workbench.subtitle') }}</div>
260
- </div>
261
- <div class="settings-tab-actions task-header-actions">
262
- <button type="button" class="btn-tool btn-tool-compact" @click="loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })" :disabled="taskOrchestration.loading">
263
- {{ taskOrchestration.loading ? t('common.refreshing') : t('common.refresh') }}
264
- </button>
265
- <button v-if="taskOrchestration.queue.length" type="button" class="btn-tool btn-tool-compact" @click="startTaskQueueRunner()" :disabled="taskOrchestration.queueStarting">
266
- {{ taskOrchestration.queueStarting ? t('orchestration.queue.starting') : t('orchestration.queue.start') }}
267
- </button>
268
- </div>
269
- </div>
270
-
271
- <div
272
- v-if="(taskOrchestration.queue.length ? 1 : 0) + (taskOrchestration.runs.length ? 1 : 0) + ((taskOrchestration.selectedRunId || taskOrchestration.selectedRunError) ? 1 : 0) > 1"
273
- class="task-workbench-tabs"
274
- role="group"
275
- :aria-label="t('orchestration.workbench.tabs.aria')">
276
- <button v-if="taskOrchestration.queue.length" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'queue' }" @click="taskOrchestration.workspaceTab = 'queue'">{{ t('orchestration.workbench.tabs.queue', { count: taskOrchestration.queue.length }) }}</button>
277
- <button v-if="taskOrchestration.runs.length" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'runs' }" @click="taskOrchestration.workspaceTab = 'runs'">{{ t('orchestration.workbench.tabs.runs', { count: taskOrchestration.runs.length }) }}</button>
278
- <button v-if="taskOrchestration.selectedRunId || taskOrchestration.selectedRunError" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'detail' }" @click="taskOrchestration.workspaceTab = 'detail'">{{ t('orchestration.workbench.tabs.detail') }}</button>
279
- </div>
280
-
281
- <div v-if="taskOrchestration.workspaceTab === 'queue' || (!taskOrchestration.runs.length && !taskOrchestration.selectedRunId && !taskOrchestration.selectedRunError)" class="task-workbench-panel">
282
- <div v-if="!taskOrchestration.queue.length" class="task-empty-state">
283
- <div class="task-empty-title">{{ t('orchestration.queue.empty.title') }}</div>
284
- <div class="task-empty-copy">{{ t('orchestration.queue.empty.subtitle') }}</div>
285
- </div>
286
- <div v-else class="task-runtime-list">
287
- <div
288
- v-for="item in taskOrchestration.queue"
289
- :key="item.taskId"
290
- :class="['task-runtime-item', { active: item.lastRunId && taskOrchestration.selectedRunId === item.lastRunId, clickable: !!item.lastRunId }]"
291
- :role="item.lastRunId ? 'button' : null"
292
- :tabindex="item.lastRunId ? 0 : -1"
293
- :aria-disabled="item.lastRunId ? null : 'true'"
294
- @click="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null"
295
- @keydown.enter.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null"
296
- @keydown.space.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null">
297
- <div class="task-runtime-item-main">
298
- <div class="task-runtime-item-title">{{ item.title || item.target || item.taskId }}</div>
299
- <div class="task-runtime-item-meta">{{ item.taskId }} · {{ item.updatedAt || item.createdAt }}</div>
300
- <div v-if="item.lastSummary" class="task-runtime-item-summary">{{ item.lastSummary }}</div>
301
- </div>
302
- <div class="task-runtime-item-actions">
303
- <span :class="['pill', taskRunStatusTone(item.status)]">{{ item.status }}</span>
304
- <button type="button" class="btn-mini" @click.stop="cancelTaskRunFromUi(item.taskId)" :disabled="item.status !== 'queued' && item.status !== 'running'">{{ t('common.cancel') }}</button>
305
- </div>
306
- </div>
307
- </div>
308
- </div>
309
-
310
- <div v-else-if="taskOrchestration.workspaceTab === 'runs' || (!taskOrchestration.queue.length && taskOrchestration.runs.length && !taskOrchestration.selectedRunId && !taskOrchestration.selectedRunError)" class="task-workbench-panel">
311
- <div v-if="!taskOrchestration.runs.length" class="task-empty-state">
312
- <div class="task-empty-title">{{ t('orchestration.runs.empty.title') }}</div>
313
- <div class="task-empty-copy">{{ t('orchestration.runs.empty.subtitle') }}</div>
314
- </div>
315
- <div v-else class="task-runtime-list">
316
- <button
317
- v-for="item in taskOrchestration.runs"
318
- :key="item.runId"
319
- type="button"
320
- :class="['task-runtime-item', { active: taskOrchestration.selectedRunId === item.runId }]"
321
- @click="taskOrchestration.workspaceTab = 'detail'; selectTaskRun(item.runId)">
322
- <div class="task-runtime-item-main">
323
- <div class="task-runtime-item-title">{{ item.title || item.taskId || item.runId }}</div>
324
- <div class="task-runtime-item-meta">{{ item.runId }} · {{ item.durationMs || 0 }}ms</div>
325
- <div v-if="item.summary" class="task-runtime-item-summary">{{ item.summary }}</div>
326
- </div>
327
- <div class="task-runtime-item-actions">
328
- <span :class="['pill', taskRunStatusTone(item.status)]">{{ item.status }}</span>
329
- </div>
330
- </button>
331
- </div>
332
- </div>
333
-
334
- <div v-else class="task-workbench-panel">
335
- <div class="task-detail-toolbar settings-tab-actions">
336
- <button type="button" class="btn-tool btn-tool-compact" @click="taskOrchestration.selectedRunId ? loadTaskRunDetail(taskOrchestration.selectedRunId) : null" :disabled="!taskOrchestration.selectedRunId || taskOrchestration.selectedRunLoading">
337
- {{ taskOrchestration.selectedRunLoading ? t('common.refreshing') : t('orchestration.detail.refresh') }}
338
- </button>
339
- <button type="button" class="btn-tool btn-tool-compact" @click="retryTaskRunFromUi(taskOrchestration.selectedRunId)" :disabled="!taskOrchestration.selectedRunId || taskOrchestration.retrying">
340
- {{ taskOrchestration.retrying ? t('orchestration.detail.retrying') : t('orchestration.detail.retry') }}
341
- </button>
342
- <button type="button" class="btn-tool btn-tool-compact" @click="cancelTaskRunFromUi(taskOrchestration.selectedRunId)" :disabled="!taskOrchestrationSelectedRun || !taskOrchestrationSelectedRun.run || !isTaskRunActive(taskOrchestrationSelectedRun.run.status)">
343
- {{ t('common.cancel') }}
344
- </button>
345
- </div>
346
-
347
- <div v-if="taskOrchestration.selectedRunError" class="task-issue-item">{{ taskOrchestration.selectedRunError }}</div>
348
- <div v-if="!taskOrchestrationSelectedRun" class="task-empty-state">
349
- <div class="task-empty-title">{{ t('orchestration.detail.empty.title') }}</div>
350
- <div class="task-empty-copy">{{ t('orchestration.detail.empty.subtitle') }}</div>
351
- </div>
352
- <template v-else>
353
- <div class="task-detail-summary-strip">
354
- <div class="task-plan-summary-item">
355
- <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.status') }}</span>
356
- <strong>{{ taskOrchestrationSelectedRun.run.status }}</strong>
357
- </div>
358
- <div class="task-plan-summary-item">
359
- <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.duration') }}</span>
360
- <strong>{{ taskOrchestrationSelectedRun.run.durationMs || 0 }}ms</strong>
361
- </div>
362
- <div class="task-plan-summary-item">
363
- <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.nodes') }}</span>
364
- <strong>{{ taskOrchestrationSelectedRunNodes.length }}</strong>
365
- </div>
366
- <div class="task-plan-summary-item">
367
- <span class="task-plan-summary-label">{{ t('orchestration.detail.summary.summary') }}</span>
368
- <strong>{{ taskOrchestrationSelectedRun.run.summary || t('common.none') }}</strong>
369
- </div>
370
- </div>
371
- <div v-if="taskOrchestrationSelectedRun.run.error" class="task-issue-item">{{ taskOrchestrationSelectedRun.run.error }}</div>
372
- <div class="task-node-list">
373
- <div v-for="node in taskOrchestrationSelectedRunNodes" :key="node.id" class="task-node-card task-node-card-detail">
374
- <div class="task-node-head">
375
- <div>
376
- <div class="task-node-title">{{ node.title || node.id }}</div>
377
- <div class="task-node-meta">{{ t('orchestration.detail.node.meta', { id: node.id, attempts: (node.attemptCount || 0), autoFix: (node.autoFixRounds || 0) }) }}</div>
378
- </div>
379
- <span :class="['pill', taskRunStatusTone(node.status)]">{{ node.status }}</span>
380
- </div>
381
- <div v-if="node.summary" class="task-runtime-item-summary">{{ node.summary }}</div>
382
- <div v-if="node.error && node.error !== node.summary" class="task-node-deps">{{ t('orchestration.labels.error') }}{{ node.error }}</div>
383
- <div class="task-node-deps">{{ t('orchestration.labels.dependencies') }}{{ formatTaskNodeDependencies(node) }}</div>
384
- <pre class="task-log-block">{{ formatTaskNodeLogs(node.logs) }}</pre>
385
- </div>
386
- </div>
387
- </template>
388
- </div>
389
- </section>
390
- </div>
391
598
  </div>