pi-goal-list-loop-audit 0.38.95 → 0.38.97

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.97 — pause_goal redirect: park-and-continue for user interruptions (2026-09-23)
4
+
5
+ - Field: a mid-goal redirect ("can we get to this first") parked via pause_goal, whose abort + "do NOT continue working" stranded the new task until the user nudged. New optional `redirect` param parks the goal WITHOUT ending the turn and orders the redirect worked immediately, goal resumed after; redirect parks ledger distinctly as `pause_goal_redirect`. Plain pauses still abort (audit/PAUSE-REDIRECT-2026-09-23.md).
6
+
7
+ ## 0.38.96 — /glla resume + /loop resume release the load hold before probing (2026-09-23)
8
+
9
+ - Field: a deterministic-400 manual hold cold-loaded (load hold engaged), then `/glla resume` consumed the manual hold and the goal stayed parked — the fired recovery probe died silently on the `supervisorPaused` gate, which includes `loadHoldAt`. No probe, no timer, stale resume text. `/glla resume` and `/loop resume` now release the cold-load hold at entry like `/goal resume` and agent-resume already do (audit/GLLA-RESUME-LOAD-HOLD-2026-09-23.md). Workaround on 0.38.95: run `/goal resume`.
10
+
3
11
  ## 0.38.95 — Stop the pause cycles: image-count clamp, ledger dedupe (2026-09-23)
4
12
 
5
13
  - The payload guard now clamps image blocks per request (default 4, the observed provider limit) as well as bytes — small screenshots fit the byte budget but 400'd on count (field 2026-09-21: `Image count 12 exceeds limit 4`). Oldest-first, keep-recent floor wins on conflict; eviction entries carry the remaining count (audit/PAUSES-2026-09-22.md).
package/docs/INDEX.md CHANGED
@@ -16,7 +16,7 @@ Policy contracts and recent changes live in the `audit/` directory of the
16
16
  failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
17
17
  handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
18
18
  shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
19
- v0.35.14–v0.38.95 continue through the supervisor freeze (`/glla pause`),
19
+ v0.35.14–v0.38.97 continue through the supervisor freeze (`/glla pause`),
20
20
  load hold, auditor picker parity, Windows launch fix, zombie-watchdog
21
21
  subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
22
22
  durable state-root selection, blank-until-resume auditor context, frozen
@@ -2481,6 +2481,16 @@ async function cmdGllaResume(ctx: ExtensionContext): Promise<void> {
2481
2481
  appendLedger(ctx.cwd, "supervisor_resume", { frozenMs });
2482
2482
  ctx.ui.notify(`Supervisor RESUMED after ${fmtElapsed(frozenMs)} — heartbeat re-arms, recovery probes, auto-resume, continuation dispatch, and auditor quiet notifies are live again.`, "info");
2483
2483
  }
2484
+ // v0.38.96 (field 2026-09-23, dracon-utilities): /glla resume carries the
2485
+ // same consent semantics as /goal resume, so it must release the cold-load
2486
+ // hold too — otherwise manuallyResumeMainModelRecovery consumes the manual
2487
+ // hold and its probe dies silently on the supervisorPaused gate (which
2488
+ // includes loadHoldAt): no probe, no timer, goal still parked.
2489
+ if (clearLoadHold(state)) {
2490
+ persistState(ctx);
2491
+ appendLedger(ctx.cwd, "load_hold_released", { via: "glla-resume" });
2492
+ ctx.ui.notify("Load hold released — automation is live again.", "info");
2493
+ }
2484
2494
  if (manuallyResumeMainModelRecovery(ctx)) return;
