pi-advisor-flow 0.5.0 → 0.5.2
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/CHANGELOG.md +54 -0
- package/README.md +37 -71
- package/extensions/index.ts +7 -7
- package/package.json +19 -6
- package/src/commands/activation-preparation.ts +117 -0
- package/src/commands/activation.ts +135 -0
- package/src/commands/lifecycle.ts +83 -0
- package/src/commands/manual-command.ts +107 -0
- package/src/commands/manual-consultation.ts +144 -0
- package/src/commands/manual-progress.ts +93 -0
- package/src/commands/model-commands.ts +66 -0
- package/src/commands/model-options.ts +181 -0
- package/src/commands/model-picker.ts +110 -0
- package/src/commands/registration.ts +27 -0
- package/src/commands/renderers.ts +76 -0
- package/src/commands/runtime.ts +114 -0
- package/src/commands/settings-commands.ts +80 -0
- package/src/commands/settings-persistence.ts +81 -0
- package/src/commands/types.ts +78 -0
- package/src/commands.ts +9 -946
- package/src/config/args.ts +37 -0
- package/src/config/defaults.ts +190 -0
- package/src/config/state.ts +199 -0
- package/src/config/storage.ts +224 -0
- package/src/config/types.ts +63 -0
- package/src/config/validation.ts +234 -0
- package/src/config.ts +104 -791
- package/src/conversation.ts +4 -2
- package/src/herdr.ts +1 -1
- package/src/model-stream.ts +25 -2
- package/src/scout-context.ts +2 -2
- package/src/scout.ts +1 -1
- package/src/tools/consultation.ts +337 -0
- package/src/tools/gate-policy.ts +132 -0
- package/src/tools/gate-protocol.ts +127 -0
- package/src/tools/loop-gate.ts +210 -0
- package/src/tools/prompts.ts +159 -0
- package/src/tools/register-ask-advisor.ts +225 -0
- package/src/tools/register-lifecycle.ts +89 -0
- package/src/tools/register-outcome.ts +75 -0
- package/src/tools/register-renderers.ts +88 -0
- package/src/tools/registration.ts +35 -0
- package/src/tools/render-advisor-result.ts +168 -0
- package/src/tools/render-common.ts +120 -0
- package/src/tools/scout-status.ts +206 -0
- package/src/tools/session.ts +3 -0
- package/src/tools/types.ts +119 -0
- package/src/tools.ts +42 -1811
- package/src/ui/manual-dialog-render.ts +150 -0
- package/src/ui/manual-dialog.ts +305 -0
- package/src/ui/model-selector.ts +166 -0
- package/src/ui/settings-formatting.ts +133 -0
- package/src/ui/settings-items.ts +323 -0
- package/src/ui/settings-list-adapter.ts +103 -0
- package/src/ui/settings-mutations.ts +80 -0
- package/src/ui/settings-selector.ts +161 -0
- package/src/ui/text-setting-submenu.ts +66 -0
- package/src/ui/types.ts +111 -0
- package/src/ui.ts +9 -1418
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,60 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.5.2 - 2026-09-05
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Reorganized configuration, UI, Advisor tool, and command code into smaller modules behind the existing public facades without changing the extension's public API.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Kept failed or aborted Advisor streams from treating partial responses as successful advice or automatic gate permission.
|
|
16
|
+
- Made malformed automatic-gate Markdown fences fail closed when their character or delimiter length does not match.
|
|
17
|
+
- Applied optional secret redaction to targeted Advisor questions before sending provider requests while retaining the raw question for local displays.
|
|
18
|
+
- Kept the TUI tab keybinding out of Socket's URL-string heuristic without changing its runtime behavior.
|
|
19
|
+
- Made benchmark extension attestations follow the root `package.json` version instead of a duplicated constant.
|
|
20
|
+
|
|
21
|
+
## 0.5.1
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Updated CI and local validation to Bun 1.4.1 and kept the Pi compatibility packages at 0.84.4; Pi 0.85.0 currently adds an eager `/server` import without declaring the `@earendil-works/pi-server` dependency required by its coding-agent entry point.
|
|
26
|
+
- Kept benchmark tests out of the normal test run; use `bun run test:bench` to run them explicitly.
|
|
27
|
+
- Added Socket security dependency overrides and a `socket` script for `bunx socket optimize`.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Added a repository-only failsafe benchmark with an offline replay tier, a
|
|
32
|
+
24-item decision-point corpus, a fail-closed Pi/Harbor ReactBench adapter,
|
|
33
|
+
pinned live-tier controls, hard budget limits, and task-level
|
|
34
|
+
uplift/dominance reporting. See [Benchmarking](docs/benchmark.md).
|
|
35
|
+
- Added bounded local Harbor runtime controls for Apple Container experiments,
|
|
36
|
+
Docker build-cache cleanup, and a recorded one-hour agent timeout for complex
|
|
37
|
+
ReactBench tasks.
|
|
38
|
+
- Added a disposable, credential-free GitHub build compatibility layer for
|
|
39
|
+
pinned ReactBench Dockerfiles whose Git 2.39 transport is rejected by the
|
|
40
|
+
GitHub endpoint, with bounded pre-agent Harbor infrastructure retries and
|
|
41
|
+
per-attempt artifacts for transient build and transport failures.
|
|
42
|
+
- Derived the host Harbor command timeout from the bounded agent timeout and
|
|
43
|
+
made timeout cleanup terminate the full Harbor process group, including the
|
|
44
|
+
broker and descendants.
|
|
45
|
+
- Ensured configured Advisor reasoning effort reaches the provider-facing
|
|
46
|
+
request field used by current Pi AI adapters.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- Made `/advisor` open the available-model picker on first use or when either saved model is missing or unavailable; activation never silently selects a model outside the user's available catalog.
|
|
51
|
+
- Added a concise `/advisor` explanation of the Advisor's second-opinion role.
|
|
52
|
+
- Preserved Markdown formatting in visible Scout and nested Advisor thinking previews while retaining their speech-bubble cue. During streaming, incomplete Markdown delimiters display transiently until they close, which is expected behavior when thinking arrives incrementally.
|
|
53
|
+
- Adopted an explicit `/model` selection made before `/advisor` as the Executor on the next successful activation, without persisting ordinary model changes while the flow is off.
|
|
54
|
+
|
|
55
|
+
## [5.1.0] - 2026-09-04 [YANKED]
|
|
56
|
+
|
|
57
|
+
### Release status
|
|
58
|
+
|
|
59
|
+
- Published by mistake instead of `0.5.1`; withdrawn from npm and removed from Git, with `latest` restored to `0.5.0`.
|
|
60
|
+
|
|
7
61
|
## 0.5.0
|
|
8
62
|
|
|
9
63
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pi-advisor
|
|
1
|
+
# [pi-advisor](https://github.com/philipbrembeck/pi-advisor)
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
@@ -8,114 +8,80 @@ A configurable second-opinion workflow for <a href="https://github.com/earendil-
|
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
+
  
