@ian-pascoe/pi-advisor 0.3.5 → 0.5.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 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`. Advisor does not pin a Pi version: at load it checks the Pi SDK exports and methods it uses. If any are missing, it warns, hides `advisor_ask`, and stays unavailable; `/advisor status` names the missing requirements. The observed session keeps working.
5
+ Requires Node `>=22.19.0` and Pi `>=0.99.0`.
6
6
 
7
7
  ## Install
8
8
 
@@ -17,6 +17,7 @@ Advisor is **disabled by default**. Configuration precedence is session, trusted
17
17
  ## Commands
18
18
 
19
19
  ```text
20
+ /advisor
20
21
  /advisor status
21
22
  /advisor on|off [--global|--project]
22
23
  /advisor inherit [key] [--global|--project]
@@ -29,6 +30,12 @@ Advisor is **disabled by default**. Configuration precedence is session, trusted
29
30
 
30
31
  Argument autocomplete suggests command names, settings keys, and valid trailing scope flags.
31
32
 
33
+ In the interactive TUI, `/advisor` alone opens a settings menu built from Pi's native settings list, like `/settings`. A Scope row chooses where edits are written (session, trusted project, or global), and each setting shows its effective value with any non-default source. `enabled`, `includeSubagents`, and `thinkingLevel` cycle the selected scope's own value in place (`inherit → on → off`, or through each thinking level); while that scope has none, the row shows what it inherits, such as `inherit (on · project)`, and a value overridden by a higher-precedence scope is marked. Model, tools, numbers, and the prompt open submenus, and the prompt uses Pi's own editor component. Every edit is validated and applied immediately; a paused Advisor shows its reason and a Resume row. Closing the menu records one status entry listing the changes it applied, or nothing if none were. Without the TUI, `/advisor` records the status entry instead.
34
+
35
+ `/advisor status` and the other subcommands each record a status entry in the transcript. Collapsed, it shows state, model, backlog, usage and cost, unavailable tools, and the last error; expanded, it lists every setting with its source and each watched Child Agent. A mutating command's entry starts with the changes it applied. While enabled, the footer shows `advisor` when idle, `advisor: reviewing`/`consulting` with any `backlog N`, or `advisor: paused`, plus Child Agents that are reviewing or paused. `advisor_ask` renders its question and a Markdown answer preview, and Child Agent findings carry a `↳ <agentId>` label.
36
+
37
+ Interventions render with their severity and Advisor attribution. Long Nits collapse until expanded; Concerns and Blockers always show in full.
38
+
32
39
  ## On-demand consultations
33
40
 
34
41
  When Advisor is enabled, the main agent can call `advisor_ask({ message })` for analysis or a second opinion. The call waits for the current Review, then returns plain Markdown before any further Review Backlog is processed. Follow-up calls continue the same private Advisor Session.
@@ -41,6 +48,8 @@ Consultation authorizes analysis and investigation, not implementation or other
41
48
 
42
49
  `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
50
 
51
+ `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.
52
+
44
53
  ## Defaults and access
45
54
 
46
55
  | Option | Default |
@@ -54,13 +63,13 @@ Consultation authorizes analysis and investigation, not implementation or other
54
63
  | Findings per Review | 4 |
55
64
  | Automatic Corrective Turns | 1 per request/task |
56
65
 
57
- A Tool Grant names tools; it does not sandbox their full native interfaces. Explicitly granting `lsp`, for example, permits its native operations, including mutations. Unavailable names are ignored and reported. Advisor never autogrants missing tools. 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.
66
+ 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
67
 
59
68
  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
69
 
61
70
  ## Sessions and Context Management
62
71
 
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. If Pi no longer exports a needed factory, the Advisor pauses with that diagnostic. 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.
72
+ 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
73
 
65
74
  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
