@dreki-gg/pi-subagent 0.8.1 → 0.8.3
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/CHANGELOG.md +22 -0
- package/extensions/subagent/index.ts +52 -30
- package/extensions/subagent/spawn-utils.ts +2 -1
- package/package.json +1 -1
- package/docs/plans/01_structured-handoffs-and-synthesis.plan.md +0 -376
- package/docs/plans/02_clean-context-review-loop.plan.md +0 -240
- package/docs/plans/03_parallel-recon-single-writer-docs.plan.md +0 -199
- package/docs/plans/04_manager-workflow.plan.md +0 -248
- package/docs/plans/05_advisor-routing.plan.md +0 -244
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @dreki-gg/pi-subagent
|
|
2
2
|
|
|
3
|
+
## 0.8.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`6f0b219`](https://github.com/dreki-gg/pi-extensions/commit/6f0b219ac357ce1607a7a8211fd1c66bd35c62f1) Thanks [@jalbarrang](https://github.com/jalbarrang)! - fix(subagent): resolve TDZ crash when onMessage/onToolResult callbacks fire before spawnResult is assigned
|
|
8
|
+
|
|
9
|
+
Previously, `runSingleAgent` declared `const spawnResult = await spawnPiAgent({...})` and referenced `spawnResult` inside the `onMessage`/`onToolResult` callbacks. Since callbacks fire during the await (before the const is assigned), this caused a `ReferenceError: Cannot access 'spawnResult' before initialization`. Now uses `let` with a guard to safely accumulate messages when the result is not yet available.
|
|
10
|
+
|
|
11
|
+
## 0.8.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [`32797ff`](https://github.com/dreki-gg/pi-extensions/commit/32797ff18d968e22c6c44e95c46e3393d8928cef) Thanks [@jalbarrang](https://github.com/jalbarrang)! - feat(plan-mode): add Windows compatibility — replace Unix shell commands with cross-platform Bun/Node APIs
|
|
16
|
+
|
|
17
|
+
Plan-mode no longer shells out to `cat`, `bash`, or `mkdir` via `pi.exec()`. File I/O now uses `Bun.file()` / `Bun.write()` and `node:fs/promises` `mkdir`, making the extension fully cross-platform. Destructive and safe command pattern lists now include Windows equivalents (`del`, `rd`, `copy`, `move`, `powershell`, `dir`, `where`, `tasklist`, etc.).
|
|
18
|
+
|
|
19
|
+
Also fixes Windows compatibility in three other packages:
|
|
20
|
+
|
|
21
|
+
- **browser-tools**: `spawn` now uses `shell: true` on Windows so `.cmd` wrappers resolve correctly; `shellEscape` uses double-quote style on Windows; install guidance is platform-aware (Homebrew shown only on macOS).
|
|
22
|
+
- **subagent**: `spawn` uses `shell: true` on Windows when the command is bare `pi`, allowing `pi.cmd` resolution.
|
|
23
|
+
- **lsp**: `globalConfigPath()` now uses `os.homedir()` on Windows instead of the unreliable `process.env.HOME`.
|
|
24
|
+
|
|
3
25
|
## 0.8.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -27,14 +27,16 @@ import {
|
|
|
27
27
|
SettingsManager,
|
|
28
28
|
} from '@earendil-works/pi-coding-agent';
|
|
29
29
|
import type { ResolvedPaths } from '@earendil-works/pi-coding-agent';
|
|
30
|
-
import { Box, Container, Markdown, Spacer, Text, type AutocompleteItem } from '@earendil-works/pi-tui';
|
|
31
|
-
import { Type } from 'typebox';
|
|
32
30
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
Box,
|
|
32
|
+
Container,
|
|
33
|
+
Markdown,
|
|
34
|
+
Spacer,
|
|
35
|
+
Text,
|
|
36
|
+
type AutocompleteItem,
|
|
37
|
+
} from '@earendil-works/pi-tui';
|
|
38
|
+
import { Type } from 'typebox';
|
|
39
|
+
import { type AgentConfig, type AgentScope, type AgentSource, discoverAgents } from './agents.js';
|
|
38
40
|
import { formatRunAgentUsage, parseRunAgentArgs } from './run-agent-args.js';
|
|
39
41
|
import { runAgent } from './agent-runner.js';
|
|
40
42
|
import { extractRecentConversation } from './synthesis.js';
|
|
@@ -247,7 +249,6 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
|
|
|
247
249
|
return results;
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
|
|
251
252
|
type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
|
|
252
253
|
|
|
253
254
|
async function runSingleAgent(
|
|
@@ -303,7 +304,8 @@ async function runSingleAgent(
|
|
|
303
304
|
}
|
|
304
305
|
};
|
|
305
306
|
|
|
306
|
-
|
|
307
|
+
let spawnResult: Awaited<ReturnType<typeof spawnPiAgent>> | undefined;
|
|
308
|
+
spawnResult = await spawnPiAgent({
|
|
307
309
|
cwd: cwd ?? defaultCwd,
|
|
308
310
|
agentName: agent.name,
|
|
309
311
|
task,
|
|
@@ -313,15 +315,23 @@ async function runSingleAgent(
|
|
|
313
315
|
tools: agent.tools,
|
|
314
316
|
signal,
|
|
315
317
|
onMessage: (msg) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
if (!spawnResult) {
|
|
319
|
+
currentResult.messages = [...currentResult.messages, msg];
|
|
320
|
+
} else {
|
|
321
|
+
currentResult.messages = spawnResult.messages;
|
|
322
|
+
currentResult.usage = spawnResult.usage;
|
|
323
|
+
currentResult.model = spawnResult.model ?? currentResult.model;
|
|
324
|
+
currentResult.stopReason = spawnResult.stopReason;
|
|
325
|
+
currentResult.errorMessage = spawnResult.errorMessage;
|
|
326
|
+
}
|
|
321
327
|
emitUpdate();
|
|
322
328
|
},
|
|
323
|
-
onToolResult: () => {
|
|
324
|
-
|
|
329
|
+
onToolResult: (msg) => {
|
|
330
|
+
if (!spawnResult) {
|
|
331
|
+
currentResult.messages = [...currentResult.messages, msg];
|
|
332
|
+
} else {
|
|
333
|
+
currentResult.messages = spawnResult.messages;
|
|
334
|
+
}
|
|
325
335
|
emitUpdate();
|
|
326
336
|
},
|
|
327
337
|
});
|
|
@@ -373,13 +383,19 @@ const SubagentParams = Type.Object({
|
|
|
373
383
|
Type.String({ description: 'Optional default model override for the run or all steps/tasks' }),
|
|
374
384
|
),
|
|
375
385
|
thinking: Type.Optional(
|
|
376
|
-
Type.String({
|
|
386
|
+
Type.String({
|
|
387
|
+
description: 'Optional default reasoning level override for the run or all steps/tasks',
|
|
388
|
+
}),
|
|
377
389
|
),
|
|
378
390
|
tasks: Type.Optional(
|
|
379
|
-
Type.Array(TaskItem, {
|
|
391
|
+
Type.Array(TaskItem, {
|
|
392
|
+
description: 'Array of {agent, task, model?, thinking?} for parallel execution',
|
|
393
|
+
}),
|
|
380
394
|
),
|
|
381
395
|
chain: Type.Optional(
|
|
382
|
-
Type.Array(ChainItem, {
|
|
396
|
+
Type.Array(ChainItem, {
|
|
397
|
+
description: 'Array of {agent, task, model?, thinking?} for sequential execution',
|
|
398
|
+
}),
|
|
383
399
|
),
|
|
384
400
|
agentScope: Type.Optional(AgentScopeSchema),
|
|
385
401
|
confirmProjectAgents: Type.Optional(
|
|
@@ -424,9 +440,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
424
440
|
if (!trimmedEnd) return replacement;
|
|
425
441
|
if (/\s$/.test(argumentText)) return `${trimmedEnd} ${replacement}`;
|
|
426
442
|
const lastSpace = trimmedEnd.lastIndexOf(' ');
|
|
427
|
-
return lastSpace === -1
|
|
428
|
-
? replacement
|
|
429
|
-
: `${trimmedEnd.slice(0, lastSpace + 1)}${replacement}`;
|
|
443
|
+
return lastSpace === -1 ? replacement : `${trimmedEnd.slice(0, lastSpace + 1)}${replacement}`;
|
|
430
444
|
}
|
|
431
445
|
|
|
432
446
|
function buildArgumentCompletions(
|
|
@@ -456,9 +470,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
456
470
|
return discoverAgents(autocompleteCwd, scope, resolvedPaths).agents;
|
|
457
471
|
}
|
|
458
472
|
|
|
459
|
-
async function getAutocompleteModels(
|
|
460
|
-
|
|
461
|
-
|
|
473
|
+
async function getAutocompleteModels(
|
|
474
|
+
scope: AgentScope,
|
|
475
|
+
): Promise<Array<{ value: string; label?: string; description?: string }>> {
|
|
462
476
|
const items = new Map<string, { value: string; label?: string; description?: string }>();
|
|
463
477
|
|
|
464
478
|
try {
|
|
@@ -1238,7 +1252,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1238
1252
|
title,
|
|
1239
1253
|
modeLine,
|
|
1240
1254
|
theme.fg('muted', `Task: ${details.task}`),
|
|
1241
|
-
failed && details.errorMessage
|
|
1255
|
+
failed && details.errorMessage
|
|
1256
|
+
? theme.fg('error', `Error: ${details.errorMessage}`)
|
|
1257
|
+
: previewLines,
|
|
1242
1258
|
usageLine ? theme.fg('dim', usageLine) : undefined,
|
|
1243
1259
|
theme.fg('muted', '(Ctrl+O to expand)'),
|
|
1244
1260
|
]
|
|
@@ -1347,7 +1363,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1347
1363
|
{ value: '--model', description: 'Override the agent model for this run' },
|
|
1348
1364
|
{ value: '--thinking', description: 'Override the reasoning level for this run' },
|
|
1349
1365
|
{ value: '--reasoning-level', description: 'Alias for --thinking' },
|
|
1350
|
-
{
|
|
1366
|
+
{
|
|
1367
|
+
value: '--yes-project-agents',
|
|
1368
|
+
description: 'Skip the project-agent trust confirmation prompt',
|
|
1369
|
+
},
|
|
1351
1370
|
...agents.map((agent) => ({
|
|
1352
1371
|
value: agent.name,
|
|
1353
1372
|
label: agent.name,
|
|
@@ -1363,7 +1382,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1363
1382
|
return;
|
|
1364
1383
|
}
|
|
1365
1384
|
|
|
1366
|
-
const { agentName, explicitTask, agentScope, confirmProjectAgents, model, thinking } =
|
|
1385
|
+
const { agentName, explicitTask, agentScope, confirmProjectAgents, model, thinking } =
|
|
1386
|
+
parsed.options;
|
|
1367
1387
|
if (!agentName) {
|
|
1368
1388
|
ctx.ui.notify(formatRunAgentUsage(), 'warning');
|
|
1369
1389
|
return;
|
|
@@ -1394,7 +1414,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1394
1414
|
const conversation = extractRecentConversation(ctx);
|
|
1395
1415
|
const task = buildRunAgentTask(conversation, explicitTask);
|
|
1396
1416
|
if (!task) {
|
|
1397
|
-
ctx.ui.notify(
|
|
1417
|
+
ctx.ui.notify(
|
|
1418
|
+
`No conversation context and no task specified.\n\n${formatRunAgentUsage()}`,
|
|
1419
|
+
'warning',
|
|
1420
|
+
);
|
|
1398
1421
|
return;
|
|
1399
1422
|
}
|
|
1400
1423
|
|
|
@@ -1486,5 +1509,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
1486
1509
|
await runAndReport(ctx.cwd, ctx.ui, (message) => pi.sendMessage(message), false);
|
|
1487
1510
|
},
|
|
1488
1511
|
});
|
|
1489
|
-
|
|
1490
1512
|
}
|
|
@@ -114,9 +114,10 @@ export async function spawnPiAgent(options: SpawnPiAgentOptions): Promise<SpawnP
|
|
|
114
114
|
|
|
115
115
|
const exitCode = await new Promise<number>((resolve) => {
|
|
116
116
|
const invocation = getPiInvocation(args);
|
|
117
|
+
const needsShell = process.platform === 'win32' && invocation.command === 'pi';
|
|
117
118
|
const proc = spawn(invocation.command, invocation.args, {
|
|
118
119
|
cwd: options.cwd,
|
|
119
|
-
shell:
|
|
120
|
+
shell: needsShell,
|
|
120
121
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
121
122
|
});
|
|
122
123
|
let buffer = '';
|
package/package.json
CHANGED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "Structured handoffs + synthesis for chain workflows"
|
|
3
|
-
overview: "Introduce a first-class handoff format for `@dreki-gg/pi-subagent` so chained agents pass compact, decision-rich context instead of raw `{previous}` markdown blobs. This slice should improve coherence across isolated agents without changing the core single/parallel/chain execution model."
|
|
4
|
-
todo:
|
|
5
|
-
- id: "structured-handoffs-1"
|
|
6
|
-
task: "Add a handoff module that normalizes agent output into a compact envelope with goal, decisions, constraints, files, symbols, and raw output"
|
|
7
|
-
status: pending
|
|
8
|
-
- id: "structured-handoffs-2"
|
|
9
|
-
task: "Thread the structured handoff through chain execution in `extensions/subagent/index.ts` while preserving `{previous}` compatibility"
|
|
10
|
-
status: pending
|
|
11
|
-
- id: "structured-handoffs-3"
|
|
12
|
-
task: "Tighten bundled prompts so scout/planner/worker/docs-scout emit sections the handoff module can reliably summarize"
|
|
13
|
-
status: pending
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
# Goal
|
|
17
|
-
|
|
18
|
-
Make chained subagent workflows in `packages/subagent` pass structured, compact handoffs instead of blindly injecting the previous agent’s final markdown blob into the next task.
|
|
19
|
-
|
|
20
|
-
# Context
|
|
21
|
-
|
|
22
|
-
- Parent rationale: apply the “single writer, many thinkers” / “context engineering over prompt gimmicks” learnings we discussed for `@dreki-gg/pi-subagent`.
|
|
23
|
-
- Module root: `packages/subagent`
|
|
24
|
-
- This slice is infrastructure. Later slices like clean-context review, manager orchestration, and advisor routing should be able to reuse the same handoff format, but this plan must **not** assume those slices already exist.
|
|
25
|
-
- Existing repo convention for package-local planning docs lives under `packages/<pkg>/docs/plans/` (see `packages/browser-tools/docs/plans/`).
|
|
26
|
-
|
|
27
|
-
## What exists
|
|
28
|
-
|
|
29
|
-
Current relevant file tree on disk:
|
|
30
|
-
|
|
31
|
-
- `packages/subagent/package.json`
|
|
32
|
-
- `packages/subagent/tsconfig.json`
|
|
33
|
-
- `packages/subagent/README.md`
|
|
34
|
-
- `packages/subagent/CHANGELOG.md`
|
|
35
|
-
- `packages/subagent/extensions/subagent/index.ts`
|
|
36
|
-
- `packages/subagent/extensions/subagent/agents.ts`
|
|
37
|
-
- `packages/subagent/extensions/subagent/agent-runner.ts`
|
|
38
|
-
- `packages/subagent/extensions/subagent/agent-runner-types.ts`
|
|
39
|
-
- `packages/subagent/extensions/subagent/agent-result-utils.ts`
|
|
40
|
-
- `packages/subagent/extensions/subagent/run-agent-args.ts`
|
|
41
|
-
- `packages/subagent/extensions/subagent/synthesis.ts`
|
|
42
|
-
- `packages/subagent/prompts/docs-scout.md`
|
|
43
|
-
- `packages/subagent/prompts/planner.md`
|
|
44
|
-
- `packages/subagent/prompts/reviewer.md`
|
|
45
|
-
- `packages/subagent/prompts/scout.md`
|
|
46
|
-
- `packages/subagent/prompts/ux-designer.md`
|
|
47
|
-
- `packages/subagent/prompts/worker.md`
|
|
48
|
-
- `packages/subagent/prompts/implement.md`
|
|
49
|
-
- `packages/subagent/prompts/implement-and-review.md`
|
|
50
|
-
- `packages/subagent/prompts/scout-and-plan.md`
|
|
51
|
-
- `packages/subagent/skills/spawn-subagents/SKILL.md`
|
|
52
|
-
- `packages/subagent/skills/write-an-agent/SKILL.md`
|
|
53
|
-
|
|
54
|
-
Actual current behavior on disk:
|
|
55
|
-
|
|
56
|
-
- Chain mode is still raw text plumbing. In `packages/subagent/extensions/subagent/index.ts:658-716`, each step computes `taskWithContext = step.task.replace(/\{previous\}/g, previousOutput)` and then updates `previousOutput = getFinalOutput(result.messages)`. There is no typed handoff object, parser, or summarizer.
|
|
57
|
-
- `getFinalOutput()` in `packages/subagent/extensions/subagent/index.ts:204-214` returns only the last assistant text block, so any rich structure from tool calls or earlier assistant messages is discarded before handoff.
|
|
58
|
-
- `run-agent` synthesizes main-conversation context via `extractRecentConversation()` and `buildRunAgentTask()` (`index.ts:1215-1229`, `index.ts:1557-1559`), but that synthesis is only used for direct `/run-agent` entry and not for agent-to-agent handoff.
|
|
59
|
-
- `packages/subagent/extensions/subagent/synthesis.ts:5-38` already defines a strong section-oriented synthesis instruction (`Goal`, `Decisions`, `Constraints`, `Architecture`, `Open Questions`, `Intent`), but those sections are not reused anywhere in chain orchestration.
|
|
60
|
-
- Bundled prompt templates still explicitly rely on `{previous}` text substitution:
|
|
61
|
-
- `packages/subagent/prompts/implement.md:6-10`
|
|
62
|
-
- `packages/subagent/prompts/scout-and-plan.md:6-9`
|
|
63
|
-
- `packages/subagent/prompts/implement-and-review.md:6-10`
|
|
64
|
-
- The `spawn-subagents` skill already hints at the desired future shape: it says to “return a compact synthesis to the main thread” and to pass previous outputs “verbatim or as a tight structured summary” (`packages/subagent/skills/spawn-subagents/SKILL.md:31-35`, `:49-54`). That guidance is not enforced in code.
|
|
65
|
-
- Bundled agents already emit semi-structured markdown, but the section names are inconsistent across roles:
|
|
66
|
-
- `scout` => `Files Retrieved`, `Key Code`, `Architecture`, `Start Here`
|
|
67
|
-
- `docs-scout` => `Libraries`, `Key Documentation`, `Integration Notes`, `Recommended Next Step`
|
|
68
|
-
- `planner` => `Goal`, `Plan`, `Files to Modify`, `New Files`, `Risks`
|
|
69
|
-
- `worker` => `Completed`, `Files Changed`, `Notes`
|
|
70
|
-
- There is no test directory in `packages/subagent`; current validation is package typechecking (`packages/subagent/package.json`, `packages/subagent/tsconfig.json`).
|
|
71
|
-
|
|
72
|
-
# API inventory
|
|
73
|
-
|
|
74
|
-
## Existing public types and helpers the implementation will touch
|
|
75
|
-
|
|
76
|
-
From `packages/subagent/extensions/subagent/agents.ts`:
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
export type AgentScope = 'user' | 'project' | 'both';
|
|
80
|
-
export type AgentSource = 'bundled' | 'user' | 'project' | 'package';
|
|
81
|
-
export type AgentSessionStrategy = 'inline' | 'fork-at';
|
|
82
|
-
|
|
83
|
-
export interface AgentConfig {
|
|
84
|
-
name: string;
|
|
85
|
-
description: string;
|
|
86
|
-
tools?: string[];
|
|
87
|
-
model?: string;
|
|
88
|
-
thinking?: string;
|
|
89
|
-
sessionStrategy?: AgentSessionStrategy;
|
|
90
|
-
systemPrompt: string;
|
|
91
|
-
source: AgentSource;
|
|
92
|
-
filePath: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface AgentDiscoveryResult {
|
|
96
|
-
agents: AgentConfig[];
|
|
97
|
-
projectAgentsDir: string | null;
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
From `packages/subagent/extensions/subagent/agent-runner-types.ts`:
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
export interface UsageStats {
|
|
105
|
-
input: number;
|
|
106
|
-
output: number;
|
|
107
|
-
cacheRead: number;
|
|
108
|
-
cacheWrite: number;
|
|
109
|
-
cost: number;
|
|
110
|
-
contextTokens: number;
|
|
111
|
-
turns: number;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface AgentResult {
|
|
115
|
-
agent: string;
|
|
116
|
-
task: string;
|
|
117
|
-
exitCode: number;
|
|
118
|
-
messages: Message[];
|
|
119
|
-
stderr: string;
|
|
120
|
-
usage: UsageStats;
|
|
121
|
-
model?: string;
|
|
122
|
-
stopReason?: string;
|
|
123
|
-
errorMessage?: string;
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
From `packages/subagent/extensions/subagent/agent-runner.ts`:
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
export type OnPhaseUpdate = (
|
|
131
|
-
phaseName: string,
|
|
132
|
-
agentName: string,
|
|
133
|
-
result: AgentResult,
|
|
134
|
-
) => void;
|
|
135
|
-
|
|
136
|
-
export interface RunAgentOptions {
|
|
137
|
-
agentScope?: AgentScope;
|
|
138
|
-
cwd?: string;
|
|
139
|
-
onUpdate?: OnPhaseUpdate;
|
|
140
|
-
phaseName?: string;
|
|
141
|
-
signal?: AbortSignal;
|
|
142
|
-
resolvedPaths?: ResolvedPaths;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export async function runAgent(
|
|
146
|
-
cwd: string,
|
|
147
|
-
agentName: string,
|
|
148
|
-
task: string,
|
|
149
|
-
options: RunAgentOptions = {},
|
|
150
|
-
): Promise<AgentResult>
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
From `packages/subagent/extensions/subagent/synthesis.ts`:
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
export function extractRecentConversation(
|
|
157
|
-
ctx: ExtensionCommandContext,
|
|
158
|
-
): string
|
|
159
|
-
|
|
160
|
-
export function buildSynthesisPrompt(
|
|
161
|
-
conversation: string,
|
|
162
|
-
explicitTask?: string,
|
|
163
|
-
): string
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Existing internal chain data shape in `index.ts`
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
interface SingleResult {
|
|
170
|
-
agent: string;
|
|
171
|
-
agentSource: AgentSource | 'unknown';
|
|
172
|
-
task: string;
|
|
173
|
-
exitCode: number;
|
|
174
|
-
messages: Message[];
|
|
175
|
-
stderr: string;
|
|
176
|
-
usage: UsageStats;
|
|
177
|
-
model?: string;
|
|
178
|
-
stopReason?: string;
|
|
179
|
-
errorMessage?: string;
|
|
180
|
-
step?: number;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
interface SubagentDetails {
|
|
184
|
-
mode: 'single' | 'parallel' | 'chain';
|
|
185
|
-
agentScope: AgentScope;
|
|
186
|
-
projectAgentsDir: string | null;
|
|
187
|
-
results: SingleResult[];
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
These are currently private to `index.ts`, but the new handoff helper can either:
|
|
192
|
-
- accept a narrow `Pick<SingleResult, ...>` shape, or
|
|
193
|
-
- define its own exported `HandoffSource` input type to avoid importing internal UI state.
|
|
194
|
-
|
|
195
|
-
## Proposed new handoff shape for this slice
|
|
196
|
-
|
|
197
|
-
Create a dedicated module for this instead of baking ad hoc string munging into `index.ts`:
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
export interface HandoffFileRef {
|
|
201
|
-
path: string;
|
|
202
|
-
notes?: string;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export interface HandoffEnvelope {
|
|
206
|
-
version: 'subagent-handoff/v1';
|
|
207
|
-
sourceAgent: string;
|
|
208
|
-
sourceStep?: number;
|
|
209
|
-
task: string;
|
|
210
|
-
summary: string;
|
|
211
|
-
goal?: string;
|
|
212
|
-
decisions: string[];
|
|
213
|
-
constraints: string[];
|
|
214
|
-
files: HandoffFileRef[];
|
|
215
|
-
symbols: string[];
|
|
216
|
-
openQuestions: string[];
|
|
217
|
-
rawOutput: string;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export interface RenderHandoffOptions {
|
|
221
|
-
includeRawOutput?: boolean;
|
|
222
|
-
maxRawChars?: number;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function buildHandoffFromResult(input: {
|
|
226
|
-
agent: string;
|
|
227
|
-
step?: number;
|
|
228
|
-
task: string;
|
|
229
|
-
output: string;
|
|
230
|
-
}): HandoffEnvelope
|
|
231
|
-
|
|
232
|
-
export function renderHandoffForPrompt(
|
|
233
|
-
handoff: HandoffEnvelope,
|
|
234
|
-
options?: RenderHandoffOptions,
|
|
235
|
-
): string
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
The parser can be heuristic and markdown-based. It does **not** need to become a full AST parser. The goal is reliable compaction, not perfect semantic extraction.
|
|
239
|
-
|
|
240
|
-
# Tasks
|
|
241
|
-
|
|
242
|
-
## 1. Add a handoff module that normalizes agent output into a compact envelope
|
|
243
|
-
|
|
244
|
-
### Files
|
|
245
|
-
- Create `packages/subagent/extensions/subagent/handoffs.ts`
|
|
246
|
-
|
|
247
|
-
### What to add
|
|
248
|
-
- Export a small, typed handoff model (see `Proposed new handoff shape` above).
|
|
249
|
-
- Implement markdown-section extraction that recognizes the current bundled agent contracts:
|
|
250
|
-
- `## Goal`
|
|
251
|
-
- `## Decisions`
|
|
252
|
-
- `## Constraints`
|
|
253
|
-
- `## Architecture`
|
|
254
|
-
- `## Open Questions`
|
|
255
|
-
- `## Plan`
|
|
256
|
-
- `## Files Retrieved`
|
|
257
|
-
- `## Files to Modify`
|
|
258
|
-
- `## New Files`
|
|
259
|
-
- `## Files Changed`
|
|
260
|
-
- `## Notes`
|
|
261
|
-
- `## Risks`
|
|
262
|
-
- `## Recommended Next Step`
|
|
263
|
-
- Build a compact summary by prioritizing the most useful sections instead of returning the entire raw output.
|
|
264
|
-
- Extract file paths by scanning markdown bullets/code spans for repo-looking paths (e.g. `` `packages/subagent/...` ``).
|
|
265
|
-
- Extract symbols conservatively from code fences or bullets when they are clearly named functions/types; do not overfit.
|
|
266
|
-
- Always keep `rawOutput` so callers can still include the source text when needed.
|
|
267
|
-
|
|
268
|
-
### Code sketch
|
|
269
|
-
|
|
270
|
-
```ts
|
|
271
|
-
const SECTION_RE = /^##\s+(.+)$/gm;
|
|
272
|
-
|
|
273
|
-
function splitMarkdownSections(markdown: string): Map<string, string> {
|
|
274
|
-
// return section title -> body
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export function buildHandoffFromResult(input: {
|
|
278
|
-
agent: string;
|
|
279
|
-
step?: number;
|
|
280
|
-
task: string;
|
|
281
|
-
output: string;
|
|
282
|
-
}): HandoffEnvelope {
|
|
283
|
-
const sections = splitMarkdownSections(input.output);
|
|
284
|
-
return {
|
|
285
|
-
version: 'subagent-handoff/v1',
|
|
286
|
-
sourceAgent: input.agent,
|
|
287
|
-
sourceStep: input.step,
|
|
288
|
-
task: input.task,
|
|
289
|
-
summary: ...,
|
|
290
|
-
goal: ...,
|
|
291
|
-
decisions: ...,
|
|
292
|
-
constraints: ...,
|
|
293
|
-
files: ...,
|
|
294
|
-
symbols: ...,
|
|
295
|
-
openQuestions: ...,
|
|
296
|
-
rawOutput: input.output,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### Notes
|
|
302
|
-
- Keep this module dependency-free.
|
|
303
|
-
- Do not move UI rendering logic into this file.
|
|
304
|
-
- The formatter should emit readable markdown because existing prompt templates still use string substitution.
|
|
305
|
-
|
|
306
|
-
## 2. Thread the structured handoff through chain execution in `extensions/subagent/index.ts`
|
|
307
|
-
|
|
308
|
-
### Files
|
|
309
|
-
- Modify `packages/subagent/extensions/subagent/index.ts`
|
|
310
|
-
|
|
311
|
-
### What to change
|
|
312
|
-
- Import the new handoff helper.
|
|
313
|
-
- In the chain path (`index.ts:658-716`), replace the raw `previousOutput` update with a rendered handoff string built from the prior step’s final output.
|
|
314
|
-
- Preserve the existing `{previous}` placeholder contract so current prompt templates still work, but change what gets injected:
|
|
315
|
-
- before: raw final assistant text
|
|
316
|
-
- after: a compact structured markdown summary plus optional truncated raw output
|
|
317
|
-
- Keep error handling behavior unchanged.
|
|
318
|
-
- Keep streaming UI behavior unchanged.
|
|
319
|
-
|
|
320
|
-
### Implementation rules
|
|
321
|
-
- Do **not** change single-mode or parallel-mode semantics in this slice.
|
|
322
|
-
- Do **not** add a new tool mode; keep `single`, `parallel`, and `chain` only.
|
|
323
|
-
- Avoid coupling the handoff module to `Message[]`; extract final text in `index.ts` and pass plain strings into the helper.
|
|
324
|
-
- If the previous step returned no final text, fall back to `'(no output)'` exactly as current code does.
|
|
325
|
-
|
|
326
|
-
## 3. Tighten bundled prompts so handoffs have stable sections to summarize
|
|
327
|
-
|
|
328
|
-
### Files
|
|
329
|
-
- Modify `packages/subagent/prompts/scout.md`
|
|
330
|
-
- Modify `packages/subagent/prompts/docs-scout.md`
|
|
331
|
-
- Modify `packages/subagent/prompts/planner.md`
|
|
332
|
-
- Modify `packages/subagent/prompts/worker.md`
|
|
333
|
-
- Modify `packages/subagent/prompts/implement.md`
|
|
334
|
-
- Modify `packages/subagent/prompts/scout-and-plan.md`
|
|
335
|
-
|
|
336
|
-
### What to change
|
|
337
|
-
- Keep the roles the same, but make their output contracts easier to summarize reliably.
|
|
338
|
-
- Prefer stable section names over freeform prose.
|
|
339
|
-
- Add a small “Handoff” / “Next step” section where helpful instead of forcing the summarizer to infer everything from generic notes.
|
|
340
|
-
|
|
341
|
-
### Recommended section adjustments
|
|
342
|
-
- `scout.md`: keep `Files Retrieved` / `Key Code` / `Architecture`, and add `## Constraints or Unknowns`
|
|
343
|
-
- `docs-scout.md`: keep `Libraries` / `Key Documentation` / `Integration Notes`, and make `Recommended Next Step` mandatory
|
|
344
|
-
- `planner.md`: keep `Goal` / `Plan` / `Files to Modify` / `New Files` / `Risks`; optionally add `## Constraints`
|
|
345
|
-
- `worker.md`: keep `Completed` / `Files Changed` / `Notes`, but require a short bullet list of decisions made and unresolved concerns
|
|
346
|
-
- Prompt templates should mention that `{previous}` now contains a structured handoff, not an unbounded transcript dump
|
|
347
|
-
|
|
348
|
-
# Files to create
|
|
349
|
-
|
|
350
|
-
- `packages/subagent/extensions/subagent/handoffs.ts`
|
|
351
|
-
|
|
352
|
-
# Files to modify
|
|
353
|
-
|
|
354
|
-
- `packages/subagent/extensions/subagent/index.ts` — switch chain substitution from raw final text to rendered structured handoff
|
|
355
|
-
- `packages/subagent/prompts/scout.md` — make scout output easier to summarize deterministically
|
|
356
|
-
- `packages/subagent/prompts/docs-scout.md` — make docs handoff more consistent
|
|
357
|
-
- `packages/subagent/prompts/planner.md` — preserve plan structure but expose constraints explicitly
|
|
358
|
-
- `packages/subagent/prompts/worker.md` — add explicit decision / unresolved sections for downstream agents
|
|
359
|
-
- `packages/subagent/prompts/implement.md` — document structured `{previous}` expectations
|
|
360
|
-
- `packages/subagent/prompts/scout-and-plan.md` — document structured `{previous}` expectations
|
|
361
|
-
|
|
362
|
-
# Testing notes
|
|
363
|
-
|
|
364
|
-
- Run `bun run --filter '@dreki-gg/pi-subagent' typecheck` after adding `handoffs.ts` and updating imports.
|
|
365
|
-
- Manually inspect the rendered handoff string by running a small local chain (for example, a trivial `scout -> planner` task) and confirm that the second step receives a compact summary instead of a huge raw blob.
|
|
366
|
-
- Verify that existing prompt templates still work without changing the user-facing `{previous}` placeholder name.
|
|
367
|
-
- Do not add runtime-only assumptions about sibling plans. This slice must provide value on its own.
|
|
368
|
-
|
|
369
|
-
# Patterns to follow
|
|
370
|
-
|
|
371
|
-
- `packages/subagent/extensions/subagent/synthesis.ts:5-38` — section-oriented synthesis prompt structure worth mirroring in handoffs
|
|
372
|
-
- `packages/subagent/extensions/subagent/index.ts:658-716` — existing chain control flow to preserve
|
|
373
|
-
- `packages/subagent/extensions/subagent/index.ts:204-214` — current final-output extraction behavior to wrap, not duplicate all over the package
|
|
374
|
-
- `packages/subagent/skills/spawn-subagents/SKILL.md:31-35` — “compact synthesis” guidance already present in the skill
|
|
375
|
-
- `packages/subagent/skills/spawn-subagents/SKILL.md:49-54` — existing advice about structured summaries
|
|
376
|
-
- `packages/subagent/skills/write-an-agent/SKILL.md:15-20` and `:62-67` — output contracts should stay sharp and structured
|