|
|
11
12
|
|
|
12
|
-
 
|
|
13
13
|
|
|
14
14
|
`pi-advisor-flow` keeps one model focused on execution and makes a second, smarter model available for consequential decisions, stalled work, and final reviews. The Executor still owns the work. The Advisor challenges assumptions, exposes risks, and suggests verification steps without taking over or running tools.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Keep implementation on a fast model and borrow frontier reasoning only when decisions matter. [Read why this workflow is useful](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need).
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## How it works
|
|
19
|
+
|
|
20
|
+
1. The Executor works on your task as usual.
|
|
21
|
+
2. It calls `ask_advisor`, or an enabled gate starts a review.
|
|
22
|
+
3. pi-advisor reconstructs the relevant conversation and allowed repository context.
|
|
23
|
+
4. The Advisor returns an opinion. The Executor decides what to adopt, changes the code, and validates it.
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
- **Configurable review gates** before plans, after repeated failures, and before declaring completion.
|
|
22
|
-
- **Automatic loop detection** for repeated tool calls, with explicit proceed, revise, or blocked decisions.
|
|
23
|
-
- **Separate model and reasoning controls** for the Executor and Advisor.
|
|
24
|
-
- **Advisor usage accounting** with per-response token/cost details and optional cumulative direct usage in the Pi footer and session summary. Per-response usage and cost details are shown by default and can be hidden independently from the footer in `/advisor-settings`.
|
|
25
|
-
- **Privacy controls** for conversation history, repository context, explicit tracked/untracked file handoff, tool results, secret redaction, and outcome logging.
|
|
26
|
-
- **Optional persistent activation, Simple mode, session summaries, and Herdr integration.**
|
|
27
|
-
- **Compact searchable `/advisor-settings` controls** that match Pi's settings list and save changes immediately.
|
|
28
|
-
- **EXPERIMENTAL Advisor Scout** that uses the configured Executor model to curate conversation evidence before every Advisor call.
|
|
25
|
+
Regular consultations do not block execution. Automatic loop gates are different: they can stop a repeated tool action or session based on your configured failure policy.
|
|
29
26
|
|
|
30
27
|
## Install
|
|
31
28
|
|
|
32
|
-
Requires Pi 0.84.1 or later
|
|
29
|
+
Requires Pi 0.84.1 or later.
|
|
33
30
|
|
|
34
31
|
```bash
|
|
35
|
-
# npm
|
|
36
32
|
pi install npm:pi-advisor-flow
|
|
33
|
+
```
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
pi install git:github.com/philipbrembeck/pi-advisor.git
|
|
35
|
+
You can also install from GitHub:
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
pi install /
|
|
37
|
+
```bash
|
|
38
|
+
pi install git:github.com/philipbrembeck/pi-advisor.git
|
|
43
39
|
```
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
Reload Pi after installing.
|
|
46
42
|
|
|
47
43
|
## Quick start
|
|
48
44
|
|
|
49
|
-
1. Run `/advisor` to enable the flow and register `ask_advisor`.
|
|
50
|
-
2. Run `/advisor-models` to choose the Executor and Advisor models. Current model and thinking-level selections appear first and ticked, so pressing Enter keeps them.
|
|
51
|
-
3. Run `/advisor-settings` to configure review gates, context, privacy, and limits. Type to fuzzy-search settings; changes save immediately.
|
|
52
|
-
|
|
53
|
-

