@juicesharp/rpiv-advisor 1.8.2 → 1.9.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 +70 -14
  2. package/index.ts +5 -2
  3. package/package.json +2 -2
package/advisor.ts CHANGED
@@ -50,6 +50,7 @@ const OFF_VALUE = "__off__";
50
50
  // Effort levels
51
51
  const BASE_EFFORT_LEVELS: ThinkingLevel[] = ["minimal", "low", "medium", "high"];
52
52
  const XHIGH_EFFORT_LEVEL: ThinkingLevel = "xhigh";
53
+ const EFFORT_ORDINAL: readonly ThinkingLevel[] = ["minimal", "low", "medium", "high", "xhigh"];
53
54
  const DEFAULT_EFFORT: ThinkingLevel = "high";
54
55
  const RECOMMENDED_EFFORT_SUFFIX = " (recommended)";
55
56
 
@@ -98,7 +99,7 @@ interface AdvisorConfig {
98
99
  modelKey?: string;
99
100
  effort?: ThinkingLevel;
100
101
  guidance?: GuidanceFields;
101
- disabledForModels?: string[];
102
+ disabledForModels?: Array<string | { model: string; minEffort?: ThinkingLevel }>;
102
103
  }
103
104
 
104
105
  export function loadAdvisorConfig(): AdvisorConfig {
@@ -107,9 +108,16 @@ export function loadAdvisorConfig(): AdvisorConfig {
107
108
 
108
109
  // validateGuidanceFields is now imported from @juicesharp/rpiv-config
109
110
 
110
- function validateDisabledForModels(value: unknown): string[] {
111
+ function validateDisabledForModels(value: unknown): Array<string | { model: string; minEffort?: ThinkingLevel }> {
111
112
  if (!Array.isArray(value)) return [];
112
- return value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
113
+ return value.filter((entry): entry is string | { model: string; minEffort?: ThinkingLevel } => {
114
+ if (typeof entry === "string") return entry.length > 0;
115
+ if (typeof entry !== "object" || entry === null) return false;
116
+ const obj = entry as Record<string, unknown>;
117
+ if (typeof obj.model !== "string" || obj.model.length === 0) return false;
118
+ if (obj.minEffort !== undefined && !EFFORT_ORDINAL.includes(obj.minEffort as ThinkingLevel)) return false;
119
+ return true;
120
+ });
113
121
  }
114
122
 
115
123
  export function saveAdvisorConfig(key: string | undefined, effort: ThinkingLevel | undefined): boolean {
@@ -271,9 +279,9 @@ export function setAdvisorEffort(effort: ThinkingLevel | undefined): void {
271
279
  selectedAdvisorEffort = effort;
272
280
  }
273
281
 
274
- let disabledForModelsCache: string[] = [];
282
+ let disabledForModelsCache: Array<string | { model: string; minEffort?: ThinkingLevel }> = [];
275
283
 
276
- export function setDisabledForModels(models: string[]): void {
284
+ export function setDisabledForModels(models: Array<string | { model: string; minEffort?: ThinkingLevel }>): void {
277
285
  disabledForModelsCache = models;
278
286
  }
279
287
 
@@ -304,7 +312,7 @@ export function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): vo
304
312
  setAdvisorEffort(config.effort);
305
313
  }
306
314
 
307
- if (isExecutorBlocked(ctx)) {
315
+ if (isExecutorBlocked(ctx, pi.getThinkingLevel())) {
308
316
  if (ctx.hasUI) {
309
317
  const advisorLabel = `${model.provider}:${model.id}`;
310
318
  ctx.ui.notify(msgAdvisorRestoredInactive(advisorLabel, config.effort), "info");
@@ -503,12 +511,21 @@ export function registerAdvisorTool(pi: ExtensionAPI): void {
503
511
 
504
512
  export function registerAdvisorBeforeAgentStart(pi: ExtensionAPI): void {
505
513
  pi.on("before_agent_start", async (_event, ctx) => {
506
- const shouldStrip = !getAdvisorModel() || isExecutorBlocked(ctx);
507
- if (shouldStrip) {
514
+ const advisor = getAdvisorModel();
515
+ if (!advisor) {
508
516
  const active = pi.getActiveTools();
509
517
  if (active.includes(ADVISOR_TOOL_NAME)) {
510
518
  pi.setActiveTools(active.filter((n) => n !== ADVISOR_TOOL_NAME));
511
519
  }
520
+ return;
521
+ }
522
+ const blocked = isExecutorBlocked(ctx, pi.getThinkingLevel());
523
+ const active = pi.getActiveTools();
524
+ const hasTool = active.includes(ADVISOR_TOOL_NAME);
525
+ if (blocked && hasTool) {
526
+ pi.setActiveTools(active.filter((n) => n !== ADVISOR_TOOL_NAME));
527
+ } else if (!blocked && !hasTool) {
528
+ pi.setActiveTools([...active, ADVISOR_TOOL_NAME]);
512
529
  }
513
530
  });
514
531
  }
@@ -527,7 +544,7 @@ export function registerModelSelectHandler(pi: ExtensionAPI): void {
527
544
  const advisor = getAdvisorModel();
528
545
  if (!advisor) return;
529
546
 
530
- const blocked = isModelBlocked(event.model);
547
+ const blocked = isModelBlocked(event.model, pi.getThinkingLevel());
531
548
  const active = pi.getActiveTools();
532
549
  const hasTool = active.includes(ADVISOR_TOOL_NAME);
533
550
 
@@ -545,6 +562,33 @@ export function registerModelSelectHandler(pi: ExtensionAPI): void {
545
562
  });
546
563
  }
547
564
 
565
+ // ---------------------------------------------------------------------------
566
+ // thinking_level_select handler — mid-session effort changes strip/re-add advisor
567
+ // ---------------------------------------------------------------------------
568
+
569
+ export function registerThinkingLevelSelectHandler(pi: ExtensionAPI): void {
570
+ pi.on("thinking_level_select", async (event, ctx) => {
571
+ const advisor = getAdvisorModel();
572
+ if (!advisor) return;
573
+
574
+ const blocked = isModelBlocked(ctx?.model, event.level);
575
+ const active = pi.getActiveTools();
576
+ const hasTool = active.includes(ADVISOR_TOOL_NAME);
577
+
578
+ if (blocked && hasTool) {
579
+ pi.setActiveTools(active.filter((n) => n !== ADVISOR_TOOL_NAME));
580
+ if (ctx.hasUI) {
581
+ ctx.ui.notify(`Advisor disabled for ${modelKey(ctx.model!)}`, "info");
582
+ }
583
+ } else if (!blocked && !hasTool) {
584
+ pi.setActiveTools([...active, ADVISOR_TOOL_NAME]);
585
+ if (ctx.hasUI) {
586
+ ctx.ui.notify(msgAdvisorRestored(modelKey(advisor), getAdvisorEffort()), "info");
587
+ }
588
+ }
589
+ });
590
+ }
591
+
548
592
  // ---------------------------------------------------------------------------
549
593
  // /advisor slash command — opens selector panel for picking the advisor model
550
594
  // ---------------------------------------------------------------------------
@@ -553,13 +597,25 @@ function modelKey(m: { provider: string; id: string }): string {
553
597
  return `${m.provider}:${m.id}`;
554
598
  }
555
599
 
556
- function isModelBlocked(model: Model<Api> | undefined): boolean {
600
+ function isModelBlocked(model: Model<Api> | undefined, thinkingLevel?: string): boolean {
557
601
  if (!model) return false;
558
- return disabledForModelsCache.includes(modelKey(model));
602
+ const key = modelKey(model);
603
+ for (const entry of disabledForModelsCache) {
604
+ if (typeof entry === "string") {
605
+ if (entry === key) return true;
606
+ } else {
607
+ if (entry.model !== key) continue;
608
+ if (entry.minEffort === undefined) return true;
609
+ const thresholdOrdinal = EFFORT_ORDINAL.indexOf(entry.minEffort);
610
+ const executorOrdinal = EFFORT_ORDINAL.indexOf(thinkingLevel as ThinkingLevel);
611
+ if (executorOrdinal >= thresholdOrdinal) return true;
612
+ }
613
+ }
614
+ return false;
559
615
  }
560
616
 
561
- function isExecutorBlocked(ctx: ExtensionContext): boolean {
562
- return isModelBlocked(ctx?.model);
617
+ function isExecutorBlocked(ctx: ExtensionContext, thinkingLevel?: string): boolean {
618
+ return isModelBlocked(ctx?.model, thinkingLevel);
563
619
  }
564
620
 
565
621
  export function registerAdvisorCommand(pi: ExtensionAPI): void {
@@ -652,7 +708,7 @@ export function registerAdvisorCommand(pi: ExtensionAPI): void {
652
708
  // `showEffortPicker` is stale once execution yields.
653
709
  const activeToolsNow = pi.getActiveTools();
654
710
  const activeHasNow = activeToolsNow.includes(ADVISOR_TOOL_NAME);
655
- const blocked = isExecutorBlocked(ctx);
711
+ const blocked = isExecutorBlocked(ctx, pi.getThinkingLevel());
656
712
  if (!activeHasNow && !blocked) {
657
713
  pi.setActiveTools([...activeToolsNow, ADVISOR_TOOL_NAME]);
658
714
  }
package/index.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * rpiv-advisor — Pi extension
3
3
  *
4
- * Registers the `advisor` tool, `/advisor` command, and the three lifecycle
4
+ * Registers the `advisor` tool, `/advisor` command, and four lifecycle
5
5
  * hooks (session_start restore, before_agent_start strip, model_select
6
- * re-evaluation) that together implement the advisor-strategy pattern.
6
+ * re-evaluation, thinking_level_select re-evaluation) that together
7
+ * implement the advisor-strategy pattern.
7
8
  *
8
9
  * Config persists at ~/.config/rpiv-advisor/advisor.json. Tool name
9
10
  * preserved verbatim from rpiv-pi@7525a5d.
@@ -15,6 +16,7 @@ import {
15
16
  registerAdvisorCommand,
16
17
  registerAdvisorTool,
17
18
  registerModelSelectHandler,
19
+ registerThinkingLevelSelectHandler,
18
20
  restoreAdvisorState,
19
21
  } from "./advisor.js";
20
22
 
@@ -23,6 +25,7 @@ export default function (pi: ExtensionAPI) {
23
25
  registerAdvisorCommand(pi);
24
26
  registerAdvisorBeforeAgentStart(pi);
25
27
  registerModelSelectHandler(pi);
28
+ registerThinkingLevelSelectHandler(pi);
26
29
 
27
30
  pi.on("session_start", async (_event, ctx) => {
28
31
  restoreAdvisorState(ctx, pi);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juicesharp/rpiv-advisor",
3
- "version": "1.8.2",
3
+ "version": "1.9.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",
@@ -40,7 +40,7 @@
40
40
  ]
41
41
  },
42
42
  "dependencies": {
43
- "@juicesharp/rpiv-config": "^1.8.2"
43
+ "@juicesharp/rpiv-config": "^1.9.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-ai": "*",