@ferris1225/pi-subagents 1.0.0 → 1.0.1
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 +18 -14
- package/agents/reviewer.md +2 -0
- package/package.json +1 -1
- package/src/agents.ts +2 -7
- package/src/announcements.ts +4 -1
- package/src/completion.ts +7 -36
- package/src/config.ts +327 -364
- package/src/dispatch.ts +47 -198
- package/src/fixloop.ts +0 -16
- package/src/format.ts +2 -7
- package/src/index.ts +5 -2
- package/src/models.ts +0 -10
- package/src/monitor.ts +46 -98
- package/src/rpc-run.ts +1 -15
- package/src/runtime.ts +3 -15
- package/src/session-fork.ts +0 -4
- package/src/spawn.ts +542 -557
- package/src/tools.ts +5 -29
- package/src/ui.ts +3 -7
- package/src/widget.ts +107 -0
- package/src/worktree.ts +1 -1
- package/src/trajectory.ts +0 -312
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ on its own — no prompt engineering, no babysitting.
|
|
|
16
16
|
Version 1.0 turns pi-subagents from a one-shot background runner into a small
|
|
17
17
|
thread runtime. Every dispatch has a stable run id and retained Pi session, so
|
|
18
18
|
work can be steered while it runs, parked without losing context, resumed after
|
|
19
|
-
settlement, retargeted, or forked into another path.
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
settlement, retargeted, or forked into another path. Generation ownership keeps
|
|
20
|
+
retries and stale child processes from corrupting the logical thread without
|
|
21
|
+
retaining a duplicate event history.
|
|
22
22
|
|
|
23
23
|
The common quality loop now runs end to end without waking the main agent between
|
|
24
24
|
steps:
|
|
@@ -28,8 +28,10 @@ reviewer (find blockers) → worker (fix) → reviewer (verify) → final PASS/F
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Each chain is delivered as one concise completion group, while full per-run
|
|
31
|
-
reports remain available through `subagent_status`.
|
|
32
|
-
|
|
31
|
+
reports remain available through `subagent_status`. Its parent stays `running`
|
|
32
|
+
until the whole chain settles; completed internal rounds leave active status
|
|
33
|
+
immediately, so no `done` row keeps accumulating elapsed time. Ordered model
|
|
34
|
+
pools keep the same retained context across provider fallback, and isolated parallel workers
|
|
33
35
|
use detached Git worktrees whose changes are applied back without touching the
|
|
34
36
|
parent index.
|
|
35
37
|
|
|
@@ -49,6 +51,9 @@ parent index.
|
|
|
49
51
|
"go check" step. `subagent_wait` is a **non-blocking** in-turn lookup by default
|
|
50
52
|
(pass `timeoutMs` to block); `subagent_status` inspects runs; `subagent_stop`
|
|
51
53
|
cancels one and delivers its partial output.
|
|
54
|
+
- **Active-only live widget** — the TUI shows queued and running sub-agents above
|
|
55
|
+
the editor with live activity and elapsed time. Settled and parked rows disappear
|
|
56
|
+
immediately; an auto-fix parent remains `running` until its whole chain settles.
|
|
52
57
|
- **Results are not re-narrated** — a sub-agent's completion is shown to you
|
|
53
58
|
verbatim, and the main agent is told not to paraphrase it back. It replies with
|
|
54
59
|
only its own conclusion or next step, so the same findings are never paid for
|
|
@@ -286,11 +291,11 @@ sessions live until the parent Pi session shuts down.
|
|
|
286
291
|
|
|
287
292
|
### Configuration migration
|
|
288
293
|
|
|
289
|
-
The config file
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
after an update via a toast (marker persisted in `announcedFeatures`).
|
|
294
|
+
The config file normalizes itself on load — no manual steps after an upgrade:
|
|
295
|
+
configured non-empty agent names are preserved, invalid values and unsupported
|
|
296
|
+
keys (including `maxParallelTasks` and `maxSubagentDepth`) are dropped, and
|
|
297
|
+
missing current fields are filled with defaults. New features are announced to
|
|
298
|
+
you once after an update via a toast (marker persisted in `announcedFeatures`).
|
|
294
299
|
|
|
295
300
|
## Agent discovery and overrides
|
|
296
301
|
|
|
@@ -329,10 +334,9 @@ npm test
|
|
|
329
334
|
|
|
330
335
|
The source is modular: `dispatch.ts` (dispatch, controls, isolation, auto-fix),
|
|
331
336
|
`rpc-run.ts` / `spawn.ts` (persistent child transport + model pools),
|
|
332
|
-
`worktree.ts` / `session-fork.ts` (filesystem/session branching),
|
|
333
|
-
`
|
|
334
|
-
(
|
|
335
|
-
and `runtime.ts` (session-scoped ownership). No runtime dependencies beyond pi peer
|
|
337
|
+
`worktree.ts` / `session-fork.ts` (filesystem/session branching), `tools.ts`
|
|
338
|
+
(wait/status/control/stop), `widget.ts` (active-only TUI status), `announcements.ts`
|
|
339
|
+
(recovery and feature notices), and `runtime.ts` (session-scoped ownership). No runtime dependencies beyond pi peer
|
|
336
340
|
dependencies.
|
|
337
341
|
|
|
338
342
|
## License
|
package/agents/reviewer.md
CHANGED
|
@@ -60,5 +60,7 @@ Understand the context first, then verify: the fix addresses the root cause, cha
|
|
|
60
60
|
One of: APPROVE / APPROVE_WITH_NITS / REQUEST_CHANGES, plus a 2-3 sentence rationale.
|
|
61
61
|
End with exactly one machine-readable line: `VERDICT: REVIEW_PASS` for APPROVE or APPROVE_WITH_NITS; `VERDICT: REVIEW_FAIL` for REQUEST_CHANGES.
|
|
62
62
|
|
|
63
|
+
REQUEST_CHANGES (or review-blocking concern) on this extension automatically starts an auto-fix loop: a worker is briefed with your findings, then you re-review, up to the configured round limit. Choose REVIEW_PASS when nothing objectively blocks the change (style preferences, optional refactors, or items the caller can knowingly accept are not blockers) — do not force a verdict that would trigger another loop just to hedge.
|
|
64
|
+
|
|
63
65
|
## Quality standards
|
|
64
66
|
Specific file paths and line numbers. No vague feedback. A clean report means you looked hard, not that you found nothing to say.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Controllable background sub-agent threads for pi: explore, worker, reviewer, auto-fix chains, model pools, and Git worktree isolation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/agents.ts
CHANGED
|
@@ -31,11 +31,6 @@ export interface AgentConfig {
|
|
|
31
31
|
filePath: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export interface AgentDiscoveryResult {
|
|
35
|
-
agents: AgentConfig[];
|
|
36
|
-
projectAgentsDir: string | null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
34
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
40
35
|
/** <package>/agents — the agents shipped with this extension. */
|
|
41
36
|
export const BUILTIN_AGENTS_DIR = join(here, "..", "agents");
|
|
@@ -135,7 +130,7 @@ export interface DiscoverOptions {
|
|
|
135
130
|
* Discover agents across scopes and apply the enabled-name filter.
|
|
136
131
|
* Override priority for the same name: project > user > builtin.
|
|
137
132
|
*/
|
|
138
|
-
export function discoverAgents(cwd: string, options: DiscoverOptions = {}):
|
|
133
|
+
export function discoverAgents(cwd: string, options: DiscoverOptions = {}): { agents: AgentConfig[] } {
|
|
139
134
|
const scope = options.scope ?? "user";
|
|
140
135
|
const builtinDir = options.builtinDir ?? BUILTIN_AGENTS_DIR;
|
|
141
136
|
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
@@ -160,7 +155,7 @@ export function discoverAgents(cwd: string, options: DiscoverOptions = {}): Agen
|
|
|
160
155
|
agents = agents.filter((agent) => enabled.has(agent.name));
|
|
161
156
|
}
|
|
162
157
|
|
|
163
|
-
return { agents
|
|
158
|
+
return { agents };
|
|
164
159
|
}
|
|
165
160
|
|
|
166
161
|
/** One-line catalog entry for system-prompt injection and error messages. */
|
package/src/announcements.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import { loadConfig, saveConfig } from "./config.ts";
|
|
6
6
|
import { announceRecoveryRecords } from "./recovery.ts";
|
|
7
7
|
import type { SubagentRuntime } from "./runtime.ts";
|
|
8
|
+
import { installActiveRunsWidget } from "./widget.ts";
|
|
8
9
|
|
|
9
10
|
const ANNOUNCEMENTS: Array<{
|
|
10
11
|
key: string;
|
|
@@ -54,6 +55,8 @@ async function announceNewFeatures(
|
|
|
54
55
|
export function registerAnnouncements(pi: ExtensionAPI, runtime: SubagentRuntime): void {
|
|
55
56
|
pi.on("session_start", async (_event, ctx) => {
|
|
56
57
|
await announceRecoveryRecords(runtime.configPath, ctx);
|
|
57
|
-
if (ctx.mode
|
|
58
|
+
if (ctx.mode !== "tui") return;
|
|
59
|
+
installActiveRunsWidget(ctx);
|
|
60
|
+
await announceNewFeatures(ctx, runtime);
|
|
58
61
|
});
|
|
59
62
|
}
|
package/src/completion.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Smart batching for successful background completions.
|
|
3
3
|
*
|
|
4
4
|
* A short debounce coalesces sibling runs while a max-wait timer, measured from
|
|
5
|
-
* the first item in the open group, bounds delivery latency.
|
|
6
|
-
* shortly after an emitted group use a smaller straggler window. Failures are
|
|
5
|
+
* the first item in the open group, bounds delivery latency. Failures are
|
|
7
6
|
* intentionally handled by the caller: flush held successes, then emit the
|
|
8
7
|
* failure directly so it is never delayed.
|
|
9
8
|
*/
|
|
@@ -13,30 +12,14 @@ import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } fro
|
|
|
13
12
|
export interface CompletionBatchTimings {
|
|
14
13
|
debounceMs: number;
|
|
15
14
|
maxWaitMs: number;
|
|
16
|
-
stragglerDebounceMs: number;
|
|
17
|
-
stragglerMaxWaitMs: number;
|
|
18
|
-
stragglerWindowMs: number;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
export const DEFAULT_COMPLETION_BATCH_TIMINGS: CompletionBatchTimings = {
|
|
22
18
|
debounceMs: 150,
|
|
23
19
|
maxWaitMs: 1_000,
|
|
24
|
-
stragglerDebounceMs: 75,
|
|
25
|
-
stragglerMaxWaitMs: 400,
|
|
26
|
-
stragglerWindowMs: 2_000,
|
|
27
20
|
};
|
|
28
21
|
|
|
29
|
-
type TimerHandle =
|
|
30
|
-
|
|
31
|
-
export interface TimerApi {
|
|
32
|
-
setTimeout(handler: () => void, delayMs: number): TimerHandle;
|
|
33
|
-
clearTimeout(handle: TimerHandle): void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const defaultTimers: TimerApi = {
|
|
37
|
-
setTimeout: (handler, delayMs) => setTimeout(handler, delayMs),
|
|
38
|
-
clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
39
|
-
};
|
|
22
|
+
type TimerHandle = ReturnType<typeof setTimeout>;
|
|
40
23
|
|
|
41
24
|
function unrefHandle(handle: TimerHandle): void {
|
|
42
25
|
if (
|
|
@@ -52,8 +35,6 @@ function unrefHandle(handle: TimerHandle): void {
|
|
|
52
35
|
export interface CompletionBatcherOptions<T> {
|
|
53
36
|
emit: (items: T[]) => void;
|
|
54
37
|
timings?: Partial<CompletionBatchTimings>;
|
|
55
|
-
timers?: TimerApi;
|
|
56
|
-
now?: () => number;
|
|
57
38
|
}
|
|
58
39
|
|
|
59
40
|
export interface CompletionBatcher<T> {
|
|
@@ -66,22 +47,18 @@ export interface CompletionBatcher<T> {
|
|
|
66
47
|
}
|
|
67
48
|
|
|
68
49
|
export function createCompletionBatcher<T>(options: CompletionBatcherOptions<T>): CompletionBatcher<T> {
|
|
69
|
-
const timers = options.timers ?? defaultTimers;
|
|
70
|
-
const now = options.now ?? Date.now;
|
|
71
50
|
const timings = { ...DEFAULT_COMPLETION_BATCH_TIMINGS, ...options.timings };
|
|
72
51
|
let pending: T[] = [];
|
|
73
52
|
let debounceTimer: TimerHandle | null = null;
|
|
74
53
|
let maxWaitTimer: TimerHandle | null = null;
|
|
75
|
-
let straggler = false;
|
|
76
|
-
let lastEmitAt: number | null = null;
|
|
77
54
|
|
|
78
55
|
const clearTimers = (): void => {
|
|
79
56
|
if (debounceTimer !== null) {
|
|
80
|
-
|
|
57
|
+
clearTimeout(debounceTimer);
|
|
81
58
|
debounceTimer = null;
|
|
82
59
|
}
|
|
83
60
|
if (maxWaitTimer !== null) {
|
|
84
|
-
|
|
61
|
+
clearTimeout(maxWaitTimer);
|
|
85
62
|
maxWaitTimer = null;
|
|
86
63
|
}
|
|
87
64
|
};
|
|
@@ -91,25 +68,19 @@ export function createCompletionBatcher<T>(options: CompletionBatcherOptions<T>)
|
|
|
91
68
|
if (pending.length === 0) return;
|
|
92
69
|
const items = pending;
|
|
93
70
|
pending = [];
|
|
94
|
-
lastEmitAt = now();
|
|
95
71
|
options.emit(items);
|
|
96
72
|
};
|
|
97
73
|
|
|
98
74
|
return {
|
|
99
75
|
push(item: T): void {
|
|
100
|
-
if (pending.length === 0) {
|
|
101
|
-
straggler = lastEmitAt !== null && now() - lastEmitAt < timings.stragglerWindowMs;
|
|
102
|
-
}
|
|
103
76
|
pending.push(item);
|
|
104
77
|
|
|
105
|
-
if (debounceTimer !== null)
|
|
106
|
-
|
|
107
|
-
debounceTimer = timers.setTimeout(emitGroup, debounceDelay);
|
|
78
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
79
|
+
debounceTimer = setTimeout(emitGroup, timings.debounceMs);
|
|
108
80
|
unrefHandle(debounceTimer);
|
|
109
81
|
|
|
110
82
|
if (maxWaitTimer === null) {
|
|
111
|
-
|
|
112
|
-
maxWaitTimer = timers.setTimeout(emitGroup, maxWaitDelay);
|
|
83
|
+
maxWaitTimer = setTimeout(emitGroup, timings.maxWaitMs);
|
|
113
84
|
unrefHandle(maxWaitTimer);
|
|
114
85
|
}
|
|
115
86
|
},
|