@juicesharp/rpiv-advisor 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/advisor.ts +84 -5
  2. package/index.ts +5 -3
  3. package/package.json +1 -1
package/advisor.ts CHANGED
@@ -84,6 +84,10 @@ const msgAdvisorEnabled = (label: string, effort: ThinkingLevel | undefined) =>
84
84
  `Advisor: ${label}${effort ? `, ${effort}` : ""}`;
85
85
  const msgAdvisorRestored = (label: string, effort: ThinkingLevel | undefined) =>
86
86
  `Advisor restored: ${label}${effort ? `, ${effort}` : ""}`;
87
+ const msgAdvisorRestoredInactive = (label: string, effort: ThinkingLevel | undefined) =>
88
+ `Advisor restored: ${label}${effort ? `, ${effort}` : ""} (inactive for current executor)`;
89
+ const msgAdvisorEnabledInactive = (label: string, effort: ThinkingLevel | undefined) =>
90
+ `Advisor: ${label}${effort ? `, ${effort}` : ""} (inactive for current executor)`;
87
91
  const msgConsulting = (label: string, effort: ThinkingLevel | undefined) =>
88
92
  `Consulting advisor (${label}${effort ? `, ${effort}` : ""})…`;
89
93
 
@@ -100,6 +104,7 @@ interface AdvisorConfig {
100
104
  modelKey?: string;
101
105
  effort?: ThinkingLevel;
102
106
  guidance?: GuidanceFields;
107
+ disabledForModels?: string[];
103
108
  }
104
109
 
105
110
  export function loadAdvisorConfig(): AdvisorConfig {
@@ -128,6 +133,11 @@ function validateGuidanceFields(fields: unknown): GuidanceFields {
128
133
  return result;
129
134
  }
130
135
 
136
+ function validateDisabledForModels(value: unknown): string[] {
137
+ if (!Array.isArray(value)) return [];
138
+ return value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
139
+ }
140
+
131
141
  export function saveAdvisorConfig(key: string | undefined, effort: ThinkingLevel | undefined): void {
132
142
  const existing = loadAdvisorConfig();
133
143
  const config: AdvisorConfig = { ...existing };
@@ -297,12 +307,21 @@ export function setAdvisorEffort(effort: ThinkingLevel | undefined): void {
297
307
  selectedAdvisorEffort = effort;
298
308
  }
299
309
 
310
+ let disabledForModelsCache: string[] = [];
311
+
312
+ export function setDisabledForModels(models: string[]): void {
313
+ disabledForModelsCache = models;
314
+ }
315
+
300
316
  // ---------------------------------------------------------------------------
301
317
  // Session restoration — called from index.ts session_start handler
302
318
  // ---------------------------------------------------------------------------
303
319
 
304
320
  export function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): void {
305
321
  const config = loadAdvisorConfig();
322
+
323
+ setDisabledForModels(validateDisabledForModels(config.disabledForModels));
324
+
306
325
  if (!config.modelKey) return;
307
326
 
308
327
  const parsed = parseModelKey(config.modelKey);
@@ -321,6 +340,14 @@ export function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): vo
321
340
  setAdvisorEffort(config.effort);
322
341
  }
323
342
 
343
+ if (isExecutorBlocked(ctx)) {
344
+ if (ctx.hasUI) {
345
+ const advisorLabel = `${model.provider}:${model.id}`;
346
+ ctx.ui.notify(msgAdvisorRestoredInactive(advisorLabel, config.effort), "info");
347
+ }
348
+ return;
349
+ }
350
+
324
351
  const active = pi.getActiveTools();
