codexmate 0.0.56 → 0.1.1

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 (42) hide show
  1. package/README.md +2 -2
  2. package/README.vi.md +1 -0
  3. package/README.zh.md +2 -2
  4. package/cli/openai-bridge-retry.js +62 -0
  5. package/cli/openai-bridge-runtime.js +1819 -0
  6. package/cli/openai-bridge.js +137 -2048
  7. package/cli.js +749 -133
  8. package/lib/task-orchestrator.js +90 -21
  9. package/lib/task-workspace-chat.js +292 -0
  10. package/package.json +2 -2
  11. package/web-ui/app.js +67 -131
  12. package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
  13. package/web-ui/modules/app.methods.agents.mjs +192 -1
  14. package/web-ui/modules/app.methods.claude-config.mjs +65 -25
  15. package/web-ui/modules/app.methods.navigation.mjs +67 -83
  16. package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
  17. package/web-ui/modules/app.methods.providers.mjs +40 -10
  18. package/web-ui/modules/app.methods.session-actions.mjs +1 -12
  19. package/web-ui/modules/app.methods.session-browser.mjs +23 -54
  20. package/web-ui/modules/app.methods.session-trash.mjs +0 -1
  21. package/web-ui/modules/app.methods.startup-claude.mjs +16 -31
  22. package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
  23. package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
  24. package/web-ui/modules/app.methods.web-ui-preferences.mjs +442 -68
  25. package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
  26. package/web-ui/modules/i18n/locales/en.mjs +181 -30
  27. package/web-ui/modules/i18n/locales/ja.mjs +178 -27
  28. package/web-ui/modules/i18n/locales/vi.mjs +180 -29
  29. package/web-ui/modules/i18n/locales/zh-tw.mjs +181 -30
  30. package/web-ui/modules/i18n/locales/zh.mjs +183 -32
  31. package/web-ui/modules/i18n.mjs +5 -12
  32. package/web-ui/modules/sessions-filters-url.mjs +1 -2
  33. package/web-ui/partials/index/layout-header.html +44 -38
  34. package/web-ui/partials/index/modals-basic.html +26 -0
  35. package/web-ui/partials/index/panel-orchestration.html +489 -282
  36. package/web-ui/partials/index/panel-prompts.html +66 -3
  37. package/web-ui/res/web-ui-render.precompiled.js +1181 -604
  38. package/web-ui/styles/layout-shell.css +157 -1
  39. package/web-ui/styles/modals-core.css +173 -0
  40. package/web-ui/styles/navigation-panels.css +11 -0
  41. package/web-ui/styles/responsive.css +32 -0
  42. package/web-ui/styles/task-orchestration.css +2161 -4
@@ -12,7 +12,8 @@ function readTaskOrchestrationDraftMetrics(taskOrchestration) {
12
12
  const title = String(state.title || '').trim();
13
13
  const workflowIds = normalizeTaskDraftLines(state.workflowIdsText);
14
14
  const followUps = normalizeTaskDraftLines(state.followUpsText);
15
- const engine = String(state.selectedEngine || 'codex').trim().toLowerCase() === 'workflow' ? 'workflow' : 'codex';
15
+ const requestCount = target ? followUps.length + 1 : 0;
16
+ const engine = String(state.selectedEngine || 'openai-chat').trim().toLowerCase() === 'workflow' ? 'workflow' : 'openai-chat';
16
17
  const runMode = String(state.runMode || 'write').trim().toLowerCase();
17
18
  const allowWrite = runMode === 'write';
18
19
  const dryRun = runMode === 'dry-run';
@@ -37,12 +38,234 @@ function readTaskOrchestrationDraftMetrics(taskOrchestration) {
37
38
  planWarnings,
38
39
  workflowCount: workflowIds.length,
39
40
  followUpCount: followUps.length,
41
+ requestCount,
42
+ hasSequentialFollowUps: followUps.length > 0,
40
43
  planNodeCount: planNodes.length,
41
44
  allowWrite,
42
45
  dryRun
43
46
  };
44
47
  }
45
48
 