|
|
54
|
-
|
|
55
|
-
Unknown fields in `advisor.json` are preserved for forward compatibility and reported as non-blocking warnings. Invalid recognized values remain errors, and Advisor commands show the configuration problem without crashing their handlers.
|
|
56
|
-
|
|
57
|
-
You can also enable the flow and select both models at once:
|
|
58
|
-
|
|
59
45
|
```text
|
|
60
|
-
/advisor
|
|
46
|
+
/advisor # Enable the Advisor Flow
|
|
47
|
+
/advisor-models # Choose the Executor and Advisor models
|
|
48
|
+
/advisor-settings # Configure behavior, modes, etc.
|
|
61
49
|
```
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
1. The Executor investigates the task and forms its own candidate direction.
|
|
66
|
-
2. For a consequential decision, stalled attempt, or final review, it calls `ask_advisor` with the reconstructed conversation and allowed repository context.
|
|
67
|
-
3. When Experimental Advisor Scout is enabled, the configured Executor model selects relevant conversation groups and writes a short, explicitly untrusted synthesis.
|
|
68
|
-
4. The Advisor receives selected verbatim evidence, required current-request context, and the unchanged deterministic repository, preference, draft, and attachment regions.
|
|
69
|
-
5. The Executor decides what to adopt, performs the work, and validates the result.
|
|
70
|
-
|
|
71
|
-
A normal consultation never blocks execution. The optional automatic loop gate is different: it evaluates repeated tool calls and applies the configured failure policy when the Advisor says to revise, reports a block, is unavailable, or returns an invalid decision.
|
|
51
|
+
On first use, or whenever a saved model is unavailable, `/advisor` opens the same available-model picker as `/advisor-models`; it never silently chooses an unconfigured model. After activation, `/advisor` explains that the Advisor reviews the Executor's context without changing files or running tools.
|
|
72
52
|
|
|
73
|
-
|
|
53
|
+
From the Executor, `ask_advisor({})` requests a general review. A targeted `question` or concise `draft` can focus the review on a particular decision.
|
|
74
54
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### Experimental Advisor Scout
|
|
78
|
-
|
|
79
|
-
Experimental Advisor Scout is off by default. Enable `Experimental Advisor Scout` in the advanced `/advisor-settings` screen or set `"advisorScoutEnabled": true` in the global `advisor.json`.
|
|
80
|
-
|
|
81
|
-
Scout runs before `ask_advisor`, `/advisor-manual`, and automatic Advisor gates. It uses the configured Executor model and Executor reasoning effort in a separate model call. This adds cost and latency, but can reduce cost in the Advisor call. The compact result shows the model, selection counts, elapsed time, and usage/cost details; `Ctrl+O` shows bounded selected labels and the synthesis. Usage and cost details can be hidden in `/advisor-settings`.
|
|
82
|
-
|
|
83
|
-
Scout receives a bounded manifest of conversation and tool-history groups after the normal tool disclosure, result-cap, and redaction policies are applied. The Scout manifest has its own fixed transport limit, while the reconstructed conversation remains bounded by the Advisor's remaining context budget after repository context; manifest metadata no longer consumes that Advisor conversation budget. A zero remaining budget produces no history groups. For a pending `ask_advisor` call, Scout receives only the allowlisted question and Git-context preference, never the draft or explicit attachment paths. Scout does not receive the deterministic Git context, draft, project preferences, or explicit tracked and untracked attachments. Those regions are appended later through their existing consent and cap rules.
|
|
84
|
-
|
|
85
|
-
This experiment adapts the context-boundary idea from Zhang et al., ["FastContext: Training Efficient Repository Explorer for Coding Agents"](https://arxiv.org/html/2606.14066v1). It is not a reproduction of FastContext. pi-advisor Scout curates conversation history only.
|
|
55
|
+
In the Settings, enable the Simple Mode for a quick start.
|
|
56
|
+

|
|
86
57
|
|
|
87
58
|
## Commands
|
|
88
59
|
|
|
89
|
-
| Command
|
|
90
|
-
|
|
|
91
|
-
| `/advisor`
|
|
92
|
-
| `/advisor-manual [focus]` |
|
|
93
|
-
| `/advisor-models`
|
|
94
|
-
| `/advisor-settings`
|
|
95
|
-
| `/advisor-off`
|
|
60
|
+
| Command | What it does |
|
|
61
|
+
| ------------------------- | ------------------------------------------------- |
|
|
62
|
+
| `/advisor` | Enable the flow; choose available models when needed. |
|
|
63
|
+
| `/advisor-manual [focus]` | Ask for an immediate second opinion. |
|
|
64
|
+
| `/advisor-models` | Choose the Executor and Advisor models. |
|
|
65
|
+
| `/advisor-settings` | Configure behavior, context, privacy, and limits. |
|
|
66
|
+
| `/advisor-off` | Disable the flow and persistent activation. |
|
|
96
67
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
The Executor calls `ask_advisor({})` for a general review. It can pass a targeted `question` or a concise `draft` describing proposed work, validation, and remaining risks. If the Advisor explicitly says it cannot review a specifically named file, the Executor may make a sequential follow-up call with `includeTrackedFiles` when global consent is enabled and the file is relevant. Draft claims give the Advisor review context; they are not verification evidence.
|
|
68
|
+
### Experimental Advisor Scout
|
|
100
69
|
|
|
101
|
-
|
|
70
|
+
Advisor Scout is off by default. When enabled, the Executor model first selects relevant conversation history before the Advisor sees it. This adds a model call, latency, and cost. See the [configuration guide](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/configuration.md) for details.
|
|
102
71
|
|
|
103
|
-
|
|
72
|
+
## Privacy
|
|
104
73
|
|
|
105
|
-
|
|
74
|
+
Advisor requests can include user messages, tool calls, tool results, targeted questions, and repository information. `/advisor-settings` controls context, tool disclosure, redaction, and explicit file handoff. Secret redaction is off by default; when enabled, credential-shaped values in targeted questions are redacted before the provider request. Tools without an explicit policy use full context. Settings are global, so a project cannot silently change them.
|
|
106
75
|
|
|
107
|
-
|
|
76
|
+
Read [Privacy and data handling](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/privacy.md) before using pi-advisor with sensitive work.
|
|
108
77
|
|
|
109
78
|
## Documentation
|
|
110
79
|
|
|
111
80
|
- [Configuration and automatic loop gates](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/configuration.md)
|
|
112
81
|
- [Privacy and data handling](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/privacy.md)
|
|
113
82
|
- [Development](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/development.md)
|
|
114
|
-
- [
|
|
115
|
-
|
|
116
|
-
## Links
|
|
117
|
-
|
|
118
|
-
- [MIT License](LICENSE)
|
|
83
|
+
- [Benchmarking](docs/benchmark.md)
|
|
119
84
|
- [Changelog](CHANGELOG.md)
|
|
85
|
+
- [MIT License](LICENSE)
|
|
120
86
|
- [npm package](https://www.npmjs.com/package/pi-advisor-flow)
|
|
121
87
|
- [Why use an Advisor flow?](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need)
|
package/extensions/index.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { registerCommands } from "../src/commands.js";
|
|
2
|
+
import { registerCommands } from "../src/commands/registration.js";
|
|
3
3
|
import { setHerdrBlockedEmitter } from "../src/herdr.js";
|
|
4
4
|
import { AdvisorSessionState } from "../src/session-state.js";
|
|
5
5
|
import {
|
|
6
6
|
consultAdvisor as consultAdvisorImplementation,
|
|
7
|
-
parseAutomaticDecision as parseAutomaticDecisionImplementation,
|
|
8
|
-
registerAdvisorTool,
|
|
9
7
|
runAdvisorGate as runAdvisorGateImplementation,
|
|
10
|
-
|
|
11
|
-
} from "../src/tools.js";
|
|
8
|
+
} from "../src/tools/consultation.js";
|
|
9
|
+
import { parseAutomaticDecision as parseAutomaticDecisionImplementation } from "../src/tools/gate-protocol.js";
|
|
10
|
+
import { registerAdvisorTool } from "../src/tools/registration.js";
|
|
11
|
+
import { ScoutStatusManager } from "../src/tools/scout-status.js";
|
|
12
12
|
|
|
13
|
-
export type { AdvisorConfig, GateFailureMode } from "../src/config.js";
|
|
13
|
+
export type { AdvisorConfig, GateFailureMode } from "../src/config/types.js";
|
|
14
14
|
export type {
|
|
15
15
|
AdvisorConsultationResult,
|
|
16
16
|
AdvisorGateFailure,
|
|
@@ -19,7 +19,7 @@ export type {
|
|
|
19
19
|
ConsultationTrigger,
|
|
20
20
|
GateDecision,
|
|
21
21
|
GateTrigger,
|
|
22
|
-
} from "../src/tools.js";
|
|
22
|
+
} from "../src/tools/types.js";
|
|
23
23
|
export const consultAdvisor = (
|
|
24
24
|
...args: Parameters<typeof consultAdvisorImplementation>
|
|
25
25
|
) => consultAdvisorImplementation(...args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-advisor-flow",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -28,33 +28,46 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
+
"bench:decisions": "bun bench/src/cli.ts decisions",
|
|
32
|
+
"bench:evaluate": "bun bench/src/cli.ts evaluate",
|
|
33
|
+
"bench:replay": "bun bench/src/cli.ts replay",
|
|
34
|
+
"bench:screen": "bun bench/src/cli.ts screen",
|
|
35
|
+
"check:boundaries": "node scripts/check-module-boundaries.mjs",
|
|
31
36
|
"format": "bunx ultracite fix --linter-enabled=false",
|
|
32
37
|
"lint": "bunx ultracite check",
|
|
33
38
|
"lint:fix": "bunx ultracite fix",
|
|
34
39
|
"package:check": "node scripts/check-package.mjs",
|
|
35
40
|
"prepare": "husky",
|
|
41
|
+
"socket": "bunx socket optimize",
|
|
36
42
|
"test": "bun test",
|
|
43
|
+
"test:bench": "bun --config=./bunfig.bench.toml test ./bench/test/",
|
|
37
44
|
"typecheck": "tsc --noEmit"
|
|
38
45
|
},
|
|
39
46
|
"lint-staged": {
|
|
40
47
|
"*.{json,jsonc,ts}": "bun run lint:fix --"
|
|
41
48
|
},
|
|
49
|
+
"resolutions": {
|
|
50
|
+
"is-unicode-supported": "npm:@socketregistry/is-unicode-supported@^1",
|
|
51
|
+
"safe-buffer": "npm:@socketregistry/safe-buffer@^1"
|
|
52
|
+
},
|
|
42
53
|
"overrides": {
|
|
43
54
|
"brace-expansion": "5.0.9",
|
|
55
|
+
"is-unicode-supported": "npm:@socketregistry/is-unicode-supported@^1",
|
|
56
|
+
"safe-buffer": "npm:@socketregistry/safe-buffer@^1",
|
|
44
57
|
"undici": "8.9.0"
|
|
45
58
|
},
|
|
46
59
|
"devDependencies": {
|
|
47
|
-
"@biomejs/biome": "2.5.
|
|
60
|
+
"@biomejs/biome": "2.5.12",
|
|
48
61
|
"@earendil-works/pi-ai": "^0.84.4",
|
|
49
62
|
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
50
63
|
"@earendil-works/pi-tui": "^0.84.4",
|
|
51
|
-
"@types/node": "^26.4.
|
|
52
|
-
"bun-types": "1.
|
|
64
|
+
"@types/node": "^26.4.1",
|
|
65
|
+
"bun-types": "1.4.1",
|
|
53
66
|
"husky": "^9.1.7",
|
|
54
67
|
"lint-staged": "^17.4.1",
|
|
55
|
-
"typebox": "^1.3.
|
|
68
|
+
"typebox": "^1.3.25",
|
|
56
69
|
"typescript": "^7.0.2",
|
|
57
|
-
"ultracite": "7.10.
|
|
70
|
+
"ultracite": "7.10.8"
|
|
58
71
|
},
|
|
59
72
|
"peerDependencies": {
|
|
60
73
|
"@earendil-works/pi-ai": "^0.84.1",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
advisorEffortRef,
|
|
4
|
+
advisorRef,
|
|
5
|
+
executorEffortRef,
|
|
6
|
+
executorRef,
|
|
7
|
+
getPersistedModelRefs,
|
|
8
|
+
setAdvisorEffortRef,
|
|
9
|
+
setAdvisorRef,
|
|
10
|
+
setExecutorEffortRef,
|
|
11
|
+
setExecutorRef,
|
|
12
|
+
} from "../config/state.js";
|
|
13
|
+
import { loadConfig } from "../config/storage.js";
|
|
14
|
+
import {
|
|
15
|
+
getAvailableModelRefs,
|
|
16
|
+
getExplicitModelError,
|
|
17
|
+
planActivationModels,
|
|
18
|
+
} from "./model-options.js";
|
|
19
|
+
import { selectAdvisorModels } from "./model-picker.js";
|
|
20
|
+
import { notify } from "./runtime.js";
|
|
21
|
+
import type { CommandRuntime } from "./types.js";
|
|
22
|
+
|
|
23
|
+
export interface PreparedActivationModels {
|
|
24
|
+
pendingExecutor?: string;
|
|
25
|
+
pickedModels: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const loadCommandConfig = (ctx: ExtensionContext) => {
|
|
29
|
+
try {
|
|
30
|
+
loadConfig(ctx);
|
|
31
|
+
return true;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
notify(
|
|
35
|
+
ctx,
|
|
36
|
+
`Advisor command could not load configuration: ${message} Fix advisor.json and retry.`,
|
|
37
|
+
"error"
|
|
38
|
+
);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const prepareActivationModels = async (
|
|
44
|
+
runtime: CommandRuntime,
|
|
45
|
+
ctx: ExtensionContext,
|
|
46
|
+
announce: boolean,
|
|
47
|
+
executorOverride: boolean,
|
|
48
|
+
advisorOverride: boolean
|
|
49
|
+
): Promise<PreparedActivationModels | undefined> => {
|
|
50
|
+
const persisted = getPersistedModelRefs();
|
|
51
|
+
const availableRefs = getAvailableModelRefs(ctx);
|
|
52
|
+
const availableRefSet = availableRefs ? new Set(availableRefs) : undefined;
|
|
53
|
+
const explicitError =
|
|
54
|
+
getExplicitModelError(
|
|
55
|
+
ctx,
|
|
56
|
+
executorRef,
|
|
57
|
+
"Executor",
|
|
58
|
+
executorOverride,
|
|
59
|
+
availableRefSet
|
|
60
|
+
) ??
|
|
61
|
+
getExplicitModelError(
|
|
62
|
+
ctx,
|
|
63
|
+
advisorRef,
|
|
64
|
+
"Advisor",
|
|
65
|
+
advisorOverride,
|
|
66
|
+
availableRefSet
|
|
67
|
+
);
|
|
68
|
+
if (explicitError) {
|
|
69
|
+
notify(ctx, explicitError, "error");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const plan = planActivationModels(
|
|
74
|
+
ctx,
|
|
75
|
+
executorRef,
|
|
76
|
+
advisorRef,
|
|
77
|
+
runtime.pendingExecutorModelRef,
|
|
78
|
+
persisted,
|
|
79
|
+
executorOverride,
|
|
80
|
+
advisorOverride,
|
|
81
|
+
availableRefSet
|
|
82
|
+
);
|
|
83
|
+
setExecutorRef(plan.pendingExecutor ?? executorRef);
|
|
84
|
+
if (!(plan.selectExecutor || plan.selectAdvisor)) {
|
|
85
|
+
return { pendingExecutor: plan.pendingExecutor, pickedModels: false };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Always-on startup cannot open an interactive picker, so it leaves the
|
|
89
|
+
// flow disabled until the user selects both models with `/advisor`.
|
|
90
|
+
if (!announce) {
|
|
91
|
+
notify(
|
|
92
|
+
ctx,
|
|
93
|
+
"Advisor models are not configured or available. Run /advisor to choose them.",
|
|
94
|
+
"error"
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const selection = await selectAdvisorModels(ctx, {
|
|
99
|
+
advisor: advisorOverride || persisted.advisor ? advisorRef : "",
|
|
100
|
+
advisorEffort: advisorEffortRef,
|
|
101
|
+
executor:
|
|
102
|
+
executorOverride || plan.pendingExecutor || persisted.executor
|
|
103
|
+
? executorRef
|
|
104
|
+
: "",
|
|
105
|
+
executorEffort: executorEffortRef,
|
|
106
|
+
selectAdvisor: plan.selectAdvisor,
|
|
107
|
+
selectExecutor: plan.selectExecutor,
|
|
108
|
+
});
|
|
109
|
+
if (!selection) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setAdvisorRef(selection.advisor);
|
|
113
|
+
setAdvisorEffortRef(selection.advisorEffort);
|
|
114
|
+
setExecutorRef(selection.executor);
|
|
115
|
+
setExecutorEffortRef(selection.executorEffort);
|
|
116
|
+
return { pendingExecutor: plan.pendingExecutor, pickedModels: true };
|
|
117
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { parseArgs } from "../config/args.js";
|
|
3
|
+
import {
|
|
4
|
+
advisorEffortRef,
|
|
5
|
+
advisorRef,
|
|
6
|
+
contextMaxCharsRef,
|
|
7
|
+
executorEffortRef,
|
|
8
|
+
executorRef,
|
|
9
|
+
setAdvisorEffortRef,
|
|
10
|
+
setAdvisorRef,
|
|
11
|
+
setContextMaxCharsRef,
|
|
12
|
+
setExecutorEffortRef,
|
|
13
|
+
setExecutorRef,
|
|
14
|
+
} from "../config/state.js";
|
|
15
|
+
import { saveConfig } from "../config/storage.js";
|
|
16
|
+
import {
|
|
17
|
+
loadCommandConfig,
|
|
18
|
+
prepareActivationModels,
|
|
19
|
+
} from "./activation-preparation.js";
|
|
20
|
+
import {
|
|
21
|
+
ADVISOR_ACTIVATION_EXPLANATION,
|
|
22
|
+
findConfiguredModel,
|
|
23
|
+
hasAdvisorOverride,
|
|
24
|
+
hasExecutorOverride,
|
|
25
|
+
} from "./model-options.js";
|
|
26
|
+
import { notify } from "./runtime.js";
|
|
27
|
+
import type { CommandRuntime, ThinkingLevel } from "./types.js";
|
|
28
|
+
|
|
29
|
+
const resolveActivationModels = async (
|
|
30
|
+
runtime: CommandRuntime,
|
|
31
|
+
ctx: ExtensionContext
|
|
32
|
+
) => {
|
|
33
|
+
const executor = findConfiguredModel(ctx, executorRef);
|
|
34
|
+
if (!executor) {
|
|
35
|
+
return {
|
|
36
|
+
error: executorRef
|
|
37
|
+
? `Executor model not found: ${executorRef}`
|
|
38
|
+
: "Executor model not configured",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const advisor = findConfiguredModel(ctx, advisorRef);
|
|
42
|
+
if (!advisor) {
|
|
43
|
+
return {
|
|
44
|
+
error: advisorRef
|
|
45
|
+
? `Advisor model not found: ${advisorRef}`
|
|
46
|
+
: "Advisor model not configured",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const advisorAuth = await ctx.modelRegistry.getApiKeyAndHeaders(advisor);
|
|
50
|
+
if (!(advisorAuth.ok && advisorAuth.apiKey)) {
|
|
51
|
+
return { error: `No API key for Advisor ${advisorRef}` };
|
|
52
|
+
}
|
|
53
|
+
if (!(await runtime.setExecutorModel(executor))) {
|
|
54
|
+
return { error: `No API key for Executor ${executorRef}` };
|
|
55
|
+
}
|
|
56
|
+
return {};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const activateAdvisor = async (
|
|
60
|
+
runtime: CommandRuntime,
|
|
61
|
+
args: string,
|
|
62
|
+
ctx: ExtensionContext,
|
|
63
|
+
announce = true
|
|
64
|
+
) => {
|
|
65
|
+
if (!loadCommandConfig(ctx)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const previous = {
|
|
69
|
+
advisor: advisorRef,
|
|
70
|
+
advisorEffort: advisorEffortRef,
|
|
71
|
+
contextMaxChars: contextMaxCharsRef,
|
|
72
|
+
executor: executorRef,
|
|
73
|
+
executorEffort: executorEffortRef,
|
|
74
|
+
};
|
|
75
|
+
const restoreRefs = () => {
|
|
76
|
+
setAdvisorRef(previous.advisor);
|
|
77
|
+
setAdvisorEffortRef(previous.advisorEffort);
|
|
78
|
+
setContextMaxCharsRef(previous.contextMaxChars);
|
|
79
|
+
setExecutorRef(previous.executor);
|
|
80
|
+
setExecutorEffortRef(previous.executorEffort);
|
|
81
|
+
};
|
|
82
|
+
const executorOverride = hasExecutorOverride(args);
|
|
83
|
+
const advisorOverride = hasAdvisorOverride(args);
|
|
84
|
+
const argumentError = parseArgs(args);
|
|
85
|
+
if (argumentError) {
|
|
86
|
+
restoreRefs();
|
|
87
|
+
notify(ctx, argumentError, "error");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const prepared = await prepareActivationModels(
|
|
92
|
+
runtime,
|
|
93
|
+
ctx,
|
|
94
|
+
announce,
|
|
95
|
+
executorOverride,
|
|
96
|
+
advisorOverride
|
|
97
|
+
);
|
|
98
|
+
if (!prepared) {
|
|
99
|
+
restoreRefs();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const { error } = await resolveActivationModels(runtime, ctx);
|
|
103
|
+
if (error) {
|
|
104
|
+
restoreRefs();
|
|
105
|
+
notify(ctx, error, "error");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// parseArgs, model picking, and an inactive `/model` selection only mutate
|
|
109
|
+
// in-memory refs. Persist them once both models are known and authenticated,
|
|
110
|
+
// so an unusable model reference is never written to the configuration.
|
|
111
|
+
if (args.trim() || prepared.pickedModels || prepared.pendingExecutor) {
|
|
112
|
+
saveConfig(ctx, { persistAdvisor: true, persistExecutor: true });
|
|
113
|
+
}
|
|
114
|
+
// A successful activation has committed the effective Executor. Do not let
|
|
115
|
+
// an older inactive selection override an explicit activation argument on a
|
|
116
|
+
// later attempt.
|
|
117
|
+
runtime.pendingExecutorModelRef = undefined;
|
|
118
|
+
if (executorEffortRef) {
|
|
119
|
+
runtime.pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
|
|
120
|
+
}
|
|
121
|
+
if (!runtime.flowEnabled()) {
|
|
122
|
+
runtime.pi.setActiveTools([
|
|
123
|
+
...runtime.pi.getActiveTools(),
|
|
124
|
+
"ask_advisor",
|
|
125
|
+
"record_advisor_outcome",
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
if (announce) {
|
|
129
|
+
notify(
|
|
130
|
+
ctx,
|
|
131
|
+
`${ADVISOR_ACTIVATION_EXPLANATION}\n\nAdvisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
132
|
+
"info"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
alwaysOnRef,
|
|
4
|
+
executorRef,
|
|
5
|
+
getPersistedModelRefs,
|
|
6
|
+
setExecutorRef,
|
|
7
|
+
} from "../config/state.js";
|
|
8
|
+
import { loadConfig, saveConfig } from "../config/storage.js";
|
|
9
|
+
import { herdrAdvisorActivity } from "../herdr.js";
|
|
10
|
+
import { notify } from "./runtime.js";
|
|
11
|
+
import type { CommandRuntime } from "./types.js";
|
|
12
|
+
|
|
13
|
+
export type ActivateAdvisor = (
|
|
14
|
+
args: string,
|
|
15
|
+
ctx: ExtensionContext,
|
|
16
|
+
announce?: boolean
|
|
17
|
+
) => Promise<void>;
|
|
18
|
+
|
|
19
|
+
export const registerCommandLifecycle = (
|
|
20
|
+
runtime: CommandRuntime,
|
|
21
|
+
activateAdvisor: ActivateAdvisor
|
|
22
|
+
) => {
|
|
23
|
+
runtime.pi.on("session_start", async (_event, ctx) => {
|
|
24
|
+
runtime.pendingExecutorModelRef = undefined;
|
|
25
|
+
// A malformed advisor.json or a provider auth failure must not reject a
|
|
26
|
+
// lifecycle handler and break session startup.
|
|
27
|
+
try {
|
|
28
|
+
loadConfig(ctx);
|
|
29
|
+
if (alwaysOnRef) {
|
|
30
|
+
await activateAdvisor("", ctx, false);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
notify(ctx, `Advisor activation failed: ${message}`, "error");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
runtime.pi.on("model_select", (event, ctx) => {
|
|
39
|
+
// "restore" replays a stored session model and "cycle" changes the active
|
|
40
|
+
// model without an explicit `/model` choice. Neither should redefine the
|
|
41
|
+
// configured Executor.
|
|
42
|
+
if (event.source !== "set" || runtime.suppressModelSelectionSync) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const selected = `${event.model.provider}/${event.model.id}`;
|
|
46
|
+
if (!runtime.flowEnabled()) {
|
|
47
|
+
// Defer persistence until `/advisor` succeeds. This keeps ordinary model
|
|
48
|
+
// selection global defaults untouched when the flow is not enabled.
|
|
49
|
+
runtime.pendingExecutorModelRef = selected;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
runtime.pendingExecutorModelRef = undefined;
|
|
53
|
+
if (selected === executorRef) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const persisted = getPersistedModelRefs();
|
|
57
|
+
setExecutorRef(selected);
|
|
58
|
+
saveConfig(ctx, {
|
|
59
|
+
persistAdvisor: Boolean(persisted.advisor),
|
|
60
|
+
persistExecutor: true,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
runtime.pi.on("session_shutdown", (_event, ctx) => {
|
|
65
|
+
if (ctx.hasUI) {
|
|
66
|
+
ctx.ui.setStatus("advisor-usage", undefined);
|
|
67
|
+
}
|
|
68
|
+
for (const [controller, token] of runtime.manualConsultations) {
|
|
69
|
+
controller.abort();
|
|
70
|
+
const timer = runtime.manualProgressTimers.get(controller);
|
|
71
|
+
if (timer) {
|
|
72
|
+
clearInterval(timer);
|
|
73
|
+
runtime.manualProgressTimers.delete(controller);
|
|
74
|
+
}
|
|
75
|
+
runtime.scoutStatus.release(ctx, token);
|
|
76
|
+
}
|
|
77
|
+
runtime.scoutStatus.clear(ctx);
|
|
78
|
+
runtime.manualConsultations.clear();
|
|
79
|
+
runtime.manualProgressTimers.clear();
|
|
80
|
+
runtime.manualProgress.clear();
|
|
81
|
+
herdrAdvisorActivity.clear();
|
|
82
|
+
});
|
|
83
|
+
};
|