@cr1ms0n/pi-subagent 0.9.0 → 0.11.0

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.
@@ -0,0 +1,42 @@
1
+ # Pi Smart Subagents
2
+
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ 在 [Pi](https://pi.dev/) 中运行独立子代理,由 Jev 为每项任务选择模型和工具。
6
+
7
+ 本项目是 Luke Parke 的 `@parke.dev/pi-subagent` 的独立社区分支,上游来自 [LukasParke/pi-extensions](https://github.com/LukasParke/pi-extensions/tree/main/packages/pi-subagent)。保留上游的命名代理、并行与后台任务、工作树和用量统计,增加 Jev 路由与子代理能力核验。
8
+
9
+ ---
10
+
11
+ <a id="quick-start"></a>
12
+ ### 安装
13
+
14
+ ```bash
15
+ pi install npm:@cr1ms0n/pi-subagent
16
+ ```
17
+
18
+ ---
19
+
20
+ <a id="delegation"></a>
21
+ ### 使用
22
+
23
+ 首次使用前,在 `~/.pi/subagent.json` 中配置 TypeSafe API key 和候选模型,见[配置示例](docs/REFERENCE.md#jev-routing)。
24
+
25
+ 然后直接对 Pi 说,例如:
26
+
27
+ > 用只读子代理查看这个项目的目录结构,并总结主要模块。
28
+
29
+ Jev 会选择模型和工具。使用 `/subagents` 查看任务,使用 `/subagent-cost` 查看用量。
30
+
31
+ 并行任务、后台执行、工作树和结构化结果见[使用参考](docs/REFERENCE.md#quick-usage),键盘操作见 [TUI 指南](docs/UX.md)。
32
+
33
+ ---
34
+
35
+ <a id="license"></a>
36
+ ### 许可证
37
+
38
+ [MIT](LICENSE)。Copyright (c) 2026 Luke Parke。社区分支由 cr1ms0n(awoaCrim)维护。重新分发时请保留原始版权声明和许可证。
39
+
40
+ 译自 [README.md](README.md),英文文件 blob:`f55b496dc010bd4242e6f6ffc8c987425ee9d7f4`。中英文内容如有差异,以英文为准。
41
+
42
+ 感谢 [Linux.do](https://linux.do/)。
@@ -12,10 +12,7 @@
12
12
  after a second window so retry can take over. Group kills verify process start-time
13
13
  identity (Linux `/proc`, macOS/BSD `ps lstart`) before signalling a possibly-recycled
14
14
  PID; transcript joins happen only on message boundaries, not per-chunk ticks.
15
- - Retry lives in `orchestrator.ts` (`isTransientFailure`): queue timeouts, stalls, spawn
16
- errors, and provider errors re-run the same already-selected spec (model and tool set)
17
- with accumulated usage; there is no fallback-model escalation, and task-quality failures
18
- never retry.
15
+ - Retry lives in [orchestrator.ts](../src/orchestrator.ts). Ranked extension tasks advance through a locally finalized Jev candidate plan only for recognized settled availability failures before any current-invocation tool starts. [model-failover.ts](../src/model-failover.ts) owns conservative evidence classification and bounded attempt helpers. Tools stay fixed, thinking resolves per candidate, and usage accumulates once. Unknown execution evidence blocks restart. The trusted unranked SDK retains its separate `isTransientFailure` and explicit fallback contract.
19
16
  - `context: "fork"` spawns the child with `--fork <parent session file>` so it starts
20
17
  from a real branched copy of the parent conversation. Fail-fast when the parent
21
18
  session is not persisted; single-task only.
@@ -24,7 +21,7 @@
24
21
  - `semaphore.ts`: per-parent-runtime child-process limit.
25
22
  - `process-lock.ts`: machine-wide durable coordination under `~/.pi/subagent-locks/` —
26
23
  exclusive per-child-session resume locks, global concurrency slots, and run process
27
- identity records for orphan reconcile.
24
+ identity records for orphan reconcile. Ranked tasks keep one record running across attempts and final artifact/worktree work; the orchestrator terminalizes it once. A bounded list of attempt sessions protects earlier transcripts from other parents' maintenance while the task is live.
28
25
  - `launch.ts`: resolve the child `pi` invocation via `PI_SUBAGENT_BIN` or
29
26
  `process.execPath` + CLI entry (bare PATH name only as last-resort fallback).
30
27
  - `persistence.ts`: versioned active-branch event folding, the bounded routing-event decoder
@@ -38,14 +35,12 @@
38
35
  shapes and local resource limits; `routing-policy.ts` owns the strict `jevRouting`
39
36
  parser, the candidate intersection with locally available models, and the injected
40
37
  model-facing guidance; `jev-router.ts` owns the injectable TypeSafe transport,
41
- response validation, deadlines and per-request receipts; `dispatch-routing.ts`
42
- resolves every worker before any launch and refuses a partially selected fanout. The
43
- router has no engine imports and makes no parent UI calls.
38
+ response validation, deadlines and per-request receipts, including a validated full probability ranking and model-independent task tool decisions; `dispatch-routing.ts` resolves every worker before any launch and refuses a partially selected fanout. Local policy finalizes a frozen candidate attempt plan with per-model thinking and one shared tool set. The router has no engine imports and makes no parent UI calls.
44
39
  - `config.ts`: defaults ← `~/.pi/subagent.json` ← `PI_SUBAGENT_*` env overrides.
45
40
  - `structured.ts`: structured-output contract (dependency-free JSON-Schema subset
46
41
  validation, fenced json:result extraction, contract/repair prompts) and
47
42
  conservative double-encoded-arg repair. The runner gates the child's settle on
48
- validation and runs one steer-based repair round before accepting failure.
43
+ validation and runs one steer-based repair round after an otherwise successful invalid answer. Ranked provider-error/aborted attempts skip repair and cannot publish structuredOutput from failed text. The latest completed assistant text replaces earlier text even when empty, so a host retry cannot reuse the failed turn's JSON.
49
44
  - `agents.ts`: named agent files (`.pi/agents/`, `.agents/agents/`, global agent dir).
50
45
  Flat-YAML frontmatter + markdown persona body; resolved in policy with explicit
51
46
  params > agent file > profile taskDefaults > parent inheritance. Catalog refreshes
@@ -80,7 +75,7 @@ Invariants:
80
75
  Selector usage is a separate category folded once per selector request ID, with currency
81
76
  reported as unreported rather than inferred.
82
77
  11. Checkpoint persistence events are lightweight (state, usage, process identity, pointers).
83
- Full transcripts and final output are persisted exactly once, in the terminal event.
78
+ Full transcripts and final output are persisted exactly once, in the terminal event. Ranked attempt histories carry bounded metadata/session pointers at checkpoints, with output previews only in terminal projections (1 KiB per preview, 16 KiB total). Active-branch retention includes earlier attempt session references.
84
79
  12. High-frequency registry "changed" events coalesce (trailing window); state transitions,
85
80
  new child sessions, billed-usage advances, and terminal events flush immediately.
86
81
  13. `wait` is interruptible: aborting a wait returns promptly and does NOT cancel the
@@ -108,18 +103,14 @@ Invariants:
108
103
  20. Budget breaches (`max_turns`/`max_cost`) steer a wrap-up message and allow grace
109
104
  turns before SIGTERM; a child that concludes within grace ends `partial` with
110
105
  `wrappedUp: true`. `graceTurns: 0` restores immediate stops.
111
- 21. Transient failures (queued timeout, stall, spawn error, provider error, protocol
112
- truncation) retry up to `maxRetries` extra attempts on the already selected model and
113
- tool set; there is no fallback-model escalation and no reselection. Usage accumulates
114
- across attempts. Task-quality failures (nonzero exit with complete protocol,
115
- cancellation, budget stop, running timeout) never retry.
106
+ 21. Extension-managed ranked tasks permit at most `maxRetries` extra child attempts, locally capped at 255 total launches. A recognized settled availability failure advances to the next candidate only with conclusive no-tool activity; started or unknown activity blocks all new-child restart. Candidate exhaustion never wraps to the primary. Conclusively pre-work infrastructure retry may repeat the same candidate within the same budget. No retry calls Jev or broadens tools. Usage and budget comparisons include prior attempts. Authentication, quota/billing, invalid requests, context limits, task-quality, cancellation, task-deadline and budget stops do not cause model failover. The trusted unranked SDK keeps its legacy transient/explicit-fallback behavior.
116
107
  22. The stall watchdog treats protocol silence as suspect, not fatal: after
117
108
  `stallAfterMs` the task is flagged and probed via `get_state` (a live child's
118
109
  answer clears the flag); only continued silence for `stallKillAfterMs` more kills
119
- the child which is then a transient failure eligible for retry.
110
+ the child. A stall does not authorize restart on the ranked path: silence cannot establish that no work began.
120
111
  23. Only `async: true` runs notify on completion and appear in the ambient widget.
121
112
  Notification delivery respects delivered-once: a `wait` that consumed the run
122
- suppresses the notification.
113
+ suppresses the notification. The actual final model and a bounded attempt-chain tail with its total count use the same display projection as compact results.
123
114
  24. Named agent files supply per-field defaults only; explicit request params always
124
115
  win, and capability profiles fail closed regardless of what an agent file declares.
125
116
  25. Structured-output validation never discards paid work: schema failure after the
@@ -137,11 +128,9 @@ Invariants:
137
128
  local preflights of a real spawn and returns the resolved plan and its selector usage
138
129
  without spawning. It creates no registry entry, and its fee-bearing selection is not
139
130
  cached for a later dispatch.
140
- 29. Every new extension-managed launch (`task`/`tasks[]`, `action:"plan"`, `/btw`,
131
+ 29. Every new extension-managed invocation (`task`/`tasks[]`, `action:"plan"`, `/btw`,
141
132
  resume, fork, nested dispatch and the optional synthesis child) crosses one selector
142
- interface before any child starts. The dedicated candidate list intersected with
143
- locally available models is the only source of execution models; the full locally
144
- permitted tool catalog is the only candidate source. Legacy `model`/`fallback_models`
133
+ interface before any child starts. The dedicated candidate list intersected with locally available models is the only source of execution models. One validated full probability ranking belongs to the invocation, and fallback reuses it without another selection. The original selectedModel/confidence remain immutable; result.model reports the actual attempt. The full locally permitted tool catalog is the only tool candidate source, and one model-independent selection is shared by every attempt. Legacy `model`/`fallback_models`
145
134
  fields are rejected on new work, and an empty selected tool set never becomes
146
135
  inheritance or "all tools".
147
136
  30. The finalized tool subset is passed to the child as Pi's `--tools` allowlist
@@ -154,13 +143,12 @@ Invariants:
154
143
  the nonce-specific bootstrap command exists from the expected package source, then
155
144
  requires the child to acknowledge the exact selected model and finalized tool names
156
145
  (including nested-tool source) before the real task prompt is sent. Missing or
157
- mismatched acknowledgement is a capability/startup diagnostic, never compensated by
158
- broadening tools or choosing another model.
146
+ mismatched acknowledgement is a capability/startup diagnostic, never compensated by broadening tools or choosing another model. Every ranked replacement gets its own exact-model/shared-tools acknowledgement before the task prompt.
159
147
  31. An absolute task deadline is created before preflight/selection, and routing, setup,
160
148
  queue and retries all count against it. Pending selector work is tracked per session
161
149
  runtime, aborted on cancellation, shutdown or session switch, and every post-await
162
150
  transition re-checks captured runtime/session ownership so a late response cannot
163
- launch into a replaced session.
151
+ launch into a replaced session. Replacement attempts await prior child cleanup, retain run/worktree/resume ownership and recheck cancellation, deadline and cumulative task budgets before launch. Durable task records remain running during replacement and finalization, protecting all known attempt sessions and the worktree across parent processes; only child slots are released per attempt. Pi/provider internal retries and global retry settings remain unchanged.
164
152
  32. Before any paid selection, plan and dispatch share a side-effect-free direct-resume
165
153
  availability check (in-memory owner, `resumeBlocked`, durable lock ownership and
166
154
  staleness) that acquires, renews or reaps nothing. Dispatch still takes the
@@ -44,9 +44,7 @@ own record, and all of them count once by full request ID. Plan selections and
44
44
  pre-spawn failures are included, because no child run exists to carry them.
45
45
 
46
46
  Successful route metadata (decision ID, selector model and reported version(s),
47
- selected execution model/tools, locally added control-plane tools, confidence,
48
- success outcome, latency and receipt IDs) travels with the run and both registry
49
- projections. Per-request failure outcomes and safe error codes stay in selector receipts. It carries no descriptions, raw request bodies, headers,
47
+ original selected model, ranked candidate probabilities, shared selected tools, locally added control-plane tools, confidence, success outcome, latency and receipt IDs) travels with the run and both registry projections. Actual attempt models are recorded separately; fallback never rewrites the initial selection as a new decision. Per-request failure outcomes and safe error codes stay in selector receipts. It carries no descriptions, raw request bodies, headers,
50
48
  credentials or invented rationale.
51
49
 
52
50
  Receipts pending append visibility remain in a bounded session-local overlay until the
@@ -79,10 +77,7 @@ Because the native footer counts parent assistant messages plus delivered tool-r
79
77
  4. If an old run is evicted from in-memory UI history, its latest persisted usage still contributes to the session ledger.
80
78
  5. Active and immediately completed runs supplement or replace stale persisted checkpoints until newer session entries become visible; the full run UUID prevents double counting afterward.
81
79
  6. Resumed and forked invocations are distinct billed runs. Their new provider usage is counted once, even though they reuse prior context.
82
- 7. Retry attempts (transient-failure retries on the already selected model and tool set)
83
- accumulate into their run's single usage record: every attempt's billed usage counts once
84
- under one run UUID. There is no fallback-model escalation, so `attemptedModels` repeats the
85
- selected model rather than recording a route change.
80
+ 7. Ranked failover and permitted same-model pre-work retries accumulate into one run usage record. Every attempt's provider-reported usage counts once under the same run UUID; attempted-model metadata is descriptive, not another ledger input. Advancing to a ranked candidate makes no additional selector request and creates no new receipt. The initial selector decision remains distinct from the actual execution model.
86
81
  8. The optional parallel `synthesis` child bills into the same run as an extra result.
87
82
  9. Selector requests are counted once by full selector request ID, including plan
88
83
  requests and pre-spawn rejected decisions. Route references inside task results
@@ -106,6 +101,10 @@ Only `sessionManager.getBranch()` is used. Costs from abandoned sibling branches
106
101
 
107
102
  Any usage reported before a failure, timeout, budget stop, cancellation, or parent crash is retained in a cumulative checkpoint/terminal record. A run with no provider response contributes zero rather than an estimate.
108
103
 
104
+ All ranked attempts share the absolute task deadline and cumulative `max_cost`/`max_turns` budget. Prior usage is an offset for the next child's budget comparisons, not part of that child's returned usage, so aggregation adds each attempt only once. A replacement cannot start after reported cost or turns meets its ceiling. In-attempt completed-turn checks and wrap-up grace remain unchanged. Pi's internal provider retries are not counted as separately launched extension attempts.
105
+
106
+ When every attempt fails, earlier output previews retain their originating model/session and the final task remains failed. Preserving billed work does not convert failure into `partial` or success. The existing native undercount for thrown failures still applies; the extension ledger retains all reported attempt usage.
107
+
109
108
  ## Provider limitations
110
109
 
111
110
  Accounting is only as precise as the provider data normalized by Pi:
@@ -0,0 +1,124 @@
1
+ # Development and verification
2
+
3
+ This is a standalone ESM TypeScript package loaded by Pi, not the upstream monorepo. Start with the [README](../README.md) for installation and the [architecture contract](ARCHITECTURE.md) before changing engine behavior.
4
+
5
+ ---
6
+
7
+ ### Checkout and runtime
8
+
9
+ [package.json](../package.json) declares Node.js 22.19.0 or newer and Pi/TypeBox peer dependencies. There is no compiled distribution or build step. The extension entry is [extensions/subagent.ts](../extensions/subagent.ts); the supported SDK exports are in [src/index.ts](../src/index.ts).
10
+
11
+ To use a local checkout, replace the example path with your own:
12
+
13
+ ```bash
14
+ pi install /absolute/path/to/pi-smart-subagents
15
+ ```
16
+
17
+ Pi registers a local path without copying it. Do not enable that checkout and another copy of the same extension together. Reload or restart Pi after package changes. Keep user settings, provider credentials and real session files outside the repository.
18
+
19
+ The product skill at [skills/subagent/SKILL.md](../skills/subagent/SKILL.md) is distributed with the package. Maintainer-specific agents, workflows, task records and local verification artifacts are not required to use or inspect the public source.
20
+
21
+ ---
22
+
23
+ ### Source ownership
24
+
25
+ | Responsibility | Owner |
26
+ | --- | --- |
27
+ | Pi registration and lifecycle wiring | [extension.ts](../src/extension.ts) |
28
+ | Request schema, permission checks and normalization | [schema.ts](../src/schema.ts), [policy.ts](../src/policy.ts), [config.ts](../src/config.ts), [agents.ts](../src/agents.ts) |
29
+ | Jev transport, candidate policy and dispatch | [routing-types.ts](../src/routing-types.ts), [routing-policy.ts](../src/routing-policy.ts), [jev-router.ts](../src/jev-router.ts), [dispatch-routing.ts](../src/dispatch-routing.ts) |
30
+ | Local preflight and child capability verification | [dispatch-preflight.ts](../src/dispatch-preflight.ts), [startup-check.ts](../src/startup-check.ts), [child-preflight.ts](../src/child-preflight.ts) |
31
+ | Child processes, retries and protocol | [runner.ts](../src/runner.ts), [orchestrator.ts](../src/orchestrator.ts), [protocol.ts](../src/protocol.ts), [backend adapters](../src/backends/) |
32
+ | Concurrency, ownership and durable worktrees | [semaphore.ts](../src/semaphore.ts), [process-lock.ts](../src/process-lock.ts), [registry.ts](../src/registry.ts), [worktree.ts](../src/worktree.ts) |
33
+ | Persistence, accounting and output | [persistence.ts](../src/persistence.ts), [usage.ts](../src/usage.ts), [output.ts](../src/output.ts), [structured.ts](../src/structured.ts) |
34
+ | TUI, notifications and transcript display | [format.ts](../src/format.ts), [ui.ts](../src/ui.ts), [notifications.ts](../src/notifications.ts), [transcript.ts](../src/transcript.ts) |
35
+
36
+ Keep engine rules in their owning modules. The extension is a composition root, and renderers consume narrow projections rather than owning a second run store. The architecture document covers the remaining modules and invariants.
37
+
38
+ ---
39
+
40
+ ### Checks available in this checkout
41
+
42
+ There are no npm scripts, devDependencies, TypeScript project configuration or bundled test suite. `npm test`, `npm run typecheck` and the upstream release-check scripts are not available here. Installing dependencies alone does not create those commands. The historical [plan](PLAN.md) and [roadmap](ROADMAP.md) refer to upstream tooling and previous release work.
43
+
44
+ #### Documentation and whitespace
45
+
46
+ Review the English and Chinese READMEs together: commands, config keys, feature claims, language links, anchors and license references must agree. Check links against the intended public Git tree, not only files that happen to exist in a maintainer's checkout. Keep the English-source blob reference in the Chinese README current when changing the translation.
47
+
48
+ For tracked changes:
49
+
50
+ ```bash
51
+ git diff --check
52
+ ```
53
+
54
+ For a prepared commit:
55
+
56
+ ```bash
57
+ git diff --cached --check
58
+ ```
59
+
60
+ These commands do not check Markdown links, translation accuracy or untracked files. Review those separately. Documentation-only changes do not require a provider call or a claim that engine tests passed.
61
+
62
+ #### Offline TypeScript syntax check
63
+
64
+ When the global Pi installation includes esbuild, the following Bash/Git Bash command enumerates production TypeScript files, parses and strips their types, and writes only to a unique temporary directory. It removes that directory afterward. It does not download a tool or modify source files.
65
+
66
+ ```bash
67
+ node --input-type=module - "$(npm root -g)" <<'NODE'
68
+ import * as fs from "node:fs";
69
+ import * as os from "node:os";
70
+ import * as path from "node:path";
71
+ import { spawnSync } from "node:child_process";
72
+
73
+ const esbuild = path.join(process.argv[2], "@earendil-works/pi-coding-agent/node_modules/esbuild/bin/esbuild");
74
+ if (!fs.existsSync(esbuild)) throw new Error("Global Pi esbuild is unavailable; no syntax check was run.");
75
+ const files = [];
76
+ function walk(dir) {
77
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
78
+ const file = path.join(dir, entry.name);
79
+ if (entry.isDirectory()) walk(file);
80
+ else if (file.endsWith(".ts")) files.push(file);
81
+ }
82
+ }
83
+ walk("src");
84
+ walk("extensions");
85
+ const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-syntax-"));
86
+ try {
87
+ for (const file of files.sort()) {
88
+ const result = spawnSync(process.execPath, [esbuild, file, "--loader:.ts=ts", "--format=esm",
89
+ "--target=node22", `--outfile=${path.join(outputDir, "check.js")}`], { stdio: "inherit" });
90
+ if (result.error) throw result.error;
91
+ if (result.status !== 0) throw new Error(`Syntax transform failed for ${file}`);
92
+ }
93
+ process.stdout.write(`Parsed ${files.length} TypeScript files; no semantic typechecking performed.\n`);
94
+ } finally {
95
+ fs.rmSync(outputDir, { recursive: true, force: true });
96
+ }
97
+ NODE
98
+ ```
99
+
100
+ This catches malformed TypeScript only. It does not resolve imports, check types or validate peer APIs. If your Pi installation does not expose that esbuild path, report the missing check rather than claiming success or installing new tooling silently.
101
+
102
+ A semantic typecheck requires a separately configured TypeScript environment and compatible peers. There is no repository-owned command for it here. Any external harness or isolated fixture run must be reported with its actual setup, scope and limitations; private maintainer fixtures are not a test suite shipped in this checkout.
103
+
104
+ #### Package contents
105
+
106
+ Review the npm file list without generating or publishing a tarball:
107
+
108
+ ```bash
109
+ npm pack --dry-run --ignore-scripts --json
110
+ ```
111
+
112
+ The output should contain production source, the distributed skill, public documentation, both READMEs, changelog, license and package metadata. It must not contain local settings, agent instructions, tasks, backups, transcripts, credentials, tarballs or generated test bundles. Git exclusions and the package `files` allowlist are separate controls; inspect both when changing packaging.
113
+
114
+ A dry run verifies packaging, not application behavior. For an actual release, follow the additional artifact and installation checks in [release maintenance](RELEASING.md).
115
+
116
+ ---
117
+
118
+ ### Behavior verification
119
+
120
+ For engine changes, trace the affected architecture invariants and exercise the owned boundary with an injected transport, fake process or isolated host when such a harness is available. State what was exercised and keep paid partial output/accounting semantics intact.
121
+
122
+ A real `subagent` call and `action: "plan"` both invoke Jev and may incur charges. A plan avoids spawning a child; it is not an offline test. Obtain explicit permission before live routing/provider smoke tests, use synthetic task data and keep real user sessions out of fixtures.
123
+
124
+ Report syntax transforms, semantic typechecks, package checks, fixture assertions and live-provider checks separately. Do not summarize them as “tests passed” when no test suite was run.
package/docs/PLAN.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Execution plan — remaining roadmap phases
2
2
 
3
+ > **Historical upstream plan.** Retained as design rationale for phases that shipped before this fork's Jev routing changes. Version targets, test paths and `npm run release:check` below describe the upstream monorepo, not tools available in this standalone checkout. See [the current reference](REFERENCE.md), [architecture contract](ARCHITECTURE.md) and [development checks](DEVELOPMENT.md) for the maintained behavior and verification instructions. Historical gate statements are not verification results for the current checkout.
4
+
3
5
  Formalization of [ROADMAP.md](./ROADMAP.md) phases 2–4 into implementable work
4
6
  items. Where the roadmap holds rationale and design sketches, this document
5
7
  holds the execution contract: exact scope, file-level work breakdown,