@clanker-code/pi-subagents 0.10.5 → 0.10.7
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/AGENTS.md +11 -1
- package/CHANGELOG.md +12 -1
- package/README.md +6 -5
- package/RELEASE.md +18 -12
- package/dist/agent-manager.d.ts +4 -0
- package/dist/agent-manager.js +4 -0
- package/dist/bounded-output.d.ts +16 -0
- package/dist/bounded-output.js +26 -0
- package/dist/index.js +30 -14
- package/dist/notifications.js +7 -5
- package/dist/peek.js +24 -11
- package/package.json +1 -1
- package/src/agent-manager.ts +8 -0
- package/src/bounded-output.ts +35 -0
- package/src/index.ts +30 -14
- package/src/notifications.ts +7 -5
- package/src/peek.ts +25 -12
- package/.plans/PLAN-next-changes.md +0 -183
- package/.plans/README.md +0 -14
- package/reviews/proposal-structured-output-schema.md +0 -135
- package/reviews/recursive-subagent-widget-preview-rev2.png +0 -0
- package/reviews/recursive-subagent-widget-preview.html +0 -137
- package/reviews/recursive-subagent-widget-preview.png +0 -0
- package/reviews/subagent-features-comparison.md +0 -350
package/src/notifications.ts
CHANGED
|
@@ -30,12 +30,14 @@ export function formatTaskNotification(record: AgentRecord, resultMaxLen: number
|
|
|
30
30
|
|
|
31
31
|
const resultPreview = record.result
|
|
32
32
|
? record.result.length > resultMaxLen
|
|
33
|
-
? record.result.slice(0, resultMaxLen) +
|
|
33
|
+
? record.result.slice(0, resultMaxLen) + (record.outputFile
|
|
34
|
+
? "\n...(truncated, use get_subagent_result for a bounded preview or inspect the transcript file)"
|
|
35
|
+
: "\n...(truncated, use get_subagent_result for a bounded preview)")
|
|
34
36
|
: record.result
|
|
35
37
|
: "No output.";
|
|
36
38
|
const fullOutputInstruction = record.outputFile
|
|
37
|
-
? `Read
|
|
38
|
-
: `Read
|
|
39
|
+
? `Read a bounded preview with get_subagent_result for agent ${record.id}, or inspect the full transcript file: ${record.outputFile}`
|
|
40
|
+
: `Read a bounded preview with get_subagent_result for agent ${record.id}.`;
|
|
39
41
|
|
|
40
42
|
return [
|
|
41
43
|
`<task-notification>`,
|
|
@@ -106,8 +108,8 @@ export function registerSubagentNotificationRenderer(pi: ExtensionAPI): void {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
const fullOutputHint = d.outputFile
|
|
109
|
-
? `
|
|
110
|
-
: `
|
|
111
|
+
? `bounded preview: get_subagent_result ${d.id}; full transcript: ${d.outputFile}`
|
|
112
|
+
: `bounded preview: get_subagent_result ${d.id}`;
|
|
111
113
|
line += "\n " + theme.fg("muted", fullOutputHint);
|
|
112
114
|
|
|
113
115
|
return line;
|
package/src/peek.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { clampPeekLines, formatOutputFileHint, limitText, MAX_PEEK_CHARS, MAX_PEEK_LINES } from "./bounded-output.js";
|
|
18
19
|
import type { AgentRecord } from "./types.js";
|
|
19
20
|
|
|
20
21
|
export interface PeekOptions {
|
|
@@ -35,9 +36,6 @@ export interface PeekResult {
|
|
|
35
36
|
source: "outputFile" | "result";
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
/** Default number of tail lines when neither `after` nor `lines` is given. */
|
|
39
|
-
const DEFAULT_LINES = 20;
|
|
40
|
-
|
|
41
39
|
/**
|
|
42
40
|
* Produce a peek view of an agent's output. Returns null when there is no
|
|
43
41
|
* source content at all (the caller renders a "no output yet" message).
|
|
@@ -48,27 +46,39 @@ export function peekAgentOutput(record: AgentRecord, opts: PeekOptions = {}): Pe
|
|
|
48
46
|
|
|
49
47
|
const regex = opts.regex ? compileRegex(opts.regex) : undefined;
|
|
50
48
|
const after = typeof opts.after === "number" ? opts.after : -1;
|
|
51
|
-
const tail =
|
|
49
|
+
const tail = clampPeekLines(opts.lines);
|
|
52
50
|
|
|
53
51
|
// Index each source line with its original position (1-based for display).
|
|
54
52
|
const indexed = lines.map((text, i) => ({ no: i + 1, text }));
|
|
55
53
|
|
|
56
54
|
// Filter-then-select.
|
|
57
55
|
const filtered = regex ? indexed.filter((l) => regex.test(l.text)) : indexed;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
: filtered.slice(-tail);
|
|
56
|
+
const matching = after >= 0 ? filtered.filter((l) => l.no > after) : filtered;
|
|
57
|
+
const selected = after >= 0 ? matching.slice(0, MAX_PEEK_LINES) : matching.slice(-tail);
|
|
58
|
+
const lineLimited = matching.length > selected.length;
|
|
62
59
|
|
|
63
60
|
const totalLines = lines.length;
|
|
64
61
|
const isRunning = record.status === "running" || record.status === "queued";
|
|
65
62
|
const source: PeekResult["source"] =
|
|
66
63
|
isRunning && record.outputFile && existsSync(record.outputFile) ? "outputFile" : "result";
|
|
67
64
|
|
|
68
|
-
const header = buildHeader(opts, selected.length, totalLines, source);
|
|
65
|
+
const header = buildHeader(opts, selected.length, totalLines, source, tail, lineLimited, matching.length);
|
|
69
66
|
const body = selected.map((l) => `[${l.no}] ${l.text}`).join("\n");
|
|
67
|
+
const limited = limitText(body, MAX_PEEK_CHARS);
|
|
68
|
+
const lastLine = selected.at(-1)?.no;
|
|
69
|
+
const notices: string[] = [];
|
|
70
|
+
if (lineLimited && lastLine !== undefined) {
|
|
71
|
+
notices.push(`Peek limited to ${MAX_PEEK_LINES} lines from ${matching.length} matching lines. Use peek.after: ${lastLine} to continue.`);
|
|
72
|
+
}
|
|
73
|
+
if (limited.truncated) {
|
|
74
|
+
notices.push(`Peek output truncated by ${limited.omittedChars} chars. Use a smaller lines value or regex filter.${formatOutputFileHint(record.outputFile)}`);
|
|
75
|
+
}
|
|
70
76
|
|
|
71
|
-
return {
|
|
77
|
+
return {
|
|
78
|
+
text: `${header}\n\n${limited.text}${notices.length ? `\n\n---\n${notices.join("\n")}` : ""}`,
|
|
79
|
+
totalLines,
|
|
80
|
+
source,
|
|
81
|
+
};
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
/** Read the most useful text lines from the agent's output. */
|
|
@@ -141,13 +151,16 @@ function buildHeader(
|
|
|
141
151
|
shown: number,
|
|
142
152
|
total: number,
|
|
143
153
|
source: PeekResult["source"],
|
|
154
|
+
tail: number,
|
|
155
|
+
lineLimited: boolean,
|
|
156
|
+
matching: number,
|
|
144
157
|
): string {
|
|
145
158
|
const parts: string[] = [];
|
|
146
159
|
if (typeof opts.after === "number") {
|
|
147
160
|
parts.push(`after line number ${opts.after}`);
|
|
161
|
+
if (lineLimited) parts.push(`limited to ${MAX_PEEK_LINES} lines from ${matching} matching lines`);
|
|
148
162
|
} else {
|
|
149
|
-
|
|
150
|
-
parts.push(`last ${n} lines`);
|
|
163
|
+
parts.push(`last ${tail} lines`);
|
|
151
164
|
}
|
|
152
165
|
if (opts.regex) parts.push(`filtered by regex /${opts.regex}/`);
|
|
153
166
|
parts.push(`of ${total} total (${source === "outputFile" ? "live output file" : "result"})`);
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# Plan: Next pi-subagents improvements
|
|
2
|
-
|
|
3
|
-
This plan covers four coordinated changes:
|
|
4
|
-
1. **Abort-detach + interruptible/timeout wait** for `get_subagent_result wait:true` (Escape no longer wedges the turn; default 4.5 min timeout).
|
|
5
|
-
2. Configurable `waitTimeoutSeconds` setting (default 270s) exposed in `/agents → Settings`.
|
|
6
|
-
3. `get_subagent_result` peek/tail capability with line numbers, regex filter, and `after` line offset.
|
|
7
|
-
4. Description polish for `inherit_context` and `isolation: "worktree"`.
|
|
8
|
-
|
|
9
|
-
All changes are scoped to keep diffs minimal and upstream-merge-friendly.
|
|
10
|
-
|
|
11
|
-
### Key finding (abort-detach)
|
|
12
|
-
Background spawns already do NOT pass the parent abort signal to `manager.spawn()` — only the now-dead foreground path did. So background subagents are already detached from Escape. The Escape-wedge symptom comes from `get_subagent_result wait:true`, which awaits `record.promise` and ignores the parent `signal`. Fix: race the wait against (a) the parent abort `signal` and (b) the configurable timeout, returning current status on either WITHOUT aborting the subagent.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## 1. Settings foundation: configurable `waitTimeoutSeconds`
|
|
17
|
-
|
|
18
|
-
### Motivation
|
|
19
|
-
Users want to avoid typical 5-minute LLM cache expiry; 4.5 minutes is a safe default. We also want this exposed in `/agents → Settings` so it is discoverable and adjustable per project.
|
|
20
|
-
|
|
21
|
-
### Files touched
|
|
22
|
-
- `src/types.ts` — add `waitTimeoutSeconds` to `AgentRecord`? No, this is runtime/config, not per-agent. Keep it in settings only.
|
|
23
|
-
- `src/settings.ts`:
|
|
24
|
-
- Add `waitTimeoutSeconds?: number` to `SubagentsSettings`.
|
|
25
|
-
- Add `setWaitTimeoutSeconds: (seconds: number) => void` to `SettingsAppliers`.
|
|
26
|
-
- Add sanitize rule: integer, min 30, max 3600 (30s–1h). Default 270 (4.5m) when absent.
|
|
27
|
-
- Add `applySettings` wiring.
|
|
28
|
-
- `src/index.ts`:
|
|
29
|
-
- Add `let waitTimeoutSeconds = 270` and getter/setter.
|
|
30
|
-
- Add to `snapshotSettings()`.
|
|
31
|
-
- Add `/agents → Settings` item with id `waitTimeoutSeconds`.
|
|
32
|
-
- Wire `applyValue` for `waitTimeoutSeconds` (numeric prompt, 30–3600).
|
|
33
|
-
- Add to `applyAndEmitLoaded` appliers object.
|
|
34
|
-
- Pass timeout value into `get_subagent_result` execute closure.
|
|
35
|
-
|
|
36
|
-
### Return value / UI impact
|
|
37
|
-
- `get_subagent_result` `wait` description: mention current configured timeout.
|
|
38
|
-
- On timeout, return message:
|
|
39
|
-
```
|
|
40
|
-
Agent is still running after 4 minutes 30 seconds.
|
|
41
|
-
This wait timed out to avoid blocking the parent session longer than the configured limit.
|
|
42
|
-
Call get_subagent_result with wait: true again to keep waiting, or omit wait to check status.
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Tests
|
|
46
|
-
- `test/settings.test.ts` (if exists) or new assertions in `test/settings.test.ts`: sanitize boundaries.
|
|
47
|
-
- New/updated test for `get_subagent_result` timeout behavior using a mocked promise and fake timers.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 2. `get_subagent_result` peek parameter
|
|
52
|
-
|
|
53
|
-
### Motivation
|
|
54
|
-
Agents should be able to cheaply check recent output / logs without fetching the full result or conversation.
|
|
55
|
-
|
|
56
|
-
### Proposed schema
|
|
57
|
-
```ts
|
|
58
|
-
peek: Type.Optional(
|
|
59
|
-
Type.Object({
|
|
60
|
-
lines: Type.Optional(Type.Number({ minimum: 1, description: "Number of trailing lines to return. Default: 20." })),
|
|
61
|
-
regex: Type.Optional(Type.String({ description: "Optional regex filter. Only lines matching this regex are included." })),
|
|
62
|
-
after: Type.Optional(Type.Number({ minimum: 0, description: "Return all lines after this 0-based line index. Overrides lines when set." })),
|
|
63
|
-
}, {
|
|
64
|
-
description: "Return a peek (tail/filter/update) of the agent result or streaming output file. Ignored when verbose is true.",
|
|
65
|
-
}),
|
|
66
|
-
),
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Behavior
|
|
70
|
-
- `peek` is ignored when `verbose: true`.
|
|
71
|
-
- If `after` is set, return all lines with index > `after` (or >= `after+1`). Include line numbers.
|
|
72
|
-
- Else, return the last `lines` lines (default 20). Include line numbers.
|
|
73
|
-
- If `regex` is provided, filter matching lines **first**, then apply tail/after semantics. Include line numbers of the original source.
|
|
74
|
-
- Source precedence:
|
|
75
|
-
1. If agent is running and `record.outputFile` exists, read from the streaming output file. Parse JSONL and extract assistant/toolResult text (most useful live content).
|
|
76
|
-
2. Else if `record.result` exists, split it into lines.
|
|
77
|
-
3. Else return: "No output yet."
|
|
78
|
-
|
|
79
|
-
### Return format
|
|
80
|
-
```
|
|
81
|
-
Showing last 20 lines of agent output (line numbers from full output):
|
|
82
|
-
|
|
83
|
-
[42] some line
|
|
84
|
-
[43] another line
|
|
85
|
-
...
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
Use verbose: true for the full conversation, or omit peek for the complete result.
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
If `regex` is used, add: `(filtered by regex: /.../)`.
|
|
92
|
-
If `after` is used, add: `(lines after index N)`.
|
|
93
|
-
|
|
94
|
-
### Implementation notes
|
|
95
|
-
- Add helper `peekAgentOutput(record, peek)` in a new file or in `src/index.ts` near `get_subagent_result`.
|
|
96
|
-
- For output-file JSONL parsing, reuse/extract from `output-file.ts` or read the file line-by-line.
|
|
97
|
-
- Handle regex parse errors gracefully — return a clear error message.
|
|
98
|
-
- Avoid reading the whole file into memory if possible; for now `readFileSync` is acceptable because output files are bounded by session length and the tail case is common.
|
|
99
|
-
|
|
100
|
-
### Tests
|
|
101
|
-
- New test file `test/get-subagent-result-peek.test.ts` or extend existing `status-note-wiring.test.ts`:
|
|
102
|
-
- Peek tail of result.
|
|
103
|
-
- Peek with regex.
|
|
104
|
-
- Peek `after` offset.
|
|
105
|
-
- Peek ignored when verbose true.
|
|
106
|
-
- Peek on running agent with output file.
|
|
107
|
-
- Regex parse error handling.
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## 3. Description polish
|
|
112
|
-
|
|
113
|
-
### `inherit_context`
|
|
114
|
-
Current:
|
|
115
|
-
> "If true, fork parent conversation into the agent. Default: false (fresh context)."
|
|
116
|
-
|
|
117
|
-
New:
|
|
118
|
-
> "If true, fork the parent conversation into the agent so it sees the chat history. Recommended for questions or requests that require current context. Default: false (fresh context)."
|
|
119
|
-
|
|
120
|
-
Also update the eject template line in `src/index.ts`.
|
|
121
|
-
|
|
122
|
-
### `isolation: "worktree"`
|
|
123
|
-
Current:
|
|
124
|
-
> "Set to 'worktree' to run the agent in a temporary git worktree (isolated copy of the repo). Changes are saved to a branch on completion."
|
|
125
|
-
|
|
126
|
-
New:
|
|
127
|
-
> "Set to 'worktree' to run the agent in a temporary git worktree that is automatically created from the current repo state at HEAD and removed on completion. Changes are saved to a branch. Requires the working directory to be a git repo with at least one commit."
|
|
128
|
-
|
|
129
|
-
Also update README.md and `examples/agent-tool-description.md` to match.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## 4. Coordination / cross-cutting concerns
|
|
134
|
-
|
|
135
|
-
- `get_subagent_result` description must mention the configurable timeout, e.g.:
|
|
136
|
-
> "If true, wait for the agent to complete before returning. Blocks up to the configured wait timeout (default 4.5 minutes). If the agent is still running when the timeout is reached, returns current status with instructions to call again. Default: false."
|
|
137
|
-
|
|
138
|
-
- The settings UI should present `waitTimeoutSeconds` as "Wait timeout (30–3600s, default 270 = 4m30s)".
|
|
139
|
-
|
|
140
|
-
- Tests for the timeout should use Vitest fake timers so they run fast and deterministically.
|
|
141
|
-
|
|
142
|
-
- Peek should not duplicate verbose. Keep the contract: `verbose` = full conversation, `peek` = lightweight tail/filter.
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## 5. Things to consider removing / avoiding
|
|
147
|
-
|
|
148
|
-
Before jumping in, review whether any existing code becomes dead weight:
|
|
149
|
-
|
|
150
|
-
- The foreground execution path in `src/index.ts` is now dead code after the background-by-default change. We currently left it in place but unreachable. Consider whether to remove it in a separate cleanup pass (it complicates future changes).
|
|
151
|
-
- `run_in_background` param is deprecated but still in the schema. Keep it for prompt compatibility; do not remove.
|
|
152
|
-
- `AgentConfig.runInBackground` frontmatter default is still consulted by `resolveAgentInvocationConfig`. Since `index.ts` now forces `runInBackground = true`, the frontmatter field is effectively ignored. Consider whether to deprecate it in docs or leave it as a no-op.
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## 6. Acceptance criteria
|
|
157
|
-
|
|
158
|
-
- [ ] `get_subagent_result wait:true` times out after the configured number of seconds and returns a clear message.
|
|
159
|
-
- [ ] Timeout duration is configurable via `/agents → Settings` and persists to `.pi/subagents.json`.
|
|
160
|
-
- [ ] `get_subagent_result` supports `peek` with `lines`, `regex`, and `after`.
|
|
161
|
-
- [ ] `peek` returns line numbers and respects `verbose: true` (is ignored).
|
|
162
|
-
- [ ] `inherit_context` and `isolation` descriptions are updated in tool schema, eject template, README, and example template.
|
|
163
|
-
- [ ] All existing tests pass; new tests cover timeout + peek.
|
|
164
|
-
- [ ] `npm run typecheck` and `npm run lint` clean.
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## 7. Implementation order
|
|
169
|
-
|
|
170
|
-
1. Add `waitTimeoutSeconds` setting (settings.ts + index.ts wiring).
|
|
171
|
-
2. Implement wait timeout in `get_subagent_result` execute.
|
|
172
|
-
3. Add `peek` parameter + helper.
|
|
173
|
-
4. Update descriptions for `inherit_context` and `isolation`.
|
|
174
|
-
5. Update README + example template.
|
|
175
|
-
6. Write tests.
|
|
176
|
-
7. Run full test/typecheck/lint.
|
|
177
|
-
|
|
178
|
-
--- SUMMARY ---
|
|
179
|
-
|
|
180
|
-
- Add a configurable `waitTimeoutSeconds` setting (default 270s) wired into `/agents → Settings` and `get_subagent_result wait:true`.
|
|
181
|
-
- Add a `peek` object param to `get_subagent_result` for tail/filter/offset access to result/output-file with line numbers, ignored when `verbose` is true.
|
|
182
|
-
- Polish `inherit_context` and `isolation: "worktree"` descriptions across schema, template, README, and example.
|
|
183
|
-
- Tests, typecheck, lint.
|
package/.plans/README.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# `.plans/` — local planning scratch
|
|
2
|
-
|
|
3
|
-
This directory holds ad-hoc plans, notes, and design sketches for this fork. It is gitignored so it does not pollute upstream diffs.
|
|
4
|
-
|
|
5
|
-
## Conventions
|
|
6
|
-
|
|
7
|
-
- One plan per file: `PLAN-<short-name>.md`.
|
|
8
|
-
- Plans should include: motivation, files touched, behavior changes, UI/message changes, tests, acceptance criteria, implementation order, and a summary.
|
|
9
|
-
- When a plan graduates to implementation, update the plan file with progress or archive it.
|
|
10
|
-
- Delete obsolete plans to avoid stale guidance.
|
|
11
|
-
|
|
12
|
-
## Active plans
|
|
13
|
-
|
|
14
|
-
- `PLAN-next-changes.md` — configurable `get_subagent_result` wait timeout, peek/tail/regex/after parameter, and description polish for `inherit_context` / `isolation: "worktree"`.
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
# Proposal: Structured JSON Output for Subagents
|
|
2
|
-
|
|
3
|
-
**Date:** 2026-06-19
|
|
4
|
-
**Status:** Proposal / draft
|
|
5
|
-
**Related:** `reviews/subagent-features-comparison.md`
|
|
6
|
-
|
|
7
|
-
## Problem
|
|
8
|
-
|
|
9
|
-
Today, subagents return free-form text. The parent model must parse the text to extract lists, file paths, decisions, or any other structured data before it can feed results into the next tool or agent. This is slow, unreliable, and becomes a bottleneck when chaining multiple agents together.
|
|
10
|
-
|
|
11
|
-
## Goal
|
|
12
|
-
|
|
13
|
-
Add an optional `output_schema` parameter to the `Agent` tool. When supplied, the agent must return JSON matching the schema. The extension validates the output and surfaces either the parsed object or a clear validation error.
|
|
14
|
-
|
|
15
|
-
The feature must be strictly opt-in: the default is free-form text, and agents should only use structured output when the caller explicitly needs it.
|
|
16
|
-
|
|
17
|
-
## Design
|
|
18
|
-
|
|
19
|
-
### Tool parameter
|
|
20
|
-
|
|
21
|
-
Add to the `Agent` tool schema:
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
output_schema: Type.Optional(
|
|
25
|
-
Type.Union([Type.String(), Type.Object({})], {
|
|
26
|
-
description:
|
|
27
|
-
'Optional JSON Schema the agent\'s final answer must match. "": free-form text (recommended default; only use this when the consumer needs structured data).',
|
|
28
|
-
})
|
|
29
|
-
),
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
- Accepts either a JSON Schema object or a JSON-stringified schema.
|
|
33
|
-
- Default is `undefined` / `""` → free-form text, no validation.
|
|
34
|
-
- The schema type is JSON Schema, the same standard already used for tool-call schemas in pi.
|
|
35
|
-
|
|
36
|
-
### Frontmatter support
|
|
37
|
-
|
|
38
|
-
Optionally allow agent `.md` files to pin a default schema:
|
|
39
|
-
|
|
40
|
-
```yaml
|
|
41
|
-
---
|
|
42
|
-
output_schema:
|
|
43
|
-
type: object
|
|
44
|
-
properties:
|
|
45
|
-
files:
|
|
46
|
-
type: array
|
|
47
|
-
items:
|
|
48
|
-
type: object
|
|
49
|
-
properties:
|
|
50
|
-
path: { type: string }
|
|
51
|
-
reason: { type: string }
|
|
52
|
-
required: [path, reason]
|
|
53
|
-
required: [files]
|
|
54
|
-
---
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The caller-supplied `output_schema` overrides the frontmatter value. This lets custom agent types advertise a structured contract without every caller repeating it.
|
|
58
|
-
|
|
59
|
-
### Prompt injection
|
|
60
|
-
|
|
61
|
-
When `output_schema` is non-empty, append a concise instruction to the agent system prompt:
|
|
62
|
-
|
|
63
|
-
> Your final answer must be a single JSON object matching the provided JSON Schema. Do not wrap the JSON in markdown fences, do not add commentary outside the JSON, and do not emit any text after the JSON object.
|
|
64
|
-
|
|
65
|
-
### Validation
|
|
66
|
-
|
|
67
|
-
On agent completion:
|
|
68
|
-
|
|
69
|
-
1. Extract the last JSON object from the final assistant message.
|
|
70
|
-
2. If the output is wrapped in markdown fences, strip them.
|
|
71
|
-
3. Validate the parsed object against the JSON Schema using a lightweight validator (e.g. `ajv` or TypeBox's `Value.Check`).
|
|
72
|
-
4. If validation fails:
|
|
73
|
-
- Mark the agent status as `failed`.
|
|
74
|
-
- Return an error message containing the schema validation errors and a snippet of the raw output.
|
|
75
|
-
5. If validation passes:
|
|
76
|
-
- Include the parsed object in the result under a `structured` field.
|
|
77
|
-
- Keep the normal textual result preview so the notification UI stays readable.
|
|
78
|
-
|
|
79
|
-
### Result shape
|
|
80
|
-
|
|
81
|
-
For a structured agent, the result should expose:
|
|
82
|
-
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"ok": true,
|
|
86
|
-
"data": { "files": [...] },
|
|
87
|
-
"preview": "Found 5 auth-related files..."
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
For failures:
|
|
92
|
-
|
|
93
|
-
```json
|
|
94
|
-
{
|
|
95
|
-
"ok": false,
|
|
96
|
-
"error": "Output did not match the provided JSON Schema",
|
|
97
|
-
"validationErrors": [...],
|
|
98
|
-
"rawPreview": "..."
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Notification and join modes
|
|
103
|
-
|
|
104
|
-
Structured output does not change the notification path. Background agents still send steering-style notifications. The XML payload can include a `<structured-output>` block with the serialized JSON so the parent model can reason about it directly.
|
|
105
|
-
|
|
106
|
-
### Interaction with existing features
|
|
107
|
-
|
|
108
|
-
- **Worktree isolation:** unchanged; the structured result is still returned through the normal completion path.
|
|
109
|
-
- **Scheduling:** scheduled agents may use `output_schema` so recurring jobs produce machine-readable results.
|
|
110
|
-
- **Resume:** resuming a structured agent does not re-validate old output; only the final completion is validated.
|
|
111
|
-
- **Cross-extension RPC:** `output_schema` is serializable JSON and can be passed through the RPC spawn envelope.
|
|
112
|
-
|
|
113
|
-
## Acceptance criteria
|
|
114
|
-
|
|
115
|
-
- [ ] `Agent` tool accepts optional `output_schema` as JSON Schema object or string.
|
|
116
|
-
- [ ] Default behavior remains free-form text; no validation when omitted.
|
|
117
|
-
- [ ] Frontmatter supports optional `output_schema`.
|
|
118
|
-
- [ ] System prompt instructs the agent to emit only matching JSON.
|
|
119
|
-
- [ ] Output is parsed and validated strictly on completion.
|
|
120
|
-
- [ ] Validation failures produce a clear error with the raw output snippet.
|
|
121
|
-
- [ ] Validation successes expose parsed data in result notifications and RPC responses.
|
|
122
|
-
- [ ] Tests cover valid schema, invalid schema, fenced JSON, and missing schema cases.
|
|
123
|
-
- [ ] `npm run typecheck` and `npm run lint` pass.
|
|
124
|
-
|
|
125
|
-
## Open questions
|
|
126
|
-
|
|
127
|
-
1. Should we add a small built-in helper agent type that demonstrates structured output (e.g. `json-explorer`)?
|
|
128
|
-
2. Should `output_schema` be surfaced in `/agents` agent-type descriptions so the orchestrator knows which agents return structured data?
|
|
129
|
-
3. Should failed validation trigger automatic retry with a steering message, or fail fast?
|
|
130
|
-
|
|
131
|
-
## Rationale
|
|
132
|
-
|
|
133
|
-
JSON Schema was chosen because it is the same standard already used for pi tool definitions. Agents and users do not need to learn a new format, and existing tooling (TypeBox, `ajv`) integrates cleanly.
|
|
134
|
-
|
|
135
|
-
Strict validation keeps the contract trustworthy. If a consumer asks for structured output, receiving invalid data is worse than receiving no data, because the consumer is likely to feed it into code that assumes correctness.
|
|
Binary file
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>Recursive Subagents Widget Preview</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root {
|
|
9
|
-
color-scheme: dark;
|
|
10
|
-
--bg: #0b1020;
|
|
11
|
-
--panel: #121a2d;
|
|
12
|
-
--terminal: #070b13;
|
|
13
|
-
--line: #29364f;
|
|
14
|
-
--text: #dbe7ff;
|
|
15
|
-
--muted: #7f8daa;
|
|
16
|
-
--dim: #566176;
|
|
17
|
-
--accent: #82aaff;
|
|
18
|
-
--green: #8bdc9f;
|
|
19
|
-
--yellow: #ffd479;
|
|
20
|
-
--red: #ff7f8f;
|
|
21
|
-
--cyan: #7ee7f5;
|
|
22
|
-
--purple: #c099ff;
|
|
23
|
-
}
|
|
24
|
-
* { box-sizing: border-box; }
|
|
25
|
-
body {
|
|
26
|
-
margin: 0;
|
|
27
|
-
background: radial-gradient(circle at 20% 0%, #1b2b50 0, transparent 35%), var(--bg);
|
|
28
|
-
color: var(--text);
|
|
29
|
-
font: 14px/1.45 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
30
|
-
}
|
|
31
|
-
main { max-width: 1220px; margin: 0 auto; padding: 32px; }
|
|
32
|
-
h1 { font-size: 28px; margin: 0 0 8px; }
|
|
33
|
-
h2 { font-size: 18px; margin: 28px 0 12px; color: #f0f5ff; }
|
|
34
|
-
h3 { font-size: 14px; margin: 0 0 10px; color: var(--accent); text-transform: uppercase; letter-spacing: .08em; }
|
|
35
|
-
p { color: #b9c6df; margin: 0 0 14px; }
|
|
36
|
-
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; align-items: start; }
|
|
37
|
-
.card { background: color-mix(in srgb, var(--panel) 92%, white 8%); border: 1px solid var(--line); border-radius: 16px; padding: 18px; box-shadow: 0 12px 40px rgba(0,0,0,.25); }
|
|
38
|
-
.recommended { border-color: color-mix(in srgb, var(--accent) 70%, white 10%); box-shadow: 0 0 0 1px rgba(130,170,255,.15), 0 18px 55px rgba(22,41,82,.5); }
|
|
39
|
-
.terminal { background: var(--terminal); border: 1px solid #1e2a3e; border-radius: 12px; padding: 12px 14px; font: 13px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; white-space: pre; overflow: hidden; min-height: 260px; }
|
|
40
|
-
.term-title { display: flex; gap: 6px; align-items: center; margin-bottom: 10px; color: var(--muted); font: 12px ui-monospace, monospace; }
|
|
41
|
-
.dot { width: 9px; height: 9px; border-radius: 999px; display: inline-block; }
|
|
42
|
-
.green { color: var(--green); } .yellow { color: var(--yellow); } .red { color: var(--red); } .cyan { color: var(--cyan); } .purple { color: var(--purple); }
|
|
43
|
-
.muted { color: var(--muted); } .dim { color: var(--dim); } .accent { color: var(--accent); }
|
|
44
|
-
.legend { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; color: var(--muted); font-size: 12px; }
|
|
45
|
-
.plan { display: grid; grid-template-columns: 1.2fr .8fr; gap: 18px; }
|
|
46
|
-
ol, ul { margin: 0; padding-left: 20px; color: #c4cee2; }
|
|
47
|
-
li { margin: 6px 0; }
|
|
48
|
-
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px; border-radius: 999px; background: #1b2740; border: 1px solid #344260; color: #cfd9ef; font-size: 12px; }
|
|
49
|
-
.wide { grid-column: 1 / -1; }
|
|
50
|
-
@media (max-width: 980px) { .grid, .plan { grid-template-columns: 1fr; } }
|
|
51
|
-
</style>
|
|
52
|
-
</head>
|
|
53
|
-
<body>
|
|
54
|
-
<main>
|
|
55
|
-
<h1>Recursive Subagents Widget — Plan + Visual Preview</h1>
|
|
56
|
-
<p>Verified issue: the current widget is flat and bound to one manager instance. It does not assemble a durable parent → child → grandchild tree from recursive agent metadata.</p>
|
|
57
|
-
|
|
58
|
-
<section class="plan">
|
|
59
|
-
<div class="card">
|
|
60
|
-
<h2>Implementation plan, once design is approved</h2>
|
|
61
|
-
<ol>
|
|
62
|
-
<li>Create a small tree model layer: records keyed by id, linked by <code>parentAgentId</code>, sorted by start time/status.</li>
|
|
63
|
-
<li>Teach lifecycle events / shared manager state to surface descendants to the root widget, not only direct children.</li>
|
|
64
|
-
<li>Add configurable widget modes: <code>compact</code> (Option A), <code>rich</code> (Option B), and <code>auto</code>.</li>
|
|
65
|
-
<li>Use the focused path view (Option C) when rendering from inside a subagent, or as an optional display mode later.</li>
|
|
66
|
-
<li>Add deterministic tests for parent → child → grandchild rendering, overflow, width truncation, completed/error linger, and mode switching.</li>
|
|
67
|
-
<li>Keep status bar compact: aggregate running/queued counts across the full tree.</li>
|
|
68
|
-
</ol>
|
|
69
|
-
</div>
|
|
70
|
-
<div class="card">
|
|
71
|
-
<h2>Acceptance criteria</h2>
|
|
72
|
-
<ul>
|
|
73
|
-
<li>Grandchildren and deeper descendants are visible.</li>
|
|
74
|
-
<li>Users can choose compact vs rich rendering in settings.</li>
|
|
75
|
-
<li>Subagents can see their location in the larger recursive tree.</li>
|
|
76
|
-
<li>Width/height bounded; no TUI overflow.</li>
|
|
77
|
-
<li>Running activity remains live and readable.</li>
|
|
78
|
-
<li>Completed/error states linger briefly in context.</li>
|
|
79
|
-
</ul>
|
|
80
|
-
</div>
|
|
81
|
-
</section>
|
|
82
|
-
|
|
83
|
-
<h2>Design options</h2>
|
|
84
|
-
<div class="grid">
|
|
85
|
-
<article class="card">
|
|
86
|
-
<h3>Mode: Compact tree</h3>
|
|
87
|
-
<div class="terminal"><div class="term-title"><span class="dot" style="background:#ff5f57"></span><span class="dot" style="background:#ffbd2e"></span><span class="dot" style="background:#28c840"></span><span>Agents widget</span></div><span class="accent">● Agents</span>
|
|
88
|
-
├─ <span class="green">⠋</span> <b>Plan</b> split task <span class="dim">· ↻3 · 2 tools · 1m 12s</span>
|
|
89
|
-
│ ├─ <span class="green">⠹</span> <b>Explore</b> inspect UI <span class="dim">· reading…</span>
|
|
90
|
-
│ └─ <span class="yellow">✓</span> <b>auditor</b> review paths <span class="dim">· 42s</span>
|
|
91
|
-
└─ <span class="green">⠼</span> <b>general-purpose</b> write tests <span class="dim">· editing…</span></div>
|
|
92
|
-
<p><b>Use as:</b> configurable mode <code>compact</code>. <b>Pros:</b> dense, low-risk, close to current widget. <b>Cons:</b> less rich for deep trees.</p>
|
|
93
|
-
</article>
|
|
94
|
-
|
|
95
|
-
<article class="card recommended">
|
|
96
|
-
<h3>Mode: Rich tree (default)</h3>
|
|
97
|
-
<div class="terminal"><div class="term-title"><span class="dot" style="background:#ff5f57"></span><span class="dot" style="background:#ffbd2e"></span><span class="dot" style="background:#28c840"></span><span>Agents widget</span></div><span class="accent">● Agents</span> <span class="muted">3 running · 1 queued · depth 3/4</span>
|
|
98
|
-
├─ <span class="green">⠋</span> <b>Plan</b> <span class="purple">opus</span> <span class="muted">split task</span>
|
|
99
|
-
│ <span class="dim">⎿ ↻3≤20 · 4 tools · 22.8k token (38%) · 1m 12s</span>
|
|
100
|
-
│ ├─ <span class="green">⠹</span> <b>Explore</b> <span class="muted">inspect widget data flow</span>
|
|
101
|
-
│ │ <span class="dim">⎿ reading agent-widget.ts · 14.2s</span>
|
|
102
|
-
│ │ └─ <span class="green">⠼</span> <b>general-purpose</b> <span class="muted">trace manager ownership</span>
|
|
103
|
-
│ │ <span class="dim">⎿ searching tests · 4.8s</span>
|
|
104
|
-
│ └─ <span class="yellow">✓</span> <b>auditor</b> <span class="muted">review plan</span> <span class="dim">· 31s</span>
|
|
105
|
-
├─ <span class="cyan">◦</span> <b>Explore</b> <span class="muted">queued after concurrency cap</span>
|
|
106
|
-
└─ <span class="red">✗</span> <b>Plan</b> <span class="muted">old attempt</span> <span class="red">error: model unavailable</span></div>
|
|
107
|
-
<p><b>Use as:</b> configurable mode <code>rich</code>, recommended default. <b>Pros:</b> best visibility; activity/stats are readable; relationships are obvious. <b>Cons:</b> needs careful overflow rules.</p>
|
|
108
|
-
</article>
|
|
109
|
-
|
|
110
|
-
<article class="card">
|
|
111
|
-
<h3>Focused subagent location view</h3>
|
|
112
|
-
<div class="terminal"><div class="term-title"><span class="dot" style="background:#ff5f57"></span><span class="dot" style="background:#ffbd2e"></span><span class="dot" style="background:#28c840"></span><span>Agents widget</span></div><span class="accent">● Agents</span> <span class="muted">7 total · showing active branch</span>
|
|
113
|
-
├─ <span class="green">⠋</span> <b>Plan</b> split task <span class="dim">· 1m 12s</span>
|
|
114
|
-
│ └─ <span class="green">⠹</span> <b>Explore</b> inspect UI <span class="dim">· reading…</span>
|
|
115
|
-
│ └─ <span class="green">⠼</span> <b>general-purpose</b> trace manager <span class="dim">· searching…</span>
|
|
116
|
-
├─ <span class="dim">+2 completed siblings hidden</span>
|
|
117
|
-
└─ <span class="dim">+2 queued / stale descendants hidden</span></div>
|
|
118
|
-
<p><b>Use as:</b> view shown inside a subagent session to answer “where am I in the tree?” It can also become a selectable mode later. <b>Pros:</b> very compact under load.</p>
|
|
119
|
-
</article>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<section class="card wide" style="margin-top:18px;">
|
|
123
|
-
<h2>Settled direction so far</h2>
|
|
124
|
-
<p><span class="pill">rich default</span> <span class="pill">compact setting</span> <span class="pill">focused subagent view</span></p>
|
|
125
|
-
<p>Build one recursive tree model, then render it through multiple views. The root TUI widget can use <code>rich</code>, <code>compact</code>, or <code>auto</code>. A subagent-local widget can render the focused path view so the agent/operator sees the current subagent’s location in the whole delegation tree.</p>
|
|
126
|
-
<ul>
|
|
127
|
-
<li><b>Header:</b> aggregate full-tree counts and max observed depth.</li>
|
|
128
|
-
<li><b>Rich node:</b> connector + status icon/spinner + agent type + model/tag chips + description, plus detail line.</li>
|
|
129
|
-
<li><b>Compact node:</b> same tree structure, one line per agent, fewer stats.</li>
|
|
130
|
-
<li><b>Focused node:</b> ancestor path + current active branch + hidden sibling summaries.</li>
|
|
131
|
-
<li><b>Overflow:</b> collapse by subtree: <code>└─ +4 descendants hidden (2 running, 1 queued, 1 finished)</code>.</li>
|
|
132
|
-
<li><b>Fallback:</b> orphan records render under <code>Other agents</code> with a dim warning marker.</li>
|
|
133
|
-
</ul>
|
|
134
|
-
</section>
|
|
135
|
-
</main>
|
|
136
|
-
</body>
|
|
137
|
-
</html>
|
|
Binary file
|