@tt-a1i/openpi 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +102 -40
  2. package/SETUP.md +22 -6
  3. package/assets/openpi-launch-card-v1.webp +0 -0
  4. package/bin/openpi.js +145 -0
  5. package/extensions/background-terminals/index.ts +30 -2
  6. package/extensions/background-terminals/src/domain.ts +2 -0
  7. package/extensions/background-terminals/src/manager.ts +486 -106
  8. package/extensions/background-terminals/src/output.ts +33 -0
  9. package/extensions/background-terminals/src/prompt.ts +13 -5
  10. package/extensions/background-terminals/src/result-delivery.ts +4 -1
  11. package/extensions/clear-context/index.ts +83 -0
  12. package/extensions/context-pivot/index.ts +16 -6
  13. package/extensions/cron/schedule.ts +7 -1
  14. package/extensions/file-mutation-display/render.ts +17 -257
  15. package/extensions/file-search/src/binaries.ts +57 -41
  16. package/extensions/git-read/index.ts +1 -3
  17. package/extensions/model-info/index.ts +21 -33
  18. package/extensions/model-info/session-metrics.ts +96 -0
  19. package/extensions/plan-mode/bash-policy.ts +54 -9
  20. package/extensions/plan-mode/index.ts +7 -2
  21. package/extensions/post-edit/index.ts +16 -6
  22. package/extensions/sessions/git-stats.ts +258 -72
  23. package/extensions/sessions/index.ts +153 -86
  24. package/extensions/sessions/preview-cache.ts +104 -0
  25. package/extensions/sessions/preview-loader.ts +856 -0
  26. package/extensions/sessions/sessions.ts +43 -4
  27. package/extensions/setup/index.ts +123 -127
  28. package/extensions/shared/activity-status.ts +30 -0
  29. package/extensions/shared/agent-session-page.ts +319 -0
  30. package/extensions/shared/agent-tool-renderer.ts +218 -0
  31. package/extensions/shared/agent-transcript.ts +524 -0
  32. package/extensions/shared/capability-intent.ts +1 -1
  33. package/extensions/shared/child-session.ts +437 -21
  34. package/extensions/shared/result-delivery.ts +34 -0
  35. package/extensions/shared/setup-config.ts +73 -33
  36. package/extensions/shared/setup-episode-state.ts +1 -1
  37. package/extensions/shared/terminal-text.ts +110 -23
  38. package/extensions/shared/text-projection.ts +72 -15
  39. package/extensions/shared/tool-activity.ts +382 -0
  40. package/extensions/shared/tool-surface.ts +29 -2
  41. package/extensions/shared/transcript-viewport.ts +46 -0
  42. package/extensions/shared/web-observer-registry.ts +390 -0
  43. package/extensions/shared/worktree.ts +11 -0
  44. package/extensions/subagents/index.ts +270 -59
  45. package/extensions/subagents/navigation.ts +34 -5
  46. package/extensions/subagents/src/backend.ts +12 -1
  47. package/extensions/subagents/src/backends/pi.ts +375 -66
  48. package/extensions/subagents/src/domain.ts +5 -0
  49. package/extensions/subagents/src/manager.ts +34 -2
  50. package/extensions/subagents/src/prompt.ts +32 -4
  51. package/extensions/subagents/src/result-artifact.ts +4 -0
  52. package/extensions/subagents/src/result-delivery.ts +7 -1
  53. package/extensions/subagents/src/runtime.ts +15 -1
  54. package/extensions/subagents/src/ui/takeover.ts +73 -257
  55. package/extensions/subagents/src/ui/transcript.ts +38 -535
  56. package/extensions/subagents/src/ui/wait-result.ts +103 -15
  57. package/extensions/suggestions/src/ui.ts +10 -4
  58. package/extensions/tasks/index.ts +0 -3
  59. package/extensions/ui-customization/footer.ts +0 -40
  60. package/extensions/ui-customization/index.ts +0 -4
  61. package/extensions/user-input-fold/index.ts +1 -1
  62. package/extensions/web/index.ts +234 -0
  63. package/extensions/workflows/artifacts.ts +137 -47
  64. package/extensions/workflows/completion-projection.ts +457 -0
  65. package/extensions/workflows/coordinator.ts +8 -10
  66. package/extensions/workflows/dashboard.ts +167 -228
  67. package/extensions/workflows/handoff.ts +70 -16
  68. package/extensions/workflows/index.ts +488 -198
  69. package/extensions/workflows/journal.ts +148 -13
  70. package/extensions/workflows/model.ts +74 -4
  71. package/extensions/workflows/navigation.ts +32 -8
  72. package/extensions/workflows/progress-projection.ts +306 -0
  73. package/extensions/workflows/prompt.ts +66 -6
  74. package/extensions/workflows/replay-safety.ts +42 -21
  75. package/extensions/workflows/result-delivery.ts +128 -64
  76. package/extensions/workflows/retention.ts +593 -0
  77. package/extensions/workflows/runner.ts +388 -279
  78. package/extensions/workflows/sandbox-child.cjs +25 -3
  79. package/extensions/workflows/sandbox.ts +62 -8
  80. package/extensions/workflows/serialization.ts +325 -17
  81. package/extensions/workflows/tool-renderer.ts +22 -0
  82. package/extensions/workflows/transcript.ts +149 -0
  83. package/extensions/workspace-cleanup-guard/index.ts +54 -0
  84. package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
  85. package/package.json +28 -8
  86. package/skills/subagents/REFERENCE.md +189 -0
  87. package/skills/subagents/SKILL.md +1 -1
  88. package/skills/workflows/REFERENCE.md +4 -2
  89. package/web/adapter/pi-adapter.ts +661 -0
  90. package/web/host/browser-launcher.ts +20 -0
  91. package/web/host/static-assets.ts +4 -0
  92. package/web/host/terminal-status.ts +38 -0
  93. package/web/host/web-host.ts +789 -0
  94. package/web/http-dispatcher.ts +125 -0
  95. package/web/protocol/types.ts +462 -0
  96. package/web/runtime/pi-runtime.ts +991 -0
  97. package/web/runtime/types.ts +71 -0
  98. package/web/runtime/web-host-lease.ts +497 -0
  99. package/web/trace.ts +18 -0
  100. package/web/ui/app.js +1398 -0
  101. package/web/ui/index.html +139 -0
  102. package/web/ui/styles.css +598 -0
  103. package/web/vite.config.mjs +34 -0
  104. package/extensions/execution-convergence/active-evidence.ts +0 -129
  105. package/extensions/execution-convergence/index.ts +0 -442
  106. package/extensions/execution-convergence/workspace-provenance.ts +0 -338
  107. package/extensions/setup/intercom-fs-helper.cjs +0 -130
  108. package/extensions/setup/intercom.ts +0 -603
  109. package/extensions/subagents/src/backends/stub.ts +0 -303
