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 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 Flow Screenshot" width="600">
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
- An on-demand Advisor model flow for autonomous **Pi** coding agents.
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
- The primary agent (the Executor) acts, writes code, and executes tools. Whenever the Executor encounters high risk, ambiguity, or potential loops, it MUST escalate the scenario to a smarter, second-opinion LLM (the Advisor) for strategic guidance.
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
- ## Installation
16
+ [Read more about Advisors here](https://philipbrembeck.com/writings/2026/07/only-as-much-intelligence-as-you-need).
16
17
 
17
- Install the package directly into your global Pi agent environment:
18
+ ## Install
18
19
 
19
- ### From NPM
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
- ```bash
26
+ # GitHub
28
27
  pi install git:github.com/philipbrembeck/pi-advisor.git
29
- ```
30
28
 
31
- ### From Local Folder (For Development)
32
-
33
- ```bash
29
+ # local checkout, useful during development
34
30
  pi install /path/to/pi-advisor
35
31
  ```
36
32
 
37
- ## Usage & Commands
33
+ Restart or reload Pi after installation, then run `/advisor` in a session.
38
34
 
39
- Once installed, the following commands are available inside the Pi terminal:
35
+ ## Start using it
40
36
 
41
- ### `/advisor`
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
- Enables the Advisor flow. Switches the primary model to the configured Executor model and registers the `ask_advisor` tool.
41
+ Enable with models in one command when preferred:
44
42
 
45
- - _Example:_ `/advisor executor=anthropic/claude-sonnet-5 advisor=openai/gpt-5.6-sol`
46
- - _Context size:_ `/advisor contextMaxChars=30000` uses up to 30,000 characters of the reconstructed conversation for each consultation. `0` disables history; `Number.MAX_SAFE_INTEGER` represents the complete branch. Larger values increase request cost and can exceed the Advisor model's context window.
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
- ### `/advisor-manual [focus]`
49
+ ## Commands
49
50
 
50
- Starts an Advisor consultation in parallel without interrupting the Executor's active tool work. An optional `focus` is passed to the Advisor; when it completes, the advice is delivered to the Executor before its next model call. This works while the Executor is mid-turn.
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
- ### `/advisor-settings`
59
+ ### `ask_advisor`
53
60
 
54
- Opens a single keyboard-navigable settings screen. It includes a Claude Code-style context slider with `0`, `10k`, `25k`, `100k`, `200k`, and `ALL`; `0` sends no reconstructed history and `ALL` sends the complete current branch, subject to the Advisor model's context limit.
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
- It also configures Advisor reasoning effort, whether long Advisor responses collapse to a short preview (`Ctrl+O` expands them), and each built-in invocation gate independently (consequential plans, repeated failures, and completion review). It includes controls for critical-response blocking, the automatic loop gate and its repeat threshold, a per-session Advisor-call limit, and the local Session Advisor Summary. Response collapsing is off by default. You can add one custom natural-language invocation rule. Settings persist in `advisor.json`.
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
- ### `/advisor-models`
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
- Opens an interactive, scrollable fuzzy-search picker in the TUI to choose:
67
+ ## Automatic loop gate
61
68
 
62
- 1. Executor Model & Reasoning Effort
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
- Saves and persists your configuration to `~/.pi/agent/advisor.json`.
71
+ Unlike ordinary consultations, a loop-gate reply must start with exactly one decision header:
66
72
 
67
- ### `ask_advisor`
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
- The Executor can call `ask_advisor` with an empty object for a general review of the current task and conversation, or provide `question` for targeted feedback. The Advisor is a brief second opinion: the Executor investigates and forms its own candidate direction first, then uses the Advisor to challenge assumptions and validate a consequential next step. It should not delegate the entire plan or task.
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
- Normal Advisor consultations return the provider's final Markdown unchanged. They never parse JSON, synthesize a verdict, or block Executor work. Only automatic loop gates use machine-readable decisions: the first non-empty line must be `Decision: proceed`, `Decision: revise`, or `Decision: blocked`; malformed, missing, duplicate, or contradictory decisions are explicit gate failures.
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
- ### Automatic loop gate
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
- When enabled, the loop gate consults the Advisor after the configured number of consecutive calls with the same normalized tool signature (default: three). A `proceed` decision resets the repetition counter and allows the call. `revise` blocks only the repeated tool action; `blocked` can block the session according to the configured policy. Gate failures default to `block-session` and may be changed to `block-tool` or `warn-and-continue`. Normalization is allowlisted: object keys are deterministic, arrays retain order, and only volatile IDs/timestamps, temporary paths, and safe shell whitespace are normalized.
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
- Every manual, Executor-requested, and automatic Advisor invocation consumes one shared session budget. The default is unlimited; a finite budget appears as used/remaining in the Executor instructions and local summary. The optional Session Advisor Summary is local, in-memory only, and appears after a non-blocked settled run; it is never persisted or sent to Herdr. It separates Markdown advice from gate decisions and records trigger, model, usage/cost when available, failure, budget, and execution effect.
102
+ ## Settings and configuration
78
103
 
79
- ### Context configuration
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
- The selected configuration is saved as `advisor.json` in the Pi agent directory (or an existing trusted project configuration). `/advisor-models` and `/advisor-settings` share this file:
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
- "advisorBlockOnBlocked": true,
120
+ "advisorCollapseResponses": false,
121
+
96
122
  "advisorAutoLoopGate": true,
97
123
  "advisorLoopThreshold": 3,
98
124
  "advisorMaxCallsPerSession": 5,
99
- "advisorSessionSummary": true,
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
- All fields are optional. `executor`, `advisor`, and their effort settings are managed by `/advisor-models`. `/advisor-settings` manages `advisorEffort`, `contextMaxChars`, the three invocation-gate booleans, `advisorCollapseResponses`, `advisorCustomInvocation`, `advisorBlockOnBlocked`, `advisorAutoLoopGate`, `advisorLoopThreshold`, `advisorMaxCallsPerSession`, `advisorSessionSummary`, `gateFailureMode`, `advisorHerdrIntegration`, and the tool-result line/byte limits.
143
+ ### Simple mode and persistent activation
108
144
 
109
- `contextMaxChars` is a soft character budget: it preserves complete semantic entries and adds an older-context omission marker rather than starting mid-message. Its default is 15,000, `0` omits history, and `9007199254740991` means ALL. Oversized tool results default to Pi's 2,000-line/50 KiB limits and preserve beginning/end sections with an omission marker; `advisorToolResultMaxLines` and `advisorToolResultMaxBytes` override them. `advisorLoopThreshold` must be at least `2` and defaults to `3`. Omit `advisorMaxCallsPerSession` for an unlimited shared budget; otherwise it must be a non-negative safe integer. `gateFailureMode` accepts `block-session`, `block-tool`, or `warn-and-continue`, defaulting to `block-session`. Herdr integration and the notification/activity/blocked metadata paths default to enabled; failure notifications use sanitized `notification.show` requests and can be disabled with `advisorHerdrIntegration`. Critical blocking, the automatic loop gate, and the Session Advisor Summary default to `true`. Unknown or invalid configuration keys fail at startup with the file, key, accepted values, and remediation; save operations preserve unknown fields.
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
- ### `/advisor-off`
149
+ ### Context and limits
112
150
 
113
- Disables the Advisor flow, removing the `ask_advisor` tool from the active session.
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
- ## Publishing releases
157
+ ### Privacy controls
116
158
 
117
- CI manages `vX.Y.Z` release tags from the version in `package.json`; contributors must not create or push release tags manually. The release workflow verifies the version, type-checks, tests, and then publishes:
159
+ Advisor context can contain user messages, tool calls, and tool results. Configure disclosure deliberately:
118
160
 
119
- - `pi-advisor-flow` to [npm](https://www.npmjs.com/package/pi-advisor-flow)
120
- - `@philipbrembeck/pi-advisor-flow` to GitHub Packages, which makes the package appear in this repository’s **Packages** sidebar
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
- ## Local Development
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
- `pi-advisor` uses Bun for rapid testing and TypeScript. Standard commands apply:
170
+ ### Session summary and Herdr
125
171
 
126
- ### 1. Clone the repository
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
- ```bash
129
- git clone git@github.com:philipbrembeck/pi-advisor.git
130
- cd pi-advisor
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
- ### 2. Install dependencies
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
- ### 3. Run type-checks & tests
185
+ bun test
186
+ bun run typecheck
187
+ bun run lint
188
+ ```
140
189
 
141
- Verify code-splitting correctness and registration logic:
190
+ ## Links
142
191
 
143
- ```bash
144
- bun test # Run unit tests
145
- bun run typecheck # Perform strict TS checks
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
@@ -32,7 +32,7 @@
32
32
  "*.{json,jsonc,ts}": "bun run lint:fix --"
33
33
  },
34
34
  "type": "module",
35
- "version": "0.2.2",
35
+ "version": "0.2.4",
36
36
  "license": "MIT",
37
37
  "repository": {
38
38
  "type": "git",
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
- const activateAdvisor = async (args: string, ctx: ExtensionContext) => {
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 executor = findConfiguredModel(ctx, executorRef);
205
- if (!executor) {
206
- notify(ctx, `Executor model not found: ${executorRef}`, "error");
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
- if (!(await pi.setModel(executor))) {
214
- notify(ctx, `No API key for Executor ${executorRef}`, "error");
215
- return;
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
- notify(
224
- ctx,
225
- `Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
226
- "info"
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 (!advisorSessionState.canConsult(advisorMaxCallsPerSessionRef)) {
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
- advisorSessionState.consumeCall();
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 ?? true);
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
- if (ctx.hasUI) {
445
- ctx.ui.notify(
446
- "Advisor flow disabled. Current model unchanged.",
447
- "info"
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
  });