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.
- package/extensions/goal-loop-subagents.ts +120 -13
- package/extensions/loops/goal.ts +35 -5
- package/extensions/quota-retry.ts +10 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -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 (
|
|
2739
|
-
const
|
|
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
|
|
2744
|
-
else delete next
|
|
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(
|
|
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.
|
|
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",
|