pi-advisor-flow 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +43 -170
- package/extensions/index.ts +4 -2
- package/package.json +3 -2
- package/src/commands.ts +49 -11
- package/src/config.ts +32 -9
- package/src/conversation.ts +25 -12
- package/src/git.ts +5 -1
- package/src/outcomes.ts +93 -23
- package/src/session-state.ts +22 -0
- package/src/tools.ts +91 -22
- package/src/ui.ts +62 -29
- package/src/untracked.ts +138 -50
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ 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.3.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added discretionary, sequential Advisor file handoff: when globally enabled, the Executor can attach explicitly named tracked working-tree files with `includeTrackedFiles` after the Advisor says it cannot review one. Tracked and untracked attachments remain separate consent paths and share bounded, redacted disclosure limits.
|
|
12
|
+
|
|
13
|
+
## 0.2.9
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Preserved custom numeric values when stepping `/advisor-settings` controls instead of resetting off-preset budgets and disclosure caps.
|
|
18
|
+
- Kept recorded session blocks enforced after `/advisor-off`, restored configuration after failed activation, and charged Advisor-call budgets only when consultation execution begins.
|
|
19
|
+
- Kept conversation and repository disclosure within their configured caps and explicitly identified disabled repository context as withheld.
|
|
20
|
+
- Made concurrent outcome records append safely with one exclusively initialized digest salt, and stopped superseded manual consultations from entering session summaries.
|
|
21
|
+
- Preserved and ignored forward-compatible `advisor.json` fields with a warning, and made malformed configuration errors actionable in every Advisor command.
|
|
22
|
+
- Isolated call budgets, repeated-action counters, and safety blocks between concurrent same-process sessions.
|
|
23
|
+
- Kept the repository-context withheld warning visible when its disclosure budget is zero.
|
|
24
|
+
|
|
7
25
|
## 0.2.8
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -8,17 +8,22 @@ A configurable second-opinion workflow for <a href="https://github.com/earendil-
|
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
`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.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The idea is simple: 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).
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Features
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- **On-demand second opinions** through the `ask_advisor` tool or `/advisor-manual`.
|
|
18
|
+
- **Configurable review gates** before plans, after repeated failures, and before declaring completion.
|
|
19
|
+
- **Automatic loop detection** for repeated tool calls, with explicit proceed, revise, or blocked decisions.
|
|
20
|
+
- **Separate model and reasoning controls** for the Executor and Advisor.
|
|
21
|
+
- **Privacy controls** for conversation history, repository context, explicit tracked/untracked file handoff, tool results, secret redaction, and outcome logging.
|
|
22
|
+
- **Optional persistent activation, Simple mode, session summaries, and Herdr integration.**
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
## Install
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
Current release: **0.3.0**. Requires Pi 0.80.7 or later. The extension installs no dependencies of its own; Pi supplies its runtime modules.
|
|
22
27
|
|
|
23
28
|
```bash
|
|
24
29
|
# npm
|
|
@@ -27,197 +32,65 @@ pi install npm:pi-advisor-flow
|
|
|
27
32
|
# GitHub
|
|
28
33
|
pi install git:github.com/philipbrembeck/pi-advisor.git
|
|
29
34
|
|
|
30
|
-
# local checkout
|
|
35
|
+
# local checkout
|
|
31
36
|
pi install /path/to/pi-advisor
|
|
32
37
|
```
|
|
33
38
|
|
|
34
|
-
Restart or reload Pi after installation
|
|
39
|
+
Restart or reload Pi after installation.
|
|
35
40
|
|
|
36
|
-
##
|
|
41
|
+
## Quick start
|
|
37
42
|
|
|
38
43
|
1. Run `/advisor` to enable the flow and register `ask_advisor`.
|
|
39
44
|
2. Run `/advisor-models` to choose the Executor and Advisor models.
|
|
40
|
-
3. Run `/advisor-settings` to
|
|
41
|
-
|
|
42
|
-
Enable with models in one command when preferred:
|
|
43
|
-
|
|
44
|
-
```text
|
|
45
|
-
/advisor executor=anthropic/claude-sonnet-5 advisor=openai/gpt-5.6-sol
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
`/advisor contextMaxChars=30000` sets the reconstructed-context limit for the current session. Use `0` for no history. The `ALL` option in settings represents the complete current branch and is still subject to the Advisor model's context limit.
|
|
49
|
-
|
|
50
|
-
## Commands
|
|
51
|
-
|
|
52
|
-
| Command | Purpose |
|
|
53
|
-
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
-
| `/advisor` | Enable the flow, select the configured Executor, and register `ask_advisor`. Accepts `executor=`, `advisor=`, and `contextMaxChars=` overrides. |
|
|
55
|
-
| `/advisor-manual [focus]` | Start a parallel Advisor consultation without interrupting the current Executor turn. |
|
|
56
|
-
| `/advisor-models` | Choose Executor and Advisor models plus their reasoning effort. |
|
|
57
|
-
| `/advisor-settings` | Open all Advisor settings in one keyboard-navigable screen. |
|
|
58
|
-
| `/advisor-off` | Disable the flow and remove `ask_advisor` from the active session. |
|
|
59
|
-
|
|
60
|
-
### `ask_advisor`
|
|
61
|
-
|
|
62
|
-
The Executor calls `ask_advisor({})` for a general review of the current task and reconstructed conversation. It can pass a `question` for a targeted review, or a concise `draft` for plan and completion reviews. A draft should name proposed work, validation, and remaining risks; it is an unverified claim, not evidence.
|
|
63
|
-
|
|
64
|
-
Successful calls return an opaque `adviceId`. When global outcome logging is enabled, the Executor may voluntarily call `record_advisor_outcome` once with that ID, an adoption value, and a final validation status.
|
|
65
|
-
|
|
66
|
-
Use the Advisor after the Executor has investigated and formed a candidate direction. It is intended to challenge assumptions, expose risks, and confirm the next verification step—not to replace the Executor's work.
|
|
67
|
-
|
|
68
|
-
Normal consultations preserve the provider's final Markdown and never block execution. When the Advisor has no material concern or recommendation, it may begin with the exact first line `Verdict: sound`; Pi renders that response with the static `◆ ADVISOR · SOUND` header, for both `ask_advisor` results and `/advisor-manual`.
|
|
45
|
+
3. Run `/advisor-settings` to configure review gates, context, privacy, and limits.
|
|
69
46
|
|
|
70
|
-
|
|
47
|
+
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.
|
|
71
48
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Unlike ordinary consultations, a loop-gate reply must start with exactly one decision header:
|
|
49
|
+
You can also enable the flow and select both models at once:
|
|
75
50
|
|
|
76
51
|
```text
|
|
77
|
-
|
|
78
|
-
Decision: revise
|
|
79
|
-
Decision: blocked
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
| Decision | Effect |
|
|
83
|
-
| --------- | --------------------------------------------------- |
|
|
84
|
-
| `proceed` | Reset the repeat counter and allow the tool action. |
|
|
85
|
-
| `revise` | Block the repeated tool action. |
|
|
86
|
-
| `blocked` | Apply the configured gate-failure policy. |
|
|
87
|
-
|
|
88
|
-
Malformed, missing, duplicate, or contradictory decisions are gate failures. The same policy also applies when the Advisor is unavailable or the shared call budget is exhausted.
|
|
89
|
-
|
|
90
|
-
| Failure mode | Effect |
|
|
91
|
-
| ------------------------- | ----------------------------------- |
|
|
92
|
-
| `block-session` (default) | Block the session. |
|
|
93
|
-
| `block-tool` | Block only the current tool action. |
|
|
94
|
-
| `warn-and-continue` | Show a warning and continue. |
|
|
95
|
-
|
|
96
|
-
| Condition | `block-session` | `block-tool` | `warn-and-continue` |
|
|
97
|
-
| -------------------------------------------------------- | --------------- | ----------------- | ------------------- |
|
|
98
|
-
| Advisor unavailable or timed out | Block session | Block tool action | Warn and continue |
|
|
99
|
-
| Missing, malformed, duplicate, or contradictory decision | Block session | Block tool action | Warn and continue |
|
|
100
|
-
| Shared budget exhausted | Block session | Block tool action | Warn and continue |
|
|
101
|
-
| `Decision: blocked` | Block session | Block tool action | Warn and continue |
|
|
102
|
-
|
|
103
|
-
`advisorBlockOnBlocked` controls whether a session block immediately aborts the active run. It never turns a session block into a tool-only block.
|
|
104
|
-
|
|
105
|
-
## Settings and configuration
|
|
106
|
-
|
|
107
|
-
`/advisor-models` and `/advisor-settings` save to global `advisor.json` in the Pi agent directory. Repository-controlled project `advisor.json` files are not applied; models, prompts, gates, budgets, disclosure, redaction, integrations, and consent remain under the user's global configuration.
|
|
108
|
-
|
|
109
|
-
All fields are optional. This example shows the available settings and their normal defaults:
|
|
110
|
-
|
|
111
|
-
```json
|
|
112
|
-
{
|
|
113
|
-
"executor": "openai/gpt-5.6-luna",
|
|
114
|
-
"advisor": "anthropic/claude-fable-5",
|
|
115
|
-
"executorEffort": "medium",
|
|
116
|
-
"advisorEffort": "xhigh",
|
|
117
|
-
"contextMaxChars": 25000,
|
|
118
|
-
|
|
119
|
-
"advisorPlanGate": true,
|
|
120
|
-
"advisorFailureGate": true,
|
|
121
|
-
"advisorCompletionGate": true,
|
|
122
|
-
"advisorCustomInvocation": "before changing a production deployment",
|
|
123
|
-
"advisorCollapseResponses": false,
|
|
124
|
-
|
|
125
|
-
"advisorAutoLoopGate": true,
|
|
126
|
-
"advisorLoopThreshold": 3,
|
|
127
|
-
"advisorMaxCallsPerSession": 5,
|
|
128
|
-
"advisorBlockOnBlocked": true,
|
|
129
|
-
"gateFailureMode": "block-session",
|
|
130
|
-
|
|
131
|
-
"advisorSessionSummary": false,
|
|
132
|
-
"advisorGitContext": "summary",
|
|
133
|
-
"advisorGitContextMaxChars": 20000,
|
|
134
|
-
"simpleMode": false,
|
|
135
|
-
"alwaysOn": false,
|
|
136
|
-
"advisorHerdrIntegration": true,
|
|
137
|
-
"advisorToolResultMaxLines": 2000,
|
|
138
|
-
"advisorToolResultMaxBytes": 51200,
|
|
139
|
-
|
|
140
|
-
"advisorRedactSecrets": false,
|
|
141
|
-
"advisorUntrackedContent": false,
|
|
142
|
-
"advisorOutcomeLogging": false,
|
|
143
|
-
"advisorToolPolicies": {
|
|
144
|
-
"bash": "summary",
|
|
145
|
-
"deploy": "exclude"
|
|
146
|
-
}
|
|
147
|
-
}
|
|
52
|
+
/advisor executor=anthropic/claude-sonnet-5 advisor=openai/gpt-5.6-sol
|
|
148
53
|
```
|
|
149
54
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
- `simpleMode` defaults to `false`. When enabled, `ask_advisor` and `/advisor-manual` remain available for voluntary second opinions, while plan/failure/completion rules, loop gates, blocking, call budgets, and session summaries are disabled. Context limits, result caps, redaction, and tool disclosure policies still apply.
|
|
153
|
-
- `alwaysOn` defaults to `false`. When enabled, Pi restores the configured Executor and activates `ask_advisor` for new, resumed, forked, and reloaded sessions. While the Advisor flow is active, an explicit `/model` selection becomes the persisted Executor for the next activation; a model restored with a session does not change the saved Executor. `/advisor-off` turns `alwaysOn` off so the flow stays disabled in later sessions.
|
|
154
|
-
- In Simple mode, settings keeps the Context window/history slider alongside Simple mode and Always on; advanced values remain saved and take effect when Simple mode is disabled.
|
|
155
|
-
|
|
156
|
-
### Repository context
|
|
157
|
-
|
|
158
|
-
- `advisorGitContext` defaults to `summary`. It controls how much of the working tree reaches the Advisor:
|
|
159
|
-
- `off` sends no repository information.
|
|
160
|
-
- `summary` sends changed file names, change status, and line counts. It never sends file contents.
|
|
161
|
-
- `full` additionally sends the patch.
|
|
162
|
-
- Changes are measured against the last commit and cover staged and unstaged work. Untracked files are always listed by name only; their contents are never sent by `gitContext: full`.
|
|
163
|
-
- `advisorUntrackedContent` defaults to false. When enabled, `includeUntracked` can attach only exact named, repository-relative, untracked regular files. Files are redacted and capped before egress; sibling files remain withheld.
|
|
164
|
-
- `advisorGitContextMaxChars` defaults to `20000`. Repository context may claim its own cap or half of `contextMaxChars`, whichever is smaller, so it cannot crowd out the conversation.
|
|
165
|
-
- The Executor may pass `gitContext` to `ask_advisor` as `none`, `summary`, or `full`. `advisorGitContext` is the ceiling: a larger request is narrowed to the configured level and the Advisor is told that a fuller view was withheld, so it does not claim verification it could not perform.
|
|
166
|
-
- `summary` deliberately excludes diff hunk headers. Git derives those from surrounding file content, so a hunk header can reproduce a line the change never touched, including a credential.
|
|
167
|
-
- Redaction runs before the region is capped, and repository content is labelled as untrusted data in the request. Paths and patch text are escaped so a crafted path cannot close the region early and have the remainder read as instructions.
|
|
168
|
-
- File names themselves can be sensitive. `summary` withholds file contents, not file names; use `off` when names must not leave the machine.
|
|
169
|
-
- Collection shares a single overall time budget across its git commands and degrades to a stated failure rather than implying a clean tree.
|
|
170
|
-
|
|
171
|
-
### Project preferences and outcomes
|
|
55
|
+
## How it works
|
|
172
56
|
|
|
173
|
-
|
|
57
|
+
1. The Executor investigates the task and forms its own candidate direction.
|
|
58
|
+
2. For a consequential decision, stalled attempt, or final review, it calls `ask_advisor` with the reconstructed conversation and allowed repository context.
|
|
59
|
+
3. The Advisor returns a concise review. It may challenge assumptions, identify risks, or recommend the next verification step.
|
|
60
|
+
4. The Executor decides what to adopt, performs the work, and validates the result.
|
|
174
61
|
|
|
175
|
-
|
|
62
|
+
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.
|
|
176
63
|
|
|
177
|
-
|
|
64
|
+
Successful calls return an opaque `adviceId`. If global outcome logging is enabled, the Executor can call `record_advisor_outcome` once to record whether the advice was adopted and whether final validation passed.
|
|
178
65
|
|
|
179
|
-
|
|
180
|
-
- Set `contextMaxChars` to `0` to omit reconstructed history. `9007199254740991` is the persisted value for `ALL`.
|
|
181
|
-
- Tool results default to Pi's `2000` lines and `50 KiB` limits. Oversized results preserve their beginning and end with an omission marker.
|
|
182
|
-
- `advisorLoopThreshold` is an integer of at least `2`; its default is `3`.
|
|
183
|
-
- Omit `advisorMaxCallsPerSession` for an unlimited shared budget. Otherwise it must be a non-negative safe integer.
|
|
184
|
-
|
|
185
|
-
### Privacy controls
|
|
186
|
-
|
|
187
|
-
Advisor context can contain user messages, tool calls, and tool results. Configure disclosure deliberately:
|
|
188
|
-
|
|
189
|
-
- `advisorRedactSecrets` defaults to `false`. When enabled, pi-advisor locally redacts common credential patterns before including context in an Advisor request.
|
|
190
|
-
- `advisorToolPolicies` matches an **exact tool name**. Each tool may use `full`, `summary`, or `exclude`.
|
|
191
|
-
- `full` includes the call arguments and capped result output.
|
|
192
|
-
- `summary` omits call arguments and result output but includes result status and size metadata.
|
|
193
|
-
- `exclude` omits both call details and output.
|
|
194
|
-
- Tools not listed in `advisorToolPolicies`, including custom and newly added tools, use `full` for backward compatibility.
|
|
195
|
-
|
|
196
|
-
Redaction and output limits reduce accidental disclosure; they are not a data-classification system and cannot guarantee every secret is found. Use tool policies for content that must not be sent to the Advisor.
|
|
66
|
+
## Commands
|
|
197
67
|
|
|
198
|
-
|
|
68
|
+
| Command | Purpose |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `/advisor` | Enable the flow and optionally override the Executor, Advisor, or context limit. |
|
|
71
|
+
| `/advisor-manual [focus]` | Start a parallel consultation without interrupting the current Executor turn. |
|
|
72
|
+
| `/advisor-models` | Choose both models and their reasoning effort. |
|
|
73
|
+
| `/advisor-settings` | Configure behavior, context, gates, privacy, and output limits. |
|
|
74
|
+
| `/advisor-off` | Disable the flow and turn off persistent activation. |
|
|
199
75
|
|
|
200
|
-
The
|
|
76
|
+
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.
|
|
201
77
|
|
|
202
|
-
|
|
78
|
+
## What gets sent to the Advisor
|
|
203
79
|
|
|
204
|
-
|
|
80
|
+
Advisor context can include user messages, tool calls, tool results, and repository information. Secret redaction is off by default, and tools without an explicit disclosure policy default to full context. Review the privacy settings before using the extension with sensitive work.
|
|
205
81
|
|
|
206
|
-
|
|
82
|
+
Repository context is configurable from no access through changed-file summaries to a capped patch. When context is disabled or its budget is zero, the Advisor is told it was withheld rather than shown an apparently clean tree. Explicit tracked and untracked file contents require separate global opt-ins; attachments are capped, redacted when configured, and sent as untrusted data.
|
|
207
83
|
|
|
208
|
-
|
|
209
|
-
git clone git@github.com:philipbrembeck/pi-advisor.git
|
|
210
|
-
cd pi-advisor
|
|
211
|
-
bun install
|
|
84
|
+
## Documentation
|
|
212
85
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
86
|
+
- [Configuration and automatic loop gates](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/configuration.md)
|
|
87
|
+
- [Privacy and data handling](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/privacy.md)
|
|
88
|
+
- [Development](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/development.md)
|
|
89
|
+
- [Documentation index](https://github.com/philipbrembeck/pi-advisor/blob/main/docs/README.md)
|
|
217
90
|
|
|
218
91
|
## Links
|
|
219
92
|
|
|
220
|
-
- [MIT
|
|
93
|
+
- [MIT License](LICENSE)
|
|
221
94
|
- [Changelog](CHANGELOG.md)
|
|
222
95
|
- [npm package](https://www.npmjs.com/package/pi-advisor-flow)
|
|
223
96
|
- [Why use an Advisor flow?](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need)
|
package/extensions/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { registerCommands } from "../src/commands.js";
|
|
3
3
|
import { setHerdrBlockedEmitter } from "../src/herdr.js";
|
|
4
|
+
import { AdvisorSessionState } from "../src/session-state.js";
|
|
4
5
|
import {
|
|
5
6
|
consultAdvisor as consultAdvisorImplementation,
|
|
6
7
|
parseAutomaticDecision as parseAutomaticDecisionImplementation,
|
|
@@ -29,9 +30,10 @@ export const runAdvisorGate = (
|
|
|
29
30
|
) => runAdvisorGateImplementation(...args);
|
|
30
31
|
|
|
31
32
|
export default function (pi: ExtensionAPI) {
|
|
33
|
+
const sessionState = new AdvisorSessionState();
|
|
32
34
|
setHerdrBlockedEmitter((active, label) =>
|
|
33
35
|
pi.events.emit("herdr:blocked", { active, label })
|
|
34
36
|
);
|
|
35
|
-
registerAdvisorTool(pi);
|
|
36
|
-
registerCommands(pi);
|
|
37
|
+
registerAdvisorTool(pi, sessionState);
|
|
38
|
+
registerCommands(pi, { sessionState });
|
|
37
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-advisor-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"author": "Philip Brembeck",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
"*.{json,jsonc,ts}": "bun run lint:fix --"
|
|
63
63
|
},
|
|
64
64
|
"overrides": {
|
|
65
|
-
"brace-expansion": "5.0.
|
|
65
|
+
"brace-expansion": "5.0.9",
|
|
66
|
+
"undici": "8.9.0"
|
|
66
67
|
},
|
|
67
68
|
"pi": {
|
|
68
69
|
"extensions": [
|
package/src/commands.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
advisorMaxCallsPerSessionRef,
|
|
10
10
|
advisorRef,
|
|
11
11
|
alwaysOnRef,
|
|
12
|
+
contextMaxCharsRef,
|
|
12
13
|
executorEffortRef,
|
|
13
14
|
executorRef,
|
|
14
15
|
getAdvisorSettings,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
setAdvisorToolPoliciesRef,
|
|
39
40
|
setAdvisorToolResultMaxBytesRef,
|
|
40
41
|
setAdvisorToolResultMaxLinesRef,
|
|
42
|
+
setAdvisorTrackedFileContentRef,
|
|
41
43
|
setAdvisorUntrackedContentRef,
|
|
42
44
|
setAlwaysOnRef,
|
|
43
45
|
setContextMaxCharsRef,
|
|
@@ -47,10 +49,11 @@ import {
|
|
|
47
49
|
splitRef,
|
|
48
50
|
} from "./config.js";
|
|
49
51
|
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "./herdr.js";
|
|
52
|
+
import type { AdvisorSessionState } from "./session-state.js";
|
|
50
53
|
import {
|
|
51
54
|
adviceForDisplay,
|
|
52
|
-
advisorSessionState,
|
|
53
55
|
consultAdvisor,
|
|
56
|
+
advisorSessionState as defaultAdvisorSessionState,
|
|
54
57
|
hasSoundVerdict,
|
|
55
58
|
renderAdvisorCallBox,
|
|
56
59
|
renderAdvisorResponseHeader,
|
|
@@ -138,8 +141,13 @@ const findConfiguredModel = (ctx: ExtensionContext, ref: string) => {
|
|
|
138
141
|
|
|
139
142
|
export const registerCommands = (
|
|
140
143
|
pi: ExtensionAPI,
|
|
141
|
-
dependencies: {
|
|
144
|
+
dependencies: {
|
|
145
|
+
consult?: ManualConsult;
|
|
146
|
+
sessionState?: AdvisorSessionState;
|
|
147
|
+
} = {}
|
|
142
148
|
) => {
|
|
149
|
+
const advisorSessionState =
|
|
150
|
+
dependencies.sessionState ?? defaultAdvisorSessionState;
|
|
143
151
|
const flowEnabled = () => pi.getActiveTools().includes("ask_advisor");
|
|
144
152
|
const requestAdvisor =
|
|
145
153
|
dependencies.consult ??
|
|
@@ -155,15 +163,15 @@ export const registerCommands = (
|
|
|
155
163
|
herdrAdvisorActivity.start();
|
|
156
164
|
return requestAdvisor(ctx, question, controller.signal)
|
|
157
165
|
.then(({ markdown }) => {
|
|
166
|
+
if (controller.signal.aborted) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
158
169
|
advisorSessionState.recordInvocation({
|
|
159
170
|
executionEffect: "continued",
|
|
160
171
|
kind: "markdown",
|
|
161
172
|
model: advisorRef,
|
|
162
173
|
trigger: "manual",
|
|
163
174
|
});
|
|
164
|
-
if (controller.signal.aborted) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
175
|
pi.sendMessage(
|
|
168
176
|
{
|
|
169
177
|
content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${markdown}`,
|
|
@@ -232,19 +240,48 @@ export const registerCommands = (
|
|
|
232
240
|
return {};
|
|
233
241
|
};
|
|
234
242
|
|
|
243
|
+
const loadCommandConfig = (ctx: ExtensionContext) => {
|
|
244
|
+
try {
|
|
245
|
+
loadConfig(ctx);
|
|
246
|
+
return true;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
249
|
+
notify(
|
|
250
|
+
ctx,
|
|
251
|
+
`Advisor command could not load configuration: ${message} Fix advisor.json and retry.`,
|
|
252
|
+
"error"
|
|
253
|
+
);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
235
258
|
const activateAdvisor = async (
|
|
236
259
|
args: string,
|
|
237
260
|
ctx: ExtensionContext,
|
|
238
261
|
announce = true
|
|
239
262
|
) => {
|
|
240
|
-
|
|
263
|
+
if (!loadCommandConfig(ctx)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const previous = {
|
|
267
|
+
advisor: advisorRef,
|
|
268
|
+
contextMaxChars: contextMaxCharsRef,
|
|
269
|
+
executor: executorRef,
|
|
270
|
+
};
|
|
271
|
+
const restoreRefs = () => {
|
|
272
|
+
setAdvisorRef(previous.advisor);
|
|
273
|
+
setContextMaxCharsRef(previous.contextMaxChars);
|
|
274
|
+
setExecutorRef(previous.executor);
|
|
275
|
+
};
|
|
241
276
|
const argumentError = parseArgs(args);
|
|
242
277
|
if (argumentError) {
|
|
278
|
+
restoreRefs();
|
|
243
279
|
notify(ctx, argumentError, "error");
|
|
244
280
|
return;
|
|
245
281
|
}
|
|
246
282
|
const { error } = await resolveActivationModels(ctx);
|
|
247
283
|
if (error) {
|
|
284
|
+
restoreRefs();
|
|
248
285
|
notify(ctx, error, "error");
|
|
249
286
|
return;
|
|
250
287
|
}
|
|
@@ -356,7 +393,9 @@ export const registerCommands = (
|
|
|
356
393
|
description:
|
|
357
394
|
"Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
|
|
358
395
|
handler: (args, ctx) => {
|
|
359
|
-
|
|
396
|
+
if (!loadCommandConfig(ctx)) {
|
|
397
|
+
return Promise.resolve();
|
|
398
|
+
}
|
|
360
399
|
if (
|
|
361
400
|
!(
|
|
362
401
|
isSimpleMode() ||
|
|
@@ -396,8 +435,7 @@ export const registerCommands = (
|
|
|
396
435
|
description:
|
|
397
436
|
"Select and persist the Executor and Advisor models with reasoning levels",
|
|
398
437
|
handler: async (_args, ctx) => {
|
|
399
|
-
|
|
400
|
-
if (!ctx.hasUI) {
|
|
438
|
+
if (!(loadCommandConfig(ctx) && ctx.hasUI)) {
|
|
401
439
|
return;
|
|
402
440
|
}
|
|
403
441
|
const refs = ctx.modelRegistry
|
|
@@ -475,8 +513,7 @@ export const registerCommands = (
|
|
|
475
513
|
description: "Configure Advisor context and reasoning effort",
|
|
476
514
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one settings form maps every persisted control.
|
|
477
515
|
handler: async (_args, ctx) => {
|
|
478
|
-
|
|
479
|
-
if (!ctx.hasUI) {
|
|
516
|
+
if (!(loadCommandConfig(ctx) && ctx.hasUI)) {
|
|
480
517
|
return;
|
|
481
518
|
}
|
|
482
519
|
|
|
@@ -523,6 +560,7 @@ export const registerCommands = (
|
|
|
523
560
|
setAdvisorGitContextRef(settings.gitContext ?? "summary");
|
|
524
561
|
setAdvisorGitContextMaxCharsRef(settings.gitContextMaxChars ?? 20_000);
|
|
525
562
|
setAdvisorToolPoliciesRef(settings.toolPolicies ?? {});
|
|
563
|
+
setAdvisorTrackedFileContentRef(settings.trackedFileContent ?? false);
|
|
526
564
|
setAdvisorUntrackedContentRef(settings.untrackedContent ?? false);
|
|
527
565
|
setAdvisorOutcomeLoggingRef(settings.outcomeLogging ?? false);
|
|
528
566
|
const path = saveConfig(ctx);
|
package/src/config.ts
CHANGED
|
@@ -64,6 +64,7 @@ export let advisorGitContextMaxCharsRef = DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS;
|
|
|
64
64
|
export let advisorToolPoliciesRef: AdvisorToolPolicies = {};
|
|
65
65
|
export let advisorOutcomeLoggingRef = false;
|
|
66
66
|
export let advisorUntrackedContentRef = false;
|
|
67
|
+
export let advisorTrackedFileContentRef = false;
|
|
67
68
|
|
|
68
69
|
export const setExecutorRef = (ref: string) => {
|
|
69
70
|
executorRef = ref;
|
|
@@ -166,6 +167,9 @@ export const setAdvisorOutcomeLoggingRef = (enabled: boolean) => {
|
|
|
166
167
|
export const setAdvisorUntrackedContentRef = (enabled: boolean) => {
|
|
167
168
|
advisorUntrackedContentRef = enabled;
|
|
168
169
|
};
|
|
170
|
+
export const setAdvisorTrackedFileContentRef = (enabled: boolean) => {
|
|
171
|
+
advisorTrackedFileContentRef = enabled;
|
|
172
|
+
};
|
|
169
173
|
|
|
170
174
|
/**
|
|
171
175
|
* Returns the current live settings state. Use this at UI boundaries instead of
|
|
@@ -195,6 +199,7 @@ export const getAdvisorSettings = () => ({
|
|
|
195
199
|
toolPolicies: { ...advisorToolPoliciesRef },
|
|
196
200
|
toolResultMaxBytes: advisorToolResultMaxBytesRef,
|
|
197
201
|
toolResultMaxLines: advisorToolResultMaxLinesRef,
|
|
202
|
+
trackedFileContent: advisorTrackedFileContentRef,
|
|
198
203
|
untrackedContent: advisorUntrackedContentRef,
|
|
199
204
|
});
|
|
200
205
|
|
|
@@ -231,6 +236,7 @@ export interface AdvisorConfig {
|
|
|
231
236
|
advisorToolPolicies?: AdvisorToolPolicies;
|
|
232
237
|
advisorToolResultMaxBytes?: number;
|
|
233
238
|
advisorToolResultMaxLines?: number;
|
|
239
|
+
advisorTrackedFileContent?: boolean;
|
|
234
240
|
advisorUntrackedContent?: boolean;
|
|
235
241
|
alwaysOn?: boolean;
|
|
236
242
|
contextMaxChars?: number;
|
|
@@ -260,6 +266,7 @@ const CONFIG_KEYS = new Set<keyof AdvisorConfig>([
|
|
|
260
266
|
"alwaysOn",
|
|
261
267
|
"advisorToolResultMaxBytes",
|
|
262
268
|
"advisorToolResultMaxLines",
|
|
269
|
+
"advisorTrackedFileContent",
|
|
263
270
|
"advisorRedactSecrets",
|
|
264
271
|
"advisorUntrackedContent",
|
|
265
272
|
"advisorOutcomeLogging",
|
|
@@ -280,6 +287,7 @@ const BOOLEAN_CONFIG_KEYS = [
|
|
|
280
287
|
"simpleMode",
|
|
281
288
|
"alwaysOn",
|
|
282
289
|
"advisorHerdrIntegration",
|
|
290
|
+
"advisorTrackedFileContent",
|
|
283
291
|
"advisorRedactSecrets",
|
|
284
292
|
"advisorUntrackedContent",
|
|
285
293
|
"advisorOutcomeLogging",
|
|
@@ -305,16 +313,10 @@ const invalidConfigValue = (
|
|
|
305
313
|
);
|
|
306
314
|
};
|
|
307
315
|
|
|
308
|
-
const
|
|
309
|
-
|
|
316
|
+
const unknownConfigKeys = (config: ConfigRecord) =>
|
|
317
|
+
Object.keys(config).filter(
|
|
310
318
|
(key) => !CONFIG_KEYS.has(key as keyof AdvisorConfig)
|
|
311
319
|
);
|
|
312
|
-
if (unknownKeys.length > 0) {
|
|
313
|
-
throw new TypeError(
|
|
314
|
-
`Invalid advisor configuration at ${path}: unknown key(s) ${unknownKeys.map((key) => JSON.stringify(key)).join(", ")}. Remove them or upgrade pi-advisor.`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
320
|
|
|
319
321
|
const validateStringValues = (config: ConfigRecord, path: string) => {
|
|
320
322
|
for (const key of STRING_CONFIG_KEYS) {
|
|
@@ -406,7 +408,6 @@ export const validateConfig = (
|
|
|
406
408
|
);
|
|
407
409
|
}
|
|
408
410
|
const config = value as ConfigRecord;
|
|
409
|
-
validateKnownKeys(config, path);
|
|
410
411
|
validateStringValues(config, path);
|
|
411
412
|
validateBooleanValues(config, path);
|
|
412
413
|
validateNumericValues(config, path);
|
|
@@ -467,6 +468,7 @@ const resetDefaults = () => {
|
|
|
467
468
|
advisorToolPoliciesRef = {};
|
|
468
469
|
advisorOutcomeLoggingRef = false;
|
|
469
470
|
advisorUntrackedContentRef = false;
|
|
471
|
+
advisorTrackedFileContentRef = false;
|
|
470
472
|
};
|
|
471
473
|
|
|
472
474
|
const applyOptionalConfig = <Key extends keyof AdvisorConfig>(
|
|
@@ -568,6 +570,11 @@ const applyConfig = (config: AdvisorConfig) => {
|
|
|
568
570
|
"advisorUntrackedContent",
|
|
569
571
|
setAdvisorUntrackedContentRef
|
|
570
572
|
);
|
|
573
|
+
applyOptionalConfig(
|
|
574
|
+
config,
|
|
575
|
+
"advisorTrackedFileContent",
|
|
576
|
+
setAdvisorTrackedFileContentRef
|
|
577
|
+
);
|
|
571
578
|
};
|
|
572
579
|
|
|
573
580
|
const readConfig = (path: string): AdvisorConfig => {
|
|
@@ -587,10 +594,12 @@ const configCache = new Map<
|
|
|
587
594
|
string,
|
|
588
595
|
{ config: AdvisorConfig; identity: string }
|
|
589
596
|
>();
|
|
597
|
+
const warnedUnknownConfigIdentities = new Set<string>();
|
|
590
598
|
|
|
591
599
|
/** Drops the parsed-configuration cache; the next load re-reads from disk. */
|
|
592
600
|
export const resetConfigCache = () => {
|
|
593
601
|
configCache.clear();
|
|
602
|
+
warnedUnknownConfigIdentities.clear();
|
|
594
603
|
};
|
|
595
604
|
|
|
596
605
|
/**
|
|
@@ -626,6 +635,19 @@ export const loadConfig = (_ctx: ExtensionContext) => {
|
|
|
626
635
|
: undefined;
|
|
627
636
|
if (globalConfig) {
|
|
628
637
|
applyConfig(globalConfig);
|
|
638
|
+
const unknownKeys = unknownConfigKeys(globalConfig as ConfigRecord);
|
|
639
|
+
const warningIdentity = `${global}:${configIdentity(global)}`;
|
|
640
|
+
if (
|
|
641
|
+
unknownKeys.length > 0 &&
|
|
642
|
+
_ctx.hasUI &&
|
|
643
|
+
!warnedUnknownConfigIdentities.has(warningIdentity)
|
|
644
|
+
) {
|
|
645
|
+
_ctx.ui.notify(
|
|
646
|
+
`Advisor configuration at ${global} contains unrecognized key(s) ${unknownKeys.map((key) => JSON.stringify(key)).join(", ")}. They were preserved but ignored; check for typos or upgrade pi-advisor.`,
|
|
647
|
+
"warning"
|
|
648
|
+
);
|
|
649
|
+
warnedUnknownConfigIdentities.add(warningIdentity);
|
|
650
|
+
}
|
|
629
651
|
}
|
|
630
652
|
// Repository-controlled project configuration is never applied. Models,
|
|
631
653
|
// prompts, gates, budgets, disclosure, redaction, integrations, and consent
|
|
@@ -675,6 +697,7 @@ export const saveConfig = (_ctx: ExtensionContext) => {
|
|
|
675
697
|
advisorToolPolicies: advisorToolPoliciesRef,
|
|
676
698
|
advisorToolResultMaxBytes: advisorToolResultMaxBytesRef,
|
|
677
699
|
advisorToolResultMaxLines: advisorToolResultMaxLinesRef,
|
|
700
|
+
advisorTrackedFileContent: advisorTrackedFileContentRef,
|
|
678
701
|
advisorUntrackedContent: advisorUntrackedContentRef,
|
|
679
702
|
alwaysOn: alwaysOnRef,
|
|
680
703
|
gateFailureMode: advisorFailureModeRef,
|