pi-goal-list-loop-audit 0.25.5 → 0.25.6

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.
@@ -82,19 +82,81 @@ export const EXPLORE_DEFAULT_TOOLS = "read, bash, grep, find, ls";
82
82
  interface EmbeddedAgentDefault {
83
83
  description: string;
84
84
  systemPrompt: string;
85
+ /** "" means "all tools" (upstream omits builtinToolNames). */
85
86
  tools: string;
86
87
  }
87
88
 
88
- /** Embedded copies keyed by agent name. Only agents in
89
- * KNOWN_PINNED_DEFAULT_AGENTS need entries. */
89
+ export const PLAN_DEFAULT_DESCRIPTION = "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs.";
90
+
91
+ export const PLAN_DEFAULT_SYSTEM_PROMPT = `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
92
+ You are a software architect and planning specialist.
93
+ Your role is EXCLUSIVELY to explore the codebase and design implementation plans.
94
+ You do NOT have access to file editing tools — attempting to edit files will fail.
95
+
96
+ You are STRICTLY PROHIBITED from:
97
+ - Creating new files
98
+ - Modifying existing files
99
+ - Deleting files
100
+ - Moving or copying files
101
+ - Creating temporary files anywhere, including /tmp
102
+ - Using redirect operators (>, >>, |) or heredocs to write to files
103
+ - Running ANY commands that change system state
104
+
105
+ # Planning Process
106
+ 1. Understand requirements
107
+ 2. Explore thoroughly (read files, find patterns, understand architecture)
108
+ 3. Design solution based on your assigned perspective
109
+ 4. Detail the plan with step-by-step implementation strategy
110
+
111
+ # Requirements
112
+ - Consider trade-offs and architectural decisions
113
+ - Identify dependencies and sequencing
114
+ - Anticipate potential challenges
115
+ - Follow existing patterns where appropriate
116
+
117
+ # Tool Usage
118
+ - Use the find tool for file pattern matching (NOT the bash find command)
119
+ - Use the grep tool for content search (NOT bash grep/rg command)
120
+ - Use the read tool for reading files (NOT bash cat/head/tail)
121
+ - Use Bash ONLY for read-only operations
122
+
123
+ # Output Format
124
+ - Use absolute file paths
125
+ - Do not use emojis
126
+ - End your response with:
127
+
128
+ ### Critical Files for Implementation
129
+ List 3-5 files most critical for implementing this plan:
130
+ - /absolute/path/to/file.ts - [Brief reason]`;
131
+
132
+ export const GENERAL_PURPOSE_DEFAULT_DESCRIPTION = "General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.";
133
+
134
+ /** Embedded copies keyed by agent name. Explore needs an entry for the
135
+ * strategy-driven sync (KNOWN_PINNED_DEFAULT_AGENTS); Plan and
136
+ * general-purpose entries exist so users can pin them per-type via
137
+ * subagentModelOverrides (v0.25.6). */
90
138
  const EMBEDDED_DEFAULTS: Record<string, EmbeddedAgentDefault> = {
91
139
  Explore: {
92
140
  description: EXPLORE_DEFAULT_DESCRIPTION,
93
141
  systemPrompt: EXPLORE_DEFAULT_SYSTEM_PROMPT,
94
142
  tools: EXPLORE_DEFAULT_TOOLS,
95
143
  },
144
+ Plan: {
145
+ description: PLAN_DEFAULT_DESCRIPTION,
146
+ systemPrompt: PLAN_DEFAULT_SYSTEM_PROMPT,
147
+ tools: EXPLORE_DEFAULT_TOOLS, // same read-only set upstream
148
+ },
149
+ "general-purpose": {
150
+ description: GENERAL_PURPOSE_DEFAULT_DESCRIPTION,
151
+ systemPrompt: "", // upstream: empty prompt, promptMode append
152
+ tools: "", // upstream: all tools (builtinToolNames omitted)
153
+ },
96
154
  };
97
155
 
156
+ /** v0.25.6: agent types the user can pin per-type via
157
+ * subagentModelOverrides (embedded defaults exist for each). */
158
+ export const OVERRIDABLE_AGENT_TYPES = Object.keys(EMBEDDED_DEFAULTS);
159
+
98
160
  /** Default global agent dir (pi-subagents reads $PI_CODING_AGENT_DIR/agents,
99
161
  * default ~/.pi/agent/agents). Parameterized in sync for tests. */
