pi-goal-list-loop-audit 0.28.21 → 0.28.22
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.
|
@@ -156,6 +156,16 @@ export interface Goal {
|
|
|
156
156
|
stopReason?: string;
|
|
157
157
|
pauseReason?: string;
|
|
158
158
|
pauseSuggestedAction?: string;
|
|
159
|
+
/** v0.28.22: pause classification — drives the widget/status rendering
|
|
160
|
+
* (a decision pause, an operational failure, a time-gated wait, and a
|
|
161
|
+
* generic block must not look alike). Undefined = legacy flat card. */
|
|
162
|
+
pauseKind?: "decision" | "error" | "wait" | "blocked";
|
|
163
|
+
/** v0.28.22: decision pauses — the options the user picks between. */
|
|
164
|
+
pauseOptions?: string[];
|
|
165
|
+
/** v0.28.22: 1-based index into pauseOptions the agent recommends. */
|
|
166
|
+
pauseRecommended?: number;
|
|
167
|
+
/** v0.28.22: ISO time a wait-pause becomes resumable (countdown shown). */
|
|
168
|
+
pauseResumeAt?: string;
|
|
159
169
|
/** v0.28.1 (S1/S2): stale-handle interrupt marker. Set INSTEAD of pausing
|
|
160
170
|
* when pi invalidates the extension handle mid-goal — the goal stays
|
|
161
171
|
* active so a fresh session auto-resumes it via the restore gate. Cleared
|
|
@@ -107,6 +107,18 @@ const paint = (theme: DisplayTheme | undefined, color: DisplayColor, text: strin
|
|
|
107
107
|
const ERROR_PAUSE = /token limit|stalled|infra|auditor.*fail/i;
|
|
108
108
|
const pauseIsError = (g: Goal): boolean => ERROR_PAUSE.test(g.pauseReason ?? "");
|
|
109
109
|
|
|
110
|
+
/** v0.28.22: the rendering class of a pause — declared kind wins; legacy
|
|
111
|
+
* pauses (no kind) fall back to the error-regex so old states still
|
|
112
|
+
* classify sensibly. */
|
|
113
|
+
type PauseKind = "decision" | "error" | "wait" | "blocked";
|
|
114
|
+
const pauseKind = (g: Goal): PauseKind | undefined => g.pauseKind ?? (pauseIsError(g) ? "error" : undefined);
|
|
115
|
+
|
|
116
|
+
/** v0.28.22: "06:40 UTC" from an ISO string (wait-pause countdown). */
|
|
117
|
+
const shortClock = (iso: string): string => {
|
|
118
|
+
const d = new Date(iso);
|
|
119
|
+
return Number.isNaN(d.getTime()) ? iso.slice(0, 16) : d.toISOString().slice(11, 16) + " UTC";
|
|
120
|
+
};
|
|
121
|
+
|
|
110
122
|
// ---- status line (one-liner, always-on) ----
|
|
111
123
|
|
|
112
124
|
export interface AuditDisplayProgress {
|
|
@@ -150,6 +162,13 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
|
|
|
150
162
|
return `glla: ${paint(theme, "accent", "auditing…")}${tool}${heldSuffix}`;
|
|
151
163
|
}
|
|
152
164
|
if (g.status === "paused") {
|
|
165
|
+
// v0.28.22: the status line names the ACTIONABILITY, not the reason —
|
|
166
|
+
// "decision needed" / "action needed" / "waiting" tell you at a glance
|
|
167
|
+
// whether the session needs you. Legacy pauses keep the reason dump.
|
|
168
|
+
const kind = pauseKind(g);
|
|
169
|
+
if (kind === "decision") return `glla: ${g.policy} ${paint(theme, "accent", "⏸ decision needed")}${heldSuffix}`;
|
|
170
|
+
if (kind === "error") return `glla: ${g.policy} ${paint(theme, "error", `⏸ action needed — ${truncate(g.pauseReason ?? "", 30)}`)}${heldSuffix}`;
|
|
171
|
+
if (kind === "wait") return `glla: ${g.policy} ${paint(theme, "dim", `⏳ waiting${g.pauseResumeAt ? ` · resumes ${shortClock(g.pauseResumeAt)}` : ""}`)}${heldSuffix}`;
|
|
153
172
|
const label = `${g.policy} paused ⏸ ${truncate(g.pauseReason ?? "", 40)}`;
|
|
154
173
|
return `glla: ${paint(theme, pauseIsError(g) ? "error" : "warning", label)}${heldSuffix}`;
|
|
155
174
|
}
|
|
@@ -274,14 +293,38 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
274
293
|
return lines;
|
|
275
294
|
}
|
|
276
295
|
if (g.status === "paused" && g.pauseReason) {
|
|
277
|
-
const
|
|
296
|
+
const kind = pauseKind(g);
|
|
297
|
+
const isErr = kind === "error";
|
|
278
298
|
const budget = budgetFor(width, 3, 60);
|
|
279
|
-
// v0.
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
299
|
+
// v0.28.22: actionability banner — a decision pause, an operational
|
|
300
|
+
// failure, and a time-gated wait must not look alike (user report:
|
|
301
|
+
// "if something actionable is going on it can be hard to tell").
|
|
302
|
+
if (kind === "decision") lines.push(`├─ ${paint(theme, "accent", "decision needed — your call unblocks this")}`);
|
|
303
|
+
else if (kind === "error") lines.push(`├─ ${paint(theme, "error", "action needed — this won't fix itself")}`);
|
|
304
|
+
else if (kind === "wait") lines.push(`├─ ${paint(theme, "dim", "waiting — nothing for you to do")}`);
|
|
305
|
+
// v0.27.1: wrap reason + suggested action (see wrap()). v0.28.22:
|
|
306
|
+
// decision/wait reasons cap at 2 lines — the options/countdown below
|
|
307
|
+
// carry the actionable content; error reasons keep 3.
|
|
308
|
+
const reasonPaint = isErr ? "error" : kind === "wait" ? "dim" : "warning";
|
|
309
|
+
wrap(g.pauseReason, budget, kind === "decision" || kind === "wait" ? 2 : 3).forEach((w, i) => {
|
|
310
|
+
lines.push(`${i === 0 ? "├─" : "│ "} ${paint(theme, reasonPaint, w)}`);
|
|
284
311
|
});
|
|
312
|
+
// v0.28.22: decision options — one numbered line each (Claude Code /
|
|
313
|
+
// muselinn-Ask convention), the recommended one accented and flagged.
|
|
314
|
+
if (kind === "decision" && g.pauseOptions && g.pauseOptions.length > 0) {
|
|
315
|
+
g.pauseOptions.slice(0, 6).forEach((opt, i) => {
|
|
316
|
+
const rec = g.pauseRecommended === i + 1;
|
|
317
|
+
const text = `${i + 1}. ${truncate(opt, budget - 4)}${rec ? " ◂ recommended" : ""}`;
|
|
318
|
+
lines.push(`│ ${paint(theme, rec ? "accent" : "dim", text)}`);
|
|
319
|
+
});
|
|
320
|
+
if (g.pauseOptions.length > 6) lines.push(`│ ${paint(theme, "dim", `… and ${g.pauseOptions.length - 6} more`)}`);
|
|
321
|
+
}
|
|
322
|
+
// v0.28.22: wait countdown — when the pause lifts on its own.
|
|
323
|
+
if (kind === "wait" && g.pauseResumeAt) {
|
|
324
|
+
const ms = Date.parse(g.pauseResumeAt) - now;
|
|
325
|
+
const when = Number.isNaN(ms) ? g.pauseResumeAt : ms <= 0 ? "now" : `${shortClock(g.pauseResumeAt)} (in ${fmtElapsed(ms)})`;
|
|
326
|
+
lines.push(`├─ ${paint(theme, "dim", `resumes ${when} — or /goal resume now`)}`);
|
|
327
|
+
}
|
|
285
328
|
// v0.27.1: what survives the pause — the first question at a pause is
|
|
286
329
|
// "did I lose the work?". Answer it on the card.
|
|
287
330
|
// v0.27.9: when the goal has no telemetry yet (restored-in-fresh-session
|
|
@@ -300,7 +343,9 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
300
343
|
if (g.pauseSuggestedAction) {
|
|
301
344
|
lines.push(`├─ ${paint(theme, "dim", truncate(savedLine, budget))}`);
|
|
302
345
|
const wrapped = wrap(g.pauseSuggestedAction, budget, 3);
|
|
303
|
-
|
|
346
|
+
// v0.28.22: for ACTION NEEDED pauses the action is the point — pop it.
|
|
347
|
+
const actionPaint = kind === "error" ? "warning" : "dim";
|
|
348
|
+
wrapped.forEach((w, i) => lines.push(`${i === wrapped.length - 1 ? "└─" : "│ "} ${paint(theme, actionPaint, w)}`));
|
|
304
349
|
} else {
|
|
305
350
|
lines.push(`└─ ${paint(theme, "dim", truncate(savedLine, budget))}`);
|
|
306
351
|
}
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -498,6 +498,7 @@ function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "l
|
|
|
498
498
|
if (state.goal && state.goal.status === "active") {
|
|
499
499
|
updateGoal({
|
|
500
500
|
status: "paused",
|
|
501
|
+
pauseKind: "error",
|
|
501
502
|
pauseReason: `send-retry storm: ${mins}m of 50ms re-arms — the session never went idle for the continuation`,
|
|
502
503
|
pauseSuggestedAction: "The session never went idle for the send (wedged queue or permanently busy). Restart pi, then /goal resume.",
|
|
503
504
|
}, ctx);
|
|
@@ -521,6 +522,7 @@ function escalateStallNow(ctx: ExtensionContext, threshold: number): boolean {
|
|
|
521
522
|
if (state.goal && state.goal.status === "active") {
|
|
522
523
|
updateGoal({
|
|
523
524
|
status: "paused",
|
|
525
|
+
pauseKind: "error",
|
|
524
526
|
pauseReason: `stalled: ${threshold} continuation refires landed no turn`,
|
|
525
527
|
pauseSuggestedAction: "The continuation chain is broken in this process (wedged message queue or stale API). Restart pi, then /goal resume.",
|
|
526
528
|
}, ctx);
|
|
@@ -1259,7 +1261,7 @@ async function cmdResume(ctx: ExtensionContext): Promise<void> {
|
|
|
1259
1261
|
const usage = state.goal.usage
|
|
1260
1262
|
? { tokensUsed: state.goal.usage.tokensUsed, tokensLimit: freshLimit }
|
|
1261
1263
|
: undefined;
|
|
1262
|
-
updateGoal({ status: "active", pauseReason: undefined, pauseSuggestedAction: undefined, ...(staleEntry ? { interruptedAt: nowIso(), interruptedReason: "resumed in a stale session" } : {}), ...(usage ? { usage } : {}) }, ctx);
|
|
1264
|
+
updateGoal({ status: "active", pauseReason: undefined, pauseSuggestedAction: undefined, pauseKind: undefined, pauseOptions: undefined, pauseRecommended: undefined, pauseResumeAt: undefined, ...(staleEntry ? { interruptedAt: nowIso(), interruptedReason: "resumed in a stale session" } : {}), ...(usage ? { usage } : {}) }, ctx);
|
|
1263
1265
|
if (staleEntry) return;
|
|
1264
1266
|
// v0.22.5: say what was resumed — with a non-empty list this also resumes
|
|
1265
1267
|
// the queue (the active goal IS the list's head item).
|
|
@@ -2050,10 +2052,10 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2050
2052
|
const sub = (parts[0] ?? "").toLowerCase();
|
|
2051
2053
|
const rest = args.trim().slice(sub.length).trim();
|
|
2052
2054
|
|
|
2053
|
-
if (!sub) {
|
|
2054
|
-
// /loop with no args → resume a held loop
|
|
2055
|
-
// draft the loop config (metric design is
|
|
2056
|
-
// long-running loop; never start one blind).
|
|
2055
|
+
if (!sub || sub === "resume") {
|
|
2056
|
+
// /loop with no args (or /loop resume, v0.28.22) → resume a held loop
|
|
2057
|
+
// if one is waiting; otherwise draft the loop config (metric design is
|
|
2058
|
+
// the whole game for a long-running loop; never start one blind).
|
|
2057
2059
|
if (isLoopActive()) {
|
|
2058
2060
|
ctx.ui.notify("A loop is already active — /loop status to inspect, /loop stop to end it.", "info");
|
|
2059
2061
|
return;
|
|
@@ -2063,7 +2065,7 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2063
2065
|
// v0.28.14: one-active-thing — a held loop must not resume over an
|
|
2064
2066
|
// active goal/list-item (this was the last unguarded stacking path).
|
|
2065
2067
|
if (state.goal && state.goal.status === "active") {
|
|
2066
|
-
ctx.ui.notify("A goal is active — the held loop stays held. /goal pause or /goal cancel it first, then /loop
|
|
2068
|
+
ctx.ui.notify("A goal is active — the held loop stays held. /goal pause or /goal cancel it first, then /loop resume.", "warning");
|
|
2067
2069
|
return;
|
|
2068
2070
|
}
|
|
2069
2071
|
state.loop = { ...stored, active: true, stopReason: undefined };
|
|
@@ -2075,6 +2077,10 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2075
2077
|
);
|
|
2076
2078
|
return;
|
|
2077
2079
|
}
|
|
2080
|
+
if (sub === "resume") {
|
|
2081
|
+
ctx.ui.notify("No held loop to resume. /loop to draft one, or /loop start \"<target>\" for an infinite metricless loop.", "info");
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2078
2084
|
await startDrafting(ctx, "loop");
|
|
2079
2085
|
return;
|
|
2080
2086
|
}
|
|
@@ -2440,6 +2446,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2440
2446
|
updateGoal({
|
|
2441
2447
|
status: "paused",
|
|
2442
2448
|
auditHistory: history,
|
|
2449
|
+
pauseKind: "decision",
|
|
2443
2450
|
pauseReason: `auditor verdict: IMPOSSIBLE — ${reason}`,
|
|
2444
2451
|
pauseSuggestedAction: "The auditor says this goal can never be satisfied as stated. /goal tweak the objective (or /goal cancel), then /goal resume.",
|
|
2445
2452
|
}, ctx);
|
|
@@ -2472,6 +2479,8 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2472
2479
|
status: "paused",
|
|
2473
2480
|
auditHistory: history,
|
|
2474
2481
|
auditInfraStreak: undefined, // quota reached the auditor — infra streak broken
|
|
2482
|
+
pauseKind: "wait",
|
|
2483
|
+
pauseResumeAt: new Date(Date.now() + quota.retryAfterSec * 1000).toISOString(),
|
|
2475
2484
|
pauseReason: `auditor quota: ${result.error}`,
|
|
2476
2485
|
pauseSuggestedAction: `Quota auto-retry in ${retryMin}m — or /goal resume to retry now`,
|
|
2477
2486
|
}, ctx);
|
|
@@ -2506,6 +2515,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2506
2515
|
status: "paused",
|
|
2507
2516
|
auditHistory: history,
|
|
2508
2517
|
auditInfraStreak: infraStreak,
|
|
2518
|
+
pauseKind: "error",
|
|
2509
2519
|
pauseReason: `auditor infrastructure failed ${infraStreak}× in a row — the auditor model is likely broken (last: ${result.error.slice(0, 120)})`,
|
|
2510
2520
|
pauseSuggestedAction: "Fix the auditor model (/glla model=provider/id) or restart pi, then /goal resume. Your work was NOT judged.",
|
|
2511
2521
|
}, ctx);
|
|
@@ -2612,6 +2622,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2612
2622
|
updateGoal({
|
|
2613
2623
|
status: "paused",
|
|
2614
2624
|
auditHistory: history,
|
|
2625
|
+
pauseKind: "decision",
|
|
2615
2626
|
pauseReason: `auditor disapproved ${trailingDisapprovals}× consecutively (cap ${auditCap})`,
|
|
2616
2627
|
pauseSuggestedAction: "Read the audit history (/goal status), fix the actual gap or /goal tweak the objective, then /goal resume. Raise the cap with /glla auditcap=N.",
|
|
2617
2628
|
}, ctx);
|
|
@@ -2646,20 +2657,28 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2646
2657
|
pi.registerTool(defineTool({
|
|
2647
2658
|
name: "pause_goal",
|
|
2648
2659
|
label: "Pause goal",
|
|
2649
|
-
description: "Pause the active goal with a reason and suggested action. Use when blocked on user input or unable to make progress.",
|
|
2660
|
+
description: "Pause the active goal with a reason and suggested action. Use when blocked on user input or unable to make progress. When the user must CHOOSE between options, pass kind=\"decision\" with the options list (recommended = 1-based index of the best one) — decision pauses render as a prominent DECISION NEEDED card. Time-gated waits (retry at a specific time) use kind=\"wait\" with resumeAt (ISO). Operational failures use kind=\"error\".",
|
|
2650
2661
|
parameters: Type.Object({
|
|
2651
2662
|
reason: Type.String({ description: "Why the work is paused" }),
|
|
2652
2663
|
suggestedAction: Type.Optional(Type.String({ description: "What the user should do next" })),
|
|
2664
|
+
kind: Type.Optional(Type.Union([Type.Literal("decision"), Type.Literal("error"), Type.Literal("wait"), Type.Literal("blocked")], { description: "Pause class: decision (user picks an option), error (operational failure), wait (time-gated), blocked (generic)" })),
|
|
2665
|
+
options: Type.Optional(Type.Array(Type.String(), { description: "For kind=decision: the options the user picks between (one line each)" })),
|
|
2666
|
+
recommended: Type.Optional(Type.Number({ description: "For kind=decision: 1-based index of the recommended option" })),
|
|
2667
|
+
resumeAt: Type.Optional(Type.String({ description: "For kind=wait: ISO time the pause lifts (countdown is shown)" })),
|
|
2653
2668
|
}),
|
|
2654
2669
|
async execute(_id, params, _signal, _onUpdate, execCtx) {
|
|
2655
2670
|
const foreign1 = foreignToolGuard(execCtx);
|
|
2656
2671
|
if (foreign1) return { content: [{ type: "text", text: foreign1 }], details: {} };
|
|
2657
|
-
const p = params as { reason: string; suggestedAction?: string };
|
|
2672
|
+
const p = params as { reason: string; suggestedAction?: string; kind?: "decision" | "error" | "wait" | "blocked"; options?: string[]; recommended?: number; resumeAt?: string };
|
|
2658
2673
|
if (!state.goal) return { content: [{ type: "text", text: "No active goal." }], details: {} };
|
|
2659
2674
|
updateGoal({
|
|
2660
2675
|
status: "paused",
|
|
2661
2676
|
pauseReason: p.reason,
|
|
2662
2677
|
pauseSuggestedAction: p.suggestedAction,
|
|
2678
|
+
pauseKind: p.kind,
|
|
2679
|
+
pauseOptions: p.kind === "decision" && p.options && p.options.length > 0 ? p.options : undefined,
|
|
2680
|
+
pauseRecommended: p.kind === "decision" && p.recommended && p.recommended >= 1 ? Math.floor(p.recommended) : undefined,
|
|
2681
|
+
pauseResumeAt: p.kind === "wait" && p.resumeAt ? p.resumeAt : undefined,
|
|
2663
2682
|
}, ctx);
|
|
2664
2683
|
// v0.27.1: surface the FULL pause contract — reason AND suggested
|
|
2665
2684
|
// action. Before, the action only appeared in /goal status and the
|
|
@@ -4462,7 +4481,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4462
4481
|
state.loop = { ...l, active: false, stopReason: HELD_ON_RESTORE };
|
|
4463
4482
|
persistState(ctx);
|
|
4464
4483
|
ctx.ui.notify(
|
|
4465
|
-
`Loop held on restore: ${l.target.slice(0, 60)} — /loop to
|
|
4484
|
+
`Loop held on restore: ${l.target.slice(0, 60)} — /loop resume to continue, /glla autoresume=on to auto-resume on session load in this project.`,
|
|
4466
4485
|
"info",
|
|
4467
4486
|
);
|
|
4468
4487
|
}
|
|
@@ -4492,6 +4511,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4492
4511
|
const resumeHint = `${resumeCmd} to continue${queued > 0 ? ` (+${queued} waiting in the list)` : ""} · /glla autoresume=on to auto-resume in this project`;
|
|
4493
4512
|
updateGoal({
|
|
4494
4513
|
status: "paused",
|
|
4514
|
+
pauseKind: "blocked",
|
|
4495
4515
|
pauseReason: "restored on session load — held for explicit resume",
|
|
4496
4516
|
pauseSuggestedAction: resumeHint,
|
|
4497
4517
|
}, ctx);
|
|
@@ -4522,6 +4542,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4522
4542
|
if (state.loop && state.goal && state.goal.status === "active") {
|
|
4523
4543
|
updateGoal({
|
|
4524
4544
|
status: "paused",
|
|
4545
|
+
pauseKind: "decision",
|
|
4525
4546
|
pauseReason: "held on session load — the loop owns the active slot (one active thing at a time)",
|
|
4526
4547
|
pauseSuggestedAction: "/loop to work the loop, or /loop stop then /goal resume to work the goal",
|
|
4527
4548
|
}, ctx);
|
|
@@ -4617,6 +4638,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4617
4638
|
if (state.goal) {
|
|
4618
4639
|
updateGoal({
|
|
4619
4640
|
status: "paused",
|
|
4641
|
+
pauseKind: "decision",
|
|
4620
4642
|
pauseReason: `stalled: ${HEARTBEAT_MAX_NUDGES} consecutive unproductive turns (no tools, short or repetitive)`,
|
|
4621
4643
|
pauseSuggestedAction: "Inspect the goal — /goal resume to retry, /goal tweak to narrow it, /goal cancel to abort.",
|
|
4622
4644
|
}, ctx);
|
|
@@ -4664,6 +4686,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4664
4686
|
updateGoal({
|
|
4665
4687
|
usage: { tokensUsed: used, tokensLimit: limit },
|
|
4666
4688
|
status: "paused",
|
|
4689
|
+
pauseKind: "error",
|
|
4667
4690
|
pauseReason: `token limit exceeded (${used.toLocaleString()} > ${limit.toLocaleString()})`,
|
|
4668
4691
|
pauseSuggestedAction: "/glla tokenlimit=<n> to raise the cap (or 0 to disable), then /goal resume",
|
|
4669
4692
|
}, ctx);
|
|
@@ -4687,6 +4710,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4687
4710
|
const reason = `5 consecutive errors${detail}`;
|
|
4688
4711
|
updateGoal({
|
|
4689
4712
|
status: "paused",
|
|
4713
|
+
pauseKind: "wait",
|
|
4714
|
+
pauseResumeAt: new Date(Date.now() + 60_000).toISOString(),
|
|
4690
4715
|
pauseReason: reason,
|
|
4691
4716
|
pauseSuggestedAction: "Transient provider flake? The goal auto-resumes once in 60s if still paused for this reason — or /goal resume now.",
|
|
4692
4717
|
}, ctx);
|
|
@@ -4714,6 +4739,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4714
4739
|
if (consecutiveAbortIterations >= 5) {
|
|
4715
4740
|
updateGoal({
|
|
4716
4741
|
status: "paused",
|
|
4742
|
+
pauseKind: "blocked",
|
|
4717
4743
|
pauseReason: "5 consecutive aborts (user interrupted)",
|
|
4718
4744
|
pauseSuggestedAction: "You interrupted 5 turns in a row — the goal stays paused until you /goal resume (or /goal cancel).",
|
|
4719
4745
|
}, ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.22",
|
|
4
4
|
"description": "Goal. Loop. Audit. Done. \u2014 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 \u2014 only the read tools needed to verify your goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|
package/schemas/goal.schema.json
CHANGED
|
@@ -46,6 +46,10 @@
|
|
|
46
46
|
"stopReason": { "type": "string" },
|
|
47
47
|
"pauseReason": { "type": "string" },
|
|
48
48
|
"pauseSuggestedAction": { "type": "string" },
|
|
49
|
+
"pauseKind": { "type": "string", "enum": ["decision", "error", "wait", "blocked"] },
|
|
50
|
+
"pauseOptions": { "type": "array", "items": { "type": "string" } },
|
|
51
|
+
"pauseRecommended": { "type": "number" },
|
|
52
|
+
"pauseResumeAt": { "type": "string" },
|
|
49
53
|
"interruptedAt": { "type": "string" },
|
|
50
54
|
"interruptedReason": { "type": "string" },
|
|
51
55
|
"auditInfraStreak": { "type": "number" },
|