75
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian-pascoe/pi-advisor",
3
- "version": "0.3.5",
3
+ "version": "0.5.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.0"
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": {
@@ -15,7 +15,7 @@ export function completeAdvisorCommandArguments(prefix: string): AutocompleteIte
15
15
  if (scopePrefix) {
16
16
  try {
17
17
  const command = parseAdvisorCommand(scopePrefix);
18
- if (command.action !== "status" && command.scope === "session")
18
+ if ("scope" in command && command.scope === "session")
19
19
  candidates.push(`${scopePrefix}--global`, `${scopePrefix}--project`);
20
20
  } catch {
21
21
  // Incomplete commands and JSON values cannot accept a scope yet.
@@ -27,7 +27,7 @@ export function completeAdvisorCommandArguments(prefix: string): AutocompleteIte
27
27
  }
28
28
 
29
29
  const usage =
30
- "Usage: /advisor [on|off|status|prompt|inherit [key]|set <key> <JSON>] [--global|--project]";
30
+ "Usage: /advisor [on|off|status|prompt|inherit [key]|set <key> <JSON>] [--global|--project]; /advisor alone opens settings";
31
31
 
32
32
  /** Parse one configuration change; validated patches cannot invent option keys. */
33
33
  export function parseAdvisorCommand(input: string) {
@@ -38,14 +38,19 @@ export function parseAdvisorCommand(input: string) {
38
38
  : flag?.[1] === "project"
39
39
  ? ("project" as const)
40
40
  : ("session" as const);
41
- const text = (flag ? input.trim().slice(0, flag.index) : input.trim()) || "status";
42
- if (text === "status") {
41
+ const text = flag ? input.trim().slice(0, flag.index) : input.trim();
42
+ if (text === "" || text === "status") {
43
43
  if (flag) throw new Error(usage);
44
- return { action: "status" as const };
44
+ return text === "" ? { action: "menu" as const } : { action: "status" as const };
45
45
  }
46
46
  if (text === "prompt") return { action: "prompt" as const, scope };
47
47
  if (text === "on" || text === "off") {
48
- return { action: "set" as const, scope, patch: { enabled: text === "on" } };
48
+ return {
49
+ action: "set" as const,
50
+ scope,
51
+ key: "enabled" as const,
52
+ patch: { enabled: text === "on" },
53
+ };
49
54
  }
50
55
  const inherit = /^inherit(?:\s+(\S+))?$/.exec(text);
51
56
  if (inherit) {
@@ -53,6 +58,7 @@ export function parseAdvisorCommand(input: string) {
53
58
  }
54
59
  const set = /^set\s+(\S+)\s+([\s\S]+)$/.exec(text);
55
60
  if (!set?.[1] || !set[2]) throw new Error(usage);
56
- const patch: AdvisorOptions = parseAdvisorOptions({ [set[1]]: JSON.parse(set[2]) }, scope);
57
- return { action: "set" as const, scope, patch };
61
+ const key = advisorOptionKey(set[1]);
62
+ const patch: AdvisorOptions = parseAdvisorOptions({ [key]: JSON.parse(set[2]) }, scope);
63
+ return { action: "set" as const, scope, key, patch };
58
64
  }
@@ -0,0 +1,33 @@
1
+ import { Type, type Static } from "typebox";
2
+
3
+ /** Intervention severities, in ascending urgency. */
4
+ export const advisorSeveritySchema = Type.Union([
5
+ Type.Literal("nit"),
6
+ Type.Literal("concern"),
7
+ Type.Literal("blocker"),
8
+ ]);
9
+ export type AdvisorSeverity = Static<typeof advisorSeveritySchema>;
10
+
11
+ /** One reported finding; also the journaled details of an Intervention message. */
12
+ export const advisorFindingSchema = Type.Object(
13
+ {
14
+ severity: advisorSeveritySchema,
15
+ message: Type.String({ minLength: 1, maxLength: 4000 }),
16
+ },
17
+ { additionalProperties: false },
18
+ );
19
+ export type AdvisorFinding = Static<typeof advisorFindingSchema>;
20
+
21
+ /** Observer lifecycle state reported by its `status`. */
22
+ export const advisorObserverStateSchema = Type.Union([
23
+ Type.Literal("disabled"),
24
+ Type.Literal("armed"),
25
+ Type.Literal("reviewing"),
26
+ Type.Literal("consulting"),
27
+ Type.Literal("paused"),
28
+ ]);
29
+ export type AdvisorObserverState = Static<typeof advisorObserverStateSchema>;
30
+
31
+ /** State recorded in status entries; private Advisor Sessions cannot host an Advisor. */
32
+ export const advisorStateSchema = Type.Union([advisorObserverStateSchema, Type.Literal("private")]);
33
+ export type AdvisorState = Static<typeof advisorStateSchema>;