codexmate 0.0.55 → 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.
- package/README.md +2 -0
- package/README.vi.md +2 -0
- package/README.zh.md +2 -0
- package/cli/local-bridge.js +221 -22
- package/cli.js +797 -134
- package/lib/task-orchestrator.js +90 -21
- package/lib/task-workspace-chat.js +292 -0
- package/package.json +2 -2
- package/web-ui/app.js +57 -129
- package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
- package/web-ui/modules/app.computed.session.mjs +210 -0
- package/web-ui/modules/app.methods.agents.mjs +3 -0
- package/web-ui/modules/app.methods.claude-config.mjs +178 -22
- package/web-ui/modules/app.methods.codex-config.mjs +294 -65
- package/web-ui/modules/app.methods.navigation.mjs +71 -84
- package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
- package/web-ui/modules/app.methods.providers.mjs +15 -1
- package/web-ui/modules/app.methods.runtime.mjs +7 -2
- package/web-ui/modules/app.methods.session-actions.mjs +25 -12
- package/web-ui/modules/app.methods.session-browser.mjs +23 -54
- package/web-ui/modules/app.methods.session-trash.mjs +0 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +35 -17
- package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +415 -68
- package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
- package/web-ui/modules/i18n/locales/en.mjs +187 -28
- package/web-ui/modules/i18n/locales/ja.mjs +184 -25
- package/web-ui/modules/i18n/locales/vi.mjs +186 -33
- package/web-ui/modules/i18n/locales/zh-tw.mjs +187 -28
- package/web-ui/modules/i18n/locales/zh.mjs +189 -30
- package/web-ui/modules/i18n.mjs +5 -12
- package/web-ui/modules/provider-default-names.mjs +25 -0
- package/web-ui/modules/sessions-filters-url.mjs +1 -2
- package/web-ui/partials/index/layout-header.html +37 -14
- package/web-ui/partials/index/modal-health-check.html +69 -5
- package/web-ui/partials/index/panel-config-codex.html +2 -2
- package/web-ui/partials/index/panel-orchestration.html +489 -282
- package/web-ui/partials/index/panel-sessions.html +97 -17
- package/web-ui/res/web-ui-render.precompiled.js +1423 -732
- package/web-ui/session-helpers.mjs +4 -1
- package/web-ui/styles/layout-shell.css +157 -1
- package/web-ui/styles/navigation-panels.css +11 -0
- package/web-ui/styles/responsive.css +98 -0
- package/web-ui/styles/sessions-preview.css +212 -2
- package/web-ui/styles/sessions-toolbar-trash.css +61 -18
- package/web-ui/styles/skills-list.css +122 -0
- package/web-ui/styles/task-orchestration.css +2161 -4
- package/web-ui/styles/titles-cards.css +52 -0
|
@@ -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
|
|
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.
|
|
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:
|
|
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',
|
|
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
|
}
|
|
@@ -51,6 +51,190 @@ function formatUsageRangeLabel(range, t) {
|
|
|
51
51
|
return '近 7 天';
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function normalizeWorkspaceText(value) {
|
|
55
|
+
if (value == null) {
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
return String(value)
|
|
59
|
+
.replace(/<[^>]*>/g, ' ')
|
|
60
|
+
.replace(/ /gi, ' ')
|
|
61
|
+
.replace(/</gi, '<')
|
|
62
|
+
.replace(/>/gi, '>')
|
|
63
|
+
.replace(/&/gi, '&')
|
|
64
|
+
.replace(/\s+/g, ' ')
|
|
65
|
+
.trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function trimWorkspaceLine(value, maxLength = 160) {
|
|
69
|
+
const text = normalizeWorkspaceText(value);
|
|
70
|
+
if (!text) return '';
|
|
71
|
+
const limit = Number.isFinite(Number(maxLength)) ? Math.max(20, Math.floor(Number(maxLength))) : 160;
|
|
72
|
+
return text.length > limit ? `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…` : text;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function pushUniqueWorkspaceItem(items, value, limit = 6, maxLength = 160) {
|
|
76
|
+
if (!Array.isArray(items) || items.length >= limit) return;
|
|
77
|
+
const text = trimWorkspaceLine(value, maxLength);
|
|
78
|
+
if (!text) return;
|
|
79
|
+
const key = text.toLowerCase();
|
|
80
|
+
if (items.some(item => String(item || '').toLowerCase() === key)) return;
|
|
81
|
+
items.push(text);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function extractWorkspaceLines(text) {
|
|
85
|
+
return String(text || '')
|
|
86
|
+
.split(/\r?\n|(?:^|\s)(?:[-*•]|\d+[.)])\s+|[。;;]\s*/g)
|
|
87
|
+
.map(line => normalizeWorkspaceText(line))
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isWorkspaceCommandLine(line) {
|
|
92
|
+
return /^\$\s+/.test(line)
|
|
93
|
+
|| /^(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|go test|cargo test|make|docker)\b/i.test(line);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function extractInlineWorkspaceCommands(text) {
|
|
97
|
+
const source = normalizeWorkspaceText(text);
|
|
98
|
+
if (!source) return [];
|
|
99
|
+
const matches = source.match(/(?:^|[^A-Za-z0-9_])(?:\$\s*)?(?:(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|make|docker)\s+(?:run\s+)?[A-Za-z0-9:_./@-]+(?:\s+&&\s+(?:npm|pnpm|yarn|bun|node|npx|git|gh|codex|claude|gemini|codebuddy|openclaw|pytest|make|docker)\s+(?:run\s+)?[A-Za-z0-9:_./@-]+)?|go\s+test(?:\s+[A-Za-z0-9:_./@-]+)?|cargo\s+test(?:\s+[A-Za-z0-9:_./@-]+)?)(?=$|\s|[,。;,;])/gi) || [];
|
|
100
|
+
return matches.map(match => match.replace(/^[^A-Za-z0-9_$]*\$?\s*/, '').trim());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function extractWorkspaceArtifacts(line, target) {
|
|
104
|
+
if (!target || typeof line !== 'string') return;
|
|
105
|
+
const urlMatches = line.match(/https?:\/\/[^\s)\]"'<>,。;、]+/gi) || [];
|
|
106
|
+
for (const url of urlMatches) {
|
|
107
|
+
pushUniqueWorkspaceItem(target.links, url.replace(/[,。;、.,;:!?]+$/g, ''), 5, 140);
|
|
108
|
+
}
|
|
109
|
+
const fileMatches = line.match(/(?:[A-Za-z]:[\\/]|\/)?(?:[A-Za-z0-9_.@+-]+[\\/])*[A-Za-z0-9_.@+-]+\.(?:mjs|js|cjs|ts|tsx|jsx|vue|css|scss|html|json|jsonl|md|yml|yaml|toml|lock|py|go|rs|sh|txt)/g) || [];
|
|
110
|
+
for (const filePath of fileMatches) {
|
|
111
|
+
pushUniqueWorkspaceItem(target.files, filePath, 8, 120);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildWorkspaceBriefText(summary, labels = {}) {
|
|
116
|
+
if (!summary || !summary.available) return '';
|
|
117
|
+
const label = (key, fallback) => (labels && labels[key]) || fallback;
|
|
118
|
+
const lines = [
|
|
119
|
+
`# ${label('title', 'Session workspace brief')}`,
|
|
120
|
+
'',
|
|
121
|
+
`${label('source', 'Source')}: ${summary.sourceLabel || summary.source || '-'}`,
|
|
122
|
+
`${label('messages', 'Messages')}: ${summary.messageCount}`,
|
|
123
|
+
`${label('path', 'Path')}: ${summary.cwd || '-'}`,
|
|
124
|
+
''
|
|
125
|
+
];
|
|
126
|
+
const sections = [
|
|
127
|
+
['signals', label('signals', 'Signals'), summary.signals],
|
|
128
|
+
['commands', label('commands', 'Reusable commands'), summary.commands],
|
|
129
|
+
['files', label('files', 'Files'), summary.files],
|
|
130
|
+
['links', label('links', 'Links'), summary.links],
|
|
131
|
+
['risks', label('risks', 'Risks / blockers'), summary.risks],
|
|
132
|
+
['nextSteps', label('nextSteps', 'Next steps'), summary.nextSteps]
|
|
133
|
+
];
|
|
134
|
+
for (const [, sectionTitle, items] of sections) {
|
|
135
|
+
if (!Array.isArray(items) || !items.length) continue;
|
|
136
|
+
lines.push(`## ${sectionTitle}`);
|
|
137
|
+
for (const item of items) {
|
|
138
|
+
lines.push(`- ${item}`);
|
|
139
|
+
}
|
|
140
|
+
lines.push('');
|
|
141
|
+
}
|
|
142
|
+
return lines.join('\n').trimEnd();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function buildSessionWorkspaceSummary(session, messages, options = {}) {
|
|
146
|
+
const safeMessages = Array.isArray(messages) ? messages : [];
|
|
147
|
+
const roleCounts = { user: 0, assistant: 0, system: 0, other: 0 };
|
|
148
|
+
const signals = [];
|
|
149
|
+
const commands = [];
|
|
150
|
+
const files = [];
|
|
151
|
+
const links = [];
|
|
152
|
+
const risks = [];
|
|
153
|
+
const nextSteps = [];
|
|
154
|
+
const target = { files, links };
|
|
155
|
+
let firstUser = '';
|
|
156
|
+
let lastUser = '';
|
|
157
|
+
let lastAssistant = '';
|
|
158
|
+
let firstTimestamp = '';
|
|
159
|
+
let lastTimestamp = '';
|
|
160
|
+
|
|
161
|
+
safeMessages.forEach((message) => {
|
|
162
|
+
if (!message || typeof message !== 'object') return;
|
|
163
|
+
const normalizedRole = String(message.normalizedRole || message.role || '').trim().toLowerCase();
|
|
164
|
+
const role = normalizedRole === 'user' || normalizedRole === 'assistant' || normalizedRole === 'system'
|
|
165
|
+
? normalizedRole
|
|
166
|
+
: 'other';
|
|
167
|
+
roleCounts[role] += 1;
|
|
168
|
+
const timestamp = normalizeWorkspaceText(message.timestamp || '');
|
|
169
|
+
if (timestamp && !firstTimestamp) firstTimestamp = timestamp;
|
|
170
|
+
if (timestamp) lastTimestamp = timestamp;
|
|
171
|
+
const text = normalizeWorkspaceText(message.text || message.content || '');
|
|
172
|
+
if (!text) return;
|
|
173
|
+
if (role === 'user') {
|
|
174
|
+
if (!firstUser) firstUser = text;
|
|
175
|
+
lastUser = text;
|
|
176
|
+
} else if (role === 'assistant') {
|
|
177
|
+
lastAssistant = text;
|
|
178
|
+
}
|
|
179
|
+
const lines = extractWorkspaceLines(message.text || message.content || text);
|
|
180
|
+
for (const command of extractInlineWorkspaceCommands(message.text || message.content || text)) {
|
|
181
|
+
pushUniqueWorkspaceItem(commands, command, 6, 140);
|
|
182
|
+
}
|
|
183
|
+
for (const line of lines) {
|
|
184
|
+
extractWorkspaceArtifacts(line, target);
|
|
185
|
+
if (isWorkspaceCommandLine(line)) {
|
|
186
|
+
pushUniqueWorkspaceItem(commands, line.replace(/^\$\s+/, ''), 6, 140);
|
|
187
|
+
}
|
|
188
|
+
if (/(?:blocker|blocked|risk|failed|failure|error|timeout|regression|冲突|失败|错误|阻塞|风险|不可合|超时|回归)/i.test(line)) {
|
|
189
|
+
pushUniqueWorkspaceItem(risks, line, 5, 150);
|
|
190
|
+
}
|
|
191
|
+
if (/(?:todo|next|follow[- ]?up|remaining|后续|下一步|待办|继续|剩余)/i.test(line)) {
|
|
192
|
+
pushUniqueWorkspaceItem(nextSteps, line, 5, 150);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
pushUniqueWorkspaceItem(signals, firstUser, 3, 150);
|
|
198
|
+
if (lastUser && lastUser !== firstUser) {
|
|
199
|
+
pushUniqueWorkspaceItem(signals, lastUser, 3, 150);
|
|
200
|
+
}
|
|
201
|
+
pushUniqueWorkspaceItem(signals, lastAssistant, 3, 150);
|
|
202
|
+
|
|
203
|
+
const source = session && typeof session.source === 'string' ? session.source : '';
|
|
204
|
+
const sourceLabel = session && typeof session.sourceLabel === 'string' ? session.sourceLabel : source;
|
|
205
|
+
const cwd = session && typeof session.cwd === 'string' ? session.cwd : '';
|
|
206
|
+
const messageCount = safeMessages.length;
|
|
207
|
+
const available = messageCount > 0;
|
|
208
|
+
const metrics = [
|
|
209
|
+
{ key: 'messages', value: String(messageCount), label: options.messagesLabel || 'Messages' },
|
|
210
|
+
{ key: 'user', value: String(roleCounts.user), label: options.userLabel || 'User' },
|
|
211
|
+
{ key: 'assistant', value: String(roleCounts.assistant), label: options.assistantLabel || 'Assistant' },
|
|
212
|
+
{ key: 'commands', value: String(commands.length), label: options.commandsLabel || 'Commands' },
|
|
213
|
+
{ key: 'artifacts', value: String(files.length + links.length), label: options.artifactsLabel || 'Artifacts' },
|
|
214
|
+
{ key: 'risks', value: String(risks.length + nextSteps.length), label: options.risksLabel || 'Risks' }
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
const summary = {
|
|
218
|
+
available,
|
|
219
|
+
source,
|
|
220
|
+
sourceLabel,
|
|
221
|
+
cwd,
|
|
222
|
+
firstTimestamp,
|
|
223
|
+
lastTimestamp,
|
|
224
|
+
messageCount,
|
|
225
|
+
roleCounts,
|
|
226
|
+
metrics,
|
|
227
|
+
signals,
|
|
228
|
+
commands,
|
|
229
|
+
files,
|
|
230
|
+
links,
|
|
231
|
+
risks,
|
|
232
|
+
nextSteps
|
|
233
|
+
};
|
|
234
|
+
summary.briefText = buildWorkspaceBriefText(summary, options.briefLabels || {});
|
|
235
|
+
return summary;
|
|
236
|
+
}
|
|
237
|
+
|
|
54
238
|
function formatUsageDuration(value, options = {}) {
|
|
55
239
|
const normalizedLang = typeof options.lang === 'string' ? options.lang.trim().toLowerCase() : '';
|
|
56
240
|
const isEn = normalizedLang === 'en';
|
|
@@ -267,6 +451,32 @@ export function createSessionComputed() {
|
|
|
267
451
|
if (index < 0) return 0;
|
|
268
452
|
return Math.round(((index + 1) / this.sessionTimelineNodes.length) * 100);
|
|
269
453
|
},
|
|
454
|
+
activeSessionWorkspaceSummary() {
|
|
455
|
+
if (this.mainTab !== 'sessions' || !this.activeSession) {
|
|
456
|
+
return buildSessionWorkspaceSummary(null, []);
|
|
457
|
+
}
|
|
458
|
+
const t = typeof this.t === 'function' ? this.t : null;
|
|
459
|
+
return buildSessionWorkspaceSummary(this.activeSession, this.activeSessionMessages, {
|
|
460
|
+
messagesLabel: t ? t('sessions.workspace.metric.messages') : 'Messages',
|
|
461
|
+
userLabel: t ? t('sessions.workspace.metric.user') : 'User',
|
|
462
|
+
assistantLabel: t ? t('sessions.workspace.metric.assistant') : 'Assistant',
|
|
463
|
+
commandsLabel: t ? t('sessions.workspace.metric.commands') : 'Commands',
|
|
464
|
+
artifactsLabel: t ? t('sessions.workspace.metric.artifacts') : 'Artifacts',
|
|
465
|
+
risksLabel: t ? t('sessions.workspace.metric.risks') : 'Risks',
|
|
466
|
+
briefLabels: {
|
|
467
|
+
title: t ? t('sessions.workspace.copy.title') : 'Session workspace brief',
|
|
468
|
+
source: t ? t('sessions.workspace.copy.source') : 'Source',
|
|
469
|
+
messages: t ? t('sessions.workspace.metric.messages') : 'Messages',
|
|
470
|
+
path: t ? t('sessions.workspace.copy.path') : 'Path',
|
|
471
|
+
signals: t ? t('sessions.workspace.signals') : 'Signals',
|
|
472
|
+
commands: t ? t('sessions.workspace.commands') : 'Reusable commands',
|
|
473
|
+
files: t ? t('sessions.workspace.files') : 'Files',
|
|
474
|
+
links: t ? t('sessions.workspace.links') : 'Links',
|
|
475
|
+
risks: t ? t('sessions.workspace.risks') : 'Risks / blockers',
|
|
476
|
+
nextSteps: t ? t('sessions.workspace.nextSteps') : 'Next steps'
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
},
|
|
270
480
|
sessionQueryPlaceholder() {
|
|
271
481
|
if (this.isSessionQueryEnabled) {
|
|
272
482
|
return typeof this.t === 'function'
|
|
@@ -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
|
}
|