pi-goal-list-loop-audit 0.25.4 โ†’ 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.
@@ -1090,6 +1090,10 @@ export interface AuditLogEntry {
1090
1090
  report: string;
1091
1091
  impossibleReason?: string;
1092
1092
  error?: string;
1093
+ /** v0.25.4 post-audit: how long the audit took, and whether the infra
1094
+ * retry fired. */
1095
+ durationMs?: number;
1096
+ retriedOnce?: boolean;
1093
1097
  }
1094
1098
 
1095
1099
  export function auditLogPath(cwd: string): string {
@@ -1145,3 +1149,55 @@ export function formatAuditLog(entries: AuditLogEntry[]): string {
1145
1149
  })
1146
1150
  .join("\n");
1147
1151
  }
1152
+
1153
+ // =================================================================
1154
+ // v0.25.4 (post-audit fix): infra-failure retry-once-with-backoff
1155
+ // =================================================================
1156
+
1157
+ /** Which auditor infra errors are worth an automatic retry? User aborts
1158
+ * and missing-model config are NOT โ€” retrying can't help them. */
1159
+ export function isRetriableInfraError(error?: string): boolean {
1160
+ if (!error) return false;
1161
+ if (/aborted/i.test(error)) return false;
1162
+ if (/no model/i.test(error)) return false;
1163
+ return true;
1164
+ }
1165
+
1166
+ export interface InfraRetryOutcome<T> {
1167
+ result: T;
1168
+ retriedOnce: boolean;
1169
+ }
1170
+
1171
+ /** Run the auditor; on a retriable infra failure, wait `backoffMs` and
1172
+ * retry EXACTLY once before reporting "auditor infrastructure error
1173
+ * (retried once)". The failed pair is never a verdict on the work. */
1174
+ export async function runWithInfraRetry<T extends { error?: string; approved: boolean; disapproved: boolean }>(
1175
+ run: () => Promise<T>,
1176
+ opts: { backoffMs?: number; sleep?: (ms: number) => Promise<void>; onRetry?: (error: string) => void } = {},
1177
+ ): Promise<InfraRetryOutcome<T>> {
1178
+ const sleep = opts.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
1179
+ const first = await run();
1180
+ if (first.approved || first.disapproved || !isRetriableInfraError(first.error)) {
1181
+ return { result: first, retriedOnce: false };
1182
+ }
1183
+ opts.onRetry?.(first.error!);
1184
+ await sleep(opts.backoffMs ?? 5000);
1185
+ const second = await run();
1186
+ return { result: second, retriedOnce: true };
1187
+ }
1188
+
1189
+ /** /glla audits default view: the ACTIVE goal's own audit history (the
1190
+ * surface the goal spec asked for), one line per verdict. */
1191
+ export function formatGoalAuditHistory(goal: { id: string; auditHistory?: Array<any> }): string {
1192
+ const history = goal.auditHistory ?? [];
1193
+ if (history.length === 0) return "(no audits on this goal yet)";
1194
+ return history
1195
+ .map((v) => {
1196
+ const glyph = v.approved ? (v.regressionShieldPassed === false ? "๐Ÿ›ก" : "โœ”") : v.impossible ? "โ›”" : v.disapproved ? "โœ–" : "โš ";
1197
+ const day = String(v.at ?? "").slice(5, 16).replace("T", " ");
1198
+ const elapsed = v.durationMs ? ` ยท ${Math.round(v.durationMs / 60000)}m` : "";
1199
+ const firstLine = (String(v.report ?? "").split("\n").find((l: string) => l.trim()) ?? "").trim().slice(0, 80);
1200
+ return `${glyph} ${day} ${v.model ?? "?"}${elapsed} โ€” ${firstLine}`;
1201
+ })
1202
+ .join("\n");
1203
+ }
@@ -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
+ }
@@ -42,6 +42,8 @@ import {
42
42
  appendAuditLog,
43
43
  computeListDepth,
44
44
  formatAuditLog,
45
+ formatGoalAuditHistory,
46
+ runWithInfraRetry,
45
47
  readAuditLog,
46
48
  stripThinkBlocks,
47
49
  type AuditLogEntry,
@@ -85,6 +87,7 @@ import {
85
87
  } from "../goal-loop-core.js";
86
88
  import {
87
89
  isQuotaError,
90
+ isSubagentQuotaResult,
88
91
  parseQuotaError,
89
92
  scheduleQuotaRetry,
90
93
  cancelQuotaRetry,
@@ -120,6 +123,7 @@ import {
120
123
  import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
121
124
  import {
122
125
  defaultAgentDir,
126
+ resolveEffectiveSubagentModel,
123
127
  syncSubagentModelOverrides,
124
128
  type SubagentModelStrategy,
125
129
  } from "../goal-loop-subagents.js";
@@ -1703,24 +1707,38 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1703
1707
  // Esc during the audit aborts this tool's signal โ†’ threaded into the
1704
1708
  // auditor session, which aborts cleanly and returns "Auditor aborted."
1705
1709
  latestAuditProgress = { label: "starting", lastEventAt: Date.now() };
1706
- const result = await runGoalCompletionAuditor({
1707
- ctx,
1708
- goal: state.goal,
1709
- completionSummary: p.completionSummary,
1710
- verificationSummary: p.verificationSummary,
1711
- model: auditorModel,
1712
- thinkingLevel: settings.auditorThinkingLevel ?? getSessionThinkingLevel(),
1713
- signal: signal ?? undefined,
1714
- onProgress: (progress) => {
1715
- latestAuditProgress = {
1716
- currentTool: progress.currentTool,
1717
- label: progress.label,
1718
- elapsedMs: progress.elapsedMs,
1719
- lastEventAt: Date.now(),
1720
- };
1710
+ const runAudit = () =>
1711
+ runGoalCompletionAuditor({
1712
+ ctx,
1713
+ goal: state.goal!,
1714
+ completionSummary: p.completionSummary,
1715
+ verificationSummary: p.verificationSummary,
1716
+ model: auditorModel,
1717
+ thinkingLevel: settings.auditorThinkingLevel ?? getSessionThinkingLevel(),
1718
+ signal: signal ?? undefined,
1719
+ onProgress: (progress) => {
1720
+ latestAuditProgress = {
1721
+ currentTool: progress.currentTool,
1722
+ label: progress.label,
1723
+ elapsedMs: progress.elapsedMs,
1724
+ lastEventAt: Date.now(),
1725
+ };
1726
+ refreshUI(ctx);
1727
+ },
1728
+ });
1729
+ // v0.25.4 (post-audit fix): a retriable infra failure (stream error,
1730
+ // auth blip โ€” NOT user abort, NOT missing model) gets ONE automatic
1731
+ // retry with backoff before we report "auditor infrastructure error
1732
+ // (retried once)". Neither attempt is a verdict on the work.
1733
+ const auditStartMs = Date.now();
1734
+ const { result, retriedOnce } = await runWithInfraRetry(runAudit, {
1735
+ onRetry: (err) => {
1736
+ latestAuditProgress = { label: `infra error (${err.slice(0, 40)}) โ€” retrying once`, lastEventAt: Date.now() };
1721
1737
  refreshUI(ctx);
1738
+ appendLedger(ctx.cwd, "audit_infra_retry", { goalId: state.goal?.id, error: err.slice(0, 200) });
1722
1739
  },
1723
1740
  });
1741
+ const auditDurationMs = Date.now() - auditStartMs;
1724
1742
  latestAuditProgress = null;
1725
1743
  // Audit history: record REAL verdicts only โ€” a non-empty report is the
1726
1744
  // evidence the auditor actually inspected something. Empty-report runs
@@ -1746,7 +1764,8 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1746
1764
  error: result.error,
1747
1765
  regressionShieldPassed: result.regressionShieldPassed,
1748
1766
  regressionShieldMissing: result.regressionShieldMissing,
1749
- });
1767
+ durationMs: auditDurationMs,
1768
+ } as any);
1750
1769
  // Cap history โ€” 39 infra errors taught us unbounded growth is real.
1751
1770
  if (history.length > 20) history.splice(0, history.length - 20);
1752
1771
  // v0.25.4: durable append-only audit log โ€” survives state-snapshot
@@ -1771,7 +1790,9 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1771
1790
  report: cleanOutput,
1772
1791
  impossibleReason: result.impossibleReason,
1773
1792
  error: result.error,
1774
- });
1793
+ durationMs: auditDurationMs,
1794
+ retriedOnce,
1795
+ } as AuditLogEntry);
1775
1796
  }