2485
2495
  if (state.mainModelRecovery?.retryAt || state.mainModelRecovery?.pendingModelSwitch) {
2486
2496
  clearMainModelRecoveryTimer();
@@ -1015,6 +1015,14 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
1015
1015
 
1016
1016
  if (!sub || sub === "resume") {
1017
1017
  releaseInitialSessionLoadBarrier();
1018
+ // v0.38.96 (field 2026-09-23): same release the held-loop path does
1019
+ // below, but at entry — the recovery branches return before it, and a
1020
+ // manual-resume probe fired under a load hold dies silently on the
1021
+ // supervisorPaused gate (hold consumed, loop still held, nothing armed).
1022
+ if (clearLoadHold(state)) {
1023
+ persistState(ctx);
1024
+ appendLedger(ctx.cwd, "load_hold_released", { via: "loop-resume" });
1025
+ }
1018
1026
  if (state.mainModelRecovery?.manualResumeRequired && state.mainModelRecovery.kind === "loop") {
1019
1027
  manuallyResumeMainModelRecovery(ctx);
1020
1028
  return;
@@ -2276,7 +2276,7 @@ function registerAgentTools(pi: any): void {
2276
2276
  pi.registerTool(defineTool({
2277
2277
  name: "pause_goal",
2278
2278
  label: "Pause goal",
2279
- description: "Pause the active goal with a reason and suggested action. Use when blocked on user input or unable to make progress. Pausing ABORTS the current turn immediately — after this call, stop; never keep working. 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 and pop a picker for the user. Time-gated waits (retry at a specific time) use kind=\"wait\" with resumeAt (ISO). Operational failures use kind=\"error\". Pauses that only wait on a running background subagent — its native completion wakes the goal, no manual action exists — use kind=\"standby\" so the card waits instead of demanding action. VOCABULARY (v0.28.24): decision options and reasons must reference REAL commands only — /goal resume, /goal cancel, /goal tweak \"<new text>\", /list remove N, /list next, /list resume, /loop stop, /loop resume. These all act on the ACTIVE goal/item: there is NO /goal drop and NO command takes a goal id. Never show goal ids to the user — name the thing ('the active goal', 'list item \"<short name>\"'); ids are internal plumbing the user cannot act on.",
2279
+ description: "Pause the active goal with a reason and suggested action. Use when blocked on user input or unable to make progress. Pausing ABORTS the current turn immediately — after this call, stop; never keep working. When the user interrupts with a NEW task ('do X first'), pass redirect=\"<their request>\" instead: the goal parks but the turn CONTINUES so you work X immediately — a plain pause strands the redirect until the user nudges. Never use redirect to dodge a real blocker, decision, or wait. 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 and pop a picker for the user. Time-gated waits (retry at a specific time) use kind=\"wait\" with resumeAt (ISO). Operational failures use kind=\"error\". Pauses that only wait on a running background subagent — its native completion wakes the goal, no manual action exists — use kind=\"standby\" so the card waits instead of demanding action. VOCABULARY (v0.28.24): decision options and reasons must reference REAL commands only — /goal resume, /goal cancel, /goal tweak \"<new text>\", /list remove N, /list next, /list resume, /loop stop, /loop resume. These all act on the ACTIVE goal/item: there is NO /goal drop and NO command takes a goal id. Never show goal ids to the user — name the thing ('the active goal', 'list item \"<short name>\"'); ids are internal plumbing the user cannot act on.",
2280
2280
  parameters: Type.Object({
2281
2281
  reason: Type.String({ description: "Why the work is paused" }),
2282
2282
  suggestedAction: Type.Optional(Type.String({ description: "What the user should do next" })),
@@ -2284,13 +2284,14 @@ function registerAgentTools(pi: any): void {
2284
2284
  options: Type.Optional(Type.Array(Type.String(), { description: "For kind=decision: the options the user picks between (one line each)" })),
2285
2285
  recommended: Type.Optional(Type.Number({ description: "For kind=decision: 1-based index of the recommended option" })),
2286
2286
  resumeAt: Type.Optional(Type.String({ description: "For kind=wait: ISO time the pause lifts (countdown is shown)" })),
2287
+ redirect: Type.Optional(Type.String({ description: "User redirect to work IMMEDIATELY in this same turn (quote or summarize the interrupting request). Parks the goal WITHOUT ending the turn; the result orders the redirect worked now and the goal resumed after. Only for genuine user redirects." })),
2287
2288
  }),
2288
2289
  async execute(_id, params, _signal, _onUpdate, execCtx) {
2289
2290
  const foreign1 = foreignToolGuard(execCtx);
2290
2291
  if (foreign1) return { content: [{ type: "text", text: foreign1 }], details: {} };
2291
2292
  const ctx = currentToolContext(execCtx);
2292
2293
  if (!ctx) return staleToolResult();
2293
- const p = params as { reason: string; suggestedAction?: string; kind?: "decision" | "error" | "wait" | "blocked" | "standby"; options?: string[]; recommended?: number; resumeAt?: string };
2294
+ const p = params as { reason: string; suggestedAction?: string; kind?: "decision" | "error" | "wait" | "blocked" | "standby"; options?: string[]; recommended?: number; resumeAt?: string; redirect?: string };
2294
2295
  // v0.35.15: a model that passes options but forgets kind="decision"
2295
2296
  // still gets the decision card — a non-empty options array IS the
2296
2297
  // decision intent; silently dropping it left the user with no picker.
@@ -2540,13 +2541,31 @@ function registerAgentTools(pi: any): void {
2540
2541
  // picker can own the screen. The impossible-drop advance is the one
2541
2542
  // exception: the queue already moved to the next item and its
2542
2543
  // continuation owns this turn — aborting would kill the hand-off.
2543
- if (!droppedImpossible) {
2544
+ // v0.38.97 (field 2026-09-23, dracon-platform): a user redirect is
2545
+ // park-and-CONTINUE, not park-and-stop. When redirect carries the
2546
+ // interrupting request, skip the abort so the turn survives and the
2547
+ // redirect runs NOW — aborting strands it until the user nudges.
2548
+ // Ledgered distinctly so redirect parks don't pollute abort
2549
+ // forensics. An impossible-drop still wins the result copy below.
2550
+ const redirect = (p.redirect ?? "").trim();
2551
+ if (!droppedImpossible && !redirect) {
2544
2552
  try {
2545
2553
  ctx.abort();
2546
2554
  appendLedger(ctx.cwd, "pause_goal_aborted_turn", { goalId: state.goal?.id, kind: p.kind ?? "blocked" });
2547
2555
  } catch (abortError) {
2548
2556
  appendLedger(ctx.cwd, "pause_goal_abort_failed", { goalId: state.goal?.id, error: abortError instanceof Error ? abortError.message : String(abortError) });
2549
2557
  }
2558
+ } else if (redirect && !droppedImpossible) {
2559
+ appendLedger(ctx.cwd, "pause_goal_redirect", { goalId: state.goal?.id, kind: p.kind ?? "blocked" });
2560
+ }
2561
+ if (redirect && !droppedImpossible) {
2562
+ return {
2563
+ content: [{
2564
+ type: "text",
2565
+ text: `Goal parked for a user redirect — do NOT work the goal. Work this NOW in the same turn, without ending the turn: ${redirect}. When it is done, ${activeGoalSurfaceCommand("resume")} the goal (or resume_goal) and continue.${waitClampNotice}`,
2566
+ }],
2567
+ details: {},
2568
+ };
2550
2569
  }
2551
2570
  return {
2552
2571
  content: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.95",
3
+ "version": "0.38.97",
4
4
  "description": "Mission control for long running pi work: interview drafted goals, an audited task queue, and metric or audit loops that run for hours. Every completion is rechecked by a detached auditor with raw evidence, while Confirm drafts, decision pauses, and consent gates keep you in charge.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "dracon",