@tt-a1i/openpi 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -40
- package/SETUP.md +22 -6
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +145 -0
- 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 +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +4 -1
- 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/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- 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 +153 -86
- 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 +123 -127
- package/extensions/shared/activity-status.ts +30 -0
- 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/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +437 -21
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +73 -33
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- 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 +270 -59
- package/extensions/subagents/navigation.ts +34 -5
- 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/manager.ts +34 -2
- package/extensions/subagents/src/prompt.ts +32 -4
- package/extensions/subagents/src/result-artifact.ts +4 -0
- package/extensions/subagents/src/result-delivery.ts +7 -1
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- 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/ui-customization/footer.ts +0 -40
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +1 -1
- package/extensions/web/index.ts +234 -0
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +457 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +167 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +488 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +74 -4
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +66 -6
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +128 -64
- package/extensions/workflows/retention.ts +593 -0
- package/extensions/workflows/runner.ts +388 -279
- package/extensions/workflows/sandbox-child.cjs +25 -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 +1 -1
- package/skills/workflows/REFERENCE.md +4 -2
- 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 -303
|
@@ -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
|
|
@@ -44,10 +45,12 @@ import {
|
|
|
44
45
|
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
45
46
|
import { Type } from "typebox";
|
|
46
47
|
import {
|
|
48
|
+
createStatusWriter,
|
|
47
49
|
formatActivityStatus,
|
|
48
50
|
hasActivity,
|
|
49
51
|
unreadActivityCounts,
|
|
50
52
|
} from "../shared/activity-status.ts";
|
|
53
|
+
import { sanitizeText } from "../shared/agent-transcript.ts";
|
|
51
54
|
import {
|
|
52
55
|
BelowEditorNavigationEditor,
|
|
53
56
|
BelowEditorStripState,
|
|
@@ -67,13 +70,22 @@ import {
|
|
|
67
70
|
planModeAllowsDeclaredTools,
|
|
68
71
|
planModeChildTools,
|
|
69
72
|
} from "../shared/plan-mode-state.ts";
|
|
70
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
allocateResultBudgets,
|
|
75
|
+
type ParentContextUsage,
|
|
76
|
+
} from "../shared/result-budget.ts";
|
|
77
|
+
import { type DetailDisplay, loadSetupConfig } from "../shared/setup-config.ts";
|
|
71
78
|
import {
|
|
72
79
|
OPENPI_TOOL_SURFACE,
|
|
73
80
|
patchOwnedTools,
|
|
74
81
|
} from "../shared/tool-surface.ts";
|
|
82
|
+
import {
|
|
83
|
+
projectSubagentCapability,
|
|
84
|
+
registerWebCapability,
|
|
85
|
+
} from "../shared/web-observer-registry.ts";
|
|
75
86
|
import {
|
|
76
87
|
createWorktree,
|
|
88
|
+
formatWorktreeCleanupWarning,
|
|
77
89
|
reclaimWorktree,
|
|
78
90
|
type Worktree,
|
|
79
91
|
} from "../shared/worktree.ts";
|
|
@@ -81,9 +93,9 @@ import {
|
|
|
81
93
|
normalizeSubagentTitle,
|
|
82
94
|
SubagentStripWidget,
|
|
83
95
|
selectSubagentStripEntry,
|
|
96
|
+
subagentStripEntryKey,
|
|
84
97
|
} from "./navigation.ts";
|
|
85
98
|
import {
|
|
86
|
-
type AgentType,
|
|
87
99
|
formatAgentTypeDiagnostics,
|
|
88
100
|
loadAgentTypes,
|
|
89
101
|
roleModelForAgentType,
|
|
@@ -104,6 +116,7 @@ import {
|
|
|
104
116
|
} from "./src/id-sequence.ts";
|
|
105
117
|
import { SubagentManager, type SubagentManagerShape } from "./src/manager.ts";
|
|
106
118
|
import {
|
|
119
|
+
buildSubagentResultDisplayMessage,
|
|
107
120
|
buildSubagentResultMessage,
|
|
108
121
|
buildSubagentSendResult,
|
|
109
122
|
buildSubagentSpawnResult,
|
|
@@ -119,12 +132,13 @@ import {
|
|
|
119
132
|
SUBAGENT_SPAWN_PROMPT_SNIPPET,
|
|
120
133
|
SUBAGENT_WAIT_PARAMETER_DESCRIPTIONS,
|
|
121
134
|
SUBAGENT_WAIT_TOOL_DESCRIPTION,
|
|
135
|
+
stripSubagentResultTransportInstruction,
|
|
122
136
|
} from "./src/prompt.ts";
|
|
123
|
-
import { persistResultArtifact, projectResult } from "./src/result-artifact.ts";
|
|
124
137
|
import {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
persistResultArtifact,
|
|
139
|
+
projectResult,
|
|
140
|
+
type ResultProjection,
|
|
141
|
+
} from "./src/result-artifact.ts";
|
|
128
142
|
import { createSubagentResultDelivery } from "./src/result-delivery.ts";
|
|
129
143
|
import {
|
|
130
144
|
createSubagentRuntime,
|
|
@@ -133,8 +147,8 @@ import {
|
|
|
133
147
|
} from "./src/runtime.ts";
|
|
134
148
|
import { openSubagentPicker, openSubagentTakeover } from "./src/ui/takeover.ts";
|
|
135
149
|
import {
|
|
136
|
-
buildWaitResultPreview,
|
|
137
150
|
renderWaitResult,
|
|
151
|
+
renderWaitResultPreview,
|
|
138
152
|
type WaitResultDetails,
|
|
139
153
|
} from "./src/ui/wait-result.ts";
|
|
140
154
|
|
|
@@ -146,6 +160,8 @@ const WAIT_PER_AGENT_MAX_BYTES = 16 * 1024;
|
|
|
146
160
|
const WAIT_MIN_RESULT_BYTES = 512;
|
|
147
161
|
const RESULT_HEADROOM_SHARE = 0.5;
|
|
148
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.]";
|
|
149
165
|
|
|
150
166
|
interface SpawnResultDetails {
|
|
151
167
|
readonly id?: string;
|
|
@@ -166,12 +182,24 @@ interface SubagentResultDetails {
|
|
|
166
182
|
readonly id?: string;
|
|
167
183
|
readonly title?: string;
|
|
168
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;
|
|
169
190
|
readonly count?: number;
|
|
170
191
|
readonly results?: ReadonlyArray<{
|
|
171
192
|
readonly id: string;
|
|
172
193
|
readonly title: string;
|
|
173
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;
|
|
174
200
|
}>;
|
|
201
|
+
/** Display-only projection for the custom message renderer. */
|
|
202
|
+
readonly displayContent?: string;
|
|
175
203
|
}
|
|
176
204
|
|
|
177
205
|
interface SubagentResultEntryData {
|
|
@@ -213,12 +241,53 @@ export function truncatedOutput(
|
|
|
213
241
|
}).text;
|
|
214
242
|
}
|
|
215
243
|
|
|
244
|
+
function projectSubagentOutput(
|
|
245
|
+
snap: SubagentSnapshot,
|
|
246
|
+
maxBytes: number,
|
|
247
|
+
): ResultProjection {
|
|
248
|
+
const output = snap.finalText || "(no output)";
|
|
249
|
+
return projectResult(output, {
|
|
250
|
+
maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
|
|
251
|
+
maxLines: Math.min(600, DEFAULT_MAX_LINES),
|
|
252
|
+
writeArtifact: (content) => persistResultArtifact(getAgentDir(), content),
|
|
253
|
+
});
|
|
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}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
216
285
|
export function createSubagentResultDispatcher(
|
|
217
286
|
pi: ExtensionAPI,
|
|
218
287
|
outputFor: (
|
|
219
288
|
snap: SubagentSnapshot,
|
|
220
289
|
maxBytes: number,
|
|
221
|
-
) => string =
|
|
290
|
+
) => string | OutputProjection = projectSubagentOutput,
|
|
222
291
|
getContextUsage: () => ParentContextUsage | undefined = () => undefined,
|
|
223
292
|
) {
|
|
224
293
|
return (snaps: readonly SubagentSnapshot[]) => {
|
|
@@ -239,7 +308,7 @@ export function createSubagentResultDispatcher(
|
|
|
239
308
|
) +
|
|
240
309
|
Math.max(0, snaps.length - 1) * 2;
|
|
241
310
|
const projectionBatchBytes = Math.max(
|
|
242
|
-
|
|
311
|
+
0,
|
|
243
312
|
AUTOMATIC_OUTPUT_MAX_BYTES - wrapperBytes,
|
|
244
313
|
);
|
|
245
314
|
const allocation = allocateResultBudgets(
|
|
@@ -256,34 +325,73 @@ export function createSubagentResultDispatcher(
|
|
|
256
325
|
fixedBytes: wrapperBytes,
|
|
257
326
|
},
|
|
258
327
|
);
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
);
|
|
270
358
|
const details: SubagentResultDetails =
|
|
271
359
|
snaps.length === 1
|
|
272
360
|
? {
|
|
273
361
|
id: snaps[0]!.id,
|
|
274
362
|
title: snaps[0]!.title,
|
|
275
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
|
+
: {}),
|
|
276
373
|
}
|
|
277
374
|
: {
|
|
278
375
|
count: snaps.length,
|
|
279
|
-
results: snaps.map((snap) => ({
|
|
376
|
+
results: snaps.map((snap, index) => ({
|
|
280
377
|
id: snap.id,
|
|
281
378
|
title: snap.title,
|
|
282
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
|
+
: {}),
|
|
283
391
|
})),
|
|
284
392
|
};
|
|
285
393
|
pi.appendEntry<SubagentResultEntryData>("subagent-result", {
|
|
286
|
-
content,
|
|
394
|
+
content: displayContent,
|
|
287
395
|
details,
|
|
288
396
|
});
|
|
289
397
|
pi.sendMessage(
|
|
@@ -291,7 +399,7 @@ export function createSubagentResultDispatcher(
|
|
|
291
399
|
customType: "subagent-result",
|
|
292
400
|
content,
|
|
293
401
|
display: false,
|
|
294
|
-
details,
|
|
402
|
+
details: { ...details, displayContent },
|
|
295
403
|
},
|
|
296
404
|
{ deliverAs: "followUp", triggerTurn: true },
|
|
297
405
|
);
|
|
@@ -304,36 +412,52 @@ function renderSubagentResult(
|
|
|
304
412
|
content: string,
|
|
305
413
|
details: SubagentResultDetails,
|
|
306
414
|
expanded: boolean,
|
|
415
|
+
resultDisplay: DetailDisplay,
|
|
307
416
|
theme: SubagentResultTheme,
|
|
308
417
|
) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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);
|
|
322
439
|
}
|
|
323
440
|
|
|
324
|
-
const failed =
|
|
441
|
+
const failed = results.some((result) => result.status === "error");
|
|
442
|
+
const batched = results.length > 1;
|
|
325
443
|
const icon = failed ? theme.fg("error", "x") : theme.fg("success", "✓");
|
|
326
|
-
const header =
|
|
327
|
-
`${icon} `
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
+
);
|
|
333
455
|
|
|
334
|
-
// Remove only the summary line.
|
|
335
|
-
//
|
|
336
|
-
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();
|
|
337
461
|
const md = new Markdown(body, 0, 0, getMarkdownTheme());
|
|
338
462
|
const container = new Text(header, 0, 0);
|
|
339
463
|
return {
|
|
@@ -348,7 +472,17 @@ function renderSubagentResult(
|
|
|
348
472
|
};
|
|
349
473
|
}
|
|
350
474
|
|
|
351
|
-
|
|
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);
|
|
352
486
|
let runtime: SubagentRuntime | undefined;
|
|
353
487
|
let managerPromise: Promise<SubagentManagerShape> | undefined;
|
|
354
488
|
let restoredIdCounters: SubagentIdCounters = {
|
|
@@ -364,15 +498,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
364
498
|
*/
|
|
365
499
|
let settledAcknowledgedAt = 0;
|
|
366
500
|
const stripState = new BelowEditorStripState();
|
|
501
|
+
const statusWriter = createStatusWriter("subagents");
|
|
367
502
|
const widgetKey = "subagent-navigation";
|
|
368
503
|
let navigationManager: SubagentManagerShape | undefined;
|
|
504
|
+
let unregisterWebCapability: (() => void) | undefined;
|
|
369
505
|
let widgetVisible = false;
|
|
506
|
+
let widgetEntryKey: string | undefined;
|
|
370
507
|
let requestWidgetRender: (() => void) | undefined;
|
|
371
508
|
let navigationLayerRegistered = false;
|
|
372
509
|
let dashboardOpen = false;
|
|
373
510
|
const dispatchResults = createSubagentResultDispatcher(
|
|
374
511
|
pi,
|
|
375
|
-
|
|
512
|
+
projectSubagentOutput,
|
|
376
513
|
() => sessionContext?.getContextUsage(),
|
|
377
514
|
);
|
|
378
515
|
const resultDelivery = createSubagentResultDelivery<SubagentSnapshot>({
|
|
@@ -398,10 +535,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
398
535
|
|
|
399
536
|
/** Resolve the manager service once per runtime and wire the extension hooks. */
|
|
400
537
|
const getManager = () => {
|
|
538
|
+
const scope = sessionContext?.sessionManager;
|
|
401
539
|
managerPromise ??= getRuntime()
|
|
402
540
|
.runPromise(SubagentManager)
|
|
403
541
|
.then((manager) => {
|
|
404
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;
|
|
405
552
|
manager.view.setOnSettled(onSettled);
|
|
406
553
|
unsubStatus?.();
|
|
407
554
|
unsubStatus = manager.view.subscribe(() => updateStatus(manager));
|
|
@@ -422,10 +569,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
422
569
|
const updateSubagentWidget = () => {
|
|
423
570
|
const ctx = sessionContext;
|
|
424
571
|
if (!ctx || ctx.mode !== "tui") return;
|
|
425
|
-
const
|
|
426
|
-
|
|
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
|
+
}
|
|
427
582
|
if (!visible) {
|
|
428
583
|
stripState.focused = false;
|
|
584
|
+
widgetEntryKey = undefined;
|
|
429
585
|
requestWidgetRender = undefined;
|
|
430
586
|
ctx.ui.setWidget(widgetKey, undefined);
|
|
431
587
|
widgetVisible = false;
|
|
@@ -440,6 +596,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
440
596
|
{ placement: "belowEditor" },
|
|
441
597
|
);
|
|
442
598
|
widgetVisible = true;
|
|
599
|
+
widgetEntryKey = entryKey;
|
|
443
600
|
};
|
|
444
601
|
|
|
445
602
|
const updateStatus = (manager: SubagentManagerShape) => {
|
|
@@ -451,8 +608,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
451
608
|
// In the TUI the below-editor strip already reports the same activity and
|
|
452
609
|
// carries the manage affordance, so a footer status line would repeat it.
|
|
453
610
|
const tui = sessionContext?.mode === "tui";
|
|
454
|
-
|
|
455
|
-
|
|
611
|
+
statusWriter.write(
|
|
612
|
+
ui,
|
|
456
613
|
!tui && hasActivity(counts)
|
|
457
614
|
? formatActivityStatus(ui.theme, "subagents", counts)
|
|
458
615
|
: undefined,
|
|
@@ -586,16 +743,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
586
743
|
resultDelivery.clear();
|
|
587
744
|
unsubStatus?.();
|
|
588
745
|
unsubStatus = undefined;
|
|
746
|
+
unregisterWebCapability?.();
|
|
747
|
+
unregisterWebCapability = undefined;
|
|
589
748
|
try {
|
|
590
749
|
ui?.setStatus("subagents", undefined);
|
|
591
750
|
sessionContext?.ui.setWidget(widgetKey, undefined);
|
|
592
751
|
} catch {
|
|
593
752
|
// UI may already be disposed.
|
|
594
753
|
}
|
|
754
|
+
statusWriter.reset();
|
|
595
755
|
sessionContext = undefined;
|
|
596
756
|
ui = undefined;
|
|
597
757
|
navigationManager = undefined;
|
|
598
758
|
widgetVisible = false;
|
|
759
|
+
widgetEntryKey = undefined;
|
|
599
760
|
requestWidgetRender = undefined;
|
|
600
761
|
stripState.focused = false;
|
|
601
762
|
dashboardOpen = false;
|
|
@@ -791,7 +952,37 @@ export default function (pi: ExtensionAPI) {
|
|
|
791
952
|
} catch (error) {
|
|
792
953
|
// The session scope owns reclamation, but it never opened, so this
|
|
793
954
|
// worktree would otherwise be orphaned on disk.
|
|
794
|
-
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
|
+
}
|
|
795
986
|
throw error;
|
|
796
987
|
}
|
|
797
988
|
persistId(snap.id);
|
|
@@ -954,10 +1145,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
954
1145
|
},
|
|
955
1146
|
);
|
|
956
1147
|
let resultIndex = 0;
|
|
1148
|
+
const artifactSaveFailures = new Set<string>();
|
|
1149
|
+
const fullResultsSaved = new Set<string>();
|
|
957
1150
|
const sections = entries.map((entry) => {
|
|
958
1151
|
if ("section" in entry) return entry.section;
|
|
959
1152
|
const outputBudget = allocation.budgets[resultIndex++]!;
|
|
960
|
-
|
|
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}`;
|
|
961
1157
|
});
|
|
962
1158
|
|
|
963
1159
|
const combined = sections.join("\n\n---\n\n");
|
|
@@ -973,7 +1169,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
973
1169
|
details: {
|
|
974
1170
|
results: ids.map((id) => {
|
|
975
1171
|
const snap = manager.view.get(id);
|
|
976
|
-
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
|
+
};
|
|
977
1186
|
}),
|
|
978
1187
|
},
|
|
979
1188
|
};
|
|
@@ -998,7 +1207,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
998
1207
|
return renderWaitResult(
|
|
999
1208
|
content,
|
|
1000
1209
|
result.details as WaitResultDetails | undefined,
|
|
1001
|
-
expanded ||
|
|
1210
|
+
expanded || getResultDisplay() === "full",
|
|
1002
1211
|
theme,
|
|
1003
1212
|
);
|
|
1004
1213
|
},
|
|
@@ -1193,6 +1402,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1193
1402
|
content,
|
|
1194
1403
|
(message.details ?? {}) as SubagentResultDetails,
|
|
1195
1404
|
expanded,
|
|
1405
|
+
getResultDisplay(),
|
|
1196
1406
|
theme,
|
|
1197
1407
|
);
|
|
1198
1408
|
},
|
|
@@ -1205,6 +1415,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1205
1415
|
entry.data?.content ?? "",
|
|
1206
1416
|
entry.data?.details ?? {},
|
|
1207
1417
|
expanded,
|
|
1418
|
+
getResultDisplay(),
|
|
1208
1419
|
theme,
|
|
1209
1420
|
),
|
|
1210
1421
|
);
|
|
@@ -1247,7 +1458,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1247
1458
|
.filter(Boolean)
|
|
1248
1459
|
.join("\n\n");
|
|
1249
1460
|
|
|
1250
|
-
if (expanded ||
|
|
1461
|
+
if (expanded || getResultDisplay() === "full") {
|
|
1251
1462
|
const md = new Markdown(body, 0, 0, getMarkdownTheme());
|
|
1252
1463
|
const container = new Text(header, 0, 0);
|
|
1253
1464
|
return {
|
|
@@ -19,6 +19,11 @@ export interface SubagentStripEntry {
|
|
|
19
19
|
counts: ActivityCounts;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** Changes only when the strip needs an immediate lifecycle repaint. */
|
|
23
|
+
export function subagentStripEntryKey(entry: SubagentStripEntry | undefined) {
|
|
24
|
+
return entry ? `${entry.snapshot.id}:${entry.snapshot.status}` : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
function cleanLine(value: string) {
|
|
23
28
|
return sanitizeTerminalText(value).replace(/\s+/g, " ").trim();
|
|
24
29
|
}
|
|
@@ -71,9 +76,11 @@ function statusGlyph(snapshot: SubagentSnapshot, theme: Theme, now: number) {
|
|
|
71
76
|
return theme.fg("error", "✗");
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
const SUBAGENT_STRIP_INTERVAL_MS = 500;
|
|
80
|
+
|
|
74
81
|
/** One-line subagent manager entry with the same affordance as Workflow. */
|
|
75
82
|
export class SubagentStripWidget {
|
|
76
|
-
private
|
|
83
|
+
private timer: ReturnType<typeof setInterval> | undefined;
|
|
77
84
|
private readonly tui: TUI;
|
|
78
85
|
private readonly theme: Theme;
|
|
79
86
|
private readonly strip: BelowEditorStripState;
|
|
@@ -89,17 +96,39 @@ export class SubagentStripWidget {
|
|
|
89
96
|
this.theme = theme;
|
|
90
97
|
this.strip = strip;
|
|
91
98
|
this.getEntry = getEntry;
|
|
92
|
-
this.
|
|
93
|
-
this.timer.unref?.();
|
|
99
|
+
this.syncSpinner();
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
dispose() {
|
|
97
|
-
clearInterval(this.timer);
|
|
103
|
+
if (this.timer) clearInterval(this.timer);
|
|
104
|
+
this.timer = undefined;
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
invalidate() {
|
|
107
|
+
invalidate() {
|
|
108
|
+
this.syncSpinner();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private syncSpinner() {
|
|
112
|
+
if (this.getEntry()?.snapshot.status === "running") {
|
|
113
|
+
if (this.timer) return;
|
|
114
|
+
const timer = setInterval(() => {
|
|
115
|
+
if (this.timer !== timer) return;
|
|
116
|
+
if (this.getEntry()?.snapshot.status !== "running") {
|
|
117
|
+
clearInterval(timer);
|
|
118
|
+
this.timer = undefined;
|
|
119
|
+
}
|
|
120
|
+
this.tui.requestRender();
|
|
121
|
+
}, SUBAGENT_STRIP_INTERVAL_MS);
|
|
122
|
+
this.timer = timer;
|
|
123
|
+
timer.unref?.();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (this.timer) clearInterval(this.timer);
|
|
127
|
+
this.timer = undefined;
|
|
128
|
+
}
|
|
101
129
|
|
|
102
130
|
render(width: number) {
|
|
131
|
+
this.syncSpinner();
|
|
103
132
|
const entry = this.getEntry();
|
|
104
133
|
if (!entry || width <= 0) return [];
|
|
105
134
|
const { snapshot, counts } = entry;
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* on it rather than on the pi session directly.
|
|
6
6
|
*
|
|
7
7
|
* - pi (./backends/pi.ts): in-process `createAgentSession()` via the pi SDK.
|
|
8
|
-
* - stub (./backends/stub.ts): scripted sessions, test-only.
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import type { Effect, Scope, Stream } from "effect";
|
|
12
11
|
import { Context } from "effect";
|
|
12
|
+
import type { AgentToolRenderer } from "../../shared/agent-tool-renderer.ts";
|
|
13
13
|
import type {
|
|
14
14
|
BackendName,
|
|
15
15
|
SendError,
|
|
@@ -19,6 +19,13 @@ import type {
|
|
|
19
19
|
SubagentMeta,
|
|
20
20
|
} from "./domain.ts";
|
|
21
21
|
|
|
22
|
+
export interface SubagentCleanupReceipt {
|
|
23
|
+
/** True when the session/worktree state could not be proven quiescent. */
|
|
24
|
+
readonly uncertain: boolean;
|
|
25
|
+
/** Bounded operator-facing explanation of the cleanup result. */
|
|
26
|
+
readonly message: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
30
|
* A live subagent session. The manager is the single consumer of `events`;
|
|
24
31
|
* it folds them into the `SubagentSnapshot` everything else reads.
|
|
@@ -42,6 +49,10 @@ export interface SubagentSession {
|
|
|
42
49
|
* this with a timeout and fall back to closing the session scope.
|
|
43
50
|
*/
|
|
44
51
|
readonly interrupt: Effect.Effect<void>;
|
|
52
|
+
/** Ephemeral Pi-native tool projection for the operator-facing child page. */
|
|
53
|
+
readonly toolRenderer?: AgentToolRenderer;
|
|
54
|
+
/** Cleanup receipt populated when scope teardown cannot prove safety. */
|
|
55
|
+
readonly cleanupReceipt?: () => SubagentCleanupReceipt | undefined;
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
export interface SubagentBackend {
|