1776
1797
 
1777
1798
  // Escape hatch: the user aborted the audit (Esc). Offer the explicit
@@ -1897,14 +1918,14 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1897
1918
  updateGoal({
1898
1919
  status: "active",
1899
1920
  auditHistory: history,
1900
- pauseReason: `auditor infrastructure: ${result.error}`,
1921
+ pauseReason: `auditor infrastructure${retriedOnce ? " (retried once)" : ""}: ${result.error}`,
1901
1922
  pauseSuggestedAction: "Fix the auditor model (/glla model=provider/id) and call complete_goal again โ€” your work was NOT judged",
1902
1923
  }, ctx);
1903
1924
  scheduleContinuation(ctx, true);
1904
1925
  return {
1905
1926
  content: [{
1906
1927
  type: "text",
1907
- text: `The auditor could not run (infrastructure, NOT a verdict): ${result.error}\nYour completion claim was not evaluated. Fix the auditor model with /glla model=provider/id and call complete_goal again โ€” do not change your deliverable for this.`,
1928
+ text: `The auditor could not run (infrastructure, NOT a verdict${retriedOnce ? "; retried once with backoff, both attempts failed" : ""}): ${result.error}\nYour completion claim was not evaluated. Fix the auditor model with /glla model=provider/id and call complete_goal again โ€” do not change your deliverable for this.`,
1908
1929
  }],
1909
1930
  details: {},
1910
1931
  };