49
+
50
+ function formatTaskConversationMeta(items) {
51
+ return items.filter(Boolean).join(' · ');
52
+ }
53
+
54
+ function collectTaskRunWorkspaceFileLines(detail) {
55
+ const run = detail && detail.run && typeof detail.run === 'object' ? detail.run : null;
56
+ const nodes = Array.isArray(detail && detail.nodes)
57
+ ? detail.nodes
58
+ : (Array.isArray(run && run.nodes) ? run.nodes : []);
59
+ const files = [];
60
+ for (const node of nodes) {
61
+ const output = node && node.output && typeof node.output === 'object' ? node.output : null;
62
+ const workspaceFiles = Array.isArray(output && output.workspaceFiles) ? output.workspaceFiles : [];
63
+ const materializedFiles = Array.isArray(output && output.materializedFiles) ? output.materializedFiles : [];
64
+ for (const file of workspaceFiles) {
65
+ if (!file || typeof file !== 'object') continue;
66
+ const op = String(file.operation || '').trim().toUpperCase() || 'FILE';
67
+ const filePath = String(file.relativePath || file.path || '').trim();
68
+ if (filePath) files.push(`${op} ${filePath}`);
69
+ }
70
+ for (const file of materializedFiles) {
71
+ if (!file || typeof file !== 'object') continue;
72
+ const filePath = String(file.relativePath || file.path || '').trim();
73
+ if (filePath) files.push(`WRITE ${filePath}`);
74
+ }
75
+ }
76
+ return Array.from(new Set(files)).slice(0, 6);
77
+ }
78
+
79
+ function normalizeTaskWorkspacePath(value) {
80
+ return String(value || '').trim().replace(/\\+/g, '/').replace(/\/$/, '');
81
+ }
82
+
83
+ function readTaskWorkspacePathFromItem(item) {
84
+ if (!item || typeof item !== 'object') return '';
85
+ return normalizeTaskWorkspacePath(item.cwd || item.workspacePath || item.projectPath || item.path);
86
+ }
87
+
88
+ function formatTaskWorkspaceLabel(workspacePath, fallback = 'Auto workspace') {
89
+ const normalized = normalizeTaskWorkspacePath(workspacePath);
90
+ if (!normalized) return fallback;
91
+ const parts = normalized.split('/').filter(Boolean);
92
+ return parts.length ? parts[parts.length - 1] : normalized;
93
+ }
94
+
95
+ function readTaskItemTimestamp(item) {
96
+ if (!item || typeof item !== 'object') return '';
97
+ return String(item.updatedAt || item.completedAt || item.startedAt || item.createdAt || item.enqueuedAt || '').trim();
98
+ }
99
+
100
+ function addTaskWorkspaceCandidate(workspaces, workspacePath, source, timestamp = '') {
101
+ const normalized = normalizeTaskWorkspacePath(workspacePath);
102
+ if (!normalized) return;
103
+ const key = normalized.toLowerCase();
104
+ const current = workspaces.get(key) || {
105
+ key,
106
+ path: normalized,
107
+ label: formatTaskWorkspaceLabel(normalized),
108
+ meta: normalized,
109
+ queueCount: 0,
110
+ runCount: 0,
111
+ lastActivity: ''
112
+ };
113
+ if (source === 'queue') current.queueCount += 1;
114
+ if (source === 'run') current.runCount += 1;
115
+ if (timestamp && (!current.lastActivity || timestamp > current.lastActivity)) {
116
+ current.lastActivity = timestamp;
117
+ }
118
+ workspaces.set(key, current);
119
+ }
120
+
121
+ function taskItemMatchesWorkspace(item, workspacePath) {
122
+ const selectedWorkspacePath = normalizeTaskWorkspacePath(workspacePath);
123
+ if (!selectedWorkspacePath) return true;
124
+ return readTaskWorkspacePathFromItem(item) === selectedWorkspacePath;
125
+ }
126
+
127
+ function createTaskWorkspaceItems(taskOrchestration) {
128
+ const state = taskOrchestration && typeof taskOrchestration === 'object' ? taskOrchestration : {};
129
+ const workspaces = new Map();
130
+ addTaskWorkspaceCandidate(workspaces, state.workspacePath, 'draft');
131
+ const detail = state.selectedRunDetail && typeof state.selectedRunDetail === 'object' ? state.selectedRunDetail : null;
132
+ if (detail) addTaskWorkspaceCandidate(workspaces, detail.cwd, 'detail');
133
+ const queue = Array.isArray(state.queue) ? state.queue : [];
134
+ const runs = Array.isArray(state.runs) ? state.runs : [];
135
+ queue.forEach((item) => addTaskWorkspaceCandidate(workspaces, readTaskWorkspacePathFromItem(item), 'queue', readTaskItemTimestamp(item)));
136
+ runs.forEach((item) => addTaskWorkspaceCandidate(workspaces, readTaskWorkspacePathFromItem(item), 'run', readTaskItemTimestamp(item)));
137
+ const items = Array.from(workspaces.values()).sort((a, b) => {
138
+ const byActivity = String(b.lastActivity || '').localeCompare(String(a.lastActivity || ''));
139
+ if (byActivity !== 0) return byActivity;
140
+ return a.label.localeCompare(b.label);
141
+ });
142
+ if (items.length === 0) {
143
+ items.push({
144
+ key: '__auto__',
145
+ path: '',
146
+ label: 'Auto workspace',
147
+ meta: 'Server default cwd',
148
+ queueCount: queue.length,
149
+ runCount: runs.length,
150
+ lastActivity: ''
151
+ });
152
+ }
153
+ return items;
154
+ }
155
+
156
+ function createTaskWorkspaceSessions(taskOrchestration, workspacePath) {
157
+ const state = taskOrchestration && typeof taskOrchestration === 'object' ? taskOrchestration : {};
158
+ const queue = Array.isArray(state.queue) ? state.queue : [];
159
+ const runs = Array.isArray(state.runs) ? state.runs : [];
160
+ const queueSessions = queue
161
+ .filter((item) => taskItemMatchesWorkspace(item, workspacePath))
162
+ .map((item) => ({
163
+ id: item.taskId || item.lastRunId || `queue-${readTaskItemTimestamp(item)}`,
164
+ type: 'queue',
165
+ status: item.status || item.runStatus || 'queued',
166
+ title: item.title || item.target || item.taskId || 'Queued task',
167
+ meta: item.threadId || item.cwd || item.taskId || '',
168
+ taskId: item.taskId || '',
169
+ runId: item.lastRunId || '',
170
+ threadId: item.threadId || '',
171
+ cwd: readTaskWorkspacePathFromItem(item),
172
+ timestamp: readTaskItemTimestamp(item),
173
+ active: true
174
+ }));
175
+ const runSessions = runs
176
+ .filter((item) => taskItemMatchesWorkspace(item, workspacePath))
177
+ .map((item) => ({
178
+ id: item.runId || item.taskId || `run-${readTaskItemTimestamp(item)}`,
179
+ type: 'run',
180
+ status: item.status || 'completed',
181
+ title: item.title || item.summary || item.taskId || item.runId || 'Run',
182
+ meta: item.threadId || item.cwd || `${item.durationMs || 0}ms`,
183
+ taskId: item.taskId || '',
184
+ runId: item.runId || '',
185
+ threadId: item.threadId || '',
186
+ cwd: readTaskWorkspacePathFromItem(item),
187
+ timestamp: readTaskItemTimestamp(item),
188
+ active: false
189
+ }));
190
+ return queueSessions.concat(runSessions).sort((a, b) => {
191
+ if (a.active !== b.active) return a.active ? -1 : 1;
192
+ return String(b.timestamp || '').localeCompare(String(a.timestamp || ''));
193
+ });
194
+ }
195
+
196
+ function createTaskConversationMessages(taskOrchestration, t = null) {
197
+ const state = taskOrchestration && typeof taskOrchestration === 'object' ? taskOrchestration : {};
198
+ const messages = [];
199
+ const target = String(state.target || '').trim();
200
+ const followUps = normalizeTaskDraftLines(state.followUpsText);
201
+ const detail = state.selectedRunDetail && typeof state.selectedRunDetail === 'object' ? state.selectedRunDetail : null;
202
+ const detailRun = detail && detail.run && typeof detail.run === 'object' ? detail.run : null;
203
+ if (detail) {
204
+ const fileLines = collectTaskRunWorkspaceFileLines(detail);
205
+ const resultLines = [];
206
+ if (detailRun && detailRun.summary) {
207
+ resultLines.push(detailRun.summary);
208
+ } else {
209
+ resultLines.push(translateTaskText(t, 'orchestration.chat.assistant.contextFallback', '已选中一个历史任务,继续时会继承它的线程和工作区。'));
210
+ }
211
+ if (fileLines.length > 0) {
212
+ resultLines.push(translateTaskText(t, 'orchestration.chat.assistant.filesSummary', `文件操作:${fileLines.join(';')}`, { files: fileLines.join(';') }));
213
+ }
214
+ messages.push({
215
+ id: 'context-run',
216
+ role: 'assistant',
217
+ label: detailRun && detailRun.status === 'success'
218
+ ? translateTaskText(t, 'orchestration.chat.assistant.resultLabel', '实现结果')
219
+ : translateTaskText(t, 'orchestration.chat.assistant.contextLabel', '上一轮上下文'),
220
+ text: resultLines.join('\n'),
221
+ meta: formatTaskConversationMeta([
222
+ detail.threadId ? translateTaskText(t, 'orchestration.chat.meta.thread', `线程 ${detail.threadId}`, { value: detail.threadId }) : '',
223
+ detail.cwd ? translateTaskText(t, 'orchestration.chat.meta.workspace', `工作区 ${detail.cwd}`, { value: detail.cwd }) : '',
224
+ detailRun && detailRun.status ? detailRun.status : ''
225
+ ])
226
+ });
227
+ } else if (!target && followUps.length === 0) {
228
+ messages.push({
229
+ id: 'assistant-empty',
230
+ role: 'assistant',
231
+ label: translateTaskText(t, 'orchestration.chat.assistant.readyLabel', 'Codexmate'),
232
+ text: translateTaskText(t, 'orchestration.chat.assistant.empty', '先发第一个需求;如果还有第二个需求,继续发送,Codexmate 会按顺序处理并保留上下文。'),
233
+ meta: translateTaskText(t, 'orchestration.chat.meta.order', '顺序执行 · 保留上下文')
234
+ });
235
+ }
236
+ if (target) {
237
+ messages.push({
238
+ id: 'user-target',
239
+ role: 'user',
240
+ label: translateTaskText(t, 'orchestration.chat.user.step', '需求 {count}', { count: 1 }),
241
+ text: target,
242
+ meta: translateTaskText(t, 'orchestration.chat.meta.first', '先完成这一条')
243
+ });
244
+ }
245
+ followUps.forEach((item, index) => {
246
+ const step = index + 2;
247
+ messages.push({
248
+ id: `user-follow-up-${index}`,
249
+ role: 'user',
250
+ label: translateTaskText(t, 'orchestration.chat.user.step', '需求 {count}', { count: step }),
251
+ text: item,
252
+ meta: translateTaskText(t, 'orchestration.chat.meta.afterPrevious', '等待前一条完成后继续')
253
+ });
254
+ });
255
+ if (!state.plan && target) {
256
+ messages.push({
257
+ id: 'assistant-next',
258
+ role: 'assistant',
259
+ label: translateTaskText(t, 'orchestration.chat.assistant.readyLabel', 'Codexmate'),
260
+ text: followUps.length > 0
261
+ ? translateTaskText(t, 'orchestration.chat.assistant.sequenceReady', '已收到多条需求;执行时会先完成需求 1,再带着上下文继续后续需求。')
262
+ : translateTaskText(t, 'orchestration.chat.assistant.singleReady', '已收到第一条需求。可以继续补需求 2,或直接预览并执行。'),
263
+ meta: translateTaskText(t, 'orchestration.chat.meta.previewNext', '下一步:预览计划')
264
+ });
265
+ }
266
+ return messages;
267
+ }
268
+
46
269
  function translateTaskText(t, key, fallback, params = null) {
47
270
  if (typeof t !== 'function') return fallback;
48
271
  const translated = t(key, params);
@@ -60,13 +283,23 @@ function createTaskDraftChecklist(metrics, t = null) {
60
283
  done: metrics.hasTarget,
61
284
  detail: metrics.hasTarget ? translateTaskText(t, 'orchestration.readiness.target.done', '已写目标') : translateTaskText(t, 'orchestration.readiness.target.missing', '还没写目标')
62
285
  },
286
+ {
287
+ key: 'sequence',
288
+ label: translateTaskText(t, 'orchestration.readiness.sequence.label', '顺序'),
289
+ done: metrics.hasTarget,
290
+ detail: !metrics.hasTarget
291
+ ? translateTaskText(t, 'orchestration.readiness.sequence.missing', '先发送需求 1')
292
+ : (metrics.hasSequentialFollowUps
293
+ ? translateTaskText(t, 'orchestration.readiness.sequence.multiple', '{count} 条需求会按顺序执行:先完成需求 1,再继续需求 2。', { count: metrics.requestCount })
294
+ : translateTaskText(t, 'orchestration.readiness.sequence.single', '当前只有需求 1;继续发送会变成需求 2。'))
295
+ },
63
296
  {
64
297
  key: 'engine',
65
298
  label: metrics.engine === 'workflow' ? 'Workflow' : translateTaskText(t, 'orchestration.readiness.engine.label', '执行策略'),
66
299
  done: workflowReady,
67
300
  detail: metrics.engine === 'workflow'
68
301
  ? (metrics.workflowCount > 0 ? translateTaskText(t, 'orchestration.readiness.workflow.done', `已选 ${metrics.workflowCount} 个 Workflow`, { count: metrics.workflowCount }) : translateTaskText(t, 'orchestration.readiness.workflow.missing', '还没选 Workflow ID'))
69
- : translateTaskText(t, 'orchestration.readiness.engine.codex', '使用 Codex 规划节点')
302
+ : translateTaskText(t, 'orchestration.readiness.engine.openaiChat', '使用 OpenAI Chat-compatible 节点')
70
303
  },
71
304
  {
72
305
  key: 'scope',
@@ -81,7 +314,7 @@ function createTaskDraftChecklist(metrics, t = null) {
81
314
  label: translateTaskText(t, 'orchestration.readiness.preview.label', '预览'),
82
315
  done: previewReady,
83
316
  detail: !metrics.hasPlan
84
- ? translateTaskText(t, 'orchestration.readiness.preview.missing', '还没生成计划')
317
+ ? translateTaskText(t, 'orchestration.readiness.preview.missing', '还没发送 /plan')
85
318
  : (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 }))
86
319
  }
