@bacnh85/pi-advisor 0.1.4 → 0.1.7
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/CHANGELOG.md +32 -0
- package/extensions/commands/advisor.ts +5 -1
- package/extensions/index.ts +7 -3
- package/extensions/lib/watcher.ts +23 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.7 (2026-08-30)
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Only nits defer during the post-steer cooldown; concerns now always steer
|
|
8
|
+
(wake the agent) even inside the cooldown window. A deferred concern
|
|
9
|
+
contradicted its own delivery template ("address this or state why it does
|
|
10
|
+
not apply") — the agent couldn't address what it wasn't woken for, and the
|
|
11
|
+
note just sat visible-but-unacted until the user's next prompt (session
|
|
12
|
+
01a051c6: 1h21m). Blockers already always steered; nit ping-pong guard is
|
|
13
|
+
unchanged.
|
|
14
|
+
|
|
15
|
+
## 0.1.6 (2026-08-30)
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Deferred (cooldown) advisor notes are now visible immediately as a display-only
|
|
20
|
+
card ("Advisor (deferred — next turn)") at settle time. Previously they sat in
|
|
21
|
+
the SDK's pending-next-turn queue with no visual presence and only materialized
|
|
22
|
+
when the user's next prompt flushed them into the agent's context — looking
|
|
23
|
+
like the advisor stayed silent and then blurted a note out after user input
|
|
24
|
+
(session 01a05099, 1h37m gap). The flushed LLM message is now `display:false`
|
|
25
|
+
so the note never renders twice. Anti-ping-pong deferral behavior unchanged.
|
|
26
|
+
|
|
27
|
+
## 0.1.5 (2026-08-29)
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `/advisor` argument completion now offers the `on` / `off` / `status` /
|
|
32
|
+
`watch-off` keywords ahead of model suggestions (previously only models were
|
|
33
|
+
offered, so keywords could not be discovered or completed).
|
|
34
|
+
|
|
3
35
|
## 0.1.4 (2026-08-26)
|
|
4
36
|
|
|
5
37
|
### Changed
|
|
@@ -130,9 +130,13 @@ export function registerAdvisor(pi: ExtensionAPI, state: AdvisorState): void {
|
|
|
130
130
|
pi.registerCommand("advisor", {
|
|
131
131
|
description: "Configure the advisor: /advisor [model hint|on|off|status]",
|
|
132
132
|
getArgumentCompletions: (prefix) => {
|
|
133
|
+
const kws = ["on", "off", "status", "watch-off"].filter((k) => k.startsWith(prefix.toLowerCase()));
|
|
134
|
+
const kwItems = kws.map((k) => ({ value: k, label: k, description: k === "watch-off" ? "disable background watch" : `advisor ${k}` }));
|
|
133
135
|
const models = registry?.getAvailable() ?? [];
|
|
134
136
|
const matches = prefix ? fuzzyFilter(models, prefix, modelSearchText) : models;
|
|
135
|
-
|
|
137
|
+
const modelItems = matches.map((model) => ({ value: modelRef(model), label: model.id, description: model.provider }));
|
|
138
|
+
const items = [...kwItems, ...modelItems];
|
|
139
|
+
return items.length > 0 ? items : null;
|
|
136
140
|
},
|
|
137
141
|
handler: async (args, ctx) => {
|
|
138
142
|
registry = ctx.modelRegistry;
|
package/extensions/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ interface NoteData {
|
|
|
18
18
|
note: string;
|
|
19
19
|
timestamp: number;
|
|
20
20
|
downgraded?: boolean;
|
|
21
|
+
deferred?: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export default function piAdvisor(pi: ExtensionAPI): void {
|
|
@@ -32,7 +33,7 @@ export default function piAdvisor(pi: ExtensionAPI): void {
|
|
|
32
33
|
? { ...entry.data, note: sanitizeNote(entry.data.note) }
|
|
33
34
|
: { severity: "nit" as Severity, note: "(unavailable)", timestamp: 0 };
|
|
34
35
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
35
|
-
const label = data.downgraded ? "Advisor (downgraded)" : "Advisor";
|
|
36
|
+
const label = data.downgraded ? "Advisor (downgraded)" : data.deferred ? "Advisor (deferred — next turn)" : "Advisor";
|
|
36
37
|
const sev = data.severity === "blocker"
|
|
37
38
|
? theme.fg("error", data.severity)
|
|
38
39
|
: data.severity === "concern"
|
|
@@ -45,7 +46,9 @@ export default function piAdvisor(pi: ExtensionAPI): void {
|
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
// Message renderer for next-turn asides (LLM-visible deferred notes). Guard for
|
|
48
|
-
// older Pi builds/tests that only mock registerEntryRenderer.
|
|
49
|
+
// older Pi builds/tests that only mock registerEntryRenderer. In the TUI the
|
|
50
|
+
// deferred message is display:false (the immediate card is the visible surface),
|
|
51
|
+
// so this is a fallback for non-TUI surfaces that render flushed messages.
|
|
49
52
|
if (typeof (pi as unknown as { registerMessageRenderer?: unknown }).registerMessageRenderer === "function") {
|
|
50
53
|
(pi as unknown as { registerMessageRenderer: typeof pi.registerEntryRenderer }).registerMessageRenderer<NoteData>(REVIEW_ENTRY, (message, { expanded }, theme) => {
|
|
51
54
|
const raw = (message as unknown as { details?: unknown }).details as NoteData | undefined;
|
|
@@ -53,7 +56,7 @@ export default function piAdvisor(pi: ExtensionAPI): void {
|
|
|
53
56
|
? { ...raw, note: sanitizeNote(raw.note) }
|
|
54
57
|
: { severity: "nit" as Severity, note: "(unavailable)", timestamp: 0 };
|
|
55
58
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
56
|
-
const label = data.downgraded ? "Advisor (downgraded)" : "Advisor";
|
|
59
|
+
const label = data.downgraded ? "Advisor (downgraded)" : data.deferred ? "Advisor (deferred — next turn)" : "Advisor";
|
|
57
60
|
const sev = data.severity === "blocker"
|
|
58
61
|
? theme.fg("error", data.severity)
|
|
59
62
|
: data.severity === "concern"
|
|
@@ -110,6 +113,7 @@ export default function piAdvisor(pi: ExtensionAPI): void {
|
|
|
110
113
|
await reviewTurn(runtime, ctx, {
|
|
111
114
|
sendMessage: (message, options) => pi.sendMessage(message, options as never),
|
|
112
115
|
sendUserMessage: (content, options) => pi.sendUserMessage(content, options),
|
|
116
|
+
appendEntry: (customType, data) => pi.appendEntry(customType, data),
|
|
113
117
|
}, testIsolated);
|
|
114
118
|
});
|
|
115
119
|
|
|
@@ -121,6 +121,8 @@ export interface WatcherHost {
|
|
|
121
121
|
/** Defer a note as an LLM-visible next-turn aside (never wakes the agent now). */
|
|
122
122
|
sendMessage(message: { customType: string; content: string; display: boolean; details?: unknown }, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }): void;
|
|
123
123
|
sendUserMessage(content: string, options?: { deliverAs?: "steer" | "followUp" }): void;
|
|
124
|
+
/** Display-only immediate card (session entry; never enters LLM context). */
|
|
125
|
+
appendEntry<T = unknown>(customType: string, data?: T): void;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
/** Injectable isolated-model call — defaults to the real one; tests pass a fake. */
|
|
@@ -192,20 +194,30 @@ export async function reviewTurn(rt: WatcherRuntime, ctx: ExtensionContext, host
|
|
|
192
194
|
else if (verdict.severity === "blocker") rt.stats.blockers++;
|
|
193
195
|
else rt.stats.concerns++;
|
|
194
196
|
const isBlocker = verdict.severity === "blocker";
|
|
195
|
-
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
|
|
197
|
+
const isConcern = verdict.severity === "concern";
|
|
198
|
+
// Post-steer cooldown: only nits defer. Concerns carry a must-address contract
|
|
199
|
+
// ("address this or state why it does not apply") — deferring one while its own
|
|
200
|
+
// template claims authority contradicts the injected agent instructions, and
|
|
201
|
+
// waiting for the user's next prompt looks like the advisor was ignored.
|
|
202
|
+
// Blockers always steer (OMP #5628: handing off broken work must be
|
|
203
|
+
// acknowledged). Nits within the next immuneTurns settled turns after a steer
|
|
204
|
+
// are deferred to next-turn asides rather than waking the agent again;
|
|
205
|
+
// otherwise every settled turn's fresh transcript text lets the reviewer emit
|
|
206
|
+
// a NEW note each cycle and the identical-note dedupe never trips — an
|
|
207
|
+
// unbounded nit ping-pong. Each steer re-arms the cooldown. Deferred asides are
|
|
208
|
+
// LLM-visible on the next turn — never lost, only deferred.
|
|
209
|
+
if (!isBlocker && !isConcern && rt.steerCooldownTurns > 0) {
|
|
205
210
|
// The cooldown ticks once per settled turn at the top of reviewTurn — no
|
|
206
211
|
// extra decrement here (OMP's window is purely turn-count based).
|
|
207
212
|
// nextTurn injects into the agent's context on the next turn without waking it now.
|
|
208
|
-
|
|
213
|
+
// display:false — the immediate card below is the visible surface; the flushed
|
|
214
|
+
// message stays LLM-only so the note doesn't render twice.
|
|
215
|
+
const at = Date.now();
|
|
216
|
+
host.sendMessage({ customType: REVIEW_ENTRY, content: templates[verdict.severity], display: false, details: { severity: verdict.severity, note: verdict.note, timestamp: at, deferred: true } }, { deliverAs: "nextTurn" });
|
|
217
|
+
// Immediate display-only card: without it the deferred note is invisible
|
|
218
|
+
// until the next user prompt flushes it, looking like the advisor stayed
|
|
219
|
+
// silent then blurted out a note after user input.
|
|
220
|
+
host.appendEntry(REVIEW_ENTRY, { severity: verdict.severity, note: verdict.note, timestamp: at, deferred: true });
|
|
209
221
|
return;
|
|
210
222
|
}
|
|
211
223
|
// Steering delivery (blockers always steer; non-blockers steer when off-cooldown).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-advisor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Pi extension for an automatic advisor: a second model that reviews each settled turn and injects severity-routed notes, plus an on-demand consult tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|