@tt-a1i/openpi 0.3.1 → 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 (129) hide show
  1. package/README.md +184 -59
  2. package/SETUP.md +23 -7
  3. package/assets/openpi-launch-card-v1.webp +0 -0
  4. package/bin/openpi.js +145 -0
  5. package/extensions/ask-user/index.ts +30 -14
  6. package/extensions/background-terminals/index.ts +30 -2
  7. package/extensions/background-terminals/src/domain.ts +2 -0
  8. package/extensions/background-terminals/src/manager.ts +486 -106
  9. package/extensions/background-terminals/src/output.ts +33 -0
  10. package/extensions/background-terminals/src/prompt.ts +14 -6
  11. package/extensions/background-terminals/src/result-delivery.ts +4 -1
  12. package/extensions/background-terminals/src/ui/ps.ts +132 -129
  13. package/extensions/capabilities/index.ts +30 -42
  14. package/extensions/capabilities/src/ui.ts +93 -0
  15. package/extensions/clear-context/index.ts +83 -0
  16. package/extensions/context-pivot/index.ts +16 -6
  17. package/extensions/cron/schedule.ts +7 -1
  18. package/extensions/file-mutation-display/index.ts +34 -76
  19. package/extensions/file-mutation-display/render.ts +146 -87
  20. package/extensions/file-search/index.ts +8 -7
  21. package/extensions/file-search/src/binaries.ts +75 -59
  22. package/extensions/git-info/src/changed-files-view.ts +47 -14
  23. package/extensions/git-read/index.ts +328 -0
  24. package/extensions/git-read/src/args.ts +171 -0
  25. package/extensions/git-read/src/process.ts +81 -0
  26. package/extensions/git-read/src/prompt.ts +56 -0
  27. package/extensions/model-info/index.ts +21 -33
  28. package/extensions/model-info/session-metrics.ts +96 -0
  29. package/extensions/plan-mode/bash-policy.ts +54 -9
  30. package/extensions/plan-mode/index.ts +7 -2
  31. package/extensions/post-edit/index.ts +16 -6
  32. package/extensions/sessions/git-stats.ts +258 -72
  33. package/extensions/sessions/index.ts +222 -140
  34. package/extensions/sessions/preview-cache.ts +104 -0
  35. package/extensions/sessions/preview-loader.ts +856 -0
  36. package/extensions/sessions/sessions.ts +43 -4
  37. package/extensions/setup/index.ts +127 -131
  38. package/extensions/shared/activity-status.ts +36 -5
  39. package/extensions/shared/agent-session-page.ts +319 -0
  40. package/extensions/shared/agent-tool-renderer.ts +218 -0
  41. package/extensions/shared/agent-transcript.ts +524 -0
  42. package/extensions/shared/below-editor-navigation.ts +26 -0
  43. package/extensions/shared/capability-intent.ts +53 -0
  44. package/extensions/shared/child-session.ts +444 -22
  45. package/extensions/shared/result-budget.ts +134 -0
  46. package/extensions/shared/result-delivery.ts +34 -0
  47. package/extensions/shared/screen-chrome.ts +133 -0
  48. package/extensions/shared/setup-config.ts +97 -38
  49. package/extensions/shared/setup-episode-state.ts +1 -1
  50. package/extensions/shared/spinner.ts +28 -0
  51. package/extensions/shared/terminal-text.ts +110 -23
  52. package/extensions/shared/text-projection.ts +113 -0
  53. package/extensions/shared/tool-activity.ts +382 -0
  54. package/extensions/shared/tool-surface.ts +42 -8
  55. package/extensions/shared/transcript-viewport.ts +46 -0
  56. package/extensions/shared/web-observer-registry.ts +390 -0
  57. package/extensions/shared/worktree.ts +11 -0
  58. package/extensions/subagents/index.ts +461 -186
  59. package/extensions/subagents/navigation.ts +86 -28
  60. package/extensions/subagents/src/agent-types.ts +37 -15
  61. package/extensions/subagents/src/backend.ts +12 -1
  62. package/extensions/subagents/src/backends/pi.ts +375 -66
  63. package/extensions/subagents/src/domain.ts +5 -0
  64. package/extensions/subagents/src/id-sequence.ts +84 -0
  65. package/extensions/subagents/src/manager.ts +651 -536
  66. package/extensions/subagents/src/prompt.ts +185 -42
  67. package/extensions/subagents/src/result-artifact.ts +146 -0
  68. package/extensions/subagents/src/result-delivery.ts +7 -1
  69. package/extensions/subagents/src/runtime.ts +23 -6
  70. package/extensions/subagents/src/ui/takeover.ts +128 -337
  71. package/extensions/subagents/src/ui/transcript.ts +38 -501
  72. package/extensions/subagents/src/ui/wait-result.ts +103 -15
  73. package/extensions/suggestions/src/ui.ts +10 -4
  74. package/extensions/tasks/index.ts +0 -3
  75. package/extensions/tasks/ui.ts +79 -62
  76. package/extensions/ui-customization/footer.ts +7 -44
  77. package/extensions/ui-customization/index.ts +0 -4
  78. package/extensions/user-input-fold/index.ts +185 -0
  79. package/extensions/web/index.ts +234 -0
  80. package/extensions/workflows/artifacts.ts +147 -22
  81. package/extensions/workflows/completion-projection.ts +457 -0
  82. package/extensions/workflows/controller.ts +14 -2
  83. package/extensions/workflows/coordinator.ts +62 -0
  84. package/extensions/workflows/dashboard.ts +458 -339
  85. package/extensions/workflows/handoff.ts +121 -25
  86. package/extensions/workflows/index.ts +1042 -492
  87. package/extensions/workflows/journal.ts +148 -13
  88. package/extensions/workflows/model.ts +131 -19
  89. package/extensions/workflows/navigation.ts +61 -18
  90. package/extensions/workflows/progress-projection.ts +306 -0
  91. package/extensions/workflows/prompt.ts +166 -10
  92. package/extensions/workflows/replay-safety.ts +58 -27
  93. package/extensions/workflows/result-delivery.ts +253 -0
  94. package/extensions/workflows/retention.ts +593 -0
  95. package/extensions/workflows/runner.ts +388 -279
  96. package/extensions/workflows/sandbox-child.cjs +36 -3
  97. package/extensions/workflows/sandbox.ts +62 -8
  98. package/extensions/workflows/serialization.ts +325 -17
  99. package/extensions/workflows/tool-renderer.ts +22 -0
  100. package/extensions/workflows/transcript.ts +149 -0
  101. package/extensions/workspace-cleanup-guard/index.ts +54 -0
  102. package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
  103. package/package.json +28 -8
  104. package/skills/subagents/REFERENCE.md +189 -0
  105. package/skills/subagents/SKILL.md +2 -2
  106. package/skills/workflows/REFERENCE.md +10 -5
  107. package/skills/workflows/SKILL.md +53 -10
  108. package/web/adapter/pi-adapter.ts +661 -0
  109. package/web/host/browser-launcher.ts +20 -0
  110. package/web/host/static-assets.ts +4 -0
  111. package/web/host/terminal-status.ts +38 -0
  112. package/web/host/web-host.ts +789 -0
  113. package/web/http-dispatcher.ts +125 -0
  114. package/web/protocol/types.ts +462 -0
  115. package/web/runtime/pi-runtime.ts +991 -0
  116. package/web/runtime/types.ts +71 -0
  117. package/web/runtime/web-host-lease.ts +497 -0
  118. package/web/trace.ts +18 -0
  119. package/web/ui/app.js +1398 -0
  120. package/web/ui/index.html +139 -0
  121. package/web/ui/styles.css +598 -0
  122. package/web/vite.config.mjs +34 -0
  123. package/extensions/execution-convergence/active-evidence.ts +0 -129
  124. package/extensions/execution-convergence/index.ts +0 -442
  125. package/extensions/execution-convergence/workspace-provenance.ts +0 -338
  126. package/extensions/setup/intercom-fs-helper.cjs +0 -130
  127. package/extensions/setup/intercom.ts +0 -603
  128. package/extensions/subagents/src/backends/stub.ts +0 -296
  129. package/extensions/subagents/src/format.ts +0 -48
