@tt-a1i/openpi 0.3.1 → 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 +30 -42
- 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 +204 -140
- 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/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,44 +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 {
|
|
123
|
+
import { persistResultArtifact, projectResult } from "./src/result-artifact.ts";
|
|
99
124
|
import {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
} from "../shared/
|
|
103
|
-
import {
|
|
104
|
-
BelowEditorNavigationEditor,
|
|
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";
|
|
125
|
+
allocateResultBudgets,
|
|
126
|
+
type ParentContextUsage,
|
|
127
|
+
} from "../shared/result-budget.ts";
|
|
128
|
+
import { createSubagentResultDelivery } from "./src/result-delivery.ts";
|
|
119
129
|
import {
|
|
120
130
|
createSubagentRuntime,
|
|
121
131
|
runTool,
|
|
122
132
|
type SubagentRuntime,
|
|
123
133
|
} from "./src/runtime.ts";
|
|
124
|
-
import {
|
|
125
|
-
normalizeSubagentTitle,
|
|
126
|
-
selectSubagentStripEntry,
|
|
127
|
-
SubagentStripWidget,
|
|
128
|
-
} from "./navigation.ts";
|
|
129
134
|
import { openSubagentPicker, openSubagentTakeover } from "./src/ui/takeover.ts";
|
|
130
135
|
import {
|
|
131
136
|
buildWaitResultPreview,
|
|
@@ -134,8 +139,13 @@ import {
|
|
|
134
139
|
} from "./src/ui/wait-result.ts";
|
|
135
140
|
|
|
136
141
|
const SUBAGENT_OUTPUT_MAX_BYTES = 24 * 1024;
|
|
142
|
+
const AUTOMATIC_OUTPUT_MAX_BYTES = 48 * 1024;
|
|
143
|
+
const AUTOMATIC_MIN_RESULT_BYTES = 2 * 1024;
|
|
137
144
|
const WAIT_OUTPUT_MAX_BYTES = 48 * 1024;
|
|
138
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;
|
|
139
149
|
|
|
140
150
|
interface SpawnResultDetails {
|
|
141
151
|
readonly id?: string;
|
|
@@ -189,36 +199,71 @@ function describeSubagent(snap: SubagentSnapshot) {
|
|
|
189
199
|
return `${snap.id} [${snap.status}] "${snap.title}" (${details.join(", ")})`;
|
|
190
200
|
}
|
|
191
201
|
|
|
192
|
-
function truncatedOutput(
|
|
202
|
+
export function truncatedOutput(
|
|
193
203
|
snap: SubagentSnapshot,
|
|
194
204
|
maxBytes = SUBAGENT_OUTPUT_MAX_BYTES,
|
|
205
|
+
writeArtifact: (content: string) => string = (content) =>
|
|
206
|
+
persistResultArtifact(getAgentDir(), content),
|
|
195
207
|
): string {
|
|
196
208
|
const output = snap.finalText || "(no output)";
|
|
197
|
-
|
|
209
|
+
return projectResult(output, {
|
|
198
210
|
maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
|
|
199
211
|
maxLines: Math.min(600, DEFAULT_MAX_LINES),
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (truncation.truncated) {
|
|
203
|
-
text += `\n\n[Output truncated: ${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)} shown. Full transcript in session file: ${snap.meta.sessionFilePath ?? "?"}]`;
|
|
204
|
-
}
|
|
205
|
-
return text;
|
|
212
|
+
writeArtifact,
|
|
213
|
+
}).text;
|
|
206
214
|
}
|
|
207
215
|
|
|
208
216
|
export function createSubagentResultDispatcher(
|
|
209
217
|
pi: ExtensionAPI,
|
|
210
|
-
outputFor: (
|
|
218
|
+
outputFor: (
|
|
219
|
+
snap: SubagentSnapshot,
|
|
220
|
+
maxBytes: number,
|
|
221
|
+
) => string = truncatedOutput,
|
|
222
|
+
getContextUsage: () => ParentContextUsage | undefined = () => undefined,
|
|
211
223
|
) {
|
|
212
224
|
return (snaps: readonly SubagentSnapshot[]) => {
|
|
213
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
|
+
);
|
|
214
259
|
const content = snaps
|
|
215
|
-
.map((snap) =>
|
|
260
|
+
.map((snap, index) =>
|
|
216
261
|
buildSubagentResultMessage({
|
|
217
262
|
id: snap.id,
|
|
218
263
|
title: snap.title,
|
|
219
264
|
status: snap.status,
|
|
220
265
|
errorText: snap.errorText,
|
|
221
|
-
output: outputFor(snap),
|
|
266
|
+
output: outputFor(snap, allocation.budgets[index]!),
|
|
222
267
|
}),
|
|
223
268
|
)
|
|
224
269
|
.join("\n\n");
|
|
@@ -277,7 +322,7 @@ function renderSubagentResult(
|
|
|
277
322
|
}
|
|
278
323
|
|
|
279
324
|
const failed = details.status === "error";
|
|
280
|
-
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "
|
|
325
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
281
326
|
const header =
|
|
282
327
|
`${icon} ` +
|
|
283
328
|
theme.fg("accent", theme.bold(`subagent ${details.id ?? "?"}`)) +
|
|
@@ -306,6 +351,10 @@ function renderSubagentResult(
|
|
|
306
351
|
export default function (pi: ExtensionAPI) {
|
|
307
352
|
let runtime: SubagentRuntime | undefined;
|
|
308
353
|
let managerPromise: Promise<SubagentManagerShape> | undefined;
|
|
354
|
+
let restoredIdCounters: SubagentIdCounters = {
|
|
355
|
+
modelCounter: 0,
|
|
356
|
+
btwCounter: 0,
|
|
357
|
+
};
|
|
309
358
|
let sessionContext: ExtensionContext | undefined;
|
|
310
359
|
let ui: ExtensionUIContext | undefined;
|
|
311
360
|
let unsubStatus: (() => void) | undefined;
|
|
@@ -321,7 +370,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
321
370
|
let requestWidgetRender: (() => void) | undefined;
|
|
322
371
|
let navigationLayerRegistered = false;
|
|
323
372
|
let dashboardOpen = false;
|
|
324
|
-
const dispatchResults = createSubagentResultDispatcher(
|
|
373
|
+
const dispatchResults = createSubagentResultDispatcher(
|
|
374
|
+
pi,
|
|
375
|
+
truncatedOutput,
|
|
376
|
+
() => sessionContext?.getContextUsage(),
|
|
377
|
+
);
|
|
325
378
|
const resultDelivery = createSubagentResultDelivery<SubagentSnapshot>({
|
|
326
379
|
isIdle: () => sessionContext?.isIdle() === true,
|
|
327
380
|
// Every unconsumed fire-and-forget result must reach the parent. The
|
|
@@ -329,16 +382,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
329
382
|
deliver: dispatchResults,
|
|
330
383
|
});
|
|
331
384
|
pi.on("agent_settled", () => resultDelivery.parentSettled());
|
|
332
|
-
const
|
|
333
|
-
patchOwnedTools(pi, "subagents", {
|
|
334
|
-
disable: OPENPI_TOOL_SURFACE.subagents.deferred,
|
|
335
|
-
});
|
|
336
|
-
const showLifecycleTools = () =>
|
|
385
|
+
const registerStableToolFamily = () =>
|
|
337
386
|
patchOwnedTools(pi, "subagents", {
|
|
338
|
-
enable: OPENPI_TOOL_SURFACE.subagents.
|
|
387
|
+
enable: OPENPI_TOOL_SURFACE.subagents.entry,
|
|
339
388
|
});
|
|
340
389
|
|
|
341
|
-
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));
|
|
342
398
|
|
|
343
399
|
/** Resolve the manager service once per runtime and wire the extension hooks. */
|
|
344
400
|
const getManager = () => {
|
|
@@ -392,9 +448,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
392
448
|
manager.view.list(),
|
|
393
449
|
settledAcknowledgedAt,
|
|
394
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";
|
|
395
454
|
ui.setStatus(
|
|
396
455
|
"subagents",
|
|
397
|
-
hasActivity(counts)
|
|
456
|
+
!tui && hasActivity(counts)
|
|
398
457
|
? formatActivityStatus(ui.theme, "subagents", counts)
|
|
399
458
|
: undefined,
|
|
400
459
|
);
|
|
@@ -493,8 +552,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
493
552
|
};
|
|
494
553
|
|
|
495
554
|
pi.on("session_start", (_event, ctx) => {
|
|
555
|
+
restoredIdCounters = restoreSubagentIdCounters(
|
|
556
|
+
ctx.sessionManager.getBranch(),
|
|
557
|
+
);
|
|
496
558
|
refreshAgentTypes(ctx.cwd, ctx.isProjectTrusted());
|
|
497
|
-
|
|
559
|
+
registerStableToolFamily();
|
|
498
560
|
sessionContext = ctx;
|
|
499
561
|
settledAcknowledgedAt = 0;
|
|
500
562
|
if (ctx.hasUI) ui = ctx.ui;
|
|
@@ -592,57 +654,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
592
654
|
agentTypes = loaded.agentTypes;
|
|
593
655
|
agentTypeDiagnostics = loaded.diagnostics;
|
|
594
656
|
agentTypeList = [...agentTypes.values()];
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
subagentSpawnTool.parameters =
|
|
657
|
+
const surface = createSubagentSpawnToolSurface(agentTypeList);
|
|
658
|
+
subagentSpawnTool.description = surface.description;
|
|
659
|
+
subagentSpawnTool.parameters = surface.parameters;
|
|
598
660
|
registerSubagentSpawnTool();
|
|
599
661
|
};
|
|
600
662
|
|
|
601
663
|
// --- Tools -------------------------------------------------------------
|
|
602
664
|
|
|
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
|
-
});
|
|
665
|
+
const initialSpawnSurface = createSubagentSpawnToolSurface(agentTypeList);
|
|
638
666
|
|
|
639
667
|
const subagentSpawnTool = defineTool({
|
|
640
668
|
name: "subagent_spawn",
|
|
641
669
|
label: "Spawn Subagent",
|
|
642
|
-
|
|
670
|
+
...initialSpawnSurface,
|
|
643
671
|
promptSnippet: SUBAGENT_SPAWN_PROMPT_SNIPPET,
|
|
644
672
|
promptGuidelines: SUBAGENT_SPAWN_PROMPT_GUIDELINES,
|
|
645
|
-
parameters: createSubagentSpawnParameters(),
|
|
646
673
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
647
674
|
// Only one backend exists; harness is optional and defaults to it.
|
|
648
675
|
const harness = params.harness ?? BACKEND_NAMES[0];
|
|
@@ -767,8 +794,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
767
794
|
if (worktree) await reclaimWorktree(cwd, worktree).catch(() => {});
|
|
768
795
|
throw error;
|
|
769
796
|
}
|
|
770
|
-
|
|
771
|
-
showLifecycleTools();
|
|
797
|
+
persistId(snap.id);
|
|
772
798
|
|
|
773
799
|
return {
|
|
774
800
|
content: [
|
|
@@ -813,8 +839,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
813
839
|
const meta = [details.harness, details.model]
|
|
814
840
|
.filter(Boolean)
|
|
815
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.
|
|
816
845
|
return new Text(
|
|
817
|
-
`${theme.fg("
|
|
846
|
+
`${theme.fg("toolTitle", "Spawned")} ${theme.bold(details.title ?? details.id)} ${theme.fg("dim", meta)}`,
|
|
818
847
|
0,
|
|
819
848
|
0,
|
|
820
849
|
);
|
|
@@ -834,7 +863,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
834
863
|
description: SUBAGENT_WAIT_PARAMETER_DESCRIPTIONS.ids,
|
|
835
864
|
}),
|
|
836
865
|
}),
|
|
837
|
-
async execute(_toolCallId, params, signal, onUpdate) {
|
|
866
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
838
867
|
const manager = await getManager();
|
|
839
868
|
const ids = [...new Set(params.ids)];
|
|
840
869
|
if (ids.length === 0)
|
|
@@ -870,33 +899,66 @@ export default function (pi: ExtensionAPI) {
|
|
|
870
899
|
// deferred automatic delivery now that the tool is returning the result.
|
|
871
900
|
resultDelivery.consume(ids);
|
|
872
901
|
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
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) => {
|
|
876
910
|
const snap = manager.view.get(id);
|
|
877
|
-
if (!snap) {
|
|
878
|
-
sections.push(`## ${id}\n\n(no longer tracked)`);
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
911
|
+
if (!snap) return { id, section: `## ${id}\n\n(no longer tracked)` };
|
|
881
912
|
const verb = snap.status === "error" ? "failed" : "finished";
|
|
882
|
-
let
|
|
883
|
-
if (snap.errorText)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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,
|
|
888
928
|
);
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
+
});
|
|
900
962
|
|
|
901
963
|
const combined = sections.join("\n\n---\n\n");
|
|
902
964
|
const bounded = truncateHead(combined, {
|
|
@@ -1152,11 +1214,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1152
1214
|
(entry, _options, theme) => {
|
|
1153
1215
|
const data = entry.data;
|
|
1154
1216
|
const failed = data?.status === "error";
|
|
1217
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
1155
1218
|
return new Text(
|
|
1156
|
-
`${theme.fg(
|
|
1219
|
+
`${icon} ${theme.fg("accent", data?.title ?? "?")}` +
|
|
1157
1220
|
theme.fg(
|
|
1158
|
-
"
|
|
1159
|
-
`
|
|
1221
|
+
"dim",
|
|
1222
|
+
` ${failed ? "failed" : "finished"} · ${data?.elapsed ?? "?"}`,
|
|
1160
1223
|
),
|
|
1161
1224
|
1,
|
|
1162
1225
|
0,
|
|
@@ -1169,7 +1232,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1169
1232
|
(entry, { expanded }, theme) => {
|
|
1170
1233
|
const data = entry.data;
|
|
1171
1234
|
const failed = data?.status === "error";
|
|
1172
|
-
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "
|
|
1235
|
+
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
1173
1236
|
const header =
|
|
1174
1237
|
`${icon} ` +
|
|
1175
1238
|
theme.fg("accent", theme.bold(`by the way · ${data?.title ?? "?"}`)) +
|
|
@@ -1253,6 +1316,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1253
1316
|
);
|
|
1254
1317
|
return;
|
|
1255
1318
|
}
|
|
1319
|
+
persistId(snap.id);
|
|
1256
1320
|
|
|
1257
1321
|
await openSubagentTakeover(ctx, manager.view, snap.id, {
|
|
1258
1322
|
badge: "by the way",
|
|
@@ -2,15 +2,17 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { TUI } from "@earendil-works/pi-tui";
|
|
3
3
|
import {
|
|
4
4
|
fitNavigationSides,
|
|
5
|
+
renderNavigationMetrics,
|
|
5
6
|
type BelowEditorStripState,
|
|
6
7
|
} from "../shared/below-editor-navigation.ts";
|
|
7
8
|
import {
|
|
8
9
|
unreadActivityCounts,
|
|
9
10
|
type ActivityCounts,
|
|
10
11
|
} from "../shared/activity-status.ts";
|
|
12
|
+
import { spinnerFrame } from "../shared/spinner.ts";
|
|
11
13
|
import { sanitizeTerminalText } from "../shared/terminal-text.ts";
|
|
12
14
|
import { formatElapsed, type SubagentSnapshot } from "./src/domain.ts";
|
|
13
|
-
import {
|
|
15
|
+
import { contextPercent } from "../shared/context-utilization.ts";
|
|
14
16
|
|
|
15
17
|
export interface SubagentStripEntry {
|
|
16
18
|
snapshot: SubagentSnapshot;
|
|
@@ -58,8 +60,15 @@ function statusColor(status: SubagentSnapshot["status"]) {
|
|
|
58
60
|
return "error" as const;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
/**
|
|
64
|
+
* One status indicator per run state; doubles as the focus marker when
|
|
65
|
+
* selected. Running spins, in step with the dashboard and takeover headers.
|
|
66
|
+
*/
|
|
67
|
+
function statusGlyph(snapshot: SubagentSnapshot, theme: Theme, now: number) {
|
|
68
|
+
if (snapshot.status === "running")
|
|
69
|
+
return theme.fg("warning", spinnerFrame(now));
|
|
70
|
+
if (snapshot.status === "done") return theme.fg("success", "✓");
|
|
71
|
+
return theme.fg("error", "✗");
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
/** One-line subagent manager entry with the same affordance as Workflow. */
|
|
@@ -94,28 +103,48 @@ export class SubagentStripWidget {
|
|
|
94
103
|
const entry = this.getEntry();
|
|
95
104
|
if (!entry || width <= 0) return [];
|
|
96
105
|
const { snapshot, counts } = entry;
|
|
97
|
-
const
|
|
106
|
+
const glyph = this.strip.focused
|
|
98
107
|
? this.theme.fg("accent", "❯")
|
|
99
|
-
: this.theme.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
?
|
|
106
|
-
:
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
: statusGlyph(snapshot, this.theme, Date.now());
|
|
109
|
+
// A name only means something when it names the only active subagent; with
|
|
110
|
+
// several, an aggregate label is honest and the counts carry the detail.
|
|
111
|
+
const total = counts.running + counts.done + counts.failed;
|
|
112
|
+
const single = total === 1;
|
|
113
|
+
const labelText = single
|
|
114
|
+
? normalizeSubagentTitle(snapshot.title, snapshot.id)
|
|
115
|
+
: "subagents";
|
|
116
|
+
const label = this.strip.focused
|
|
117
|
+
? this.theme.bold(this.theme.fg("accent", labelText))
|
|
118
|
+
: this.theme.fg("text", labelText);
|
|
119
|
+
// The footer already shows the session model; the takeover view keeps the
|
|
120
|
+
// per-subagent model, so the one-line strip stays title-only.
|
|
121
|
+
const left = ` ${glyph} ${label}`;
|
|
122
|
+
// Worded counts read at a glance; the selected run's own state comes
|
|
123
|
+
// first so the emphasis colour always lands on the matching count. A lone
|
|
124
|
+
// subagent needs no count — the glyph and label already say it.
|
|
125
|
+
const donePart = counts.done > 0 ? `${counts.done} done` : undefined;
|
|
126
|
+
const failedPart =
|
|
127
|
+
counts.failed > 0 ? `${counts.failed} failed` : undefined;
|
|
128
|
+
const activity = single
|
|
129
|
+
? []
|
|
130
|
+
: counts.running > 0
|
|
131
|
+
? [`${counts.running} running`]
|
|
132
|
+
: snapshot.status === "error"
|
|
133
|
+
? [failedPart, donePart]
|
|
134
|
+
: [donePart, failedPart];
|
|
135
|
+
const percent = contextPercent(snapshot.usage);
|
|
136
|
+
const right = renderNavigationMetrics(
|
|
137
|
+
this.theme,
|
|
138
|
+
[
|
|
139
|
+
...activity,
|
|
140
|
+
formatElapsed(snapshot),
|
|
141
|
+
percent === undefined ? undefined : `${percent}% ctx`,
|
|
142
|
+
],
|
|
114
143
|
this.strip.focused ? "enter open · ↑ back" : "↓ to manage",
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
144
|
+
single || snapshot.status === "running"
|
|
145
|
+
? undefined
|
|
146
|
+
: statusColor(snapshot.status),
|
|
147
|
+
);
|
|
119
148
|
return [fitNavigationSides(left, right, width)];
|
|
120
149
|
}
|
|
121
150
|
}
|