@fiale-plus/pi-rogue-advisor 0.1.9 → 0.1.10
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/package.json +1 -1
- package/src/extension.test.ts +37 -1
- package/src/extension.ts +44 -20
package/package.json
CHANGED
package/src/extension.test.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { completeSimple } from "@earendil-works/pi-ai";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildAdvisorCheckinPrompt,
|
|
5
|
+
completeWithHigherAdvisorModel,
|
|
6
|
+
completeWithModelFallback,
|
|
7
|
+
contentText,
|
|
8
|
+
normalizeAdvisorConfig,
|
|
9
|
+
shouldRunCheckin,
|
|
10
|
+
type AdvisorConfig,
|
|
11
|
+
} from "./extension.js";
|
|
4
12
|
|
|
5
13
|
vi.mock("@earendil-works/pi-ai", async () => {
|
|
6
14
|
const actual = await vi.importActual<typeof import("@earendil-works/pi-ai")>("@earendil-works/pi-ai");
|
|
@@ -72,6 +80,14 @@ describe("AdvisorConfig", () => {
|
|
|
72
80
|
});
|
|
73
81
|
});
|
|
74
82
|
|
|
83
|
+
describe("advisor message extraction", () => {
|
|
84
|
+
it("extracts nested structured content without object string leakage", () => {
|
|
85
|
+
expect(contentText({ content: [{ type: "text", text: "done" }] })).toBe("done");
|
|
86
|
+
expect(contentText([{ type: "toolResult", content: [{ type: "text", text: "ok" }] }])).toBe("ok");
|
|
87
|
+
expect(contentText({ arbitrary: "shape" })).toBe("");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
75
91
|
describe("mid-hour check-ins", () => {
|
|
76
92
|
it("does not run immediately after session start", () => {
|
|
77
93
|
const cfg = normalizeAdvisorConfig({ checkins: "mid-hour", checkinIntervalMinutes: 30 });
|
|
@@ -110,6 +126,26 @@ describe("mid-hour check-ins", () => {
|
|
|
110
126
|
})),
|
|
111
127
|
).toBe("queued mid-session check-in");
|
|
112
128
|
});
|
|
129
|
+
|
|
130
|
+
it("keeps check-in guidance anchored to the active goal", () => {
|
|
131
|
+
const prompt = buildAdvisorCheckinPrompt(
|
|
132
|
+
"loop_tick",
|
|
133
|
+
[
|
|
134
|
+
"Orchestration:",
|
|
135
|
+
"- Goal: active — Autoresearch: solve advisor weaknesses",
|
|
136
|
+
"- Autoresearch: active — solve advisor weaknesses; cycles=1, doneAttempts=0",
|
|
137
|
+
"- Loop: active every 5m — Run one autoresearch cycle toward the active goal.",
|
|
138
|
+
].join("\n"),
|
|
139
|
+
"Task: solve advisor weaknesses\nNotes:\n- found shallow mid-hour feedback",
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(prompt).toContain("alignment reviewer");
|
|
143
|
+
expect(prompt).toContain("Do not create a new task");
|
|
144
|
+
expect(prompt).toContain("preserve its research question");
|
|
145
|
+
expect(prompt).toContain("solving the named weakness");
|
|
146
|
+
expect(prompt).toContain("Nudge: <one concrete next action that continues the active goal>");
|
|
147
|
+
expect(prompt).toContain("found shallow mid-hour feedback");
|
|
148
|
+
});
|
|
113
149
|
});
|
|
114
150
|
|
|
115
151
|
|
package/src/extension.ts
CHANGED
|
@@ -267,9 +267,16 @@ function renderAdvisorHint(message: any, options: { expanded?: boolean }, theme:
|
|
|
267
267
|
return box;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
/** Extract readable text from message content (handles
|
|
271
|
-
function contentText(content: unknown): string {
|
|
270
|
+
/** Extract readable text from message content (handles strings, blocks, and nested message payloads). */
|
|
271
|
+
export function contentText(content: unknown): string {
|
|
272
272
|
if (typeof content === "string") return content.trim();
|
|
273
|
+
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
274
|
+
const obj = content as Record<string, unknown>;
|
|
275
|
+
if (typeof obj.text === "string") return obj.text.trim();
|
|
276
|
+
if (obj.content !== undefined) return contentText(obj.content);
|
|
277
|
+
if (obj.message !== undefined) return contentText(obj.message);
|
|
278
|
+
return "";
|
|
279
|
+
}
|
|
273
280
|
if (!Array.isArray(content)) return String(content ?? "").trim();
|
|
274
281
|
const parts: string[] = [];
|
|
275
282
|
for (const item of content) {
|
|
@@ -278,6 +285,14 @@ function contentText(content: unknown): string {
|
|
|
278
285
|
const obj = item as Record<string, unknown>;
|
|
279
286
|
if (obj.type === "text" && typeof obj.text === "string") parts.push(obj.text);
|
|
280
287
|
else if (typeof obj.text === "string") parts.push(obj.text);
|
|
288
|
+
else if (obj.content !== undefined) {
|
|
289
|
+
const nested = contentText(obj.content);
|
|
290
|
+
if (nested) parts.push(nested);
|
|
291
|
+
}
|
|
292
|
+
else if (obj.message !== undefined) {
|
|
293
|
+
const nested = contentText(obj.message);
|
|
294
|
+
if (nested) parts.push(nested);
|
|
295
|
+
}
|
|
281
296
|
}
|
|
282
297
|
return parts.join("\n").replace(/\s+/g, " ").trim();
|
|
283
298
|
}
|
|
@@ -333,13 +348,27 @@ function orchestrationSnapshotText(ctx: any): string {
|
|
|
333
348
|
: "no active goal";
|
|
334
349
|
return [
|
|
335
350
|
"Orchestration:",
|
|
336
|
-
`- Goal: ${goalActive ? `active — ${truncate(snapshot.goal,
|
|
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 || "",
|
|
351
|
+
`- Goal: ${goalActive ? `active — ${truncate(snapshot.goal, 360)}` : "off"}`,
|
|
352
|
+
`- Autoresearch: ${researchActive ? `active — ${truncate(snapshot.research.instruction || "", 240)}; cycles=${snapshot.research.cycles ?? 0}, doneAttempts=${snapshot.research.doneAttempts ?? 0}${snapshot.research.lastResult ? `, last=${snapshot.research.lastResult}` : ""}` : "off"}`,
|
|
353
|
+
`- Loop: ${loopActive ? `active every ${snapshot.loop.interval || "?"} — ${truncate(snapshot.loop.instruction || "", 260)}` : "off"}`,
|
|
339
354
|
`- Status: ${status}`,
|
|
340
355
|
].join("\n");
|
|
341
356
|
}
|
|
342
357
|
|
|
358
|
+
export function buildAdvisorCheckinPrompt(source: string, orchestration: string, sessionBrief: string): string {
|
|
359
|
+
return [
|
|
360
|
+
`Mid-session check-in (${source})`,
|
|
361
|
+
"Role: alignment reviewer for the active work. Do not create a new task, research direction, benchmark, script, artifact, or model switch unless the active goal explicitly asks for it.",
|
|
362
|
+
"Stay anchored to the active goal/autoresearch/loop. If autoresearch is active, preserve its research question and judge whether the latest work is gathering evidence toward that question.",
|
|
363
|
+
"Bad nudge examples: research the existence of weaknesses instead of solving the named weakness; create a script/report about weaknesses when the goal is to fix advisor behavior; swap to a shallower research mode.",
|
|
364
|
+
"Return exactly two short lines:",
|
|
365
|
+
"Status: on_track|stuck|off_track - <why, tied to the active goal>",
|
|
366
|
+
"Nudge: <one concrete next action that continues the active goal>",
|
|
367
|
+
orchestration,
|
|
368
|
+
sessionBrief ? `Session brief:\n${sessionBrief}` : "",
|
|
369
|
+
].filter(Boolean).join("\n\n");
|
|
370
|
+
}
|
|
371
|
+
|
|
343
372
|
function setPiRogueStatus(ctx: any, config = loadConfig(), state = loadState()): void {
|
|
344
373
|
const normalized = normalizeAdvisorConfig(config);
|
|
345
374
|
const checkin = normalized.checkins === "off" ? "checkins off" : `checkins ${normalized.checkinIntervalMinutes}m`;
|
|
@@ -406,26 +435,19 @@ async function maybeAdvisorCheckin(pi: ExtensionAPI, ctx: any, source: string):
|
|
|
406
435
|
|
|
407
436
|
checkinLocks.add(key);
|
|
408
437
|
try {
|
|
438
|
+
const prompt = buildAdvisorCheckinPrompt(source, orchestrationSnapshotText(ctx), brief(state));
|
|
409
439
|
const completed = await completeWithHigherAdvisorModel(
|
|
410
440
|
ctx,
|
|
411
441
|
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"),
|
|
442
|
+
prompt,
|
|
417
443
|
[
|
|
418
444
|
{
|
|
419
445
|
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"),
|
|
446
|
+
content: prompt,
|
|
425
447
|
timestamp: new Date().toISOString(),
|
|
426
448
|
},
|
|
427
449
|
],
|
|
428
|
-
{ maxTokens:
|
|
450
|
+
{ maxTokens: 260, reasoning: "low" as ThinkingLevel },
|
|
429
451
|
);
|
|
430
452
|
if (!completed) return false;
|
|
431
453
|
|
|
@@ -585,15 +607,17 @@ async function doReview(pi: ExtensionAPI, ctx: any, trigger: string, delta: stri
|
|
|
585
607
|
const gatePrediction = binaryGatePredict(reviewInput.text);
|
|
586
608
|
let reviewRoute = reviewHeuristic;
|
|
587
609
|
if (gatePrediction && gatePrediction.confidence >= 0.55 && !reviewHeuristic.safety) {
|
|
588
|
-
const
|
|
610
|
+
const gateContinues = gatePrediction.decision === "continue";
|
|
589
611
|
reviewRoute = {
|
|
590
612
|
...reviewHeuristic,
|
|
613
|
+
label: gateContinues ? "abstain" : reviewHeuristic.label,
|
|
614
|
+
confidence: gatePrediction.confidence,
|
|
591
615
|
source: "model",
|
|
592
|
-
reason:
|
|
616
|
+
reason: gateContinues
|
|
593
617
|
? "local gate predicts continue"
|
|
594
618
|
: "local gate predicts review",
|
|
595
|
-
review:
|
|
596
|
-
escalate:
|
|
619
|
+
review: gateContinues ? "off" as const : reviewHeuristic.review,
|
|
620
|
+
escalate: gateContinues ? false : reviewHeuristic.escalate,
|
|
597
621
|
};
|
|
598
622
|
}
|
|
599
623
|
appendRouteLog(reviewRoute);
|