@@ -0,0 +1,319 @@
1
+ import type {
2
+ KeybindingsManager,
3
+ Theme,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import type { Component, Focusable, TUI } from "@earendil-works/pi-tui";
6
+ import {
7
+ Key,
8
+ matchesKey,
9
+ truncateToWidth,
10
+ visibleWidth,
11
+ } from "@earendil-works/pi-tui";
12
+ import {
13
+ AgentTranscriptRenderer,
14
+ type AgentTranscriptDocument,
15
+ } from "./agent-transcript.ts";
16
+ import { hintLine, type ScreenHint } from "./screen-chrome.ts";
17
+ import { spinnerFrame } from "./spinner.ts";
18
+ import { sanitizeTerminalText } from "./terminal-text.ts";
19
+ import { TranscriptViewport } from "./transcript-viewport.ts";
20
+
21
+ const SCROLL_STEP = 6;
22
+
23
+ export type AgentSessionPageStatus = "running" | "done" | "error" | "uncertain";
24
+
25
+ export interface AgentSessionPageState {
26
+ readonly id: string;
27
+ readonly title: string;
28
+ readonly status: AgentSessionPageStatus;
29
+ readonly document: AgentTranscriptDocument;
30
+ readonly metadata?: ReadonlyArray<string | undefined>;
31
+ readonly errorText?: string;
32
+ readonly emptyText?: string;
33
+ }
34
+
35
+ export interface AgentSessionPageSource {
36
+ getState(): AgentSessionPageState | undefined;
37
+ close(): void;
38
+ abort?(): void;
39
+ }
40
+
41
+ export interface AgentSessionPageOptions {
42
+ /** Inherited from the parent UI when the child page opens. */
43
+ readonly toolsExpanded?: boolean;
44
+ }
45
+
46
+ function safeLine(value: string) {
47
+ return sanitizeTerminalText(value).replace(/\s+/g, " ").trim();
48
+ }
49
+
50
+ function configuredKeys(
51
+ keybindings: KeybindingsManager,
52
+ binding: Parameters<KeybindingsManager["getKeys"]>[0],
53
+ ) {
54
+ return keybindings.getKeys(binding).join("/") || "unbound";
55
+ }
56
+
57
+ function stateGlyph(state: AgentSessionPageState, theme: Theme, now: number) {
58
+ switch (state.status) {
59
+ case "running":
60
+ return theme.fg("warning", spinnerFrame(now));
61
+ case "done":
62
+ return theme.fg("success", "✓");
63
+ case "error":
64
+ return theme.fg("error", "✗");
65
+ case "uncertain":
66
+ return theme.fg("warning", "?");
67
+ }
68
+ }
69
+
70
+ /**
71
+ * One full-screen child-session page shared by Direct and Workflow adapters.
72
+ * The source owns lifecycle facts; this module owns only page rendering,
73
+ * reading position, and navigation back to the parent view.
74
+ */
75
+ export class AgentSessionPage implements Component, Focusable {
76
+ private tui: TUI;
77
+ private theme: Theme;
78
+ private keybindings: KeybindingsManager;
79
+ private source: AgentSessionPageSource;
80
+ private renderer = new AgentTranscriptRenderer();
81
+ private viewport = new TranscriptViewport();
82
+ private rowCount = 0;
83
+ private viewportSize = 1;
84
+ private toolsExpanded: boolean;
85
+
86
+ private _focused = false;
87
+ get focused() {
88
+ return this._focused;
89
+ }
90
+ set focused(value: boolean) {
91
+ this._focused = value;
92
+ }
93
+
94
+ constructor(
95
+ tui: TUI,
96
+ theme: Theme,
97
+ keybindings: KeybindingsManager,
98
+ source: AgentSessionPageSource,
99
+ options?: AgentSessionPageOptions,
100
+ ) {
101
+ this.tui = tui;
102
+ this.theme = theme;
103
+ this.keybindings = keybindings;
104
+ this.source = source;
105
+ this.toolsExpanded = options?.toolsExpanded === true;
106
+ }
107
+
108
+ handleInput(data: string) {
109
+ const state = this.source.getState();
110
+ if (this.keybindings.matches(data, "app.tools.expand")) {
111
+ this.toolsExpanded = !this.toolsExpanded;
112
+ this.tui.requestRender();
113
+ return;
114
+ }
115
+ if (
116
+ this.source.abort &&
117
+ state?.status === "running" &&
118
+ this.keybindings.matches(data, "app.clear")
119
+ ) {
120
+ this.source.abort();
121
+ return;
122
+ }
123
+ if (
124
+ this.keybindings.matches(data, "app.interrupt") ||
125
+ this.keybindings.matches(data, "tui.select.cancel")
126
+ ) {
127
+ this.source.close();
128
+ return;
129
+ }
130
+
131
+ if (
132
+ this.keybindings.matches(data, "tui.editor.cursorLeft") ||
133
+ data === "h"
134
+ ) {
135
+ this.source.close();
136
+ return;
137
+ }
138
+ if (this.keybindings.matches(data, "tui.editor.cursorUp") || data === "k") {
139
+ this.viewport.scrollBy(-SCROLL_STEP, this.rowCount, this.viewportSize);
140
+ this.tui.requestRender();
141
+ return;
142
+ }
143
+ if (
144
+ this.keybindings.matches(data, "tui.editor.cursorDown") ||
145
+ data === "j"
146
+ ) {
147
+ this.viewport.scrollBy(SCROLL_STEP, this.rowCount, this.viewportSize);
148
+ this.tui.requestRender();
149
+ return;
150
+ }
151
+ if (
152
+ this.keybindings.matches(data, "tui.editor.pageUp") ||
153
+ matchesKey(data, Key.ctrl("u"))
154
+ ) {
155
+ this.viewport.scrollBy(
156
+ -this.viewportSize,
157
+ this.rowCount,
158
+ this.viewportSize,
159
+ );
160
+ this.tui.requestRender();
161
+ return;
162
+ }
163
+ if (
164
+ this.keybindings.matches(data, "tui.editor.pageDown") ||
165
+ matchesKey(data, Key.ctrl("d"))
166
+ ) {
167
+ this.viewport.scrollBy(
168
+ this.viewportSize,
169
+ this.rowCount,
170
+ this.viewportSize,
171
+ );
172
+ this.tui.requestRender();
173
+ return;
174
+ }
175
+ if (matchesKey(data, Key.home) || data === "g") {
176
+ this.viewport.scrollToTop(this.rowCount, this.viewportSize);
177
+ this.tui.requestRender();
178
+ return;
179
+ }
180
+ if (matchesKey(data, Key.end) || data === "G") {
181
+ this.viewport.scrollToEnd(this.rowCount, this.viewportSize);
182
+ this.tui.requestRender();
183
+ return;
184
+ }
185
+ }
186
+
187
+ private rule(width: number, left = "", right = "") {
188
+ const available = Math.max(1, width);
189
+ const leftWidth = visibleWidth(left);
190
+ const rightWidth = visibleWidth(right);
191
+ if (!right || leftWidth + rightWidth + 2 > available) {
192
+ return truncateToWidth(
193
+ left + "─".repeat(Math.max(0, available - leftWidth)),
194
+ available,
195
+ );
196
+ }
197
+ return (
198
+ left + "─".repeat(Math.max(1, available - leftWidth - rightWidth)) + right
199
+ );
200
+ }
201
+
202
+ render(width: number) {
203
+ const state = this.source.getState();
204
+ const height = Math.max(1, this.tui.terminal.rows || 30);
205
+ if (!state) {
206
+ const border = this.theme.fg(
207
+ "borderAccent",
208
+ "─".repeat(Math.max(1, width)),
209
+ );
210
+ const lines = [
211
+ border,
212
+ this.theme.fg("dim", "child is no longer tracked"),
213
+ ];
214
+ while (lines.length < height - 1) lines.push("");
215
+ lines.push(border);
216
+ return lines;
217
+ }
218
+
219
+ const now = Date.now();
220
+ const title = safeLine(state.title) || state.id;
221
+ const headerLeft =
222
+ this.theme.fg("borderAccent", "─ ") +
223
+ stateGlyph(state, this.theme, now) +
224
+ " " +
225
+ this.theme.fg("accent", this.theme.bold(title)) +
226
+ this.theme.fg("borderAccent", " ");
227
+ const metadata = (state.metadata ?? [])
228
+ .map((item) => (item ? safeLine(item) : ""))
229
+ .filter(Boolean)
230
+ .map((item) => this.theme.fg("muted", item));
231
+ const dot = this.theme.fg("dim", " · ");
232
+ while (
233
+ metadata.length > 1 &&
234
+ visibleWidth(headerLeft) + visibleWidth(metadata.join(dot)) + 2 > width
235
+ ) {
236
+ metadata.shift();
237
+ }
238
+
239
+ const chromeRows = 4;
240
+ const errorRows = state.errorText ? 1 : 0;
241
+ const bodyHeight = Math.max(1, height - chromeRows);
242
+ const transcriptCapacity = Math.max(1, bodyHeight - errorRows);
243
+ const transcript = this.renderer.render(state.document, width, this.theme, {
244
+ now,
245
+ expanded: this.toolsExpanded,
246
+ });
247
+ this.rowCount = transcript.length;
248
+ this.viewportSize = transcriptCapacity;
249
+ this.viewport.reconcile(transcript.length, transcriptCapacity);
250
+
251
+ const lines = [
252
+ this.rule(
253
+ width,
254
+ truncateToWidth(
255
+ headerLeft,
256
+ Math.max(1, width - visibleWidth(metadata.join(dot)) - 2),
257
+ ),
258
+ metadata.join(dot),
259
+ ),
260
+ ];
261
+ const body: string[] = [];
262
+ if (state.errorText) {
263
+ body.push(
264
+ truncateToWidth(
265
+ this.theme.fg("error", `error: ${safeLine(state.errorText)}`),
266
+ width,
267
+ ),
268
+ );
269
+ }
270
+ const visible = transcript.slice(
271
+ this.viewport.scrollTop,
272
+ this.viewport.scrollTop + transcriptCapacity,
273
+ );
274
+ if (visible.length === 0) {
275
+ body.push(this.theme.fg("dim", state.emptyText ?? "waiting for output…"));
276
+ } else {
277
+ body.push(...visible);
278
+ }
279
+ while (body.length < bodyHeight) body.push("");
280
+ lines.push(...body.slice(0, bodyHeight));
281
+
282
+ lines.push(
283
+ this.rule(
284
+ width,
285
+ this.theme.fg("borderAccent", "─"),
286
+ this.viewport.followingEnd
287
+ ? ""
288
+ : this.theme.fg(
289
+ "dim",
290
+ `↓ ${this.viewport.linesBelow(transcript.length, transcriptCapacity)}`,
291
+ ),
292
+ ),
293
+ );
294
+ const keys = (binding: Parameters<KeybindingsManager["getKeys"]>[0]) =>
295
+ configuredKeys(this.keybindings, binding);
296
+ const hints: ScreenHint[] = [];
297
+ hints.push([keys("app.interrupt"), "back"]);
298
+ if (this.source.abort) hints.push([keys("app.clear"), "abort run"]);
299
+ hints.push([
300
+ keys("app.tools.expand"),
301
+ this.toolsExpanded ? "collapse tools" : "expand tools",
302
+ ]);
303
+ hints.push(
304
+ [
305
+ `${keys("tui.editor.cursorUp")}/${keys("tui.editor.cursorDown")}`,
306
+ "scroll",
307
+ ],
308
+ [`${keys("tui.editor.pageUp")}/${keys("tui.editor.pageDown")}`, "page"],
309
+ ["g/G", "top/bottom"],
310
+ );
311
+ lines.push(hintLine(this.theme, hints, width));
312
+ lines.push(this.rule(width, this.theme.fg("borderAccent", "─")));
313
+ return lines.slice(0, height);
314
+ }
315
+
316
+ invalidate() {
317
+ this.renderer.invalidate();
318
+ }
319
+ }
@@ -0,0 +1,218 @@
1
+ /** Ephemeral bridge from child tool events to Pi's canonical tool renderer. */
2
+
3
+ import {
4
+ ToolExecutionComponent,
5
+ type ToolDefinition,
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import type { TUI } from "@earendil-works/pi-tui";
8
+
9
+ export interface AgentToolRenderRequest {
10
+ readonly toolId: string;
11
+ readonly name: string;
12
+ readonly cwd?: string;
13
+ /** The child page's local Pi-native evidence visibility. */
14
+ readonly expanded?: boolean;
15
+ }
16
+
17
+ /** Operator-only projection. It is deliberately absent from persisted state. */
18
+ export interface AgentToolRenderer {
19
+ renderTool(
20
+ request: AgentToolRenderRequest,
21
+ width: number,
22
+ ): string[] | undefined;
23
+ invalidate?(): void;
24
+ }
25
+
26
+ type ToolResult = Parameters<ToolExecutionComponent["updateResult"]>[0];
27
+
28
+ interface ToolExecutionRecord {
29
+ name: string;
30
+ args: unknown;
31
+ definition?: ToolDefinition;
32
+ executionStarted: boolean;
33
+ argsComplete: boolean;
34
+ result?: ToolResult;
35
+ resultSource?: unknown;
36
+ isPartial: boolean;
37
+ component?: ToolExecutionComponent;
38
+ componentCwd?: string;
39
+ componentExpanded?: boolean;
40
+ }
41
+
42
+ const inertTui = {
43
+ requestRender() {},
44
+ } as TUI;
45
+
46
+ function resultContent(value: unknown): ToolResult["content"] {
47
+ if (typeof value === "string") return [{ type: "text", text: value }];
48
+ if (!value || typeof value !== "object") return [];
49
+ const content = (value as { content?: unknown }).content;
50
+ if (!Array.isArray(content)) return [];
51
+ return content.flatMap((part) => {
52
+ if (!part || typeof part !== "object") return [];
53
+ const candidate = part as {
54
+ type?: unknown;
55
+ text?: unknown;
56
+ data?: unknown;
57
+ mimeType?: unknown;
58
+ };
59
+ if (typeof candidate.type !== "string") return [];
60
+ return [
61
+ {
62
+ type: candidate.type,
63
+ ...(typeof candidate.text === "string" ? { text: candidate.text } : {}),
64
+ ...(typeof candidate.data === "string" ? { data: candidate.data } : {}),
65
+ ...(typeof candidate.mimeType === "string"
66
+ ? { mimeType: candidate.mimeType }
67
+ : {}),
68
+ },
69
+ ];
70
+ });
71
+ }
72
+
73
+ function normalizeResult(value: unknown, isError: boolean): ToolResult {
74
+ const details =
75
+ value && typeof value === "object"
76
+ ? (value as { details?: unknown }).details
77
+ : undefined;
78
+ return {
79
+ content: resultContent(value),
80
+ ...(details === undefined ? {} : { details }),
81
+ isError,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Retains the real definition and event payloads only for the lifetime of the
87
+ * child session/run. The model transcript and persisted artifacts remain the
88
+ * compact, serializable source of execution facts.
89
+ */
90
+ export class AgentToolRenderLedger implements AgentToolRenderer {
91
+ private executions = new Map<string, ToolExecutionRecord>();
92
+
93
+ start(
94
+ toolId: string,
95
+ name: string,
96
+ args: unknown,
97
+ definition?: ToolDefinition,
98
+ ) {
99
+ const current = this.executions.get(toolId);
100
+ if (current) {
101
+ if (definition && definition !== current.definition) {
102
+ current.definition = definition;
103
+ current.component = undefined;
104
+ current.componentCwd = undefined;
105
+ current.componentExpanded = undefined;
106
+ }
107
+ current.name = name;
108
+ if (current.args !== args) {
109
+ current.args = args;
110
+ current.component?.updateArgs(args);
111
+ }
112
+ const wasStarted = current.executionStarted;
113
+ const wereArgsComplete = current.argsComplete;
114
+ current.executionStarted = true;
115
+ current.argsComplete = true;
116
+ if (!wasStarted) current.component?.markExecutionStarted();
117
+ if (!wereArgsComplete) current.component?.setArgsComplete();
118
+ return;
119
+ }
120
+ this.executions.set(toolId, {
121
+ name,
122
+ args,
123
+ ...(definition ? { definition } : {}),
124
+ executionStarted: true,
125
+ argsComplete: true,
126
+ isPartial: true,
127
+ });
128
+ }
129
+
130
+ update(toolId: string, name: string, args: unknown, result: unknown) {
131
+ const current = this.executions.get(toolId);
132
+ if (!current) {
133
+ this.executions.set(toolId, {
134
+ name,
135
+ args,
136
+ executionStarted: true,
137
+ argsComplete: true,
138
+ result: normalizeResult(result, false),
139
+ resultSource: result,
140
+ isPartial: true,
141
+ });
142
+ return;
143
+ }
144
+ if (current.args !== args) {
145
+ current.args = args;
146
+ current.component?.updateArgs(args);
147
+ }
148
+ if (current.resultSource === result && current.isPartial) return;
149
+ current.result = normalizeResult(result, false);
150
+ current.resultSource = result;
151
+ current.isPartial = true;
152
+ current.component?.updateResult(current.result, true);
153
+ }
154
+
155
+ end(toolId: string, name: string, result: unknown, isError: boolean) {
156
+ const current = this.executions.get(toolId);
157
+ if (!current) {
158
+ this.executions.set(toolId, {
159
+ name,
160
+ args: {},
161
+ executionStarted: true,
162
+ argsComplete: true,
163
+ result: normalizeResult(result, isError),
164
+ resultSource: result,
165
+ isPartial: false,
166
+ });
167
+ return;
168
+ }
169
+ if (
170
+ current.resultSource === result &&
171
+ !current.isPartial &&
172
+ current.result?.isError === isError
173
+ ) {
174
+ return;
175
+ }
176
+ current.result = normalizeResult(result, isError);
177
+ current.resultSource = result;
178
+ current.isPartial = false;
179
+ current.component?.updateResult(current.result, false);
180
+ }
181
+
182
+ renderTool(request: AgentToolRenderRequest, width: number) {
183
+ const execution = this.executions.get(request.toolId);
184
+ if (!execution || execution.name !== request.name) return undefined;
185
+ const cwd = request.cwd ?? process.cwd();
186
+ if (!execution.component || execution.componentCwd !== cwd) {
187
+ execution.component = new ToolExecutionComponent(
188
+ execution.name,
189
+ request.toolId,
190
+ execution.args,
191
+ { showImages: false },
192
+ execution.definition,
193
+ inertTui,
194
+ cwd,
195
+ );
196
+ execution.componentCwd = cwd;
197
+ execution.componentExpanded = false;
198
+ if (execution.executionStarted)
199
+ execution.component.markExecutionStarted();
200
+ if (execution.argsComplete) execution.component.setArgsComplete();
201
+ if (execution.result) {
202
+ execution.component.updateResult(execution.result, execution.isPartial);
203
+ }
204
+ }
205
+ const expanded = request.expanded === true;
206
+ if (execution.componentExpanded !== expanded) {
207
+ execution.component.setExpanded(expanded);
208
+ execution.componentExpanded = expanded;
209
+ }
210
+ return execution.component.render(width);
211
+ }
212
+
213
+ invalidate() {
214
+ for (const execution of this.executions.values()) {
215
+ execution.component?.invalidate();
216
+ }
217
+ }
218
+ }