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 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
- A configurable second-opinion workflow for <a href="https://github.com/earendil-works/pi">Pi</a> coding agents.
5
+ ![Pi Advisor consultation in the terminal](https://raw.githubusercontent.com/philipbrembeck/pi-advisor/refs/heads/main/assets/screenshot.png)
7
6
 
8
- </div>
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
- Keep the work to the executor model, while the advisor steers it.
9
+ </div>
11
10
 
12
- This extension introduces a strategic "Executor/Advisor" workflow, inspired by Claudes [Advisor](https://code.claude.com/docs/en/advisor).
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. They do not parse a verdict or block execution.
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": true,
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. It appears after a non-blocked settled run and is never persisted.
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
- "main": "extensions/index.ts",
45
- "types": "extensions/index.ts",
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
- "files": [
55
- "extensions",
56
- "src",
57
- "README.md"
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<{ markdown: string; thinkingText: string }>;
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
- const activateAdvisor = async (args: string, ctx: ExtensionContext) => {
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 executor = findConfiguredModel(ctx, executorRef);
207
- if (!executor) {
208
- notify(ctx, `Executor model not found: ${executorRef}`, "error");
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
- if (!(await pi.setModel(executor))) {
216
- notify(ctx, `No API key for Executor ${executorRef}`, "error");
217
- return;
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([...pi.getActiveTools(), "ask_advisor"]);
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 (!advisorSessionState.canConsult(advisorMaxCallsPerSessionRef)) {
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
- advisorSessionState.consumeCall();
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 ?? true);
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
- ctx.ui.notify(`Saved Advisor settings to ${path}`, "info");
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.getActiveTools().filter((name) => name !== "ask_advisor")
543
+ pi
544
+ .getActiveTools()
545
+ .filter(
546
+ (name) =>
547
+ name !== "ask_advisor" && name !== "record_advisor_outcome"
548
+ )
447
549
  );
448
- if (ctx.hasUI) {
449
- ctx.ui.notify(
450
- "Advisor flow disabled. Current model unchanged.",
451
- "info"
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
  });