325
352
  if (!active.includes(ADVISOR_TOOL_NAME)) {
326
353
  pi.setActiveTools([...active, ADVISOR_TOOL_NAME]);
@@ -511,8 +538,9 @@ export function registerAdvisorTool(pi: ExtensionAPI): void {
511
538
  // ---------------------------------------------------------------------------
512
539
 
513
540
  export function registerAdvisorBeforeAgentStart(pi: ExtensionAPI): void {
514
- pi.on("before_agent_start", async () => {
515
- if (!getAdvisorModel()) {
541
+ pi.on("before_agent_start", async (_event, ctx) => {
542
+ const shouldStrip = !getAdvisorModel() || isExecutorBlocked(ctx);
543
+ if (shouldStrip) {
516
544
  const active = pi.getActiveTools();
517
545
  if (active.includes(ADVISOR_TOOL_NAME)) {
518
546
  pi.setActiveTools(active.filter((n) => n !== ADVISOR_TOOL_NAME));
@@ -521,6 +549,38 @@ export function registerAdvisorBeforeAgentStart(pi: ExtensionAPI): void {
521
549
  });
522
550
  }
523
551
 
552
+ // ---------------------------------------------------------------------------
553
+ // model_select handler — mid-session model switches strip/re-add advisor
554
+ // ---------------------------------------------------------------------------
555
+
556
+ export function registerModelSelectHandler(pi: ExtensionAPI): void {
557
+ pi.on("model_select", async (event, ctx) => {
558
+ // session_start restore path is owned by restoreAdvisorState — it already
559
+ // activates the tool and notifies. Skipping "restore" here prevents a
560
+ // duplicate notification on initial model load.
561
+ if (event.source === "restore") return;
562
+
563
+ const advisor = getAdvisorModel();
564
+ if (!advisor) return;
565
+
566
+ const blocked = isModelBlocked(event.model);
567
+ const active = pi.getActiveTools();
568
+ const hasTool = active.includes(ADVISOR_TOOL_NAME);
569
+
570
+ if (blocked && hasTool) {
571
+ pi.setActiveTools(active.filter((n) => n !== ADVISOR_TOOL_NAME));
572
+ if (ctx.hasUI) {
573
+ ctx.ui.notify(`Advisor disabled for ${modelKey(event.model)}`, "info");
574
+ }
575
+ } else if (!blocked && !hasTool) {
576
+ pi.setActiveTools([...active, ADVISOR_TOOL_NAME]);
577
+ if (ctx.hasUI) {
578
+ ctx.ui.notify(msgAdvisorRestored(modelKey(advisor), getAdvisorEffort()), "info");
579
+ }
580
+ }
581
+ });
582
+ }
583
+
524
584
  // ---------------------------------------------------------------------------
525
585
  // /advisor slash command — opens selector panel for picking the advisor model
526
586
  // ---------------------------------------------------------------------------
@@ -529,6 +589,15 @@ function modelKey(m: { provider: string; id: string }): string {
529
589
  return `${m.provider}:${m.id}`;
530
590
  }
531
591
 
592
+ function isModelBlocked(model: Model<Api> | undefined): boolean {
593
+ if (!model) return false;
594
+ return disabledForModelsCache.includes(modelKey(model));
595
+ }
596
+
597
+ function isExecutorBlocked(ctx: ExtensionContext): boolean {
598
+ return isModelBlocked(ctx?.model);
599
+ }
600
+
532
601
  export function registerAdvisorCommand(pi: ExtensionAPI): void {
533
602
  pi.registerCommand("advisor", {
534
603
  description: "Configure the advisor model for the advisor-strategy pattern",
@@ -602,10 +671,20 @@ export function registerAdvisorCommand(pi: ExtensionAPI): void {
602
671
  setAdvisorEffort(effortChoice);
603
672
  setAdvisorModel(picked);
604
673
  saveAdvisorConfig(modelKey(picked), effortChoice);
605
- if (!activeHas) {
606
- pi.setActiveTools([...activeTools, ADVISOR_TOOL_NAME]);
674
+
675
+ // Re-read after the effort-picker await — the snapshot taken before
676
+ // `showEffortPicker` is stale once execution yields.
677
+ const activeToolsNow = pi.getActiveTools();
678
+ const activeHasNow = activeToolsNow.includes(ADVISOR_TOOL_NAME);
679
+ const blocked = isExecutorBlocked(ctx);
680
+ if (!activeHasNow && !blocked) {
681
+ pi.setActiveTools([...activeToolsNow, ADVISOR_TOOL_NAME]);
682
+ }
683
+ if (blocked) {
684
+ ctx.ui.notify(msgAdvisorEnabledInactive(modelKey(picked), effortChoice), "info");
685
+ } else {
686
+ ctx.ui.notify(msgAdvisorEnabled(modelKey(picked), effortChoice), "info");
607
687
  }
608
- ctx.ui.notify(msgAdvisorEnabled(modelKey(picked), effortChoice), "info");
609
688
  },
610
689
  });
611
690
  }
package/index.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * rpiv-advisor — Pi extension
3
3
  *
4
- * Registers the `advisor` tool, `/advisor` command, and the two lifecycle
5
- * hooks (session_start restore, before_agent_start strip) that together
6
- * implement the advisor-strategy pattern.
4
+ * Registers the `advisor` tool, `/advisor` command, and the three lifecycle
5
+ * hooks (session_start restore, before_agent_start strip, model_select
6
+ * re-evaluation) that together implement the advisor-strategy pattern.
7
7
  *
8
8
  * Config persists at ~/.config/rpiv-advisor/advisor.json. Tool name
9
9
  * preserved verbatim from rpiv-pi@7525a5d.
@@ -14,6 +14,7 @@ import {
14
14
  registerAdvisorBeforeAgentStart,
15
15
  registerAdvisorCommand,
16
16
  registerAdvisorTool,
17
+ registerModelSelectHandler,
17
18
  restoreAdvisorState,
18
19
  } from "./advisor.js";
19
20
 
@@ -21,6 +22,7 @@ export default function (pi: ExtensionAPI) {
21
22
  registerAdvisorTool(pi);
22
23
  registerAdvisorCommand(pi);
23
24
  registerAdvisorBeforeAgentStart(pi);
25
+ registerModelSelectHandler(pi);
24
26
 
25
27
  pi.on("session_start", async (_event, ctx) => {
26
28
  restoreAdvisorState(ctx, pi);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juicesharp/rpiv-advisor",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "Pi extension. A second opinion the model can request from a stronger reviewer model before it acts.",
5
5
  "keywords": [
6
6
  "pi-package",