@@ -2647,6 +2668,8 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2647
2668
  `Stuck max interventions โ€” ${show("stuckMaxInterventions", "(5 default)")}`,
2648
2669
  `Subagent model strategy โ€” ${show("subagentModelStrategy", "(inherit-parent)")}`,
2649
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)"}`,
2650
2673
  `Audit feedback characters โ€” ${show("auditFeedbackChars", "(full report)")}`,
2651
2674
  "Done",
2652
2675
  ],
@@ -2716,15 +2739,16 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2716
2739
  saveSettings("global", ctx.cwd, { subagentModelStrategy: strategy });
2717
2740
  ctx.ui.notify("Subagent model strategy saved โ€” applies to NEW pi sessions (pi-subagents registers agents at session start).", "info");
2718
2741
  }
2719
- } else if (choice.startsWith("Subagent Explore model pin")) {
2720
- 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");
2721
2745
  if (v !== undefined) {
2722
2746
  const current = loadSettings(ctx.cwd).subagentModelOverrides ?? {};
2723
2747
  const next = { ...current };
2724
- if (v.trim()) next.Explore = v.trim();
2725
- else delete next.Explore;
2748
+ if (v.trim()) next[agentType] = v.trim();
2749
+ else delete next[agentType];
2726
2750
  saveSettings("global", ctx.cwd, { subagentModelOverrides: Object.keys(next).length > 0 ? next : undefined });
2727
- 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");
2728
2752
  }
2729
2753
  } else if (choice.startsWith("Audit feedback")) {
2730
2754
  const v = await ctx.ui.input("Auditor feedback returned to the executor (characters)", "non-negative integer cap; 0 or empty = full report (default)");
@@ -2785,12 +2809,28 @@ function cmdStats(args: string, ctx: ExtensionContext): void {
2785
2809
  */
2786
2810
  function cmdAudits(args: string, ctx: ExtensionContext): void {
2787
2811
  const full = /\bfull\b/.test(args);
2812
+ const all = /\b(?:all|global|log)\b/.test(args);
2788
2813
  const nMatch = args.match(/\b(\d+)\b/);
2789
2814
  if (full) {
2815
+ // Latest report โ€” active goal's history first, then the log.
2816
+ const fromGoal = state.goal?.auditHistory?.at(-1);
2817
+ if (fromGoal?.report) {
2818
+ ctx.ui.notify(`Latest audit on this goal โ€” ${fromGoal.model} (${fromGoal.at})\n${fromGoal.report}`, "info");
2819
+ return;
2820
+ }
2790
2821
  const latest = readAuditLog(ctx.cwd).at(-1);
2791
2822
  ctx.ui.notify(latest ? `Latest audit โ€” ${latest.verdict} (${latest.model}, ${latest.at})\n${latest.report}` : "No audits logged yet.", "info");
2792
2823
  return;
2793
2824
  }
2825
+ // Default: the ACTIVE goal's own audit history (with per-audit elapsed);
2826
+ // "all"/"global"/"log" browses the durable cross-goal log.
2827
+ if (!all && state.goal?.auditHistory && state.goal.auditHistory.length > 0) {
2828
+ ctx.ui.notify(
2829
+ `glla audits โ€” this goal's history (${state.goal.auditHistory.length} verdict(s); /glla audits all for the project log)\n${formatGoalAuditHistory(state.goal)}`,
2830
+ "info",
2831
+ );
2832
+ return;
2833
+ }
2794
2834
  const n = nMatch ? Number(nMatch[1]) : 10;
2795
2835
  const entries = readAuditLog(ctx.cwd, n);
2796
2836
  ctx.ui.notify(`glla audits โ€” last ${entries.length} verdict(s) in ${ctx.cwd}\n${formatAuditLog(entries)}`, "info");
@@ -2843,6 +2883,10 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2843
2883
  fmt("aggressiveMode", "aggressiveMode"),
2844
2884
  fmt("quotaRetryMinutes", "quotaRetryMinutes"),
2845
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
+ ),
2846
2890
  `\nglobal: ${globalSettingsPath()}`,
2847
2891
  `project: ${projectSettingsPath(ctx.cwd)}`,
2848
2892
  `Set with: /glla key=value (global) ยท /glla project key=value (project override)`,
@@ -3217,6 +3261,18 @@ export default function (pi: ExtensionAPI): void {
3217
3261
  state.goal.telemetry = t;
3218
3262
  }
3219
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
+ }
3220
3276
  if (draftingTarget === null) return;
3221
3277
  if (askUserQuestionAnswered(String(event?.toolName ?? ""), event?.details)) {
3222
3278
  draftingUserReplies++;
@@ -3250,6 +3306,15 @@ export default function (pi: ExtensionAPI): void {
3250
3306
  for (const skip of sync.skipped) {
3251
3307
  ctx.ui.notify(`glla subagent override skipped [${skip.name}]: ${skip.reason}`, "warning");
3252
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
+ }
3253
3318
  } catch (err) {
3254
3319
  ctx.ui.notify(`glla subagent override sync failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
3255
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.4",
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",