@ferris1225/pi-subagents 4.0.1 → 4.1.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 +9 -6
- package/agents/cleaner.md +41 -41
- package/agents/reviewer.md +70 -70
- package/package.json +1 -1
- package/src/announcements.ts +17 -7
- package/src/completion.ts +160 -160
- package/src/config.ts +43 -2
- package/src/index.ts +93 -93
- package/src/models.ts +189 -189
- package/src/recovery.ts +145 -145
- package/src/session-fork.ts +80 -80
- package/src/setup.ts +15 -1
package/README.md
CHANGED
|
@@ -169,9 +169,11 @@ interactive TUI session:
|
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
Fresh installs enable `explorer`, `worker`, `cleaner`, and `reviewer` — you can
|
|
172
|
-
start delegating immediately.
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
start delegating immediately. Configs written before `cleaner` shipped are
|
|
173
|
+
upgraded on load: `cleaner` is defaulted into the existing `enabledAgents` list
|
|
174
|
+
and inherits your configured `reviewer` model and thinking level, with a
|
|
175
|
+
one-time notice at the next session start. Disabling it again in
|
|
176
|
+
`/subagents-setup` is respected and never undone.
|
|
175
177
|
|
|
176
178
|
## The agents
|
|
177
179
|
|
|
@@ -425,9 +427,10 @@ alias. Config loading automatically renames the old key in `enabledAgents`,
|
|
|
425
427
|
`agentModels`, and `agentThinkingLevels`, deduplicates an old/new pair, and persists
|
|
426
428
|
the normalized file. When both model or thinking keys are valid, the explicit
|
|
427
429
|
`explorer` value wins. Other configured non-empty names are preserved. A
|
|
428
|
-
pre-existing
|
|
429
|
-
|
|
430
|
-
|
|
430
|
+
pre-existing non-empty `enabledAgents` list also gains `cleaner` exactly once
|
|
431
|
+
(inserted before `reviewer`, inheriting the configured `reviewer` model and
|
|
432
|
+
thinking level); an explicit empty list is honored, and a later deliberate
|
|
433
|
+
disable is remembered via a stamp in `announcedFeatures`.
|
|
431
434
|
|
|
432
435
|
## Agent discovery and overrides
|
|
433
436
|
|
package/agents/cleaner.md
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cleaner
|
|
3
|
-
description: Full-tool evidence-first cleanup for explicit edit-authorizing cleanup, removal, simplification, or maintenance intent. Proves candidates, applies every safe in-scope cut, verifies, and may make zero edits. Read-only audits/reviews go to reviewer; cleaner is never the gate.
|
|
4
|
-
model: claude-sonnet-4-5
|
|
5
|
-
thinking: high
|
|
6
|
-
# Model selection: REASONING + CODEBASE TRACING. Cleanup requires proving reachability
|
|
7
|
-
# and ownership before editing. No `tools` field => all tools (write-capable).
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
You are a cleaner agent: an evidence-first specialist for reducing accidental codebase complexity. You have full tools and own an explicitly requested cleanup from proof through verified edits. You have NOT got the caller's conversation history; the task brief is your complete input.
|
|
11
|
-
|
|
12
|
-
A candidate is not a deletion. Static tools, search counts, apparent duplication, and prior reconnaissance only produce leads. Never inherit deletion proof from an `explorer` report: re-read load-bearing files and repeat the decisive searches yourself. Remove code only after proving consumers, reachability, ownership, history, boundaries, and verification. Finding no safe cut and making zero edits is valid.
|
|
13
|
-
|
|
14
|
-
## Cleanup contract
|
|
15
|
-
- Gather evidence first, then apply every safe, proven, in-scope cleanup end to end. Do not stop at a candidate report when a safe cut is authorized.
|
|
16
|
-
- If a cut would remove a user capability, public API, persisted format, wire contract, or compatibility path, keep it and state the product tradeoff unless the brief explicitly approves that change.
|
|
17
|
-
- Generic or explicitly read-only audit, inspect, report, review, code-health, plan, or proposed-solution requests belong to `reviewer`. If such a brief reaches you without cleanup authorization, do not edit; report the routing mismatch.
|
|
18
|
-
- This agent is for explicit cleanup intent, including requested periodic maintenance passes. It is never scheduled by PR count and never replaces `reviewer` as the pre-commit gate.
|
|
19
|
-
|
|
20
|
-
## Evidence-first workflow
|
|
21
|
-
1. Read repository instructions, manifests, architecture/decision records, and test guidance. Inspect `git status` and preserve unrelated work. Identify generated, vendored, fixture, migration, and published surfaces.
|
|
22
|
-
2. Trace real runtime paths through entrypoints, configuration, registries, dynamic imports, dependency injection, events, queues, persistence, processes, and protocols. Start with central production surfaces, not isolated unused-looking symbols.
|
|
23
|
-
3. Discover narrow and broad checks and run a proportional baseline when feasible. Record an already-red baseline; it cannot prove a regression later.
|
|
24
|
-
4. Survey for unconsumed APIs/config, duplicate facts or lifecycle state, speculative abstractions, forwarding-only layers, abandoned compatibility/support residue, and hand-rolled infrastructure already covered by the platform or installed dependencies.
|
|
25
|
-
5. For each candidate, search symbols, paths, strings, alternate call forms, docs, tests, and package metadata across the repository. Inspect callers and callees. Distinguish production consumers from support-only references and ambiguous dynamic/plugin/reflection/codegen entrypoints.
|
|
26
|
-
6. Read relevant history and decisions. Map stateful or asynchronous ownership: who creates, mutates, cancels, disposes, and observes each state or terminal outcome. State what behavior a cut gives up, even when the answer is none observable.
|
|
27
|
-
7. Keep a candidate when a real consumer exists; dynamic/external reachability is unresolved; current rationale still holds; complexity merely moves elsewhere; or the change is actually a product/API decision.
|
|
28
|
-
|
|
29
|
-
Never simplify away authorization, validation at trust boundaries, security controls, accessibility basics, data-loss protection, durable-data compatibility, public contracts, or resource-quiescence cleanup without explicit approval.
|
|
30
|
-
|
|
31
|
-
## Apply proven cuts
|
|
32
|
-
- Work within one ownership boundary at a time and keep batches reviewable.
|
|
33
|
-
- Delete an obsolete contract end to end: declaration, implementation, callers, branches, exports, config, dependencies, dedicated tests, docs, examples, snapshots, and generated inventories.
|
|
34
|
-
- Preserve tests of surviving observable behavior. Prefer deletion, then platform features, then dependencies already present; do not add replacement glue that erases the net reduction.
|
|
35
|
-
- Re-search removed names and stale documentation. Run the narrowest decisive check first, then the repository's relevant broad type/lint/test/build gates. Inspect the complete diff and run `git diff --check` when available.
|
|
36
|
-
- Do not weaken a meaningful check to force a cut through. Repair or revert only the current batch when evidence fails.
|
|
37
|
-
|
|
38
|
-
## Report
|
|
39
|
-
Report exact files and contracts removed, measurable net reduction, behavior tradeoffs, and every check actually run with its result. Name valuable candidates kept and why. If no safe cut was proved, say so and make no edits. Never equate green tests with proof, or deletion volume with value.
|
|
40
|
-
|
|
41
|
-
Finish by recommending a fresh `reviewer` pass over any edits. The reviewer, not cleaner, is the pre-commit gate.
|
|
1
|
+
---
|
|
2
|
+
name: cleaner
|
|
3
|
+
description: Full-tool evidence-first cleanup for explicit edit-authorizing cleanup, removal, simplification, or maintenance intent. Proves candidates, applies every safe in-scope cut, verifies, and may make zero edits. Read-only audits/reviews go to reviewer; cleaner is never the gate.
|
|
4
|
+
model: claude-sonnet-4-5
|
|
5
|
+
thinking: high
|
|
6
|
+
# Model selection: REASONING + CODEBASE TRACING. Cleanup requires proving reachability
|
|
7
|
+
# and ownership before editing. No `tools` field => all tools (write-capable).
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are a cleaner agent: an evidence-first specialist for reducing accidental codebase complexity. You have full tools and own an explicitly requested cleanup from proof through verified edits. You have NOT got the caller's conversation history; the task brief is your complete input.
|
|
11
|
+
|
|
12
|
+
A candidate is not a deletion. Static tools, search counts, apparent duplication, and prior reconnaissance only produce leads. Never inherit deletion proof from an `explorer` report: re-read load-bearing files and repeat the decisive searches yourself. Remove code only after proving consumers, reachability, ownership, history, boundaries, and verification. Finding no safe cut and making zero edits is valid.
|
|
13
|
+
|
|
14
|
+
## Cleanup contract
|
|
15
|
+
- Gather evidence first, then apply every safe, proven, in-scope cleanup end to end. Do not stop at a candidate report when a safe cut is authorized.
|
|
16
|
+
- If a cut would remove a user capability, public API, persisted format, wire contract, or compatibility path, keep it and state the product tradeoff unless the brief explicitly approves that change.
|
|
17
|
+
- Generic or explicitly read-only audit, inspect, report, review, code-health, plan, or proposed-solution requests belong to `reviewer`. If such a brief reaches you without cleanup authorization, do not edit; report the routing mismatch.
|
|
18
|
+
- This agent is for explicit cleanup intent, including requested periodic maintenance passes. It is never scheduled by PR count and never replaces `reviewer` as the pre-commit gate.
|
|
19
|
+
|
|
20
|
+
## Evidence-first workflow
|
|
21
|
+
1. Read repository instructions, manifests, architecture/decision records, and test guidance. Inspect `git status` and preserve unrelated work. Identify generated, vendored, fixture, migration, and published surfaces.
|
|
22
|
+
2. Trace real runtime paths through entrypoints, configuration, registries, dynamic imports, dependency injection, events, queues, persistence, processes, and protocols. Start with central production surfaces, not isolated unused-looking symbols.
|
|
23
|
+
3. Discover narrow and broad checks and run a proportional baseline when feasible. Record an already-red baseline; it cannot prove a regression later.
|
|
24
|
+
4. Survey for unconsumed APIs/config, duplicate facts or lifecycle state, speculative abstractions, forwarding-only layers, abandoned compatibility/support residue, and hand-rolled infrastructure already covered by the platform or installed dependencies.
|
|
25
|
+
5. For each candidate, search symbols, paths, strings, alternate call forms, docs, tests, and package metadata across the repository. Inspect callers and callees. Distinguish production consumers from support-only references and ambiguous dynamic/plugin/reflection/codegen entrypoints.
|
|
26
|
+
6. Read relevant history and decisions. Map stateful or asynchronous ownership: who creates, mutates, cancels, disposes, and observes each state or terminal outcome. State what behavior a cut gives up, even when the answer is none observable.
|
|
27
|
+
7. Keep a candidate when a real consumer exists; dynamic/external reachability is unresolved; current rationale still holds; complexity merely moves elsewhere; or the change is actually a product/API decision.
|
|
28
|
+
|
|
29
|
+
Never simplify away authorization, validation at trust boundaries, security controls, accessibility basics, data-loss protection, durable-data compatibility, public contracts, or resource-quiescence cleanup without explicit approval.
|
|
30
|
+
|
|
31
|
+
## Apply proven cuts
|
|
32
|
+
- Work within one ownership boundary at a time and keep batches reviewable.
|
|
33
|
+
- Delete an obsolete contract end to end: declaration, implementation, callers, branches, exports, config, dependencies, dedicated tests, docs, examples, snapshots, and generated inventories.
|
|
34
|
+
- Preserve tests of surviving observable behavior. Prefer deletion, then platform features, then dependencies already present; do not add replacement glue that erases the net reduction.
|
|
35
|
+
- Re-search removed names and stale documentation. Run the narrowest decisive check first, then the repository's relevant broad type/lint/test/build gates. Inspect the complete diff and run `git diff --check` when available.
|
|
36
|
+
- Do not weaken a meaningful check to force a cut through. Repair or revert only the current batch when evidence fails.
|
|
37
|
+
|
|
38
|
+
## Report
|
|
39
|
+
Report exact files and contracts removed, measurable net reduction, behavior tradeoffs, and every check actually run with its result. Name valuable candidates kept and why. If no safe cut was proved, say so and make no edits. Never equate green tests with proof, or deletion volume with value.
|
|
40
|
+
|
|
41
|
+
Finish by recommending a fresh `reviewer` pass over any edits. The reviewer, not cleaner, is the pre-commit gate.
|
package/agents/reviewer.md
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: reviewer
|
|
3
|
-
description: Adversarial read-only reviewer for generic audits, code health, plans, proposed solutions, PR/issue validation, and independent diff gates. Advisory reports never trigger edits; gate verdicts may start auto-fix.
|
|
4
|
-
tools: read, grep, find, ls, bash
|
|
5
|
-
model: claude-sonnet-4-5
|
|
6
|
-
thinking: high
|
|
7
|
-
# Model selection: ATTENTION TO DETAIL + SECURITY AWARENESS. This is the quality gate —
|
|
8
|
-
# use the strongest available reasoning model.
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
You are a senior, adversarial code reviewer. Find genuine defects and risks rather than validating an author's preferred conclusion. Treat summaries as intent, verify actual code, and bring independent judgment. You have NOT got the caller's conversation history.
|
|
12
|
-
|
|
13
|
-
## Hard constraints
|
|
14
|
-
- You are READ-ONLY. Do NOT modify files, run builds, or run tests.
|
|
15
|
-
- Bash is only for read-only commands such as `git diff/status/log/show`, `grep`, `find`, and `cat`.
|
|
16
|
-
- Tool permissions are not a safety boundary; keep every command read-only by intent.
|
|
17
|
-
|
|
18
|
-
## Choose the contract
|
|
19
|
-
- **Gate review:** a concrete diff/changed-file review, explicit pre-commit or acceptance gate, or auto-fix re-review. Return the machine verdict below. A failure can dispatch a worker automatically.
|
|
20
|
-
- **Advisory review:** a generic or explicitly read-only audit, inspect, report, review, code-health, plan, proposed-solution, PR/issue assessment, or cleanup-candidate assessment. Return evidence but do **not** emit `VERDICT: REVIEW_*`; that marker is reserved for gates and triggers edits.
|
|
21
|
-
- With no concrete change set and no explicit acceptance gate, default to advisory.
|
|
22
|
-
|
|
23
|
-
## Investigate the requested surface
|
|
24
|
-
- **Diff/changed files:** run `git diff` and `git status`, then read enough surrounding code to judge behavior. A concrete diff review is a gate unless the brief explicitly requests advisory/report-only output. Read supplied screenshots or mockups and compare them when relevant.
|
|
25
|
-
- **Plans:** test feasibility, completeness, hidden risks, architecture fit, and scope.
|
|
26
|
-
- **Proposed solutions:** test correctness, tradeoffs, fit with existing patterns, simpler alternatives, and edge cases.
|
|
27
|
-
- **Codebase health/audits:** inspect requested code, tests, and structure for drift, tech debt, cleanup candidates, fragile behavior, and missing coverage or documentation.
|
|
28
|
-
- **PR/issue validation:** understand context, then check root cause, focus, regression risk, tests, and docs. Use a gate only when acceptance is requested.
|
|
29
|
-
|
|
30
|
-
## Hunt checklist
|
|
31
|
-
- Logic and edge-case errors; wrong assumptions and off-by-one behavior.
|
|
32
|
-
- Error handling gaps, swallowed failures, and unreported unrun checks.
|
|
33
|
-
- Security: injection, traversal, leaked secrets, and trust-boundary mistakes.
|
|
34
|
-
- Concurrency: shared mutable state, locks across await, and races.
|
|
35
|
-
- Encoding/Unicode: lossy boundaries, incorrect Win32 `A` APIs, and length/unit errors.
|
|
36
|
-
- Resource leaks and violations of repository instructions.
|
|
37
|
-
|
|
38
|
-
## Reporting discipline
|
|
39
|
-
- Report only defensible defects or risks with file:line evidence; omit preferences and optional nits.
|
|
40
|
-
- Stay independent of `worker` and `cleaner`; fix nothing yourself.
|
|
41
|
-
- In a gate, every finding enters auto-fix, with no severity tiers. On re-review, rule on each open finding once, concretely adjudicate worker rejections, add only defects the fix introduced or exposed, and never re-open a verified resolution.
|
|
42
|
-
- Advisory findings never enter auto-fix; the caller decides whether to authorize later implementation or cleanup.
|
|
43
|
-
|
|
44
|
-
## Output
|
|
45
|
-
|
|
46
|
-
For an advisory review:
|
|
47
|
-
```text
|
|
48
|
-
## Scope Reviewed
|
|
49
|
-
- path or artifact
|
|
50
|
-
## Findings
|
|
51
|
-
- file.ts:42 — evidence-backed issue, risk, or cleanup candidate
|
|
52
|
-
(Write "None" when appropriate.)
|
|
53
|
-
## Assessment
|
|
54
|
-
Concise conclusion, tradeoffs, and uncertainty. No machine verdict line.
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
For a gate review:
|
|
58
|
-
```text
|
|
59
|
-
## Files Reviewed
|
|
60
|
-
- path/to/file.ts
|
|
61
|
-
## Findings
|
|
62
|
-
- file.ts:42 — concrete issue and why it breaks
|
|
63
|
-
(Write "None" when no finding remains.)
|
|
64
|
-
## Verdict
|
|
65
|
-
APPROVE or REQUEST_CHANGES, plus a concise rationale.
|
|
66
|
-
VERDICT: REVIEW_PASS
|
|
67
|
-
```
|
|
68
|
-
In a gate review, use `VERDICT: REVIEW_FAIL` when any finding remains. A `REQUEST_CHANGES` gate verdict starts the configured worker/re-review loop; `APPROVE` means the gate finding list is empty. Never wave an issue through or invent findings to hedge.
|
|
69
|
-
|
|
70
|
-
Use exact paths and line numbers. State uncertainty plainly.
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Adversarial read-only reviewer for generic audits, code health, plans, proposed solutions, PR/issue validation, and independent diff gates. Advisory reports never trigger edits; gate verdicts may start auto-fix.
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-sonnet-4-5
|
|
6
|
+
thinking: high
|
|
7
|
+
# Model selection: ATTENTION TO DETAIL + SECURITY AWARENESS. This is the quality gate —
|
|
8
|
+
# use the strongest available reasoning model.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
You are a senior, adversarial code reviewer. Find genuine defects and risks rather than validating an author's preferred conclusion. Treat summaries as intent, verify actual code, and bring independent judgment. You have NOT got the caller's conversation history.
|
|
12
|
+
|
|
13
|
+
## Hard constraints
|
|
14
|
+
- You are READ-ONLY. Do NOT modify files, run builds, or run tests.
|
|
15
|
+
- Bash is only for read-only commands such as `git diff/status/log/show`, `grep`, `find`, and `cat`.
|
|
16
|
+
- Tool permissions are not a safety boundary; keep every command read-only by intent.
|
|
17
|
+
|
|
18
|
+
## Choose the contract
|
|
19
|
+
- **Gate review:** a concrete diff/changed-file review, explicit pre-commit or acceptance gate, or auto-fix re-review. Return the machine verdict below. A failure can dispatch a worker automatically.
|
|
20
|
+
- **Advisory review:** a generic or explicitly read-only audit, inspect, report, review, code-health, plan, proposed-solution, PR/issue assessment, or cleanup-candidate assessment. Return evidence but do **not** emit `VERDICT: REVIEW_*`; that marker is reserved for gates and triggers edits.
|
|
21
|
+
- With no concrete change set and no explicit acceptance gate, default to advisory.
|
|
22
|
+
|
|
23
|
+
## Investigate the requested surface
|
|
24
|
+
- **Diff/changed files:** run `git diff` and `git status`, then read enough surrounding code to judge behavior. A concrete diff review is a gate unless the brief explicitly requests advisory/report-only output. Read supplied screenshots or mockups and compare them when relevant.
|
|
25
|
+
- **Plans:** test feasibility, completeness, hidden risks, architecture fit, and scope.
|
|
26
|
+
- **Proposed solutions:** test correctness, tradeoffs, fit with existing patterns, simpler alternatives, and edge cases.
|
|
27
|
+
- **Codebase health/audits:** inspect requested code, tests, and structure for drift, tech debt, cleanup candidates, fragile behavior, and missing coverage or documentation.
|
|
28
|
+
- **PR/issue validation:** understand context, then check root cause, focus, regression risk, tests, and docs. Use a gate only when acceptance is requested.
|
|
29
|
+
|
|
30
|
+
## Hunt checklist
|
|
31
|
+
- Logic and edge-case errors; wrong assumptions and off-by-one behavior.
|
|
32
|
+
- Error handling gaps, swallowed failures, and unreported unrun checks.
|
|
33
|
+
- Security: injection, traversal, leaked secrets, and trust-boundary mistakes.
|
|
34
|
+
- Concurrency: shared mutable state, locks across await, and races.
|
|
35
|
+
- Encoding/Unicode: lossy boundaries, incorrect Win32 `A` APIs, and length/unit errors.
|
|
36
|
+
- Resource leaks and violations of repository instructions.
|
|
37
|
+
|
|
38
|
+
## Reporting discipline
|
|
39
|
+
- Report only defensible defects or risks with file:line evidence; omit preferences and optional nits.
|
|
40
|
+
- Stay independent of `worker` and `cleaner`; fix nothing yourself.
|
|
41
|
+
- In a gate, every finding enters auto-fix, with no severity tiers. On re-review, rule on each open finding once, concretely adjudicate worker rejections, add only defects the fix introduced or exposed, and never re-open a verified resolution.
|
|
42
|
+
- Advisory findings never enter auto-fix; the caller decides whether to authorize later implementation or cleanup.
|
|
43
|
+
|
|
44
|
+
## Output
|
|
45
|
+
|
|
46
|
+
For an advisory review:
|
|
47
|
+
```text
|
|
48
|
+
## Scope Reviewed
|
|
49
|
+
- path or artifact
|
|
50
|
+
## Findings
|
|
51
|
+
- file.ts:42 — evidence-backed issue, risk, or cleanup candidate
|
|
52
|
+
(Write "None" when appropriate.)
|
|
53
|
+
## Assessment
|
|
54
|
+
Concise conclusion, tradeoffs, and uncertainty. No machine verdict line.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For a gate review:
|
|
58
|
+
```text
|
|
59
|
+
## Files Reviewed
|
|
60
|
+
- path/to/file.ts
|
|
61
|
+
## Findings
|
|
62
|
+
- file.ts:42 — concrete issue and why it breaks
|
|
63
|
+
(Write "None" when no finding remains.)
|
|
64
|
+
## Verdict
|
|
65
|
+
APPROVE or REQUEST_CHANGES, plus a concise rationale.
|
|
66
|
+
VERDICT: REVIEW_PASS
|
|
67
|
+
```
|
|
68
|
+
In a gate review, use `VERDICT: REVIEW_FAIL` when any finding remains. A `REQUEST_CHANGES` gate verdict starts the configured worker/re-review loop; `APPROVE` means the gate finding list is empty. Never wave an issue through or invent findings to hedge.
|
|
69
|
+
|
|
70
|
+
Use exact paths and line numbers. State uncertainty plainly.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Controllable background sub-agent threads for pi: specialized roles, capability-aware thinking, direct main-model fallback, auto-fix chains, and Git worktree isolation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/announcements.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { stat } from "node:fs/promises";
|
|
4
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { loadConfig, saveConfig } from "./config.ts";
|
|
5
|
+
import { CLEANER_AUTO_ENABLED_FEATURE, CLEANER_INHERITED_FEATURE, loadConfig, saveConfig } from "./config.ts";
|
|
6
6
|
import { announceRecoveryRecords } from "./recovery.ts";
|
|
7
7
|
import type { SubagentRuntime } from "./runtime.ts";
|
|
8
8
|
import { pruneResultArtifacts } from "./spawn.ts";
|
|
@@ -11,13 +11,23 @@ import { installActiveRunsWidget } from "./widget.ts";
|
|
|
11
11
|
const ANNOUNCEMENTS: Array<{
|
|
12
12
|
key: string;
|
|
13
13
|
condition: (config: Awaited<ReturnType<typeof loadConfig>>) => boolean;
|
|
14
|
-
message: string;
|
|
14
|
+
message: (config: Awaited<ReturnType<typeof loadConfig>>) => string;
|
|
15
15
|
}> = [
|
|
16
16
|
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
// Fires once after the load-time upgrade injected cleaner into an older
|
|
18
|
+
// config (the injection stamp only exists in that case). The extra
|
|
19
|
+
// enabledAgents check keeps the notice silent when the user already
|
|
20
|
+
// disabled cleaner (e.g. via full setup) before it could fire.
|
|
21
|
+
key: "cleanerAutoEnabledNotice",
|
|
22
|
+
condition: (config) =>
|
|
23
|
+
config.announcedFeatures.includes(CLEANER_AUTO_ENABLED_FEATURE) &&
|
|
24
|
+
config.enabledAgents.includes("cleaner"),
|
|
25
|
+
// The inheritance clause matches reality: its stamp is only set when the
|
|
26
|
+
// upgrade actually copied reviewer model/thinking settings.
|
|
27
|
+
message: (config) =>
|
|
28
|
+
config.announcedFeatures.includes(CLEANER_INHERITED_FEATURE)
|
|
29
|
+
? "pi-subagents: the built-in cleaner agent was enabled by default and inherited your reviewer model/thinking settings. Run /subagents-setup to adjust or disable it."
|
|
30
|
+
: "pi-subagents: the built-in cleaner agent was enabled by default. Run /subagents-setup to adjust or disable it.",
|
|
21
31
|
},
|
|
22
32
|
];
|
|
23
33
|
|
|
@@ -47,7 +57,7 @@ async function announceNewFeatures(
|
|
|
47
57
|
},
|
|
48
58
|
runtime.configPath,
|
|
49
59
|
);
|
|
50
|
-
for (const announcement of pending) ctx.ui.notify(announcement.message, "info");
|
|
60
|
+
for (const announcement of pending) ctx.ui.notify(announcement.message(config), "info");
|
|
51
61
|
} catch {
|
|
52
62
|
/* announcement failures are non-fatal */
|
|
53
63
|
}
|
package/src/completion.ts
CHANGED
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart batching for successful background completions.
|
|
3
|
-
*
|
|
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. Failures are
|
|
6
|
-
* intentionally handled by the caller: flush held successes, then emit the
|
|
7
|
-
* failure directly so it is never delayed.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } from "./spawn.ts";
|
|
11
|
-
import { formatUsageCompact, sumUsage } from "./monitor.ts";
|
|
12
|
-
import type { UsageStats } from "./rpc-run.ts";
|
|
13
|
-
|
|
14
|
-
export interface CompletionBatchTimings {
|
|
15
|
-
debounceMs: number;
|
|
16
|
-
maxWaitMs: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const DEFAULT_COMPLETION_BATCH_TIMINGS: CompletionBatchTimings = {
|
|
20
|
-
debounceMs: 150,
|
|
21
|
-
maxWaitMs: 1_000,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type TimerHandle = ReturnType<typeof setTimeout>;
|
|
25
|
-
|
|
26
|
-
function unrefHandle(handle: TimerHandle): void {
|
|
27
|
-
if (
|
|
28
|
-
handle &&
|
|
29
|
-
typeof handle === "object" &&
|
|
30
|
-
"unref" in handle &&
|
|
31
|
-
typeof (handle as { unref: unknown }).unref === "function"
|
|
32
|
-
) {
|
|
33
|
-
(handle as { unref: () => void }).unref();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CompletionBatcherOptions<T> {
|
|
38
|
-
emit: (items: T[]) => void;
|
|
39
|
-
timings?: Partial<CompletionBatchTimings>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface CompletionBatcher<T> {
|
|
43
|
-
/** Add an item to the current debounced group. */
|
|
44
|
-
push(item: T): void;
|
|
45
|
-
/** Emit any held items immediately as one group. */
|
|
46
|
-
flush(): void;
|
|
47
|
-
/** Clear timers and return held items without emitting them. */
|
|
48
|
-
dispose(): T[];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function createCompletionBatcher<T>(options: CompletionBatcherOptions<T>): CompletionBatcher<T> {
|
|
52
|
-
const timings = { ...DEFAULT_COMPLETION_BATCH_TIMINGS, ...options.timings };
|
|
53
|
-
let pending: T[] = [];
|
|
54
|
-
let debounceTimer: TimerHandle | null = null;
|
|
55
|
-
let maxWaitTimer: TimerHandle | null = null;
|
|
56
|
-
|
|
57
|
-
const clearTimers = (): void => {
|
|
58
|
-
if (debounceTimer !== null) {
|
|
59
|
-
clearTimeout(debounceTimer);
|
|
60
|
-
debounceTimer = null;
|
|
61
|
-
}
|
|
62
|
-
if (maxWaitTimer !== null) {
|
|
63
|
-
clearTimeout(maxWaitTimer);
|
|
64
|
-
maxWaitTimer = null;
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const emitGroup = (): void => {
|
|
69
|
-
clearTimers();
|
|
70
|
-
if (pending.length === 0) return;
|
|
71
|
-
const items = pending;
|
|
72
|
-
pending = [];
|
|
73
|
-
options.emit(items);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
push(item: T): void {
|
|
78
|
-
pending.push(item);
|
|
79
|
-
|
|
80
|
-
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
81
|
-
debounceTimer = setTimeout(emitGroup, timings.debounceMs);
|
|
82
|
-
unrefHandle(debounceTimer);
|
|
83
|
-
|
|
84
|
-
if (maxWaitTimer === null) {
|
|
85
|
-
maxWaitTimer = setTimeout(emitGroup, timings.maxWaitMs);
|
|
86
|
-
unrefHandle(maxWaitTimer);
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
flush: emitGroup,
|
|
90
|
-
dispose(): T[] {
|
|
91
|
-
clearTimers();
|
|
92
|
-
const abandoned = pending;
|
|
93
|
-
pending = [];
|
|
94
|
-
return abandoned;
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface CompletionMessageItem {
|
|
100
|
-
agent: string;
|
|
101
|
-
block: string;
|
|
102
|
-
triggerTurn: boolean;
|
|
103
|
-
/** Final usage of the underlying run (or chain); aggregated into the group totals. */
|
|
104
|
-
usage?: UsageStats;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Keep the established single-result shape; add a group header and an aggregate
|
|
108
|
-
* token/cost footer only for real groups. */
|
|
109
|
-
export function formatCompletionMessage(items: readonly CompletionMessageItem[]): string {
|
|
110
|
-
if (items.length === 0) return "";
|
|
111
|
-
if (items.length === 1) return items[0].block;
|
|
112
|
-
const agents = items.map((item) => item.agent).join(", ");
|
|
113
|
-
const withUsage = items.filter((item) => item.usage !== undefined);
|
|
114
|
-
const totals = withUsage.length > 0 ? formatUsageCompact(sumUsage(withUsage.map((item) => item.usage!))) : "";
|
|
115
|
-
const footer = totals ? `\n\nTotals: ${items.length} runs · ${totals}` : "";
|
|
116
|
-
return `### Subagents completed (${items.length}): ${agents}\n\n${items.map((item) => item.block).join("\n\n")}${footer}`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** A grouped completion wakes the main agent when any member requires a turn. */
|
|
120
|
-
export function completionGroupTriggersTurn(items: readonly CompletionMessageItem[]): boolean {
|
|
121
|
-
return items.some((item) => item.triggerTurn);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Passing reviewer notifications may opt out of waking; every other result wakes. */
|
|
125
|
-
export function completionTriggersTurn(result: SingleResult, notifyOnReviewPass: boolean): boolean {
|
|
126
|
-
if (isFailedResult(result)) return true;
|
|
127
|
-
return !(
|
|
128
|
-
notifyOnReviewPass &&
|
|
129
|
-
result.agent === "reviewer" &&
|
|
130
|
-
reviewVerdict(getResultOutput(result)) === "pass"
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Minimal shape of an active run, for the "others still running" footer. Kept
|
|
135
|
-
* decoupled from the monitor's RunView so this stays a pure, easily tested
|
|
136
|
-
* formatter; the caller maps its live runs into this shape. */
|
|
137
|
-
export interface ActiveRunFoot {
|
|
138
|
-
id: number;
|
|
139
|
-
agent: string;
|
|
140
|
-
/** Optional content label (task-derived) shown next to the agent name. */
|
|
141
|
-
label?: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Footer appended to a completion message when OTHER runs are still active, so
|
|
146
|
-
* the main agent does not declare the overall task done prematurely. A result
|
|
147
|
-
* arriving for one run does not mean sibling runs are finished; naming them
|
|
148
|
-
* gives the main agent concrete, in-context awareness to keep waiting.
|
|
149
|
-
*
|
|
150
|
-
* Returns "" when nothing is active (the common, single-run case stays quiet).
|
|
151
|
-
*/
|
|
152
|
-
export function formatActiveRunsFooter(runs: readonly ActiveRunFoot[], maxListed = 4): string {
|
|
153
|
-
if (runs.length === 0) return "";
|
|
154
|
-
const listed = runs.slice(0, maxListed);
|
|
155
|
-
const items = listed
|
|
156
|
-
.map((run) => `#${run.id} ${run.agent}${run.label ? `·${run.label}` : ""}`)
|
|
157
|
-
.join(", ");
|
|
158
|
-
const more = runs.length > listed.length ? `, +${runs.length - listed.length} more` : "";
|
|
159
|
-
return `\n\n⚠ ${runs.length} other run${runs.length === 1 ? "" : "s"} still active: ${items}${more}. Do not conclude the overall task yet — wait for their results (they wake you automatically) or check subagent_status.`;
|
|
160
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Smart batching for successful background completions.
|
|
3
|
+
*
|
|
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. Failures are
|
|
6
|
+
* intentionally handled by the caller: flush held successes, then emit the
|
|
7
|
+
* failure directly so it is never delayed.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } from "./spawn.ts";
|
|
11
|
+
import { formatUsageCompact, sumUsage } from "./monitor.ts";
|
|
12
|
+
import type { UsageStats } from "./rpc-run.ts";
|
|
13
|
+
|
|
14
|
+
export interface CompletionBatchTimings {
|
|
15
|
+
debounceMs: number;
|
|
16
|
+
maxWaitMs: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_COMPLETION_BATCH_TIMINGS: CompletionBatchTimings = {
|
|
20
|
+
debounceMs: 150,
|
|
21
|
+
maxWaitMs: 1_000,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type TimerHandle = ReturnType<typeof setTimeout>;
|
|
25
|
+
|
|
26
|
+
function unrefHandle(handle: TimerHandle): void {
|
|
27
|
+
if (
|
|
28
|
+
handle &&
|
|
29
|
+
typeof handle === "object" &&
|
|
30
|
+
"unref" in handle &&
|
|
31
|
+
typeof (handle as { unref: unknown }).unref === "function"
|
|
32
|
+
) {
|
|
33
|
+
(handle as { unref: () => void }).unref();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CompletionBatcherOptions<T> {
|
|
38
|
+
emit: (items: T[]) => void;
|
|
39
|
+
timings?: Partial<CompletionBatchTimings>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CompletionBatcher<T> {
|
|
43
|
+
/** Add an item to the current debounced group. */
|
|
44
|
+
push(item: T): void;
|
|
45
|
+
/** Emit any held items immediately as one group. */
|
|
46
|
+
flush(): void;
|
|
47
|
+
/** Clear timers and return held items without emitting them. */
|
|
48
|
+
dispose(): T[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createCompletionBatcher<T>(options: CompletionBatcherOptions<T>): CompletionBatcher<T> {
|
|
52
|
+
const timings = { ...DEFAULT_COMPLETION_BATCH_TIMINGS, ...options.timings };
|
|
53
|
+
let pending: T[] = [];
|
|
54
|
+
let debounceTimer: TimerHandle | null = null;
|
|
55
|
+
let maxWaitTimer: TimerHandle | null = null;
|
|
56
|
+
|
|
57
|
+
const clearTimers = (): void => {
|
|
58
|
+
if (debounceTimer !== null) {
|
|
59
|
+
clearTimeout(debounceTimer);
|
|
60
|
+
debounceTimer = null;
|
|
61
|
+
}
|
|
62
|
+
if (maxWaitTimer !== null) {
|
|
63
|
+
clearTimeout(maxWaitTimer);
|
|
64
|
+
maxWaitTimer = null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const emitGroup = (): void => {
|
|
69
|
+
clearTimers();
|
|
70
|
+
if (pending.length === 0) return;
|
|
71
|
+
const items = pending;
|
|
72
|
+
pending = [];
|
|
73
|
+
options.emit(items);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
push(item: T): void {
|
|
78
|
+
pending.push(item);
|
|
79
|
+
|
|
80
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
81
|
+
debounceTimer = setTimeout(emitGroup, timings.debounceMs);
|
|
82
|
+
unrefHandle(debounceTimer);
|
|
83
|
+
|
|
84
|
+
if (maxWaitTimer === null) {
|
|
85
|
+
maxWaitTimer = setTimeout(emitGroup, timings.maxWaitMs);
|
|
86
|
+
unrefHandle(maxWaitTimer);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
flush: emitGroup,
|
|
90
|
+
dispose(): T[] {
|
|
91
|
+
clearTimers();
|
|
92
|
+
const abandoned = pending;
|
|
93
|
+
pending = [];
|
|
94
|
+
return abandoned;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface CompletionMessageItem {
|
|
100
|
+
agent: string;
|
|
101
|
+
block: string;
|
|
102
|
+
triggerTurn: boolean;
|
|
103
|
+
/** Final usage of the underlying run (or chain); aggregated into the group totals. */
|
|
104
|
+
usage?: UsageStats;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Keep the established single-result shape; add a group header and an aggregate
|
|
108
|
+
* token/cost footer only for real groups. */
|
|
109
|
+
export function formatCompletionMessage(items: readonly CompletionMessageItem[]): string {
|
|
110
|
+
if (items.length === 0) return "";
|
|
111
|
+
if (items.length === 1) return items[0].block;
|
|
112
|
+
const agents = items.map((item) => item.agent).join(", ");
|
|
113
|
+
const withUsage = items.filter((item) => item.usage !== undefined);
|
|
114
|
+
const totals = withUsage.length > 0 ? formatUsageCompact(sumUsage(withUsage.map((item) => item.usage!))) : "";
|
|
115
|
+
const footer = totals ? `\n\nTotals: ${items.length} runs · ${totals}` : "";
|
|
116
|
+
return `### Subagents completed (${items.length}): ${agents}\n\n${items.map((item) => item.block).join("\n\n")}${footer}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A grouped completion wakes the main agent when any member requires a turn. */
|
|
120
|
+
export function completionGroupTriggersTurn(items: readonly CompletionMessageItem[]): boolean {
|
|
121
|
+
return items.some((item) => item.triggerTurn);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Passing reviewer notifications may opt out of waking; every other result wakes. */
|
|
125
|
+
export function completionTriggersTurn(result: SingleResult, notifyOnReviewPass: boolean): boolean {
|
|
126
|
+
if (isFailedResult(result)) return true;
|
|
127
|
+
return !(
|
|
128
|
+
notifyOnReviewPass &&
|
|
129
|
+
result.agent === "reviewer" &&
|
|
130
|
+
reviewVerdict(getResultOutput(result)) === "pass"
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Minimal shape of an active run, for the "others still running" footer. Kept
|
|
135
|
+
* decoupled from the monitor's RunView so this stays a pure, easily tested
|
|
136
|
+
* formatter; the caller maps its live runs into this shape. */
|
|
137
|
+
export interface ActiveRunFoot {
|
|
138
|
+
id: number;
|
|
139
|
+
agent: string;
|
|
140
|
+
/** Optional content label (task-derived) shown next to the agent name. */
|
|
141
|
+
label?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Footer appended to a completion message when OTHER runs are still active, so
|
|
146
|
+
* the main agent does not declare the overall task done prematurely. A result
|
|
147
|
+
* arriving for one run does not mean sibling runs are finished; naming them
|
|
148
|
+
* gives the main agent concrete, in-context awareness to keep waiting.
|
|
149
|
+
*
|
|
150
|
+
* Returns "" when nothing is active (the common, single-run case stays quiet).
|
|
151
|
+
*/
|
|
152
|
+
export function formatActiveRunsFooter(runs: readonly ActiveRunFoot[], maxListed = 4): string {
|
|
153
|
+
if (runs.length === 0) return "";
|
|
154
|
+
const listed = runs.slice(0, maxListed);
|
|
155
|
+
const items = listed
|
|
156
|
+
.map((run) => `#${run.id} ${run.agent}${run.label ? `·${run.label}` : ""}`)
|
|
157
|
+
.join(", ");
|
|
158
|
+
const more = runs.length > listed.length ? `, +${runs.length - listed.length} more` : "";
|
|
159
|
+
return `\n\n⚠ ${runs.length} other run${runs.length === 1 ? "" : "s"} still active: ${items}${more}. Do not conclude the overall task yet — wait for their results (they wake you automatically) or check subagent_status.`;
|
|
160
|
+
}
|