87
320
  ];
@@ -106,7 +339,9 @@ function createTaskDraftReadiness(metrics, t = null) {
106
339
  return {
107
340
  tone: 'warn',
108
341
  title: translateTaskText(t, 'orchestration.readiness.preview.title', '建议先预览'),
109
- summary: translateTaskText(t, 'orchestration.readiness.preview.summary', '草稿已成形,先生成一次计划,确认节点和依赖再执行。')
342
+ summary: metrics.hasSequentialFollowUps
343
+ ? translateTaskText(t, 'orchestration.readiness.preview.sequenceSummary', '草稿已成形,已锁定 {count} 条顺序需求:先完成需求 1,再继续需求 2。', { count: metrics.requestCount })
344
+ : translateTaskText(t, 'orchestration.readiness.preview.summary', '草稿已成形,发送 /plan 预览方案,确认节点和依赖再执行。')
110
345
  };
111
346
  }
112
347
  if (metrics.planIssues.length > 0) {
@@ -120,7 +355,7 @@ function createTaskDraftReadiness(metrics, t = null) {
120
355
  return {
121
356
  tone: 'warn',
122
357
  title: translateTaskText(t, 'orchestration.readiness.warn.title', '可以执行,但有提醒'),
123
- summary: translateTaskText(t, 'orchestration.readiness.warn.summary', `计划已生成,但还有 ${metrics.planWarnings.length} 条提醒值得先看一眼。`, { count: metrics.planWarnings.length })
358
+ summary: translateTaskText(t, 'orchestration.readiness.warn.summary', `/plan 方案已生成,但还有 ${metrics.planWarnings.length} 条提醒值得先看一眼。`, { count: metrics.planWarnings.length })
124
359
  };
125
360
  }
126
361
  if (metrics.dryRun) {
@@ -134,7 +369,7 @@ function createTaskDraftReadiness(metrics, t = null) {
134
369
  tone: 'success',
135
370
  title: translateTaskText(t, 'orchestration.readiness.ready.title', '可以执行'),
136
371
  summary: metrics.followUpCount > 0
137
- ? translateTaskText(t, 'orchestration.readiness.ready.withFollowUps', `主目标和收尾动作都已具备,可以直接执行或入队。`)
372
+ ? translateTaskText(t, 'orchestration.readiness.ready.withFollowUps', `已锁定 ${metrics.requestCount} 条顺序需求:先完成需求 1,再带上下文继续需求 2。`, { count: metrics.requestCount })
138
373
  : translateTaskText(t, 'orchestration.readiness.ready.summary', '主目标已经够清楚了,可以直接执行或入队。')
139
374
  };
140
375
  }
@@ -191,13 +426,72 @@ export function createMainTabsComputed() {
191
426
  if (detail && Array.isArray(detail.nodes)) return detail.nodes;
192
427
  return Array.isArray(run.nodes) ? run.nodes : [];
193
428
  },
429
+ taskOrchestrationActiveQueue() {
430
+ const queue = this.taskOrchestration && Array.isArray(this.taskOrchestration.queue)
431
+ ? this.taskOrchestration.queue
432
+ : [];
433
+ return queue.filter((item) => {
434
+ const status = String((item && (item.status || item.runStatus)) || '').trim().toLowerCase();
435
+ return status === 'queued' || status === 'running';
436
+ });
437
+ },
438
+ taskOrchestrationEngineLabel() {
439
+ const state = this.taskOrchestration && typeof this.taskOrchestration === 'object'
440
+ ? this.taskOrchestration
441
+ : {};
442
+ return state.selectedEngine === 'workflow' ? 'Workflow' : 'Codex';
443
+ },
444
+ taskOrchestrationWorkbenchVisible() {
445
+ const state = this.taskOrchestration && typeof this.taskOrchestration === 'object'
446
+ ? this.taskOrchestration
447
+ : {};
448
+ const activeQueue = Array.isArray(this.taskOrchestrationActiveQueue)
449
+ ? this.taskOrchestrationActiveQueue
450
+ : [];
451
+ return activeQueue.length > 0
452
+ || !!String(state.selectedRunId || '').trim()
453
+ || !!String(state.selectedRunError || '').trim()
454
+ || (state.workspaceTab === 'runs' && Array.isArray(state.runs) && state.runs.length > 0);
455
+ },
456
+ taskOrchestrationWorkspacePath() {
457
+ const state = this.taskOrchestration && typeof this.taskOrchestration === 'object'
458
+ ? this.taskOrchestration
459
+ : {};
460
+ const detail = state.selectedRunDetail && typeof state.selectedRunDetail === 'object'
461
+ ? state.selectedRunDetail
462
+ : null;
463
+ return normalizeTaskWorkspacePath(state.workspacePath || (detail && detail.cwd) || '');
464
+ },
465
+ taskOrchestrationWorkspaceItems() {
466
+ const items = createTaskWorkspaceItems(this.taskOrchestration);
467
+ const selectedPath = this.taskOrchestrationWorkspacePath;
468
+ return items.map((item) => ({
469
+ ...item,
470
+ active: normalizeTaskWorkspacePath(item.path) === selectedPath || (!selectedPath && !item.path)
471
+ }));
472
+ },
473
+ taskOrchestrationWorkspaceQueue() {
474
+ const queue = this.taskOrchestration && Array.isArray(this.taskOrchestration.queue)
475
+ ? this.taskOrchestration.queue
476
+ : [];
477
+ return queue.filter((item) => taskItemMatchesWorkspace(item, this.taskOrchestrationWorkspacePath));
478
+ },
479
+ taskOrchestrationWorkspaceRuns() {
480
+ const runs = this.taskOrchestration && Array.isArray(this.taskOrchestration.runs)
481
+ ? this.taskOrchestration.runs
482
+ : [];
483
+ return runs.filter((item) => taskItemMatchesWorkspace(item, this.taskOrchestrationWorkspacePath));
484
+ },
485
+ taskOrchestrationWorkspaceSessions() {
486
+ return createTaskWorkspaceSessions(this.taskOrchestration, this.taskOrchestrationWorkspacePath);
487
+ },
194
488
  taskOrchestrationQueueStats() {
195
489
  const queue = this.taskOrchestration && Array.isArray(this.taskOrchestration.queue)
196
490
  ? this.taskOrchestration.queue
197
491
  : [];
198
492
  const stats = { queued: 0, running: 0, failed: 0 };
199
493
  for (const item of queue) {
200
- const status = String(item && item.status || '').trim().toLowerCase();
494
+ const status = String((item && (item.status || item.runStatus)) || '').trim().toLowerCase();
201
495
  if (status === 'queued') stats.queued += 1;
202
496
  else if (status === 'running') stats.running += 1;
203
497
  else if (status === 'failed') stats.failed += 1;
@@ -212,6 +506,9 @@ export function createMainTabsComputed() {
212
506
  },
213
507
  taskOrchestrationDraftReadiness() {
214
508
  return createTaskDraftReadiness(this.taskOrchestrationDraftMetrics, this.t && this.t.bind(this));
509
+ },
510
+ taskOrchestrationConversationMessages() {
511
+ return createTaskConversationMessages(this.taskOrchestration, this.t && this.t.bind(this));
215
512
  }
216
513
  };
217
514
  }
@@ -354,6 +354,9 @@ export function createAgentsMethods(options = {}) {
354
354
  return !!this.agentsSaving || this.hasAgentsContentChanged() || this.agentsDiffVisible;
355
355
  },
356
356
  handleBeforeUnload(event) {
357
+ if (typeof this.flushWebUiPreferences === 'function') {
358
+ this.flushWebUiPreferences();
359
+ }
357
360
  if (!this.hasPendingAgentsDraft()) {
358
361
  return;
359
362
  }
@@ -705,8 +708,193 @@ export function createAgentsMethods(options = {}) {
705
708
  }
706
709
  },
707
710
 
711
+
712
+ normalizePromptPresetName(name) {
713
+ return typeof name === 'string' ? name.trim() : '';
714
+ },
715
+ buildPromptPresetId() {
716
+ const randomPart = Math.random().toString(36).slice(2, 8);
717
+ return `prompt-preset-${Date.now()}-${randomPart}`;
718
+ },
719
+ getPromptPresetRenameDraft(preset) {
720
+ if (!preset || !preset.id) return '';
721
+ if (Object.prototype.hasOwnProperty.call(this.promptPresetRenameDraft || {}, preset.id)) {
722
+ return this.promptPresetRenameDraft[preset.id];
723
+ }
724
+ return preset.name || '';
725
+ },
726
+ setPromptPresetRenameDraft(id, value) {
727
+ if (!id) return;
728
+ this.promptPresetRenameDraft = {
729
+ ...(this.promptPresetRenameDraft || {}),
730
+ [id]: value
731
+ };
732
+ },
733
+ formatPromptPresetTime(value) {
734
+ if (typeof value !== 'string' || !value) {
735
+ return this.t('common.none');
736
+ }
737
+ const date = new Date(value);
738
+ if (Number.isNaN(date.getTime())) {
739
+ return value;
740
+ }
741
+ return date.toLocaleString();
742
+ },
743
+ findPromptPresetByName(name, excludeId = '') {
744
+ const normalizedName = this.normalizePromptPresetName(name).toLowerCase();
745
+ return (Array.isArray(this.promptPresets) ? this.promptPresets : []).find((preset) => {
746
+ if (!preset || preset.id === excludeId) return false;
747
+ return this.normalizePromptPresetName(preset.name).toLowerCase() === normalizedName;
748
+ }) || null;
749
+ },
750
+ async persistPromptPresets() {
751
+ if (typeof this.persistWebUiPreferences === 'function') {
752
+ await this.persistWebUiPreferences({ promptPresets: this.promptPresets });
753
+ }
754
+ },
755
+ getCurrentPromptPresetDefaultName() {
756
+ if (this.promptsSubTab === 'claude-project') {
757
+ const projectPath = typeof this.projectClaudeMdPath === 'string' ? this.projectClaudeMdPath.trim() : '';
758
+ return projectPath
759
+ ? this.t('prompts.presets.defaultName.project', { path: projectPath })
760
+ : this.t('prompts.subTab.project');
761
+ }
762
+ const fileName = typeof this.agentsPath === 'string' && this.agentsPath.trim()
763
+ ? (this.agentsPath.trim().split(/[\\/]/).filter(Boolean).pop() || '')
764
+ : '';
765
+ return fileName || this.t('prompts.subTab.codex');
766
+ },
767
+ async saveEditorPromptAsPreset() {
768
+ if (this.promptPresetSaving || this.agentsLoading || this.agentsDiffVisible) return;
769
+ const name = this.getCurrentPromptPresetDefaultName();
770
+ const content = typeof this.agentsContent === 'string' ? this.agentsContent : '';
771
+ if (!content.trim()) {
772
+ this.showMessage(this.t('prompts.presets.error.emptyContent'), 'error');
773
+ return;
774
+ }
775
+ const confirmed = await this.requestConfirmDialog({
776
+ title: this.t('prompts.presets.confirm.addCurrentTitle'),
777
+ message: this.t('prompts.presets.confirm.addCurrentMessage', { name }),
778
+ confirmText: this.t('prompts.presets.addCurrent'),
779
+ cancelText: this.t('common.cancel'),
780
+ danger: false
781
+ });
782
+ if (!confirmed) return;
783
+ this.promptPresetNameDraft = name;
784
+ await this.saveCurrentPromptAsPreset();
785
+ },
786
+ async saveCurrentPromptAsPreset() {
787
+ if (this.promptPresetSaving || this.agentsLoading) return;
788
+ const draftName = this.normalizePromptPresetName(this.promptPresetNameDraft);
789
+ const name = draftName || this.normalizePromptPresetName(this.getCurrentPromptPresetDefaultName());
790
+ const content = typeof this.agentsContent === 'string' ? this.agentsContent : '';
791
+ if (!name) {
792
+ this.showMessage(this.t('prompts.presets.error.emptyName'), 'error');
793
+ return;
794
+ }
795
+ if (!content.trim()) {
796
+ this.showMessage(this.t('prompts.presets.error.emptyContent'), 'error');
797
+ return;
798
+ }
799
+ const existing = this.findPromptPresetByName(name);
800
+ if (existing) {
801
+ const confirmed = await this.requestConfirmDialog({
802
+ title: this.t('prompts.presets.confirm.overwriteTitle'),
803
+ message: this.t('prompts.presets.confirm.overwriteMessage', { name }),
804
+ confirmText: this.t('prompts.presets.confirm.overwriteConfirm'),
805
+ cancelText: this.t('common.cancel'),
806
+ danger: false
807
+ });
808
+ if (!confirmed) return;
809
+ }
810
+ this.promptPresetSaving = true;
811
+ try {
812
+ const now = new Date().toISOString();
813
+ if (existing) {
814
+ this.promptPresets = this.promptPresets.map((preset) => preset.id === existing.id
815
+ ? { ...preset, name, content, updatedAt: now }
816
+ : preset);
817
+ this.selectedPromptPresetId = existing.id;
818
+ } else {
819
+ const preset = {
820
+ id: this.buildPromptPresetId(),
821
+ name,
822
+ content,
823
+ updatedAt: now
824
+ };
825
+ this.promptPresets = [preset, ...this.promptPresets];
826
+ this.selectedPromptPresetId = preset.id;
827
+ }
828
+ this.promptPresetNameDraft = '';
829
+ await this.persistPromptPresets();
830
+ this.showMessage(this.t('prompts.presets.toast.saved'), 'success');
831
+ } finally {
832
+ this.promptPresetSaving = false;
833
+ }
834
+ },
835
+ async applyPromptPresetToEditor(preset) {
836
+ if (!preset || typeof preset.content !== 'string' || !preset.content) return;
837
+ this.agentsContent = preset.content;
838
+ this.onAgentsContentInput();
839
+ this.selectedPromptPresetId = preset.id;
840
+ this.showMessage(this.t('prompts.presets.toast.pasted'), 'success');
841
+ },
842
+ async applyPromptPresetSelection(event) {
843
+ const select = event && event.target;
844
+ const presetId = select && typeof select.value === 'string' ? select.value : '';
845
+ if (!presetId) return;
846
+ const preset = (Array.isArray(this.promptPresets) ? this.promptPresets : []).find((item) => item && item.id === presetId);
847
+ await this.applyPromptPresetToEditor(preset);
848
+ if (select) select.value = '';
849
+ },
850
+ async renamePromptPreset(preset) {
851
+ if (!preset || !preset.id) return;
852
+ const name = this.normalizePromptPresetName(this.getPromptPresetRenameDraft(preset));
853
+ if (!name) {
854
+ this.showMessage(this.t('prompts.presets.error.emptyName'), 'error');
855
+ return;
856
+ }
857
+ const existing = this.findPromptPresetByName(name, preset.id);
858
+ if (existing) {
859
+ this.showMessage(this.t('prompts.presets.error.duplicateName'), 'error');
860
+ return;
861
+ }
862
+ if (name === preset.name) {
863
+ this.showMessage(this.t('toast.noChanges'), 'info');
864
+ return;
865
+ }
866
+ const now = new Date().toISOString();
867
+ this.promptPresets = this.promptPresets.map((item) => item.id === preset.id
868
+ ? { ...item, name, updatedAt: now }
869
+ : item);
870
+ const drafts = { ...(this.promptPresetRenameDraft || {}) };
871
+ delete drafts[preset.id];
872
+ this.promptPresetRenameDraft = drafts;
873
+ await this.persistPromptPresets();
874
+ this.showMessage(this.t('prompts.presets.toast.renamed'), 'success');
875
+ },
876
+ async deletePromptPreset(preset) {
877
+ if (!preset || !preset.id) return;
878
+ const confirmed = await this.requestConfirmDialog({
879
+ title: this.t('prompts.presets.confirm.deleteTitle'),
880
+ message: this.t('prompts.presets.confirm.deleteMessage', { name: preset.name || '' }),
881
+ confirmText: this.t('common.delete'),
882
+ cancelText: this.t('common.cancel'),
883
+ danger: true
884
+ });
885
+ if (!confirmed) return;
886
+ this.promptPresets = this.promptPresets.filter((item) => item.id !== preset.id);
887
+ if (this.selectedPromptPresetId === preset.id) {
888
+ this.selectedPromptPresetId = '';
889
+ }
890
+ const drafts = { ...(this.promptPresetRenameDraft || {}) };
891
+ delete drafts[preset.id];
892
+ this.promptPresetRenameDraft = drafts;
893
+ await this.persistPromptPresets();
894
+ this.showMessage(this.t('prompts.presets.toast.deleted'), 'success');
895
+ },
708
896
  switchPromptsSubTab(subTab) {
709
- const normalized = subTab === 'claude-project' ? 'claude-project' : 'codex';
897
+ const normalized = subTab === 'claude-project' ? subTab : 'codex';
710
898
  if (normalized === 'claude-project' && !this.projectPathOptions.length && !this.projectPathOptionsLoading) {
711
899
  this.loadProjectPathOptions();
712
900
  }
@@ -715,6 +903,9 @@ export function createAgentsMethods(options = {}) {
715
903
  return;
716
904
  }
717
905
  this.promptsSubTab = normalized;
906
+ this.$nextTick(() => {
907
+ document.querySelector('.main-panel')?.scrollTo({ top: 0, left: 0, behavior: 'auto' });
908
+ });
718
909
  },
719
910
 
720
911
  async loadPromptsContent() {