pi-goal-list-loop-audit 0.24.5 → 0.24.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.
@@ -101,7 +101,12 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
101
101
  return `glla: ${paint(theme, pauseIsError(g) ? "error" : "warning", label)}`;
102
102
  }
103
103
  if (g.status === "active") {
104
- const queue = state.list?.length ? ` · list ${state.list.length}` : "";
104
+ // v0.24.7: list policy gets its own wording a queue item is not a goal.
105
+ // Before: "glla: list ● 3m 19s · list 29" (policy label AND queue counter
106
+ // both said "list"). After: "glla: list ● 3m 19s · 29 queued". Goal
107
+ // policy keeps the bare "list N" suffix (no duplication there).
108
+ const n = state.list?.length ?? 0;
109
+ const queue = n === 0 ? "" : g.policy === "list" ? ` · ${n} queued` : ` · list ${n}`;
105
110
  const tasks = g.taskList ? ` ${countDone(g)}/${countTotal(g)} tasks ·` : "";
106
111
  return `glla: ${g.policy} ${paint(theme, "success", "●")}${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
107
112
  }
@@ -158,12 +163,16 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
158
163
  ? paint(theme, "accent", "⟡")
159
164
  : paint(theme, "success", "●");
160
165
  const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), budgetFor(width, 3, 64))}`;
166
+ // v0.24.7: a list item is named as such and points at /list — before,
167
+ // the widget called it "active" and hinted "/goal status", reading as if
168
+ // queue work were a standalone goal.
169
+ const isList = g.policy === "list";
161
170
  const statusWord = g.status === "active" ? paint(theme, "success", "active") : g.status;
162
171
  // Token segment only when a budget is set (v0.22.0): the guard is opt-in,
163
172
  // and "0/0 tok" carried no information when off.
164
173
  const tokenLimit = g.usage?.tokensLimit ?? 0;
165
174
  const tokens = tokenLimit > 0 ? ` · ${paint(theme, "dim", `${fmtTokens(g.usage?.tokensUsed ?? 0)}/${fmtTokens(tokenLimit)} tok`)}` : "";
166
- const lines = [head, `├─ ${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))}${tokens}`];
175
+ const lines = [head, `├─ ${isList ? "list item · " : ""}${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))}${tokens}`];
167
176
  if (g.status === "auditing") {
168
177
  lines.push(`├─ auditor: ${audit?.label ?? "running"}${audit?.currentTool ? ` · ${truncate(audit.currentTool, 30)}` : ""}`);
169
178
  if (audit?.elapsedMs) lines.push(`└─ ${paint(theme, "dim", `${fmtElapsed(audit.elapsedMs)} in isolated session`)}`);
@@ -178,7 +187,10 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
178
187
  const next = nextPending(g);
179
188
  if (next) lines.push(`├─ next: ${truncate(next, budgetFor(width, 9, 56))}`);
180
189
  const queue = state.list?.length ?? 0;
181
- lines.push(`└─ ${paint(theme, "dim", `${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`)}`);
190
+ const footer = isList
191
+ ? `${queue > 0 ? `${queue} queued · ` : ""}/list · /glla`
192
+ : `${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`;
193
+ lines.push(`└─ ${paint(theme, "dim", footer)}`);
182
194
  return lines;
183
195
  }
184
196
 
@@ -0,0 +1,191 @@
1
+ // pi-goal-list-loop-audit — v0.24.6
2
+ // extensions/goal-loop-subagents.ts
3
+ //
4
+ // Subagent model inheritance fix (v0.24.6, Section I of the eager-continuation
5
+ // contract). pi-subagents v0.14.3's default Explore agent pins
6
+ // "anthropic/claude-haiku-4-5" (default-agents.ts:40). Its model resolution is
7
+ // explicit option > agent config > parent model (agent-runner.ts:720), so an
8
+ // Explore spawn NEVER inherits the session model — it silently routes to a
9
+ // different provider with a different quota pool. On rigs where the session
10
+ // model is local/alternative (e.g. MiniMax-M3) and claude-haiku-4-5 resolves
11
+ // through a quota-capped key (OpenRouter), three concurrent Explore spawns
12
+ // exhaust the key with 403 "Key limit exceeded" while the parent session is
13
+ // unaffected.
14
+ //
15
+ // pi-subagents has no per-agent model setting (subagents.json covers
16
+ // concurrency/scope/etc., not models). Its supported override mechanism is
17
+ // user agent files: <agentDir>/agents/<Name>.md fully replaces the default
18
+ // config of the same name (custom-agents.ts + agent-types.ts overlay by
19
+ // exact key). A file that omits "model:" falls through to the parent model.
20
+ //
21
+ // This module manages exactly one override file — Explore.md — the only
22
+ // default agent with a model pin today. It writes/updates/removes the file
23
+ // according to glla settings, and NEVER touches a file it didn't write
24
+ // (marker frontmatter field). If tintinweb pins more defaults later, the
25
+ // drift test in tests/subagent-model-override.test.ts fails and prompts us
26
+ // to add embedded copies here.
27
+
28
+ import * as fs from "node:fs";
29
+ import * as os from "node:os";
30
+ import * as path from "node:path";
31
+
32
+ /** Frontmatter marker identifying files this module wrote. Files without it
33
+ * are user-owned: never modified, never deleted. */
34
+ export const SUBAGENT_MANAGED_MARKER = "pi-goal-list-loop-audit";
35
+
36
+ /** Default pi-subagents agents that pin a model. Today: only Explore
37
+ * (anthropic/claude-haiku-4-5). Plan and general-purpose already inherit the
38
+ * parent model. Guarded by the drift test. */
39
+ export const KNOWN_PINNED_DEFAULT_AGENTS = ["Explore"] as const;
40
+
41
+ /** Strategy for subagent model selection. Default "inherit-parent": managed
42
+ * override drops the model pin so subagents share the session model AND its
43
+ * quota pool. "agent-default": upstream behavior (Explore pins haiku). */
44
+ export type SubagentModelStrategy = "inherit-parent" | "agent-default";
45
+
46
+ // ---- Embedded default-agent copies (verbatim from pi-subagents v0.14.3
47
+ // src/default-agents.ts; drift-tested against the installed package) ----
48
+
49
+ export const EXPLORE_DEFAULT_DESCRIPTION = "Fast read-only search agent for locating code. Use it to find files by pattern (eg. \"src/components/**/*.tsx\"), grep for symbols or keywords (eg. \"API endpoints\"), or answer \"where is X defined / which files reference Y.\" Do NOT use it for code review, design-doc auditing, cross-file consistency checks, or open-ended analysis \u2014 it reads excerpts rather than whole files and will miss content past its read window. When calling, specify search breadth: \"quick\" for a single targeted lookup, \"medium\" for moderate exploration, or \"very thorough\" to search across multiple locations and naming conventions.";
50
+
51
+ export const EXPLORE_DEFAULT_SYSTEM_PROMPT = `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
52
+ You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
53
+ Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools.
54
+
55
+ You are STRICTLY PROHIBITED from:
56
+ - Creating new files
57
+ - Modifying existing files
58
+ - Deleting files
59
+ - Moving or copying files
60
+ - Creating temporary files anywhere, including /tmp
61
+ - Using redirect operators (>, >>, |) or heredocs to write to files
62
+ - Running ANY commands that change system state
63
+
64
+ Use Bash ONLY for read-only operations: ls, git status, git log, git diff, find, cat, head, tail.
65
+
66
+ # Tool Usage
67
+ - Use the find tool for file pattern matching (NOT the bash find command)
68
+ - Use the grep tool for content search (NOT bash grep/rg command)
69
+ - Use the read tool for reading files (NOT bash cat/head/tail)
70
+ - Use Bash ONLY for read-only operations
71
+ - Make independent tool calls in parallel for efficiency
72
+ - Adapt search approach based on thoroughness level specified
73
+
74
+ # Output
75
+ - Use absolute file paths in all references
76
+ - Report findings as regular messages
77
+ - Do not use emojis
78
+ - Be thorough and precise`;
79
+
80
+ export const EXPLORE_DEFAULT_TOOLS = "read, bash, grep, find, ls";
81
+
82
+ interface EmbeddedAgentDefault {
83
+ description: string;
84
+ systemPrompt: string;
85
+ tools: string;
86
+ }
87
+
88
+ /** Embedded copies keyed by agent name. Only agents in
89
+ * KNOWN_PINNED_DEFAULT_AGENTS need entries. */
90
+ const EMBEDDED_DEFAULTS: Record<string, EmbeddedAgentDefault> = {
91
+ Explore: {
92
+ description: EXPLORE_DEFAULT_DESCRIPTION,
93
+ systemPrompt: EXPLORE_DEFAULT_SYSTEM_PROMPT,
94
+ tools: EXPLORE_DEFAULT_TOOLS,
95
+ },
96
+ };
97
+
98
+ /** Default global agent dir (pi-subagents reads $PI_CODING_AGENT_DIR/agents,
99
+ * default ~/.pi/agent/agents). Parameterized in sync for tests. */
100
+ export function defaultAgentDir(): string {
101
+ return path.join(os.homedir(), ".pi", "agent");
102
+ }
103
+
104
+ /** Build the override .md file content. With `model`, the pin is written;
105
+ * without, the file falls through to the parent session model. */
106
+ export function buildAgentOverrideMd(name: string, model?: string): string {
107
+ const def = EMBEDDED_DEFAULTS[name];
108
+ if (!def) throw new Error(`no embedded default config for agent "${name}"`);
109
+ const yamlDesc = "'" + def.description.replace(/'/g, "''") + "'";
110
+ const lines = [
111
+ "---",
112
+ `description: ${yamlDesc}`,
113
+ `tools: ${def.tools}`,
114
+ "prompt_mode: replace",
115
+ ];
116
+ if (model) lines.push(`model: ${model}`);
117
+ lines.push(
118
+ `x-managed-by: ${SUBAGENT_MANAGED_MARKER}`,
119
+ model
120
+ ? `x-glla-note: model pinned to ${model} by glla subagentModelOverrides. Remove the file or change /glla subagent settings to inherit the session model.`
121
+ : "x-glla-note: model pin removed (upstream default pins a fixed model) so this agent inherits the parent session model and its quota pool. Managed by glla — flip /glla subagent strategy to agent-default to restore upstream behavior.",
122
+ "---",
123
+ "",
124
+ def.systemPrompt,
125
+ "",
126
+ );
127
+ return lines.join("\n");
128
+ }
129
+
130
+ function hasManagedMarker(content: string): boolean {
131
+ return content.includes(`x-managed-by: ${SUBAGENT_MANAGED_MARKER}`);
132
+ }
133
+
134
+ export interface SubagentSyncResult {
135
+ written: string[];
136
+ removed: string[];
137
+ /** Files left untouched because the user owns them (no marker). */
138
+ skipped: Array<{ name: string; reason: string }>;
139
+ }
140
+
141
+ /** Sync <agentDir>/agents/<Name>.md with the desired state. Idempotent:
142
+ * writes only when content differs. Never touches non-managed files. */
143
+ export function syncSubagentModelOverrides(opts: {
144
+ agentDir: string;
145
+ strategy: SubagentModelStrategy;
146
+ overrides?: Record<string, string>;
147
+ }): SubagentSyncResult {
148
+ const result: SubagentSyncResult = { written: [], removed: [], skipped: [] };
149
+ const overrides = opts.overrides ?? {};
150
+ const names = new Set<string>([...KNOWN_PINNED_DEFAULT_AGENTS, ...Object.keys(overrides)]);
151
+
152
+ for (const name of names) {
153
+ const overrideModel = overrides[name];
154
+ if (overrideModel !== undefined && !EMBEDDED_DEFAULTS[name]) {
155
+ result.skipped.push({
156
+ name,
157
+ reason: `no embedded default config for "${name}" — create ${name}.md manually (only ${KNOWN_PINNED_DEFAULT_AGENTS.join("/")} are glla-managed)`,
158
+ });
159
+ continue;
160
+ }
161
+ const file = path.join(opts.agentDir, "agents", `${name}.md`);
162
+ const desired = overrideModel
163
+ ? buildAgentOverrideMd(name, overrideModel)
164
+ : opts.strategy === "inherit-parent"
165
+ ? buildAgentOverrideMd(name)
166
+ : undefined; // agent-default + no per-type override → file should be absent
167
+
168
+ const exists = fs.existsSync(file);
169
+ const current = exists ? fs.readFileSync(file, "utf-8") : undefined;
170
+
171
+ if (desired === undefined) {
172
+ if (exists && hasManagedMarker(current!)) {
173
+ fs.unlinkSync(file);
174
+ result.removed.push(name);
175
+ } else if (exists) {
176
+ result.skipped.push({ name, reason: "user-owned file (no glla marker) — left untouched" });
177
+ }
178
+ continue;
179
+ }
180
+
181
+ if (exists && !hasManagedMarker(current!)) {
182
+ result.skipped.push({ name, reason: "user-owned file (no glla marker) — left untouched" });
183
+ continue;
184
+ }
185
+ if (current === desired) continue; // idempotent no-op
186
+ fs.mkdirSync(path.dirname(file), { recursive: true });
187
+ fs.writeFileSync(file, desired);
188
+ result.written.push(name);
189
+ }
190
+ return result;
191
+ }
@@ -75,6 +75,11 @@ import {
75
75
  pushCapped as pushRepetitionCapped,
76
76
  } from "../goal-loop-repetition.js";
77
77
  import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
78
+ import {
79
+ defaultAgentDir,
80
+ syncSubagentModelOverrides,
81
+ type SubagentModelStrategy,
82
+ } from "../goal-loop-subagents.js";
78
83
  import {
79
84
  applyMeasurement,
80
85
  applyMetriclessTick,
@@ -590,6 +595,8 @@ async function cmdStatus(ctx: ExtensionContext): Promise<void> {
590
595
  const lines = [
591
596
  `[${g.id}] ${statusLabel(g.status)}`,
592
597
  `Objective: ${g.objective}`,
598
+ // v0.24.7: name WHERE the work came from — a queue item is not a goal.
599
+ ...(g.policy === "list" ? [`Source: /list queue (${listQueue().length} waiting) — /list to manage`] : []),
593
600
  `Auto-continue: ${g.autoContinue ? "on" : "off"}`,
594
601
  `Iteration: ${iterationCounter}`,
595
602
  `Tokens: ${(g.usage?.tokensUsed ?? 0).toLocaleString()}${(g.usage?.tokensLimit ?? 0) > 0 ? ` / ${(g.usage!.tokensLimit).toLocaleString()}` : " (no cap — /glla tokenlimit=<n> to set)"}`,
@@ -2264,6 +2271,18 @@ interface Settings {
2264
2271
  * interview floor is skipped — the seed carries the intent (unattended
2265
2272
  * rigs). Default off: nothing activates before the user confirms. */
2266
2273
  autoAcceptDrafts?: boolean;
2274
+ /** v0.24.6: subagent model strategy for pi-subagents default agents that
2275
+ * pin a model (Explore pins claude-haiku-4-5, which silently routes
2276
+ * subagents to a different provider/quota pool than the session).
2277
+ * "inherit-parent" (default) writes a managed ~/.pi/agent/agents/Explore.md
2278
+ * override without the model pin so subagents share the session model and
2279
+ * its quota; "agent-default" restores upstream behavior. Applies to NEW
2280
+ * sessions (pi-subagents registers agents at session start). */
2281
+ subagentModelStrategy?: SubagentModelStrategy;
2282
+ /** v0.24.6: per-agent-type model pin, e.g. { "Explore": "minimax/MiniMax-M3" }.
2283
+ * Always wins over subagentModelStrategy — the managed override is written
2284
+ * WITH this pin regardless of strategy. */
2285
+ subagentModelOverrides?: Record<string, string>;
2267
2286
  }
2268
2287
 
2269
2288
  const DEFAULT_SETTINGS: Settings = {
@@ -2271,6 +2290,9 @@ const DEFAULT_SETTINGS: Settings = {
2271
2290
  // pi, auditor follows), floor "high" — the auditor is the verification
2272
2291
  // gate, depth is worth more there than speed. /glla thinking= overrides.
2273
2292
  auditorThinkingLevel: undefined,
2293
+ // v0.24.6: subagents inherit the session model by default — one quota
2294
+ // pool, no surprise 403s from a pinned default agent's provider.
2295
+ subagentModelStrategy: "inherit-parent",
2274
2296
  };
2275
2297
 
2276
2298
  // Two-tier config (v0.7.0): GLOBAL is the normal home — you set things once
@@ -2408,6 +2430,8 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2408
2430
  `Notify command — ${show("notifyCmd", "(off)")}`,
2409
2431
  `Token limit per goal — ${show("tokenLimit", "(off)")}`,
2410
2432
  `Wedge alert minutes — ${show("wedgeAlertMinutes", `(${WEDGE_ALERT_DEFAULT_MINUTES}m default)`)}`,
2433
+ `Subagent model strategy — ${show("subagentModelStrategy", "(inherit-parent)")}`,
2434
+ `Subagent Explore model pin — ${loadSettings(ctx.cwd).subagentModelOverrides?.Explore ?? "(follows strategy)"}`,
2411
2435
  "Done",
2412
2436
  ],
2413
2437
  );
@@ -2441,6 +2465,26 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2441
2465
  else if (!v.trim()) saveSettings("global", ctx.cwd, { wedgeAlertMinutes: undefined });
2442
2466
  else ctx.ui.notify(`Not a non-negative integer: ${v}`, "warning");
2443
2467
  }
2468
+ } else if (choice.startsWith("Subagent model strategy")) {
2469
+ const v = await ctx.ui.select("Subagent model (pi-subagents default agents)", [
2470
+ "inherit-parent — subagents share your session model + its quota pool (fixes separate-provider 403s; search agents may run on a pricier model)",
2471
+ "agent-default — upstream behavior: Explore pins claude-haiku-4-5 (cheap search, but a SEPARATE provider quota from your session)",
2472
+ ]);
2473
+ if (v) {
2474
+ const strategy: SubagentModelStrategy = v.startsWith("agent-default") ? "agent-default" : "inherit-parent";
2475
+ saveSettings("global", ctx.cwd, { subagentModelStrategy: strategy });
2476
+ ctx.ui.notify("Subagent model strategy saved — applies to NEW pi sessions (pi-subagents registers agents at session start).", "info");
2477
+ }
2478
+ } else if (choice.startsWith("Subagent Explore model pin")) {
2479
+ const v = await ctx.ui.input("Model pin for Explore subagents", "provider/model-id e.g. minimax/MiniMax-M3 — always wins over strategy; empty = follow strategy");
2480
+ if (v !== undefined) {
2481
+ const current = loadSettings(ctx.cwd).subagentModelOverrides ?? {};
2482
+ const next = { ...current };
2483
+ if (v.trim()) next.Explore = v.trim();
2484
+ else delete next.Explore;
2485
+ saveSettings("global", ctx.cwd, { subagentModelOverrides: Object.keys(next).length > 0 ? next : undefined });
2486
+ ctx.ui.notify("Explore model pin saved — applies to NEW pi sessions.", "info");
2487
+ }
2444
2488
  }
2445
2489
  } catch {
2446
2490
  return;
@@ -2800,6 +2844,22 @@ export default function (pi: ExtensionAPI): void {
2800
2844
  ensureAgentToolsActive(pi, ctx);
2801
2845
  warnOnCommandCollision(ctx);
2802
2846
  warnIfAuditorProviderRisky(ctx);
2847
+ // v0.24.6: sync the pi-subagents model override (managed Explore.md) with
2848
+ // settings. Idempotent; applies to NEW sessions (pi-subagents registers
2849
+ // its agents at its own session start).
2850
+ try {
2851
+ const s = loadSettings(ctx.cwd);
2852
+ const sync = syncSubagentModelOverrides({
2853
+ agentDir: defaultAgentDir(),
2854
+ strategy: s.subagentModelStrategy ?? "inherit-parent",
2855
+ overrides: s.subagentModelOverrides,
2856
+ });
2857
+ for (const skip of sync.skipped) {
2858
+ ctx.ui.notify(`glla subagent override skipped [${skip.name}]: ${skip.reason}`, "warning");
2859
+ }
2860
+ } catch (err) {
2861
+ ctx.ui.notify(`glla subagent override sync failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
2862
+ }
2803
2863
  // Restore gate (v0.21.0): a fresh session ("startup"/"new", or a pi too
2804
2864
  // old to report a reason) has no conversation context for the restored
2805
2865
  // work — HOLD instead of auto-firing, so opening pi in a folder never
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.24.5",
3
+ "version": "0.24.7",
4
4
  "description": "Goal. Loop. Audit. Done. — a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor — only the read tools needed to verify your goal.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",