mixdog 0.9.66 → 0.9.68
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/package.json +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +20 -2
- package/src/runtime/channels/lib/config.mjs +13 -5
- package/src/runtime/channels/lib/owned-runtime.mjs +65 -5
- package/src/runtime/channels/lib/scheduler.mjs +7 -15
- package/src/runtime/channels/lib/status-snapshot.mjs +6 -30
- package/src/runtime/channels/lib/tool-dispatch.mjs +6 -1
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +203 -0
- package/src/runtime/channels/lib/webhook.mjs +46 -246
- package/src/runtime/channels/lib/worker-main.mjs +45 -92
- package/src/runtime/shared/automation-attachments.mjs +72 -0
- package/src/runtime/shared/automation-workflow.mjs +41 -0
- package/src/runtime/shared/config.mjs +0 -9
- package/src/runtime/shared/schedule-session-run.mjs +10 -1
- package/src/runtime/shared/schedules-db.mjs +18 -3
- package/src/runtime/shared/time-format.mjs +56 -0
- package/src/runtime/shared/tool-card-model.mjs +740 -0
- package/src/runtime/shared/webhook-session-run.mjs +57 -0
- package/src/runtime/shared/webhooks-db.mjs +19 -3
- package/src/session-runtime/channel-config-api.mjs +13 -1
- package/src/session-runtime/lifecycle-api.mjs +12 -0
- package/src/session-runtime/prewarm.mjs +13 -7
- package/src/session-runtime/runtime-core.mjs +127 -22
- package/src/session-runtime/session-text.mjs +6 -0
- package/src/session-runtime/settings-api.mjs +0 -12
- package/src/standalone/channel-admin.mjs +71 -22
- package/src/standalone/channel-daemon-client.mjs +5 -1
- package/src/standalone/channel-daemon-transport.mjs +112 -20
- package/src/standalone/channel-daemon.mjs +6 -4
- package/src/tui/App.jsx +2 -47
- package/src/tui/app/channel-pickers.mjs +8 -173
- package/src/tui/app/doctor.mjs +0 -1
- package/src/tui/app/slash-commands.mjs +1 -3
- package/src/tui/app/slash-dispatch.mjs +3 -3
- package/src/tui/components/ToolExecution.jsx +47 -196
- package/src/tui/components/tool-execution/surface-detail.mjs +33 -394
- package/src/tui/components/tool-execution/text-format.mjs +27 -104
- package/src/tui/dist/index.mjs +340 -346
- package/src/tui/engine/live-share.mjs +9 -0
- package/src/tui/engine/session-api-ext.mjs +32 -16
- package/src/tui/engine.mjs +14 -1
- package/src/tui/time-format.mjs +4 -51
- package/src/runtime/channels/lib/webhook/ngrok.mjs +0 -181
|
@@ -1,400 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* components/tool-execution/surface-detail.mjs —
|
|
3
|
-
* surface detection
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* components/tool-execution/surface-detail.mjs — theme-bound status color for
|
|
3
|
+
* the tool card. All surface detection and title/summary/detail derivation
|
|
4
|
+
* moved VERBATIM to runtime/shared/tool-card-model.mjs (single source shared
|
|
5
|
+
* with the desktop renderer); this module re-exports those helpers for the
|
|
6
|
+
* existing TUI imports and keeps only the theme-dependent dot color.
|
|
6
7
|
*/
|
|
7
8
|
import { theme } from '../../theme.mjs';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return 'completed';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function shellHeader(status, count = 1) {
|
|
40
|
-
const n = Math.max(1, Number(count) || 1);
|
|
41
|
-
const object = `${n} ${plural(n, 'command')}`;
|
|
42
|
-
if (status === 'running') return `Running ${object}`;
|
|
43
|
-
return `Ran ${object}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function isAgentTool(normalizedName) {
|
|
47
|
-
return normalizedName === 'agent';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const SKILL_SURFACE_NAMES = new Set([
|
|
51
|
-
'skill', 'skill_execute', 'skill_view', 'skills_list', 'use_skill',
|
|
52
|
-
]);
|
|
53
|
-
|
|
54
|
-
export function isBackgroundTaskTool(normalizedName) {
|
|
55
|
-
return new Set(['explore', 'search', 'shell', 'bash', 'bash_session', 'shell_command', 'task']).has(String(normalizedName || '').toLowerCase());
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const AGENT_DISPLAY_NAMES = new Map([
|
|
59
|
-
['explore', 'Explore'],
|
|
60
|
-
['maintainer', 'Maintainer'],
|
|
61
|
-
['worker', 'Worker'],
|
|
62
|
-
['heavy-worker', 'Heavy Worker'],
|
|
63
|
-
['reviewer', 'Reviewer'],
|
|
64
|
-
['debugger', 'Debugger'],
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
function titleizeAgentName(value) {
|
|
68
|
-
const text = String(value || '').trim();
|
|
69
|
-
if (!text) return '';
|
|
70
|
-
const key = text.toLowerCase().replace(/[\s_]+/g, '-');
|
|
71
|
-
if (AGENT_DISPLAY_NAMES.has(key)) return AGENT_DISPLAY_NAMES.get(key);
|
|
72
|
-
return text
|
|
73
|
-
.replace(/[_-]+/g, ' ')
|
|
74
|
-
.split(/\s+/)
|
|
75
|
-
.filter(Boolean)
|
|
76
|
-
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1).toLowerCase()}`)
|
|
77
|
-
.join(' ');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function agentModelLabel(args) {
|
|
81
|
-
const a = args && typeof args === 'object' ? args : {};
|
|
82
|
-
const provider = String(a.provider || a.providerId || a.provider_id || '').trim();
|
|
83
|
-
const model = String(a.model || '').trim();
|
|
84
|
-
const displayHint = String(a.modelDisplay || a.model_display || a.displayModel || '').trim();
|
|
85
|
-
return displayModelName(model, provider, displayHint);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function agentTagLabel(args) {
|
|
89
|
-
// The real spawn tag (engine fills parsedArgs.tag from the envelope target).
|
|
90
|
-
// Never fall back to task_id — only the human-meaningful spawn tag belongs in
|
|
91
|
-
// the header parentheses.
|
|
92
|
-
return String(args?.tag || '').trim();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function withModelAndTag(label, args) {
|
|
96
|
-
const model = agentModelLabel(args);
|
|
97
|
-
const tag = agentTagLabel(args);
|
|
98
|
-
const inner = [model, tag].filter(Boolean).join(', ');
|
|
99
|
-
return inner ? `${label} (${inner})` : label;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Append an agent name to a base action word without leaving a trailing space
|
|
103
|
-
// when the agent is unknown (no generic "Agent" fallback).
|
|
104
|
-
function joinActionAgent(action, agent) {
|
|
105
|
-
return agent ? `${action} ${agent}` : action;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function agentResponseTitle(args, count = 1) {
|
|
109
|
-
const total = Math.max(1, Number(count) || 1);
|
|
110
|
-
if (total > 1) return `Responses ${total} agents`;
|
|
111
|
-
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
112
|
-
// The agent + model identify the responder; the response summary itself
|
|
113
|
-
// is hidden in the collapsed card (ctrl+o expand still shows the full body).
|
|
114
|
-
// No generic "Agent" fallback — render just "Response" when the agent is empty.
|
|
115
|
-
return withModelAndTag(joinActionAgent('Response', name), args);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function agentActionTitle(args) {
|
|
119
|
-
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
120
|
-
// Runtime treats an omitted type/action as "spawn" (see agent-tool.mjs default),
|
|
121
|
-
// so mirror that contract here instead of falling through to the generic
|
|
122
|
-
// "Called agent" status copy.
|
|
123
|
-
const action = String(args?.type || args?.action || 'spawn').toLowerCase();
|
|
124
|
-
// Fixed action verbs regardless of running/completed status. No generic
|
|
125
|
-
// "Agent" fallback for the agent: when the agent is unknown render the action
|
|
126
|
-
// word alone ("Spawn") instead of "Spawn Agent".
|
|
127
|
-
if (action === 'spawn') return withModelAndTag(joinActionAgent('Spawn', name), args);
|
|
128
|
-
if (action === 'send') return withModelAndTag(joinActionAgent('Send', name), args);
|
|
129
|
-
if (action === 'list') return 'Agent status';
|
|
130
|
-
if (action === 'cancel') return withModelAndTag(joinActionAgent('Cancel', name), args);
|
|
131
|
-
if (action === 'close') return withModelAndTag(joinActionAgent('Close', name), args);
|
|
132
|
-
if (action === 'cleanup') return withModelAndTag(joinActionAgent('Cleanup', name), args);
|
|
133
|
-
if (action === 'read' || action === 'status') return withModelAndTag(joinActionAgent('Status', name), args);
|
|
134
|
-
return '';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function agentActionSummary(args, summary) {
|
|
138
|
-
const text = String(summary || '').trim();
|
|
139
|
-
if (!text) return '';
|
|
140
|
-
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
141
|
-
if (name && text === name) return '';
|
|
142
|
-
let rest = name && text.startsWith(`${name} · `) ? text.slice(name.length + 3).trim() : text;
|
|
143
|
-
// The agent/model/tag surface summary ("Heavy Worker · Opus 4.8") is now folded
|
|
144
|
-
// into the header label itself ("Spawn Heavy Worker (Opus 4.8, tag)"), so drop
|
|
145
|
-
// the model and tag tokens from the parenthesized summary to avoid showing
|
|
146
|
-
// them twice.
|
|
147
|
-
const model = agentModelLabel(args);
|
|
148
|
-
if (model && rest === model) return '';
|
|
149
|
-
const tag = agentTagLabel(args);
|
|
150
|
-
if (tag && rest === tag) return '';
|
|
151
|
-
return rest;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function hasAgentResponseResult(value) {
|
|
155
|
-
const text = String(value || '').trim();
|
|
156
|
-
if (!text) return false;
|
|
157
|
-
if (/^(?:undefined|null)$/i.test(text)) return false;
|
|
158
|
-
if (/^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)(?:\s*·\s*task_id:\s*\S+)?$/i.test(text)) return false;
|
|
159
|
-
const isBridgeEnvelope = /^(?:agent task:|background task\b|agent message queued\b|agent close:)/i.test(text)
|
|
160
|
-
|| /^(?:agents|tasks):\s*\d/i.test(text)
|
|
161
|
-
|| /^\(no agents or tasks\)$/i.test(text)
|
|
162
|
-
|| (/^task_id:\s*\S+/mi.test(text) && /^(?:surface|operation|status):\s*/mi.test(text));
|
|
163
|
-
if (!isBridgeEnvelope) return true;
|
|
164
|
-
let sawBlank = false;
|
|
165
|
-
for (const line of text.split('\n')) {
|
|
166
|
-
const trimmed = line.trim();
|
|
167
|
-
if (!trimmed) {
|
|
168
|
-
sawBlank = true;
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
if (/^agent result\b/i.test(trimmed)) continue;
|
|
172
|
-
if (/^(?:undefined|null)$/i.test(trimmed)) continue;
|
|
173
|
-
if (/^<\/?(?:final-answer|task-notification|task-id|tool-use-id|output-file|result|status|summary|usage|total_tokens|tool_uses|duration_ms|worktree|worktreePath|worktreeBranch)[^>]*>$/i.test(trimmed)) continue;
|
|
174
|
-
if (!sawBlank && /^(?:agent task|background task|agent message queued\b|agent close:|task_id|surface|operation|label|status|type|target|agent|preset|model|effort|fast|limits|started|finished|error|notification|queueDepth):?\s*/i.test(trimmed)) continue;
|
|
175
|
-
if (!sawBlank && /^(?:agents|tasks):\s*/i.test(trimmed)) continue;
|
|
176
|
-
if (/^\(no agents or tasks\)$/i.test(trimmed)) continue;
|
|
177
|
-
if (!sawBlank && /^-\s+\S+/i.test(trimmed)) continue;
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function parseBackgroundTaskResult(value) {
|
|
184
|
-
const text = String(value || '').trim();
|
|
185
|
-
if (!text) return null;
|
|
186
|
-
const allLines = text.split('\n');
|
|
187
|
-
const start = allLines.findIndex((line) => line.trim() === 'background task');
|
|
188
|
-
if (start < 0) return null;
|
|
189
|
-
const rest = allLines.slice(start + 1);
|
|
190
|
-
const blank = rest.findIndex((line) => !line.trim());
|
|
191
|
-
const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
|
|
192
|
-
const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
|
|
193
|
-
const fields = {};
|
|
194
|
-
for (const line of headLines) {
|
|
195
|
-
const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
|
|
196
|
-
if (match) fields[match[1].toLowerCase()] = match[2].trim();
|
|
197
|
-
}
|
|
198
|
-
const status = String(fields.status || '').toLowerCase();
|
|
199
|
-
const error = fields.error || '';
|
|
200
|
-
return {
|
|
201
|
-
taskId: fields.task_id || fields.taskid || '',
|
|
202
|
-
surface: fields.surface || '',
|
|
203
|
-
operation: fields.operation || '',
|
|
204
|
-
label: fields.label || '',
|
|
205
|
-
status,
|
|
206
|
-
startedAt: fields.started || fields.startedat || '',
|
|
207
|
-
finishedAt: fields.finished || fields.finishedat || '',
|
|
208
|
-
body,
|
|
209
|
-
error,
|
|
210
|
-
hasResponse: Boolean(body) && !isBackgroundErrorOnlyBody(body, error) && !/^(running|pending|queued)$/i.test(status),
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function backgroundTaskMetaFromArgs(args = {}) {
|
|
215
|
-
const taskId = String(args.task_id || args.taskId || '').trim();
|
|
216
|
-
if (!taskId) return null;
|
|
217
|
-
return {
|
|
218
|
-
taskId,
|
|
219
|
-
surface: args.surface || '',
|
|
220
|
-
operation: args.operation || '',
|
|
221
|
-
label: args.label || '',
|
|
222
|
-
status: String(args.status || '').toLowerCase(),
|
|
223
|
-
startedAt: args.startedAt || args.started || '',
|
|
224
|
-
finishedAt: args.finishedAt || args.finished || '',
|
|
225
|
-
error: args.error || '',
|
|
226
|
-
body: '',
|
|
227
|
-
hasResponse: false,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function resolveBackgroundTaskMeta(parsedArgs = {}, resultText = '') {
|
|
232
|
-
const parsed = parseBackgroundTaskResult(resultText);
|
|
233
|
-
if (parsed) {
|
|
234
|
-
if (!parsed.error && parsedArgs?.error) parsed.error = parsedArgs.error;
|
|
235
|
-
if (!parsed.status && parsedArgs?.status) parsed.status = String(parsedArgs.status).toLowerCase();
|
|
236
|
-
if (!parsed.surface && parsedArgs?.surface) parsed.surface = parsedArgs.surface;
|
|
237
|
-
return parsed;
|
|
238
|
-
}
|
|
239
|
-
return backgroundTaskMetaFromArgs(parsedArgs);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export function backgroundTaskElapsed(meta = {}, fallback = '') {
|
|
243
|
-
const startedMs = Date.parse(meta.startedAt || '');
|
|
244
|
-
const finishedMs = Date.parse(meta.finishedAt || '');
|
|
245
|
-
if (Number.isFinite(startedMs) && Number.isFinite(finishedMs) && finishedMs >= startedMs) {
|
|
246
|
-
const elapsedMs = finishedMs - startedMs;
|
|
247
|
-
return elapsedMs >= 1000 ? formatElapsed(elapsedMs) : '';
|
|
248
|
-
}
|
|
249
|
-
return fallback || '';
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function prefixElapsed(detail, elapsed = '') {
|
|
253
|
-
const text = String(detail || '').trim();
|
|
254
|
-
const time = String(elapsed || '').trim();
|
|
255
|
-
if (!time) return text;
|
|
256
|
-
// Unified convention: the elapsed time ALWAYS goes at the END, ` · ` separated.
|
|
257
|
-
// Guard against a double-append when the text already ends with the same time.
|
|
258
|
-
if (text && text.endsWith(`· ${time}`)) return text;
|
|
259
|
-
return text ? `${text} · ${time}` : time;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function mergeTerminalDetail(status, detail = '') {
|
|
263
|
-
const label = displayTerminalStatus(status);
|
|
264
|
-
const text = String(detail || '').trim();
|
|
265
|
-
if (!label) return text;
|
|
266
|
-
if (label === 'Finished' && text) return text;
|
|
267
|
-
if (!text) return label;
|
|
268
|
-
if (text.toLowerCase().startsWith(label.toLowerCase())) return text;
|
|
269
|
-
return `${label} · ${text}`;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function shouldPrefixSyncElapsed(normalizedName, label) {
|
|
273
|
-
const n = String(normalizedName || '').toLowerCase();
|
|
274
|
-
const l = String(label || '').toLowerCase();
|
|
275
|
-
return n === 'explore' || l === 'explore' || n === 'search' || l === 'search' || l === 'web search';
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function backgroundTaskDisplayName(normalizedName, meta = {}) {
|
|
279
|
-
const surface = String(meta.surface || normalizedName || '').toLowerCase();
|
|
280
|
-
if (surface === 'explore') return 'Explore';
|
|
281
|
-
if (surface === 'search') return 'Search';
|
|
282
|
-
if (surface === 'shell' || surface === 'bash' || surface === 'bash_session' || surface === 'shell_command' || surface === 'task') return 'Shell';
|
|
283
|
-
return titleizeAgentName(surface || normalizedName || 'Task');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export function backgroundTaskResultTitle(normalizedName, meta = {}) {
|
|
287
|
-
const display = backgroundTaskDisplayName(normalizedName, meta);
|
|
288
|
-
if (display === 'Shell') return 'Shell output';
|
|
289
|
-
if (display === 'Search') return 'Search results';
|
|
290
|
-
return `${display} response`;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export function backgroundTaskActionTitle(normalizedName, meta = {}) {
|
|
294
|
-
const display = backgroundTaskDisplayName(normalizedName, meta);
|
|
295
|
-
if (/^(running|pending|queued)$/i.test(meta.status || '')) return `Started ${display}`;
|
|
296
|
-
if (meta.hasResponse) return backgroundTaskResultTitle(normalizedName, meta);
|
|
297
|
-
return `${display} status`;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export function backgroundTaskFailureDetail(meta = {}, parsedArgs = {}) {
|
|
301
|
-
const status = meta.status || parsedArgs?.status;
|
|
302
|
-
const error = meta.error || parsedArgs?.error;
|
|
303
|
-
if (!error) return '';
|
|
304
|
-
const surface = meta.surface || parsedArgs?.surface || '';
|
|
305
|
-
return backgroundTaskFailureStatusLabel(status, error, { surface });
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export function backgroundTaskDetail(meta = {}, elapsed = '', parsedArgs = {}) {
|
|
309
|
-
const parts = [];
|
|
310
|
-
const status = displayTerminalStatus(meta.status);
|
|
311
|
-
if (status) parts.push(status);
|
|
312
|
-
if (meta.taskId) parts.push(`task_id: ${meta.taskId}`);
|
|
313
|
-
const firstBodyLine = String(meta.body || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
314
|
-
if (firstBodyLine && /^(running|pending|queued)$/i.test(meta.status || '')) parts.push(firstBodyLine);
|
|
315
|
-
return prefixElapsed(parts.join(' · '), elapsed);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export function isBackgroundTaskResponseArgs(normalizedName, args = {}) {
|
|
319
|
-
if (!isBackgroundTaskTool(normalizedName)) return false;
|
|
320
|
-
const type = String(args?.type || args?.action || '').toLowerCase();
|
|
321
|
-
const status = String(args?.status || '').toLowerCase();
|
|
322
|
-
return type === 'result' || type === 'completion' || (/^(completed|cancelled|canceled)$/i.test(status) && Boolean(args?.task_id));
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function isOutputDetailTool(normalizedName, label) {
|
|
326
|
-
const n = String(normalizedName || '').toLowerCase();
|
|
327
|
-
const l = String(label || '').toLowerCase();
|
|
328
|
-
return new Set([
|
|
329
|
-
'shell', 'bash', 'bash_session', 'shell_command', 'job_wait',
|
|
330
|
-
'read', 'view_image', 'read_mcp_resource',
|
|
331
|
-
'grep', 'glob', 'search', 'search_query', 'image_query', 'web_search', 'web_search_call', 'explore', 'web_fetch', 'fetch',
|
|
332
|
-
'list', 'ls', 'code_graph',
|
|
333
|
-
'recall', 'recall_memory', 'search_memories', 'remember', 'save_memory', 'update_memory',
|
|
334
|
-
]).has(n) || l === 'read' || l === 'search' || l === 'web search' || l === 'run';
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export function genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) {
|
|
338
|
-
const n = String(normalizedName || '').toLowerCase();
|
|
339
|
-
const l = String(label || '').toLowerCase();
|
|
340
|
-
if (isError) return hasResult ? firstResultLine : 'Failed';
|
|
341
|
-
if (n === 'shell' || n === 'bash' || n === 'bash_session' || n === 'shell_command' || n === 'job_wait') {
|
|
342
|
-
return '';
|
|
343
|
-
}
|
|
344
|
-
if (isOutputDetailTool(n, l)) {
|
|
345
|
-
return hasResult ? firstResultLine : '';
|
|
346
|
-
}
|
|
347
|
-
return '';
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export function toolSearchLoadedSummary(resultText) {
|
|
351
|
-
let parsed;
|
|
352
|
-
try {
|
|
353
|
-
parsed = JSON.parse(String(resultText || ''));
|
|
354
|
-
} catch {
|
|
355
|
-
const text = String(resultText || '');
|
|
356
|
-
const loaded = /^Loaded deferred tools:\s*(.+)$/m.exec(text)?.[1] || '';
|
|
357
|
-
const already = /^Already active:\s*(.+)$/m.exec(text)?.[1] || '';
|
|
358
|
-
return [
|
|
359
|
-
...(loaded ? [`Loaded: ${loaded}`] : []),
|
|
360
|
-
...(already ? [`Already active: ${already}`] : []),
|
|
361
|
-
].join(' · ');
|
|
362
|
-
}
|
|
363
|
-
const tools = parsed?.selected?.tools;
|
|
364
|
-
if (!tools || typeof tools !== 'object') return '';
|
|
365
|
-
const uniqueNames = (names) => [...new Set((Array.isArray(names) ? names : [])
|
|
366
|
-
.map((name) => String(name || '').trim())
|
|
367
|
-
.filter(Boolean))];
|
|
368
|
-
const loaded = uniqueNames(tools.added);
|
|
369
|
-
const already = uniqueNames(tools.already);
|
|
370
|
-
return [
|
|
371
|
-
...(loaded.length ? [`Loaded: ${loaded.join(', ')}`] : []),
|
|
372
|
-
...(already.length ? [`Already active: ${already.join(', ')}`] : []),
|
|
373
|
-
].join(' · ');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export function agentTerminalDetail(status, isError, elapsed, error = '') {
|
|
377
|
-
const failureDetail = isError && error
|
|
378
|
-
? backgroundTaskFailureStatusLabel(status, error, { surface: 'agent' })
|
|
379
|
-
: '';
|
|
380
|
-
if (failureDetail) return failureDetail;
|
|
381
|
-
const s = String(status || '').toLowerCase();
|
|
382
|
-
const word = /cancel/.test(s)
|
|
383
|
-
? 'Cancelled'
|
|
384
|
-
: /error|fail|killed|timeout/.test(s) || isError
|
|
385
|
-
? 'Failed'
|
|
386
|
-
: /done|success|complete|closed/.test(s)
|
|
387
|
-
? 'Finished'
|
|
388
|
-
: '';
|
|
389
|
-
// Unified ` · <time>` convention (previously "Finished after 12s").
|
|
390
|
-
return word ? `${word}${elapsed ? ` · ${elapsed}` : ''}` : '';
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export function clampFailureCount(errorCount, groupCount, isError) {
|
|
394
|
-
const explicit = Number(errorCount);
|
|
395
|
-
if (Number.isFinite(explicit)) return Math.max(0, Math.min(groupCount, Math.floor(explicit)));
|
|
396
|
-
return isError ? groupCount : 0;
|
|
397
|
-
}
|
|
9
|
+
import { normalizeTerminalStatus } from '../../../runtime/shared/tool-card-model.mjs';
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
isShellTool,
|
|
13
|
+
shellDisplayStatus,
|
|
14
|
+
shellHeader,
|
|
15
|
+
isAgentTool,
|
|
16
|
+
SKILL_SURFACE_NAMES,
|
|
17
|
+
isBackgroundTaskTool,
|
|
18
|
+
agentResponseTitle,
|
|
19
|
+
agentActionTitle,
|
|
20
|
+
agentActionSummary,
|
|
21
|
+
hasAgentResponseResult,
|
|
22
|
+
resolveBackgroundTaskMeta,
|
|
23
|
+
backgroundTaskElapsed,
|
|
24
|
+
prefixElapsed,
|
|
25
|
+
mergeTerminalDetail,
|
|
26
|
+
shouldPrefixSyncElapsed,
|
|
27
|
+
backgroundTaskResultTitle,
|
|
28
|
+
backgroundTaskActionTitle,
|
|
29
|
+
backgroundTaskFailureDetail,
|
|
30
|
+
backgroundTaskDetail,
|
|
31
|
+
isBackgroundTaskResponseArgs,
|
|
32
|
+
genericCompletedDetail,
|
|
33
|
+
toolSearchLoadedSummary,
|
|
34
|
+
agentTerminalDetail,
|
|
35
|
+
clampFailureCount,
|
|
36
|
+
} from '../../../runtime/shared/tool-card-model.mjs';
|
|
398
37
|
|
|
399
38
|
// Single source of truth for the tool-card dot (●) color. Both the aggregate
|
|
400
39
|
// and normal (single-tool) render paths must call this with a resolved
|
|
@@ -1,63 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* components/tool-execution/text-format.mjs —
|
|
3
|
-
* the tool card
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* components/tool-execution/text-format.mjs — width/theme-bound text helpers
|
|
3
|
+
* for the tool card. The pure, surface-agnostic pieces (inline sanitization,
|
|
4
|
+
* status parsing/casing, count normalization) moved VERBATIM to
|
|
5
|
+
* runtime/shared/tool-card-model.mjs so the desktop renderer derives IDENTICAL
|
|
6
|
+
* tool-card text; this module re-exports them for existing TUI imports and
|
|
7
|
+
* keeps only what needs the live terminal theme or display-width math.
|
|
6
8
|
*/
|
|
7
9
|
import { displayWidth } from '../../display-width.mjs';
|
|
8
10
|
import { theme } from '../../theme.mjs';
|
|
9
|
-
import { formatElapsed } from '../../time-format.mjs';
|
|
10
|
-
import stripAnsi from 'strip-ansi';
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const INLINE_CONTROL_RE = /[\u0000-\u001F\u007F]/g;
|
|
34
|
-
|
|
35
|
-
export function safeInlineText(value) {
|
|
36
|
-
return stripAnsi(String(value ?? ''))
|
|
37
|
-
.replace(/\r\n?/g, '\n')
|
|
38
|
-
.replace(/\t/g, ' ')
|
|
39
|
-
.replace(/\n+/g, ' ')
|
|
40
|
-
.replace(INLINE_CONTROL_RE, ' ')
|
|
41
|
-
.replace(/\s+/g, ' ')
|
|
42
|
-
.trim();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function normalizeCount(value) {
|
|
46
|
-
const n = Number(value || 0);
|
|
47
|
-
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function normalizeCountMap(value = {}) {
|
|
51
|
-
const out = {};
|
|
52
|
-
for (const [key, raw] of Object.entries(value || {})) {
|
|
53
|
-
if (raw && typeof raw === 'object') {
|
|
54
|
-
out[key] = { ...raw, count: normalizeCount(raw.count) };
|
|
55
|
-
} else {
|
|
56
|
-
out[key] = normalizeCount(raw);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
12
|
+
safeInlineText,
|
|
13
|
+
MIN_RESULT_LINE_CHARS,
|
|
14
|
+
RESULT_LINE_HARD_MAX,
|
|
15
|
+
} from '../../../runtime/shared/tool-card-model.mjs';
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
MIN_RESULT_LINE_CHARS,
|
|
19
|
+
RESULT_LINE_HARD_MAX,
|
|
20
|
+
SUMMARY_MAX_CHARS,
|
|
21
|
+
HEADER_FAILURE_STATUS_MAX,
|
|
22
|
+
safeInlineText,
|
|
23
|
+
normalizeCountMap,
|
|
24
|
+
plural,
|
|
25
|
+
shellResultStatus,
|
|
26
|
+
normalizeTerminalStatus,
|
|
27
|
+
displayTerminalStatus,
|
|
28
|
+
resultTerminalStatus,
|
|
29
|
+
stripLeadingStatusMarkerLines,
|
|
30
|
+
stripLeadingStatusMarkerFromText,
|
|
31
|
+
shellResultElapsed,
|
|
32
|
+
} from '../../../runtime/shared/tool-card-model.mjs';
|
|
61
33
|
|
|
62
34
|
function deltaColor(token) {
|
|
63
35
|
return String(token || '').startsWith('+') ? theme.success : theme.error;
|
|
@@ -82,10 +54,6 @@ export function deltaTextParts(text) {
|
|
|
82
54
|
return parts;
|
|
83
55
|
}
|
|
84
56
|
|
|
85
|
-
export function plural(count, singular, pluralText = `${singular}s`) {
|
|
86
|
-
return count === 1 ? singular : pluralText;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
57
|
export function fitResultLine(line, columns) {
|
|
90
58
|
const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
|
|
91
59
|
const text = safeInlineText(line);
|
|
@@ -105,48 +73,3 @@ export function truncateToWidth(text, maxWidth) {
|
|
|
105
73
|
}
|
|
106
74
|
return `${out}…`;
|
|
107
75
|
}
|
|
108
|
-
|
|
109
|
-
export function shellResultStatus(value) {
|
|
110
|
-
const match = String(value || '').match(/(?:^|\b)status:\s*(running|pending|queued|completed|failed|cancelled|canceled)\b/im);
|
|
111
|
-
return match ? String(match[1] || '').toLowerCase() : '';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function normalizeTerminalStatus(value) {
|
|
115
|
-
return normalizeToolTerminalStatus(value);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function displayTerminalStatus(value) {
|
|
119
|
-
// 'exit' is a shell-only pseudo-status (command RAN but exited non-zero); it
|
|
120
|
-
// is intentionally NOT a normalized terminal status so it never colors red.
|
|
121
|
-
if (String(value || '').trim().toLowerCase() === 'exit') return 'Exit';
|
|
122
|
-
const status = normalizeTerminalStatus(value);
|
|
123
|
-
if (status === 'running') return 'Running';
|
|
124
|
-
if (status === 'completed') return 'Finished';
|
|
125
|
-
if (status === 'failed') return 'Failed';
|
|
126
|
-
if (status === 'cancelled') return 'Cancelled';
|
|
127
|
-
if (status === 'denied') return 'Denied';
|
|
128
|
-
return '';
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function resultTerminalStatus(value) {
|
|
132
|
-
return toolResultTerminalStatus(value);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const LEADING_STATUS_MARKER_LINE_RE = /^\[status:\s*[^\]]*\]\s*$/i;
|
|
136
|
-
|
|
137
|
-
export function stripLeadingStatusMarkerLines(lines) {
|
|
138
|
-
const out = Array.isArray(lines) ? lines.slice() : [];
|
|
139
|
-
if (out.length > 0 && LEADING_STATUS_MARKER_LINE_RE.test(String(out[0] ?? '').trim())) out.shift();
|
|
140
|
-
return out;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function stripLeadingStatusMarkerFromText(text) {
|
|
144
|
-
return stripLeadingStatusMarkerLines(String(text || '').split('\n')).join('\n');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function shellResultElapsed(value) {
|
|
148
|
-
const match = String(value || '').match(/^\[elapsed:\s*(\d+)\s*ms\]/mi);
|
|
149
|
-
if (!match) return '';
|
|
150
|
-
const elapsedMs = Number(match[1]);
|
|
151
|
-
return Number.isFinite(elapsedMs) && elapsedMs >= 1000 ? formatElapsed(elapsedMs) : '';
|
|
152
|
-
}
|