pi-advisor-flow 0.2.2 → 0.2.4
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 +116 -67
- package/package.json +1 -1
- package/src/commands.ts +111 -30
- package/src/config.ts +122 -4
- package/src/conversation.ts +102 -19
- package/src/tools.ts +71 -15
- package/src/ui.ts +374 -80
package/README.md
CHANGED
|
@@ -1,84 +1,109 @@
|
|
|
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
|
|
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
|
+
|
|
6
|
+
A configurable second-opinion workflow for <a href="https://github.com/earendil-works/pi">Pi</a> coding agents.
|
|
7
|
+
|
|
5
8
|
</div>
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Keep the work to the executor model, while the advisor steers it.
|
|
8
11
|
|
|
9
12
|
This extension introduces a strategic "Executor/Advisor" workflow, inspired by Claudes [Advisor](https://code.claude.com/docs/en/advisor).
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
[Read more about it here](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need).
|
|
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 provides a concise review, answers questions and can provide help; it does not take over planning or run tools.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
[Read more about Advisors here](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need).
|
|
16
17
|
|
|
17
|
-
Install
|
|
18
|
+
## Install
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
Install into your Pi agent environment:
|
|
20
21
|
|
|
21
22
|
```bash
|
|
23
|
+
# npm
|
|
22
24
|
pi install npm:pi-advisor-flow
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### From Git
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
# GitHub
|
|
28
27
|
pi install git:github.com/philipbrembeck/pi-advisor.git
|
|
29
|
-
```
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```bash
|
|
29
|
+
# local checkout, useful during development
|
|
34
30
|
pi install /path/to/pi-advisor
|
|
35
31
|
```
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
Restart or reload Pi after installation, then run `/advisor` in a session.
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
## Start using it
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
1. Run `/advisor` to enable the flow and register `ask_advisor`.
|
|
38
|
+
2. Run `/advisor-models` to choose the Executor and Advisor models.
|
|
39
|
+
3. Run `/advisor-settings` to set Simple mode, persistent activation, context, gates, privacy controls, and output limits.
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
Enable with models in one command when preferred:
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
-
|
|
43
|
+
```text
|
|
44
|
+
/advisor executor=anthropic/claude-sonnet-5 advisor=openai/gpt-5.6-sol
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`/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.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
## Commands
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
| Command | Purpose |
|
|
52
|
+
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `/advisor` | Enable the flow, select the configured Executor, and register `ask_advisor`. Accepts `executor=`, `advisor=`, and `contextMaxChars=` overrides. |
|
|
54
|
+
| `/advisor-manual [focus]` | Start a parallel Advisor consultation without interrupting the current Executor turn. |
|
|
55
|
+
| `/advisor-models` | Choose Executor and Advisor models plus their reasoning effort. |
|
|
56
|
+
| `/advisor-settings` | Open all Advisor settings in one keyboard-navigable screen. |
|
|
57
|
+
| `/advisor-off` | Disable the flow and remove `ask_advisor` from the active session. |
|
|
51
58
|
|
|
52
|
-
###
|
|
59
|
+
### `ask_advisor`
|
|
53
60
|
|
|
54
|
-
|
|
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.
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
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.
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
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`.
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
## Automatic loop gate
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
2. Advisor Model & Reasoning Effort
|
|
69
|
+
The optional loop gate detects consecutive calls with the same normalized tool signature. By default, it consults the Advisor after three repeats.
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
Unlike ordinary consultations, a loop-gate reply must start with exactly one decision header:
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
```text
|
|
74
|
+
Decision: proceed
|
|
75
|
+
Decision: revise
|
|
76
|
+
Decision: blocked
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Decision | Effect |
|
|
80
|
+
| --------- | --------------------------------------------------- |
|
|
81
|
+
| `proceed` | Reset the repeat counter and allow the tool action. |
|
|
82
|
+
| `revise` | Block the repeated tool action. |
|
|
83
|
+
| `blocked` | Apply the configured gate-failure policy. |
|
|
68
84
|
|
|
69
|
-
|
|
85
|
+
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.
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
| Failure mode | Effect |
|
|
88
|
+
| ------------------------- | ----------------------------------- |
|
|
89
|
+
| `block-session` (default) | Block the session. |
|
|
90
|
+
| `block-tool` | Block only the current tool action. |
|
|
91
|
+
| `warn-and-continue` | Show a warning and continue. |
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
| Condition | `block-session` | `block-tool` | `warn-and-continue` |
|
|
94
|
+
| -------------------------------------------------------- | --------------- | ----------------- | ------------------- |
|
|
95
|
+
| Advisor unavailable or timed out | Block session | Block tool action | Warn and continue |
|
|
96
|
+
| Missing, malformed, duplicate, or contradictory decision | Block session | Block tool action | Warn and continue |
|
|
97
|
+
| Shared budget exhausted | Block session | Block tool action | Warn and continue |
|
|
98
|
+
| `Decision: blocked` | Block session | Block tool action | Warn and continue |
|
|
74
99
|
|
|
75
|
-
|
|
100
|
+
`advisorBlockOnBlocked` controls whether a session block immediately aborts the active run. It never turns a session block into a tool-only block.
|
|
76
101
|
|
|
77
|
-
|
|
102
|
+
## Settings and configuration
|
|
78
103
|
|
|
79
|
-
|
|
104
|
+
`/advisor-models` and `/advisor-settings` save to `advisor.json` in the Pi agent directory. If a trusted project already has its own configuration, Pi uses that file instead.
|
|
80
105
|
|
|
81
|
-
|
|
106
|
+
All fields are optional. This example shows the available settings and their normal defaults:
|
|
82
107
|
|
|
83
108
|
```json
|
|
84
109
|
{
|
|
@@ -87,60 +112,84 @@ The selected configuration is saved as `advisor.json` in the Pi agent directory
|
|
|
87
112
|
"executorEffort": "medium",
|
|
88
113
|
"advisorEffort": "xhigh",
|
|
89
114
|
"contextMaxChars": 25000,
|
|
115
|
+
|
|
90
116
|
"advisorPlanGate": true,
|
|
91
117
|
"advisorFailureGate": true,
|
|
92
118
|
"advisorCompletionGate": true,
|
|
93
|
-
"advisorCollapseResponses": false,
|
|
94
119
|
"advisorCustomInvocation": "before changing a production deployment",
|
|
95
|
-
"
|
|
120
|
+
"advisorCollapseResponses": false,
|
|
121
|
+
|
|
96
122
|
"advisorAutoLoopGate": true,
|
|
97
123
|
"advisorLoopThreshold": 3,
|
|
98
124
|
"advisorMaxCallsPerSession": 5,
|
|
99
|
-
"
|
|
125
|
+
"advisorBlockOnBlocked": true,
|
|
100
126
|
"gateFailureMode": "block-session",
|
|
127
|
+
|
|
128
|
+
"advisorSessionSummary": false,
|
|
129
|
+
"simpleMode": false,
|
|
130
|
+
"alwaysOn": false,
|
|
101
131
|
"advisorHerdrIntegration": true,
|
|
102
132
|
"advisorToolResultMaxLines": 2000,
|
|
103
|
-
"advisorToolResultMaxBytes": 51200
|
|
133
|
+
"advisorToolResultMaxBytes": 51200,
|
|
134
|
+
|
|
135
|
+
"advisorRedactSecrets": false,
|
|
136
|
+
"advisorToolPolicies": {
|
|
137
|
+
"bash": "summary",
|
|
138
|
+
"deploy": "exclude"
|
|
139
|
+
}
|
|
104
140
|
}
|
|
105
141
|
```
|
|
106
142
|
|
|
107
|
-
|
|
143
|
+
### Simple mode and persistent activation
|
|
108
144
|
|
|
109
|
-
|
|
145
|
+
- `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.
|
|
146
|
+
- `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.
|
|
147
|
+
- 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.
|
|
110
148
|
|
|
111
|
-
###
|
|
149
|
+
### Context and limits
|
|
112
150
|
|
|
113
|
-
|
|
151
|
+
- `contextMaxChars` defaults to `15000`. It preserves complete semantic entries and adds an omission marker rather than splitting a message.
|
|
152
|
+
- Set `contextMaxChars` to `0` to omit reconstructed history. `9007199254740991` is the persisted value for `ALL`.
|
|
153
|
+
- Tool results default to Pi's `2000` lines and `50 KiB` limits. Oversized results preserve their beginning and end with an omission marker.
|
|
154
|
+
- `advisorLoopThreshold` is an integer of at least `2`; its default is `3`.
|
|
155
|
+
- Omit `advisorMaxCallsPerSession` for an unlimited shared budget. Otherwise it must be a non-negative safe integer.
|
|
114
156
|
|
|
115
|
-
|
|
157
|
+
### Privacy controls
|
|
116
158
|
|
|
117
|
-
|
|
159
|
+
Advisor context can contain user messages, tool calls, and tool results. Configure disclosure deliberately:
|
|
118
160
|
|
|
119
|
-
- `
|
|
120
|
-
-
|
|
161
|
+
- `advisorRedactSecrets` defaults to `false`. When enabled, pi-advisor locally redacts common credential patterns before including context in an Advisor request.
|
|
162
|
+
- `advisorToolPolicies` matches an **exact tool name**. Each tool may use `full`, `summary`, or `exclude`.
|
|
163
|
+
- `full` includes the call arguments and capped result output.
|
|
164
|
+
- `summary` omits call arguments and result output but includes result status and size metadata.
|
|
165
|
+
- `exclude` omits both call details and output.
|
|
166
|
+
- Tools not listed in `advisorToolPolicies`, including custom and newly added tools, use `full` for backward compatibility.
|
|
121
167
|
|
|
122
|
-
|
|
168
|
+
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.
|
|
123
169
|
|
|
124
|
-
|
|
170
|
+
### Session summary and Herdr
|
|
125
171
|
|
|
126
|
-
|
|
172
|
+
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.
|
|
127
173
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
```
|
|
174
|
+
It distinguishes regular Markdown advice from gate decisions and records the trigger, model, usage/cost when available, failures, budget, and execution effect.
|
|
175
|
+
|
|
176
|
+
[Herdr](https://github.com/ogulcancelik/herdr) integration is enabled by default. It reports Advisor activity and blocked state through Herdr's metadata paths; disable it with `advisorHerdrIntegration`.
|
|
132
177
|
|
|
133
|
-
|
|
178
|
+
## Development
|
|
134
179
|
|
|
135
180
|
```bash
|
|
181
|
+
git clone git@github.com:philipbrembeck/pi-advisor.git
|
|
182
|
+
cd pi-advisor
|
|
136
183
|
bun install
|
|
137
|
-
```
|
|
138
184
|
|
|
139
|
-
|
|
185
|
+
bun test
|
|
186
|
+
bun run typecheck
|
|
187
|
+
bun run lint
|
|
188
|
+
```
|
|
140
189
|
|
|
141
|
-
|
|
190
|
+
## Links
|
|
142
191
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
192
|
+
- [MIT LICENSE](LICENSE)
|
|
193
|
+
- [Changelog](CHANGELOG.md)
|
|
194
|
+
- [npm package](https://www.npmjs.com/package/pi-advisor-flow)
|
|
195
|
+
- [Why use an Advisor flow?](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need)
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -8,9 +8,11 @@ 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,
|
|
@@ -26,13 +28,17 @@ import {
|
|
|
26
28
|
setAdvisorLoopThresholdRef,
|
|
27
29
|
setAdvisorMaxCallsPerSessionRef,
|
|
28
30
|
setAdvisorPlanGateRef,
|
|
31
|
+
setAdvisorRedactSecretsRef,
|
|
29
32
|
setAdvisorRef,
|
|
30
33
|
setAdvisorSessionSummaryRef,
|
|
34
|
+
setAdvisorToolPoliciesRef,
|
|
31
35
|
setAdvisorToolResultMaxBytesRef,
|
|
32
36
|
setAdvisorToolResultMaxLinesRef,
|
|
37
|
+
setAlwaysOnRef,
|
|
33
38
|
setContextMaxCharsRef,
|
|
34
39
|
setExecutorEffortRef,
|
|
35
40
|
setExecutorRef,
|
|
41
|
+
setSimpleModeRef,
|
|
36
42
|
splitRef,
|
|
37
43
|
} from "./config.js";
|
|
38
44
|
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "./herdr.js";
|
|
@@ -40,7 +46,9 @@ import {
|
|
|
40
46
|
adviceForDisplay,
|
|
41
47
|
advisorSessionState,
|
|
42
48
|
consultAdvisor,
|
|
49
|
+
hasSoundVerdict,
|
|
43
50
|
renderAdvisorCallBox,
|
|
51
|
+
renderAdvisorResponseHeader,
|
|
44
52
|
resolveAdvisorRequest,
|
|
45
53
|
} from "./tools.js";
|
|
46
54
|
import {
|
|
@@ -194,25 +202,47 @@ export const registerCommands = (
|
|
|
194
202
|
});
|
|
195
203
|
};
|
|
196
204
|
|
|
197
|
-
|
|
205
|
+
/** Resolves both models and their auth, or reports why activation cannot proceed. */
|
|
206
|
+
const resolveActivationModels = async (ctx: ExtensionContext) => {
|
|
207
|
+
const executor = findConfiguredModel(ctx, executorRef);
|
|
208
|
+
if (!executor) {
|
|
209
|
+
return { error: `Executor model not found: ${executorRef}` };
|
|
210
|
+
}
|
|
211
|
+
const advisor = findConfiguredModel(ctx, advisorRef);
|
|
212
|
+
if (!advisor) {
|
|
213
|
+
return { error: `Advisor model not found: ${advisorRef}` };
|
|
214
|
+
}
|
|
215
|
+
const advisorAuth = await ctx.modelRegistry.getApiKeyAndHeaders(advisor);
|
|
216
|
+
if (!(advisorAuth.ok && advisorAuth.apiKey)) {
|
|
217
|
+
return { error: `No API key for Advisor ${advisorRef}` };
|
|
218
|
+
}
|
|
219
|
+
if (!(await pi.setModel(executor))) {
|
|
220
|
+
return { error: `No API key for Executor ${executorRef}` };
|
|
221
|
+
}
|
|
222
|
+
return {};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const activateAdvisor = async (
|
|
226
|
+
args: string,
|
|
227
|
+
ctx: ExtensionContext,
|
|
228
|
+
announce = true
|
|
229
|
+
) => {
|
|
198
230
|
loadConfig(ctx);
|
|
199
231
|
const argumentError = parseArgs(args);
|
|
200
232
|
if (argumentError) {
|
|
201
233
|
notify(ctx, argumentError, "error");
|
|
202
234
|
return;
|
|
203
235
|
}
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
notify(ctx,
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
if (!findConfiguredModel(ctx, advisorRef)) {
|
|
210
|
-
notify(ctx, `Advisor model not found: ${advisorRef}`, "error");
|
|
236
|
+
const { error } = await resolveActivationModels(ctx);
|
|
237
|
+
if (error) {
|
|
238
|
+
notify(ctx, error, "error");
|
|
211
239
|
return;
|
|
212
240
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
241
|
+
// parseArgs only mutates in-memory refs, and every later loadConfig resets
|
|
242
|
+
// them from disk. Persist supplied arguments once they are known to resolve,
|
|
243
|
+
// so an unusable model reference is never written to the configuration.
|
|
244
|
+
if (args.trim()) {
|
|
245
|
+
saveConfig(ctx);
|
|
216
246
|
}
|
|
217
247
|
if (executorEffortRef) {
|
|
218
248
|
pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
|
|
@@ -220,11 +250,13 @@ export const registerCommands = (
|
|
|
220
250
|
if (!flowEnabled()) {
|
|
221
251
|
pi.setActiveTools([...pi.getActiveTools(), "ask_advisor"]);
|
|
222
252
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
253
|
+
if (announce) {
|
|
254
|
+
notify(
|
|
255
|
+
ctx,
|
|
256
|
+
`Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
257
|
+
"info"
|
|
258
|
+
);
|
|
259
|
+
}
|
|
228
260
|
};
|
|
229
261
|
|
|
230
262
|
pi.registerEntryRenderer?.(
|
|
@@ -242,17 +274,22 @@ export const registerCommands = (
|
|
|
242
274
|
| { advisor?: string; text?: string }
|
|
243
275
|
| undefined;
|
|
244
276
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
245
|
-
box.addChild(
|
|
246
|
-
new Text(theme.fg("warning", theme.bold("◆ ADVISOR RESPONSE")), 0, 0)
|
|
247
|
-
);
|
|
248
|
-
if (details?.advisor) {
|
|
249
|
-
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
250
|
-
}
|
|
251
277
|
const advice =
|
|
252
278
|
details?.text ??
|
|
253
279
|
(typeof message.content === "string"
|
|
254
280
|
? message.content
|
|
255
281
|
: "(Advisor returned no advice.)");
|
|
282
|
+
// Manual consultations must render exactly like an Executor ask_advisor call.
|
|
283
|
+
box.addChild(
|
|
284
|
+
new Text(
|
|
285
|
+
renderAdvisorResponseHeader(hasSoundVerdict(advice), theme),
|
|
286
|
+
0,
|
|
287
|
+
0
|
|
288
|
+
)
|
|
289
|
+
);
|
|
290
|
+
if (details?.advisor) {
|
|
291
|
+
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
292
|
+
}
|
|
256
293
|
box.addChild(
|
|
257
294
|
new Markdown(
|
|
258
295
|
adviceForDisplay(advice, expanded),
|
|
@@ -265,6 +302,34 @@ export const registerCommands = (
|
|
|
265
302
|
}
|
|
266
303
|
);
|
|
267
304
|
|
|
305
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
306
|
+
// A malformed advisor.json or a provider auth failure must not reject a
|
|
307
|
+
// lifecycle handler and break session startup.
|
|
308
|
+
try {
|
|
309
|
+
loadConfig(ctx);
|
|
310
|
+
if (alwaysOnRef) {
|
|
311
|
+
await activateAdvisor("", ctx, false);
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
315
|
+
notify(ctx, `Advisor activation failed: ${message}`, "error");
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
pi.on("model_select", (event, ctx) => {
|
|
320
|
+
// Only an explicit user selection redefines the Executor. "restore" replays a
|
|
321
|
+
// stored session model and would otherwise overwrite saved configuration.
|
|
322
|
+
if (event.source !== "set" || !flowEnabled()) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const selected = `${event.model.provider}/${event.model.id}`;
|
|
326
|
+
if (selected === executorRef) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
setExecutorRef(selected);
|
|
330
|
+
saveConfig(ctx);
|
|
331
|
+
});
|
|
332
|
+
|
|
268
333
|
pi.on("session_shutdown", () => {
|
|
269
334
|
for (const controller of manualConsultations) {
|
|
270
335
|
controller.abort();
|
|
@@ -278,13 +343,20 @@ export const registerCommands = (
|
|
|
278
343
|
"Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
|
|
279
344
|
handler: (args, ctx) => {
|
|
280
345
|
loadConfig(ctx);
|
|
281
|
-
if (
|
|
346
|
+
if (
|
|
347
|
+
!(
|
|
348
|
+
isSimpleMode() ||
|
|
349
|
+
advisorSessionState.canConsult(advisorMaxCallsPerSessionRef)
|
|
350
|
+
)
|
|
351
|
+
) {
|
|
282
352
|
const message = "Advisor call budget exhausted for this session.";
|
|
283
353
|
notify(ctx, message, "warning");
|
|
284
354
|
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
285
355
|
return Promise.resolve();
|
|
286
356
|
}
|
|
287
|
-
|
|
357
|
+
if (!isSimpleMode()) {
|
|
358
|
+
advisorSessionState.consumeCall();
|
|
359
|
+
}
|
|
288
360
|
const question = resolveAdvisorRequest(args);
|
|
289
361
|
// A single visible progress surface avoids competing consultations overwriting
|
|
290
362
|
// each other's streamed state. A newer manual request replaces the previous one.
|
|
@@ -425,11 +497,15 @@ export const registerCommands = (
|
|
|
425
497
|
setAdvisorAutoLoopGateRef(settings.autoLoopGate ?? true);
|
|
426
498
|
setAdvisorLoopThresholdRef(settings.loopThreshold ?? 3);
|
|
427
499
|
setAdvisorMaxCallsPerSessionRef(settings.maxCallsPerSession);
|
|
428
|
-
setAdvisorSessionSummaryRef(settings.sessionSummary ??
|
|
500
|
+
setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
|
|
501
|
+
setSimpleModeRef(settings.simpleMode ?? false);
|
|
502
|
+
setAlwaysOnRef(settings.alwaysOn ?? false);
|
|
429
503
|
setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
|
|
430
504
|
setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
|
|
431
505
|
setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
|
|
432
506
|
setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
|
|
507
|
+
setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
|
|
508
|
+
setAdvisorToolPoliciesRef(settings.toolPolicies ?? {});
|
|
433
509
|
const path = saveConfig(ctx);
|
|
434
510
|
ctx.ui.notify(`Saved Advisor settings to ${path}`, "info");
|
|
435
511
|
},
|
|
@@ -441,12 +517,17 @@ export const registerCommands = (
|
|
|
441
517
|
pi.setActiveTools(
|
|
442
518
|
pi.getActiveTools().filter((name) => name !== "ask_advisor")
|
|
443
519
|
);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
);
|
|
520
|
+
// Leaving alwaysOn set would silently reactivate the flow next session.
|
|
521
|
+
const wasAlwaysOn = alwaysOnRef;
|
|
522
|
+
if (wasAlwaysOn) {
|
|
523
|
+
setAlwaysOnRef(false);
|
|
524
|
+
saveConfig(ctx);
|
|
449
525
|
}
|
|
526
|
+
notify(
|
|
527
|
+
ctx,
|
|
528
|
+
`Advisor flow disabled. Current model unchanged.${wasAlwaysOn ? " Always on turned off." : ""}`,
|
|
529
|
+
"info"
|
|
530
|
+
);
|
|
450
531
|
return Promise.resolve();
|
|
451
532
|
},
|
|
452
533
|
});
|