@arhen/pi-core-subagent 1.3.45 → 1.3.47
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 +8 -10
- package/package.json +1 -1
- package/src/child.ts +1 -1
- package/src/index.ts +5 -6
- package/src/manager.ts +5 -10
- package/src/peek.ts +68 -10
- package/src/schemas.ts +0 -3
- package/src/types.ts +0 -1
package/README.md
CHANGED
|
@@ -93,11 +93,10 @@ Define agents inline per call, or reference a named agent file (see [Agent files
|
|
|
93
93
|
}
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
Parallel — mixed toolsets, siblings can talk via mailbox:
|
|
96
|
+
Parallel — mixed toolsets, siblings can talk via mailbox (intercom is always on):
|
|
97
97
|
|
|
98
98
|
```json
|
|
99
99
|
{
|
|
100
|
-
"allowIntercom": true,
|
|
101
100
|
"tasks": [
|
|
102
101
|
{ "agent": "researcher", "prompt": "You find facts. Cite paths.", "task": "Map the auth flow", "write": false },
|
|
103
102
|
{ "agent": "implementer", "prompt": "You make minimal changes.", "task": "Implement POST /api/upload", "write": true }
|
|
@@ -272,14 +271,13 @@ flowchart LR
|
|
|
272
271
|
|
|
273
272
|
And the rule that keeps this from becoming ceremony: **zero `needs` anywhere = plain parallel.** No waves, no gates, no graph vocabulary imposed on flat work.
|
|
274
273
|
|
|
275
|
-
Background (default) + intercom — the run returns a runId immediately; you stay steerable while it works:
|
|
274
|
+
Background (default) + intercom — the run returns a runId immediately; you stay steerable while it works. Children can always ask you questions and message each other:
|
|
276
275
|
|
|
277
276
|
```json
|
|
278
277
|
{
|
|
279
278
|
"agent": "auditor",
|
|
280
279
|
"prompt": "You audit dependencies.",
|
|
281
|
-
"task": "Audit package.json for outdated deps"
|
|
282
|
-
"allowIntercom": true
|
|
280
|
+
"task": "Audit package.json for outdated deps"
|
|
283
281
|
}
|
|
284
282
|
```
|
|
285
283
|
|
|
@@ -289,7 +287,7 @@ Background (default) + intercom — the run returns a runId immediately; you sta
|
|
|
289
287
|
|
|
290
288
|
| Tool | Purpose |
|
|
291
289
|
|---|---|
|
|
292
|
-
| `subagent` | single / `tasks` (parallel or graph via `needs`) / `chain` (`{previous}`); every run is background — returns a runId, completion notifies you; `autoAwait:true` parks the call until the run finishes and returns the final result inline;
|
|
290
|
+
| `subagent` | single / `tasks` (parallel or graph via `needs`) / `chain` (`{previous}`); every run is background — returns a runId, completion notifies you; `autoAwait:true` parks the call until the run finishes and returns the final result inline; children always carry talk tools (ask/notify/mailbox); `notifyPerTask` (default true) wakes you as each task completes |
|
|
293
291
|
| `subagent_status` | live per-task snapshot (non-blocking), including each child's session file path |
|
|
294
292
|
| `subagent_result` | full output of a run or one task |
|
|
295
293
|
| `await_subagent` | block until a run finishes (optional `timeoutMs`) |
|
|
@@ -299,9 +297,9 @@ Background (default) + intercom — the run returns a runId immediately; you sta
|
|
|
299
297
|
|
|
300
298
|
### Per-task fields
|
|
301
299
|
|
|
302
|
-
`agent` (name you invent — required), `task` (required), `prompt` (system prompt, optional — minimal default used), `write` (toolset, default read-only), plus optional `model` (`provider/model-id`), `thinking` (validated enum: `off|minimal|low|medium|high|xhigh|max`), `tools` (explicit allowlist), `cwd`, `maxRuntimeMs`, `id`, `needs` (dependency edges — see [Graph mode](#graph-mode--needs)). Top-level only: `autoAwait`, `notifyPerTask`, `
|
|
300
|
+
`agent` (name you invent — required), `task` (required), `prompt` (system prompt, optional — minimal default used), `write` (toolset, default read-only), plus optional `model` (`provider/model-id`), `thinking` (validated enum: `off|minimal|low|medium|high|xhigh|max`), `tools` (explicit allowlist), `cwd`, `maxRuntimeMs`, `id`, `needs` (dependency edges — see [Graph mode](#graph-mode--needs)). Top-level only: `autoAwait`, `notifyPerTask`, `concurrency`.
|
|
303
301
|
|
|
304
|
-
### Child talk tools (
|
|
302
|
+
### Child talk tools (always on)
|
|
305
303
|
|
|
306
304
|
| Tool | Meaning |
|
|
307
305
|
|---|---|
|
|
@@ -315,7 +313,7 @@ Background (default) + intercom — the run returns a runId immediately; you sta
|
|
|
315
313
|
## Commands
|
|
316
314
|
|
|
317
315
|
- `/subagents` — list runs; `/subagents peek` (or `ctrl+shift+a`) — browsable pane
|
|
318
|
-
- `/subagents auto-limit on|off` — toggle leader-imposed `maxRuntimeMs` caps (persists to `~/.pi/agent/subagents-config.json`; default
|
|
316
|
+
- `/subagents auto-limit on|off` — toggle leader-imposed `maxRuntimeMs` caps (persists to `~/.pi/agent/subagents-config.json`; default **off**). `on` gives tasks without an explicit `maxRuntimeMs` the 1 h default ceiling; `off` raises the ceiling to 6 h (still a ceiling — an unbounded child would pin the run forever). Bare `/subagents auto-limit` shows the current state.
|
|
319
317
|
|
|
320
318
|
## Peek — `/subagents peek` or `ctrl+shift+a`
|
|
321
319
|
|
|
@@ -356,7 +354,7 @@ The extension has no multiplexer integration and does not want one: it exposes t
|
|
|
356
354
|
|
|
357
355
|
- Parent tools: 6 schemas with short descriptions. **No catalog, no context hook** — nothing injected per request.
|
|
358
356
|
- Background completion: 3-line notice. Full text only via `subagent_result`.
|
|
359
|
-
- Children: isolated sessions; talk tools injected
|
|
357
|
+
- Children: isolated sessions; talk tools always injected; each child's prompt states its own task id and its siblings' so mailbox addressing works. Model resolution: explicit `provider/model-id` or bare id via the pi model registry → the parent's current model → settings default. Thinking levels validated against the resolved model's `thinkingLevelMap`.
|
|
360
358
|
|
|
361
359
|
## What this is built on
|
|
362
360
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
|
|
6
6
|
"license": "MIT",
|
package/src/child.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Child-session tools.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Every child gets four talk tools:
|
|
5
5
|
* - ask_parent blocking Q&A with the leader (parent)
|
|
6
6
|
* - notify_parent one-way message to the leader
|
|
7
7
|
* - send_agent_message one-way message to another subagent's mailbox
|
package/src/index.ts
CHANGED
|
@@ -81,12 +81,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
81
81
|
if (value === "on" || value === "off") {
|
|
82
82
|
const next = manager.setAutoLimit(value === "on");
|
|
83
83
|
ctx.ui.notify(
|
|
84
|
-
`auto-limit ${next ? "on" : "off"} — ${next ? "
|
|
84
|
+
`auto-limit ${next ? "on" : "off"} — ${next ? "tasks without an explicit maxRuntimeMs get the 1 h default ceiling" : "raised 6 h ceiling applies (no 1 h cap)"}.`,
|
|
85
85
|
"info",
|
|
86
86
|
);
|
|
87
87
|
} else {
|
|
88
88
|
ctx.ui.notify(
|
|
89
|
-
`auto-limit is ${manager.autoLimitOn ? "on" : "off"} — use \`/subagents auto-limit on|off
|
|
89
|
+
`auto-limit is ${manager.autoLimitOn ? "on (1 h default ceiling)" : "off (6 h ceiling)"} — use \`/subagents auto-limit on|off\`.`,
|
|
90
90
|
"info",
|
|
91
91
|
);
|
|
92
92
|
}
|
|
@@ -157,7 +157,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
157
|
// ponytail: this string is billed on every request. No example block — an example
|
|
158
158
|
// biases the model toward one shape; guidelines + JSON schema describe all of them.
|
|
159
159
|
description:
|
|
160
|
-
"Run isolated subagents (own context, own session). You invent each agent: name, optional system prompt, toolset (read-only default, write:true to edit). Use `agent`+`task` for one, `tasks` for many. `needs` declares dependency edges: a task waits for its needs and receives their outputs prepended to its prompt. If a user agent file in `.agents/agents`, `.claude/agents`, or `.pi/agents` (project dirs, then home) has a `description` matching the spawn goal (name + task), that file is authoritative: body = system prompt, frontmatter `model`/`tools` apply, inline prompt/model/tools ignored. No match → the inline definition stands. Write agents run in an isolated git worktree: on completion the result reports the branch + changed files — review, then merge with `git merge --no-ff <branch>` (merged branches are cleaned automatically). Every run is background: the call returns a runId immediately and completion notifies you — do NOT park waiting on it. If you have no other work, end your turn; the completion notice wakes you with the results. Set autoAwait:true only when the very next step in the SAME turn consumes the result.
|
|
160
|
+
"Run isolated subagents (own context, own session). You invent each agent: name, optional system prompt, toolset (read-only default, write:true to edit). Use `agent`+`task` for one, `tasks` for many. `needs` declares dependency edges: a task waits for its needs and receives their outputs prepended to its prompt. If a user agent file in `.agents/agents`, `.claude/agents`, or `.pi/agents` (project dirs, then home) has a `description` matching the spawn goal (name + task), that file is authoritative: body = system prompt, frontmatter `model`/`tools` apply, inline prompt/model/tools ignored. No match → the inline definition stands. Write agents run in an isolated git worktree: on completion the result reports the branch + changed files — review, then merge with `git merge --no-ff <branch>` (merged branches are cleaned automatically). Every run is background: the call returns a runId immediately and completion notifies you — do NOT park waiting on it. If you have no other work, end your turn; the completion notice wakes you with the results. Set autoAwait:true only when the very next step in the SAME turn consumes the result. Children always carry talk tools: they can ask you questions, notify you, and message siblings.",
|
|
161
161
|
promptSnippet: "Define and delegate work to specialized subagents.",
|
|
162
162
|
promptGuidelines: [
|
|
163
163
|
"Use subagent when independent review, testing, research, or parallel analysis improves quality.",
|
|
@@ -172,7 +172,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
172
172
|
"Never block with nothing to do: if you have no work left after spawning, end your turn. Task completion notifies you and wakes a fresh turn with the results — await_subagent/autoAwait in that situation only burns time and tokens.",
|
|
173
173
|
"autoAwait:true only when the same turn must consume the result immediately (e.g. you spawn a reviewer and then must act on its verdict before replying). Otherwise spawn background and read results from the completion notice, or subagent_result when you come back.",
|
|
174
174
|
"await_subagent is for the rare case where you have parallel work of your own and need to sync at a specific point — not the default follow-up to a spawn.",
|
|
175
|
-
"allowIntercom:true only when a child may need to ask you something.",
|
|
176
175
|
],
|
|
177
176
|
parameters: SubagentParams,
|
|
178
177
|
executionMode: "parallel", // sibling subagent calls run concurrently, not serialized
|
|
@@ -237,7 +236,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
237
236
|
: args.agent
|
|
238
237
|
? `single ${args.agent}`
|
|
239
238
|
: "preparing…";
|
|
240
|
-
const flags =
|
|
239
|
+
const flags = args.autoAwait ? "await" : "bg";
|
|
241
240
|
// Params used, dimmed: model, thinking, toolset, per-task write count.
|
|
242
241
|
const tasks = args.tasks ?? args.chain ?? [];
|
|
243
242
|
const writeCount = tasks.filter((t) => t.write).length;
|
|
@@ -269,7 +268,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
269
268
|
})
|
|
270
269
|
.join("");
|
|
271
270
|
return new Text(
|
|
272
|
-
`${theme.fg("toolTitle", theme.bold("subagent"))} ${theme.fg("accent", mode)}
|
|
271
|
+
`${theme.fg("toolTitle", theme.bold("subagent"))} ${theme.fg("accent", mode)} ${theme.fg("muted", `[${flags}]`)}${params}${graphLine}${plan}`,
|
|
273
272
|
0,
|
|
274
273
|
0,
|
|
275
274
|
);
|
package/src/manager.ts
CHANGED
|
@@ -321,8 +321,8 @@ export class SubagentManager {
|
|
|
321
321
|
/** Set by clearRuns — blocks late persists from erasing the sidecar. */
|
|
322
322
|
private cleared = false;
|
|
323
323
|
|
|
324
|
-
/** When
|
|
325
|
-
private autoLimit =
|
|
324
|
+
/** When true, tasks without an explicit maxRuntimeMs get the 1 h default ceiling; when false (default) the raised 6 h ceiling applies — toggle via `/subagents auto-limit on|off`. */
|
|
325
|
+
private autoLimit = false;
|
|
326
326
|
|
|
327
327
|
turnActivity = false;
|
|
328
328
|
|
|
@@ -850,7 +850,7 @@ export class SubagentManager {
|
|
|
850
850
|
const allowedTools = input.write ? WRITE_TOOLS : READONLY_TOOLS;
|
|
851
851
|
const fileTools = file?.tools?.filter((t) => allowedTools.includes(t));
|
|
852
852
|
const baseTools = fileTools?.length ? fileTools : (input.tools ?? allowedTools);
|
|
853
|
-
const tools = [...baseTools, ...
|
|
853
|
+
const tools = [...baseTools, ...CHILD_TALK_TOOLS];
|
|
854
854
|
// Isolation follows the DELIVERED toolset, never the raw request: explicit
|
|
855
855
|
// tools: [bash] without write:true still gets a worktree, and a file that
|
|
856
856
|
// narrowed the child to read-only never gets the commit/merge ceremony.
|
|
@@ -990,9 +990,7 @@ export class SubagentManager {
|
|
|
990
990
|
const worktreeNote = wt
|
|
991
991
|
? ` You work in an isolated git worktree (branch ${wt.branch})${task.stackedOn ? `, stacked on ${task.stackedOn} (its changes are already in your tree)` : ""}. Never run git commands that switch branches, create branches, or move the worktree (git switch/checkout/branch/worktree). The extension commits your changes when you finish. git status/diff are fine for inspecting your own changes. node_modules is a SHARED symlink to the main checkout: never install, upgrade, or delete dependencies (no npm/bun/yarn/pnpm install, no \`rm -rf node_modules\`) — those writes escape your worktree and damage the user's project. If the task truly needs a dependency change, edit the manifest only and say so in your answer.`
|
|
992
992
|
: "";
|
|
993
|
-
const subagentInstruction = run.
|
|
994
|
-
? `You are running as a subagent. Your bash tool already executes in the project working directory — never prefix commands with \`cd\`. Do not call subagent/delegation tools unless the parent explicitly asks. Return a concise final answer. You MAY use ask_parent only when truly blocked on information only the parent has; notify_parent for one-way updates; send_agent_message/poll_agent_messages to coordinate with siblings. Your mailbox address and siblings: ${task.roster ?? "(none)"}. Use the exact task ids (e.g. task_2) as send_agent_message targets. Siblings run independently and may start late or finish early — never block indefinitely on their replies: poll at most 5 times, then proceed with your best judgment. A gated sibling (marked ↳ waits in the graph) may not be running yet; do not wait for it. Stalled waits get the whole run killed. When your work is done, call notify_parent ONCE with a concise result summary — key findings, verdicts, file:line evidence — so the leader can start consuming your output before the run finishes.${worktreeNote}`
|
|
995
|
-
: `You are running as a subagent. Your bash tool already executes in the project working directory — never prefix commands with \`cd\`. Do not call subagent/delegation tools unless the parent explicitly asks. Return a concise final answer for the parent agent.${worktreeNote}`;
|
|
993
|
+
const subagentInstruction = `You are running as a subagent. Your bash tool already executes in the project working directory — never prefix commands with \`cd\`. Do not call subagent/delegation tools unless the parent explicitly asks. Return a concise final answer. You MAY use ask_parent only when truly blocked on information only the parent has; notify_parent for one-way updates; send_agent_message/poll_agent_messages to coordinate with siblings. Your mailbox address and siblings: ${task.roster ?? "(none)"}. Use the exact task ids (e.g. task_2) as send_agent_message targets. Siblings run independently and may start late or finish early — never block indefinitely on their replies: poll at most 5 times, then proceed with your best judgment. A gated sibling (marked ↳ waits in the graph) may not be running yet; do not wait for it. Stalled waits get the whole run killed. When your work is done, call notify_parent ONCE with a concise result summary — key findings, verdicts, file:line evidence — so the leader can start consuming your output before the run finishes.${worktreeNote}`;
|
|
996
994
|
|
|
997
995
|
const loader = new DefaultResourceLoader({
|
|
998
996
|
cwd: childCwd,
|
|
@@ -1005,9 +1003,7 @@ export class SubagentManager {
|
|
|
1005
1003
|
});
|
|
1006
1004
|
await loader.reload();
|
|
1007
1005
|
|
|
1008
|
-
const customTools: ToolDefinition[] = run
|
|
1009
|
-
? createChildTools(task.id, this.makeChildHandlers(run, task, ctx))
|
|
1010
|
-
: [];
|
|
1006
|
+
const customTools: ToolDefinition[] = createChildTools(task.id, this.makeChildHandlers(run, task, ctx));
|
|
1011
1007
|
|
|
1012
1008
|
const created = await createAgentSession({
|
|
1013
1009
|
cwd: childCwd,
|
|
@@ -1324,7 +1320,6 @@ export class SubagentManager {
|
|
|
1324
1320
|
id: newId("run"),
|
|
1325
1321
|
mode,
|
|
1326
1322
|
status: "queued",
|
|
1327
|
-
allowIntercom: Boolean(params.allowIntercom),
|
|
1328
1323
|
notifyPerTask: params.notifyPerTask ?? true,
|
|
1329
1324
|
createdAt: Date.now(),
|
|
1330
1325
|
concurrency: Math.max(1, Math.min(params.concurrency ?? DEFAULT_CONCURRENCY, MAX_CONCURRENCY)),
|
package/src/peek.ts
CHANGED
|
@@ -116,7 +116,8 @@ function eventLines(raw: string): PeekLine[] {
|
|
|
116
116
|
return out;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
/** The whole readable tail, unsliced — the viewport decides what is shown. */
|
|
120
|
+
function tailLines(path: string): PeekLine[] {
|
|
120
121
|
let text: string;
|
|
121
122
|
try {
|
|
122
123
|
text = readTail(path);
|
|
@@ -126,7 +127,7 @@ function tailLines(path: string, max: number): PeekLine[] {
|
|
|
126
127
|
const lines: PeekLine[] = [];
|
|
127
128
|
// First line of a mid-file read is usually a fragment — drop it.
|
|
128
129
|
for (const raw of text.split("\n").slice(1)) lines.push(...eventLines(raw));
|
|
129
|
-
return lines
|
|
130
|
+
return lines;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
/** Gutter colour carries the line's role, so the eye can skip to failures without
|
|
@@ -172,9 +173,32 @@ export function createPeekPane(
|
|
|
172
173
|
let selected = 0;
|
|
173
174
|
let tailing = false;
|
|
174
175
|
let confirming = false;
|
|
176
|
+
/** Rows scrolled back from the newest line. 0 = pinned to live output.
|
|
177
|
+
* A tail that keeps jumping to the bottom while you read is unusable, so any
|
|
178
|
+
* scroll detaches from live and the 700ms poll stops moving the view. */
|
|
179
|
+
let scrollback = 0;
|
|
180
|
+
/** Content rows the last render could show — page keys need the real viewport,
|
|
181
|
+
* which only render() knows (it is given the width and derives the height). */
|
|
182
|
+
let viewport = TAIL_ROWS;
|
|
183
|
+
/** Tail length at the previous render, to keep a scrolled-back view pinned to
|
|
184
|
+
* the SAME lines as the child appends new ones. Without this the window is
|
|
185
|
+
* anchored to the end of a growing list, so the text you are reading crawls
|
|
186
|
+
* upward every poll. */
|
|
187
|
+
let lastTotal = 0;
|
|
175
188
|
const timer = setInterval(requestRender, POLL_MS);
|
|
176
189
|
|
|
177
190
|
const clamp = (n: number, len: number) => (len === 0 ? 0 : Math.max(0, Math.min(len - 1, n)));
|
|
191
|
+
/** Scrolling past either end is a no-op, never a wrap: the newest line is a
|
|
192
|
+
* hard floor, the oldest readable line a hard ceiling. */
|
|
193
|
+
const scrollBy = (rows: number) => {
|
|
194
|
+
const task = getTasks()[selected];
|
|
195
|
+
if (!tailing || !task?.sessionFile) return;
|
|
196
|
+
const total = tailLines(task.sessionFile).length;
|
|
197
|
+
// Anchor here as well as in render(): a scroll before the next render would
|
|
198
|
+
// otherwise be read as "the file grew by its whole length" and jump the view.
|
|
199
|
+
lastTotal = total;
|
|
200
|
+
scrollback = Math.max(0, Math.min(Math.max(0, total - viewport), scrollback + rows));
|
|
201
|
+
};
|
|
178
202
|
|
|
179
203
|
/**
|
|
180
204
|
* Every row is a full-width bordered line: │ edge, one space of padding, the
|
|
@@ -208,7 +232,12 @@ export function createPeekPane(
|
|
|
208
232
|
const task = tasks[selected]!;
|
|
209
233
|
const hint = confirming
|
|
210
234
|
? theme.fg("error", `abort ${task.agent}? y / n`)
|
|
211
|
-
: theme.fg(
|
|
235
|
+
: theme.fg(
|
|
236
|
+
"dim",
|
|
237
|
+
tailing
|
|
238
|
+
? "↑↓ / jk scroll · ⇧ page · g/G top·live · esc back · x abort"
|
|
239
|
+
: "shift+↑↓ / jk move · enter tail · x abort · esc close",
|
|
240
|
+
);
|
|
212
241
|
// Breadcrumb, not a bare name: while tailing, WHERE you are is the thing you
|
|
213
242
|
// lose track of first, and the child's status belongs next to its name.
|
|
214
243
|
const crumb = tailing
|
|
@@ -226,9 +255,23 @@ export function createPeekPane(
|
|
|
226
255
|
lines.push(row(theme.fg("dim", "(no session file — agent has not started yet)"), width));
|
|
227
256
|
} else {
|
|
228
257
|
// ponytail: re-reads the tail each render (700ms poll). A watcher only pays off for files far bigger than a child session.
|
|
229
|
-
const tail = tailLines(task.sessionFile
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
const tail = tailLines(task.sessionFile);
|
|
259
|
+
viewport = TAIL_ROWS;
|
|
260
|
+
if (scrollback > 0 && lastTotal > 0 && tail.length > lastTotal) scrollback += tail.length - lastTotal;
|
|
261
|
+
lastTotal = tail.length;
|
|
262
|
+
// Clamp here too: the readable tail is a sliding 64KB window, so old lines
|
|
263
|
+
// fall off the front and a deep scrollback would render a blank window.
|
|
264
|
+
scrollback = Math.max(0, Math.min(Math.max(0, tail.length - viewport), scrollback));
|
|
265
|
+
const end = tail.length - scrollback;
|
|
266
|
+
const window = tail.slice(Math.max(0, end - viewport), end);
|
|
267
|
+
if (window.length === 0) lines.push(row(theme.fg("dim", "(no activity yet)"), width));
|
|
268
|
+
for (const line of window) lines.push(row(renderLine(line, theme), width));
|
|
269
|
+
// Scrolled-back views must say so: an unmoving tail is otherwise
|
|
270
|
+
// indistinguishable from a stalled child.
|
|
271
|
+
if (scrollback > 0) {
|
|
272
|
+
lines.push(edgeRow(width, "├", "┤"));
|
|
273
|
+
lines.push(row(theme.fg("warning", `↑ ${scrollback} older · G / end → live`), width));
|
|
274
|
+
}
|
|
232
275
|
}
|
|
233
276
|
lines.push(edgeRow(width, "╰", "╯"));
|
|
234
277
|
return lines;
|
|
@@ -249,17 +292,32 @@ export function createPeekPane(
|
|
|
249
292
|
if (data === "x" || data === "X") {
|
|
250
293
|
if (tasks[selected]?.running) confirming = true;
|
|
251
294
|
} else if (matchesKey(data, Key.escape)) {
|
|
252
|
-
if (tailing)
|
|
253
|
-
|
|
295
|
+
if (tailing) {
|
|
296
|
+
tailing = false;
|
|
297
|
+
scrollback = 0; // leaving the tail forgets where you were reading
|
|
298
|
+
} else close();
|
|
254
299
|
} else if (matchesKey(data, Key.enter) || matchesKey(data, Key.right)) {
|
|
255
300
|
tailing = true;
|
|
301
|
+
scrollback = 0; // a freshly opened tail always starts live
|
|
256
302
|
} else if (matchesKey(data, Key.left)) {
|
|
257
303
|
tailing = false;
|
|
304
|
+
scrollback = 0;
|
|
305
|
+
} else if (matchesKey(data, Key.pageUp)) {
|
|
306
|
+
scrollBy(viewport);
|
|
307
|
+
} else if (matchesKey(data, Key.pageDown)) {
|
|
308
|
+
scrollBy(-viewport);
|
|
309
|
+
} else if (data === "g") {
|
|
310
|
+
scrollBy(Number.MAX_SAFE_INTEGER); // oldest readable line
|
|
311
|
+
} else if (data === "G" || matchesKey(data, Key.end)) {
|
|
312
|
+
scrollback = 0; // back to live
|
|
258
313
|
} else if (matchesKey(data, "shift+up") || matchesKey(data, Key.up) || data === "k") {
|
|
314
|
+
// In the tail the same keys scroll; in the list they move between agents.
|
|
259
315
|
// shift+↑↓ and j/k are the reliable pair: bare arrows can be eaten by prompt history.
|
|
260
|
-
|
|
316
|
+
if (tailing) scrollBy(1);
|
|
317
|
+
else selected = clamp(selected - 1, len);
|
|
261
318
|
} else if (matchesKey(data, "shift+down") || matchesKey(data, Key.down) || data === "j") {
|
|
262
|
-
|
|
319
|
+
if (tailing) scrollBy(-1);
|
|
320
|
+
else selected = clamp(selected + 1, len);
|
|
263
321
|
}
|
|
264
322
|
requestRender();
|
|
265
323
|
},
|
package/src/schemas.ts
CHANGED
|
@@ -67,9 +67,6 @@ export const SubagentParams = Type.Object({
|
|
|
67
67
|
default: true,
|
|
68
68
|
}),
|
|
69
69
|
),
|
|
70
|
-
allowIntercom: Type.Optional(
|
|
71
|
-
Type.Boolean({ description: "Let children ask you questions, notify you, and message sibling subagents" }),
|
|
72
|
-
),
|
|
73
70
|
});
|
|
74
71
|
|
|
75
72
|
/** Derived from the schemas — single source of truth, no hand-maintained mirror. */
|