@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 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. An internal append-only
20
- lifecycle trajectory keeps retries and stale generations from corrupting the
21
- logical thread.
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`. Ordered model pools keep the
32
- same retained context across provider fallback, and isolated parallel workers
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 migrates itself on load — no manual steps after an upgrade:
290
- schema upgrades are normalized and saved back, removed agents are stripped,
291
- legacy keys (`maxParallelTasks`, `maxSubagentDepth`) are folded in or dropped,
292
- and new fields are filled with defaults. New features are announced to you once
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
- `trajectory.ts` (internal lifecycle history), `tools.ts`
334
- (wait/status/control/stop), `announcements.ts` (recovery and feature notices),
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
@@ -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.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 = {}): AgentDiscoveryResult {
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, projectAgentsDir };
158
+ return { agents };
164
159
  }
165
160
 
166
161
  /** One-line catalog entry for system-prompt injection and error messages. */
@@ -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 === "tui") await announceNewFeatures(ctx, runtime);
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. Runs that finish
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 = unknown;
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
- timers.clearTimeout(debounceTimer);
57
+ clearTimeout(debounceTimer);
81
58
  debounceTimer = null;
82
59
  }
83
60
  if (maxWaitTimer !== null) {
84
- timers.clearTimeout(maxWaitTimer);
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) timers.clearTimeout(debounceTimer);
106
- const debounceDelay = straggler ? timings.stragglerDebounceMs : timings.debounceMs;
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
- const maxWaitDelay = straggler ? timings.stragglerMaxWaitMs : timings.maxWaitMs;
112
- maxWaitTimer = timers.setTimeout(emitGroup, maxWaitDelay);
83
+ maxWaitTimer = setTimeout(emitGroup, timings.maxWaitMs);
113
84
  unrefHandle(maxWaitTimer);
114
85
  }
115
86
  },