@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.
- package/README.md +184 -59
- package/SETUP.md +23 -7
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +145 -0
- package/extensions/ask-user/index.ts +30 -14
- package/extensions/background-terminals/index.ts +30 -2
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +486 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +14 -6
- package/extensions/background-terminals/src/result-delivery.ts +4 -1
- package/extensions/background-terminals/src/ui/ps.ts +132 -129
- package/extensions/capabilities/index.ts +30 -42
- package/extensions/capabilities/src/ui.ts +93 -0
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/schedule.ts +7 -1
- package/extensions/file-mutation-display/index.ts +34 -76
- package/extensions/file-mutation-display/render.ts +146 -87
- package/extensions/file-search/index.ts +8 -7
- package/extensions/file-search/src/binaries.ts +75 -59
- package/extensions/git-info/src/changed-files-view.ts +47 -14
- package/extensions/git-read/index.ts +328 -0
- package/extensions/git-read/src/args.ts +171 -0
- package/extensions/git-read/src/process.ts +81 -0
- package/extensions/git-read/src/prompt.ts +56 -0
- package/extensions/model-info/index.ts +21 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +7 -2
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +222 -140
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +127 -131
- package/extensions/shared/activity-status.ts +36 -5
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/below-editor-navigation.ts +26 -0
- package/extensions/shared/capability-intent.ts +53 -0
- package/extensions/shared/child-session.ts +444 -22
- package/extensions/shared/result-budget.ts +134 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/screen-chrome.ts +133 -0
- package/extensions/shared/setup-config.ts +97 -38
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/spinner.ts +28 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +113 -0
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +42 -8
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +461 -186
- package/extensions/subagents/navigation.ts +86 -28
- package/extensions/subagents/src/agent-types.ts +37 -15
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +375 -66
- package/extensions/subagents/src/domain.ts +5 -0
- package/extensions/subagents/src/id-sequence.ts +84 -0
- package/extensions/subagents/src/manager.ts +651 -536
- package/extensions/subagents/src/prompt.ts +185 -42
- package/extensions/subagents/src/result-artifact.ts +146 -0
- package/extensions/subagents/src/result-delivery.ts +7 -1
- package/extensions/subagents/src/runtime.ts +23 -6
- package/extensions/subagents/src/ui/takeover.ts +128 -337
- package/extensions/subagents/src/ui/transcript.ts +38 -501
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/tasks/ui.ts +79 -62
- package/extensions/ui-customization/footer.ts +7 -44
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +185 -0
- package/extensions/web/index.ts +234 -0
- package/extensions/workflows/artifacts.ts +147 -22
- package/extensions/workflows/completion-projection.ts +457 -0
- package/extensions/workflows/controller.ts +14 -2
- package/extensions/workflows/coordinator.ts +62 -0
- package/extensions/workflows/dashboard.ts +458 -339
- package/extensions/workflows/handoff.ts +121 -25
- package/extensions/workflows/index.ts +1042 -492
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +131 -19
- package/extensions/workflows/navigation.ts +61 -18
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +166 -10
- package/extensions/workflows/replay-safety.ts +58 -27
- package/extensions/workflows/result-delivery.ts +253 -0
- package/extensions/workflows/retention.ts +593 -0
- package/extensions/workflows/runner.ts +388 -279
- package/extensions/workflows/sandbox-child.cjs +36 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +28 -8
- package/skills/subagents/REFERENCE.md +189 -0
- package/skills/subagents/SKILL.md +2 -2
- package/skills/workflows/REFERENCE.md +10 -5
- package/skills/workflows/SKILL.md +53 -10
- package/web/adapter/pi-adapter.ts +661 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +789 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +462 -0
- package/web/runtime/pi-runtime.ts +991 -0
- package/web/runtime/types.ts +71 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1398 -0
- package/web/ui/index.html +139 -0
- package/web/ui/styles.css +598 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -296
- package/extensions/subagents/src/format.ts +0 -48
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* settle. `/subagents` opens a picker + full interactive takeover view.
|
|
17
17
|
*
|
|
18
18
|
* Agent types (`src/agent-types.ts`) are optional named presets that fix a
|
|
19
|
-
* child's system prompt, model, and tool allowlist; see
|
|
19
|
+
* child's system prompt, model, and tool allowlist; see
|
|
20
|
+
* `skills/subagents/REFERENCE.md`.
|
|
20
21
|
*
|
|
21
22
|
* Architecture: Effect v4 generators throughout (backend -> manager ->
|
|
22
23
|
* runtime); this file is the async boundary where tool handlers run effects
|
|
@@ -25,7 +26,6 @@
|
|
|
25
26
|
|
|
26
27
|
import * as fs from "node:fs";
|
|
27
28
|
import * as path from "node:path";
|
|
28
|
-
import { StringEnum } from "@earendil-works/pi-ai";
|
|
29
29
|
import type {
|
|
30
30
|
ExtensionAPI,
|
|
31
31
|
ExtensionCommandContext,
|
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
DEFAULT_MAX_BYTES,
|
|
38
38
|
DEFAULT_MAX_LINES,
|
|
39
39
|
defineTool,
|
|
40
|
-
formatSize,
|
|
41
40
|
getAgentDir,
|
|
42
41
|
getMarkdownTheme,
|
|
43
42
|
keyHint,
|
|
@@ -45,42 +44,83 @@ import {
|
|
|
45
44
|
} from "@earendil-works/pi-coding-agent";
|
|
46
45
|
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
47
46
|
import { Type } from "typebox";
|
|
47
|
+
import {
|
|
48
|
+
createStatusWriter,
|
|
49
|
+
formatActivityStatus,
|
|
50
|
+
hasActivity,
|
|
51
|
+
unreadActivityCounts,
|
|
52
|
+
} from "../shared/activity-status.ts";
|
|
53
|
+
import { sanitizeText } from "../shared/agent-transcript.ts";
|
|
54
|
+
import {
|
|
55
|
+
BelowEditorNavigationEditor,
|
|
56
|
+
BelowEditorStripState,
|
|
57
|
+
} from "../shared/below-editor-navigation.ts";
|
|
58
|
+
import {
|
|
59
|
+
effectiveChildToolAllowlist,
|
|
60
|
+
resolveStandaloneChildProjectTrust,
|
|
61
|
+
} from "../shared/child-session.ts";
|
|
62
|
+
import { formatContextUtilization } from "../shared/context-utilization.ts";
|
|
63
|
+
import {
|
|
64
|
+
registerEditorLayer,
|
|
65
|
+
removeEditorLayer,
|
|
66
|
+
} from "../shared/editor-layers.ts";
|
|
67
|
+
import {
|
|
68
|
+
PLAN_MODE_CHANNEL,
|
|
69
|
+
type PlanModeState,
|
|
70
|
+
planModeAllowsDeclaredTools,
|
|
71
|
+
planModeChildTools,
|
|
72
|
+
} from "../shared/plan-mode-state.ts";
|
|
73
|
+
import {
|
|
74
|
+
allocateResultBudgets,
|
|
75
|
+
type ParentContextUsage,
|
|
76
|
+
} from "../shared/result-budget.ts";
|
|
77
|
+
import { type DetailDisplay, loadSetupConfig } from "../shared/setup-config.ts";
|
|
78
|
+
import {
|
|
79
|
+
OPENPI_TOOL_SURFACE,
|
|
80
|
+
patchOwnedTools,
|
|
81
|
+
} from "../shared/tool-surface.ts";
|
|
82
|
+
import {
|
|
83
|
+
projectSubagentCapability,
|
|
84
|
+
registerWebCapability,
|
|
85
|
+
} from "../shared/web-observer-registry.ts";
|
|
86
|
+
import {
|
|
87
|
+
createWorktree,
|
|
88
|
+
formatWorktreeCleanupWarning,
|
|
89
|
+
reclaimWorktree,
|
|
90
|
+
type Worktree,
|
|
91
|
+
} from "../shared/worktree.ts";
|
|
92
|
+
import {
|
|
93
|
+
normalizeSubagentTitle,
|
|
94
|
+
SubagentStripWidget,
|
|
95
|
+
selectSubagentStripEntry,
|
|
96
|
+
subagentStripEntryKey,
|
|
97
|
+
} from "./navigation.ts";
|
|
48
98
|
import {
|
|
49
99
|
formatAgentTypeDiagnostics,
|
|
50
100
|
loadAgentTypes,
|
|
51
101
|
roleModelForAgentType,
|
|
52
102
|
selectSubagentModel,
|
|
53
|
-
type AgentType,
|
|
54
103
|
} from "./src/agent-types.ts";
|
|
55
104
|
import { deriveBtwTitle, isModelVisible } from "./src/by-the-way.ts";
|
|
56
105
|
import {
|
|
57
106
|
BACKEND_NAMES,
|
|
58
107
|
formatElapsed,
|
|
59
108
|
latestText,
|
|
60
|
-
REASONING_EFFORTS,
|
|
61
109
|
type SubagentSnapshot,
|
|
62
110
|
} from "./src/domain.ts";
|
|
63
111
|
import {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
OPENPI_TOOL_SURFACE,
|
|
70
|
-
patchOwnedTools,
|
|
71
|
-
} from "../shared/tool-surface.ts";
|
|
72
|
-
import {
|
|
73
|
-
registerEditorLayer,
|
|
74
|
-
removeEditorLayer,
|
|
75
|
-
} from "../shared/editor-layers.ts";
|
|
76
|
-
import { formatContextUtilization } from "./src/format.ts";
|
|
112
|
+
restoreSubagentIdCounters,
|
|
113
|
+
SUBAGENT_ID_WATERMARK_ENTRY_TYPE,
|
|
114
|
+
type SubagentIdCounters,
|
|
115
|
+
subagentIdWatermark,
|
|
116
|
+
} from "./src/id-sequence.ts";
|
|
77
117
|
import { SubagentManager, type SubagentManagerShape } from "./src/manager.ts";
|
|
78
118
|
import {
|
|
119
|
+
buildSubagentResultDisplayMessage,
|
|
79
120
|
buildSubagentResultMessage,
|
|
80
|
-
createAgentTypeParameterSchema,
|
|
81
121
|
buildSubagentSendResult,
|
|
82
122
|
buildSubagentSpawnResult,
|
|
83
|
-
|
|
123
|
+
createSubagentSpawnToolSurface,
|
|
84
124
|
SUBAGENT_CANCEL_PARAMETER_DESCRIPTIONS,
|
|
85
125
|
SUBAGENT_CANCEL_TOOL_DESCRIPTION,
|
|
86
126
|
SUBAGENT_CHECK_PARAMETER_DESCRIPTIONS,
|
|
@@ -88,54 +128,40 @@ import {
|
|
|
88
128
|
SUBAGENT_LIST_TOOL_DESCRIPTION,
|
|
89
129
|
SUBAGENT_SEND_PARAMETER_DESCRIPTIONS,
|
|
90
130
|
SUBAGENT_SEND_TOOL_DESCRIPTION,
|
|
91
|
-
SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS,
|
|
92
131
|
SUBAGENT_SPAWN_PROMPT_GUIDELINES,
|
|
93
132
|
SUBAGENT_SPAWN_PROMPT_SNIPPET,
|
|
94
|
-
SUBAGENT_SPAWN_TOOL_DESCRIPTION,
|
|
95
133
|
SUBAGENT_WAIT_PARAMETER_DESCRIPTIONS,
|
|
96
134
|
SUBAGENT_WAIT_TOOL_DESCRIPTION,
|
|
135
|
+
stripSubagentResultTransportInstruction,
|
|
97
136
|
} from "./src/prompt.ts";
|
|
98
|
-
import { createSubagentResultDelivery } from "./src/result-delivery.ts";
|
|
99
137
|
import {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
BelowEditorStripState,
|
|
106
|
-
} from "../shared/below-editor-navigation.ts";
|
|
107
|
-
import { loadSetupConfig } from "../shared/setup-config.ts";
|
|
108
|
-
import {
|
|
109
|
-
PLAN_MODE_CHANNEL,
|
|
110
|
-
planModeAllowsDeclaredTools,
|
|
111
|
-
planModeChildTools,
|
|
112
|
-
type PlanModeState,
|
|
113
|
-
} from "../shared/plan-mode-state.ts";
|
|
114
|
-
import {
|
|
115
|
-
createWorktree,
|
|
116
|
-
reclaimWorktree,
|
|
117
|
-
type Worktree,
|
|
118
|
-
} from "../shared/worktree.ts";
|
|
138
|
+
persistResultArtifact,
|
|
139
|
+
projectResult,
|
|
140
|
+
type ResultProjection,
|
|
141
|
+
} from "./src/result-artifact.ts";
|
|
142
|
+
import { createSubagentResultDelivery } from "./src/result-delivery.ts";
|
|
119
143
|
import {
|
|
120
144
|
createSubagentRuntime,
|
|
121
145
|
runTool,
|
|
122
146
|
type SubagentRuntime,
|
|
123
147
|
} from "./src/runtime.ts";
|
|
124
|
-
import {
|
|
125
|
-
normalizeSubagentTitle,
|
|
126
|
-
selectSubagentStripEntry,
|
|
127
|
-
SubagentStripWidget,
|
|
128
|
-
} from "./navigation.ts";
|
|
129
148
|
import { openSubagentPicker, openSubagentTakeover } from "./src/ui/takeover.ts";
|
|
130
149
|
import {
|
|
131
|
-
buildWaitResultPreview,
|
|
132
150
|
renderWaitResult,
|
|
151
|
+
renderWaitResultPreview,
|
|
133
152
|
type WaitResultDetails,
|
|
134
153
|
} from "./src/ui/wait-result.ts";
|
|
135
154
|
|
|
136
155
|
const SUBAGENT_OUTPUT_MAX_BYTES = 24 * 1024;
|
|
156
|
+
const AUTOMATIC_OUTPUT_MAX_BYTES = 48 * 1024;
|
|
157
|
+
const AUTOMATIC_MIN_RESULT_BYTES = 2 * 1024;
|
|
137
158
|
const WAIT_OUTPUT_MAX_BYTES = 48 * 1024;
|
|
138
159
|
const WAIT_PER_AGENT_MAX_BYTES = 16 * 1024;
|
|
160
|
+
const WAIT_MIN_RESULT_BYTES = 512;
|
|
161
|
+
const RESULT_HEADROOM_SHARE = 0.5;
|
|
162
|
+
const ESTIMATED_BYTES_PER_TOKEN = 4;
|
|
163
|
+
const AUTOMATIC_BATCH_TRUNCATION_NOTICE =
|
|
164
|
+
"\n\n[Automatic subagent result batch truncated at the 48 KiB total limit.]";
|
|
139
165
|
|
|
140
166
|
interface SpawnResultDetails {
|
|
141
167
|
readonly id?: string;
|
|
@@ -156,12 +182,24 @@ interface SubagentResultDetails {
|
|
|
156
182
|
readonly id?: string;
|
|
157
183
|
readonly title?: string;
|
|
158
184
|
readonly status?: SubagentSnapshot["status"];
|
|
185
|
+
readonly outcome?: SubagentSnapshot["outcome"];
|
|
186
|
+
readonly worktreeBranch?: string;
|
|
187
|
+
readonly elapsed?: string;
|
|
188
|
+
readonly artifactSaveFailed?: boolean;
|
|
189
|
+
readonly fullResultSaved?: boolean;
|
|
159
190
|
readonly count?: number;
|
|
160
191
|
readonly results?: ReadonlyArray<{
|
|
161
192
|
readonly id: string;
|
|
162
193
|
readonly title: string;
|
|
163
194
|
readonly status: SubagentSnapshot["status"];
|
|
195
|
+
readonly outcome?: SubagentSnapshot["outcome"];
|
|
196
|
+
readonly worktreeBranch?: string;
|
|
197
|
+
readonly elapsed?: string;
|
|
198
|
+
readonly artifactSaveFailed?: boolean;
|
|
199
|
+
readonly fullResultSaved?: boolean;
|
|
164
200
|
}>;
|
|
201
|
+
/** Display-only projection for the custom message renderer. */
|
|
202
|
+
readonly displayContent?: string;
|
|
165
203
|
}
|
|
166
204
|
|
|
167
205
|
interface SubagentResultEntryData {
|
|
@@ -189,56 +227,171 @@ function describeSubagent(snap: SubagentSnapshot) {
|
|
|
189
227
|
return `${snap.id} [${snap.status}] "${snap.title}" (${details.join(", ")})`;
|
|
190
228
|
}
|
|
191
229
|
|
|
192
|
-
function truncatedOutput(
|
|
230
|
+
export function truncatedOutput(
|
|
193
231
|
snap: SubagentSnapshot,
|
|
194
232
|
maxBytes = SUBAGENT_OUTPUT_MAX_BYTES,
|
|
233
|
+
writeArtifact: (content: string) => string = (content) =>
|
|
234
|
+
persistResultArtifact(getAgentDir(), content),
|
|
195
235
|
): string {
|
|
196
236
|
const output = snap.finalText || "(no output)";
|
|
197
|
-
|
|
237
|
+
return projectResult(output, {
|
|
238
|
+
maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
|
|
239
|
+
maxLines: Math.min(600, DEFAULT_MAX_LINES),
|
|
240
|
+
writeArtifact,
|
|
241
|
+
}).text;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function projectSubagentOutput(
|
|
245
|
+
snap: SubagentSnapshot,
|
|
246
|
+
maxBytes: number,
|
|
247
|
+
): ResultProjection {
|
|
248
|
+
const output = snap.finalText || "(no output)";
|
|
249
|
+
return projectResult(output, {
|
|
198
250
|
maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
|
|
199
251
|
maxLines: Math.min(600, DEFAULT_MAX_LINES),
|
|
252
|
+
writeArtifact: (content) => persistResultArtifact(getAgentDir(), content),
|
|
200
253
|
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
type OutputProjection = Pick<
|
|
257
|
+
ResultProjection,
|
|
258
|
+
"text" | "artifactPath" | "artifactSaveFailed"
|
|
259
|
+
>;
|
|
260
|
+
|
|
261
|
+
function normalizeProjection(
|
|
262
|
+
output: string | OutputProjection,
|
|
263
|
+
): OutputProjection {
|
|
264
|
+
return typeof output === "string" ? { text: output } : output;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function boundAutomaticResultBatch(content: string) {
|
|
268
|
+
const probe = truncateHead(content, {
|
|
269
|
+
maxBytes: AUTOMATIC_OUTPUT_MAX_BYTES,
|
|
270
|
+
maxLines: Number.MAX_SAFE_INTEGER,
|
|
271
|
+
});
|
|
272
|
+
if (!probe.truncated) return content;
|
|
273
|
+
|
|
274
|
+
const noticeBytes = Buffer.byteLength(
|
|
275
|
+
AUTOMATIC_BATCH_TRUNCATION_NOTICE,
|
|
276
|
+
"utf8",
|
|
277
|
+
);
|
|
278
|
+
const bounded = truncateHead(content, {
|
|
279
|
+
maxBytes: Math.max(0, AUTOMATIC_OUTPUT_MAX_BYTES - noticeBytes),
|
|
280
|
+
maxLines: Number.MAX_SAFE_INTEGER,
|
|
281
|
+
});
|
|
282
|
+
return `${bounded.content}${AUTOMATIC_BATCH_TRUNCATION_NOTICE}`;
|
|
206
283
|
}
|
|
207
284
|
|
|
208
285
|
export function createSubagentResultDispatcher(
|
|
209
286
|
pi: ExtensionAPI,
|
|
210
|
-
outputFor: (
|
|
287
|
+
outputFor: (
|
|
288
|
+
snap: SubagentSnapshot,
|
|
289
|
+
maxBytes: number,
|
|
290
|
+
) => string | OutputProjection = projectSubagentOutput,
|
|
291
|
+
getContextUsage: () => ParentContextUsage | undefined = () => undefined,
|
|
211
292
|
) {
|
|
212
293
|
return (snaps: readonly SubagentSnapshot[]) => {
|
|
213
294
|
if (snaps.length === 0) return;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
.
|
|
295
|
+
const emptyMessages = snaps.map((snap) =>
|
|
296
|
+
buildSubagentResultMessage({
|
|
297
|
+
id: snap.id,
|
|
298
|
+
title: snap.title,
|
|
299
|
+
status: snap.status,
|
|
300
|
+
errorText: snap.errorText,
|
|
301
|
+
output: "",
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
const wrapperBytes =
|
|
305
|
+
emptyMessages.reduce(
|
|
306
|
+
(sum, message) => sum + Buffer.byteLength(message, "utf8"),
|
|
307
|
+
0,
|
|
308
|
+
) +
|
|
309
|
+
Math.max(0, snaps.length - 1) * 2;
|
|
310
|
+
const projectionBatchBytes = Math.max(
|
|
311
|
+
0,
|
|
312
|
+
AUTOMATIC_OUTPUT_MAX_BYTES - wrapperBytes,
|
|
313
|
+
);
|
|
314
|
+
const allocation = allocateResultBudgets(
|
|
315
|
+
snaps.map((snap) =>
|
|
316
|
+
Buffer.byteLength(snap.finalText || "(no output)", "utf8"),
|
|
317
|
+
),
|
|
318
|
+
getContextUsage(),
|
|
319
|
+
{
|
|
320
|
+
maxBatchBytes: projectionBatchBytes,
|
|
321
|
+
maxResultBytes: SUBAGENT_OUTPUT_MAX_BYTES,
|
|
322
|
+
minResultBytes: AUTOMATIC_MIN_RESULT_BYTES,
|
|
323
|
+
headroomShare: RESULT_HEADROOM_SHARE,
|
|
324
|
+
estimatedBytesPerToken: ESTIMATED_BYTES_PER_TOKEN,
|
|
325
|
+
fixedBytes: wrapperBytes,
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
const projections = snaps.map((snap, index) =>
|
|
329
|
+
normalizeProjection(outputFor(snap, allocation.budgets[index]!)),
|
|
330
|
+
);
|
|
331
|
+
const outputs = projections.map((projection) => projection.text);
|
|
332
|
+
const displayContent = boundAutomaticResultBatch(
|
|
333
|
+
snaps
|
|
334
|
+
.map((snap, index) =>
|
|
335
|
+
buildSubagentResultDisplayMessage({
|
|
336
|
+
id: snap.id,
|
|
337
|
+
title: snap.title,
|
|
338
|
+
status: snap.status,
|
|
339
|
+
errorText: snap.errorText,
|
|
340
|
+
output: outputs[index]!,
|
|
341
|
+
}),
|
|
342
|
+
)
|
|
343
|
+
.join("\n\n"),
|
|
344
|
+
);
|
|
345
|
+
const content = boundAutomaticResultBatch(
|
|
346
|
+
snaps
|
|
347
|
+
.map((snap, index) =>
|
|
348
|
+
buildSubagentResultMessage({
|
|
349
|
+
id: snap.id,
|
|
350
|
+
title: snap.title,
|
|
351
|
+
status: snap.status,
|
|
352
|
+
errorText: snap.errorText,
|
|
353
|
+
output: outputs[index]!,
|
|
354
|
+
}),
|
|
355
|
+
)
|
|
356
|
+
.join("\n\n"),
|
|
357
|
+
);
|
|
225
358
|
const details: SubagentResultDetails =
|
|
226
359
|
snaps.length === 1
|
|
227
360
|
? {
|
|
228
361
|
id: snaps[0]!.id,
|
|
229
362
|
title: snaps[0]!.title,
|
|
230
363
|
status: snaps[0]!.status,
|
|
364
|
+
...(snaps[0]!.outcome ? { outcome: snaps[0]!.outcome } : {}),
|
|
365
|
+
...(snaps[0]!.worktreeBranch
|
|
366
|
+
? { worktreeBranch: snaps[0]!.worktreeBranch }
|
|
367
|
+
: {}),
|
|
368
|
+
elapsed: formatElapsed(snaps[0]!),
|
|
369
|
+
...(projections[0]!.artifactPath ? { fullResultSaved: true } : {}),
|
|
370
|
+
...(projections[0]!.artifactSaveFailed
|
|
371
|
+
? { artifactSaveFailed: true }
|
|
372
|
+
: {}),
|
|
231
373
|
}
|
|
232
374
|
: {
|
|
233
375
|
count: snaps.length,
|
|
234
|
-
results: snaps.map((snap) => ({
|
|
376
|
+
results: snaps.map((snap, index) => ({
|
|
235
377
|
id: snap.id,
|
|
236
378
|
title: snap.title,
|
|
237
379
|
status: snap.status,
|
|
380
|
+
...(snap.outcome ? { outcome: snap.outcome } : {}),
|
|
381
|
+
...(snap.worktreeBranch
|
|
382
|
+
? { worktreeBranch: snap.worktreeBranch }
|
|
383
|
+
: {}),
|
|
384
|
+
elapsed: formatElapsed(snap),
|
|
385
|
+
...(projections[index]!.artifactPath
|
|
386
|
+
? { fullResultSaved: true }
|
|
387
|
+
: {}),
|
|
388
|
+
...(projections[index]!.artifactSaveFailed
|
|
389
|
+
? { artifactSaveFailed: true }
|
|
390
|
+
: {}),
|
|
238
391
|
})),
|
|
239
392
|
};
|
|
240
393
|
pi.appendEntry<SubagentResultEntryData>("subagent-result", {
|
|
241
|
-
content,
|
|
394
|
+
content: displayContent,
|
|
242
395
|
details,
|
|
243
396
|
});
|
|
244
397
|
pi.sendMessage(
|
|
@@ -246,7 +399,7 @@ export function createSubagentResultDispatcher(
|
|
|
246
399
|
customType: "subagent-result",
|
|
247
400
|
content,
|
|
248
401
|
display: false,
|
|
249
|
-
details,
|
|
402
|
+
details: { ...details, displayContent },
|
|
250
403
|
},
|
|
251
404
|
{ deliverAs: "followUp", triggerTurn: true },
|
|
252
405
|
);
|
|
@@ -259,36 +412,52 @@ function renderSubagentResult(
|
|
|
259
412
|
content: string,
|
|
260
413
|
details: SubagentResultDetails,
|
|
261
414
|
expanded: boolean,
|
|
415
|
+
resultDisplay: DetailDisplay,
|
|
262
416
|
theme: SubagentResultTheme,
|
|
263
417
|
) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
418
|
+
const displayContent = sanitizeText(
|
|
419
|
+
details.displayContent ?? stripSubagentResultTransportInstruction(content),
|
|
420
|
+
);
|
|
421
|
+
const results = details.results?.length
|
|
422
|
+
? details.results
|
|
423
|
+
: details.id
|
|
424
|
+
? [
|
|
425
|
+
{
|
|
426
|
+
id: details.id,
|
|
427
|
+
title: details.title,
|
|
428
|
+
status: details.status,
|
|
429
|
+
outcome: details.outcome,
|
|
430
|
+
worktreeBranch: details.worktreeBranch,
|
|
431
|
+
elapsed: details.elapsed,
|
|
432
|
+
artifactSaveFailed: details.artifactSaveFailed,
|
|
433
|
+
fullResultSaved: details.fullResultSaved,
|
|
434
|
+
},
|
|
435
|
+
]
|
|
436
|
+
: [];
|
|
437
|
+
if (!expanded && resultDisplay === "compact") {
|
|
438
|
+
return renderWaitResultPreview(displayContent, { results }, theme);
|
|
277
439
|
}
|
|
278
440
|
|
|
279
|
-
const failed =
|
|
280
|
-
const
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
theme.fg("accent", theme.bold(`
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
441
|
+
const failed = results.some((result) => result.status === "error");
|
|
442
|
+
const batched = results.length > 1;
|
|
443
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
444
|
+
const header = batched
|
|
445
|
+
? `${icon} ${theme.fg("accent", theme.bold(`${results.length} subagents`))}${theme.fg("muted", ` · ${failed ? `${results.filter((result) => result.status === "error").length} failed` : "finished"}`)}`
|
|
446
|
+
: `${icon} ` +
|
|
447
|
+
theme.fg(
|
|
448
|
+
"accent",
|
|
449
|
+
theme.bold(`subagent ${sanitizeText(details.id ?? "?")}`),
|
|
450
|
+
) +
|
|
451
|
+
theme.fg(
|
|
452
|
+
"muted",
|
|
453
|
+
` · ${sanitizeText(details.title ?? "")} · ${failed ? "failed" : "finished"}${details.elapsed ? ` · ${sanitizeText(details.elapsed)}` : ""}`,
|
|
454
|
+
);
|
|
288
455
|
|
|
289
|
-
// Remove only the summary line.
|
|
290
|
-
//
|
|
291
|
-
const body =
|
|
456
|
+
// Remove only the single-result summary line. Error lines and batched result
|
|
457
|
+
// summaries are part of the display projection and must remain visible.
|
|
458
|
+
const body = batched
|
|
459
|
+
? displayContent.trim()
|
|
460
|
+
: displayContent.split("\n").slice(1).join("\n").trim();
|
|
292
461
|
const md = new Markdown(body, 0, 0, getMarkdownTheme());
|
|
293
462
|
const container = new Text(header, 0, 0);
|
|
294
463
|
return {
|
|
@@ -303,9 +472,23 @@ function renderSubagentResult(
|
|
|
303
472
|
};
|
|
304
473
|
}
|
|
305
474
|
|
|
306
|
-
|
|
475
|
+
interface SubagentExtensionOptions {
|
|
476
|
+
readonly getResultDisplay?: () => DetailDisplay;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export default function (
|
|
480
|
+
pi: ExtensionAPI,
|
|
481
|
+
options: SubagentExtensionOptions = {},
|
|
482
|
+
) {
|
|
483
|
+
const getResultDisplay =
|
|
484
|
+
options.getResultDisplay ??
|
|
485
|
+
(() => loadSetupConfig().ui.subagentResultDisplay);
|
|
307
486
|
let runtime: SubagentRuntime | undefined;
|
|
308
487
|
let managerPromise: Promise<SubagentManagerShape> | undefined;
|
|
488
|
+
let restoredIdCounters: SubagentIdCounters = {
|
|
489
|
+
modelCounter: 0,
|
|
490
|
+
btwCounter: 0,
|
|
491
|
+
};
|
|
309
492
|
let sessionContext: ExtensionContext | undefined;
|
|
310
493
|
let ui: ExtensionUIContext | undefined;
|
|
311
494
|
let unsubStatus: (() => void) | undefined;
|
|
@@ -315,13 +498,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
315
498
|
*/
|
|
316
499
|
let settledAcknowledgedAt = 0;
|
|
317
500
|
const stripState = new BelowEditorStripState();
|
|
501
|
+
const statusWriter = createStatusWriter("subagents");
|
|
318
502
|
const widgetKey = "subagent-navigation";
|
|
319
503
|
let navigationManager: SubagentManagerShape | undefined;
|
|
504
|
+
let unregisterWebCapability: (() => void) | undefined;
|
|
320
505
|
let widgetVisible = false;
|
|
506
|
+
let widgetEntryKey: string | undefined;
|
|
321
507
|
let requestWidgetRender: (() => void) | undefined;
|
|
322
508
|
let navigationLayerRegistered = false;
|
|
323
509
|
let dashboardOpen = false;
|
|
324
|
-
const dispatchResults = createSubagentResultDispatcher(
|
|
510
|
+
const dispatchResults = createSubagentResultDispatcher(
|
|
511
|
+
pi,
|
|
512
|
+
projectSubagentOutput,
|
|
513
|
+
() => sessionContext?.getContextUsage(),
|
|
514
|
+
);
|
|
325
515
|
const resultDelivery = createSubagentResultDelivery<SubagentSnapshot>({
|
|
326
516
|
isIdle: () => sessionContext?.isIdle() === true,
|
|
327
517
|
// Every unconsumed fire-and-forget result must reach the parent. The
|
|
@@ -329,23 +519,36 @@ export default function (pi: ExtensionAPI) {
|
|
|
329
519
|
deliver: dispatchResults,
|
|
330
520
|
});
|
|
331
521
|
pi.on("agent_settled", () => resultDelivery.parentSettled());
|
|
332
|
-
const
|
|
522
|
+
const registerStableToolFamily = () =>
|
|
333
523
|
patchOwnedTools(pi, "subagents", {
|
|
334
|
-
|
|
335
|
-
});
|
|
336
|
-
const showLifecycleTools = () =>
|
|
337
|
-
patchOwnedTools(pi, "subagents", {
|
|
338
|
-
enable: OPENPI_TOOL_SURFACE.subagents.deferred,
|
|
524
|
+
enable: OPENPI_TOOL_SURFACE.subagents.entry,
|
|
339
525
|
});
|
|
340
526
|
|
|
341
|
-
const getRuntime = () =>
|
|
527
|
+
const getRuntime = () =>
|
|
528
|
+
(runtime ??= createSubagentRuntime({
|
|
529
|
+
initialModelCounter: restoredIdCounters.modelCounter,
|
|
530
|
+
initialBtwCounter: restoredIdCounters.btwCounter,
|
|
531
|
+
}));
|
|
532
|
+
|
|
533
|
+
const persistId = (id: string) =>
|
|
534
|
+
pi.appendEntry(SUBAGENT_ID_WATERMARK_ENTRY_TYPE, subagentIdWatermark(id));
|
|
342
535
|
|
|
343
536
|
/** Resolve the manager service once per runtime and wire the extension hooks. */
|
|
344
537
|
const getManager = () => {
|
|
538
|
+
const scope = sessionContext?.sessionManager;
|
|
345
539
|
managerPromise ??= getRuntime()
|
|
346
540
|
.runPromise(SubagentManager)
|
|
347
541
|
.then((manager) => {
|
|
348
542
|
navigationManager = manager;
|
|
543
|
+
unregisterWebCapability?.();
|
|
544
|
+
unregisterWebCapability =
|
|
545
|
+
scope && sessionContext?.sessionManager === scope
|
|
546
|
+
? registerWebCapability(scope, {
|
|
547
|
+
kind: "subagents",
|
|
548
|
+
snapshot: () => projectSubagentCapability(manager.view.list()),
|
|
549
|
+
subscribe: (listener) => manager.view.subscribe(listener),
|
|
550
|
+
})
|
|
551
|
+
: undefined;
|
|
349
552
|
manager.view.setOnSettled(onSettled);
|
|
350
553
|
unsubStatus?.();
|
|
351
554
|
unsubStatus = manager.view.subscribe(() => updateStatus(manager));
|
|
@@ -366,10 +569,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
366
569
|
const updateSubagentWidget = () => {
|
|
367
570
|
const ctx = sessionContext;
|
|
368
571
|
if (!ctx || ctx.mode !== "tui") return;
|
|
369
|
-
const
|
|
370
|
-
|
|
572
|
+
const entry = stripEntry();
|
|
573
|
+
const visible = Boolean(entry);
|
|
574
|
+
const entryKey = subagentStripEntryKey(entry);
|
|
575
|
+
if (visible === widgetVisible) {
|
|
576
|
+
if (visible && entryKey !== widgetEntryKey) {
|
|
577
|
+
widgetEntryKey = entryKey;
|
|
578
|
+
requestWidgetRender?.();
|
|
579
|
+
}
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
371
582
|
if (!visible) {
|
|
372
583
|
stripState.focused = false;
|
|
584
|
+
widgetEntryKey = undefined;
|
|
373
585
|
requestWidgetRender = undefined;
|
|
374
586
|
ctx.ui.setWidget(widgetKey, undefined);
|
|
375
587
|
widgetVisible = false;
|
|
@@ -384,6 +596,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
384
596
|
{ placement: "belowEditor" },
|
|
385
597
|
);
|
|
386
598
|
widgetVisible = true;
|
|
599
|
+
widgetEntryKey = entryKey;
|
|
387
600
|
};
|
|
388
601
|
|
|
389
602
|
const updateStatus = (manager: SubagentManagerShape) => {
|
|
@@ -392,9 +605,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
392
605
|
manager.view.list(),
|
|
393
606
|
settledAcknowledgedAt,
|
|
394
607
|
);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
608
|
+
// In the TUI the below-editor strip already reports the same activity and
|
|
609
|
+
// carries the manage affordance, so a footer status line would repeat it.
|
|
610
|
+
const tui = sessionContext?.mode === "tui";
|
|
611
|
+
statusWriter.write(
|
|
612
|
+
ui,
|
|
613
|
+
!tui && hasActivity(counts)
|
|
398
614
|
? formatActivityStatus(ui.theme, "subagents", counts)
|
|
399
615
|
: undefined,
|
|
400
616
|
);
|
|
@@ -493,8 +709,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
493
709
|
};
|
|
494
710
|
|
|
495
711
|
pi.on("session_start", (_event, ctx) => {
|
|
712
|
+
restoredIdCounters = restoreSubagentIdCounters(
|
|
713
|
+
ctx.sessionManager.getBranch(),
|
|
714
|
+
);
|
|
496
715
|
refreshAgentTypes(ctx.cwd, ctx.isProjectTrusted());
|
|
497
|
-
|
|
716
|
+
registerStableToolFamily();
|
|
498
717
|
sessionContext = ctx;
|
|
499
718
|
settledAcknowledgedAt = 0;
|
|
500
719
|
if (ctx.hasUI) ui = ctx.ui;
|
|
@@ -524,16 +743,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
524
743
|
resultDelivery.clear();
|
|
525
744
|
unsubStatus?.();
|
|
526
745
|
unsubStatus = undefined;
|
|
746
|
+
unregisterWebCapability?.();
|
|
747
|
+
unregisterWebCapability = undefined;
|
|
527
748
|
try {
|
|
528
749
|
ui?.setStatus("subagents", undefined);
|
|
529
750
|
sessionContext?.ui.setWidget(widgetKey, undefined);
|
|
530
751
|
} catch {
|
|
531
752
|
// UI may already be disposed.
|
|
532
753
|
}
|
|
754
|
+
statusWriter.reset();
|
|
533
755
|
sessionContext = undefined;
|
|
534
756
|
ui = undefined;
|
|
535
757
|
navigationManager = undefined;
|
|
536
758
|
widgetVisible = false;
|
|
759
|
+
widgetEntryKey = undefined;
|
|
537
760
|
requestWidgetRender = undefined;
|
|
538
761
|
stripState.focused = false;
|
|
539
762
|
dashboardOpen = false;
|
|
@@ -592,57 +815,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
592
815
|
agentTypes = loaded.agentTypes;
|
|
593
816
|
agentTypeDiagnostics = loaded.diagnostics;
|
|
594
817
|
agentTypeList = [...agentTypes.values()];
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
subagentSpawnTool.parameters =
|
|
818
|
+
const surface = createSubagentSpawnToolSurface(agentTypeList);
|
|
819
|
+
subagentSpawnTool.description = surface.description;
|
|
820
|
+
subagentSpawnTool.parameters = surface.parameters;
|
|
598
821
|
registerSubagentSpawnTool();
|
|
599
822
|
};
|
|
600
823
|
|
|
601
824
|
// --- Tools -------------------------------------------------------------
|
|
602
825
|
|
|
603
|
-
const
|
|
604
|
-
Type.Object({
|
|
605
|
-
agent_type: createAgentTypeParameterSchema(agentTypeList),
|
|
606
|
-
prompt: Type.String({
|
|
607
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.prompt,
|
|
608
|
-
}),
|
|
609
|
-
name: Type.String({
|
|
610
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.name,
|
|
611
|
-
}),
|
|
612
|
-
harness: Type.Optional(
|
|
613
|
-
StringEnum(BACKEND_NAMES, {
|
|
614
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.harness,
|
|
615
|
-
}),
|
|
616
|
-
),
|
|
617
|
-
working_dir: Type.Optional(
|
|
618
|
-
Type.String({
|
|
619
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.workingDir,
|
|
620
|
-
}),
|
|
621
|
-
),
|
|
622
|
-
isolation: Type.Optional(
|
|
623
|
-
StringEnum(["worktree"] as const, {
|
|
624
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.isolation,
|
|
625
|
-
}),
|
|
626
|
-
),
|
|
627
|
-
model: Type.Optional(
|
|
628
|
-
Type.String({
|
|
629
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.model,
|
|
630
|
-
}),
|
|
631
|
-
),
|
|
632
|
-
reasoning_effort: Type.Optional(
|
|
633
|
-
StringEnum(REASONING_EFFORTS, {
|
|
634
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.reasoningEffort,
|
|
635
|
-
}),
|
|
636
|
-
),
|
|
637
|
-
});
|
|
826
|
+
const initialSpawnSurface = createSubagentSpawnToolSurface(agentTypeList);
|
|
638
827
|
|
|
639
828
|
const subagentSpawnTool = defineTool({
|
|
640
829
|
name: "subagent_spawn",
|
|
641
830
|
label: "Spawn Subagent",
|
|
642
|
-
|
|
831
|
+
...initialSpawnSurface,
|
|
643
832
|
promptSnippet: SUBAGENT_SPAWN_PROMPT_SNIPPET,
|
|
644
833
|
promptGuidelines: SUBAGENT_SPAWN_PROMPT_GUIDELINES,
|
|
645
|
-
parameters: createSubagentSpawnParameters(),
|
|
646
834
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
647
835
|
// Only one backend exists; harness is optional and defaults to it.
|
|
648
836
|
const harness = params.harness ?? BACKEND_NAMES[0];
|
|
@@ -764,11 +952,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
764
952
|
} catch (error) {
|
|
765
953
|
// The session scope owns reclamation, but it never opened, so this
|
|
766
954
|
// worktree would otherwise be orphaned on disk.
|
|
767
|
-
if (worktree)
|
|
955
|
+
if (worktree) {
|
|
956
|
+
const spawnError =
|
|
957
|
+
error instanceof Error ? error.message : String(error);
|
|
958
|
+
let cleanupWarning: string | undefined;
|
|
959
|
+
let cleanupError: unknown;
|
|
960
|
+
try {
|
|
961
|
+
const cleanup = await reclaimWorktree(cwd, worktree);
|
|
962
|
+
cleanupWarning = formatWorktreeCleanupWarning(
|
|
963
|
+
cleanup,
|
|
964
|
+
worktree.path,
|
|
965
|
+
);
|
|
966
|
+
} catch (failure) {
|
|
967
|
+
cleanupError = failure;
|
|
968
|
+
}
|
|
969
|
+
if (cleanupError) {
|
|
970
|
+
const reason =
|
|
971
|
+
cleanupError instanceof Error
|
|
972
|
+
? cleanupError.message
|
|
973
|
+
: String(cleanupError);
|
|
974
|
+
throw new Error(
|
|
975
|
+
`${spawnError}; worktree cleanup failed: ${reason}; checkout preserved at ${worktree.path}`,
|
|
976
|
+
{ cause: error },
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
if (cleanupWarning) {
|
|
980
|
+
throw new Error(
|
|
981
|
+
`${spawnError}; worktree cleanup warning: ${cleanupWarning}`,
|
|
982
|
+
{ cause: error },
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
768
986
|
throw error;
|
|
769
987
|
}
|
|
770
|
-
|
|
771
|
-
showLifecycleTools();
|
|
988
|
+
persistId(snap.id);
|
|
772
989
|
|
|
773
990
|
return {
|
|
774
991
|
content: [
|
|
@@ -813,8 +1030,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
813
1030
|
const meta = [details.harness, details.model]
|
|
814
1031
|
.filter(Boolean)
|
|
815
1032
|
.join(" \u00b7 ");
|
|
1033
|
+
// A spawn is an event, not a state, so it speaks in the activity rows'
|
|
1034
|
+
// verb language ("Wrote" / "Ran" / "Spawned") rather than a glyph; the
|
|
1035
|
+
// strip's spinner carries the running state from here on.
|
|
816
1036
|
return new Text(
|
|
817
|
-
`${theme.fg("
|
|
1037
|
+
`${theme.fg("toolTitle", "Spawned")} ${theme.bold(details.title ?? details.id)} ${theme.fg("dim", meta)}`,
|
|
818
1038
|
0,
|
|
819
1039
|
0,
|
|
820
1040
|
);
|
|
@@ -834,7 +1054,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
834
1054
|
description: SUBAGENT_WAIT_PARAMETER_DESCRIPTIONS.ids,
|
|
835
1055
|
}),
|
|
836
1056
|
}),
|
|
837
|
-
async execute(_toolCallId, params, signal, onUpdate) {
|
|
1057
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
838
1058
|
const manager = await getManager();
|
|
839
1059
|
const ids = [...new Set(params.ids)];
|
|
840
1060
|
if (ids.length === 0)
|
|
@@ -870,33 +1090,71 @@ export default function (pi: ExtensionAPI) {
|
|
|
870
1090
|
// deferred automatic delivery now that the tool is returning the result.
|
|
871
1091
|
resultDelivery.consume(ids);
|
|
872
1092
|
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
1093
|
+
const entries: Array<
|
|
1094
|
+
| { readonly id: string; readonly section: string }
|
|
1095
|
+
| {
|
|
1096
|
+
readonly id: string;
|
|
1097
|
+
readonly snap: SubagentSnapshot;
|
|
1098
|
+
readonly header: string;
|
|
1099
|
+
}
|
|
1100
|
+
> = ids.map((id) => {
|
|
876
1101
|
const snap = manager.view.get(id);
|
|
877
|
-
if (!snap) {
|
|
878
|
-
sections.push(`## ${id}\n\n(no longer tracked)`);
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
1102
|
+
if (!snap) return { id, section: `## ${id}\n\n(no longer tracked)` };
|
|
881
1103
|
const verb = snap.status === "error" ? "failed" : "finished";
|
|
882
|
-
let
|
|
883
|
-
if (snap.errorText)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1104
|
+
let header = `## ${snap.id} "${snap.title}" ${verb}`;
|
|
1105
|
+
if (snap.errorText) header += `\nError: ${snap.errorText}`;
|
|
1106
|
+
return { id, snap, header };
|
|
1107
|
+
});
|
|
1108
|
+
const separatorsBytes = Math.max(0, entries.length - 1) * 7;
|
|
1109
|
+
const fixedBytes =
|
|
1110
|
+
separatorsBytes +
|
|
1111
|
+
entries.reduce(
|
|
1112
|
+
(sum, entry) =>
|
|
1113
|
+
sum +
|
|
1114
|
+
Buffer.byteLength(
|
|
1115
|
+
"section" in entry ? entry.section : `${entry.header}\n\n`,
|
|
1116
|
+
"utf8",
|
|
1117
|
+
),
|
|
1118
|
+
0,
|
|
888
1119
|
);
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1120
|
+
const resultEntries = entries.filter(
|
|
1121
|
+
(
|
|
1122
|
+
entry,
|
|
1123
|
+
): entry is {
|
|
1124
|
+
readonly id: string;
|
|
1125
|
+
readonly snap: SubagentSnapshot;
|
|
1126
|
+
readonly header: string;
|
|
1127
|
+
} => "snap" in entry,
|
|
1128
|
+
);
|
|
1129
|
+
const projectionBatchBytes = Math.max(
|
|
1130
|
+
WAIT_MIN_RESULT_BYTES * resultEntries.length,
|
|
1131
|
+
WAIT_OUTPUT_MAX_BYTES - fixedBytes,
|
|
1132
|
+
);
|
|
1133
|
+
const allocation = allocateResultBudgets(
|
|
1134
|
+
resultEntries.map(({ snap }) =>
|
|
1135
|
+
Buffer.byteLength(snap.finalText || "(no output)", "utf8"),
|
|
1136
|
+
),
|
|
1137
|
+
ctx.getContextUsage(),
|
|
1138
|
+
{
|
|
1139
|
+
maxBatchBytes: projectionBatchBytes,
|
|
1140
|
+
maxResultBytes: WAIT_PER_AGENT_MAX_BYTES,
|
|
1141
|
+
minResultBytes: WAIT_MIN_RESULT_BYTES,
|
|
1142
|
+
headroomShare: RESULT_HEADROOM_SHARE,
|
|
1143
|
+
estimatedBytesPerToken: ESTIMATED_BYTES_PER_TOKEN,
|
|
1144
|
+
fixedBytes,
|
|
1145
|
+
},
|
|
1146
|
+
);
|
|
1147
|
+
let resultIndex = 0;
|
|
1148
|
+
const artifactSaveFailures = new Set<string>();
|
|
1149
|
+
const fullResultsSaved = new Set<string>();
|
|
1150
|
+
const sections = entries.map((entry) => {
|
|
1151
|
+
if ("section" in entry) return entry.section;
|
|
1152
|
+
const outputBudget = allocation.budgets[resultIndex++]!;
|
|
1153
|
+
const projection = projectSubagentOutput(entry.snap, outputBudget);
|
|
1154
|
+
if (projection.artifactSaveFailed) artifactSaveFailures.add(entry.id);
|
|
1155
|
+
if (projection.artifactPath) fullResultsSaved.add(entry.id);
|
|
1156
|
+
return `${entry.header}\n\n${projection.text}`;
|
|
1157
|
+
});
|
|
900
1158
|
|
|
901
1159
|
const combined = sections.join("\n\n---\n\n");
|
|
902
1160
|
const bounded = truncateHead(combined, {
|
|
@@ -911,7 +1169,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
911
1169
|
details: {
|
|
912
1170
|
results: ids.map((id) => {
|
|
913
1171
|
const snap = manager.view.get(id);
|
|
914
|
-
return {
|
|
1172
|
+
return {
|
|
1173
|
+
id,
|
|
1174
|
+
title: snap?.title,
|
|
1175
|
+
status: snap?.status,
|
|
1176
|
+
...(snap?.outcome ? { outcome: snap.outcome } : {}),
|
|
1177
|
+
...(snap?.worktreeBranch
|
|
1178
|
+
? { worktreeBranch: snap.worktreeBranch }
|
|
1179
|
+
: {}),
|
|
1180
|
+
...(snap ? { elapsed: formatElapsed(snap) } : {}),
|
|
1181
|
+
...(fullResultsSaved.has(id) ? { fullResultSaved: true } : {}),
|
|
1182
|
+
...(artifactSaveFailures.has(id)
|
|
1183
|
+
? { artifactSaveFailed: true }
|
|
1184
|
+
: {}),
|
|
1185
|
+
};
|
|
915
1186
|
}),
|
|
916
1187
|
},
|
|
917
1188
|
};
|
|
@@ -936,7 +1207,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
936
1207
|
return renderWaitResult(
|
|
937
1208
|
content,
|
|
938
1209
|
result.details as WaitResultDetails | undefined,
|
|
939
|
-
expanded ||
|
|
1210
|
+
expanded || getResultDisplay() === "full",
|
|
940
1211
|
theme,
|
|
941
1212
|
);
|
|
942
1213
|
},
|
|
@@ -1131,6 +1402,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1131
1402
|
content,
|
|
1132
1403
|
(message.details ?? {}) as SubagentResultDetails,
|
|
1133
1404
|
expanded,
|
|
1405
|
+
getResultDisplay(),
|
|
1134
1406
|
theme,
|
|
1135
1407
|
);
|
|
1136
1408
|
},
|
|
@@ -1143,6 +1415,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1143
1415
|
entry.data?.content ?? "",
|
|
1144
1416
|
entry.data?.details ?? {},
|
|
1145
1417
|
expanded,
|
|
1418
|
+
getResultDisplay(),
|
|
1146
1419
|
theme,
|
|
1147
1420
|
),
|
|
1148
1421
|
);
|
|
@@ -1152,11 +1425,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1152
1425
|
(entry, _options, theme) => {
|
|
1153
1426
|
const data = entry.data;
|
|
1154
1427
|
const failed = data?.status === "error";
|
|
1428
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
1155
1429
|
return new Text(
|
|
1156
|
-
`${theme.fg(
|
|
1430
|
+
`${icon} ${theme.fg("accent", data?.title ?? "?")}` +
|
|
1157
1431
|
theme.fg(
|
|
1158
|
-
"
|
|
1159
|
-
`
|
|
1432
|
+
"dim",
|
|
1433
|
+
` ${failed ? "failed" : "finished"} · ${data?.elapsed ?? "?"}`,
|
|
1160
1434
|
),
|
|
1161
1435
|
1,
|
|
1162
1436
|
0,
|
|
@@ -1169,7 +1443,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1169
1443
|
(entry, { expanded }, theme) => {
|
|
1170
1444
|
const data = entry.data;
|
|
1171
1445
|
const failed = data?.status === "error";
|
|
1172
|
-
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "
|
|
1446
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
1173
1447
|
const header =
|
|
1174
1448
|
`${icon} ` +
|
|
1175
1449
|
theme.fg("accent", theme.bold(`by the way · ${data?.title ?? "?"}`)) +
|
|
@@ -1184,7 +1458,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1184
1458
|
.filter(Boolean)
|
|
1185
1459
|
.join("\n\n");
|
|
1186
1460
|
|
|
1187
|
-
if (expanded ||
|
|
1461
|
+
if (expanded || getResultDisplay() === "full") {
|
|
1188
1462
|
const md = new Markdown(body, 0, 0, getMarkdownTheme());
|
|
1189
1463
|
const container = new Text(header, 0, 0);
|
|
1190
1464
|
return {
|
|
@@ -1253,6 +1527,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1253
1527
|
);
|
|
1254
1528
|
return;
|
|
1255
1529
|
}
|
|
1530
|
+
persistId(snap.id);
|
|
1256
1531
|
|
|
1257
1532
|
await openSubagentTakeover(ctx, manager.view, snap.id, {
|
|
1258
1533
|
badge: "by the way",
|