@vvedantb/ui 0.17.1 → 0.18.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.
- package/README.md +251 -3
- package/package.json +13 -5
- package/shell/src/SidebarResize.tsx +1 -4
- package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
- package/src/ai-elements/activity-question-utils.ts +26 -0
- package/src/ai-elements/activity-question.tsx +159 -0
- package/src/ai-elements/activity-shared.tsx +459 -0
- package/src/ai-elements/activity-step-detail.tsx +115 -0
- package/src/ai-elements/activity-step-label.ts +981 -0
- package/src/ai-elements/activity-tasks-utils.ts +199 -0
- package/src/ai-elements/activity-tasks.tsx +770 -0
- package/src/ai-elements/artifact.tsx +162 -52
- package/src/ai-elements/attachments.tsx +62 -32
- package/src/ai-elements/code-block.tsx +76 -22
- package/src/ai-elements/context.tsx +370 -0
- package/src/ai-elements/conversation.tsx +186 -62
- package/src/ai-elements/file-chip.tsx +53 -0
- package/src/ai-elements/message.tsx +161 -50
- package/src/ai-elements/model-picker-content.tsx +505 -0
- package/src/ai-elements/model-picker-types.ts +56 -0
- package/src/ai-elements/model-picker.tsx +301 -0
- package/src/ai-elements/model-selector.tsx +102 -21
- package/src/ai-elements/plan.tsx +130 -0
- package/src/ai-elements/prompt-input-settings.tsx +144 -0
- package/src/ai-elements/prompt-input-speech.tsx +189 -0
- package/src/ai-elements/prompt-input-traits.tsx +281 -0
- package/src/ai-elements/prompt-input.tsx +320 -134
- package/src/ai-elements/provider-icon.tsx +133 -0
- package/src/ai-elements/queue.tsx +293 -9
- package/src/ai-elements/shimmer.tsx +72 -1
- package/src/ai-elements/simple-model-ladder.tsx +108 -0
- package/src/ai-elements/speech-input.tsx +5 -3
- package/src/ai-elements/test-results.tsx +510 -0
- package/src/ai-elements/trace.tsx +243 -0
- package/src/ai-elements/use-streaming-disclosure.ts +50 -0
- package/src/ai-elements/web-preview.tsx +349 -0
- package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
- package/src/index.ts +66 -12
- package/src/kibo/avatar-stack.tsx +40 -0
- package/src/kibo/contribution-graph.tsx +539 -0
- package/src/kibo/cursor.tsx +72 -0
- package/src/kibo/gantt-features.tsx +439 -0
- package/src/kibo/gantt-header.tsx +181 -0
- package/src/kibo/gantt-provider.tsx +657 -0
- package/src/kibo/gantt-sidebar.tsx +157 -0
- package/src/kibo/gantt-timeline.tsx +154 -0
- package/src/kibo/index.ts +151 -0
- package/src/kibo/kanban.tsx +338 -0
- package/src/kibo/kanbanDropTarget.ts +25 -0
- package/src/kibo/list.tsx +162 -0
- package/src/kibo/theme-switcher.tsx +109 -0
- package/src/kibo/video-player.tsx +237 -0
- package/src/markdown/Markdown.tsx +112 -0
- package/src/markdown/MarkdownBlockquote.tsx +45 -0
- package/src/markdown/MarkdownCodeBlock.tsx +197 -0
- package/src/markdown/MarkdownInlineCode.tsx +40 -0
- package/src/markdown/MarkdownLink.tsx +92 -0
- package/src/markdown/codeFence.ts +57 -0
- package/src/markdown/codeHighlight.ts +138 -0
- package/src/markdown/incrementalBlocks.ts +182 -0
- package/src/markdown/index.ts +3 -0
- package/src/markdown/markdownClipboard.ts +178 -0
- package/src/markdown/mermaid.ts +155 -0
- package/src/markdown/rehypeGithubAlerts.ts +80 -0
- package/src/motion/presets.ts +24 -0
- package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
- package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
- package/src/ui/_menu-parts.tsx +29 -0
- package/src/ui/accordion.alt.tsx +46 -53
- package/src/ui/accordion.tsx +59 -10
- package/src/ui/avatar.tsx +56 -0
- package/src/ui/badge.tsx +45 -21
- package/src/ui/border-beam.tsx +97 -0
- package/src/ui/button-group.tsx +50 -21
- package/src/ui/button.tsx +67 -45
- package/src/ui/calendar.tsx +48 -5
- package/src/ui/card.tsx +77 -8
- package/src/ui/carousel.tsx +60 -0
- package/src/ui/centered-spinner.tsx +30 -0
- package/src/ui/checkbox.tsx +34 -16
- package/src/ui/clear-input.alt.tsx +33 -75
- package/src/ui/clear-input.tsx +140 -15
- package/src/ui/collapsible.tsx +40 -2
- package/src/ui/command.tsx +143 -70
- package/src/ui/context-menu.tsx +159 -93
- package/src/ui/crossfade-icon.tsx +123 -0
- package/src/ui/dialog.tsx +154 -70
- package/src/ui/dropdown-menu.tsx +177 -117
- package/src/ui/hover-card.tsx +59 -17
- package/src/ui/input-group.alt.tsx +83 -83
- package/src/ui/input-group.tsx +129 -58
- package/src/ui/input.tsx +13 -4
- package/src/ui/label.tsx +11 -8
- package/src/ui/list-row.tsx +232 -0
- package/src/ui/loading-state.tsx +215 -0
- package/src/ui/popover.tsx +56 -23
- package/src/ui/progress.tsx +28 -16
- package/src/ui/refresh-spin-icon.tsx +41 -0
- package/src/ui/search-input.tsx +66 -15
- package/src/ui/select.tsx +171 -100
- package/src/ui/separator.tsx +23 -14
- package/src/ui/slider.tsx +21 -7
- package/src/ui/sonner.tsx +68 -30
- package/src/ui/spinner.tsx +229 -11
- package/src/ui/status-dot.tsx +84 -0
- package/src/ui/surface.tsx +49 -0
- package/src/ui/switch.tsx +29 -15
- package/src/ui/table.tsx +38 -16
- package/src/ui/tabs.tsx +123 -35
- package/src/ui/tabsSliding.ts +33 -1
- package/src/ui/textarea.tsx +13 -4
- package/src/ui/tooltip.tsx +50 -14
- package/src/ui/ui-config.tsx +149 -0
- package/src/utils/cat-color.ts +123 -0
- package/src/utils/dayjs.ts +6 -0
- package/src/utils/gesture.ts +59 -0
- package/src/utils/runtimeVisibility.ts +167 -0
- package/src/utils/sharedClock.ts +96 -0
- package/src/utils/surface-radius.ts +16 -0
- package/src/utils/useDragSensors.ts +58 -0
- package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
- package/tokens/examples/tokens-moved/README.md +20 -0
- package/tokens/examples/tokens-moved/index.html +12 -0
- package/tokens/examples/tokens-moved/main.tsx +6 -0
- package/tokens/examples/tokens-moved/package.json +14 -0
- package/tokens/examples/tokens-moved/styles.css +12 -0
- package/tokens/examples/tokens-moved/tsconfig.json +14 -0
- package/tokens/examples/tokens-moved/vite.config.ts +11 -0
- package/tokens/src/css/border-beam.css +116 -0
- package/tokens/src/css/markdown.css +318 -0
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
import type { ActivityStep } from "./activity-shared";
|
|
2
|
+
|
|
3
|
+
export type CommandVisualKind = "inspect" | "git" | "terminal";
|
|
4
|
+
|
|
5
|
+
export interface StepRowPresentation {
|
|
6
|
+
text: string;
|
|
7
|
+
fileChip?: { name: string; path?: string };
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const READ_FILE_COMMAND_TOOLS = new Set([
|
|
12
|
+
"cat",
|
|
13
|
+
"nl",
|
|
14
|
+
"head",
|
|
15
|
+
"tail",
|
|
16
|
+
"sed",
|
|
17
|
+
"less",
|
|
18
|
+
"more",
|
|
19
|
+
]);
|
|
20
|
+
const SEARCH_COMMAND_TOOLS = new Set(["rg", "grep", "ag", "ack"]);
|
|
21
|
+
const FIND_COMMAND_TOOLS = new Set(["find", "fd"]);
|
|
22
|
+
const LIST_COMMAND_TOOLS = new Set(["ls"]);
|
|
23
|
+
|
|
24
|
+
function isInspectCommandTool(tool: string): boolean {
|
|
25
|
+
return (
|
|
26
|
+
READ_FILE_COMMAND_TOOLS.has(tool) ||
|
|
27
|
+
SEARCH_COMMAND_TOOLS.has(tool) ||
|
|
28
|
+
FIND_COMMAND_TOOLS.has(tool) ||
|
|
29
|
+
LIST_COMMAND_TOOLS.has(tool)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function basename(value: string): string {
|
|
34
|
+
const slash = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\"));
|
|
35
|
+
return slash >= 0 ? value.slice(slash + 1) : value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function truncatePattern(value: string, max = 30): string {
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
if (trimmed.length <= max) {
|
|
41
|
+
return trimmed;
|
|
42
|
+
}
|
|
43
|
+
return `${trimmed.slice(0, max - 3).trimEnd()}...`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compactPath(path: string): string {
|
|
47
|
+
if (path === ".") {
|
|
48
|
+
return "current directory";
|
|
49
|
+
}
|
|
50
|
+
if (path === "..") {
|
|
51
|
+
return "parent directory";
|
|
52
|
+
}
|
|
53
|
+
const parts = path.split(/[\\/]/).filter(Boolean);
|
|
54
|
+
if (parts.length <= 2) {
|
|
55
|
+
return path;
|
|
56
|
+
}
|
|
57
|
+
return parts.slice(-2).join("/");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function compactInlineCommand(command: string): string {
|
|
61
|
+
const normalized = command.replace(/\s+/g, " ").trim();
|
|
62
|
+
if (normalized.length <= 140) {
|
|
63
|
+
return normalized;
|
|
64
|
+
}
|
|
65
|
+
return `${normalized.slice(0, 137).trimEnd()}...`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function tokenizeCommandArgs(args: string): string[] {
|
|
69
|
+
const tokens: string[] = [];
|
|
70
|
+
let index = 0;
|
|
71
|
+
|
|
72
|
+
while (index < args.length) {
|
|
73
|
+
while (args[index] === " ") {
|
|
74
|
+
index += 1;
|
|
75
|
+
}
|
|
76
|
+
if (index >= args.length) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const quote = args[index];
|
|
81
|
+
if (quote === '"' || quote === "'") {
|
|
82
|
+
index += 1;
|
|
83
|
+
let token = "";
|
|
84
|
+
while (index < args.length && args[index] !== quote) {
|
|
85
|
+
if (args[index] === "\\" && index + 1 < args.length) {
|
|
86
|
+
token += args[index + 1];
|
|
87
|
+
index += 2;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
token += args[index];
|
|
91
|
+
index += 1;
|
|
92
|
+
}
|
|
93
|
+
if (args[index] === quote) {
|
|
94
|
+
index += 1;
|
|
95
|
+
}
|
|
96
|
+
tokens.push(token);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let token = "";
|
|
101
|
+
while (index < args.length && args[index] !== " ") {
|
|
102
|
+
token += args[index];
|
|
103
|
+
index += 1;
|
|
104
|
+
}
|
|
105
|
+
if (token) {
|
|
106
|
+
tokens.push(token);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return tokens;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function splitToolAndArgs(command: string): [tool: string, args: string] {
|
|
114
|
+
const normalized = command.trim().replace(/\s+/g, " ");
|
|
115
|
+
if (!normalized) {
|
|
116
|
+
return ["", ""];
|
|
117
|
+
}
|
|
118
|
+
const separator = normalized.indexOf(" ");
|
|
119
|
+
if (separator === -1) {
|
|
120
|
+
return [basename(normalized).toLowerCase(), ""];
|
|
121
|
+
}
|
|
122
|
+
const tool = basename(normalized.slice(0, separator)).toLowerCase();
|
|
123
|
+
const args = normalized.slice(separator + 1).trim();
|
|
124
|
+
return [tool, args];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findShellChain(
|
|
128
|
+
value: string,
|
|
129
|
+
): { operatorStart: number; commandStart: number } | null {
|
|
130
|
+
let quote: '"' | "'" | null = null;
|
|
131
|
+
|
|
132
|
+
for (let index = 0; index < value.length - 1; index += 1) {
|
|
133
|
+
const char = value[index];
|
|
134
|
+
if (char === "\\" && index + 1 < value.length) {
|
|
135
|
+
index += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (quote) {
|
|
139
|
+
if (char === quote) {
|
|
140
|
+
quote = null;
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (char === '"' || char === "'") {
|
|
145
|
+
quote = char;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const next = value[index + 1];
|
|
149
|
+
if (char === "&" && next === "&") {
|
|
150
|
+
return { operatorStart: index, commandStart: index + 2 };
|
|
151
|
+
}
|
|
152
|
+
if (char === ";") {
|
|
153
|
+
return { operatorStart: index, commandStart: index + 1 };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isShellSetupPreamble(value: string): boolean {
|
|
161
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
162
|
+
if (!normalized) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (/^(?:builtin\s+)?cd\s+/.test(normalized)) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (/^(?:source|\.)\s+/.test(normalized)) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
if (/^set\s+[-+][A-Za-z]/.test(normalized)) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (
|
|
175
|
+
/^(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*=[^\s]+(?:\s+[A-Za-z_][A-Za-z0-9_]*=[^\s]+)*$/.test(
|
|
176
|
+
normalized,
|
|
177
|
+
)
|
|
178
|
+
) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function stripLeadingShellPreambles(value: string): string {
|
|
185
|
+
let current = value.trim();
|
|
186
|
+
for (let attempts = 0; attempts < 4; attempts += 1) {
|
|
187
|
+
const chain = findShellChain(current);
|
|
188
|
+
if (!chain) {
|
|
189
|
+
return current;
|
|
190
|
+
}
|
|
191
|
+
const head = current.slice(0, chain.operatorStart).trim();
|
|
192
|
+
if (!isShellSetupPreamble(head)) {
|
|
193
|
+
return current;
|
|
194
|
+
}
|
|
195
|
+
current = current.slice(chain.commandStart).trim();
|
|
196
|
+
}
|
|
197
|
+
return current;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function unwrapShellCommandIfPresent(rawCommand: string): string {
|
|
201
|
+
let value = rawCommand.trim();
|
|
202
|
+
if (!value) {
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const shellPrefixes = [
|
|
207
|
+
"/usr/bin/bash -lc ",
|
|
208
|
+
"/usr/bin/bash -c ",
|
|
209
|
+
"/bin/bash -lc ",
|
|
210
|
+
"/bin/bash -c ",
|
|
211
|
+
"/usr/bin/zsh -lc ",
|
|
212
|
+
"/usr/bin/zsh -c ",
|
|
213
|
+
"/bin/zsh -lc ",
|
|
214
|
+
"/bin/zsh -c ",
|
|
215
|
+
"/bin/sh -lc ",
|
|
216
|
+
"/bin/sh -c ",
|
|
217
|
+
"bash -lc ",
|
|
218
|
+
"bash -c ",
|
|
219
|
+
"zsh -lc ",
|
|
220
|
+
"zsh -c ",
|
|
221
|
+
"sh -lc ",
|
|
222
|
+
"sh -c ",
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
const lowered = value.toLowerCase();
|
|
226
|
+
for (const prefix of shellPrefixes) {
|
|
227
|
+
if (!lowered.startsWith(prefix)) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
value = value.slice(prefix.length).trim();
|
|
231
|
+
if (
|
|
232
|
+
value.length >= 2 &&
|
|
233
|
+
((value.startsWith('"') && value.endsWith('"')) ||
|
|
234
|
+
(value.startsWith("'") && value.endsWith("'")))
|
|
235
|
+
) {
|
|
236
|
+
value = value.slice(1, -1).trim();
|
|
237
|
+
}
|
|
238
|
+
value = stripLeadingShellPreambles(value);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const pipeIndex = value.search(/\s*\|\s*/);
|
|
243
|
+
if (pipeIndex > 0) {
|
|
244
|
+
value = value.slice(0, pipeIndex).trim();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function stripEnvCommand(args: string): string | null {
|
|
251
|
+
const tokens = tokenizeCommandArgs(args);
|
|
252
|
+
let index = 0;
|
|
253
|
+
while (index < tokens.length) {
|
|
254
|
+
const token = tokens[index];
|
|
255
|
+
if (token === undefined) {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
if (token === "--") {
|
|
259
|
+
index += 1;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
if (
|
|
263
|
+
token === "-u" ||
|
|
264
|
+
token === "--unset" ||
|
|
265
|
+
token === "-C" ||
|
|
266
|
+
token === "--chdir"
|
|
267
|
+
) {
|
|
268
|
+
index += 2;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (token.startsWith("--unset=") || token.startsWith("--chdir=")) {
|
|
272
|
+
index += 1;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)) {
|
|
276
|
+
index += 1;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (token.startsWith("-")) {
|
|
280
|
+
index += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
return index < tokens.length ? tokens.slice(index).join(" ") : null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function stripTimeoutCommand(args: string): string | null {
|
|
289
|
+
const tokens = tokenizeCommandArgs(args);
|
|
290
|
+
let index = 0;
|
|
291
|
+
while (index < tokens.length) {
|
|
292
|
+
const token = tokens[index];
|
|
293
|
+
if (token === undefined || !token.startsWith("-")) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
index += token === "-s" || token === "-k" ? 2 : 1;
|
|
297
|
+
}
|
|
298
|
+
const duration = tokens[index];
|
|
299
|
+
if (duration !== undefined && /^\d+(?:\.\d+)?[smhd]?$/.test(duration)) {
|
|
300
|
+
index += 1;
|
|
301
|
+
}
|
|
302
|
+
return index < tokens.length ? tokens.slice(index).join(" ") : null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function stripNiceCommand(args: string): string | null {
|
|
306
|
+
const tokens = tokenizeCommandArgs(args);
|
|
307
|
+
let index = 0;
|
|
308
|
+
if (tokens[index] === "-n") {
|
|
309
|
+
index += 2;
|
|
310
|
+
} else {
|
|
311
|
+
while (tokens[index]?.startsWith("-")) {
|
|
312
|
+
index += 1;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return index < tokens.length ? tokens.slice(index).join(" ") : null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function stripArchCommand(args: string): string | null {
|
|
319
|
+
const tokens = tokenizeCommandArgs(args);
|
|
320
|
+
let index = 0;
|
|
321
|
+
while (tokens[index]?.startsWith("-")) {
|
|
322
|
+
index += 1;
|
|
323
|
+
}
|
|
324
|
+
return index < tokens.length ? tokens.slice(index).join(" ") : null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function stripCommandDisplayWrappers(command: string): string {
|
|
328
|
+
let current = command.replace(/\s+/g, " ").trim();
|
|
329
|
+
for (let attempts = 0; attempts < 4; attempts += 1) {
|
|
330
|
+
const [tool, args] = splitToolAndArgs(current);
|
|
331
|
+
const next =
|
|
332
|
+
tool === "env"
|
|
333
|
+
? stripEnvCommand(args)
|
|
334
|
+
: tool === "timeout" || tool === "gtimeout"
|
|
335
|
+
? stripTimeoutCommand(args)
|
|
336
|
+
: tool === "nice"
|
|
337
|
+
? stripNiceCommand(args)
|
|
338
|
+
: tool === "arch"
|
|
339
|
+
? stripArchCommand(args)
|
|
340
|
+
: tool === "command"
|
|
341
|
+
? args
|
|
342
|
+
: null;
|
|
343
|
+
if (!next || next === current) {
|
|
344
|
+
return current;
|
|
345
|
+
}
|
|
346
|
+
current = next.trim();
|
|
347
|
+
}
|
|
348
|
+
return current;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function firstShellCommandSegment(command: string): string {
|
|
352
|
+
const chain = findShellChain(command);
|
|
353
|
+
return chain ? command.slice(0, chain.operatorStart).trim() : command;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function lastPathComponents(args: string, fallback: string): string {
|
|
357
|
+
const tokens = tokenizeCommandArgs(args);
|
|
358
|
+
for (let index = tokens.length - 1; index >= 0; index -= 1) {
|
|
359
|
+
const token = tokens[index];
|
|
360
|
+
if (!token) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const cleaned = token.replace(/^['"]|['"]$/g, "");
|
|
364
|
+
if (!cleaned || cleaned.startsWith("-")) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
return compactPath(cleaned);
|
|
368
|
+
}
|
|
369
|
+
return fallback;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function findTarget(args: string, fallback: string): string {
|
|
373
|
+
const tokens = tokenizeCommandArgs(args);
|
|
374
|
+
let skipNext = false;
|
|
375
|
+
for (const token of tokens) {
|
|
376
|
+
if (skipNext) {
|
|
377
|
+
skipNext = false;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (token.startsWith("-")) {
|
|
381
|
+
if (
|
|
382
|
+
token === "-maxdepth" ||
|
|
383
|
+
token === "-mindepth" ||
|
|
384
|
+
token === "-name" ||
|
|
385
|
+
token === "-type" ||
|
|
386
|
+
token === "-path"
|
|
387
|
+
) {
|
|
388
|
+
skipNext = true;
|
|
389
|
+
}
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
return compactPath(token);
|
|
393
|
+
}
|
|
394
|
+
return fallback;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function normalizeSearchPatternToken(token: string): string | null {
|
|
398
|
+
const trimmed = token.trim();
|
|
399
|
+
if (!trimmed || trimmed === "." || trimmed === "..") {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
if (!/[a-z0-9]/i.test(trimmed)) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
return trimmed.length > 30 ? `${trimmed.slice(0, 27)}...` : trimmed;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function normalizeSearchPathToken(token: string): string | null {
|
|
409
|
+
const trimmed = token.trim();
|
|
410
|
+
if (!trimmed) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
return compactPath(trimmed);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function looksLikeSearchPath(token: string): boolean {
|
|
417
|
+
return token.includes("/") || token.startsWith(".") || token.includes("\\");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function extractSearchPatternAndPath(args: string): {
|
|
421
|
+
pattern: string | null;
|
|
422
|
+
path: string | null;
|
|
423
|
+
} {
|
|
424
|
+
const tokens = tokenizeCommandArgs(args);
|
|
425
|
+
let pattern: string | null = null;
|
|
426
|
+
let path: string | null = null;
|
|
427
|
+
let skipNext = false;
|
|
428
|
+
|
|
429
|
+
for (const token of tokens) {
|
|
430
|
+
if (skipNext) {
|
|
431
|
+
skipNext = false;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (token.startsWith("-")) {
|
|
435
|
+
if (
|
|
436
|
+
token === "-t" ||
|
|
437
|
+
token === "-g" ||
|
|
438
|
+
token === "--type" ||
|
|
439
|
+
token === "--glob" ||
|
|
440
|
+
token === "--max-count"
|
|
441
|
+
) {
|
|
442
|
+
skipNext = true;
|
|
443
|
+
}
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (!pattern) {
|
|
447
|
+
const normalizedPattern = normalizeSearchPatternToken(token);
|
|
448
|
+
if (!normalizedPattern) {
|
|
449
|
+
const normalizedPath = normalizeSearchPathToken(token);
|
|
450
|
+
if (normalizedPath && (!path || path === "current directory")) {
|
|
451
|
+
path = normalizedPath;
|
|
452
|
+
}
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
pattern = normalizedPattern;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (!path || path === "current directory") {
|
|
459
|
+
path = normalizeSearchPathToken(token) ?? path;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (pattern && path === "current directory" && looksLikeSearchPath(pattern)) {
|
|
465
|
+
path = normalizeSearchPathToken(pattern);
|
|
466
|
+
pattern = null;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return { pattern, path };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function searchSummary(args: string): string {
|
|
473
|
+
const { pattern, path } = extractSearchPatternAndPath(args);
|
|
474
|
+
if (pattern && path) {
|
|
475
|
+
return `for ${pattern} in ${path}`;
|
|
476
|
+
}
|
|
477
|
+
if (pattern) {
|
|
478
|
+
return `for ${pattern}`;
|
|
479
|
+
}
|
|
480
|
+
if (path) {
|
|
481
|
+
return `in ${path}`;
|
|
482
|
+
}
|
|
483
|
+
return "files";
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function containsHeredoc(command: string): boolean {
|
|
487
|
+
return /(^|\s)<<-?\s*['"]?[A-Za-z0-9_]+/.test(command);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function hasInlineScriptFlag(args: string): boolean {
|
|
491
|
+
const tokens = tokenizeCommandArgs(args);
|
|
492
|
+
return tokens.some(
|
|
493
|
+
(token) => token === "-e" || token === "-c" || token.startsWith("-e="),
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function inlineScriptTarget(
|
|
498
|
+
tool: string,
|
|
499
|
+
command: string,
|
|
500
|
+
args: string,
|
|
501
|
+
): string | null {
|
|
502
|
+
const normalizedTool = tool === "python3" ? "python" : tool;
|
|
503
|
+
if (containsHeredoc(command) || hasInlineScriptFlag(args)) {
|
|
504
|
+
return `${normalizedTool} script`;
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function stripGitGlobalOptions(args: string): string {
|
|
510
|
+
const tokens = tokenizeCommandArgs(args);
|
|
511
|
+
let index = 0;
|
|
512
|
+
while (index < tokens.length) {
|
|
513
|
+
const token = tokens[index];
|
|
514
|
+
if (token === undefined) {
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
if (
|
|
518
|
+
token === "-C" ||
|
|
519
|
+
token === "-c" ||
|
|
520
|
+
token === "--git-dir" ||
|
|
521
|
+
token === "--work-tree"
|
|
522
|
+
) {
|
|
523
|
+
index += 2;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
if (
|
|
527
|
+
token.startsWith("-C") ||
|
|
528
|
+
token.startsWith("-c") ||
|
|
529
|
+
token.startsWith("--git-dir=") ||
|
|
530
|
+
token.startsWith("--work-tree=")
|
|
531
|
+
) {
|
|
532
|
+
index += 1;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (token.startsWith("--")) {
|
|
536
|
+
index += 1;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
return tokens.slice(index).join(" ");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function checkoutTarget(args: string): string {
|
|
545
|
+
const tokens = tokenizeCommandArgs(args);
|
|
546
|
+
const branch = tokens.at(-1)?.trim();
|
|
547
|
+
return branch ? branch : "branch";
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function humanizeGitCommand(
|
|
551
|
+
args: string,
|
|
552
|
+
isRunning: boolean,
|
|
553
|
+
): { verb: string; target: string } {
|
|
554
|
+
const normalizedArgs = stripGitGlobalOptions(args);
|
|
555
|
+
const firstToken = normalizedArgs.split(/\s+/, 1)[0];
|
|
556
|
+
const subcommand = firstToken ? firstToken.toLowerCase() : "";
|
|
557
|
+
switch (subcommand) {
|
|
558
|
+
case "status":
|
|
559
|
+
return {
|
|
560
|
+
verb: isRunning ? "Checking" : "Checked",
|
|
561
|
+
target: "git status",
|
|
562
|
+
};
|
|
563
|
+
case "diff":
|
|
564
|
+
return {
|
|
565
|
+
verb: isRunning ? "Comparing" : "Compared",
|
|
566
|
+
target: "changes",
|
|
567
|
+
};
|
|
568
|
+
case "show":
|
|
569
|
+
return {
|
|
570
|
+
verb: isRunning ? "Inspecting" : "Inspected",
|
|
571
|
+
target: "commit",
|
|
572
|
+
};
|
|
573
|
+
case "log":
|
|
574
|
+
return {
|
|
575
|
+
verb: isRunning ? "Reviewing" : "Reviewed",
|
|
576
|
+
target: "git history",
|
|
577
|
+
};
|
|
578
|
+
case "add":
|
|
579
|
+
return {
|
|
580
|
+
verb: isRunning ? "Staging" : "Staged",
|
|
581
|
+
target: "changes",
|
|
582
|
+
};
|
|
583
|
+
case "commit":
|
|
584
|
+
return {
|
|
585
|
+
verb: isRunning ? "Committing" : "Committed",
|
|
586
|
+
target: "changes",
|
|
587
|
+
};
|
|
588
|
+
case "push":
|
|
589
|
+
return {
|
|
590
|
+
verb: isRunning ? "Pushing" : "Pushed",
|
|
591
|
+
target: "to remote",
|
|
592
|
+
};
|
|
593
|
+
case "pull":
|
|
594
|
+
return {
|
|
595
|
+
verb: isRunning ? "Pulling" : "Pulled",
|
|
596
|
+
target: "from remote",
|
|
597
|
+
};
|
|
598
|
+
case "checkout":
|
|
599
|
+
case "switch":
|
|
600
|
+
return {
|
|
601
|
+
verb: isRunning ? "Switching to" : "Switched to",
|
|
602
|
+
target: checkoutTarget(args),
|
|
603
|
+
};
|
|
604
|
+
default:
|
|
605
|
+
return {
|
|
606
|
+
verb: isRunning ? "Running" : "Ran",
|
|
607
|
+
target: compactInlineCommand(`git ${normalizedArgs}`.trim()),
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export function deriveReadableCommandDisplay(
|
|
613
|
+
rawCommand: string,
|
|
614
|
+
isRunning = false,
|
|
615
|
+
): { verb: string; target: string } {
|
|
616
|
+
const command = stripCommandDisplayWrappers(
|
|
617
|
+
unwrapShellCommandIfPresent(rawCommand),
|
|
618
|
+
);
|
|
619
|
+
const primaryCommand = firstShellCommandSegment(command);
|
|
620
|
+
const [tool, args] = splitToolAndArgs(primaryCommand);
|
|
621
|
+
|
|
622
|
+
if (READ_FILE_COMMAND_TOOLS.has(tool)) {
|
|
623
|
+
return {
|
|
624
|
+
verb: isRunning ? "Reading" : "Read",
|
|
625
|
+
target: lastPathComponents(args, "file"),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
if (SEARCH_COMMAND_TOOLS.has(tool)) {
|
|
629
|
+
return {
|
|
630
|
+
verb: isRunning ? "Searching" : "Searched",
|
|
631
|
+
target: searchSummary(args),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
if (LIST_COMMAND_TOOLS.has(tool)) {
|
|
635
|
+
return {
|
|
636
|
+
verb: isRunning ? "Listing" : "Listed",
|
|
637
|
+
target: lastPathComponents(args, "directory"),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (FIND_COMMAND_TOOLS.has(tool)) {
|
|
641
|
+
return {
|
|
642
|
+
verb: isRunning ? "Finding" : "Found",
|
|
643
|
+
target: findTarget(args, "files"),
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
switch (tool) {
|
|
648
|
+
case "mkdir":
|
|
649
|
+
return {
|
|
650
|
+
verb: isRunning ? "Creating" : "Created",
|
|
651
|
+
target: lastPathComponents(args, "directory"),
|
|
652
|
+
};
|
|
653
|
+
case "rm":
|
|
654
|
+
return {
|
|
655
|
+
verb: isRunning ? "Removing" : "Removed",
|
|
656
|
+
target: lastPathComponents(args, "file"),
|
|
657
|
+
};
|
|
658
|
+
case "cp":
|
|
659
|
+
case "mv":
|
|
660
|
+
return {
|
|
661
|
+
verb: isRunning
|
|
662
|
+
? tool === "cp"
|
|
663
|
+
? "Copying"
|
|
664
|
+
: "Moving"
|
|
665
|
+
: tool === "cp"
|
|
666
|
+
? "Copied"
|
|
667
|
+
: "Moved",
|
|
668
|
+
target: lastPathComponents(args, "file"),
|
|
669
|
+
};
|
|
670
|
+
case "git":
|
|
671
|
+
return humanizeGitCommand(args, isRunning);
|
|
672
|
+
case "node":
|
|
673
|
+
case "bun":
|
|
674
|
+
case "deno":
|
|
675
|
+
case "python":
|
|
676
|
+
case "python3":
|
|
677
|
+
case "ruby":
|
|
678
|
+
case "perl":
|
|
679
|
+
return {
|
|
680
|
+
verb: isRunning ? "Running" : "Ran",
|
|
681
|
+
target:
|
|
682
|
+
inlineScriptTarget(tool, command, args) ??
|
|
683
|
+
compactInlineCommand(command),
|
|
684
|
+
};
|
|
685
|
+
case "osascript":
|
|
686
|
+
return {
|
|
687
|
+
verb: isRunning ? "Running" : "Ran",
|
|
688
|
+
target: "AppleScript",
|
|
689
|
+
};
|
|
690
|
+
default:
|
|
691
|
+
return {
|
|
692
|
+
verb: isRunning ? "Running" : "Ran",
|
|
693
|
+
target: compactInlineCommand(command),
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function resolveCommandVisualKind(
|
|
699
|
+
rawCommand: string,
|
|
700
|
+
): CommandVisualKind {
|
|
701
|
+
const command = stripCommandDisplayWrappers(
|
|
702
|
+
unwrapShellCommandIfPresent(rawCommand),
|
|
703
|
+
);
|
|
704
|
+
const [tool] = splitToolAndArgs(firstShellCommandSegment(command));
|
|
705
|
+
if (isInspectCommandTool(tool)) {
|
|
706
|
+
return "inspect";
|
|
707
|
+
}
|
|
708
|
+
if (tool === "git") {
|
|
709
|
+
return "git";
|
|
710
|
+
}
|
|
711
|
+
return "terminal";
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function humanizeMcpToken(value: string | undefined): string {
|
|
715
|
+
if (!value) {
|
|
716
|
+
return "";
|
|
717
|
+
}
|
|
718
|
+
const normalized = value
|
|
719
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
720
|
+
.replace(/[_-]+/g, " ")
|
|
721
|
+
.replace(/\s+/g, " ")
|
|
722
|
+
.trim();
|
|
723
|
+
if (!normalized) {
|
|
724
|
+
return "";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return normalized
|
|
728
|
+
.split(" ")
|
|
729
|
+
.map((token) => {
|
|
730
|
+
const lower = token.toLowerCase();
|
|
731
|
+
if (lower === "mcp") return "MCP";
|
|
732
|
+
if (token.toUpperCase() === token && token.length <= 5) return token;
|
|
733
|
+
return `${lower.charAt(0).toUpperCase()}${lower.slice(1)}`;
|
|
734
|
+
})
|
|
735
|
+
.join(" ");
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function humanizeMcpToolIdentifier(value: string): string | null {
|
|
739
|
+
const trimmed = value.trim();
|
|
740
|
+
if (!trimmed.startsWith("mcp__")) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const [, server, tool, ...rest] = trimmed.split("__");
|
|
745
|
+
const normalizedServer = humanizeMcpToken(server);
|
|
746
|
+
const normalizedTool = [tool, ...rest]
|
|
747
|
+
.map((part) => humanizeMcpToken(part))
|
|
748
|
+
.filter((part) => part.length > 0)
|
|
749
|
+
.join(" ");
|
|
750
|
+
|
|
751
|
+
if (!normalizedServer || !normalizedTool) {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
return `${normalizedServer}: ${normalizedTool}`;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function getFileVerb(type: ActivityStep["type"], active: boolean): string {
|
|
758
|
+
if (type === "edit" || type === "notebook") {
|
|
759
|
+
return active ? "Editing" : "Edited";
|
|
760
|
+
}
|
|
761
|
+
if (type === "write") {
|
|
762
|
+
return active ? "Creating" : "Created";
|
|
763
|
+
}
|
|
764
|
+
return active ? "Reading" : "Read";
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function isBackgroundShellLabel(label: string): boolean {
|
|
768
|
+
const normalized = label.toLowerCase();
|
|
769
|
+
return normalized.includes("background");
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function extractMcpIdentifier(step: ActivityStep): string | null {
|
|
773
|
+
const candidates = [step.detail, step.label];
|
|
774
|
+
for (const candidate of candidates) {
|
|
775
|
+
if (!candidate) {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const cleaned = candidate.replace(/\.{3,}$/, "").trim();
|
|
779
|
+
const match = /mcp__[^\s]+/.exec(cleaned);
|
|
780
|
+
if (match) {
|
|
781
|
+
const identifier = match[0].replace(/\.+$/, "");
|
|
782
|
+
return humanizeMcpToolIdentifier(identifier);
|
|
783
|
+
}
|
|
784
|
+
const humanized = humanizeMcpToolIdentifier(cleaned);
|
|
785
|
+
if (humanized) {
|
|
786
|
+
return humanized;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function isFileChangeTool(step: ActivityStep): boolean {
|
|
793
|
+
const label = step.label.toLowerCase();
|
|
794
|
+
return label.includes("file change") || step.detail === "file_change";
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function extractHostname(url: string): string {
|
|
798
|
+
try {
|
|
799
|
+
return new URL(url).hostname;
|
|
800
|
+
} catch {
|
|
801
|
+
const match = /^https?:\/\/([^/?#]+)/i.exec(url);
|
|
802
|
+
return match?.[1] ?? url;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function joinCommandText(verb: string, target: string): string {
|
|
807
|
+
if (!target) {
|
|
808
|
+
return verb;
|
|
809
|
+
}
|
|
810
|
+
return `${verb} ${target}`;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Past-tense phrase used when a step type is folded into a group summary line.
|
|
815
|
+
* Deliberately plural/vague — the exact call is one click away in the fold.
|
|
816
|
+
*/
|
|
817
|
+
const GROUP_PHRASES: Record<ActivityStep["type"], string> = {
|
|
818
|
+
read: "read files",
|
|
819
|
+
edit: "edited files",
|
|
820
|
+
write: "created files",
|
|
821
|
+
notebook: "edited notebooks",
|
|
822
|
+
bash: "ran commands",
|
|
823
|
+
search_files: "searched for files",
|
|
824
|
+
search_code: "searched the code",
|
|
825
|
+
web_fetch: "fetched pages",
|
|
826
|
+
web_search: "searched the web",
|
|
827
|
+
subtask: "ran agents",
|
|
828
|
+
todos: "updated the task list",
|
|
829
|
+
tool: "used tools",
|
|
830
|
+
notice: "posted notices",
|
|
831
|
+
hook: "ran hooks",
|
|
832
|
+
status: "reported status",
|
|
833
|
+
thinking: "thought it through",
|
|
834
|
+
reasoning: "thought it through",
|
|
835
|
+
response: "replied",
|
|
836
|
+
question: "asked a question",
|
|
837
|
+
steer: "took your steer",
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
function groupPhraseForStep(step: ActivityStep): string {
|
|
841
|
+
if (step.type !== "tool") {
|
|
842
|
+
return GROUP_PHRASES[step.type];
|
|
843
|
+
}
|
|
844
|
+
// Codex reports edits as a `file_change` tool call, not an edit step.
|
|
845
|
+
if (isFileChangeTool(step)) {
|
|
846
|
+
return GROUP_PHRASES.edit;
|
|
847
|
+
}
|
|
848
|
+
const mcpLabel = extractMcpIdentifier(step);
|
|
849
|
+
const server = mcpLabel?.split(":")[0]?.trim();
|
|
850
|
+
return server ? `used ${server}` : GROUP_PHRASES.tool;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* One-line summary for a folded run of steps — "Edited files, ran commands" —
|
|
855
|
+
* listing each distinct kind of work once, in the order it first happened.
|
|
856
|
+
*/
|
|
857
|
+
export function deriveActionGroupSummary(steps: ActivityStep[]): string {
|
|
858
|
+
const phrases: string[] = [];
|
|
859
|
+
for (const step of steps) {
|
|
860
|
+
const phrase = groupPhraseForStep(step);
|
|
861
|
+
if (phrase && !phrases.includes(phrase)) {
|
|
862
|
+
phrases.push(phrase);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const joined = phrases.join(", ");
|
|
866
|
+
if (!joined) {
|
|
867
|
+
return "Worked";
|
|
868
|
+
}
|
|
869
|
+
return `${joined.charAt(0).toUpperCase()}${joined.slice(1)}`;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
export function deriveStepRowPresentation(
|
|
873
|
+
step: ActivityStep,
|
|
874
|
+
isActive: boolean,
|
|
875
|
+
): StepRowPresentation {
|
|
876
|
+
switch (step.type) {
|
|
877
|
+
case "read":
|
|
878
|
+
case "edit":
|
|
879
|
+
case "write":
|
|
880
|
+
case "notebook": {
|
|
881
|
+
const filePath = step.path ?? step.detail ?? "";
|
|
882
|
+
const fileName = filePath ? basename(filePath) : step.label;
|
|
883
|
+
return {
|
|
884
|
+
text: getFileVerb(step.type, isActive),
|
|
885
|
+
fileChip: {
|
|
886
|
+
name: fileName,
|
|
887
|
+
path: step.path,
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
case "bash": {
|
|
892
|
+
if (!step.detail) {
|
|
893
|
+
return { text: step.label };
|
|
894
|
+
}
|
|
895
|
+
if (isBackgroundShellLabel(step.label)) {
|
|
896
|
+
return {
|
|
897
|
+
text: `${step.label.replace(/\.\.\.$/, "").trim()} ${compactInlineCommand(step.detail)}`.trim(),
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
const display = deriveReadableCommandDisplay(step.detail, isActive);
|
|
901
|
+
return { text: joinCommandText(display.verb, display.target) };
|
|
902
|
+
}
|
|
903
|
+
case "search_code": {
|
|
904
|
+
const pattern = step.detail ? truncatePattern(step.detail) : "pattern";
|
|
905
|
+
return {
|
|
906
|
+
text: isActive
|
|
907
|
+
? `Searching for "${pattern}"`
|
|
908
|
+
: `Searched for "${pattern}"`,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
case "search_files": {
|
|
912
|
+
const pattern = step.detail ? truncatePattern(step.detail) : "pattern";
|
|
913
|
+
return {
|
|
914
|
+
text: isActive
|
|
915
|
+
? `Finding files matching "${pattern}"`
|
|
916
|
+
: `Found files matching "${pattern}"`,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
case "web_fetch": {
|
|
920
|
+
const url = step.detail ?? "";
|
|
921
|
+
return {
|
|
922
|
+
text: isActive
|
|
923
|
+
? `Fetching ${url ? extractHostname(url) : "URL"}`
|
|
924
|
+
: `Fetched ${url ? extractHostname(url) : "URL"}`,
|
|
925
|
+
title: url || undefined,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
case "web_search": {
|
|
929
|
+
const query = step.detail ? truncatePattern(step.detail) : "query";
|
|
930
|
+
return {
|
|
931
|
+
text: isActive
|
|
932
|
+
? `Searching the web for "${query}"`
|
|
933
|
+
: `Searched the web for "${query}"`,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
case "subtask": {
|
|
937
|
+
const description = step.detail ?? step.label;
|
|
938
|
+
return {
|
|
939
|
+
text: isActive
|
|
940
|
+
? `Running agent: ${description}`
|
|
941
|
+
: `Ran agent: ${description}`,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
case "todos": {
|
|
945
|
+
return {
|
|
946
|
+
text: isActive ? "Updating tasks" : "Task list",
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
case "tool": {
|
|
950
|
+
if (isFileChangeTool(step)) {
|
|
951
|
+
return {
|
|
952
|
+
text: isActive ? "Editing files" : "Edited files",
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const mcpLabel = extractMcpIdentifier(step);
|
|
956
|
+
if (mcpLabel) {
|
|
957
|
+
return {
|
|
958
|
+
text: isActive ? `Using ${mcpLabel}` : `Used ${mcpLabel}`,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
return { text: step.label };
|
|
962
|
+
}
|
|
963
|
+
case "question": {
|
|
964
|
+
if (isActive) {
|
|
965
|
+
return { text: "Waiting for your answer" };
|
|
966
|
+
}
|
|
967
|
+
return { text: step.detail ? `Asked: ${step.detail}` : step.label };
|
|
968
|
+
}
|
|
969
|
+
case "steer": {
|
|
970
|
+
return {
|
|
971
|
+
text: step.detail ? `Steered: ${step.detail}` : step.label,
|
|
972
|
+
title: step.detail,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
case "thinking":
|
|
976
|
+
case "reasoning":
|
|
977
|
+
case "response":
|
|
978
|
+
default:
|
|
979
|
+
return { text: step.label };
|
|
980
|
+
}
|
|
981
|
+
}
|