@fiale-plus/pi-rogue-advisor 0.1.7 → 0.1.8
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 +3 -4
- package/package.json +1 -1
- package/skills/advisor/SKILL.md +3 -4
- package/src/completions.test.ts +2 -5
- package/src/completions.ts +2 -8
- package/src/extension.test.ts +85 -2
- package/src/extension.ts +254 -117
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What this package is
|
|
4
4
|
|
|
5
|
-
Strategic advisor for Pi sessions with low-overhead preflight/post-review routing, model auto-detection, session memory, and
|
|
5
|
+
Strategic advisor for Pi sessions with low-overhead preflight/post-review routing, model auto-detection, session memory, and orchestration-managed mid-session check-ins.
|
|
6
6
|
|
|
7
7
|
- SOTA-first model fallback: `gpt-5.5`/`claude-opus-4-6`/`claude-sonnet-4-6` where available.
|
|
8
8
|
- Keeps command-level behavior simple and explicit.
|
|
@@ -28,7 +28,6 @@ npm install --workspace packages/advisor
|
|
|
28
28
|
| `/advisor off` | Disable advisor |
|
|
29
29
|
| `/advisor mode auto\|manual\|off` | Change routing behavior |
|
|
30
30
|
| `/advisor review light\|strict\|off` | Change review strictness |
|
|
31
|
-
| `/advisor checkins on\|off\|<minutes>` | Enable/disable low-cost mid-hour check-ins |
|
|
32
31
|
| `/advisor config` | Show current config |
|
|
33
32
|
| `/advisor model <provider>/<model>` | Set explicit model override |
|
|
34
33
|
| `/advisor <question>` | Get one-shot advisory response |
|
|
@@ -37,11 +36,11 @@ npm install --workspace packages/advisor
|
|
|
37
36
|
|
|
38
37
|
- `mode`: `auto`
|
|
39
38
|
- `review`: `light`
|
|
40
|
-
- `checkins`: `off` (orchestration turns them on
|
|
39
|
+
- `checkins`: `off` (orchestration turns them on when a loop is active)
|
|
41
40
|
- `checkinIntervalMinutes`: `30`
|
|
42
41
|
- `model`: not set (auto-detected)
|
|
43
42
|
|
|
44
|
-
Check-ins gate on session activity, are bounded,
|
|
43
|
+
Check-ins gate on session activity, are bounded, avoid overlapping calls, and use higher/advanced advisor models first with regular model fallback enabled by default. They are lifecycle-managed by orchestration: enabling `/loop` enables them, and stopping that loop disables them.
|
|
45
44
|
|
|
46
45
|
## Stability guarantees
|
|
47
46
|
|
package/package.json
CHANGED
package/skills/advisor/SKILL.md
CHANGED
|
@@ -12,7 +12,7 @@ Use this skill for non-trivial decisions before/after significant edits.
|
|
|
12
12
|
- `/pi-rogue` — open cockpit and command pointers
|
|
13
13
|
- `/advisor status` — show current advisor settings and model route
|
|
14
14
|
- `/advisor <question>` — ask immediate advice
|
|
15
|
-
- `/
|
|
15
|
+
- Check-ins are lifecycle-managed by `/loop`, not by the advisor command surface
|
|
16
16
|
|
|
17
17
|
## Command surface
|
|
18
18
|
|
|
@@ -24,7 +24,6 @@ Use this skill for non-trivial decisions before/after significant edits.
|
|
|
24
24
|
| `/advisor off` | Disable advisor |
|
|
25
25
|
| `/advisor mode auto\|manual\|off` | Control when advisor auto-runs |
|
|
26
26
|
| `/advisor review light\|strict\|off` | Set review threshold |
|
|
27
|
-
| `/advisor checkins on\|off\|<minutes>` | Configure interval check-ins |
|
|
28
27
|
| `/advisor config` | Dump full config |
|
|
29
28
|
| `/advisor model <provider/model>` | Pin model explicitly |
|
|
30
29
|
| `/advisor <question>` | Run one advisory response |
|
|
@@ -33,7 +32,7 @@ Use this skill for non-trivial decisions before/after significant edits.
|
|
|
33
32
|
|
|
34
33
|
- Preflight is heuristics + quick local gate first.
|
|
35
34
|
- Review runs after edits and/or at completion points by policy.
|
|
36
|
-
- No
|
|
35
|
+
- No standalone check-in command: check-ins are triggered from loop cadence (not from advisor internals), using higher/advanced advisor models first with regular model fallback enabled by default.
|
|
37
36
|
|
|
38
37
|
## Keep scope clear
|
|
39
38
|
|
|
@@ -43,6 +42,6 @@ The advisor surface is separate from orchestration (`goal`/`loop`/`autoresearch`
|
|
|
43
42
|
|
|
44
43
|
- `mode: auto`
|
|
45
44
|
- `review: light`
|
|
46
|
-
- `checkins: off` by default; orchestration
|
|
45
|
+
- `checkins: off` by default; loop orchestration owns cadence and enables them when active
|
|
47
46
|
- `checkinIntervalMinutes: 30`
|
|
48
47
|
- `model: auto`
|
package/src/completions.test.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { advisorArgumentCompletions, piRogueArgumentCompletions } from "./comple
|
|
|
4
4
|
describe("advisor completions", () => {
|
|
5
5
|
it("offers top-level advisor continuations", () => {
|
|
6
6
|
const values = advisorArgumentCompletions("")?.map((i) => i.value);
|
|
7
|
-
expect(values).toEqual(expect.arrayContaining(["status", "config", "
|
|
7
|
+
expect(values).toEqual(expect.arrayContaining(["status", "config", "model", "review"]));
|
|
8
|
+
expect(values).not.toContain("checkins");
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
it("offers nested review choices", () => {
|
|
@@ -12,10 +13,6 @@ describe("advisor completions", () => {
|
|
|
12
13
|
expect(values).toEqual(["light", "strict", "off"]);
|
|
13
14
|
});
|
|
14
15
|
|
|
15
|
-
it("offers check-in choices", () => {
|
|
16
|
-
const values = advisorArgumentCompletions("checkins ")?.map((i) => i.value);
|
|
17
|
-
expect(values).toEqual(expect.arrayContaining(["on", "off", "30", "60"]));
|
|
18
|
-
});
|
|
19
16
|
});
|
|
20
17
|
|
|
21
18
|
describe("pi-rogue cockpit completions", () => {
|
package/src/completions.ts
CHANGED
|
@@ -40,24 +40,19 @@ const advisorTopLevel: Array<[string, string?]> = [
|
|
|
40
40
|
["off", "disable advisor"],
|
|
41
41
|
["mode", "set auto/manual/off"],
|
|
42
42
|
["review", "set light/strict/off"],
|
|
43
|
-
["checkins", "configure mid-hour check-ins"],
|
|
44
|
-
["checkin", "alias for checkins"],
|
|
45
43
|
["model", "set or inspect model override"],
|
|
46
44
|
];
|
|
47
45
|
|
|
48
46
|
const advisorNested: Record<string, Array<[string, string?]>> = {
|
|
49
47
|
mode: [["auto"], ["manual"], ["off"]],
|
|
50
48
|
review: [["light"], ["strict"], ["off"]],
|
|
51
|
-
checkins: [["on"], ["off"], ["10"], ["15"], ["30"], ["60"]],
|
|
52
|
-
checkin: [["on"], ["off"], ["10"], ["15"], ["30"], ["60"]],
|
|
53
49
|
model: [["auto"], ["openai-codex/gpt-5.5"], ["anthropic/claude-opus-4-6"]],
|
|
54
50
|
};
|
|
55
51
|
|
|
56
52
|
const piRogueTopLevel: Array<[string, string?]> = [
|
|
57
53
|
["status", "show cockpit"],
|
|
58
|
-
["advisor", "advisor status
|
|
54
|
+
["advisor", "advisor status"],
|
|
59
55
|
["orchestration", "goal/loop/autoresearch shortcuts"],
|
|
60
|
-
["checkins", "advisor check-ins"],
|
|
61
56
|
["help", "show cockpit help"],
|
|
62
57
|
];
|
|
63
58
|
|
|
@@ -70,8 +65,7 @@ const piRogueNested: Record<string, Array<[string, string?]>> = {
|
|
|
70
65
|
["autoresearch-lab", "parallel research flow"],
|
|
71
66
|
["status", "show all surfaces"],
|
|
72
67
|
],
|
|
73
|
-
|
|
74
|
-
help: [["advisor"], ["orchestration"], ["checkins"], ["status"]],
|
|
68
|
+
help: [["advisor"], ["orchestration"], ["status"]],
|
|
75
69
|
};
|
|
76
70
|
|
|
77
71
|
export function advisorArgumentCompletions(prefix: string): CompletionItem[] | null {
|
package/src/extension.test.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import { describe, it,
|
|
2
|
-
import {
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { completeSimple } from "@earendil-works/pi-ai";
|
|
3
|
+
import { normalizeAdvisorConfig, shouldRunCheckin, type AdvisorConfig, completeWithHigherAdvisorModel, completeWithModelFallback } from "./extension.js";
|
|
4
|
+
|
|
5
|
+
vi.mock("@earendil-works/pi-ai", async () => {
|
|
6
|
+
const actual = await vi.importActual<typeof import("@earendil-works/pi-ai")>("@earendil-works/pi-ai");
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
completeSimple: vi.fn(),
|
|
10
|
+
};
|
|
11
|
+
});
|
|
3
12
|
|
|
4
13
|
function state(overrides: Record<string, unknown> = {}) {
|
|
5
14
|
return {
|
|
@@ -89,6 +98,80 @@ describe("mid-hour check-ins", () => {
|
|
|
89
98
|
const cfg = normalizeAdvisorConfig({ checkins: "off" });
|
|
90
99
|
expect(shouldRunCheckin(cfg, state(), 999999, 1)).toBeNull();
|
|
91
100
|
});
|
|
101
|
+
|
|
102
|
+
it("flushes queued check-in regardless of turn delta", () => {
|
|
103
|
+
const cfg = normalizeAdvisorConfig({ checkins: "mid-hour", checkinIntervalMinutes: 30 });
|
|
104
|
+
expect(
|
|
105
|
+
shouldRunCheckin(cfg, state({
|
|
106
|
+
checkin: {
|
|
107
|
+
queued: true,
|
|
108
|
+
queuedReason: "queued mid-session check-in",
|
|
109
|
+
},
|
|
110
|
+
})),
|
|
111
|
+
).toBe("queued mid-session check-in");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
describe("advisor completion fallback behavior", () => {
|
|
117
|
+
function mkCtx(allowHighTier: boolean, includeRegular = true) {
|
|
118
|
+
const high = { id: "openai-codex/gpt-5.5", provider: "openai-codex", input: ["text"] };
|
|
119
|
+
const regular = { id: "provider/text-light", provider: "provider", input: ["text"] };
|
|
120
|
+
return {
|
|
121
|
+
modelRegistry: {
|
|
122
|
+
find: (_provider: string, model: string) => {
|
|
123
|
+
if (!allowHighTier) return null;
|
|
124
|
+
if (_provider === "openai-codex" && model === "gpt-5.5") return high;
|
|
125
|
+
if (_provider === "anthropic" && model === "claude-opus-4-6") return { ...high, id: "anthropic/claude-opus-4-6" };
|
|
126
|
+
if (_provider === "anthropic" && model === "claude-sonnet-4-6") return { ...high, id: "anthropic/claude-sonnet-4-6" };
|
|
127
|
+
if (_provider === "openai-codex" && model === "gpt-5.4-mini") return { ...high, id: "openai-codex/gpt-5.4-mini" };
|
|
128
|
+
return null;
|
|
129
|
+
},
|
|
130
|
+
getAvailable: () => (includeRegular ? [regular] : []),
|
|
131
|
+
getApiKeyAndHeaders: async (_model: unknown) => ({ ok: true, apiKey: "k", headers: {} }),
|
|
132
|
+
},
|
|
133
|
+
} as any;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
it("uses high/advanced models first for check-in completion", async () => {
|
|
137
|
+
const completeSimpleMock = vi.mocked(completeSimple as any);
|
|
138
|
+
completeSimpleMock.mockReset();
|
|
139
|
+
completeSimpleMock.mockResolvedValue({ content: [{ type: "text", text: "ok" }] });
|
|
140
|
+
|
|
141
|
+
const cfg = normalizeAdvisorConfig({ mode: "auto", review: "light" });
|
|
142
|
+
const result = await completeWithHigherAdvisorModel(mkCtx(true, true), cfg, "system", [{ role: "user", content: "x" }], { maxTokens: 128, reasoning: "low" as const });
|
|
143
|
+
|
|
144
|
+
expect(result).not.toBeNull();
|
|
145
|
+
expect(completeSimpleMock).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(completeSimpleMock.mock.calls[0]?.[0]?.id).toBe("openai-codex/gpt-5.5");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("falls back to regular models for check-in completion when high/advanced are unavailable", async () => {
|
|
150
|
+
const completeSimpleMock = vi.mocked(completeSimple as any);
|
|
151
|
+
completeSimpleMock.mockReset();
|
|
152
|
+
completeSimpleMock.mockResolvedValue({ content: [{ type: "text", text: "ok" }] });
|
|
153
|
+
|
|
154
|
+
const cfg = normalizeAdvisorConfig({ mode: "auto", review: "light" });
|
|
155
|
+
const result = await completeWithHigherAdvisorModel(mkCtx(false, true), cfg, "system", [{ role: "user", content: "x" }], { maxTokens: 128, reasoning: "low" as const });
|
|
156
|
+
|
|
157
|
+
expect(result).not.toBeNull();
|
|
158
|
+
expect(completeSimpleMock).toHaveBeenCalledTimes(1);
|
|
159
|
+
expect(completeSimpleMock.mock.calls[0]?.[0]?.id).toBe("provider/text-light");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("uses regular fallback for non-checkin completion", async () => {
|
|
163
|
+
const completeSimpleMock = vi.mocked(completeSimple as any);
|
|
164
|
+
completeSimpleMock.mockReset();
|
|
165
|
+
completeSimpleMock.mockResolvedValue({ content: [{ type: "text", text: "ok" }] });
|
|
166
|
+
|
|
167
|
+
const cfg = normalizeAdvisorConfig({ mode: "auto", review: "light" });
|
|
168
|
+
const result = await completeWithModelFallback(mkCtx(false), cfg, "system", [{ role: "user", content: "x" }], { maxTokens: 128, reasoning: "low" as const });
|
|
169
|
+
|
|
170
|
+
expect(result).not.toBeNull();
|
|
171
|
+
expect(result?.fallback).toBe(true);
|
|
172
|
+
expect(completeSimpleMock).toHaveBeenCalledTimes(1);
|
|
173
|
+
expect(completeSimpleMock.mock.calls[0]?.[0]?.id).toBe("provider/text-light");
|
|
174
|
+
});
|
|
92
175
|
});
|
|
93
176
|
|
|
94
177
|
|
package/src/extension.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
3
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { Box, Text } from "@earendil-works/pi-tui";
|
|
5
6
|
import { completeSimple, type ThinkingLevel } from "@earendil-works/pi-ai";
|
|
@@ -31,6 +32,8 @@ export interface AdvisorConfig {
|
|
|
31
32
|
checkins: "mid-hour" | "off";
|
|
32
33
|
/** Minutes between check-ins; bounded and cheap-gated by recent activity. */
|
|
33
34
|
checkinIntervalMinutes: number;
|
|
35
|
+
/** Optional start time (ms since epoch) for the active check-in stream. */
|
|
36
|
+
checkinStartedAt?: number;
|
|
34
37
|
/** Optional model override. Auto-detects SOTA (gpt-5.5, claude-opus-4-6…) if unset */
|
|
35
38
|
model?: string;
|
|
36
39
|
}
|
|
@@ -47,16 +50,14 @@ const STATE_PATH = featureFile("advisor", "state.json");
|
|
|
47
50
|
const CACHE_PATH = featureFile("advisor", "cache.json");
|
|
48
51
|
const CURRENT_PATH = featureFile("advisor", "current.md");
|
|
49
52
|
const HISTORY_PATH = featureFile("advisor", "history.jsonl");
|
|
53
|
+
const ORCHESTRATION_DIR = join(homedir(), ".pi", "agent", "fiale-plus", "orchestration");
|
|
50
54
|
|
|
51
55
|
const MAX_CACHE = 64;
|
|
52
56
|
const MAX_NOTES = 12;
|
|
53
57
|
const MAX_FILES = 8;
|
|
54
58
|
const MAX_ERRORS = 5;
|
|
55
|
-
const CHECKIN_POLL_MS = 5 * 60_000;
|
|
56
59
|
const MIN_CHECKIN_INTERVAL_MINUTES = 10;
|
|
57
60
|
const MAX_CHECKIN_INTERVAL_MINUTES = 240;
|
|
58
|
-
const checkinTimers = new Map<string, NodeJS.Timeout>();
|
|
59
|
-
const checkinStartedAt = new Map<string, number>();
|
|
60
61
|
const checkinLocks = new Set<string>();
|
|
61
62
|
|
|
62
63
|
// ── SOTA models (ordered by preference) ───────────────────────────────────
|
|
@@ -85,6 +86,8 @@ interface SessionState {
|
|
|
85
86
|
lastAt?: string;
|
|
86
87
|
lastTurn?: number;
|
|
87
88
|
lastReason?: string;
|
|
89
|
+
queued?: boolean;
|
|
90
|
+
queuedReason?: string;
|
|
88
91
|
};
|
|
89
92
|
}
|
|
90
93
|
|
|
@@ -99,7 +102,7 @@ function defaultState(): SessionState {
|
|
|
99
102
|
cacheHits: 0,
|
|
100
103
|
followUp: "",
|
|
101
104
|
router: {},
|
|
102
|
-
checkin: {},
|
|
105
|
+
checkin: { queued: false },
|
|
103
106
|
};
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -118,11 +121,13 @@ function writeJson(path: string, v: unknown) {
|
|
|
118
121
|
|
|
119
122
|
export function normalizeAdvisorConfig(raw: Partial<AdvisorConfig> = {}): AdvisorConfig {
|
|
120
123
|
const interval = Number(raw.checkinIntervalMinutes ?? DEFAULT_CONFIG.checkinIntervalMinutes);
|
|
124
|
+
const startedAt = Number(raw.checkinStartedAt);
|
|
121
125
|
return {
|
|
122
126
|
mode: (raw.mode === "manual" || raw.mode === "off") ? raw.mode : "auto",
|
|
123
127
|
review: (raw.review === "strict" || raw.review === "off") ? raw.review : "light",
|
|
124
128
|
checkins: raw.checkins === "mid-hour" ? "mid-hour" : DEFAULT_CONFIG.checkins,
|
|
125
129
|
checkinIntervalMinutes: Math.min(MAX_CHECKIN_INTERVAL_MINUTES, Math.max(MIN_CHECKIN_INTERVAL_MINUTES, Number.isFinite(interval) ? Math.round(interval) : DEFAULT_CONFIG.checkinIntervalMinutes)),
|
|
130
|
+
checkinStartedAt: Number.isFinite(startedAt) ? startedAt : undefined,
|
|
126
131
|
model: raw.model || undefined,
|
|
127
132
|
};
|
|
128
133
|
}
|
|
@@ -154,6 +159,8 @@ function loadState(): SessionState {
|
|
|
154
159
|
lastAt: raw.checkin?.lastAt,
|
|
155
160
|
lastTurn: raw.checkin?.lastTurn,
|
|
156
161
|
lastReason: raw.checkin?.lastReason,
|
|
162
|
+
queued: Boolean(raw.checkin?.queued),
|
|
163
|
+
queuedReason: raw.checkin?.queuedReason,
|
|
157
164
|
},
|
|
158
165
|
};
|
|
159
166
|
}
|
|
@@ -299,6 +306,40 @@ function sessionKey(ctx: any): string {
|
|
|
299
306
|
return basename(String(sessionFile)).replace(/\.[^.]+$/, "");
|
|
300
307
|
}
|
|
301
308
|
|
|
309
|
+
type OrchestrationSnapshot = {
|
|
310
|
+
goal: string;
|
|
311
|
+
loop: { enabled?: boolean; interval?: string; instruction?: string };
|
|
312
|
+
research: { instruction?: string; interval?: string; cycles?: number; doneAttempts?: number; lastResult?: string };
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
function readOrchestrationSnapshot(ctx: any): OrchestrationSnapshot {
|
|
316
|
+
const dir = join(ORCHESTRATION_DIR, sessionKey(ctx));
|
|
317
|
+
return {
|
|
318
|
+
goal: readText(join(dir, "goal.md")).trim(),
|
|
319
|
+
loop: readJson(join(dir, "loop.json"), {}),
|
|
320
|
+
research: readJson(join(dir, "autoresearch.json"), {}),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function orchestrationSnapshotText(ctx: any): string {
|
|
325
|
+
const snapshot = readOrchestrationSnapshot(ctx);
|
|
326
|
+
const goalActive = Boolean(snapshot.goal);
|
|
327
|
+
const loopActive = Boolean(snapshot.loop.enabled && snapshot.loop.instruction);
|
|
328
|
+
const researchActive = Boolean(snapshot.research.instruction);
|
|
329
|
+
const status = goalActive && !loopActive && !researchActive
|
|
330
|
+
? "setup gap — goal exists but no active autoresearch/loop progression"
|
|
331
|
+
: goalActive
|
|
332
|
+
? "progression configured"
|
|
333
|
+
: "no active goal";
|
|
334
|
+
return [
|
|
335
|
+
"Orchestration:",
|
|
336
|
+
`- Goal: ${goalActive ? `active — ${truncate(snapshot.goal, 140)}` : "off"}`,
|
|
337
|
+
`- Autoresearch: ${researchActive ? `active — cycles=${snapshot.research.cycles ?? 0}, doneAttempts=${snapshot.research.doneAttempts ?? 0}${snapshot.research.lastResult ? `, last=${snapshot.research.lastResult}` : ""}` : "off"}`,
|
|
338
|
+
`- Loop: ${loopActive ? `active every ${snapshot.loop.interval || "?"} — ${truncate(snapshot.loop.instruction || "", 120)}` : "off"}`,
|
|
339
|
+
`- Status: ${status}`,
|
|
340
|
+
].join("\n");
|
|
341
|
+
}
|
|
342
|
+
|
|
302
343
|
function setPiRogueStatus(ctx: any, config = loadConfig(), state = loadState()): void {
|
|
303
344
|
const normalized = normalizeAdvisorConfig(config);
|
|
304
345
|
const checkin = normalized.checkins === "off" ? "checkins off" : `checkins ${normalized.checkinIntervalMinutes}m`;
|
|
@@ -310,68 +351,101 @@ export function shouldRunCheckin(config: AdvisorConfig, state: SessionState, now
|
|
|
310
351
|
const normalized = normalizeAdvisorConfig(config);
|
|
311
352
|
if (normalized.mode === "off" || normalized.mode === "manual") return null;
|
|
312
353
|
if (normalized.checkins === "off") return null;
|
|
354
|
+
if (state.checkin.queued) {
|
|
355
|
+
return state.checkin.queuedReason || "Queued mid-session check-in.";
|
|
356
|
+
}
|
|
313
357
|
if (!state.lastTask && state.notes.length === 0) return null;
|
|
314
358
|
const lastTurn = state.checkin.lastTurn ?? 0;
|
|
315
359
|
if (state.turns <= lastTurn) return null;
|
|
316
360
|
const lastAt = state.checkin.lastAt ? Date.parse(state.checkin.lastAt) : 0;
|
|
317
361
|
const intervalMs = normalized.checkinIntervalMinutes * 60_000;
|
|
318
|
-
const
|
|
362
|
+
const streamStartedAt = Number.isFinite(normalized.checkinStartedAt ?? NaN) ? (normalized.checkinStartedAt as number) : startedAt;
|
|
363
|
+
const since = Math.max(lastAt, streamStartedAt);
|
|
319
364
|
if (since && now - since < intervalMs) return null;
|
|
320
365
|
return `mid-hour check-in after ${state.turns - lastTurn} new turn(s)`;
|
|
321
366
|
}
|
|
322
367
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
368
|
+
|
|
369
|
+
function isAdvisorIdle(ctx: any): boolean {
|
|
370
|
+
try {
|
|
371
|
+
return typeof ctx?.isIdle === "function" ? ctx.isIdle() : true;
|
|
372
|
+
} catch {
|
|
373
|
+
return true;
|
|
328
374
|
}
|
|
329
375
|
}
|
|
330
376
|
|
|
377
|
+
export async function requestAdvisorLoopCheckin(pi: ExtensionAPI, ctx: any, source = "loop_tick"): Promise<boolean> {
|
|
378
|
+
return maybeAdvisorCheckin(pi, ctx, source);
|
|
379
|
+
}
|
|
380
|
+
|
|
331
381
|
async function maybeAdvisorCheckin(pi: ExtensionAPI, ctx: any, source: string): Promise<boolean> {
|
|
332
382
|
const key = sessionKey(ctx);
|
|
333
383
|
if (checkinLocks.has(key)) return false;
|
|
334
384
|
|
|
335
385
|
const config = loadConfig();
|
|
336
386
|
const state = loadState();
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
387
|
+
const reason = shouldRunCheckin(config, state, Date.now(), Date.now());
|
|
388
|
+
if (!reason) {
|
|
389
|
+
if (state.checkin.queued) {
|
|
390
|
+
state.checkin.queued = false;
|
|
391
|
+
saveState(state);
|
|
392
|
+
setPiRogueStatus(ctx, config, state);
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!isAdvisorIdle(ctx)) {
|
|
398
|
+
if (!state.checkin.queued) {
|
|
399
|
+
state.checkin.queued = true;
|
|
400
|
+
state.checkin.queuedReason = reason;
|
|
401
|
+
saveState(state);
|
|
402
|
+
setPiRogueStatus(ctx, config, state);
|
|
403
|
+
}
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
341
406
|
|
|
342
407
|
checkinLocks.add(key);
|
|
343
408
|
try {
|
|
344
|
-
const
|
|
345
|
-
pi,
|
|
409
|
+
const completed = await completeWithHigherAdvisorModel(
|
|
346
410
|
ctx,
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
411
|
+
config,
|
|
412
|
+
[
|
|
413
|
+
`Mid-session check-in (${source}): briefly assess whether the current session is on track, stuck, or missing a higher-leverage next step.`,
|
|
414
|
+
orchestrationSnapshotText(ctx),
|
|
415
|
+
"If a goal exists but autoresearch/loop progression is off, call out the setup gap. Do not start or change orchestration; return one concrete nudge.",
|
|
416
|
+
].join("\n\n"),
|
|
417
|
+
[
|
|
418
|
+
{
|
|
419
|
+
role: "user",
|
|
420
|
+
content: [
|
|
421
|
+
`Mid-session check-in (${source}): briefly assess whether the current session is on track, stuck, or missing a higher-leverage next step.`,
|
|
422
|
+
orchestrationSnapshotText(ctx),
|
|
423
|
+
"If a goal exists but autoresearch/loop progression is off, call out the setup gap. Do not start or change orchestration; return one concrete nudge.",
|
|
424
|
+
].join("\n\n"),
|
|
425
|
+
timestamp: new Date().toISOString(),
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
{ maxTokens: 600, reasoning: "medium" as ThinkingLevel },
|
|
350
429
|
);
|
|
351
|
-
if (
|
|
430
|
+
if (!completed) return false;
|
|
352
431
|
|
|
353
432
|
const next = loadState();
|
|
354
|
-
next.checkin = {
|
|
433
|
+
next.checkin = {
|
|
434
|
+
lastAt: new Date().toISOString(),
|
|
435
|
+
lastTurn: next.turns,
|
|
436
|
+
lastReason: reason,
|
|
437
|
+
queued: false,
|
|
438
|
+
};
|
|
355
439
|
saveState(next);
|
|
356
440
|
setPiRogueStatus(ctx, config, next);
|
|
357
|
-
sendAdvisorHint(pi, "review", "mid-hour check-in",
|
|
441
|
+
sendAdvisorHint(pi, "review", "mid-hour check-in", completed.text, [completed.text]);
|
|
358
442
|
return true;
|
|
359
443
|
} finally {
|
|
360
444
|
checkinLocks.delete(key);
|
|
361
445
|
}
|
|
362
446
|
}
|
|
363
447
|
|
|
364
|
-
function
|
|
365
|
-
const key = sessionKey(ctx);
|
|
366
|
-
stopCheckinTimer(key);
|
|
367
|
-
checkinStartedAt.set(key, Date.now());
|
|
368
|
-
setPiRogueStatus(ctx);
|
|
369
|
-
const config = loadConfig();
|
|
370
|
-
if (config.mode === "off" || config.mode === "manual" || config.checkins === "off") return;
|
|
371
|
-
checkinTimers.set(key, setInterval(() => { void maybeAdvisorCheckin(pi, ctx, "timer"); }, CHECKIN_POLL_MS));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function piRogueCockpitText(config: AdvisorConfig, state: SessionState, currentNote: string): string {
|
|
448
|
+
function piRogueCockpitText(config: AdvisorConfig, state: SessionState, currentNote: string, orchestration = ""): string {
|
|
375
449
|
const normalized = normalizeAdvisorConfig(config);
|
|
376
450
|
return [
|
|
377
451
|
"☠︎ Pi-Rogue cockpit",
|
|
@@ -379,35 +453,95 @@ function piRogueCockpitText(config: AdvisorConfig, state: SessionState, currentN
|
|
|
379
453
|
`Mode: ${normalized.mode} | Review: ${normalized.review} | Check-ins: ${normalized.checkins === "off" ? "off" : `${normalized.checkinIntervalMinutes}m`}`,
|
|
380
454
|
`Turns: ${state.turns} | Advisor calls: ${state.advisorCalls} | Cache hits: ${state.cacheHits}`,
|
|
381
455
|
state.checkin.lastAt ? `Last check-in: ${new Date(state.checkin.lastAt).toLocaleString()} (${state.checkin.lastReason || "mid-hour"})` : "Last check-in: never",
|
|
456
|
+
state.checkin.queued ? `Queued check-in: ${state.checkin.queuedReason || "due"}` : "",
|
|
457
|
+
orchestration,
|
|
382
458
|
"",
|
|
383
|
-
"Commands: /advisor status · /
|
|
384
|
-
].join("\n");
|
|
385
|
-
}
|
|
459
|
+
"Commands: /advisor status · /goal · /loop status · /autoresearch status",
|
|
460
|
+
].filter(Boolean).join("\n");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ── Model resolution (higher/advanced first, then optional regular fallback) ──
|
|
464
|
+
type ResolvedAdvisorModel = { model: any; auth: any; label: string; fallback?: boolean };
|
|
465
|
+
type ModelResolutionOptions = { allowRegularFallback?: boolean };
|
|
466
|
+
|
|
467
|
+
export async function resolveModelCandidates(ctx: any, config: AdvisorConfig, options: ModelResolutionOptions = {}): Promise<ResolvedAdvisorModel[]> {
|
|
468
|
+
const { allowRegularFallback = true } = options;
|
|
469
|
+
const candidates: ResolvedAdvisorModel[] = [];
|
|
470
|
+
const seen = new Set<string>();
|
|
471
|
+
const add = async (found: any, label: string, fallback = false) => {
|
|
472
|
+
if (!found) return;
|
|
473
|
+
const key = String(found.id || label);
|
|
474
|
+
if (seen.has(key)) return;
|
|
475
|
+
const auth = await ctx.modelRegistry?.getApiKeyAndHeaders(found);
|
|
476
|
+
if (auth?.ok && auth.apiKey) {
|
|
477
|
+
seen.add(key);
|
|
478
|
+
candidates.push({ model: found, auth, label, fallback });
|
|
479
|
+
}
|
|
480
|
+
};
|
|
386
481
|
|
|
387
|
-
//
|
|
388
|
-
async function resolveModel(ctx: any, config: AdvisorConfig): Promise<{ model: any; auth: any; label: string } | null> {
|
|
389
|
-
// Try user's configured model first
|
|
482
|
+
// Try configured higher/advanced advisor model first.
|
|
390
483
|
if (config.model && config.model.includes("/")) {
|
|
391
484
|
const [p, ...m] = config.model.split("/");
|
|
392
|
-
|
|
393
|
-
if (found) {
|
|
394
|
-
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(found);
|
|
395
|
-
if (auth?.ok && auth.apiKey) return { model: found, auth, label: p + "/" + m.join("/") };
|
|
396
|
-
}
|
|
485
|
+
await add(ctx.modelRegistry?.find(p, m.join("/")), p + "/" + m.join("/"));
|
|
397
486
|
}
|
|
398
|
-
|
|
487
|
+
|
|
488
|
+
// Fall through SOTA chain.
|
|
399
489
|
for (const sota of SOTA_CHAIN) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
490
|
+
await add(ctx.modelRegistry?.find(sota.provider, sota.model), sota.label);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (allowRegularFallback) {
|
|
494
|
+
// Final fallback: any configured text model, i.e. the regular session-capable model.
|
|
495
|
+
for (const m of (ctx.modelRegistry?.getAvailable() ?? []).filter((model: any) => model.input?.includes?.("text"))) {
|
|
496
|
+
await add(m, m.id || "regular model", true);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return candidates;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function resolveModel(ctx: any, config: AdvisorConfig): Promise<ResolvedAdvisorModel | null> {
|
|
504
|
+
return (await resolveModelCandidates(ctx, config))[0] ?? null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export async function completeWithModelFallback(ctx: any, config: AdvisorConfig, systemPrompt: string, messages: any[], options: { maxTokens: number; reasoning: ThinkingLevel }): Promise<{ text: string; model: string; fallback?: boolean } | null> {
|
|
508
|
+
let lastError = "";
|
|
509
|
+
for (const resolved of await resolveModelCandidates(ctx, config)) {
|
|
510
|
+
try {
|
|
511
|
+
const resp = await completeSimple(resolved.model, { systemPrompt, messages }, {
|
|
512
|
+
apiKey: resolved.auth.apiKey,
|
|
513
|
+
headers: resolved.auth.headers,
|
|
514
|
+
maxTokens: options.maxTokens,
|
|
515
|
+
reasoning: options.reasoning,
|
|
516
|
+
});
|
|
517
|
+
return { text: responseText(resp) || "(empty)", model: resolved.label, fallback: resolved.fallback };
|
|
518
|
+
} catch (error) {
|
|
519
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
520
|
+
}
|
|
404
521
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
522
|
+
return lastError ? { text: `No advisor/check-in model completed successfully (${lastError}).`, model: "none" } : null;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export async function completeWithHigherAdvisorModel(
|
|
526
|
+
ctx: any,
|
|
527
|
+
config: AdvisorConfig,
|
|
528
|
+
systemPrompt: string,
|
|
529
|
+
messages: any[],
|
|
530
|
+
options: { maxTokens: number; reasoning: ThinkingLevel; allowRegularFallback?: boolean },
|
|
531
|
+
): Promise<{ text: string; model: string } | null> {
|
|
532
|
+
const { allowRegularFallback = true } = options;
|
|
533
|
+
for (const resolved of await resolveModelCandidates(ctx, config, { allowRegularFallback })) {
|
|
534
|
+
try {
|
|
535
|
+
const resp = await completeSimple(resolved.model, { systemPrompt, messages }, {
|
|
536
|
+
apiKey: resolved.auth.apiKey,
|
|
537
|
+
headers: resolved.auth.headers,
|
|
538
|
+
maxTokens: options.maxTokens,
|
|
539
|
+
reasoning: options.reasoning,
|
|
540
|
+
});
|
|
541
|
+
return { text: responseText(resp) || "(empty)", model: resolved.label };
|
|
542
|
+
} catch {
|
|
543
|
+
// keep trying remaining candidates
|
|
544
|
+
}
|
|
411
545
|
}
|
|
412
546
|
return null;
|
|
413
547
|
}
|
|
@@ -421,22 +555,17 @@ async function askAdvisor(pi: ExtensionAPI, ctx: any, question: string, scope: s
|
|
|
421
555
|
const cache = loadCache();
|
|
422
556
|
if (cache[ck]) { state.cacheHits++; saveState(state); return { text: cache[ck], cached: true }; }
|
|
423
557
|
|
|
424
|
-
const resolved = await resolveModel(ctx, config);
|
|
425
|
-
if (!resolved) return { text: "No model available. Install one via pi config.", error: "no_model" };
|
|
426
|
-
|
|
427
558
|
const msgs = [
|
|
428
559
|
{ role: "user", content: [ `Question: ${question}`, scope ? `Scope: ${scope}` : "", includeWork && brief(state) ? `Session:\n${brief(state)}` : "" ].filter(Boolean).join("\n"), timestamp: new Date().toISOString() },
|
|
429
560
|
] as any[];
|
|
430
561
|
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
435
|
-
const text = responseText(resp) || "(empty)";
|
|
562
|
+
const completed = await completeWithModelFallback(ctx, config, ADVISOR_SYSTEM, msgs, { maxTokens: 600, reasoning: "medium" as ThinkingLevel });
|
|
563
|
+
if (!completed) return { text: "No model available. Install one via pi config.", error: "no_model" };
|
|
564
|
+
const text = completed.text;
|
|
436
565
|
if (text && text !== "(empty)") { cache[ck] = text; saveCache(cache); }
|
|
437
566
|
state.advisorCalls++;
|
|
438
567
|
saveState(state);
|
|
439
|
-
return { text, model:
|
|
568
|
+
return { text, model: completed.model, fallback: completed.fallback };
|
|
440
569
|
}
|
|
441
570
|
|
|
442
571
|
async function doReview(pi: ExtensionAPI, ctx: any, trigger: string, delta: string, meta: { fileChanged: boolean; failed: boolean; isAgentEnd: boolean }) {
|
|
@@ -492,8 +621,6 @@ async function doReview(pi: ExtensionAPI, ctx: any, trigger: string, delta: stri
|
|
|
492
621
|
const cache = loadCache();
|
|
493
622
|
if (cache[rk]) return; // already reviewed this
|
|
494
623
|
|
|
495
|
-
const resolved = await resolveModel(ctx, config);
|
|
496
|
-
if (!resolved) return;
|
|
497
624
|
const msgs = [
|
|
498
625
|
{ role: "user", content: [
|
|
499
626
|
`Trigger: ${trigger}`,
|
|
@@ -504,11 +631,8 @@ async function doReview(pi: ExtensionAPI, ctx: any, trigger: string, delta: stri
|
|
|
504
631
|
`Brief:\n${b}`,
|
|
505
632
|
].join("\n"), timestamp: new Date().toISOString() },
|
|
506
633
|
] as any[];
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
maxTokens: 400, reasoning: "low" as ThinkingLevel,
|
|
510
|
-
});
|
|
511
|
-
const raw = responseText(resp);
|
|
634
|
+
const completed = await completeWithModelFallback(ctx, config, REVIEW_SYSTEM, msgs, { maxTokens: 400, reasoning: "low" as ThinkingLevel });
|
|
635
|
+
const raw = completed?.text;
|
|
512
636
|
if (!raw) return;
|
|
513
637
|
|
|
514
638
|
cache[rk] = raw;
|
|
@@ -544,13 +668,15 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
544
668
|
}
|
|
545
669
|
|
|
546
670
|
pi.on("session_start", (_event, ctx) => {
|
|
547
|
-
|
|
671
|
+
const key = sessionKey(ctx);
|
|
672
|
+
checkinLocks.delete(key);
|
|
673
|
+
setPiRogueStatus(ctx, loadConfig(), loadState());
|
|
674
|
+
// No timer is owned by advisor itself anymore; check-ins are triggered
|
|
675
|
+
// from active goal/loop/autoresearch flow progression.
|
|
548
676
|
});
|
|
549
677
|
|
|
550
678
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
551
679
|
const key = sessionKey(ctx);
|
|
552
|
-
stopCheckinTimer(key);
|
|
553
|
-
checkinStartedAt.delete(key);
|
|
554
680
|
checkinLocks.delete(key);
|
|
555
681
|
ctx.ui.setStatus("pi-rogue", undefined);
|
|
556
682
|
});
|
|
@@ -654,8 +780,10 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
654
780
|
// ── Post-review (agent_end) ────────────────────────────────────────────
|
|
655
781
|
pi.on("agent_end", async (event: any, ctx: any) => {
|
|
656
782
|
const cfg = loadConfig();
|
|
657
|
-
if (cfg.mode === "off"
|
|
658
|
-
|
|
783
|
+
if (cfg.mode === "off") return;
|
|
784
|
+
void maybeAdvisorCheckin(pi, ctx, "agent_end");
|
|
785
|
+
|
|
786
|
+
if (cfg.review === "off") return;
|
|
659
787
|
const msgs = (event.messages || []).filter((m: any) => m.role === "assistant" || m.role === "toolResult");
|
|
660
788
|
const last = msgs[msgs.length - 1];
|
|
661
789
|
const delta = contentText(last?.content) || "(none)";
|
|
@@ -666,7 +794,7 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
666
794
|
|
|
667
795
|
// ── /pi-rogue cockpit ──────────────────────────────────────────────────
|
|
668
796
|
pi.registerCommand("pi-rogue", {
|
|
669
|
-
description: "Show Pi-Rogue cockpit: advisor
|
|
797
|
+
description: "Show Pi-Rogue cockpit: advisor and orchestration command pointers",
|
|
670
798
|
getArgumentCompletions: (prefix: string) => piRogueArgumentCompletions(prefix),
|
|
671
799
|
handler: async (args, ctx) => {
|
|
672
800
|
const cfg = loadConfig();
|
|
@@ -675,7 +803,7 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
675
803
|
setPiRogueStatus(ctx, cfg, state);
|
|
676
804
|
|
|
677
805
|
if (!arg || arg === "status" || arg === "help") {
|
|
678
|
-
ctx.ui.notify(piRogueCockpitText(cfg, state, readText(CURRENT_PATH).trim()), "info");
|
|
806
|
+
ctx.ui.notify(piRogueCockpitText(cfg, state, readText(CURRENT_PATH).trim(), orchestrationSnapshotText(ctx)), "info");
|
|
679
807
|
return;
|
|
680
808
|
}
|
|
681
809
|
|
|
@@ -684,8 +812,9 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
684
812
|
"Advisor surface:",
|
|
685
813
|
" /advisor status",
|
|
686
814
|
" /advisor config",
|
|
687
|
-
" /advisor checkins on|off|<minutes>",
|
|
688
815
|
" /advisor <question>",
|
|
816
|
+
"",
|
|
817
|
+
"Check-ins are orchestration-managed: start /loop to activate them.",
|
|
689
818
|
].join("\n"), "info");
|
|
690
819
|
return;
|
|
691
820
|
}
|
|
@@ -703,13 +832,14 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
703
832
|
|
|
704
833
|
if (arg.startsWith("checkins")) {
|
|
705
834
|
ctx.ui.notify([
|
|
706
|
-
`
|
|
707
|
-
"
|
|
835
|
+
`Check-ins: ${cfg.checkins === "off" ? "off" : `${cfg.checkinIntervalMinutes}m`}`,
|
|
836
|
+
"Managed by orchestration: /loop activates them; stopping the loop disables them.",
|
|
837
|
+
orchestrationSnapshotText(ctx),
|
|
708
838
|
].join("\n"), "info");
|
|
709
839
|
return;
|
|
710
840
|
}
|
|
711
841
|
|
|
712
|
-
ctx.ui.notify(piRogueCockpitText(cfg, state, readText(CURRENT_PATH).trim()), "info");
|
|
842
|
+
ctx.ui.notify(piRogueCockpitText(cfg, state, readText(CURRENT_PATH).trim(), orchestrationSnapshotText(ctx)), "info");
|
|
713
843
|
},
|
|
714
844
|
});
|
|
715
845
|
|
|
@@ -731,22 +861,48 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
731
861
|
note ? `🧭 ${truncate(note, 200)}` : "",
|
|
732
862
|
route ? `Router: ${summarizeRoute(route)}${route.safety ? " · safety" : ""}` : "",
|
|
733
863
|
"",
|
|
734
|
-
`Mode: ${cfg.mode} | Review: ${cfg.review} | Check-ins: ${cfg.checkins === "off" ? "off" : `${cfg.checkinIntervalMinutes}m`} | Model: ${resolved?.label || cfg.model || "auto"}`,
|
|
864
|
+
`Mode: ${cfg.mode} | Review: ${cfg.review} | Check-ins: ${cfg.checkins === "off" ? "off" : `${cfg.checkinIntervalMinutes}m`} (orchestration-managed) | Model: ${resolved?.label || cfg.model || "auto"}`,
|
|
735
865
|
`Turns: ${state.turns} | Calls: ${state.advisorCalls} | Cache hits: ${state.cacheHits}`,
|
|
736
866
|
state.checkin.lastAt ? `Last check-in: ${new Date(state.checkin.lastAt).toLocaleString()} (${state.checkin.lastReason || "mid-hour"})` : "Last check-in: never",
|
|
867
|
+
state.checkin.queued ? `Queued check-in: ${state.checkin.queuedReason || "due"}` : "",
|
|
868
|
+
orchestrationSnapshotText(ctx),
|
|
737
869
|
"",
|
|
738
|
-
"Commands: /advisor on|off | /advisor status | /advisor
|
|
870
|
+
"Commands: /advisor on|off | /advisor status | /advisor config | <question>",
|
|
739
871
|
"Tip: SOTA models auto-detected. No config needed.",
|
|
740
872
|
].filter(Boolean).join("\n"), "info");
|
|
741
873
|
return;
|
|
742
874
|
}
|
|
743
875
|
|
|
744
|
-
if (cmd === "on" && cfg.mode === "off") {
|
|
745
|
-
|
|
876
|
+
if (cmd === "on" && cfg.mode === "off") {
|
|
877
|
+
const next = { ...cfg, mode: "auto" as const };
|
|
878
|
+
saveConfig(next);
|
|
879
|
+
setPiRogueStatus(ctx, next, state);
|
|
880
|
+
ctx.ui.notify("Advisor enabled (auto mode).", "info");
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (cmd === "off") {
|
|
884
|
+
const next = { ...cfg, mode: "off" as const };
|
|
885
|
+
saveConfig(next);
|
|
886
|
+
setPiRogueStatus(ctx, next, state);
|
|
887
|
+
ctx.ui.notify("Advisor disabled.", "info");
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
746
890
|
if (cmd === "mode") {
|
|
747
891
|
const v = rest[0];
|
|
748
|
-
if (v === "auto" || v === "manual") {
|
|
749
|
-
|
|
892
|
+
if (v === "auto" || v === "manual") {
|
|
893
|
+
const next: AdvisorConfig = { ...cfg, mode: v };
|
|
894
|
+
saveConfig(next);
|
|
895
|
+
setPiRogueStatus(ctx, next, state);
|
|
896
|
+
ctx.ui.notify(`Mode set to ${v}.`, "info");
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (v === "off") {
|
|
900
|
+
const next = { ...cfg, mode: "off" as const };
|
|
901
|
+
saveConfig(next);
|
|
902
|
+
setPiRogueStatus(ctx, next, state);
|
|
903
|
+
ctx.ui.notify("Advisor disabled.", "info");
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
750
906
|
ctx.ui.notify("Usage: /advisor mode auto|manual|off", "error");
|
|
751
907
|
return;
|
|
752
908
|
}
|
|
@@ -769,12 +925,12 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
769
925
|
}
|
|
770
926
|
if (cmd === "config") {
|
|
771
927
|
ctx.ui.notify([
|
|
772
|
-
"Advisor config (
|
|
928
|
+
"Advisor config (check-ins are orchestration-managed):",
|
|
773
929
|
` mode: "${cfg.mode}" — auto (preflight+post+cache) | manual | off`,
|
|
774
930
|
` review: "${cfg.review}" — light (changes/errors) | strict (every 3) | off`,
|
|
775
|
-
` checkins: "${cfg.checkins}" —
|
|
931
|
+
` checkins: "${cfg.checkins}" — set by active /loop lifecycle`,
|
|
776
932
|
` checkinIntervalMinutes: ${cfg.checkinIntervalMinutes}`,
|
|
777
|
-
` model: "${cfg.model || "auto"}" — optional override`,
|
|
933
|
+
` model: "${cfg.model || "auto"}" — optional override for higher/advanced advisor model`,
|
|
778
934
|
"",
|
|
779
935
|
"Router logs: evals/advisor-router.jsonl",
|
|
780
936
|
"Run /advisor <question> for immediate advice.",
|
|
@@ -788,31 +944,12 @@ export function registerAdvisor(pi: ExtensionAPI): void {
|
|
|
788
944
|
return;
|
|
789
945
|
}
|
|
790
946
|
if (cmd === "checkins" || cmd === "checkin") {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
ctx.ui.notify("Advisor mid-hour check-ins disabled.", "info");
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
if (v === "on" || v === "mid-hour") {
|
|
801
|
-
const next = { ...cfg, checkins: "mid-hour" as const };
|
|
802
|
-
saveConfig(next);
|
|
803
|
-
syncCheckinTimer(pi, ctx);
|
|
804
|
-
ctx.ui.notify(`Advisor mid-hour check-ins enabled every ${next.checkinIntervalMinutes}m.`, "info");
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
const minutes = Number(v);
|
|
808
|
-
if (Number.isFinite(minutes)) {
|
|
809
|
-
const next = normalizeAdvisorConfig({ ...cfg, checkins: "mid-hour", checkinIntervalMinutes: minutes });
|
|
810
|
-
saveConfig(next);
|
|
811
|
-
syncCheckinTimer(pi, ctx);
|
|
812
|
-
ctx.ui.notify(`Advisor mid-hour check-ins every ${next.checkinIntervalMinutes}m.`, "info");
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
ctx.ui.notify("Usage: /advisor checkins on|off|<minutes>", "error");
|
|
947
|
+
ctx.ui.notify([
|
|
948
|
+
"Advisor check-ins are orchestration-managed now.",
|
|
949
|
+
`Current: ${cfg.checkins === "off" ? "off" : `${cfg.checkinIntervalMinutes}m`}`,
|
|
950
|
+
"Create or resume /loop to activate scheduled higher-model check-ins; stop the loop to disable them.",
|
|
951
|
+
orchestrationSnapshotText(ctx),
|
|
952
|
+
].join("\n"), "info");
|
|
816
953
|
return;
|
|
817
954
|
}
|
|
818
955
|
|
package/src/index.ts
CHANGED