@ian-pascoe/pi-advisor 0.3.5 → 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 +12 -11
- package/src/index.ts +6 -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 {
|
|
@@ -149,8 +148,6 @@ export async function createAdvisorSession(
|
|
|
149
148
|
observed: AgentSession,
|
|
150
149
|
options: AdvisorSessionOptions,
|
|
151
150
|
): Promise<AgentSessionRuntime> {
|
|
152
|
-
const unsupported = advisorRuntimeWarning();
|
|
153
|
-
if (unsupported) throw new Error(unsupported);
|
|
154
151
|
options.signal?.throwIfAborted();
|
|
155
152
|
const cancelled = Promise.withResolvers<never>();
|
|
156
153
|
const abort = () =>
|
|
@@ -225,10 +222,6 @@ async function buildAdvisorSession(
|
|
|
225
222
|
const factoryExport = builtinFactories.get(name);
|
|
226
223
|
if (builtin && factoryExport) {
|
|
227
224
|
const create = piSdk[factoryExport];
|
|
228
|
-
if (!isCallable(create))
|
|
229
|
-
throw new Error(
|
|
230
|
-
`Unsupported Advisor resources: this Pi runtime does not export ${factoryExport} to recreate ${extension.path}`,
|
|
231
|
-
);
|
|
232
225
|
recreated.add(index);
|
|
233
226
|
const codemodeModels = options.resourceInputs?.codemodeModels;
|
|
234
227
|
builtins.push({
|
|
@@ -244,10 +237,6 @@ async function buildAdvisorSession(
|
|
|
244
237
|
}
|
|
245
238
|
// Pi ships a fresh file-backed llama.cpp factory but does not export it.
|
|
246
239
|
if (builtin && name === "llama.cpp") {
|
|
247
|
-
if (!isCallable(piSdk.getPackageDir))
|
|
248
|
-
throw new Error(
|
|
249
|
-
"Unsupported Advisor resources: this Pi runtime does not export getPackageDir to locate llama.cpp",
|
|
250
|
-
);
|
|
251
240
|
const path = join(piSdk.getPackageDir(), "dist", "extensions", "llama", "index.js");
|
|
252
241
|
if (!existsSync(path))
|
|
253
242
|
throw new Error(
|
|
@@ -458,6 +447,18 @@ async function buildAdvisorSession(
|
|
|
458
447
|
};
|
|
459
448
|
if (sessionStartEvent) sessionOptions.sessionStartEvent = sessionStartEvent;
|
|
460
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
|
+
);
|
|
461
462
|
return { ...created, services, diagnostics: services.diagnostics };
|
|
462
463
|
};
|
|
463
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 {
|
|
@@ -46,8 +45,6 @@ const childRequestSchema = Type.Object({
|
|
|
46
45
|
|
|
47
46
|
/** Review an observed session without changing its tools or standing instructions. */
|
|
48
47
|
export default function advisor(pi: ExtensionAPI): void {
|
|
49
|
-
// Capability checks, not version pins: an unmet requirement warns and leaves the Advisor unavailable.
|
|
50
|
-
const unsupported = advisorRuntimeWarning();
|
|
51
48
|
let warn: (message: string) => void = () => {};
|
|
52
49
|
let observed: AgentSession | undefined;
|
|
53
50
|
let error: string | undefined;
|
|
@@ -76,6 +73,12 @@ export default function advisor(pi: ExtensionAPI): void {
|
|
|
76
73
|
description:
|
|
77
74
|
"Ask the enabled Advisor for analysis or a second opinion. Waits for its answer; does not delegate implementation.",
|
|
78
75
|
parameters: askToolParameters,
|
|
76
|
+
annotations: {
|
|
77
|
+
readOnlyHint: true,
|
|
78
|
+
destructiveHint: false,
|
|
79
|
+
idempotentHint: true,
|
|
80
|
+
openWorldHint: false,
|
|
81
|
+
},
|
|
79
82
|
executionMode: "sequential",
|
|
80
83
|
execute: async (_id, { message }, signal) => {
|
|
81
84
|
try {
|
|
@@ -147,12 +150,6 @@ export default function advisor(pi: ExtensionAPI): void {
|
|
|
147
150
|
// A replaced session's UI is stale; the native status entry remains the durable record.
|
|
148
151
|
}
|
|
149
152
|
};
|
|
150
|
-
if (unsupported) {
|
|
151
|
-
error = unsupported;
|
|
152
|
-
setAskToolAvailable(false);
|
|
153
|
-
warn(unsupported);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
153
|
const found = discoverPiAgentSession(pi, piSdk.AgentSession);
|
|
157
154
|
if (found.ok) {
|
|
158
155
|
observed = found.session;
|
|
@@ -215,7 +212,6 @@ export default function advisor(pi: ExtensionAPI): void {
|
|
|
215
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.
|
|
216
213
|
function attachChild(payload: unknown): void {
|
|
217
214
|
if (
|
|
218
|
-
unsupported ||
|
|
219
215
|
!Value.Check(childRequestSchema, payload) ||
|
|
220
216
|
!(payload.session instanceof piSdk.AgentSession)
|
|
221
217
|
)
|
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
|
-
}
|