@tt-a1i/openpi 0.3.0 → 0.4.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 +87 -24
- package/SETUP.md +3 -3
- package/extensions/ask-user/index.ts +30 -14
- package/extensions/background-terminals/src/prompt.ts +1 -1
- package/extensions/background-terminals/src/ui/ps.ts +132 -129
- package/extensions/capabilities/index.ts +35 -44
- package/extensions/capabilities/src/ui.ts +93 -0
- package/extensions/file-mutation-display/index.ts +34 -76
- package/extensions/file-mutation-display/render.ts +387 -88
- package/extensions/file-search/index.ts +8 -7
- package/extensions/file-search/src/binaries.ts +18 -18
- package/extensions/git-info/src/changed-files-view.ts +47 -14
- package/extensions/git-read/index.ts +330 -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/sessions/index.ts +70 -55
- package/extensions/setup/index.ts +6 -6
- package/extensions/shared/activity-status.ts +6 -5
- package/extensions/shared/below-editor-navigation.ts +26 -0
- package/extensions/shared/capability-intent.ts +53 -0
- package/extensions/shared/child-session.ts +7 -1
- package/extensions/shared/result-budget.ts +134 -0
- package/extensions/shared/screen-chrome.ts +133 -0
- package/extensions/shared/setup-config.ts +24 -5
- package/extensions/shared/spinner.ts +28 -0
- package/extensions/shared/text-projection.ts +56 -0
- package/extensions/shared/tool-surface.ts +13 -6
- package/extensions/subagents/index.ts +216 -170
- package/extensions/subagents/navigation.ts +52 -23
- package/extensions/subagents/src/agent-types.ts +37 -15
- package/extensions/subagents/src/backends/stub.ts +7 -0
- package/extensions/subagents/src/id-sequence.ts +84 -0
- package/extensions/subagents/src/manager.ts +620 -537
- package/extensions/subagents/src/prompt.ts +153 -38
- package/extensions/subagents/src/result-artifact.ts +142 -0
- package/extensions/subagents/src/result-delivery.ts +50 -5
- package/extensions/subagents/src/runtime.ts +8 -5
- package/extensions/subagents/src/ui/takeover.ts +84 -109
- package/extensions/subagents/src/ui/transcript.ts +76 -42
- package/extensions/subagents/src/ui/wait-result.ts +1 -1
- package/extensions/tasks/ui.ts +79 -62
- package/extensions/ui-customization/footer.ts +7 -4
- package/extensions/user-input-fold/index.ts +185 -0
- package/extensions/workflows/artifacts.ts +35 -0
- package/extensions/workflows/controller.ts +14 -2
- package/extensions/workflows/coordinator.ts +64 -0
- package/extensions/workflows/dashboard.ts +353 -173
- package/extensions/workflows/handoff.ts +62 -20
- package/extensions/workflows/index.ts +647 -387
- package/extensions/workflows/model.ts +57 -15
- package/extensions/workflows/navigation.ts +33 -14
- package/extensions/workflows/prompt.ts +104 -8
- package/extensions/workflows/replay-safety.ts +16 -6
- package/extensions/workflows/result-delivery.ts +189 -0
- package/extensions/workflows/sandbox-child.cjs +11 -0
- package/package.json +1 -1
- package/skills/subagents/SKILL.md +2 -2
- package/skills/workflows/REFERENCE.md +7 -4
- package/skills/workflows/SKILL.md +53 -10
- package/extensions/subagents/src/format.ts +0 -48
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
|
|
26
26
|
import * as fs from "node:fs";
|
|
27
27
|
import * as path from "node:path";
|
|
28
|
-
import { StringEnum } from "@earendil-works/pi-ai";
|
|
29
28
|
import type {
|
|
30
29
|
ExtensionAPI,
|
|
31
30
|
ExtensionCommandContext,
|
|
@@ -37,7 +36,6 @@ import {
|
|
|
37
36
|
DEFAULT_MAX_BYTES,
|
|
38
37
|
DEFAULT_MAX_LINES,
|
|
39
38
|
defineTool,
|
|
40
|
-
formatSize,
|
|
41
39
|
getAgentDir,
|
|
42
40
|
getMarkdownTheme,
|
|
43
41
|
keyHint,
|
|
@@ -46,41 +44,70 @@ import {
|
|
|
46
44
|
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
47
45
|
import { Type } from "typebox";
|
|
48
46
|
import {
|
|
47
|
+
formatActivityStatus,
|
|
48
|
+
hasActivity,
|
|
49
|
+
unreadActivityCounts,
|
|
50
|
+
} from "../shared/activity-status.ts";
|
|
51
|
+
import {
|
|
52
|
+
BelowEditorNavigationEditor,
|
|
53
|
+
BelowEditorStripState,
|
|
54
|
+
} from "../shared/below-editor-navigation.ts";
|
|
55
|
+
import {
|
|
56
|
+
effectiveChildToolAllowlist,
|
|
57
|
+
resolveStandaloneChildProjectTrust,
|
|
58
|
+
} from "../shared/child-session.ts";
|
|
59
|
+
import { formatContextUtilization } from "../shared/context-utilization.ts";
|
|
60
|
+
import {
|
|
61
|
+
registerEditorLayer,
|
|
62
|
+
removeEditorLayer,
|
|
63
|
+
} from "../shared/editor-layers.ts";
|
|
64
|
+
import {
|
|
65
|
+
PLAN_MODE_CHANNEL,
|
|
66
|
+
type PlanModeState,
|
|
67
|
+
planModeAllowsDeclaredTools,
|
|
68
|
+
planModeChildTools,
|
|
69
|
+
} from "../shared/plan-mode-state.ts";
|
|
70
|
+
import { loadSetupConfig } from "../shared/setup-config.ts";
|
|
71
|
+
import {
|
|
72
|
+
OPENPI_TOOL_SURFACE,
|
|
73
|
+
patchOwnedTools,
|
|
74
|
+
} from "../shared/tool-surface.ts";
|
|
75
|
+
import {
|
|
76
|
+
createWorktree,
|
|
77
|
+
reclaimWorktree,
|
|
78
|
+
type Worktree,
|
|
79
|
+
} from "../shared/worktree.ts";
|
|
80
|
+
import {
|
|
81
|
+
normalizeSubagentTitle,
|
|
82
|
+
SubagentStripWidget,
|
|
83
|
+
selectSubagentStripEntry,
|
|
84
|
+
} from "./navigation.ts";
|
|
85
|
+
import {
|
|
86
|
+
type AgentType,
|
|
49
87
|
formatAgentTypeDiagnostics,
|
|
50
88
|
loadAgentTypes,
|
|
51
89
|
roleModelForAgentType,
|
|
52
90
|
selectSubagentModel,
|
|
53
|
-
type AgentType,
|
|
54
91
|
} from "./src/agent-types.ts";
|
|
55
92
|
import { deriveBtwTitle, isModelVisible } from "./src/by-the-way.ts";
|
|
56
93
|
import {
|
|
57
94
|
BACKEND_NAMES,
|
|
58
95
|
formatElapsed,
|
|
59
96
|
latestText,
|
|
60
|
-
REASONING_EFFORTS,
|
|
61
97
|
type SubagentSnapshot,
|
|
62
98
|
} from "./src/domain.ts";
|
|
63
99
|
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";
|
|
100
|
+
restoreSubagentIdCounters,
|
|
101
|
+
SUBAGENT_ID_WATERMARK_ENTRY_TYPE,
|
|
102
|
+
type SubagentIdCounters,
|
|
103
|
+
subagentIdWatermark,
|
|
104
|
+
} from "./src/id-sequence.ts";
|
|
77
105
|
import { SubagentManager, type SubagentManagerShape } from "./src/manager.ts";
|
|
78
106
|
import {
|
|
79
107
|
buildSubagentResultMessage,
|
|
80
|
-
createAgentTypeParameterSchema,
|
|
81
108
|
buildSubagentSendResult,
|
|
82
109
|
buildSubagentSpawnResult,
|
|
83
|
-
|
|
110
|
+
createSubagentSpawnToolSurface,
|
|
84
111
|
SUBAGENT_CANCEL_PARAMETER_DESCRIPTIONS,
|
|
85
112
|
SUBAGENT_CANCEL_TOOL_DESCRIPTION,
|
|
86
113
|
SUBAGENT_CHECK_PARAMETER_DESCRIPTIONS,
|
|
@@ -88,45 +115,22 @@ import {
|
|
|
88
115
|
SUBAGENT_LIST_TOOL_DESCRIPTION,
|
|
89
116
|
SUBAGENT_SEND_PARAMETER_DESCRIPTIONS,
|
|
90
117
|
SUBAGENT_SEND_TOOL_DESCRIPTION,
|
|
91
|
-
SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS,
|
|
92
118
|
SUBAGENT_SPAWN_PROMPT_GUIDELINES,
|
|
93
119
|
SUBAGENT_SPAWN_PROMPT_SNIPPET,
|
|
94
|
-
SUBAGENT_SPAWN_TOOL_DESCRIPTION,
|
|
95
120
|
SUBAGENT_WAIT_PARAMETER_DESCRIPTIONS,
|
|
96
121
|
SUBAGENT_WAIT_TOOL_DESCRIPTION,
|
|
97
122
|
} from "./src/prompt.ts";
|
|
98
|
-
import {
|
|
99
|
-
import { resultDeliveryOptions } from "../background-terminals/src/result-delivery.ts";
|
|
100
|
-
import {
|
|
101
|
-
effectiveChildToolAllowlist,
|
|
102
|
-
resolveStandaloneChildProjectTrust,
|
|
103
|
-
} from "../shared/child-session.ts";
|
|
123
|
+
import { persistResultArtifact, projectResult } from "./src/result-artifact.ts";
|
|
104
124
|
import {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} from "../shared/
|
|
108
|
-
import {
|
|
109
|
-
import {
|
|
110
|
-
PLAN_MODE_CHANNEL,
|
|
111
|
-
planModeAllowsDeclaredTools,
|
|
112
|
-
planModeChildTools,
|
|
113
|
-
type PlanModeState,
|
|
114
|
-
} from "../shared/plan-mode-state.ts";
|
|
115
|
-
import {
|
|
116
|
-
createWorktree,
|
|
117
|
-
reclaimWorktree,
|
|
118
|
-
type Worktree,
|
|
119
|
-
} from "../shared/worktree.ts";
|
|
125
|
+
allocateResultBudgets,
|
|
126
|
+
type ParentContextUsage,
|
|
127
|
+
} from "../shared/result-budget.ts";
|
|
128
|
+
import { createSubagentResultDelivery } from "./src/result-delivery.ts";
|
|
120
129
|
import {
|
|
121
130
|
createSubagentRuntime,
|
|
122
131
|
runTool,
|
|
123
132
|
type SubagentRuntime,
|
|
124
133
|
} from "./src/runtime.ts";
|
|
125
|
-
import {
|
|
126
|
-
normalizeSubagentTitle,
|
|
127
|
-
selectSubagentStripEntry,
|
|
128
|
-
SubagentStripWidget,
|
|
129
|
-
} from "./navigation.ts";
|
|
130
134
|
import { openSubagentPicker, openSubagentTakeover } from "./src/ui/takeover.ts";
|
|
131
135
|
import {
|
|
132
136
|
buildWaitResultPreview,
|
|
@@ -135,8 +139,13 @@ import {
|
|
|
135
139
|
} from "./src/ui/wait-result.ts";
|
|
136
140
|
|
|
137
141
|
const SUBAGENT_OUTPUT_MAX_BYTES = 24 * 1024;
|
|
142
|
+
const AUTOMATIC_OUTPUT_MAX_BYTES = 48 * 1024;
|
|
143
|
+
const AUTOMATIC_MIN_RESULT_BYTES = 2 * 1024;
|
|
138
144
|
const WAIT_OUTPUT_MAX_BYTES = 48 * 1024;
|
|
139
145
|
const WAIT_PER_AGENT_MAX_BYTES = 16 * 1024;
|
|
146
|
+
const WAIT_MIN_RESULT_BYTES = 512;
|
|
147
|
+
const RESULT_HEADROOM_SHARE = 0.5;
|
|
148
|
+
const ESTIMATED_BYTES_PER_TOKEN = 4;
|
|
140
149
|
|
|
141
150
|
interface SpawnResultDetails {
|
|
142
151
|
readonly id?: string;
|
|
@@ -190,36 +199,71 @@ function describeSubagent(snap: SubagentSnapshot) {
|
|
|
190
199
|
return `${snap.id} [${snap.status}] "${snap.title}" (${details.join(", ")})`;
|
|
191
200
|
}
|
|
192
201
|
|
|
193
|
-
function truncatedOutput(
|
|
202
|
+
export function truncatedOutput(
|
|
194
203
|
snap: SubagentSnapshot,
|
|
195
204
|
maxBytes = SUBAGENT_OUTPUT_MAX_BYTES,
|
|
205
|
+
writeArtifact: (content: string) => string = (content) =>
|
|
206
|
+
persistResultArtifact(getAgentDir(), content),
|
|
196
207
|
): string {
|
|
197
208
|
const output = snap.finalText || "(no output)";
|
|
198
|
-
|
|
209
|
+
return projectResult(output, {
|
|
199
210
|
maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
|
|
200
211
|
maxLines: Math.min(600, DEFAULT_MAX_LINES),
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (truncation.truncated) {
|
|
204
|
-
text += `\n\n[Output truncated: ${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)} shown. Full transcript in session file: ${snap.meta.sessionFilePath ?? "?"}]`;
|
|
205
|
-
}
|
|
206
|
-
return text;
|
|
212
|
+
writeArtifact,
|
|
213
|
+
}).text;
|
|
207
214
|
}
|
|
208
215
|
|
|
209
216
|
export function createSubagentResultDispatcher(
|
|
210
217
|
pi: ExtensionAPI,
|
|
211
|
-
outputFor: (
|
|
218
|
+
outputFor: (
|
|
219
|
+
snap: SubagentSnapshot,
|
|
220
|
+
maxBytes: number,
|
|
221
|
+
) => string = truncatedOutput,
|
|
222
|
+
getContextUsage: () => ParentContextUsage | undefined = () => undefined,
|
|
212
223
|
) {
|
|
213
|
-
return (snaps: readonly SubagentSnapshot[]
|
|
224
|
+
return (snaps: readonly SubagentSnapshot[]) => {
|
|
214
225
|
if (snaps.length === 0) return;
|
|
226
|
+
const emptyMessages = snaps.map((snap) =>
|
|
227
|
+
buildSubagentResultMessage({
|
|
228
|
+
id: snap.id,
|
|
229
|
+
title: snap.title,
|
|
230
|
+
status: snap.status,
|
|
231
|
+
errorText: snap.errorText,
|
|
232
|
+
output: "",
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
const wrapperBytes =
|
|
236
|
+
emptyMessages.reduce(
|
|
237
|
+
(sum, message) => sum + Buffer.byteLength(message, "utf8"),
|
|
238
|
+
0,
|
|
239
|
+
) +
|
|
240
|
+
Math.max(0, snaps.length - 1) * 2;
|
|
241
|
+
const projectionBatchBytes = Math.max(
|
|
242
|
+
AUTOMATIC_MIN_RESULT_BYTES * snaps.length,
|
|
243
|
+
AUTOMATIC_OUTPUT_MAX_BYTES - wrapperBytes,
|
|
244
|
+
);
|
|
245
|
+
const allocation = allocateResultBudgets(
|
|
246
|
+
snaps.map((snap) =>
|
|
247
|
+
Buffer.byteLength(snap.finalText || "(no output)", "utf8"),
|
|
248
|
+
),
|
|
249
|
+
getContextUsage(),
|
|
250
|
+
{
|
|
251
|
+
maxBatchBytes: projectionBatchBytes,
|
|
252
|
+
maxResultBytes: SUBAGENT_OUTPUT_MAX_BYTES,
|
|
253
|
+
minResultBytes: AUTOMATIC_MIN_RESULT_BYTES,
|
|
254
|
+
headroomShare: RESULT_HEADROOM_SHARE,
|
|
255
|
+
estimatedBytesPerToken: ESTIMATED_BYTES_PER_TOKEN,
|
|
256
|
+
fixedBytes: wrapperBytes,
|
|
257
|
+
},
|
|
258
|
+
);
|
|
215
259
|
const content = snaps
|
|
216
|
-
.map((snap) =>
|
|
260
|
+
.map((snap, index) =>
|
|
217
261
|
buildSubagentResultMessage({
|
|
218
262
|
id: snap.id,
|
|
219
263
|
title: snap.title,
|
|
220
264
|
status: snap.status,
|
|
221
265
|
errorText: snap.errorText,
|
|
222
|
-
output: outputFor(snap),
|
|
266
|
+
output: outputFor(snap, allocation.budgets[index]!),
|
|
223
267
|
}),
|
|
224
268
|
)
|
|
225
269
|
.join("\n\n");
|
|
@@ -249,7 +293,7 @@ export function createSubagentResultDispatcher(
|
|
|
249
293
|
display: false,
|
|
250
294
|
details,
|
|
251
295
|
},
|
|
252
|
-
|
|
296
|
+
{ deliverAs: "followUp", triggerTurn: true },
|
|
253
297
|
);
|
|
254
298
|
};
|
|
255
299
|
}
|
|
@@ -278,7 +322,7 @@ function renderSubagentResult(
|
|
|
278
322
|
}
|
|
279
323
|
|
|
280
324
|
const failed = details.status === "error";
|
|
281
|
-
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "
|
|
325
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
282
326
|
const header =
|
|
283
327
|
`${icon} ` +
|
|
284
328
|
theme.fg("accent", theme.bold(`subagent ${details.id ?? "?"}`)) +
|
|
@@ -307,6 +351,10 @@ function renderSubagentResult(
|
|
|
307
351
|
export default function (pi: ExtensionAPI) {
|
|
308
352
|
let runtime: SubagentRuntime | undefined;
|
|
309
353
|
let managerPromise: Promise<SubagentManagerShape> | undefined;
|
|
354
|
+
let restoredIdCounters: SubagentIdCounters = {
|
|
355
|
+
modelCounter: 0,
|
|
356
|
+
btwCounter: 0,
|
|
357
|
+
};
|
|
310
358
|
let sessionContext: ExtensionContext | undefined;
|
|
311
359
|
let ui: ExtensionUIContext | undefined;
|
|
312
360
|
let unsubStatus: (() => void) | undefined;
|
|
@@ -322,18 +370,31 @@ export default function (pi: ExtensionAPI) {
|
|
|
322
370
|
let requestWidgetRender: (() => void) | undefined;
|
|
323
371
|
let navigationLayerRegistered = false;
|
|
324
372
|
let dashboardOpen = false;
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
373
|
+
const dispatchResults = createSubagentResultDispatcher(
|
|
374
|
+
pi,
|
|
375
|
+
truncatedOutput,
|
|
376
|
+
() => sessionContext?.getContextUsage(),
|
|
377
|
+
);
|
|
378
|
+
const resultDelivery = createSubagentResultDelivery<SubagentSnapshot>({
|
|
379
|
+
isIdle: () => sessionContext?.isIdle() === true,
|
|
380
|
+
// Every unconsumed fire-and-forget result must reach the parent. The
|
|
381
|
+
// delivery coordinator batches results that settled while it was busy.
|
|
382
|
+
deliver: dispatchResults,
|
|
383
|
+
});
|
|
384
|
+
pi.on("agent_settled", () => resultDelivery.parentSettled());
|
|
385
|
+
const registerStableToolFamily = () =>
|
|
332
386
|
patchOwnedTools(pi, "subagents", {
|
|
333
|
-
enable: OPENPI_TOOL_SURFACE.subagents.
|
|
387
|
+
enable: OPENPI_TOOL_SURFACE.subagents.entry,
|
|
334
388
|
});
|
|
335
389
|
|
|
336
|
-
const getRuntime = () =>
|
|
390
|
+
const getRuntime = () =>
|
|
391
|
+
(runtime ??= createSubagentRuntime({
|
|
392
|
+
initialModelCounter: restoredIdCounters.modelCounter,
|
|
393
|
+
initialBtwCounter: restoredIdCounters.btwCounter,
|
|
394
|
+
}));
|
|
395
|
+
|
|
396
|
+
const persistId = (id: string) =>
|
|
397
|
+
pi.appendEntry(SUBAGENT_ID_WATERMARK_ENTRY_TYPE, subagentIdWatermark(id));
|
|
337
398
|
|
|
338
399
|
/** Resolve the manager service once per runtime and wire the extension hooks. */
|
|
339
400
|
const getManager = () => {
|
|
@@ -387,9 +448,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
387
448
|
manager.view.list(),
|
|
388
449
|
settledAcknowledgedAt,
|
|
389
450
|
);
|
|
451
|
+
// In the TUI the below-editor strip already reports the same activity and
|
|
452
|
+
// carries the manage affordance, so a footer status line would repeat it.
|
|
453
|
+
const tui = sessionContext?.mode === "tui";
|
|
390
454
|
ui.setStatus(
|
|
391
455
|
"subagents",
|
|
392
|
-
hasActivity(counts)
|
|
456
|
+
!tui && hasActivity(counts)
|
|
393
457
|
? formatActivityStatus(ui.theme, "subagents", counts)
|
|
394
458
|
: undefined,
|
|
395
459
|
);
|
|
@@ -436,25 +500,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
436
500
|
navigationLayerRegistered = true;
|
|
437
501
|
};
|
|
438
502
|
|
|
439
|
-
/**
|
|
440
|
-
* `wake` decides whether this costs the model a turn. A subagent that
|
|
441
|
-
* settled while the model sits idle is the result it is waiting on. A
|
|
442
|
-
* backlog that piled up while it worked is not: waking once per stale
|
|
443
|
-
* subagent forces a turn each, and the model can only answer "that one
|
|
444
|
-
* already finished". `nextTurn` still enters context with the user's next
|
|
445
|
-
* message, without demanding a reply.
|
|
446
|
-
*/
|
|
447
|
-
const deliverResults = (
|
|
448
|
-
snaps: readonly SubagentSnapshot[],
|
|
449
|
-
wake: boolean,
|
|
450
|
-
) => {
|
|
451
|
-
dispatchResults(snaps, wake);
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
const flushResults = (wake: boolean) => {
|
|
455
|
-
deliverResults(resultDelivery.drain(), wake);
|
|
456
|
-
};
|
|
457
|
-
|
|
458
503
|
const deliverBtwResult = (snap: SubagentSnapshot) => {
|
|
459
504
|
// appendEntry is a synchronous SessionManager operation and emits an
|
|
460
505
|
// entry_appended event, so it is safe while the parent is streaming and
|
|
@@ -500,15 +545,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
500
545
|
// subagent_wait can consume it before agent_settled flushes follow-ups.
|
|
501
546
|
// Defer a copy: the live snapshot keeps mutating if the subagent is
|
|
502
547
|
// restarted before the deferred result flushes.
|
|
548
|
+
// The delivery coordinator closes both sides of the wake-up race: it
|
|
549
|
+
// flushes now if the parent is already idle, otherwise the parent's next
|
|
550
|
+
// agent_settled edge rechecks this same pending Map.
|
|
503
551
|
resultDelivery.defer({ ...snap, meta: { ...snap.meta } });
|
|
504
|
-
// Settled while the model sits idle: it has nothing else in flight, so
|
|
505
|
-
// this is the result it is waiting on — wake it.
|
|
506
|
-
if (sessionContext?.isIdle()) flushResults(true);
|
|
507
552
|
};
|
|
508
553
|
|
|
509
554
|
pi.on("session_start", (_event, ctx) => {
|
|
555
|
+
restoredIdCounters = restoreSubagentIdCounters(
|
|
556
|
+
ctx.sessionManager.getBranch(),
|
|
557
|
+
);
|
|
510
558
|
refreshAgentTypes(ctx.cwd, ctx.isProjectTrusted());
|
|
511
|
-
|
|
559
|
+
registerStableToolFamily();
|
|
512
560
|
sessionContext = ctx;
|
|
513
561
|
settledAcknowledgedAt = 0;
|
|
514
562
|
if (ctx.hasUI) ui = ctx.ui;
|
|
@@ -530,10 +578,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
530
578
|
managerPromise?.then(updateStatus).catch(() => undefined);
|
|
531
579
|
});
|
|
532
580
|
|
|
533
|
-
// These settled while the model was working on something else, so they go
|
|
534
|
-
// into context without forcing a turn per stale subagent.
|
|
535
|
-
pi.on("agent_settled", () => flushResults(false));
|
|
536
|
-
|
|
537
581
|
pi.on("session_shutdown", async () => {
|
|
538
582
|
if (navigationLayerRegistered) {
|
|
539
583
|
removeEditorLayer(pi, "subagents");
|
|
@@ -610,57 +654,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
610
654
|
agentTypes = loaded.agentTypes;
|
|
611
655
|
agentTypeDiagnostics = loaded.diagnostics;
|
|
612
656
|
agentTypeList = [...agentTypes.values()];
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
subagentSpawnTool.parameters =
|
|
657
|
+
const surface = createSubagentSpawnToolSurface(agentTypeList);
|
|
658
|
+
subagentSpawnTool.description = surface.description;
|
|
659
|
+
subagentSpawnTool.parameters = surface.parameters;
|
|
616
660
|
registerSubagentSpawnTool();
|
|
617
661
|
};
|
|
618
662
|
|
|
619
663
|
// --- Tools -------------------------------------------------------------
|
|
620
664
|
|
|
621
|
-
const
|
|
622
|
-
Type.Object({
|
|
623
|
-
agent_type: createAgentTypeParameterSchema(agentTypeList),
|
|
624
|
-
prompt: Type.String({
|
|
625
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.prompt,
|
|
626
|
-
}),
|
|
627
|
-
name: Type.String({
|
|
628
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.name,
|
|
629
|
-
}),
|
|
630
|
-
harness: Type.Optional(
|
|
631
|
-
StringEnum(BACKEND_NAMES, {
|
|
632
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.harness,
|
|
633
|
-
}),
|
|
634
|
-
),
|
|
635
|
-
working_dir: Type.Optional(
|
|
636
|
-
Type.String({
|
|
637
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.workingDir,
|
|
638
|
-
}),
|
|
639
|
-
),
|
|
640
|
-
isolation: Type.Optional(
|
|
641
|
-
StringEnum(["worktree"] as const, {
|
|
642
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.isolation,
|
|
643
|
-
}),
|
|
644
|
-
),
|
|
645
|
-
model: Type.Optional(
|
|
646
|
-
Type.String({
|
|
647
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.model,
|
|
648
|
-
}),
|
|
649
|
-
),
|
|
650
|
-
reasoning_effort: Type.Optional(
|
|
651
|
-
StringEnum(REASONING_EFFORTS, {
|
|
652
|
-
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.reasoningEffort,
|
|
653
|
-
}),
|
|
654
|
-
),
|
|
655
|
-
});
|
|
665
|
+
const initialSpawnSurface = createSubagentSpawnToolSurface(agentTypeList);
|
|
656
666
|
|
|
657
667
|
const subagentSpawnTool = defineTool({
|
|
658
668
|
name: "subagent_spawn",
|
|
659
669
|
label: "Spawn Subagent",
|
|
660
|
-
|
|
670
|
+
...initialSpawnSurface,
|
|
661
671
|
promptSnippet: SUBAGENT_SPAWN_PROMPT_SNIPPET,
|
|
662
672
|
promptGuidelines: SUBAGENT_SPAWN_PROMPT_GUIDELINES,
|
|
663
|
-
parameters: createSubagentSpawnParameters(),
|
|
664
673
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
665
674
|
// Only one backend exists; harness is optional and defaults to it.
|
|
666
675
|
const harness = params.harness ?? BACKEND_NAMES[0];
|
|
@@ -785,8 +794,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
785
794
|
if (worktree) await reclaimWorktree(cwd, worktree).catch(() => {});
|
|
786
795
|
throw error;
|
|
787
796
|
}
|
|
788
|
-
|
|
789
|
-
showLifecycleTools();
|
|
797
|
+
persistId(snap.id);
|
|
790
798
|
|
|
791
799
|
return {
|
|
792
800
|
content: [
|
|
@@ -831,8 +839,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
831
839
|
const meta = [details.harness, details.model]
|
|
832
840
|
.filter(Boolean)
|
|
833
841
|
.join(" \u00b7 ");
|
|
842
|
+
// A spawn is an event, not a state, so it speaks in the activity rows'
|
|
843
|
+
// verb language ("Wrote" / "Ran" / "Spawned") rather than a glyph; the
|
|
844
|
+
// strip's spinner carries the running state from here on.
|
|
834
845
|
return new Text(
|
|
835
|
-
`${theme.fg("
|
|
846
|
+
`${theme.fg("toolTitle", "Spawned")} ${theme.bold(details.title ?? details.id)} ${theme.fg("dim", meta)}`,
|
|
836
847
|
0,
|
|
837
848
|
0,
|
|
838
849
|
);
|
|
@@ -852,7 +863,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
852
863
|
description: SUBAGENT_WAIT_PARAMETER_DESCRIPTIONS.ids,
|
|
853
864
|
}),
|
|
854
865
|
}),
|
|
855
|
-
async execute(_toolCallId, params, signal, onUpdate) {
|
|
866
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
856
867
|
const manager = await getManager();
|
|
857
868
|
const ids = [...new Set(params.ids)];
|
|
858
869
|
if (ids.length === 0)
|
|
@@ -888,33 +899,66 @@ export default function (pi: ExtensionAPI) {
|
|
|
888
899
|
// deferred automatic delivery now that the tool is returning the result.
|
|
889
900
|
resultDelivery.consume(ids);
|
|
890
901
|
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
902
|
+
const entries: Array<
|
|
903
|
+
| { readonly id: string; readonly section: string }
|
|
904
|
+
| {
|
|
905
|
+
readonly id: string;
|
|
906
|
+
readonly snap: SubagentSnapshot;
|
|
907
|
+
readonly header: string;
|
|
908
|
+
}
|
|
909
|
+
> = ids.map((id) => {
|
|
894
910
|
const snap = manager.view.get(id);
|
|
895
|
-
if (!snap) {
|
|
896
|
-
sections.push(`## ${id}\n\n(no longer tracked)`);
|
|
897
|
-
continue;
|
|
898
|
-
}
|
|
911
|
+
if (!snap) return { id, section: `## ${id}\n\n(no longer tracked)` };
|
|
899
912
|
const verb = snap.status === "error" ? "failed" : "finished";
|
|
900
|
-
let
|
|
901
|
-
if (snap.errorText)
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
913
|
+
let header = `## ${snap.id} "${snap.title}" ${verb}`;
|
|
914
|
+
if (snap.errorText) header += `\nError: ${snap.errorText}`;
|
|
915
|
+
return { id, snap, header };
|
|
916
|
+
});
|
|
917
|
+
const separatorsBytes = Math.max(0, entries.length - 1) * 7;
|
|
918
|
+
const fixedBytes =
|
|
919
|
+
separatorsBytes +
|
|
920
|
+
entries.reduce(
|
|
921
|
+
(sum, entry) =>
|
|
922
|
+
sum +
|
|
923
|
+
Buffer.byteLength(
|
|
924
|
+
"section" in entry ? entry.section : `${entry.header}\n\n`,
|
|
925
|
+
"utf8",
|
|
926
|
+
),
|
|
927
|
+
0,
|
|
906
928
|
);
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
929
|
+
const resultEntries = entries.filter(
|
|
930
|
+
(
|
|
931
|
+
entry,
|
|
932
|
+
): entry is {
|
|
933
|
+
readonly id: string;
|
|
934
|
+
readonly snap: SubagentSnapshot;
|
|
935
|
+
readonly header: string;
|
|
936
|
+
} => "snap" in entry,
|
|
937
|
+
);
|
|
938
|
+
const projectionBatchBytes = Math.max(
|
|
939
|
+
WAIT_MIN_RESULT_BYTES * resultEntries.length,
|
|
940
|
+
WAIT_OUTPUT_MAX_BYTES - fixedBytes,
|
|
941
|
+
);
|
|
942
|
+
const allocation = allocateResultBudgets(
|
|
943
|
+
resultEntries.map(({ snap }) =>
|
|
944
|
+
Buffer.byteLength(snap.finalText || "(no output)", "utf8"),
|
|
945
|
+
),
|
|
946
|
+
ctx.getContextUsage(),
|
|
947
|
+
{
|
|
948
|
+
maxBatchBytes: projectionBatchBytes,
|
|
949
|
+
maxResultBytes: WAIT_PER_AGENT_MAX_BYTES,
|
|
950
|
+
minResultBytes: WAIT_MIN_RESULT_BYTES,
|
|
951
|
+
headroomShare: RESULT_HEADROOM_SHARE,
|
|
952
|
+
estimatedBytesPerToken: ESTIMATED_BYTES_PER_TOKEN,
|
|
953
|
+
fixedBytes,
|
|
954
|
+
},
|
|
955
|
+
);
|
|
956
|
+
let resultIndex = 0;
|
|
957
|
+
const sections = entries.map((entry) => {
|
|
958
|
+
if ("section" in entry) return entry.section;
|
|
959
|
+
const outputBudget = allocation.budgets[resultIndex++]!;
|
|
960
|
+
return `${entry.header}\n\n${truncatedOutput(entry.snap, outputBudget)}`;
|
|
961
|
+
});
|
|
918
962
|
|
|
919
963
|
const combined = sections.join("\n\n---\n\n");
|
|
920
964
|
const bounded = truncateHead(combined, {
|
|
@@ -1170,11 +1214,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1170
1214
|
(entry, _options, theme) => {
|
|
1171
1215
|
const data = entry.data;
|
|
1172
1216
|
const failed = data?.status === "error";
|
|
1217
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
1173
1218
|
return new Text(
|
|
1174
|
-
`${theme.fg(
|
|
1219
|
+
`${icon} ${theme.fg("accent", data?.title ?? "?")}` +
|
|
1175
1220
|
theme.fg(
|
|
1176
|
-
"
|
|
1177
|
-
`
|
|
1221
|
+
"dim",
|
|
1222
|
+
` ${failed ? "failed" : "finished"} · ${data?.elapsed ?? "?"}`,
|
|
1178
1223
|
),
|
|
1179
1224
|
1,
|
|
1180
1225
|
0,
|
|
@@ -1187,7 +1232,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1187
1232
|
(entry, { expanded }, theme) => {
|
|
1188
1233
|
const data = entry.data;
|
|
1189
1234
|
const failed = data?.status === "error";
|
|
1190
|
-
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "
|
|
1235
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
1191
1236
|
const header =
|
|
1192
1237
|
`${icon} ` +
|
|
1193
1238
|
theme.fg("accent", theme.bold(`by the way · ${data?.title ?? "?"}`)) +
|
|
@@ -1271,6 +1316,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1271
1316
|
);
|
|
1272
1317
|
return;
|
|
1273
1318
|
}
|
|
1319
|
+
persistId(snap.id);
|
|
1274
1320
|
|
|
1275
1321
|
await openSubagentTakeover(ctx, manager.view, snap.id, {
|
|
1276
1322
|
badge: "by the way",
|