@@ -0,0 +1,524 @@
1
+ /** Shared Pi-native operator-facing agent transcript rendering. */
2
+
3
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
4
+ import {
5
+ AssistantMessageComponent,
6
+ getMarkdownTheme,
7
+ UserMessageComponent,
8
+ type Theme,
9
+ } from "@earendil-works/pi-coding-agent";
10
+ import { TruncatedText } from "@earendil-works/pi-tui";
11
+ import type { AgentToolRenderer } from "./agent-tool-renderer.ts";
12
+ import { sanitizeTerminalText } from "./terminal-text.ts";
13
+ import {
14
+ parseToolArgsPreview,
15
+ renderPaddedToolActivityLine,
16
+ type ToolActivityStatus,
17
+ } from "./tool-activity.ts";
18
+
19
+ export type AgentTranscriptPart =
20
+ | { readonly type: "text"; readonly text: string }
21
+ | {
22
+ readonly type: "thinking";
23
+ readonly text: string;
24
+ readonly redacted?: boolean;
25
+ }
26
+ | {
27
+ readonly type: "toolCall";
28
+ readonly toolId: string;
29
+ readonly name: string;
30
+ readonly argsPreview?: string;
31
+ };
32
+
33
+ export type AgentTranscriptItem =
34
+ | { readonly kind: "user"; readonly text: string }
35
+ | {
36
+ readonly kind: "assistant";
37
+ readonly parts: ReadonlyArray<AgentTranscriptPart>;
38
+ }
39
+ | {
40
+ readonly kind: "toolResult";
41
+ readonly toolId: string;
42
+ readonly name: string;
43
+ readonly isError: boolean;
44
+ readonly outputPreview?: string;
45
+ };
46
+
47
+ export interface AgentTranscriptDocument {
48
+ readonly items: ReadonlyArray<AgentTranscriptItem>;
49
+ /** Pairing index built by the document producer; render falls back to building it. */
50
+ readonly pairing?: PairingIndex;
51
+ readonly cwd?: string;
52
+ /** Ephemeral native renderer for the live child; never persisted. */
53
+ readonly toolRenderer?: AgentToolRenderer;
54
+ readonly liveAssistant?: { readonly text: string; readonly thinking: string };
55
+ readonly liveTools?: ReadonlyArray<{
56
+ readonly toolId: string;
57
+ readonly name: string;
58
+ readonly argsPreview?: string;
59
+ readonly outputPreview?: string;
60
+ readonly done?: boolean;
61
+ readonly isError?: boolean;
62
+ }>;
63
+ readonly queued?: ReadonlyArray<{
64
+ readonly text: string;
65
+ readonly kind: "steer" | "follow-up";
66
+ }>;
67
+ }
68
+
69
+ const MAX_CACHED_WIDTHS_PER_ITEM = 2;
70
+
71
+ /**
72
+ * Strip raw ANSI codes, expand tabs, and drop control chars. Terminal-expanded
73
+ * tabs (and stray escapes) make lines wider than the width we declare to the
74
+ * TUI, which desyncs the renderer and smears the overlay.
75
+ */
76
+ export function sanitizeText(text: string): string {
77
+ return sanitizeTerminalText(text);
78
+ }
79
+
80
+ const emptyUsage: AssistantMessage["usage"] = {
81
+ input: 0,
82
+ output: 0,
83
+ cacheRead: 0,
84
+ cacheWrite: 0,
85
+ totalTokens: 0,
86
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
87
+ };
88
+
89
+ function assistantMessage(
90
+ parts: ReadonlyArray<AgentTranscriptPart>,
91
+ ): AssistantMessage {
92
+ return {
93
+ role: "assistant",
94
+ content: parts.map((part) => {
95
+ if (part.type === "text")
96
+ return { type: "text", text: sanitizeText(part.text) };
97
+ if (part.type === "thinking") {
98
+ return {
99
+ type: "thinking",
100
+ thinking: part.redacted
101
+ ? "[redacted reasoning]"
102
+ : sanitizeText(part.text),
103
+ ...(part.redacted ? { redacted: true } : {}),
104
+ };
105
+ }
106
+ const { args } = parseToolArgsPreview(part.argsPreview);
107
+ return {
108
+ type: "toolCall",
109
+ id: part.toolId,
110
+ name: part.name,
111
+ arguments:
112
+ args !== null && typeof args === "object" && !Array.isArray(args)
113
+ ? args
114
+ : {},
115
+ };
116
+ }),
117
+ api: "openai-responses",
118
+ provider: "openai",
119
+ model: "child-transcript",
120
+ usage: emptyUsage,
121
+ stopReason: parts.some((part) => part.type === "toolCall")
122
+ ? "toolUse"
123
+ : "stop",
124
+ timestamp: 0,
125
+ };
126
+ }
127
+
128
+ function renderUserText(text: string, width: number) {
129
+ const clean = sanitizeText(text).trim();
130
+ if (!clean) return [];
131
+ return new UserMessageComponent(clean, getMarkdownTheme()).render(width);
132
+ }
133
+
134
+ function renderAssistantParts(
135
+ parts: ReadonlyArray<AgentTranscriptPart>,
136
+ width: number,
137
+ streaming = false,
138
+ ) {
139
+ const component = new AssistantMessageComponent(
140
+ assistantMessage(parts),
141
+ false,
142
+ getMarkdownTheme(),
143
+ );
144
+ if (streaming) component.updateContent(assistantMessage(parts), true);
145
+ return component.render(width);
146
+ }
147
+
148
+ export type ToolPhase = "live" | "ok" | "error" | "pending";
149
+
150
+ function activityStatus(phase: ToolPhase): ToolActivityStatus {
151
+ if (phase === "error") return "error";
152
+ if (phase === "ok") return "success";
153
+ return "pending";
154
+ }
155
+
156
+ function renderToolBlock(
157
+ theme: Theme,
158
+ phase: ToolPhase,
159
+ toolId: string,
160
+ name: string,
161
+ argsPreview: string | undefined,
162
+ outputPreview: string | undefined,
163
+ width: number,
164
+ now: number,
165
+ cwd?: string,
166
+ toolRenderer?: AgentToolRenderer,
167
+ expanded = false,
168
+ ) {
169
+ const native = toolRenderer?.renderTool(
170
+ { toolId, name, cwd, expanded },
171
+ width,
172
+ );
173
+ if (native) return native;
174
+ const { args, fallback } = parseToolArgsPreview(argsPreview);
175
+ return [
176
+ "",
177
+ renderPaddedToolActivityLine(
178
+ {
179
+ name,
180
+ args,
181
+ argsFallback: fallback,
182
+ output: outputPreview,
183
+ status: activityStatus(phase),
184
+ cwd,
185
+ },
186
+ theme,
187
+ width,
188
+ now,
189
+ ),
190
+ ];
191
+ }
192
+
193
+ function renderAssistantItem(
194
+ theme: Theme,
195
+ item: Extract<AgentTranscriptItem, { kind: "assistant" }>,
196
+ width: number,
197
+ tools: ReadonlyMap<string, ToolRenderState>,
198
+ now: number,
199
+ cwd?: string,
200
+ toolRenderer?: AgentToolRenderer,
201
+ expanded = false,
202
+ ) {
203
+ const out = renderAssistantParts(item.parts, width);
204
+ for (const part of item.parts) {
205
+ if (part.type === "toolCall") {
206
+ const state = tools.get(part.toolId) ?? { phase: "pending" };
207
+ // A live tool is rendered by the live block, which owns the spinner and
208
+ // the streaming output; rendering the call here too would show the same
209
+ // command twice and make the block reflow when the tool settles.
210
+ if (state.phase === "live") continue;
211
+ out.push(
212
+ ...renderToolBlock(
213
+ theme,
214
+ state.phase,
215
+ part.toolId,
216
+ part.name,
217
+ part.argsPreview,
218
+ state.result?.outputPreview,
219
+ width,
220
+ now,
221
+ cwd,
222
+ toolRenderer,
223
+ expanded,
224
+ ),
225
+ );
226
+ }
227
+ }
228
+ return out;
229
+ }
230
+
231
+ function renderToolResultItem(
232
+ theme: Theme,
233
+ item: Extract<AgentTranscriptItem, { kind: "toolResult" }>,
234
+ width: number,
235
+ paired: boolean,
236
+ now: number,
237
+ cwd?: string,
238
+ toolRenderer?: AgentToolRenderer,
239
+ expanded = false,
240
+ ) {
241
+ if (paired) return [];
242
+ return renderToolBlock(
243
+ theme,
244
+ item.isError ? "error" : "ok",
245
+ item.toolId,
246
+ item.name,
247
+ undefined,
248
+ item.outputPreview,
249
+ width,
250
+ now,
251
+ cwd,
252
+ toolRenderer,
253
+ expanded,
254
+ );
255
+ }
256
+
257
+ /** Tool-call/tool-result pairing lookups, precomputed in one forward pass. */
258
+ export interface PairingIndex {
259
+ /** Earliest call index per tool id; a result only needs "is any call earlier?". */
260
+ readonly firstCallAt: ReadonlyMap<string, number>;
261
+ /** Ascending result indices per tool id; calls pair with the first result AFTER them. */
262
+ readonly resultsById: ReadonlyMap<string, ReadonlyArray<number>>;
263
+ }
264
+
265
+ export function buildPairingIndex(
266
+ transcript: ReadonlyArray<AgentTranscriptItem>,
267
+ ): PairingIndex {
268
+ const firstCallAt = new Map<string, number>();
269
+ const resultsById = new Map<string, number[]>();
270
+ for (let index = 0; index < transcript.length; index++) {
271
+ const item = transcript[index]!;
272
+ if (item.kind === "assistant") {
273
+ for (const part of item.parts) {
274
+ if (part.type !== "toolCall") continue;
275
+ if (!firstCallAt.has(part.toolId)) firstCallAt.set(part.toolId, index);
276
+ }
277
+ continue;
278
+ }
279
+ if (item.kind !== "toolResult") continue;
280
+ const seen = resultsById.get(item.toolId);
281
+ if (seen) seen.push(index);
282
+ else resultsById.set(item.toolId, [index]);
283
+ }
284
+ return { firstCallAt, resultsById };
285
+ }
286
+
287
+ /** Whether any `toolCall` for this id appears before `resultIndex`. */
288
+ function hasEarlierToolCall(
289
+ pairing: PairingIndex,
290
+ resultIndex: number,
291
+ toolId: string,
292
+ ) {
293
+ const firstCall = pairing.firstCallAt.get(toolId);
294
+ return firstCall !== undefined && firstCall < resultIndex;
295
+ }
296
+
297
+ function renderTranscriptItem(
298
+ theme: Theme,
299
+ item: AgentTranscriptItem,
300
+ width: number,
301
+ context: ItemContext,
302
+ now: number,
303
+ cwd?: string,
304
+ toolRenderer?: AgentToolRenderer,
305
+ expanded = false,
306
+ ) {
307
+ if (item.kind === "user") return renderUserText(item.text, width);
308
+ if (item.kind === "assistant") {
309
+ return renderAssistantItem(
310
+ theme,
311
+ item,
312
+ width,
313
+ context.tools,
314
+ now,
315
+ cwd,
316
+ toolRenderer,
317
+ expanded,
318
+ );
319
+ }
320
+ return renderToolResultItem(
321
+ theme,
322
+ item,
323
+ width,
324
+ context.paired,
325
+ now,
326
+ cwd,
327
+ toolRenderer,
328
+ expanded,
329
+ );
330
+ }
331
+
332
+ function itemHasTool(item: AgentTranscriptItem) {
333
+ return (
334
+ item.kind === "toolResult" ||
335
+ (item.kind === "assistant" &&
336
+ item.parts.some((part) => part.type === "toolCall"))
337
+ );
338
+ }
339
+
340
+ interface ItemContext {
341
+ readonly tools: ReadonlyMap<string, ToolRenderState>;
342
+ readonly paired: boolean;
343
+ /** Cache discriminator: identity plus width is not enough on its own. */
344
+ readonly token: string;
345
+ }
346
+
347
+ interface ToolRenderState {
348
+ readonly phase: ToolPhase;
349
+ readonly result?: Extract<AgentTranscriptItem, { kind: "toolResult" }>;
350
+ }
351
+
352
+ /**
353
+ * An item's rendering depends on its neighbours (does a call have its result
354
+ * yet?) and on live state (is the call still running?), so the cache key has to
355
+ * carry that context or a stale glyph would outlive the phase it described.
356
+ */
357
+ function itemContext(
358
+ transcript: ReadonlyArray<AgentTranscriptItem>,
359
+ index: number,
360
+ liveIds: ReadonlySet<string>,
361
+ pairing: PairingIndex,
362
+ ): ItemContext {
363
+ const item = transcript[index]!;
364
+ if (item.kind === "user")
365
+ return { tools: new Map(), paired: false, token: "" };
366
+ if (item.kind === "toolResult") {
367
+ const paired = hasEarlierToolCall(pairing, index, item.toolId);
368
+ return { tools: new Map(), paired, token: paired ? "p" : "o" };
369
+ }
370
+
371
+ const tools = new Map<string, ToolRenderState>();
372
+ for (const part of item.parts) {
373
+ if (part.type !== "toolCall") continue;
374
+ if (liveIds.has(part.toolId)) {
375
+ tools.set(part.toolId, { phase: "live" });
376
+ continue;
377
+ }
378
+ const result = findResult(transcript, pairing, index, part.toolId);
379
+ tools.set(
380
+ part.toolId,
381
+ result
382
+ ? { phase: result.isError ? "error" : "ok", result }
383
+ : { phase: "pending" },
384
+ );
385
+ }
386
+ return {
387
+ tools,
388
+ paired: false,
389
+ token: [...tools].map(([id, state]) => `${id}:${state.phase}`).join(","),
390
+ };
391
+ }
392
+
393
+ /** The result for a call, if it has already landed later in the transcript. */
394
+ function findResult(
395
+ transcript: ReadonlyArray<AgentTranscriptItem>,
396
+ pairing: PairingIndex,
397
+ callIndex: number,
398
+ toolId: string,
399
+ ) {
400
+ const indices = pairing.resultsById.get(toolId);
401
+ if (!indices) return undefined;
402
+ // Ascending by construction, so the first entry past the call is the match.
403
+ for (const index of indices) {
404
+ if (index <= callIndex) continue;
405
+ const candidate = transcript[index];
406
+ return candidate?.kind === "toolResult" ? candidate : undefined;
407
+ }
408
+ return undefined;
409
+ }
410
+
411
+ /**
412
+ * Caches finalized transcript items by identity and width. Live state remains
413
+ * uncached because it changes on every stream tick; callers clear this cache
414
+ * from their component's invalidate() when Pi changes theme.
415
+ */
416
+ export class AgentTranscriptRenderer {
417
+ private itemCache = new WeakMap<AgentTranscriptItem, Map<string, string[]>>();
418
+ private toolRenderers = new Set<AgentToolRenderer>();
419
+
420
+ render(
421
+ document: AgentTranscriptDocument,
422
+ width: number,
423
+ theme: Theme,
424
+ options?: { readonly now?: number; readonly expanded?: boolean },
425
+ ) {
426
+ const out: string[] = [];
427
+ const now = options?.now ?? Date.now();
428
+ const expanded = options?.expanded === true;
429
+ const liveTools = document.liveTools ?? [];
430
+ if (document.toolRenderer) this.toolRenderers.add(document.toolRenderer);
431
+ const liveIds = new Set(liveTools.map((tool) => tool.toolId));
432
+ const pairing = document.pairing ?? buildPairingIndex(document.items);
433
+
434
+ for (let index = 0; index < document.items.length; index++) {
435
+ const item = document.items[index];
436
+ const context = itemContext(document.items, index, liveIds, pairing);
437
+ const key = `${width}|${context.token}`;
438
+ const cacheable = !document.toolRenderer || !itemHasTool(item);
439
+ const cached = cacheable ? this.itemCache.get(item)?.get(key) : undefined;
440
+ const lines =
441
+ cached ??
442
+ renderTranscriptItem(
443
+ theme,
444
+ item,
445
+ width,
446
+ context,
447
+ now,
448
+ document.cwd,
449
+ document.toolRenderer,
450
+ expanded,
451
+ );
452
+ if (!cached && cacheable) {
453
+ const widths = this.itemCache.get(item) ?? new Map<string, string[]>();
454
+ if (widths.size >= MAX_CACHED_WIDTHS_PER_ITEM) {
455
+ const oldestWidth = widths.keys().next().value;
456
+ if (oldestWidth !== undefined) widths.delete(oldestWidth);
457
+ }
458
+ widths.set(key, lines);
459
+ this.itemCache.set(item, widths);
460
+ }
461
+ if (lines.length > 0) {
462
+ out.push(...lines);
463
+ }
464
+ }
465
+ while (out.length > 0 && out[out.length - 1] === "") out.pop();
466
+
467
+ // Live streaming assistant buffers (cleared when the finalized message lands).
468
+ if (document.liveAssistant) {
469
+ const { thinking, text } = document.liveAssistant;
470
+ const parts: AgentTranscriptPart[] = [];
471
+ if (thinking.trim()) parts.push({ type: "thinking", text: thinking });
472
+ if (text.trim()) parts.push({ type: "text", text });
473
+ out.push(...renderAssistantParts(parts, width, true));
474
+ }
475
+
476
+ // Live tool executions. The manager drops a live entry when its ToolEnd
477
+ // lands, and the transcript's call line then takes over with the settled
478
+ // glyph in the same column, so the block never reflows.
479
+ for (const tool of liveTools) {
480
+ const phase: ToolPhase = tool.done
481
+ ? tool.isError
482
+ ? "error"
483
+ : "ok"
484
+ : "live";
485
+ out.push(
486
+ ...renderToolBlock(
487
+ theme,
488
+ phase,
489
+ tool.toolId,
490
+ tool.name,
491
+ tool.argsPreview,
492
+ tool.outputPreview,
493
+ width,
494
+ now,
495
+ document.cwd,
496
+ document.toolRenderer,
497
+ expanded,
498
+ ),
499
+ );
500
+ }
501
+
502
+ // Match Pi's pending-message projection. The dequeue hint is intentionally
503
+ // omitted because the child page does not expose Pi's queue editor.
504
+ if ((document.queued?.length ?? 0) > 0) out.push("");
505
+ for (const message of document.queued ?? []) {
506
+ const label = message.kind === "steer" ? "Steering" : "Follow-up";
507
+ out.push(
508
+ ...new TruncatedText(
509
+ theme.fg("dim", `${label}: ${sanitizeText(message.text)}`),
510
+ 1,
511
+ 0,
512
+ ).render(width),
513
+ );
514
+ }
515
+
516
+ return out;
517
+ }
518
+
519
+ invalidate() {
520
+ this.itemCache = new WeakMap();
521
+ for (const renderer of this.toolRenderers) renderer.invalidate?.();
522
+ this.toolRenderers.clear();
523
+ }
524
+ }
@@ -7,7 +7,7 @@ const CAPABILITY_INTENT = {
7
7
  search:
8
8
  /\b(?:use|run)\s+(?:fd|rg)\b|\buse\s+(?:structured\s+)?(?:(?:file|code|content)\s+)?search\b|\b(?:structured|fast)\s+(?:file|code|content)\s+search\b|(?:使用|用|运行).{0,8}(?:fd|rg|git\s+(?:show|diff|log))|结构化(?:文件|代码|内容)搜索/iu,
9
9
  delegate:
10
- /\bsubagents?\b|(?:^|[.!?]\s+)(?:please\s+)?(?:delegate|parallelize)\s+(?:this|the)\s+(?:task|work)\b|\b(?:can|could|would)\s+you\s+(?:please\s+)?(?:delegate|parallelize)\s+(?:this|the)\s+(?:task|work)\b|\bparallel\s+agents?\b|(?:使用|用|启动|调用|来|开).{0,8}子代理|(?:多个?|多路)子代理|并行.{0,8}(?:代理|agent)|委派.{0,6}(?:任务|给|出去)/iu,
10
+ /\bsubagents?\b|(?:^|[.!?]\s+)(?:please\s+)?(?:delegate|parallelize)\s+(?:this|the)\s+(?:task|work)\b|\b(?:can|could|would)\s+you\s+(?:please\s+)?(?:delegate|parallelize)\s+(?:this|the)\s+(?:task|work)\b|\bparallel\s+agents?\b|^\s*子代理\s*[,,::]?\s*(?:(?:来|去|请|帮(?:我|忙)?|负责|给我)\s*)?(?:(?:先|全面|系统(?:性地)?|深入|快速|仔细)\s*)?(?:了解|查看|检查|审查|分析|研究|调研|梳理|探索|实现|修复|处理|执行|完成|阅读|总结)|(?:使用|用|启动|调用|来|开).{0,8}子代理|(?:多个?|多路)子代理|并行.{0,8}(?:代理|agent)|委派.{0,6}(?:任务|给|出去)/iu,
11
11
  workflow: /\bworkflows?\b|(?:使用|用|运行|创建|构建).{0,8}工作流/iu,
12
12
  background:
13
13
  /\b(?:run|start|keep)\b.{0,40}\b(?:in the background|background\s+(?:process|terminal|job))\b|后台.{0,8}(?:运行|启动|进程|终端|任务)/iu,