pi-advisor-flow 0.2.3 → 0.2.6
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 +45 -11
- package/package.json +33 -30
- package/src/commands.ts +141 -34
- package/src/config.ts +167 -8
- package/src/conversation.ts +28 -3
- package/src/git.ts +190 -0
- package/src/outcomes.ts +85 -0
- package/src/preferences.ts +50 -0
- package/src/session-state.ts +36 -1
- package/src/tools.ts +340 -30
- package/src/ui.ts +410 -114
- package/src/untracked.ts +98 -0
package/README.md
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# pi-advisor
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
|
-
<img src="https://raw.githubusercontent.com/philipbrembeck/pi-advisor/refs/heads/main/assets/screenshot.png" alt="Pi Advisor consultation in the terminal" width="760">
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+

|
|
7
6
|
|
|
8
|
-
</
|
|
7
|
+
A configurable second-opinion workflow for <a href="https://github.com/earendil-works/pi">Pi</a> coding agents, inspired by the ["Steering Black-Box LLMs with Advisor Models" paper](https://arxiv.org/abs/2510.02453) and Claude's [Advisor](https://code.claude.com/docs/en/advisor) feature.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
</div>
|
|
11
10
|
|
|
12
|
-
This extension introduces a strategic "Executor/Advisor" workflow
|
|
11
|
+
This extension introduces a strategic "Executor/Advisor" workflow.
|
|
13
12
|
|
|
14
13
|
`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 provides a concise review, answers questions and can provide help; it does not take over planning or run tools.
|
|
15
14
|
|
|
16
|
-
[Read more about Advisors here](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need).
|
|
15
|
+
The concept is simple, keep implementation on a fast model, borrow frontier reasoning only when decisions actually matter. [Read more about Advisors here](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need).
|
|
17
16
|
|
|
18
17
|
## Install
|
|
19
18
|
|
|
@@ -36,7 +35,7 @@ Restart or reload Pi after installation, then run `/advisor` in a session.
|
|
|
36
35
|
|
|
37
36
|
1. Run `/advisor` to enable the flow and register `ask_advisor`.
|
|
38
37
|
2. Run `/advisor-models` to choose the Executor and Advisor models.
|
|
39
|
-
3. Run `/advisor-settings` to set context, gates, privacy controls, and output limits.
|
|
38
|
+
3. Run `/advisor-settings` to set Simple mode, persistent activation, context, gates, privacy controls, and output limits.
|
|
40
39
|
|
|
41
40
|
Enable with models in one command when preferred:
|
|
42
41
|
|
|
@@ -58,11 +57,13 @@ Enable with models in one command when preferred:
|
|
|
58
57
|
|
|
59
58
|
### `ask_advisor`
|
|
60
59
|
|
|
61
|
-
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.
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
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.
|
|
62
63
|
|
|
63
64
|
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.
|
|
64
65
|
|
|
65
|
-
Normal consultations preserve the provider's final Markdown.
|
|
66
|
+
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`.
|
|
66
67
|
|
|
67
68
|
## Automatic loop gate
|
|
68
69
|
|
|
@@ -125,12 +126,18 @@ All fields are optional. This example shows the available settings and their nor
|
|
|
125
126
|
"advisorBlockOnBlocked": true,
|
|
126
127
|
"gateFailureMode": "block-session",
|
|
127
128
|
|
|
128
|
-
"advisorSessionSummary":
|
|
129
|
+
"advisorSessionSummary": false,
|
|
130
|
+
"advisorGitContext": "summary",
|
|
131
|
+
"advisorGitContextMaxChars": 20000,
|
|
132
|
+
"simpleMode": false,
|
|
133
|
+
"alwaysOn": false,
|
|
129
134
|
"advisorHerdrIntegration": true,
|
|
130
135
|
"advisorToolResultMaxLines": 2000,
|
|
131
136
|
"advisorToolResultMaxBytes": 51200,
|
|
132
137
|
|
|
133
138
|
"advisorRedactSecrets": false,
|
|
139
|
+
"advisorUntrackedContent": false,
|
|
140
|
+
"advisorOutcomeLogging": false,
|
|
134
141
|
"advisorToolPolicies": {
|
|
135
142
|
"bash": "summary",
|
|
136
143
|
"deploy": "exclude"
|
|
@@ -138,6 +145,33 @@ All fields are optional. This example shows the available settings and their nor
|
|
|
138
145
|
}
|
|
139
146
|
```
|
|
140
147
|
|
|
148
|
+
### Simple mode and persistent activation
|
|
149
|
+
|
|
150
|
+
- `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.
|
|
151
|
+
- `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.
|
|
152
|
+
- 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.
|
|
153
|
+
|
|
154
|
+
### Repository context
|
|
155
|
+
|
|
156
|
+
- `advisorGitContext` defaults to `summary`. It controls how much of the working tree reaches the Advisor:
|
|
157
|
+
- `off` sends no repository information.
|
|
158
|
+
- `summary` sends changed file names, change status, and line counts. It never sends file contents.
|
|
159
|
+
- `full` additionally sends the patch.
|
|
160
|
+
- 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`.
|
|
161
|
+
- `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.
|
|
162
|
+
- `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.
|
|
163
|
+
- 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.
|
|
164
|
+
- `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.
|
|
165
|
+
- 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.
|
|
166
|
+
- File names themselves can be sensitive. `summary` withholds file contents, not file names; use `off` when names must not leave the machine.
|
|
167
|
+
- Collection shares a single overall time budget across its git commands and degrades to a stated failure rather than implying a clean tree.
|
|
168
|
+
|
|
169
|
+
### Project preferences and outcomes
|
|
170
|
+
|
|
171
|
+
In a trusted project only, `.pi/advisor-preferences.md` may provide a short local brief. It is never written by pi-advisor, is treated as lower-priority untrusted text, and is redacted/capped before egress. Symlinks, unreadable files, and paths outside the project are ignored.
|
|
172
|
+
|
|
173
|
+
`advisorOutcomeLogging` defaults to false and is global-only: a project config cannot enable it. When enabled, `~/.pi/agent/advisor-outcomes.jsonl` stores bounded rotating JSONL records with only a version, timestamp, salted truncated advice digest, trigger, adoption, and validation status. It stores no prompt, advice, paths, tool output, repository data, session ID, or advice ID.
|
|
174
|
+
|
|
141
175
|
### Context and limits
|
|
142
176
|
|
|
143
177
|
- `contextMaxChars` defaults to `15000`. It preserves complete semantic entries and adds an omission marker rather than splitting a message.
|
|
@@ -161,7 +195,7 @@ Redaction and output limits reduce accidental disclosure; they are not a data-cl
|
|
|
161
195
|
|
|
162
196
|
### Session summary and Herdr
|
|
163
197
|
|
|
164
|
-
The optional Session Advisor Summary is local and in-memory only
|
|
198
|
+
The optional Session Advisor Summary defaults to off. When enabled, it is local and in-memory only, appears after a non-blocked settled run, and is never persisted.
|
|
165
199
|
|
|
166
200
|
It distinguishes regular Markdown advice from gate decisions and records the trigger, model, usage/cost when available, failures, budget, and execution effect.
|
|
167
201
|
|
package/package.json
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"name": "pi-advisor-flow",
|
|
3
|
+
"version": "0.2.6",
|
|
4
|
+
"author": "Philip Brembeck",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/philipbrembeck/pi-advisor.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "extensions/index.ts",
|
|
2
10
|
"devDependencies": {
|
|
3
11
|
"@biomejs/biome": "2.5.3",
|
|
4
12
|
"@earendil-works/pi-ai": "^0.80.7",
|
|
@@ -12,37 +20,19 @@
|
|
|
12
20
|
"typescript": "^5.3.3",
|
|
13
21
|
"ultracite": "7.9.4"
|
|
14
22
|
},
|
|
15
|
-
"name": "pi-advisor-flow",
|
|
16
23
|
"peerDependencies": {
|
|
17
24
|
"@earendil-works/pi-ai": "^0.80.7",
|
|
18
25
|
"@earendil-works/pi-coding-agent": "^0.80.7",
|
|
19
26
|
"@earendil-works/pi-tui": "^0.80.7",
|
|
20
27
|
"typebox": "^1.1.38"
|
|
21
28
|
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"test": "bun test",
|
|
24
|
-
"typecheck": "tsc --noEmit",
|
|
25
|
-
"format": "bunx ultracite fix --linter-enabled=false",
|
|
26
|
-
"lint": "bunx ultracite check",
|
|
27
|
-
"lint:fix": "bunx ultracite fix",
|
|
28
|
-
"package:check": "npm pack --dry-run --json >/dev/null",
|
|
29
|
-
"prepare": "husky"
|
|
30
|
-
},
|
|
31
|
-
"lint-staged": {
|
|
32
|
-
"*.{json,jsonc,ts}": "bun run lint:fix --"
|
|
33
|
-
},
|
|
34
|
-
"type": "module",
|
|
35
|
-
"version": "0.2.3",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"repository": {
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "https://github.com/philipbrembeck/pi-advisor.git"
|
|
40
|
-
},
|
|
41
|
-
"homepage": "https://github.com/philipbrembeck/pi-advisor",
|
|
42
|
-
"author": "Philip Brembeck",
|
|
43
29
|
"description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
|
|
44
|
-
"
|
|
45
|
-
|
|
30
|
+
"files": [
|
|
31
|
+
"extensions",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"homepage": "https://github.com/philipbrembeck/pi-advisor",
|
|
46
36
|
"keywords": [
|
|
47
37
|
"pi-package",
|
|
48
38
|
"pi-extension",
|
|
@@ -51,15 +41,28 @@
|
|
|
51
41
|
"pi-advisor",
|
|
52
42
|
"herdr"
|
|
53
43
|
],
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"lint-staged": {
|
|
46
|
+
"*.{json,jsonc,ts}": "bun run lint:fix --"
|
|
47
|
+
},
|
|
48
|
+
"overrides": {
|
|
49
|
+
"brace-expansion": "5.0.8"
|
|
50
|
+
},
|
|
59
51
|
"pi": {
|
|
60
52
|
"extensions": [
|
|
61
53
|
"./extensions/index.ts"
|
|
62
54
|
],
|
|
63
55
|
"image": "https://raw.githubusercontent.com/philipbrembeck/pi-advisor/refs/heads/main/assets/hero.png"
|
|
64
|
-
}
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"test": "bun test",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"format": "bunx ultracite fix --linter-enabled=false",
|
|
61
|
+
"lint": "bunx ultracite check",
|
|
62
|
+
"lint:fix": "bunx ultracite fix",
|
|
63
|
+
"package:check": "npm pack --dry-run --json >/dev/null",
|
|
64
|
+
"prepare": "husky"
|
|
65
|
+
},
|
|
66
|
+
"type": "module",
|
|
67
|
+
"types": "extensions/index.ts"
|
|
65
68
|
}
|
package/src/commands.ts
CHANGED
|
@@ -8,12 +8,15 @@ import {
|
|
|
8
8
|
advisorEffortRef,
|
|
9
9
|
advisorMaxCallsPerSessionRef,
|
|
10
10
|
advisorRef,
|
|
11
|
+
alwaysOnRef,
|
|
11
12
|
executorEffortRef,
|
|
12
13
|
executorRef,
|
|
13
14
|
getAdvisorSettings,
|
|
15
|
+
isSimpleMode,
|
|
14
16
|
loadConfig,
|
|
15
17
|
parseArgs,
|
|
16
18
|
saveConfig,
|
|
19
|
+
saveGlobalOutcomeLogging,
|
|
17
20
|
setAdvisorAutoLoopGateRef,
|
|
18
21
|
setAdvisorBlockOnBlockedRef,
|
|
19
22
|
setAdvisorCollapseResponsesRef,
|
|
@@ -22,9 +25,12 @@ import {
|
|
|
22
25
|
setAdvisorEffortRef,
|
|
23
26
|
setAdvisorFailureGateRef,
|
|
24
27
|
setAdvisorFailureModeRef,
|
|
28
|
+
setAdvisorGitContextMaxCharsRef,
|
|
29
|
+
setAdvisorGitContextRef,
|
|
25
30
|
setAdvisorHerdrIntegrationRef,
|
|
26
31
|
setAdvisorLoopThresholdRef,
|
|
27
32
|
setAdvisorMaxCallsPerSessionRef,
|
|
33
|
+
setAdvisorOutcomeLoggingRef,
|
|
28
34
|
setAdvisorPlanGateRef,
|
|
29
35
|
setAdvisorRedactSecretsRef,
|
|
30
36
|
setAdvisorRef,
|
|
@@ -32,9 +38,12 @@ import {
|
|
|
32
38
|
setAdvisorToolPoliciesRef,
|
|
33
39
|
setAdvisorToolResultMaxBytesRef,
|
|
34
40
|
setAdvisorToolResultMaxLinesRef,
|
|
41
|
+
setAdvisorUntrackedContentRef,
|
|
42
|
+
setAlwaysOnRef,
|
|
35
43
|
setContextMaxCharsRef,
|
|
36
44
|
setExecutorEffortRef,
|
|
37
45
|
setExecutorRef,
|
|
46
|
+
setSimpleModeRef,
|
|
38
47
|
splitRef,
|
|
39
48
|
} from "./config.js";
|
|
40
49
|
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "./herdr.js";
|
|
@@ -42,7 +51,9 @@ import {
|
|
|
42
51
|
adviceForDisplay,
|
|
43
52
|
advisorSessionState,
|
|
44
53
|
consultAdvisor,
|
|
54
|
+
hasSoundVerdict,
|
|
45
55
|
renderAdvisorCallBox,
|
|
56
|
+
renderAdvisorResponseHeader,
|
|
46
57
|
resolveAdvisorRequest,
|
|
47
58
|
} from "./tools.js";
|
|
48
59
|
import {
|
|
@@ -102,7 +113,12 @@ type ManualConsult = (
|
|
|
102
113
|
ctx: ExtensionContext,
|
|
103
114
|
question?: string,
|
|
104
115
|
signal?: AbortSignal
|
|
105
|
-
) => Promise<{
|
|
116
|
+
) => Promise<{
|
|
117
|
+
markdown: string;
|
|
118
|
+
thinkingText: string;
|
|
119
|
+
draftBytes?: number;
|
|
120
|
+
preferenceBytes?: number;
|
|
121
|
+
}>;
|
|
106
122
|
type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0];
|
|
107
123
|
|
|
108
124
|
const notify = (
|
|
@@ -196,37 +212,65 @@ export const registerCommands = (
|
|
|
196
212
|
});
|
|
197
213
|
};
|
|
198
214
|
|
|
199
|
-
|
|
215
|
+
/** Resolves both models and their auth, or reports why activation cannot proceed. */
|
|
216
|
+
const resolveActivationModels = async (ctx: ExtensionContext) => {
|
|
217
|
+
const executor = findConfiguredModel(ctx, executorRef);
|
|
218
|
+
if (!executor) {
|
|
219
|
+
return { error: `Executor model not found: ${executorRef}` };
|
|
220
|
+
}
|
|
221
|
+
const advisor = findConfiguredModel(ctx, advisorRef);
|
|
222
|
+
if (!advisor) {
|
|
223
|
+
return { error: `Advisor model not found: ${advisorRef}` };
|
|
224
|
+
}
|
|
225
|
+
const advisorAuth = await ctx.modelRegistry.getApiKeyAndHeaders(advisor);
|
|
226
|
+
if (!(advisorAuth.ok && advisorAuth.apiKey)) {
|
|
227
|
+
return { error: `No API key for Advisor ${advisorRef}` };
|
|
228
|
+
}
|
|
229
|
+
if (!(await pi.setModel(executor))) {
|
|
230
|
+
return { error: `No API key for Executor ${executorRef}` };
|
|
231
|
+
}
|
|
232
|
+
return {};
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const activateAdvisor = async (
|
|
236
|
+
args: string,
|
|
237
|
+
ctx: ExtensionContext,
|
|
238
|
+
announce = true
|
|
239
|
+
) => {
|
|
200
240
|
loadConfig(ctx);
|
|
201
241
|
const argumentError = parseArgs(args);
|
|
202
242
|
if (argumentError) {
|
|
203
243
|
notify(ctx, argumentError, "error");
|
|
204
244
|
return;
|
|
205
245
|
}
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
208
|
-
notify(ctx,
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (!findConfiguredModel(ctx, advisorRef)) {
|
|
212
|
-
notify(ctx, `Advisor model not found: ${advisorRef}`, "error");
|
|
246
|
+
const { error } = await resolveActivationModels(ctx);
|
|
247
|
+
if (error) {
|
|
248
|
+
notify(ctx, error, "error");
|
|
213
249
|
return;
|
|
214
250
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
251
|
+
// parseArgs only mutates in-memory refs, and every later loadConfig resets
|
|
252
|
+
// them from disk. Persist supplied arguments once they are known to resolve,
|
|
253
|
+
// so an unusable model reference is never written to the configuration.
|
|
254
|
+
if (args.trim()) {
|
|
255
|
+
saveConfig(ctx);
|
|
218
256
|
}
|
|
219
257
|
if (executorEffortRef) {
|
|
220
258
|
pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
|
|
221
259
|
}
|
|
222
260
|
if (!flowEnabled()) {
|
|
223
|
-
pi.setActiveTools([
|
|
261
|
+
pi.setActiveTools([
|
|
262
|
+
...pi.getActiveTools(),
|
|
263
|
+
"ask_advisor",
|
|
264
|
+
"record_advisor_outcome",
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
267
|
+
if (announce) {
|
|
268
|
+
notify(
|
|
269
|
+
ctx,
|
|
270
|
+
`Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
271
|
+
"info"
|
|
272
|
+
);
|
|
224
273
|
}
|
|
225
|
-
notify(
|
|
226
|
-
ctx,
|
|
227
|
-
`Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
228
|
-
"info"
|
|
229
|
-
);
|
|
230
274
|
};
|
|
231
275
|
|
|
232
276
|
pi.registerEntryRenderer?.(
|
|
@@ -244,17 +288,22 @@ export const registerCommands = (
|
|
|
244
288
|
| { advisor?: string; text?: string }
|
|
245
289
|
| undefined;
|
|
246
290
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
247
|
-
box.addChild(
|
|
248
|
-
new Text(theme.fg("warning", theme.bold("◆ ADVISOR RESPONSE")), 0, 0)
|
|
249
|
-
);
|
|
250
|
-
if (details?.advisor) {
|
|
251
|
-
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
252
|
-
}
|
|
253
291
|
const advice =
|
|
254
292
|
details?.text ??
|
|
255
293
|
(typeof message.content === "string"
|
|
256
294
|
? message.content
|
|
257
295
|
: "(Advisor returned no advice.)");
|
|
296
|
+
// Manual consultations must render exactly like an Executor ask_advisor call.
|
|
297
|
+
box.addChild(
|
|
298
|
+
new Text(
|
|
299
|
+
renderAdvisorResponseHeader(hasSoundVerdict(advice), theme),
|
|
300
|
+
0,
|
|
301
|
+
0
|
|
302
|
+
)
|
|
303
|
+
);
|
|
304
|
+
if (details?.advisor) {
|
|
305
|
+
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
306
|
+
}
|
|
258
307
|
box.addChild(
|
|
259
308
|
new Markdown(
|
|
260
309
|
adviceForDisplay(advice, expanded),
|
|
@@ -267,6 +316,34 @@ export const registerCommands = (
|
|
|
267
316
|
}
|
|
268
317
|
);
|
|
269
318
|
|
|
319
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
320
|
+
// A malformed advisor.json or a provider auth failure must not reject a
|
|
321
|
+
// lifecycle handler and break session startup.
|
|
322
|
+
try {
|
|
323
|
+
loadConfig(ctx);
|
|
324
|
+
if (alwaysOnRef) {
|
|
325
|
+
await activateAdvisor("", ctx, false);
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
329
|
+
notify(ctx, `Advisor activation failed: ${message}`, "error");
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
pi.on("model_select", (event, ctx) => {
|
|
334
|
+
// Only an explicit user selection redefines the Executor. "restore" replays a
|
|
335
|
+
// stored session model and would otherwise overwrite saved configuration.
|
|
336
|
+
if (event.source !== "set" || !flowEnabled()) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const selected = `${event.model.provider}/${event.model.id}`;
|
|
340
|
+
if (selected === executorRef) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
setExecutorRef(selected);
|
|
344
|
+
saveConfig(ctx);
|
|
345
|
+
});
|
|
346
|
+
|
|
270
347
|
pi.on("session_shutdown", () => {
|
|
271
348
|
for (const controller of manualConsultations) {
|
|
272
349
|
controller.abort();
|
|
@@ -280,13 +357,20 @@ export const registerCommands = (
|
|
|
280
357
|
"Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
|
|
281
358
|
handler: (args, ctx) => {
|
|
282
359
|
loadConfig(ctx);
|
|
283
|
-
if (
|
|
360
|
+
if (
|
|
361
|
+
!(
|
|
362
|
+
isSimpleMode() ||
|
|
363
|
+
advisorSessionState.canConsult(advisorMaxCallsPerSessionRef)
|
|
364
|
+
)
|
|
365
|
+
) {
|
|
284
366
|
const message = "Advisor call budget exhausted for this session.";
|
|
285
367
|
notify(ctx, message, "warning");
|
|
286
368
|
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
287
369
|
return Promise.resolve();
|
|
288
370
|
}
|
|
289
|
-
|
|
371
|
+
if (!isSimpleMode()) {
|
|
372
|
+
advisorSessionState.consumeCall();
|
|
373
|
+
}
|
|
290
374
|
const question = resolveAdvisorRequest(args);
|
|
291
375
|
// A single visible progress surface avoids competing consultations overwriting
|
|
292
376
|
// each other's streamed state. A newer manual request replaces the previous one.
|
|
@@ -389,6 +473,7 @@ export const registerCommands = (
|
|
|
389
473
|
|
|
390
474
|
pi.registerCommand("advisor-settings", {
|
|
391
475
|
description: "Configure Advisor context and reasoning effort",
|
|
476
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one settings form maps every persisted control.
|
|
392
477
|
handler: async (_args, ctx) => {
|
|
393
478
|
loadConfig(ctx);
|
|
394
479
|
if (!ctx.hasUI) {
|
|
@@ -427,15 +512,27 @@ export const registerCommands = (
|
|
|
427
512
|
setAdvisorAutoLoopGateRef(settings.autoLoopGate ?? true);
|
|
428
513
|
setAdvisorLoopThresholdRef(settings.loopThreshold ?? 3);
|
|
429
514
|
setAdvisorMaxCallsPerSessionRef(settings.maxCallsPerSession);
|
|
430
|
-
setAdvisorSessionSummaryRef(settings.sessionSummary ??
|
|
515
|
+
setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
|
|
516
|
+
setSimpleModeRef(settings.simpleMode ?? false);
|
|
517
|
+
setAlwaysOnRef(settings.alwaysOn ?? false);
|
|
431
518
|
setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
|
|
432
519
|
setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
|
|
433
520
|
setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
|
|
434
521
|
setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
|
|
435
522
|
setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
|
|
523
|
+
setAdvisorGitContextRef(settings.gitContext ?? "summary");
|
|
524
|
+
setAdvisorGitContextMaxCharsRef(settings.gitContextMaxChars ?? 20_000);
|
|
436
525
|
setAdvisorToolPoliciesRef(settings.toolPolicies ?? {});
|
|
526
|
+
setAdvisorUntrackedContentRef(settings.untrackedContent ?? false);
|
|
527
|
+
setAdvisorOutcomeLoggingRef(settings.outcomeLogging ?? false);
|
|
437
528
|
const path = saveConfig(ctx);
|
|
438
|
-
|
|
529
|
+
const globalPath = saveGlobalOutcomeLogging(
|
|
530
|
+
settings.outcomeLogging ?? false
|
|
531
|
+
);
|
|
532
|
+
ctx.ui.notify(
|
|
533
|
+
`Saved Advisor settings to ${path}; outcome logging globally to ${globalPath}`,
|
|
534
|
+
"info"
|
|
535
|
+
);
|
|
439
536
|
},
|
|
440
537
|
});
|
|
441
538
|
|
|
@@ -443,14 +540,24 @@ export const registerCommands = (
|
|
|
443
540
|
description: "Disable on-demand Advisor calls; keep the current model",
|
|
444
541
|
handler: (_args, ctx) => {
|
|
445
542
|
pi.setActiveTools(
|
|
446
|
-
pi
|
|
543
|
+
pi
|
|
544
|
+
.getActiveTools()
|
|
545
|
+
.filter(
|
|
546
|
+
(name) =>
|
|
547
|
+
name !== "ask_advisor" && name !== "record_advisor_outcome"
|
|
548
|
+
)
|
|
447
549
|
);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
);
|
|
550
|
+
// Leaving alwaysOn set would silently reactivate the flow next session.
|
|
551
|
+
const wasAlwaysOn = alwaysOnRef;
|
|
552
|
+
if (wasAlwaysOn) {
|
|
553
|
+
setAlwaysOnRef(false);
|
|
554
|
+
saveConfig(ctx);
|
|
453
555
|
}
|
|
556
|
+
notify(
|
|
557
|
+
ctx,
|
|
558
|
+
`Advisor flow disabled. Current model unchanged.${wasAlwaysOn ? " Always on turned off." : ""}`,
|
|
559
|
+
"info"
|
|
560
|
+
);
|
|
454
561
|
return Promise.resolve();
|
|
455
562
|
},
|
|
456
563
|
});
|