100
162
  export function defaultAgentDir(): string {
@@ -107,12 +169,9 @@ export function buildAgentOverrideMd(name: string, model?: string): string {
107
169
  const def = EMBEDDED_DEFAULTS[name];
108
170
  if (!def) throw new Error(`no embedded default config for agent "${name}"`);
109
171
  const yamlDesc = "'" + def.description.replace(/'/g, "''") + "'";
110
- const lines = [
111
- "---",
112
- `description: ${yamlDesc}`,
113
- `tools: ${def.tools}`,
114
- "prompt_mode: replace",
115
- ];
172
+ const lines = ["---", `description: ${yamlDesc}`];
173
+ if (def.tools) lines.push(`tools: ${def.tools}`);
174
+ lines.push(def.systemPrompt ? "prompt_mode: replace" : "prompt_mode: append");
116
175
  if (model) lines.push(`model: ${model}`);
117
176
  lines.push(
118
177
  `x-managed-by: ${SUBAGENT_MANAGED_MARKER}`,
@@ -121,7 +180,7 @@ export function buildAgentOverrideMd(name: string, model?: string): string {
121
180
  : "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
181
  "---",
123
182
  "",
124
- def.systemPrompt,
183
+ def.systemPrompt || "(no system-prompt override — upstream default is an empty append-mode prompt)",
125
184
  "",
126
185
  );
127
186
  return lines.join("\n");
@@ -135,7 +194,16 @@ export interface SubagentSyncResult {
135
194
  written: string[];
136
195
  removed: string[];
137
196
  /** Files left untouched because the user owns them (no marker). */
138
- skipped: Array<{ name: string; reason: string }>;
197
+ skipped: Array<{ name: string } & { reason: string }>;
198
+ /** v0.25.6: managed files that were expected (previously written) but
199
+ * found missing or altered, and got re-written — surface these to the
200
+ * user (external edit, pi update, or dracon-sync churn). */
201
+ repaired: string[];
202
+ }
203
+
204
+ /** State file tracking what the last sync wrote (repair detection). */
205
+ export function subagentSyncStatePath(agentDir: string): string {
206
+ return path.join(agentDir, "agents", ".glla-subagent-sync.json");
139
207
  }
140
208
 
141
209
  /** Sync <agentDir>/agents/<Name>.md with the desired state. Idempotent:
@@ -145,8 +213,18 @@ export function syncSubagentModelOverrides(opts: {
145
213
  strategy: SubagentModelStrategy;
146
214
  overrides?: Record<string, string>;
147
215
  }): SubagentSyncResult {
148
- const result: SubagentSyncResult = { written: [], removed: [], skipped: [] };
216
+ const result: SubagentSyncResult = { written: [], removed: [], skipped: [], repaired: [] };
149
217
  const overrides = opts.overrides ?? {};
218
+ // v0.25.6: load the previous sync state for repair detection — a file
219
+ // we wrote before that is now MISSING or CONTENT-CHANGED was touched
220
+ // externally; re-writing it is a repair the user should hear about.
221
+ let prevWritten: string[] = [];
222
+ try {
223
+ const prev = JSON.parse(fs.readFileSync(subagentSyncStatePath(opts.agentDir), "utf-8"));
224
+ if (Array.isArray(prev?.written)) prevWritten = prev.written.map(String);
225
+ } catch {
226
+ /* first sync or unreadable state */
227
+ }
150
228
  const names = new Set<string>([...KNOWN_PINNED_DEFAULT_AGENTS, ...Object.keys(overrides)]);
151
229
 
152
230
  for (const name of names) {
@@ -159,11 +237,15 @@ export function syncSubagentModelOverrides(opts: {
159
237
  continue;
160
238
  }
161
239
  const file = path.join(opts.agentDir, "agents", `${name}.md`);
240
+ // v0.25.6: the strategy-driven (model-less) write applies ONLY to
241
+ // agents that pin a model upstream (Explore) — Plan/general-purpose
242
+ // don't pin, so inherit-parent needs no file for them; they get
243
+ // managed files only via an explicit per-type override.
162
244
  const desired = overrideModel
163
245
  ? buildAgentOverrideMd(name, overrideModel)
164
- : opts.strategy === "inherit-parent"
246
+ : opts.strategy === "inherit-parent" && (KNOWN_PINNED_DEFAULT_AGENTS as readonly string[]).includes(name)
165
247
  ? buildAgentOverrideMd(name)
166
- : undefined; // agent-default + no per-type override → file should be absent
248
+ : undefined; // agent-default / no pin upstream + no override → file should be absent
167
249
 
168
250
  const exists = fs.existsSync(file);
169
251
  const current = exists ? fs.readFileSync(file, "utf-8") : undefined;
@@ -186,6 +268,31 @@ export function syncSubagentModelOverrides(opts: {
186
268
  fs.mkdirSync(path.dirname(file), { recursive: true });
187
269
  fs.writeFileSync(file, desired);
188
270
  result.written.push(name);
271
+ if (prevWritten.includes(name)) result.repaired.push(name);
272
+ }
273
+ // Persist what we manage now (best-effort).
274
+ try {
275
+ fs.mkdirSync(path.join(opts.agentDir, "agents"), { recursive: true });
276
+ fs.writeFileSync(subagentSyncStatePath(opts.agentDir), JSON.stringify({ written: result.written, at: new Date().toISOString() }));
277
+ } catch {
278
+ /* repair detection is best-effort */
189
279
  }
190
280
  return result;
191
281
  }
282
+
283
+ /** v0.25.6: effective model for an agent type, for the headless settings
284
+ * display. Per-type override wins; inherit-parent falls to the session
285
+ * model; agent-default means upstream's own resolution (Explore's haiku
286
+ * pin for Explore, session model for the others). */
287
+ export function resolveEffectiveSubagentModel(
288
+ name: string,
289
+ settings: { subagentModelStrategy?: string; subagentModelOverrides?: Record<string, string> },
290
+ sessionModel?: string,
291
+ ): string {
292
+ const pin = settings.subagentModelOverrides?.[name];
293
+ if (pin) return `${pin} (per-type pin)`;
294
+ if ((settings.subagentModelStrategy ?? "inherit-parent") === "inherit-parent") {
295
+ return sessionModel ? `${sessionModel} (inherits session)` : "(session model)";
296
+ }
297
+ return name === "Explore" ? "anthropic/claude-haiku-4-5 (upstream pin)" : "(agent default)";
298
+ }
@@ -87,6 +87,7 @@ import {
87
87
  } from "../goal-loop-core.js";
88
88
  import {
89
89
  isQuotaError,
90
+ isSubagentQuotaResult,
90
91
  parseQuotaError,
91
92
  scheduleQuotaRetry,
92
93
  cancelQuotaRetry,
@@ -122,6 +123,7 @@ import {
122
123
  import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
123
124
  import {
124
125
  defaultAgentDir,
126
+ resolveEffectiveSubagentModel,
125
127
  syncSubagentModelOverrides,
126
128
  type SubagentModelStrategy,
127
129
  } from "../goal-loop-subagents.js";
@@ -2666,6 +2668,8 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2666
2668
  `Stuck max interventions — ${show("stuckMaxInterventions", "(5 default)")}`,
2667
2669
  `Subagent model strategy — ${show("subagentModelStrategy", "(inherit-parent)")}`,
2668
2670
  `Subagent Explore model pin — ${loadSettings(ctx.cwd).subagentModelOverrides?.Explore ?? "(follows strategy)"}`,
2671
+ `Subagent Plan model pin — ${loadSettings(ctx.cwd).subagentModelOverrides?.Plan ?? "(follows strategy)"}`,
2672
+ `Subagent general-purpose model pin — ${loadSettings(ctx.cwd).subagentModelOverrides?.["general-purpose"] ?? "(follows strategy)"}`,
2669
2673
  `Audit feedback characters — ${show("auditFeedbackChars", "(full report)")}`,
2670
2674
  "Done",
2671
2675
  ],
@@ -2735,15 +2739,16 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2735
2739
  saveSettings("global", ctx.cwd, { subagentModelStrategy: strategy });
2736
2740
  ctx.ui.notify("Subagent model strategy saved — applies to NEW pi sessions (pi-subagents registers agents at session start).", "info");
2737
2741
  }
2738
- } else if (choice.startsWith("Subagent Explore model pin")) {
2739
- 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");
2742
+ } else if (/^Subagent (Explore|Plan|general-purpose) model pin/.test(choice)) {
2743
+ const agentType = choice.match(/^Subagent (Explore|Plan|general-purpose) model pin/)![1]!;
2744
+ const v = await ctx.ui.input(`Model pin for ${agentType} subagents`, "provider/model-id e.g. minimax/MiniMax-M3 — always wins over strategy; empty = follow strategy");
2740
2745
  if (v !== undefined) {
2741
2746
  const current = loadSettings(ctx.cwd).subagentModelOverrides ?? {};
2742
2747
  const next = { ...current };
2743
- if (v.trim()) next.Explore = v.trim();
2744
- else delete next.Explore;
2748
+ if (v.trim()) next[agentType] = v.trim();
2749
+ else delete next[agentType];
2745
2750
  saveSettings("global", ctx.cwd, { subagentModelOverrides: Object.keys(next).length > 0 ? next : undefined });
2746
- ctx.ui.notify("Explore model pin saved — applies to NEW pi sessions.", "info");
2751
+ ctx.ui.notify(`${agentType} model pin saved — applies to NEW pi sessions.`, "info");
2747
2752
  }
2748
2753
  } else if (choice.startsWith("Audit feedback")) {
2749
2754
  const v = await ctx.ui.input("Auditor feedback returned to the executor (characters)", "non-negative integer cap; 0 or empty = full report (default)");
@@ -2878,6 +2883,10 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2878
2883
  fmt("aggressiveMode", "aggressiveMode"),
2879
2884
  fmt("quotaRetryMinutes", "quotaRetryMinutes"),
2880
2885
  fmt("stuckMaxInterventions", "stuckMaxInterventions"),
2886
+ // v0.25.6: effective per-type subagent model resolution.
2887
+ ...["Explore", "Plan", "general-purpose"].map(
2888
+ (t) => `subagent ${t}: ${resolveEffectiveSubagentModel(t, loadSettings(ctx.cwd), (ctx.model as any)?.id ? `${(ctx.model as any).provider}/${(ctx.model as any).id}` : undefined)}`,
2889
+ ),
2881
2890
  `\nglobal: ${globalSettingsPath()}`,
2882
2891
  `project: ${projectSettingsPath(ctx.cwd)}`,
2883
2892
  `Set with: /glla key=value (global) · /glla project key=value (project override)`,
@@ -3252,6 +3261,18 @@ export default function (pi: ExtensionAPI): void {
3252
3261
  state.goal.telemetry = t;
3253
3262
  }
3254
3263
  }
3264
+ // v0.25.6: subagent quota errors (the pi-subagents#175 shape —
3265
+ // Explore's upstream haiku pin 403s on shared keys). Surface the
3266
+ // repair path immediately; the continuation prompt's WHEN SUBAGENTS
3267
+ // HIT QUOTA ERRORS section carries the full guidance.
3268
+ if (isSubagentQuotaResult(String(event?.toolName ?? ""), Boolean(event?.isError ?? event?.error), event?.output ?? event?.result ?? event?.details ?? "")) {
3269
+ const errText = typeof (event?.output ?? event?.result) === "string" ? (event?.output ?? event?.result) : JSON.stringify(event?.output ?? event?.result ?? event?.details ?? "");
3270
+ appendLedger(registeredCtx?.cwd ?? process.cwd(), "subagent_quota_error", { error: String(errText).slice(0, 200) });
3271
+ registeredCtx?.ui.notify(
3272
+ "Subagent hit a quota error (403/limit). Repair: re-spawn with an explicit model= on your quota pool, or do the work inline — see the continuation prompt's WHEN SUBAGENTS HIT QUOTA ERRORS. Explore's upstream haiku pin is the usual cause (pi-subagents#175); glla's inherit-parent strategy removes it for NEW sessions.",
3273
+ "warning",
3274
+ );
3275
+ }
3255
3276
  if (draftingTarget === null) return;
3256
3277
  if (askUserQuestionAnswered(String(event?.toolName ?? ""), event?.details)) {
3257
3278
  draftingUserReplies++;
@@ -3285,6 +3306,15 @@ export default function (pi: ExtensionAPI): void {
3285
3306
  for (const skip of sync.skipped) {
3286
3307
  ctx.ui.notify(`glla subagent override skipped [${skip.name}]: ${skip.reason}`, "warning");
3287
3308
  }
3309
+ // v0.25.6: notify-with-repair — a managed override that went missing
3310
+ // or was altered externally (pi update, manual edit, sync churn) is
3311
+ // re-written AND surfaced, not silently restored.
3312
+ if (sync.repaired.length > 0) {
3313
+ ctx.ui.notify(
3314
+ `glla repaired managed subagent override(s): ${sync.repaired.join(", ")} — the file(s) were missing or altered externally; re-written per your subagent settings.`,
3315
+ "warning",
3316
+ );
3317
+ }
3288
3318
  } catch (err) {
3289
3319
  ctx.ui.notify(`glla subagent override sync failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
3290
3320
  }
@@ -88,3 +88,13 @@ export function scheduleQuotaRetry(
88
88
  "info",
89
89
  );
90
90
  }
91
+
92
+ /** v0.25.6: detect a SUBAGENT quota failure in a tool_result — the
93
+ * pi-subagents#175 shape (Explore's upstream haiku pin 403s on shared
94
+ * keys). Tool must be an Agent spawn and the payload a quota error. */
95
+ export function isSubagentQuotaResult(toolName: string, isError: boolean, payload: unknown): boolean {
96
+ if (!isError) return false;
97
+ if (toolName !== "Agent" && toolName !== "agent") return false;
98
+ const text = typeof payload === "string" ? payload : JSON.stringify(payload ?? "");
99
+ return isQuotaError(text);
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.25.5",
3
+ "version": "0.25.6",
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",