@juicesharp/rpiv-advisor 1.8.0 → 1.8.2
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/advisor.ts +23 -47
- package/package.json +49 -46
package/advisor.ts
CHANGED
|
@@ -13,9 +13,7 @@
|
|
|
13
13
|
* via pi.setActiveTools(). Selection is in-memory and resets each session.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import { homedir } from "node:os";
|
|
18
|
-
import { dirname, join } from "node:path";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
19
17
|
import { fileURLToPath } from "node:url";
|
|
20
18
|
import type { Api, Model, StopReason, Usage } from "@earendil-works/pi-ai";
|
|
21
19
|
import { completeSimple, getSupportedThinkingLevels, type Message, type ThinkingLevel } from "@earendil-works/pi-ai";
|
|
@@ -29,6 +27,8 @@ import {
|
|
|
29
27
|
type ToolInfo,
|
|
30
28
|
} from "@earendil-works/pi-coding-agent";
|
|
31
29
|
import type { SelectItem } from "@earendil-works/pi-tui";
|
|
30
|
+
import type { GuidanceFields } from "@juicesharp/rpiv-config";
|
|
31
|
+
import { configPath, loadJsonConfig, saveJsonConfig, validateGuidanceFields } from "@juicesharp/rpiv-config";
|
|
32
32
|
import { Type } from "typebox";
|
|
33
33
|
import { showAdvisorPicker, showEffortPicker } from "./advisor-ui.js";
|
|
34
34
|
|
|
@@ -41,9 +41,7 @@ export const ADVISOR_TOOL_NAME = "advisor";
|
|
|
41
41
|
const TOOL_LABEL = "Advisor";
|
|
42
42
|
|
|
43
43
|
// Persistence
|
|
44
|
-
const
|
|
45
|
-
const ADVISOR_CONFIG_PATH = join(CONFIG_DIR, "advisor.json");
|
|
46
|
-
const CONFIG_FILE_MODE = 0o600;
|
|
44
|
+
const ADVISOR_CONFIG_PATH = configPath("rpiv-advisor", "advisor.json");
|
|
47
45
|
|
|
48
46
|
// Selector sentinels — double-underscore form is collision-proof against real provider:id keys
|
|
49
47
|
const NO_ADVISOR_VALUE = "__no_advisor__";
|
|
@@ -62,6 +60,7 @@ const CHECKMARK = " ✓";
|
|
|
62
60
|
const MSG_ADVISOR_DISABLED = "Advisor disabled";
|
|
63
61
|
const MSG_REQUIRES_INTERACTIVE = "/advisor requires interactive mode";
|
|
64
62
|
const MSG_ADVISOR_NUDGE = "Please advise on the executor's situation above.";
|
|
63
|
+
const MSG_PERSIST_FAILED = "Failed to save advisor selection — selection not persisted";
|
|
65
64
|
|
|
66
65
|
// Errors (static)
|
|
67
66
|
const ERR_NO_MODEL = "No advisor model is configured. The user can enable one with the /advisor command.";
|
|
@@ -95,11 +94,6 @@ const msgConsulting = (label: string, effort: ThinkingLevel | undefined) =>
|
|
|
95
94
|
// Config file persistence (cross-session)
|
|
96
95
|
// ---------------------------------------------------------------------------
|
|
97
96
|
|
|
98
|
-
interface GuidanceFields {
|
|
99
|
-
promptSnippet?: string;
|
|
100
|
-
promptGuidelines?: string[];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
97
|
interface AdvisorConfig {
|
|
104
98
|
modelKey?: string;
|
|
105
99
|
effort?: ThinkingLevel;
|
|
@@ -108,37 +102,17 @@ interface AdvisorConfig {
|
|
|
108
102
|
}
|
|
109
103
|
|
|
110
104
|
export function loadAdvisorConfig(): AdvisorConfig {
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
return JSON.parse(readFileSync(ADVISOR_CONFIG_PATH, "utf-8")) as AdvisorConfig;
|
|
114
|
-
} catch {
|
|
115
|
-
return {};
|
|
116
|
-
}
|
|
105
|
+
return loadJsonConfig<AdvisorConfig>(ADVISOR_CONFIG_PATH);
|
|
117
106
|
}
|
|
118
107
|
|
|
119
|
-
|
|
120
|
-
if (!fields || typeof fields !== "object") return {};
|
|
121
|
-
const g = fields as Record<string, unknown>;
|
|
122
|
-
const result: GuidanceFields = {};
|
|
123
|
-
if (typeof g.promptSnippet === "string" && g.promptSnippet.length > 0) {
|
|
124
|
-
result.promptSnippet = g.promptSnippet;
|
|
125
|
-
}
|
|
126
|
-
if (
|
|
127
|
-
Array.isArray(g.promptGuidelines) &&
|
|
128
|
-
g.promptGuidelines.length > 0 &&
|
|
129
|
-
g.promptGuidelines.every((s) => typeof s === "string" && s.length > 0)
|
|
130
|
-
) {
|
|
131
|
-
result.promptGuidelines = g.promptGuidelines;
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
108
|
+
// validateGuidanceFields is now imported from @juicesharp/rpiv-config
|
|
135
109
|
|
|
136
110
|
function validateDisabledForModels(value: unknown): string[] {
|
|
137
111
|
if (!Array.isArray(value)) return [];
|
|
138
112
|
return value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
|
|
139
113
|
}
|
|
140
114
|
|
|
141
|
-
export function saveAdvisorConfig(key: string | undefined, effort: ThinkingLevel | undefined):
|
|
115
|
+
export function saveAdvisorConfig(key: string | undefined, effort: ThinkingLevel | undefined): boolean {
|
|
142
116
|
const existing = loadAdvisorConfig();
|
|
143
117
|
const config: AdvisorConfig = { ...existing };
|
|
144
118
|
// Delete (rather than omit) to clear fields that may exist in the spread
|
|
@@ -148,17 +122,7 @@ export function saveAdvisorConfig(key: string | undefined, effort: ThinkingLevel
|
|
|
148
122
|
else delete config.modelKey;
|
|
149
123
|
if (effort) config.effort = effort;
|
|
150
124
|
else delete config.effort;
|
|
151
|
-
|
|
152
|
-
mkdirSync(dirname(ADVISOR_CONFIG_PATH), { recursive: true });
|
|
153
|
-
writeFileSync(ADVISOR_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
154
|
-
} catch {
|
|
155
|
-
// write may fail on disk-full or permission errors — best effort only
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
chmodSync(ADVISOR_CONFIG_PATH, CONFIG_FILE_MODE);
|
|
159
|
-
} catch {
|
|
160
|
-
// chmod may fail on some filesystems — best effort only
|
|
161
|
-
}
|
|
125
|
+
return saveJsonConfig(ADVISOR_CONFIG_PATH, config);
|
|
162
126
|
}
|
|
163
127
|
|
|
164
128
|
function parseModelKey(key: string): { provider: string; modelId: string } | undefined {
|
|
@@ -630,9 +594,16 @@ export function registerAdvisorCommand(pi: ExtensionAPI): void {
|
|
|
630
594
|
const activeHas = activeTools.includes(ADVISOR_TOOL_NAME);
|
|
631
595
|
|
|
632
596
|
if (choice === NO_ADVISOR_VALUE) {
|
|
597
|
+
// Persist BEFORE applying in-memory state so a save failure can't
|
|
598
|
+
// leave the model setter and the active-tools registry divergent
|
|
599
|
+
// (review I2: early-return on failure skipped the tool-list update
|
|
600
|
+
// and left "model=undefined + tool still registered" stranded).
|
|
601
|
+
if (!saveAdvisorConfig(undefined, undefined)) {
|
|
602
|
+
ctx.ui.notify(MSG_PERSIST_FAILED, "error");
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
633
605
|
setAdvisorModel(undefined);
|
|
634
606
|
setAdvisorEffort(undefined);
|
|
635
|
-
saveAdvisorConfig(undefined, undefined);
|
|
636
607
|
if (activeHas) {
|
|
637
608
|
pi.setActiveTools(activeTools.filter((n) => n !== ADVISOR_TOOL_NAME));
|
|
638
609
|
}
|
|
@@ -668,9 +639,14 @@ export function registerAdvisorCommand(pi: ExtensionAPI): void {
|
|
|
668
639
|
effortChoice = effortResult === OFF_VALUE ? undefined : (effortResult as ThinkingLevel);
|
|
669
640
|
}
|
|
670
641
|
|
|
642
|
+
// Persist BEFORE applying in-memory state — same rationale as the
|
|
643
|
+
// disable branch above (review I2).
|
|
644
|
+
if (!saveAdvisorConfig(modelKey(picked), effortChoice)) {
|
|
645
|
+
ctx.ui.notify(MSG_PERSIST_FAILED, "error");
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
671
648
|
setAdvisorEffort(effortChoice);
|
|
672
649
|
setAdvisorModel(picked);
|
|
673
|
-
saveAdvisorConfig(modelKey(picked), effortChoice);
|
|
674
650
|
|
|
675
651
|
// Re-read after the effort-picker await — the snapshot taken before
|
|
676
652
|
// `showEffortPicker` is stale once execution yields.
|
package/package.json
CHANGED
|
@@ -1,48 +1,51 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
2
|
+
"name": "@juicesharp/rpiv-advisor",
|
|
3
|
+
"version": "1.8.2",
|
|
4
|
+
"description": "Pi extension. A second opinion the model can request from a stronger reviewer model before it acts.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"rpiv",
|
|
9
|
+
"advisor"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "juicesharp",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/juicesharp/rpiv-mono.git",
|
|
17
|
+
"directory": "packages/rpiv-advisor"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-advisor#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/juicesharp/rpiv-mono/issues"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "vitest run"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"index.ts",
|
|
31
|
+
"advisor.ts",
|
|
32
|
+
"advisor-ui.ts",
|
|
33
|
+
"prompts/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"pi": {
|
|
38
|
+
"extensions": [
|
|
39
|
+
"./index.ts"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@juicesharp/rpiv-config": "^1.8.2"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@earendil-works/pi-ai": "*",
|
|
47
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
48
|
+
"@earendil-works/pi-tui": "*",
|
|
49
|
+
"typebox": "*"
|
|
50
|
+
}
|
|
48
51
|
}
|