@ian-pascoe/pi-advisor 0.3.4 → 0.4.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.
- package/README.md +5 -3
- package/package.json +6 -6
- package/src/advisor-observer.ts +6 -0
- package/src/advisor-session.ts +19 -12
- package/src/index.ts +8 -10
- package/src/advisor-runtime.ts +0 -97
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`@ian-pascoe/pi-advisor` reviews a Pi agent's completed work and surfaces concise, attributed findings for material problems and worthwhile low-risk cleanup.
|
|
4
4
|
|
|
5
|
-
Requires Node `>=22.19.0
|
|
5
|
+
Requires Node `>=22.19.0` and Pi `>=0.99.0`.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -41,6 +41,8 @@ Consultation authorizes analysis and investigation, not implementation or other
|
|
|
41
41
|
|
|
42
42
|
`prompt` opens Pi's native editor and replaces the whole Advisor Prompt. `inherit` removes an override at the selected scope. Invalid keys and values are rejected. Lists, including `allowedTools`, replace the inherited list rather than merge. `catchUpThreshold` accepts any positive safe integer or `"off"`; `reviewTimeoutMs` accepts 1–2,147,483,647 milliseconds (the native timer range); `maxFindingsPerReview` accepts an integer from 1 through 32.
|
|
43
43
|
|
|
44
|
+
`advisor_ask` declares MCP-style `annotations` (read-only, non-destructive, idempotent, closed-world) that Pi reports through `pi.getAllTools()` for permission extensions; Pi does not send them to model providers. They describe the consultation itself, not the tools granted to the Advisor. The Advisor Session's internal `advisor_report` tool, which extensions inherited by that session can see, declares non-destructive, closed-world annotations and is not read-only, because it records the Review's findings.
|
|
45
|
+
|
|
44
46
|
## Defaults and access
|
|
45
47
|
|
|
46
48
|
| Option | Default |
|
|
@@ -54,13 +56,13 @@ Consultation authorizes analysis and investigation, not implementation or other
|
|
|
54
56
|
| Findings per Review | 4 |
|
|
55
57
|
| Automatic Corrective Turns | 1 per request/task |
|
|
56
58
|
|
|
57
|
-
A Tool Grant names tools; it does not sandbox their full native interfaces. Explicitly granting `
|
|
59
|
+
A Tool Grant names tools; it does not sandbox their full native interfaces. Explicitly granting `bash`, for example, permits any command, including mutations. Unavailable names are ignored and reported; status reflects the Advisor Session at that moment, so a granted tool that registers later, such as an MCP tool after its server connects, is reported until it does. Advisor never autogrants missing tools. Granted tools with `codemode` or `deferred` exposure (such as tools from Pi's built-in MCP) are not declared to the Advisor model; they stay callable from granted `codemode` scripts, and a granted `tool_search` can declare them, as in the observed agent. Granting `codemode` lets the Advisor script its other granted tools; it inherits the observed `codemode.mode`, so under `"only"` the Advisor calls `advisor_report` from a script. It also inherits whether scripts get the `models` API: Minimal Subagents Child Agents have none, so neither do their Advisors.
|
|
58
60
|
|
|
59
61
|
The Advisor inherits each observed agent's model and thinking level independently unless configured otherwise. One root policy can cover current and future Minimal Subagents descendants when enabled.
|
|
60
62
|
|
|
61
63
|
## Sessions and Context Management
|
|
62
64
|
|
|
63
|
-
Each watched agent has a private, persisted native Advisor Session with fresh, session-bound extension resources. Reviews reuse that session until context or configuration changes require rebuilding. Pi's enabled `builtin:<name>` extensions are recreated in their observed order: `codemode`, `tool-search`, and `mcp` from Pi's exported factories, and `llama.cpp` from the installed Pi package's shipped extension file, retaining their metadata and fresh provider state. Built-ins disabled with `-builtin:<name>` stay absent.
|
|
65
|
+
Each watched agent has a private, persisted native Advisor Session with fresh, session-bound extension resources. Reviews reuse that session until context or configuration changes require rebuilding. Pi's enabled `builtin:<name>` extensions are recreated in their observed order: `codemode`, `tool-search`, and `mcp` from Pi's exported factories, and `llama.cpp` from the installed Pi package's shipped extension file, retaining their metadata and fresh provider state. Built-ins disabled with `-builtin:<name>` stay absent. This is a built-in recreation recipe, not general inline-factory support; a host factory registered under a built-in name is rejected. Other opaque inline or custom extension resources that cannot be safely recreated are reported as unsupported and pause the Advisor; they are not silently reused or omitted. The Advisor's session state remains separate from the observed agent's state.
|
|
64
66
|
|
|
65
67
|
Context Management is optional. If loaded and its tools are granted, `context_notes`, `context_history`, and `context_rollover` operate on the Advisor's private context. If any of those three tools is excluded, the Advisor pauses before reviewing. There is no autogrant or hook bypass.
|
|
66
68
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ian-pascoe/pi-advisor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Background review and attributed corrective advice for Pi agent sessions.",
|
|
6
6
|
"keywords": [
|
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
"provenance": true
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@ian-pascoe/pi-utils": "^0.3.
|
|
35
|
+
"@ian-pascoe/pi-utils": "^0.3.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@earendil-works/pi-agent-core": "
|
|
39
|
-
"@earendil-works/pi-ai": "
|
|
40
|
-
"@earendil-works/pi-coding-agent": "
|
|
41
|
-
"@earendil-works/pi-tui": "
|
|
38
|
+
"@earendil-works/pi-agent-core": ">=0.99.0",
|
|
39
|
+
"@earendil-works/pi-ai": ">=0.99.0",
|
|
40
|
+
"@earendil-works/pi-coding-agent": ">=0.99.0",
|
|
41
|
+
"@earendil-works/pi-tui": ">=0.99.0",
|
|
42
42
|
"typebox": "*"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
package/src/advisor-observer.ts
CHANGED
|
@@ -466,6 +466,12 @@ export class AdvisorObserver {
|
|
|
466
466
|
label: "Advisor report",
|
|
467
467
|
description: `Finish the Review with up to ${this.config.maxFindingsPerReview} concise, actionable findings. Prioritize blockers, then concerns, then worthwhile nits.`,
|
|
468
468
|
parameters: reportSchema,
|
|
469
|
+
annotations: {
|
|
470
|
+
readOnlyHint: false,
|
|
471
|
+
destructiveHint: false,
|
|
472
|
+
idempotentHint: false,
|
|
473
|
+
openWorldHint: false,
|
|
474
|
+
},
|
|
469
475
|
prepareArguments: (arguments_) => {
|
|
470
476
|
if (Value.Check(reportSchema, arguments_)) return arguments_;
|
|
471
477
|
if (!Value.Check(legacyReportSchema, arguments_))
|
package/src/advisor-session.ts
CHANGED
|
@@ -19,7 +19,6 @@ import type {
|
|
|
19
19
|
ToolDefinition,
|
|
20
20
|
} from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import type { AdvisorConfig } from "./advisor-settings.js";
|
|
22
|
-
import { advisorRuntimeWarning, isCallable } from "./advisor-runtime.js";
|
|
23
22
|
|
|
24
23
|
/** Explicit file-selection recipe supplied by the resource owner, never inferred from opaque loaders. */
|
|
25
24
|
export interface AdvisorResourceInputs {
|
|
@@ -31,6 +30,8 @@ export interface AdvisorResourceInputs {
|
|
|
31
30
|
readonly hidden?: boolean | undefined;
|
|
32
31
|
}[];
|
|
33
32
|
flagValues: ReadonlyMap<string, boolean | string>;
|
|
33
|
+
/** Whether the owner's built-in codemode gives scripts the `models` API; Pi's default is true. */
|
|
34
|
+
codemodeModels?: boolean | undefined;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/** Fresh factories and native services; the observed session supplies data, never bound handlers. */
|
|
@@ -147,8 +148,6 @@ export async function createAdvisorSession(
|
|
|
147
148
|
observed: AgentSession,
|
|
148
149
|
options: AdvisorSessionOptions,
|
|
149
150
|
): Promise<AgentSessionRuntime> {
|
|
150
|
-
const unsupported = advisorRuntimeWarning();
|
|
151
|
-
if (unsupported) throw new Error(unsupported);
|
|
152
151
|
options.signal?.throwIfAborted();
|
|
153
152
|
const cancelled = Promise.withResolvers<never>();
|
|
154
153
|
const abort = () =>
|
|
@@ -223,14 +222,14 @@ async function buildAdvisorSession(
|
|
|
223
222
|
const factoryExport = builtinFactories.get(name);
|
|
224
223
|
if (builtin && factoryExport) {
|
|
225
224
|
const create = piSdk[factoryExport];
|
|
226
|
-
if (!isCallable(create))
|
|
227
|
-
throw new Error(
|
|
228
|
-
`Unsupported Advisor resources: this Pi runtime does not export ${factoryExport} to recreate ${extension.path}`,
|
|
229
|
-
);
|
|
230
225
|
recreated.add(index);
|
|
226
|
+
const codemodeModels = options.resourceInputs?.codemodeModels;
|
|
231
227
|
builtins.push({
|
|
232
228
|
name,
|
|
233
|
-
factory:
|
|
229
|
+
factory:
|
|
230
|
+
name === "codemode" && codemodeModels !== undefined
|
|
231
|
+
? piSdk.createCodemodeExtension({ models: codemodeModels })
|
|
232
|
+
: create(),
|
|
234
233
|
builtin: true,
|
|
235
234
|
replaceable: extension.replaceable === true,
|
|
236
235
|
});
|
|
@@ -238,10 +237,6 @@ async function buildAdvisorSession(
|
|
|
238
237
|
}
|
|
239
238
|
// Pi ships a fresh file-backed llama.cpp factory but does not export it.
|
|
240
239
|
if (builtin && name === "llama.cpp") {
|
|
241
|
-
if (!isCallable(piSdk.getPackageDir))
|
|
242
|
-
throw new Error(
|
|
243
|
-
"Unsupported Advisor resources: this Pi runtime does not export getPackageDir to locate llama.cpp",
|
|
244
|
-
);
|
|
245
240
|
const path = join(piSdk.getPackageDir(), "dist", "extensions", "llama", "index.js");
|
|
246
241
|
if (!existsSync(path))
|
|
247
242
|
throw new Error(
|
|
@@ -452,6 +447,18 @@ async function buildAdvisorSession(
|
|
|
452
447
|
};
|
|
453
448
|
if (sessionStartEvent) sessionOptions.sessionStartEvent = sessionStartEvent;
|
|
454
449
|
const created = await piSdk.createAgentSessionFromServices(sessionOptions);
|
|
450
|
+
// Pi's `tools` option is the ceiling and also activates (declares) every named tool. Keep
|
|
451
|
+
// `codemode` and `deferred` grants callable but undeclared, as Pi's own defaults do;
|
|
452
|
+
// later registrations follow the same rule, and extensions can still declare them.
|
|
453
|
+
const scriptOnly = new Set(
|
|
454
|
+
created.session
|
|
455
|
+
.getAllTools()
|
|
456
|
+
.filter(({ exposure }) => exposure === "codemode" || exposure === "deferred")
|
|
457
|
+
.map(({ name }) => name),
|
|
458
|
+
);
|
|
459
|
+
created.session.setActiveToolsByName(
|
|
460
|
+
created.session.getActiveToolNames().filter((name) => !scriptOnly.has(name)),
|
|
461
|
+
);
|
|
455
462
|
return { ...created, services, diagnostics: services.diagnostics };
|
|
456
463
|
};
|
|
457
464
|
const runtime = await piSdk.createAgentSessionRuntime(factory, {
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
import { completeAdvisorCommandArguments, parseAdvisorCommand } from "./advisor-command.js";
|
|
19
19
|
import { AdvisorObserver } from "./advisor-observer.js";
|
|
20
20
|
import { isAdvisorSession, type AdvisorResourceInputs } from "./advisor-session.js";
|
|
21
|
-
import { advisorRuntimeWarning } from "./advisor-runtime.js";
|
|
22
21
|
import { TROUBLESHOOTING_HINT } from "./troubleshooting-skill.js";
|
|
23
22
|
|
|
24
23
|
interface WatchedChild {
|
|
@@ -40,13 +39,12 @@ const childRequestSchema = Type.Object({
|
|
|
40
39
|
agentDir: Type.String({ minLength: 1 }),
|
|
41
40
|
extensions: Type.Array(Type.Unknown()),
|
|
42
41
|
flagValues: Type.Unknown(),
|
|
42
|
+
codemodeModels: Type.Optional(Type.Boolean()),
|
|
43
43
|
}),
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
/** Review an observed session without changing its tools or standing instructions. */
|
|
47
47
|
export default function advisor(pi: ExtensionAPI): void {
|
|
48
|
-
// Capability checks, not version pins: an unmet requirement warns and leaves the Advisor unavailable.
|
|
49
|
-
const unsupported = advisorRuntimeWarning();
|
|
50
48
|
let warn: (message: string) => void = () => {};
|
|
51
49
|
let observed: AgentSession | undefined;
|
|
52
50
|
let error: string | undefined;
|
|
@@ -75,6 +73,12 @@ export default function advisor(pi: ExtensionAPI): void {
|
|
|
75
73
|
description:
|
|
76
74
|
"Ask the enabled Advisor for analysis or a second opinion. Waits for its answer; does not delegate implementation.",
|
|
77
75
|
parameters: askToolParameters,
|
|
76
|
+
annotations: {
|
|
77
|
+
readOnlyHint: true,
|
|
78
|
+
destructiveHint: false,
|
|
79
|
+
idempotentHint: true,
|
|
80
|
+
openWorldHint: false,
|
|
81
|
+
},
|
|
78
82
|
executionMode: "sequential",
|
|
79
83
|
execute: async (_id, { message }, signal) => {
|
|
80
84
|
try {
|
|
@@ -146,12 +150,6 @@ export default function advisor(pi: ExtensionAPI): void {
|
|
|
146
150
|
// A replaced session's UI is stale; the native status entry remains the durable record.
|
|
147
151
|
}
|
|
148
152
|
};
|
|
149
|
-
if (unsupported) {
|
|
150
|
-
error = unsupported;
|
|
151
|
-
setAskToolAvailable(false);
|
|
152
|
-
warn(unsupported);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
153
|
const found = discoverPiAgentSession(pi, piSdk.AgentSession);
|
|
156
154
|
if (found.ok) {
|
|
157
155
|
observed = found.session;
|
|
@@ -214,7 +212,6 @@ export default function advisor(pi: ExtensionAPI): void {
|
|
|
214
212
|
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- SAFETY: The native owner bus is a boundary; validate identity, SDK instance, and exact resource metadata before attachment.
|
|
215
213
|
function attachChild(payload: unknown): void {
|
|
216
214
|
if (
|
|
217
|
-
unsupported ||
|
|
218
215
|
!Value.Check(childRequestSchema, payload) ||
|
|
219
216
|
!(payload.session instanceof piSdk.AgentSession)
|
|
220
217
|
)
|
|
@@ -246,6 +243,7 @@ export default function advisor(pi: ExtensionAPI): void {
|
|
|
246
243
|
agentDir: payload.resourceInputs.agentDir,
|
|
247
244
|
extensions,
|
|
248
245
|
flagValues: new Map(resources.runtime.flagValues),
|
|
246
|
+
codemodeModels: payload.resourceInputs.codemodeModels,
|
|
249
247
|
},
|
|
250
248
|
observer: undefined,
|
|
251
249
|
};
|
package/src/advisor-runtime.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import * as piAi from "@earendil-works/pi-ai";
|
|
2
|
-
import * as piSdk from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { Type } from "typebox";
|
|
4
|
-
import { Value } from "typebox/value";
|
|
5
|
-
|
|
6
|
-
const callable = Type.Function([], Type.Unknown());
|
|
7
|
-
|
|
8
|
-
/** True when a native SDK member is present and callable. */
|
|
9
|
-
export function isCallable<T>(value: T): boolean {
|
|
10
|
-
return Value.Check(callable, value);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** True when `owner` exposes `name` as a callable, including module getters and inherited methods. */
|
|
14
|
-
function hasMember<T extends object>(owner: T | undefined, name: string): boolean {
|
|
15
|
-
if (!owner) return false;
|
|
16
|
-
if (Value.Check(Type.Object({ [name]: callable }), owner)) return true;
|
|
17
|
-
// TypeBox does not treat classes as objects, so read class statics as own data properties.
|
|
18
|
-
return isCallable(Object.getOwnPropertyDescriptor(owner, name)?.value);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function missingMembers<T extends object>(
|
|
22
|
-
label: string,
|
|
23
|
-
owner: T | undefined,
|
|
24
|
-
names: readonly string[],
|
|
25
|
-
): string[] {
|
|
26
|
-
return names.filter((name) => !hasMember(owner, name)).map((name) => `${label}${name}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Pi capabilities the Advisor needs to observe a session and recreate a private native one.
|
|
31
|
-
* Modules are read through namespaces so a missing export is reported here instead of failing
|
|
32
|
-
* extension load. Built-in extension factories are checked only when the observed session uses them.
|
|
33
|
-
*/
|
|
34
|
-
export function advisorRuntimeIssues(
|
|
35
|
-
ai: Partial<typeof piAi> = piAi,
|
|
36
|
-
sdk: Partial<typeof piSdk> = piSdk,
|
|
37
|
-
): string[] {
|
|
38
|
-
return [
|
|
39
|
-
...missingMembers("pi-ai ", ai, [
|
|
40
|
-
"InMemoryCredentialStore",
|
|
41
|
-
"contentText",
|
|
42
|
-
"getCurrentSystemPrompt",
|
|
43
|
-
"getCurrentTools",
|
|
44
|
-
"toToolDeclaration",
|
|
45
|
-
]),
|
|
46
|
-
...missingMembers("", sdk, [
|
|
47
|
-
"AgentSession",
|
|
48
|
-
"AgentSessionRuntime",
|
|
49
|
-
"DefaultResourceLoader",
|
|
50
|
-
"ModelRuntime",
|
|
51
|
-
"SessionManager",
|
|
52
|
-
"SettingsManager",
|
|
53
|
-
"convertToLlm",
|
|
54
|
-
"createAgentSessionFromServices",
|
|
55
|
-
"createAgentSessionRuntime",
|
|
56
|
-
"createAgentSessionServices",
|
|
57
|
-
"defineTool",
|
|
58
|
-
]),
|
|
59
|
-
...missingMembers("SessionManager.", sdk.SessionManager, ["create"]),
|
|
60
|
-
...missingMembers("SettingsManager.", sdk.SettingsManager, ["fromStorage"]),
|
|
61
|
-
...missingMembers("SettingsManager#", sdk.SettingsManager?.prototype, [
|
|
62
|
-
"applyOverrides",
|
|
63
|
-
"getGlobalSettings",
|
|
64
|
-
"getProjectSettings",
|
|
65
|
-
"isProjectTrusted",
|
|
66
|
-
]),
|
|
67
|
-
...missingMembers("ModelRuntime.", sdk.ModelRuntime, ["create"]),
|
|
68
|
-
...missingMembers("ModelRuntime#", sdk.ModelRuntime?.prototype, [
|
|
69
|
-
"getAuth",
|
|
70
|
-
"getModel",
|
|
71
|
-
"getRegisteredProviderIds",
|
|
72
|
-
"isUsingOAuth",
|
|
73
|
-
"listCredentials",
|
|
74
|
-
"setRuntimeApiKey",
|
|
75
|
-
]),
|
|
76
|
-
...missingMembers("AgentSession#", sdk.AgentSession?.prototype, [
|
|
77
|
-
"abort",
|
|
78
|
-
"bindExtensions",
|
|
79
|
-
"getActiveToolNames",
|
|
80
|
-
"getAllTools",
|
|
81
|
-
"getSessionStats",
|
|
82
|
-
"prompt",
|
|
83
|
-
"sendCustomMessage",
|
|
84
|
-
"subscribe",
|
|
85
|
-
]),
|
|
86
|
-
...missingMembers("AgentSessionRuntime#", sdk.AgentSessionRuntime?.prototype, [
|
|
87
|
-
"dispose",
|
|
88
|
-
"setRebindSession",
|
|
89
|
-
]),
|
|
90
|
-
];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** User-facing explanation for unmet requirements, or undefined when the runtime is supported. */
|
|
94
|
-
export function advisorRuntimeWarning(issues = advisorRuntimeIssues()): string | undefined {
|
|
95
|
-
if (!issues.length) return undefined;
|
|
96
|
-
return `Advisor is unavailable: this Pi runtime lacks ${issues.join(", ")}`;
|
|
97
|
-
}
|