@bingjiang0611/rotom 0.1.24 → 0.1.26
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/THIRD_PARTY_NOTICES.md +2 -2
- package/extensions/observability/index.ts +20 -2
- package/extensions/qoder/messages.mjs +1 -1
- package/extensions/qoder/provider.mjs +14 -6
- package/extensions/qoder/transport.mjs +53 -11
- package/extensions/third-party/node_modules/.package-lock.json +6 -6
- package/extensions/third-party/node_modules/pi-subagents/README.md +17 -0
- package/extensions/third-party/node_modules/pi-subagents/agents/delegate.md +2 -0
- package/extensions/third-party/node_modules/pi-subagents/agents/oracle.md +18 -14
- package/extensions/third-party/node_modules/pi-subagents/agents/reviewer.md +5 -3
- package/extensions/third-party/node_modules/pi-subagents/docs/models.md +1 -1
- package/extensions/third-party/node_modules/pi-subagents/docs/tool-reference.md +2 -2
- package/extensions/third-party/node_modules/pi-subagents/docs/workflows.md +2 -2
- package/extensions/third-party/node_modules/pi-subagents/package.json +1 -1
- package/extensions/third-party/node_modules/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/extensions/third-party/node_modules/pi-subagents/prompts/parallel-research.md +4 -6
- package/extensions/third-party/node_modules/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +8 -8
- package/extensions/third-party/node_modules/pi-subagents/skills/pi-subagents/references/execution-controls.md +5 -5
- package/extensions/third-party/node_modules/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +7 -8
- package/extensions/third-party/node_modules/pi-subagents/src/agents/agents.ts +0 -1
- package/extensions/third-party/node_modules/pi-subagents/src/extension/index.ts +9 -2
- package/extensions/third-party/node_modules/pi-subagents/src/profiles/profiles.ts +1 -2
- package/extensions/third-party/node_modules/pi-subagents/src/runs/background/async-execution.ts +81 -52
- package/extensions/third-party/node_modules/pi-subagents/src/runs/background/process-terminal.ts +13 -0
- package/extensions/third-party/node_modules/pi-subagents/src/runs/background/stale-run-reconciler.ts +7 -3
- package/extensions/third-party/node_modules/sol-pi/UPSTREAM.md +8 -3
- package/extensions/third-party/node_modules/sol-pi/package.json +1 -1
- package/extensions/third-party/node_modules/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +6 -2
- package/extensions/third-party/node_modules/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +21 -0
- package/extensions/third-party/node_modules/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +7 -1
- package/extensions/third-party/node_modules/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +79 -16
- package/extensions/third-party/package-lock.json +8 -8
- package/extensions/third-party/package.json +2 -2
- package/extensions/third-party/vendor/README.md +12 -11
- package/extensions/third-party/vendor/pi-subagents-0.52.1-rotom.10.tgz +0 -0
- package/extensions/third-party/vendor/sol-pi-0.1.0-rotom.2.tgz +0 -0
- package/package.json +10 -10
- package/runtime/pi/fork-build.json +24 -24
- package/runtime/pi/node_modules/.package-lock.json +29 -29
- package/runtime/pi/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +30 -7
- package/runtime/pi/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +2 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +8 -0
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +37 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +6 -0
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +34 -0
- package/runtime/pi/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-ai/package.json +3 -3
- package/runtime/pi/node_modules/@earendil-works/pi-codemode/package.json +2 -2
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/anthropic-messages-H6BRNJYC.js +6 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{azure-openai-responses-KZZFKCXW.js → azure-openai-responses-45AJGMB6.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{chunk-2CKNKV2I.js → chunk-5MTGRVNE.js} +2 -2
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{chunk-ITZNWD4K.js → chunk-7OCND3IM.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/chunk-CTEAMRDY.js +2 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{chunk-3CD5ZAQX.js → chunk-ESL4WJJF.js} +11 -11
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{chunk-XQ3UQBJ2.js → chunk-MXCOAUPD.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{cloudflare-workers-ai-system-one-UTBL2KOF.js → cloudflare-workers-ai-system-one-IEPXKTM7.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{execute-4WILIDZF.js → execute-ULZNGRYF.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{google-generative-ai-KJOKBJM3.js → google-generative-ai-KXRSXWRQ.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{google-vertex-VCLZ36OB.js → google-vertex-CVLJXJIO.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{llama-cpp-classify-FYN623RH.js → llama-cpp-classify-42ZH3VVO.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{openai-codex-responses-ZYGHVE5B.js → openai-codex-responses-L4P7LV62.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{openai-completions-BGCEXFAB.js → openai-completions-ZIOWDPDJ.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{openai-decisions-WVPMQ4JD.js → openai-decisions-L7N23NC7.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{openai-responses-KCCCXI3B.js → openai-responses-XT6CQ4R7.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{openrouter-images-DUPG5DZO.js → openrouter-images-FQ7HEE4T.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{typesafe-system-one-ZSK3RN4B.js → typesafe-system-one-IH2QHGKL.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/{virtual-modules-C5BH2VI2.js → virtual-modules-YSF6BJRF.js} +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli-runtime.js +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/index.js +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/rpc-entry.js +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts +5 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +60 -27
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +1 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts +8 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.d.ts +1 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.js +4 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +37 -4
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md +1 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/docs/json.md +6 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/docs/rpc-extension-ui.md +2 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/docs/settings.md +3 -1
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
- package/runtime/pi/node_modules/@earendil-works/pi-mcp/package.json +2 -2
- package/runtime/pi/node_modules/@earendil-works/pi-telemetry/package.json +2 -2
- package/runtime/pi/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/runtime/pi/package-lock.json +37 -37
- package/runtime/pi/package.json +15 -15
- package/runtime/pi/vendor/earendil-works-pi-agent-core-1.3.0-rotom.2.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-ai-1.3.0-rotom.2.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-codemode-1.3.0-rotom.2.tgz +0 -0
- package/runtime/pi/vendor/{earendil-works-pi-coding-agent-1.3.0-rotom.1.tgz → earendil-works-pi-coding-agent-1.3.0-rotom.2.tgz} +0 -0
- package/runtime/pi/vendor/earendil-works-pi-mcp-1.3.0-rotom.2.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-telemetry-1.3.0-rotom.2.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-tui-1.3.0-rotom.2.tgz +0 -0
- package/runtime/product-config.mjs +5 -5
- package/extensions/third-party/node_modules/pi-subagents/agents/researcher.md +0 -52
- package/extensions/third-party/node_modules/pi-subagents/docs/agents.md +0 -383
- package/extensions/third-party/vendor/pi-subagents-0.52.1-rotom.7.tgz +0 -0
- package/extensions/third-party/vendor/sol-pi-0.1.0-rotom.0.tgz +0 -0
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/anthropic-messages-UIH6DSZT.js +0 -6
- package/runtime/pi/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/chunk-TEPMHNKQ.js +0 -2
- package/runtime/pi/vendor/earendil-works-pi-agent-core-1.3.0-rotom.1.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-ai-1.3.0-rotom.1.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-codemode-1.3.0-rotom.1.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-mcp-1.3.0-rotom.1.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-telemetry-1.3.0-rotom.1.tgz +0 -0
- package/runtime/pi/vendor/earendil-works-pi-tui-1.3.0-rotom.1.tgz +0 -0
|
@@ -48,7 +48,7 @@ subagent({
|
|
|
48
48
|
|
|
49
49
|
```typescript
|
|
50
50
|
subagent({
|
|
51
|
-
workflowScript: `return runs.run("
|
|
51
|
+
workflowScript: `return runs.run("continue-thread", { agent: "delegate", task: "Continue the investigation from the current conversation state.", context: "fork" })`
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -86,7 +86,7 @@ Completed workflow children from this parent session stay addressable as retaine
|
|
|
86
86
|
|
|
87
87
|
### Async/background
|
|
88
88
|
|
|
89
|
-
Prefer async mode for every subagent launch. Set `async: true` no matter the task unless the parent must block until completion. This applies to scouts,
|
|
89
|
+
Prefer async mode for every subagent launch. Set `async: true` no matter the task unless the parent must block until completion. This applies to scouts, workers, reviewers, validators, oracle checks, one-off delegates, final review gates, backlog gates, and scripted workflows. Keep the write path single-threaded even when the run is async.
|
|
90
90
|
|
|
91
91
|
Use `async:false` only when the parent must block until completion. Async mode still shows progress. Do not use `async:false` because a task is short, because it is the last gate, because no other work is ready, because the user asked to finish the overall job, or because blocking is convenient.
|
|
92
92
|
|
|
@@ -358,7 +358,7 @@ worktree, first confirm dependencies were linked, installed, or provisioned by
|
|
|
358
358
|
|
|
359
359
|
### Oracle consultation loop
|
|
360
360
|
|
|
361
|
-
For plan, design, or architecture advice that asks to ask, consult, discuss with, or come to agreement with `oracle`, start with one
|
|
361
|
+
For plan, design, or architecture advice that asks to ask, consult, discuss with, or come to agreement with `oracle`, start with one fresh oracle run whose task is a decision brief: goal and acceptance criteria, decisions already made and why, hard constraints, the proposed move, the files that matter, and the specific question. Read its result. If it challenges the direction or leaves a material tradeoff, resume that same completed child once with a focused follow-up, then synthesize the parent decision. `resume` returns a new run id, but continues the same oracle session. Do not force a second round for an explicit one-shot request, a trivial question, or a fully settled first answer.
|
|
362
362
|
|
|
363
363
|
```typescript
|
|
364
364
|
const first = await runs.run("oracle-consult", { agent: "oracle", task: "Review this plan and identify the strongest unresolved tradeoff." });
|
|
@@ -368,14 +368,14 @@ const final = await runs.run("oracle-consult-follow-up", { resume: first.runId,
|
|
|
368
368
|
The parent remains the final decision-maker. Oracle advice does not approve a direction or start implementation.
|
|
369
369
|
|
|
370
370
|
The intended oracle loop is:
|
|
371
|
-
1. the main agent
|
|
371
|
+
1. the main agent writes a decision brief and launches `oracle` with fresh context
|
|
372
372
|
2. `oracle` reviews direction, drift, assumptions, and risks
|
|
373
373
|
3. `oracle` can coordinate back through `contact_supervisor` when the bridge injects it
|
|
374
374
|
4. the main agent decides what direction to approve
|
|
375
375
|
5. only then should `worker` implement
|
|
376
376
|
|
|
377
377
|
```typescript
|
|
378
|
-
// Advisory review
|
|
378
|
+
// Advisory review from a parent-written decision brief. Oracle defaults to fresh context.
|
|
379
379
|
subagent({
|
|
380
380
|
workflowScript: `return runs.run("oracle-check", { agent: "oracle", task: "Review my current direction, challenge assumptions, and propose the best next move." })`
|
|
381
381
|
})
|
|
@@ -9,7 +9,7 @@ Parent extensions may register a session-scoped, out-of-band ceiling through `pi
|
|
|
9
9
|
## When to Use
|
|
10
10
|
|
|
11
11
|
- **Complex work orchestration**: use Fable mode as the default parent-agent loop for complex work. Complex means the task has multiple moving parts, unclear acceptance, cross-cutting code, meaningful user-visible impact, expensive or irreversible validation, broad review surface, or the user asks for orchestration. Lightweight one-off delegation can stay lightweight.
|
|
12
|
-
- **Advisory review**: use fresh-context `reviewer` agents for adversarial code review, or
|
|
12
|
+
- **Advisory review**: use fresh-context `reviewer` agents for adversarial code review, or give `oracle` a written decision brief when prior decisions and drift matter
|
|
13
13
|
- **Implementation handoff**: have `oracle` advise, then `worker` implement only after an approved direction
|
|
14
14
|
- **Recon and planning**: use `scout`, then write a plan when needed
|
|
15
15
|
- **Parallel exploration**: run multiple non-conflicting tasks concurrently
|
|
@@ -41,7 +41,7 @@ you are guiding a human through an interactive flow.
|
|
|
41
41
|
Packaged prompt shortcuts are also available for repeatable workflows. Treat them as reusable orchestration recipes, not just human slash commands. When the user asks for one of these shapes, or when the workflow clearly fits, apply the same pattern directly with `subagent(...)` and other tools:
|
|
42
42
|
- `/parallel-review` — fresh-context reviewers with distinct review angles, then synthesis
|
|
43
43
|
- `/review-loop` — parent-orchestrated worker, fresh-reviewer, and fix-worker cycles until clean or capped
|
|
44
|
-
- `/parallel-research` — combine
|
|
44
|
+
- `/parallel-research` — combine parallel `scout` lanes for local code context with external evidence the parent gathers
|
|
45
45
|
- `/gather-context-and-clarify` — scout/research first, then ask the user clarifying questions with `interview`
|
|
46
46
|
- `/parallel-cleanup` — two fresh-context reviewers (deslop + verbosity passes) for an adversarial cleanup review of the current diff
|
|
47
47
|
|
|
@@ -87,13 +87,13 @@ As a conservative orchestration policy, do not pass `turnBudget` or a hard `tool
|
|
|
87
87
|
|
|
88
88
|
### Parallel research technique
|
|
89
89
|
|
|
90
|
-
Use this when the question needs both external evidence and local implications.
|
|
90
|
+
Use this when the question needs both external evidence and local implications. The parent gathers official docs, specs, ecosystem behavior, recent changes, benchmarks, and primary sources itself (there is no builtin web-research agent) and uses `scout` for repository files, patterns, constraints, tests, and likely integration points. Give each child a distinct angle: external evidence, local code context, and practical tradeoffs. Ask for source links or file ranges, confidence level, gaps, and decision implications. Do not ask these children to edit unless implementation was explicitly requested.
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
### Gather-context-and-clarify technique
|
|
95
95
|
|
|
96
|
-
Use this at the start of non-trivial work. Launch `scout` for local context and
|
|
96
|
+
Use this at the start of non-trivial work. Launch `scout` for local context, and gather external docs, recent sources, ecosystem context, or primary evidence in the parent only when they would materially improve understanding. Ask children for concise findings plus remaining clarification questions. Then synthesize what is known and use `interview` to ask the unresolved questions needed for shared understanding before planning or implementing.
|
|
97
97
|
|
|
98
98
|
### Parallel cleanup technique
|
|
99
99
|
|
|
@@ -157,10 +157,9 @@ and user/project agents override builtins with the same name.
|
|
|
157
157
|
|-------|---------|-------|------------------------|
|
|
158
158
|
| `scout` | Fast codebase recon | inherits default | Writes `context.md` handoff material |
|
|
159
159
|
| `worker` | Implementation and approved oracle handoffs | inherits default | Single-writer implementation with decision escalation |
|
|
160
|
-
| `reviewer` | Review specialist | inherits default |
|
|
161
|
-
| `researcher` | Web research brief generator | inherits default | Writes `research.md` |
|
|
160
|
+
| `reviewer` | Review specialist | inherits default | Review-only; `bash` limited by prompt to read-only inspection and existing checks |
|
|
162
161
|
| `delegate` | Lightweight generic delegate | inherits default | No fixed output; generic delegated work |
|
|
163
|
-
| `oracle` | Decision-consistency advisory review | inherits default |
|
|
162
|
+
| `oracle` | Decision-consistency advisory review | inherits default | Read-only; fresh context from a parent-written decision brief |
|
|
164
163
|
| `advisor` | Claude Code-compatible alias for `oracle` | inherits default | Same advisory role as `oracle` |
|
|
165
164
|
|
|
166
165
|
Builtin `worker` and `delegate` use strict tool allowlists and do not inherit ambient parent extension tools. To give a child an extension tool, name it in `tools` and load its provider via `extensions`, a path-like `tools` entry, or `subagentOnlyExtensions`. Custom agents without an `extensions` field follow `subagents.defaultExtensions` when set.
|
|
@@ -206,7 +205,7 @@ A strong subagent prompt usually includes:
|
|
|
206
205
|
|
|
207
206
|
Give each role useful discovery anchors. Name source roots, filenames, symbols, types, methods, and paths for scouts. Give workers context files, plans, task paths, and named source seams before asking them to search. Give reviewers changed files, contracts, and any exhaustive-verification target. Tell oracle whether current source behavior, product/policy documents, plans, or inherited decisions are the evidence that matters.
|
|
208
207
|
|
|
209
|
-
Avoid carrying over old prompt habits that over-specify every step. Use `must`, `always`, and `never` for real invariants; for judgment calls, give decision rules. For example, tell a reviewer to inspect the staged diff directly and report only evidence-backed findings, rather than prescribing every file or command.
|
|
208
|
+
Avoid carrying over old prompt habits that over-specify every step. Use `must`, `always`, and `never` for real invariants; for judgment calls, give decision rules. For example, tell a reviewer to inspect the staged diff directly and report only evidence-backed findings, rather than prescribing every file or command.
|
|
210
209
|
|
|
211
210
|
For implementation handoffs, name the approved scope and success criteria more clearly than the process. Good prompts say what to change, what not to change, where the evidence lives, how to validate, and when to escalate. They should not ask the child to create another subagent plan or continue the parent conversation.
|
|
212
211
|
|
|
@@ -47,6 +47,7 @@ import { resolveWaitToolConfig } from "../runs/background/subagent-wait.ts";
|
|
|
47
47
|
import { registerWaitTool } from "../runs/background/wait-tool.ts";
|
|
48
48
|
import { createWaitSubscriptionManager } from "../runs/background/wait-subscriptions.ts";
|
|
49
49
|
import { drainOutstandingWork } from "../runs/background/auto-drain.ts";
|
|
50
|
+
import { asyncRunnerDrain } from "../runs/background/async-execution.ts";
|
|
50
51
|
import registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
51
52
|
import { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from "./steering-notices.ts";
|
|
52
53
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.ts";
|
|
@@ -950,7 +951,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
950
951
|
});
|
|
951
952
|
|
|
952
953
|
pi.on("session_shutdown", async () => {
|
|
953
|
-
|
|
954
|
-
|
|
954
|
+
try {
|
|
955
|
+
// Quit, reload and replacement retire this runtime's authority. Persist
|
|
956
|
+
// child close evidence before disposing the original observers.
|
|
957
|
+
await asyncRunnerDrain(pi).drain();
|
|
958
|
+
} finally {
|
|
959
|
+
runtimeEntry.cleanup();
|
|
960
|
+
await herdrStatusBridge.flush();
|
|
961
|
+
}
|
|
955
962
|
});
|
|
956
963
|
}
|
|
@@ -261,7 +261,7 @@ function qualityTierToRoleTier(quality: QualityTier, cost: CostTier): Recommende
|
|
|
261
261
|
|
|
262
262
|
function agentsForRoleTier(roleTier: RecommendedRoleTier): BuiltinAgentName[] {
|
|
263
263
|
if (roleTier === "cheap") return ["scout", "delegate"];
|
|
264
|
-
if (roleTier === "medium") return ["
|
|
264
|
+
if (roleTier === "medium") return ["reviewer"];
|
|
265
265
|
return ["worker", "reviewer", "oracle"];
|
|
266
266
|
}
|
|
267
267
|
|
|
@@ -407,7 +407,6 @@ function buildProfileFile(kind: ProfileKind, models: { cheap: string; medium: st
|
|
|
407
407
|
agentOverrides: {
|
|
408
408
|
scout: { model: models.cheap },
|
|
409
409
|
delegate: { model: models.cheap },
|
|
410
|
-
researcher: { model: models.medium },
|
|
411
410
|
worker: { model: models.strong },
|
|
412
411
|
reviewer: { model: models.strong },
|
|
413
412
|
oracle: { model: models.strong },
|
package/extensions/third-party/node_modules/pi-subagents/src/runs/background/async-execution.ts
CHANGED
|
@@ -67,7 +67,9 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
|
67
67
|
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
68
68
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
69
69
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
70
|
-
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
70
|
+
import { finalizeProcessTerminal, readProcessTerminal, registerRunnerCloseObserver } from "./process-terminal.ts";
|
|
71
|
+
import { createRunnerDrain } from "./runner-drain.ts";
|
|
72
|
+
|
|
71
73
|
import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
|
|
72
74
|
import { assertExecutionStoreScope, scopedSessionRoot, assertScopedSessionPath } from "../../shared/execution-store.ts";
|
|
73
75
|
import { OWNED_EXECUTION_SCOPE, type OwnedExecutionRequest, type OwnedWorkflowParent } from "./owned-execution.ts";
|
|
@@ -76,6 +78,17 @@ import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling,
|
|
|
76
78
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
77
79
|
import { resolvePermissionRules, type PermissionConfig } from "../shared/permissions.ts";
|
|
78
80
|
|
|
81
|
+
// Each runtime owns live ChildProcess handles, never historical PIDs.
|
|
82
|
+
const runtimeRunnerDrains = new WeakMap<object, ReturnType<typeof createRunnerDrain>>();
|
|
83
|
+
export function asyncRunnerDrain(owner: object): ReturnType<typeof createRunnerDrain> {
|
|
84
|
+
let drain = runtimeRunnerDrains.get(owner);
|
|
85
|
+
if (!drain) {
|
|
86
|
+
drain = createRunnerDrain();
|
|
87
|
+
runtimeRunnerDrains.set(owner, drain);
|
|
88
|
+
}
|
|
89
|
+
return drain;
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
const require = createRequire(import.meta.url);
|
|
80
93
|
const piPackageRoot = resolvePiPackageRoot();
|
|
81
94
|
|
|
@@ -487,7 +500,8 @@ interface SpawnRunnerResult {
|
|
|
487
500
|
startupDidNotProceed?: boolean;
|
|
488
501
|
}
|
|
489
502
|
|
|
490
|
-
function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
|
|
503
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, drain: ReturnType<typeof createRunnerDrain>, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
|
|
504
|
+
drain.assertOpen();
|
|
491
505
|
if (!jitiCliPath) {
|
|
492
506
|
return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
|
|
493
507
|
}
|
|
@@ -546,62 +560,75 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
546
560
|
proc.on("error", (error) => {
|
|
547
561
|
console.error(`[pi-subagents] async spawn failed: ${error.message}`);
|
|
548
562
|
});
|
|
563
|
+
const releaseCloseObserver = typeof launchAsyncDir === "string" && typeof launchRunId === "string"
|
|
564
|
+
? registerRunnerCloseObserver(launchAsyncDir, launchRunId, runnerProcessInstanceId) : undefined;
|
|
565
|
+
const observedClose = drain.track(() => {
|
|
566
|
+
// Awaiting a Promise alone cannot keep Node alive after RPC stdin closes.
|
|
567
|
+
proc.ref();
|
|
568
|
+
if (proc.exitCode === null && proc.signalCode === null) proc.kill("SIGTERM");
|
|
569
|
+
});
|
|
549
570
|
proc.once("close", (exitCode, signal) => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
let status: import("../../shared/types.ts").AsyncStatus;
|
|
571
|
+
try {
|
|
572
|
+
const launch = launchConfig as { asyncDir?: unknown; id?: unknown; nestedRoute?: NestedRouteInfo; nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> } };
|
|
573
|
+
const asyncDir = launch.asyncDir;
|
|
574
|
+
const runId = launch.id;
|
|
575
|
+
if (typeof asyncDir !== "string" || typeof runId !== "string") return;
|
|
576
|
+
finalizeProcessTerminal(asyncDir, runId, {
|
|
577
|
+
processInstanceId: runnerProcessInstanceId,
|
|
578
|
+
closeObservedAt: Date.now(),
|
|
579
|
+
exitCode,
|
|
580
|
+
signal,
|
|
581
|
+
});
|
|
582
|
+
const persisted = readProcessTerminal(asyncDir, { runId, runnerProcessInstanceId });
|
|
583
|
+
if (!persisted) return;
|
|
584
|
+
if (launch.nestedRoute && launch.nestedSelf) {
|
|
565
585
|
try {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
child: nestedSummaryFromAsyncStatus(status, asyncDir, {
|
|
584
|
-
id: runId,
|
|
586
|
+
let status: import("../../shared/types.ts").AsyncStatus;
|
|
587
|
+
try {
|
|
588
|
+
status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as import("../../shared/types.ts").AsyncStatus;
|
|
589
|
+
status.processTerminal = persisted;
|
|
590
|
+
} catch {
|
|
591
|
+
status = {
|
|
592
|
+
runId,
|
|
593
|
+
mode: "single",
|
|
594
|
+
state: persisted.state === "observed" ? "complete" : "failed",
|
|
595
|
+
startedAt: persisted.observedAt ?? Date.now(),
|
|
596
|
+
lastUpdate: Date.now(),
|
|
597
|
+
processTerminal: persisted,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
writeNestedEvent(launch.nestedRoute, {
|
|
601
|
+
type: "subagent.nested.completed",
|
|
602
|
+
ts: Date.now(),
|
|
585
603
|
parentRunId: launch.nestedSelf.parentRunId,
|
|
586
604
|
parentStepIndex: launch.nestedSelf.parentStepIndex,
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
605
|
+
child: nestedSummaryFromAsyncStatus(status, asyncDir, {
|
|
606
|
+
id: runId,
|
|
607
|
+
parentRunId: launch.nestedSelf.parentRunId,
|
|
608
|
+
parentStepIndex: launch.nestedSelf.parentStepIndex,
|
|
609
|
+
depth: launch.nestedSelf.depth,
|
|
610
|
+
path: launch.nestedSelf.path,
|
|
611
|
+
mode: status.mode,
|
|
612
|
+
ts: Date.now(),
|
|
613
|
+
}),
|
|
614
|
+
});
|
|
615
|
+
} catch (error) {
|
|
616
|
+
console.error("Failed to emit final nested process-terminal status:", error);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
try {
|
|
620
|
+
onProcessTerminal?.(persisted);
|
|
593
621
|
} catch (error) {
|
|
594
|
-
|
|
622
|
+
// The close proof is already durable. Reload/disposal can retire the
|
|
623
|
+
// originating Pi API before this late advisory wake; never redirect it
|
|
624
|
+
// to a new session or claim a receipt. Recovery must read the record.
|
|
625
|
+
if (!(error instanceof Error)
|
|
626
|
+
|| !/extension ctx is stale|stale after session replacement or reload/i.test(error.message)) throw error;
|
|
595
627
|
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
// The close proof is already durable. Reload/disposal can retire the
|
|
601
|
-
// originating Pi API before this late advisory wake; never redirect it
|
|
602
|
-
// to a new session or claim a receipt. Recovery must read the record.
|
|
603
|
-
if (!(error instanceof Error)
|
|
604
|
-
|| !/extension ctx is stale|stale after session replacement or reload/i.test(error.message)) throw error;
|
|
628
|
+
} finally {
|
|
629
|
+
// Finalization (including honest unknown) precedes runtime teardown.
|
|
630
|
+
releaseCloseObserver?.();
|
|
631
|
+
observedClose();
|
|
605
632
|
}
|
|
606
633
|
});
|
|
607
634
|
if (typeof proc.pid !== "number") {
|
|
@@ -1202,6 +1229,7 @@ export function executeAsyncChain(
|
|
|
1202
1229
|
},
|
|
1203
1230
|
id,
|
|
1204
1231
|
runnerCwd,
|
|
1232
|
+
asyncRunnerDrain(ctx.pi),
|
|
1205
1233
|
(proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
|
|
1206
1234
|
);
|
|
1207
1235
|
} catch (error) {
|
|
@@ -1684,6 +1712,7 @@ export function executeAsyncSingle(
|
|
|
1684
1712
|
},
|
|
1685
1713
|
id,
|
|
1686
1714
|
runnerCwd,
|
|
1715
|
+
asyncRunnerDrain(ctx.pi),
|
|
1687
1716
|
(proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
|
|
1688
1717
|
);
|
|
1689
1718
|
} catch (error) {
|
package/extensions/third-party/node_modules/pi-subagents/src/runs/background/process-terminal.ts
CHANGED
|
@@ -13,6 +13,19 @@ import { canonicalSessionId, inspectSessionLease } from "../shared/session-lease
|
|
|
13
13
|
import { releaseActiveRunIndex } from "./active-run-index.ts";
|
|
14
14
|
import { validOwnedExecution, projectOwnedClosure, type OwnedExecutionV2 } from "./owned-execution.ts";
|
|
15
15
|
|
|
16
|
+
// Process-local liveness of the original observer, not evidence of exit. A new
|
|
17
|
+
// parent cannot reconstruct this registration from disk or a numeric PID.
|
|
18
|
+
const runnerCloseObservers = new Set<string>();
|
|
19
|
+
const observerKey = (asyncDir: string, runId: string, instanceId: string) => JSON.stringify([path.resolve(asyncDir), runId, instanceId]);
|
|
20
|
+
export function registerRunnerCloseObserver(asyncDir: string, runId: string, instanceId: string): () => void {
|
|
21
|
+
const key = observerKey(asyncDir, runId, instanceId);
|
|
22
|
+
runnerCloseObservers.add(key);
|
|
23
|
+
return () => { runnerCloseObservers.delete(key); };
|
|
24
|
+
}
|
|
25
|
+
export function hasRunnerCloseObserver(asyncDir: string, runId: string, instanceId: string): boolean {
|
|
26
|
+
return runnerCloseObservers.has(observerKey(asyncDir, runId, instanceId));
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
export interface ProcessTerminalCandidate {
|
|
17
30
|
version: 1;
|
|
18
31
|
runId: string;
|
package/extensions/third-party/node_modules/pi-subagents/src/runs/background/stale-run-reconciler.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { readStatus } from "../../shared/utils.ts";
|
|
|
7
7
|
import { DIRS, type AsyncParallelGroupStatus, type AsyncStatus, type NestedRunSummary, type SubagentRunMode } from "../../shared/types.ts";
|
|
8
8
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
9
9
|
import { normalizeParallelGroups } from "./parallel-groups.ts";
|
|
10
|
+
import { hasRunnerCloseObserver } from "./process-terminal.ts";
|
|
10
11
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent, type NestedRoute } from "../shared/nested-events.ts";
|
|
11
12
|
|
|
12
13
|
export type PidLiveness = "alive" | "dead" | "unknown";
|
|
@@ -141,7 +142,7 @@ function childState(overallState: ResultRepairData["state"], child: ResultChildO
|
|
|
141
142
|
return overallState;
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
function terminalStatusFromResult(status: AsyncStatus, resultPath: string, now: number): AsyncStatus | undefined {
|
|
145
|
+
function terminalStatusFromResult(status: AsyncStatus, resultPath: string, now: number, asyncDir: string): AsyncStatus | undefined {
|
|
145
146
|
const repair = readResultRepairData(resultPath);
|
|
146
147
|
if (!repair) return undefined;
|
|
147
148
|
const steps = (status.steps ?? []).map((step, index) => {
|
|
@@ -168,7 +169,10 @@ function terminalStatusFromResult(status: AsyncStatus, resultPath: string, now:
|
|
|
168
169
|
const terminalStatus: AsyncStatus = {
|
|
169
170
|
...status,
|
|
170
171
|
state: repair.state,
|
|
171
|
-
...(status.lifecycleArtifactVersion === 3 && (!status.processTerminal || status.processTerminal.state === "pending")
|
|
172
|
+
...(status.lifecycleArtifactVersion === 3 && (!status.processTerminal || status.processTerminal.state === "pending")
|
|
173
|
+
// A result can precede the launching parent's native close callback.
|
|
174
|
+
// Retain pending for that actual observer; this never proves exit.
|
|
175
|
+
&& !(status.processTerminal?.state === "pending" && hasRunnerCloseObserver(asyncDir, status.runId, status.processTerminal.runnerProcessInstanceId)) ? {
|
|
172
176
|
processTerminal: { version: 1 as const, state: "unknown" as const, runId: status.runId, runnerProcessInstanceId: "observer-unavailable", reason: "observer-unavailable" as const },
|
|
173
177
|
} : {}),
|
|
174
178
|
...(repair.state === "stopped" ? { stopped: true } : {}),
|
|
@@ -379,7 +383,7 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
|
|
|
379
383
|
: regularFileExists(resultPath) ? resultPath : undefined;
|
|
380
384
|
if (existingResultPath) {
|
|
381
385
|
const terminalStatus = effectiveStatus.state === "running" || effectiveStatus.state === "queued"
|
|
382
|
-
? terminalStatusFromResult(effectiveStatus, existingResultPath, now)
|
|
386
|
+
? terminalStatusFromResult(effectiveStatus, existingResultPath, now, asyncDir)
|
|
383
387
|
: undefined;
|
|
384
388
|
if (terminalStatus) {
|
|
385
389
|
writeAtomicJson(path.join(asyncDir, "status.json"), terminalStatus);
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
- Upstream: SoL-Pi, <https://github.com/NVlabs/SoL-Pi>. License: MIT, Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES; the original notice stays in `LICENSE`.
|
|
4
4
|
- Upstream base: `2b791687` ("Update Pi to 0.85.1 and verify 0.84.2 compatibility (#63)").
|
|
5
5
|
- Source: the maintainer's SoL-Pi branch at `b463fad0ffab6e42488f5c80687adab67174a0ee`, imported from its tracked files. On top of the upstream base it carries five commits: recalled pages keep source references (`81b80b0`), the Pi 0.85 compaction-reason test (`83c5426`), the 0.85.1 baseline merge (`807ad60`), selected upstream reliability backports (`32840b2`), and Pi error-result handling plus runtime compatibility repairs (`b463fad`).
|
|
6
|
-
- Maintained version: `0.1.0-rotom.
|
|
6
|
+
- Maintained version: `0.1.0-rotom.1`, a private component of [rotom](https://github.com/bingjiang0611/rotom). It is not an upstream or npm release.
|
|
7
7
|
|
|
8
8
|
## Changes in this import
|
|
9
9
|
|
|
10
|
-
- `package.json` / `package-lock.json`: version `0.1.0-rotom.
|
|
10
|
+
- `package.json` / `package-lock.json`: version `0.1.0-rotom.1`, rotom description, `UPSTREAM.md` added to and the hero image removed from the packed files.
|
|
11
11
|
- Not imported: `.github/`, `AGENTS.md`, `CLAUDE.md`, `.gitattributes` and `assets/` (the README hero image link is therefore broken in this copy).
|
|
12
|
-
- `src/` and `tests/`
|
|
12
|
+
- `src/` and `tests/` were byte-identical to the source commit in `0.1.0-rotom.0`.
|
|
13
|
+
|
|
14
|
+
## rotom changes
|
|
15
|
+
|
|
16
|
+
- `0.1.0-rotom.1`: the Evidence-Preserving Reducer records each returned reducer response (applied or rejected) as a Pi `usage` entry of kind `sol_pi_reducer`, attributed to the reducer provider/model with the provider-reported usage unchanged. Pi's footer, session stats, usage breakdown and Goal budgets then include the reducer spend. It uses `pi.appendUsage`, which the rotom Pi fork exposes from `1.3.0-rotom.2`; on Pi releases without it (upstream Pi) only the SoL-Pi journal entry is written, as before. Calls that throw before a response (timeout, unavailable model, missing auth) record no usage. Files: `evidence-preserving-reducer/{provider,journal,index}.ts`, `tests/helpers.ts`, `tests/evidence-preserving-reducer.test.ts`.
|
|
17
|
+
- `0.1.0-rotom.2`: an `unverifiable-quote` fallback journal entry now carries a `quoteDiagnostic` for the first rejected evidence item: `cause` (`invalid-kind`, `not-a-string`, `empty`, `too-long` or `not-in-source`), its index and the evidence count, the quote length and SHA-256, and for `not-in-source` the longest leading part found in the log with its line, plus whether the quote would match after removing ANSI escapes or collapsing whitespace. It never stores quote or log text. Acceptance is unchanged: a receipt is still rejected unless every quote appears byte for byte. Files: `evidence-preserving-reducer/{receipt,index}.ts`, `tests/evidence-preserving-reducer.test.ts`.
|
|
13
18
|
|
|
14
19
|
## How rotom uses it
|
|
15
20
|
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
type ReducerConfigOptions,
|
|
40
40
|
sha256,
|
|
41
41
|
} from "./config.ts";
|
|
42
|
-
import { createJournal, type Journal } from "./journal.ts";
|
|
42
|
+
import { createJournal, createUsageRecorder, type Journal, type UsageRecorder } from "./journal.ts";
|
|
43
43
|
import { callReducer, type ProviderResult } from "./provider.ts";
|
|
44
44
|
import { receiptText, validateReceipt } from "./receipt.ts";
|
|
45
45
|
|
|
@@ -57,6 +57,7 @@ function errorName(error: unknown): string | undefined {
|
|
|
57
57
|
|
|
58
58
|
export async function reduceToolResult(
|
|
59
59
|
journal: Journal,
|
|
60
|
+
recordUsage: UsageRecorder,
|
|
60
61
|
config: ReducerConfig,
|
|
61
62
|
event: ToolResultEvent,
|
|
62
63
|
context: ExtensionContext,
|
|
@@ -103,6 +104,7 @@ export async function reduceToolResult(
|
|
|
103
104
|
return undefined;
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
recordUsage(provider);
|
|
106
108
|
journal("provider_response", {
|
|
107
109
|
toolCallId: event.toolCallId,
|
|
108
110
|
sourceSha256: archive.hash,
|
|
@@ -129,6 +131,7 @@ export async function reduceToolResult(
|
|
|
129
131
|
toolCallId: event.toolCallId,
|
|
130
132
|
sourceSha256: archive.hash,
|
|
131
133
|
reason: checked.reason,
|
|
134
|
+
quoteDiagnostic: checked.quoteDiagnostic,
|
|
132
135
|
usage: provider.usage,
|
|
133
136
|
});
|
|
134
137
|
return undefined;
|
|
@@ -179,6 +182,7 @@ export async function reduceToolResult(
|
|
|
179
182
|
export function createEvidencePreservingReducerExtension(options: EvidencePreservingReducerOptions = {}): ExtensionFactory {
|
|
180
183
|
return (pi: ExtensionAPI) => {
|
|
181
184
|
const states = new Map<string, { config: ReducerConfig; journal: Journal }>();
|
|
185
|
+
const recordUsage = createUsageRecorder(pi);
|
|
182
186
|
pi.on("tool_result", (event, context) => {
|
|
183
187
|
let root: string;
|
|
184
188
|
try {
|
|
@@ -192,7 +196,7 @@ export function createEvidencePreservingReducerExtension(options: EvidencePreser
|
|
|
192
196
|
state = { config, journal: createJournal(pi, config) };
|
|
193
197
|
states.set(root, state);
|
|
194
198
|
}
|
|
195
|
-
return reduceToolResult(state.journal, state.config, event, context);
|
|
199
|
+
return reduceToolResult(state.journal, recordUsage, state.config, event, context);
|
|
196
200
|
});
|
|
197
201
|
};
|
|
198
202
|
}
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
5
6
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
7
|
import { REDUCER_EVENT_SCHEMA, REDUCER_EVENT_TYPE, type ReducerConfig } from "./config.ts";
|
|
8
|
+
import type { ProviderResult } from "./provider.ts";
|
|
9
|
+
|
|
10
|
+
/** Pi usage-entry kind for reducer model calls. */
|
|
11
|
+
export const REDUCER_USAGE_KIND = "sol_pi_reducer";
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Append one non-context session entry per decision the reducer made.
|
|
@@ -23,3 +28,19 @@ export function createJournal(pi: ExtensionAPI, config: ReducerConfig): Journal
|
|
|
23
28
|
});
|
|
24
29
|
};
|
|
25
30
|
}
|
|
31
|
+
|
|
32
|
+
type AppendUsage = (kind: string, provider: string, model: string, usage: Usage, note?: string) => void;
|
|
33
|
+
export type UsageRecorder = (provider: ProviderResult) => void;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Record each reducer response in Pi's usage entries so footer, session stats
|
|
37
|
+
* and budgets include the reducer spend. Pi releases without
|
|
38
|
+
* `pi.appendUsage` (upstream Pi) keep only the journal record.
|
|
39
|
+
*/
|
|
40
|
+
export function createUsageRecorder(pi: ExtensionAPI): UsageRecorder {
|
|
41
|
+
const appendUsage = (pi as ExtensionAPI & { appendUsage?: AppendUsage }).appendUsage;
|
|
42
|
+
if (typeof appendUsage !== "function") return () => {};
|
|
43
|
+
return (provider) => {
|
|
44
|
+
appendUsage.call(pi, REDUCER_USAGE_KIND, provider.provider, provider.responseModel, provider.responseUsage);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Api, AssistantMessage, Context, Model, ProviderStreamOptions } from "@earendil-works/pi-ai";
|
|
6
|
+
import type { Api, AssistantMessage, Context, Model, ProviderStreamOptions, Usage } from "@earendil-works/pi-ai";
|
|
7
7
|
import { complete as completeCompat } from "@earendil-works/pi-ai/compat";
|
|
8
8
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import type { ArchiveObject } from "./archive.ts";
|
|
@@ -44,6 +44,10 @@ export interface ProviderResult {
|
|
|
44
44
|
readonly ok: boolean;
|
|
45
45
|
readonly outputText: string;
|
|
46
46
|
readonly provider: string;
|
|
47
|
+
/** Concrete model that served the request, for usage attribution. */
|
|
48
|
+
readonly responseModel: string;
|
|
49
|
+
/** Provider-reported usage including cost, recorded unchanged in Pi's usage entries. */
|
|
50
|
+
readonly responseUsage: Usage;
|
|
47
51
|
readonly stopReason: AssistantMessage["stopReason"];
|
|
48
52
|
readonly usage: NormalizedUsage;
|
|
49
53
|
}
|
|
@@ -154,6 +158,8 @@ export async function callReducer(
|
|
|
154
158
|
ok: response.stopReason === "stop" || response.stopReason === "length",
|
|
155
159
|
outputText: responseOutputText(response),
|
|
156
160
|
provider: response.provider,
|
|
161
|
+
responseModel: response.responseModel ?? response.model,
|
|
162
|
+
responseUsage: response.usage,
|
|
157
163
|
stopReason: response.stopReason,
|
|
158
164
|
usage: normalizedUsage(response),
|
|
159
165
|
};
|