pi-goal-list-loop-audit 0.28.27 → 0.28.29

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.
@@ -185,6 +185,10 @@ export interface Goal {
185
185
  * the retry resolves. Only consumed while paused with an "auditor quota:"
186
186
  * reason, so a stale value is unreachable by construction. */
187
187
  pendingCompletion?: { completionSummary?: string; verificationSummary?: string; at: string };
188
+ /** v0.28.28: provenance — who created this goal ("user", "list-cascade",
189
+ * "draft-confirmed", "draft-autoaccepted"). Ledgered on goal_created so
190
+ * "where did this come from" is answerable after the fact. */
191
+ createdVia?: string;
188
192
  /** v0.25.0 (contract item 22): auditor objections extracted as TODOs when
189
193
  * aggressiveMode keeps the goal active past the disapproval cap. Rendered
190
194
  * into every continuation prompt until the next audit clears them. */
@@ -482,35 +482,65 @@ const COMPACTION_GRACE_MS = 3 * 60_000;
482
482
  // brake (1m, 2m, 4m, 8m, 16m cap). A successful turn resets both.
483
483
  const ERROR_RETRY_LADDER_MS = [5_000, 15_000, 45_000, 90_000, 180_000];
484
484
  let errorBrakeStreak = 0;
485
- const SEND_REARM_LEDGER_EVERY = 600; // 600 × 50ms = 30s
486
- const SEND_REARM_ESCALATE_AT = 6000; // 6000 × 50ms = 5 minutes
485
+ const SEND_REARM_LEDGER_MILESTONES_MS = [2 * 60_000, 5 * 60_000, 10 * 60_000];
486
+ // v0.28.29: escalation is TIME-based and ACTIVITY-gated. A busy session is
487
+ // NORMAL — the user conversing, or one long subagent turn — and the old
488
+ // flat-50ms × 6000-count rule misread 5 minutes of busy as "wedged" and
489
+ // paused the goal (the polis field report). Escalate only after 15 minutes
490
+ // of failed sends AND no session activity in the last 5 minutes (a wedged
491
+ // queue shows no events at all; a busy one streams constantly).
492
+ const SEND_REARM_ESCALATE_AFTER_MS = 15 * 60_000;
493
+ const SEND_REARM_ESCALATE_SILENT_MS = 5 * 60_000;
494
+ let continuationRearmSince = 0;
495
+ let loopRearmSince = 0;
496
+ let continuationRearmMilestone = 0;
497
+ let loopRearmMilestone = 0;
498
+
499
+ /** v0.28.29: busy-retry cadence backs off — 50ms for the first beats
500
+ * (instant pickup right after a turn ends), then 250ms, 1s, 5s, 15s, 30s
501
+ * cap. agent_end reschedules independently, so the slow tail costs nothing
502
+ * in the common case; it only caps the ledger/CPU spam of a long busy stretch. */
503
+ function sendRearmDelayMs(streak: number): number {
504
+ if (streak <= 4) return 50;
505
+ if (streak <= 8) return 250;
506
+ if (streak <= 12) return 1_000;
507
+ if (streak === 13) return 5_000;
508
+ if (streak === 14) return 15_000;
509
+ return 30_000;
510
+ }
487
511
 
488
512
  function accountSendRearm(ctx: ExtensionContext, kind: "continuation" | "loop"): void {
489
513
  const streak = kind === "continuation" ? ++continuationRearmStreak : ++loopRearmStreak;
490
514
  if (streak === 1) {
515
+ if (kind === "continuation") { continuationRearmSince = Date.now(); continuationRearmMilestone = 0; } else { loopRearmSince = Date.now(); loopRearmMilestone = 0; }
491
516
  appendLedger(ctx.cwd, "send_rearm_start", { kind });
492
517
  return;
493
518
  }
494
- if (streak % SEND_REARM_LEDGER_EVERY === 0) {
495
- appendLedger(ctx.cwd, "send_rearm_storm", { kind, streak, minutes: Math.round((streak * BACKOFF_IDLE_RETRY_MS) / 60000) });
519
+ const since = kind === "continuation" ? continuationRearmSince : loopRearmSince;
520
+ const elapsed = Date.now() - since;
521
+ const milestone = kind === "continuation" ? continuationRearmMilestone : loopRearmMilestone;
522
+ if (milestone < SEND_REARM_LEDGER_MILESTONES_MS.length && elapsed >= SEND_REARM_LEDGER_MILESTONES_MS[milestone]!) {
523
+ if (kind === "continuation") continuationRearmMilestone++; else loopRearmMilestone++;
524
+ appendLedger(ctx.cwd, "send_rearm_storm", { kind, streak, minutes: Math.round(elapsed / 60000) });
496
525
  }
497
- if (streak >= SEND_REARM_ESCALATE_AT) {
498
- if (kind === "continuation") continuationRearmStreak = 0; else loopRearmStreak = 0;
526
+ if (elapsed >= SEND_REARM_ESCALATE_AFTER_MS && Date.now() - lastActivityAt >= SEND_REARM_ESCALATE_SILENT_MS) {
527
+ if (kind === "continuation") { continuationRearmStreak = 0; continuationRearmSince = 0; } else { loopRearmStreak = 0; loopRearmSince = 0; }
499
528
  escalateSendRearmStorm(ctx, kind);
500
529
  }
501
530
  }
502
531
 
503
532
  function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "loop"): void {
504
- // Same loud-terminal shape as escalateStallNow (v0.24.7): a 5-minute
505
- // re-arm storm means the session never goes idle for us — wedged queue
506
- // or a busy-forever session. Bounded and surfaced, not silent.
507
- const mins = Math.round((SEND_REARM_ESCALATE_AT * BACKOFF_IDLE_RETRY_MS) / 60000);
508
- appendLedger(ctx.cwd, "send_rearm_escalated", { kind, streak: SEND_REARM_ESCALATE_AT });
533
+ // Same loud-terminal shape as escalateStallNow (v0.24.7). v0.28.29: this
534
+ // only fires on a REAL wedge now (15m of failed sends + 5m of zero
535
+ // session activity) — busy-but-alive sessions never reach it.
536
+ const mins = Math.round(SEND_REARM_ESCALATE_AFTER_MS / 60000);
537
+ const silent = Math.round(SEND_REARM_ESCALATE_SILENT_MS / 60000);
538
+ appendLedger(ctx.cwd, "send_rearm_escalated", { kind, afterMinutes: mins, silentMinutes: silent });
509
539
  if (kind === "loop" && isLoopActive()) {
510
540
  clearLoopTimer();
511
- state.loop = { ...state.loop!, active: false, stopReason: `send-retry storm: ${mins}m of 50ms re-arms — the session never went idle for the loop turn. Restart pi, then /loop start again.` };
541
+ state.loop = { ...state.loop!, active: false, stopReason: `send-retry storm: ${mins}m of re-arms with no session activity for ${silent}m — the session is wedged. Restart pi, then /loop start again.` };
512
542
  persistState(ctx);
513
- ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m). Restart pi and /loop start.`, "warning");
543
+ ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m, session silent ${silent}m). Restart pi and /loop start.`, "warning");
514
544
  notifyExternal(ctx, "Loop stopped: send-retry storm.");
515
545
  return;
516
546
  }
@@ -518,10 +548,10 @@ function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "l
518
548
  updateGoal({
519
549
  status: "paused",
520
550
  pauseKind: "error",
521
- pauseReason: `send-retry storm: ${mins}m of 50ms re-arms — the session never went idle for the continuation`,
522
- pauseSuggestedAction: "The session never went idle for the send (wedged queue or permanently busy). Restart pi, then /goal resume.",
551
+ pauseReason: `send-retry storm: ${mins}m of re-arms with no session activity for ${silent}m — the session never went idle for the continuation`,
552
+ pauseSuggestedAction: "The session produced no events while the send retried (wedged queue). Restart pi, then /goal resume.",
523
553
  }, ctx);
524
- ctx.ui.notify(`Goal paused: send-retry storm (${mins}m). Restart pi, then /goal resume.`, "warning");
554
+ ctx.ui.notify(`Goal paused: send-retry storm (${mins}m, session silent ${silent}m). Restart pi, then /goal resume.`, "warning");
525
555
  notifyExternal(ctx, "Goal paused: send-retry storm.");
526
556
  }
527
557
  }
@@ -736,10 +766,10 @@ function sendContinuation(goalId: string): void {
736
766
  return;
737
767
  }
738
768
  if (!ctx.isIdle() || ctx.hasPendingMessages()) {
739
- // v0.28.5 (E3): count + ledger + escalate the re-arm storm.
740
769
  accountSendRearm(ctx, "continuation");
741
770
  continuationScheduledFor = goalId;
742
- continuationTimer = setTimeout(() => sendContinuation(goalId), BACKOFF_IDLE_RETRY_MS);
771
+ // v0.28.29: backing-off cadence (was flat 50ms — 6,000 spins in 5m).
772
+ continuationTimer = setTimeout(() => sendContinuation(goalId), sendRearmDelayMs(continuationRearmStreak));
743
773
  continuationTimer.unref?.();
744
774
  return;
745
775
  }
@@ -750,7 +780,7 @@ function sendContinuation(goalId: string): void {
750
780
  content: continuationPrompt(state.goal!),
751
781
  display: false,
752
782
  }, { triggerTurn: true, deliverAs: "followUp" });
753
- continuationRearmStreak = 0; // v0.28.5 (E3): a landed send clears the storm
783
+ continuationRearmStreak = 0; continuationRearmSince = 0; // v0.28.5 (E3): a landed send clears the storm
754
784
  appendLedger(ctx.cwd, "goal_continuation_sent", { goalId });
755
785
  } catch (err) {
756
786
  appendLedger(ctx.cwd, "goal_continuation_send_failed", { goalId, error: err instanceof Error ? err.message : String(err) });
@@ -895,18 +925,19 @@ function notifyPersistenceState(ctx: ExtensionContext): void {
895
925
  }
896
926
  }
897
927
 
898
- function setGoal(goal: Goal, ctx: ExtensionContext): void {
928
+ function setGoal(goal: Goal, ctx: ExtensionContext, via = "user"): void {
899
929
  // v0.28.14: never silently orphan a live goal — a paused/active goal
900
930
  // being replaced is archived honestly first (the old behavior left it in
901
931
  // goals/ but untracked: "older goals lying around leading to confusion").
902
932
  if (state.goal && state.goal.id !== goal.id && (state.goal.status === "active" || state.goal.status === "paused")) {
903
933
  archiveCurrentGoal(ctx, "aborted", `replaced by goal ${goal.id}`);
904
934
  }
935
+ goal.createdVia = via; // v0.28.28: provenance — answerable from the ledger + /glla log
905
936
  state = { ...state, goal }; // preserve list AND loop (v0.28.14: the bare reconstruction used to nuke a held/active loop whenever a goal was set)
906
937
  const file = writeGoalMd(ctx.cwd, goal);
907
938
  state.goal!.activePath = path.relative(ctx.cwd, file) || file;
908
939
  persistState(ctx);
909
- appendLedger(ctx.cwd, "goal_created", { goalId: goal.id, objective: goal.objective, policy: goal.policy });
940
+ appendLedger(ctx.cwd, "goal_created", { goalId: goal.id, objective: goal.objective, policy: goal.policy, via });
910
941
  }
911
942
 
912
943
  function updateGoal(patch: Partial<Goal>, ctx: ExtensionContext): void {
@@ -1142,7 +1173,7 @@ function fireReviewer(
1142
1173
  manual: opts.manual,
1143
1174
  ledgerEntries,
1144
1175
  sources,
1145
- enqueueListItems: (objectives) => enqueueItems(ctx, objectives, "reviewer"),
1176
+ enqueueListItems: (objectives) => enqueueItems(ctx, objectives, "reviewer", { autoActivate: loadSettings(ctx.cwd).autoResume === true }),
1146
1177
  proposeGoal: (objective, reason) => {
1147
1178
  try {
1148
1179
  extensionApi?.sendUserMessage(
@@ -1212,7 +1243,7 @@ function activateNextListItem(ctx: ExtensionContext, n = 1): boolean {
1212
1243
  state = { ...state, list: rest };
1213
1244
  const goal = createGoal(next.objective, ctx, "list");
1214
1245
  if (next.verificationContract) goal.verificationContract = next.verificationContract;
1215
- setGoal(goal, ctx);
1246
+ setGoal(goal, ctx, "list-cascade");
1216
1247
  iterationCounter = 0;
1217
1248
  consecutiveErrorIterations = 0;
1218
1249
  consecutiveAbortIterations = 0;
@@ -1631,7 +1662,7 @@ async function cmdTweak(args: string, ctx: ExtensionContext): Promise<void> {
1631
1662
  * contract extraction) → appended to the queue → persisted → first item
1632
1663
  * activated when nothing is running. Returns the count enqueued.
1633
1664
  */
1634
- function enqueueItems(ctx: ExtensionContext, texts: string[], source: string): number {
1665
+ function enqueueItems(ctx: ExtensionContext, texts: string[], source: string, opts?: { autoActivate?: boolean }): number {
1635
1666
  const items = texts.map((text) => {
1636
1667
  const extracted = extractVerificationContract(text);
1637
1668
  return { id: newGoalId(), objective: extracted.objective, verificationContract: extracted.verificationContract || undefined, addedAt: nowIso() };
@@ -1640,7 +1671,16 @@ function enqueueItems(ctx: ExtensionContext, texts: string[], source: string): n
1640
1671
  persistState(ctx);
1641
1672
  appendLedger(ctx.cwd, "list_imported", { source, count: items.length });
1642
1673
  if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
1643
- activateNextListItem(ctx);
1674
+ // v0.28.28: unsolicited sources (the reviewer) do NOT auto-start the
1675
+ // head unless autoResume is on — "I cancelled a goal and the next one
1676
+ // started itself" was the field complaint. User-driven imports keep
1677
+ // the immediate-start behavior (opts default true).
1678
+ if (opts?.autoActivate === false) {
1679
+ ctx.ui.notify(`Queued ${items.length} item(s) from ${source} — /list next when ready (auto-start is opt-in: /glla autoresume=on).`, "info");
1680
+ appendLedger(ctx.cwd, "list_autoactivation_held", { source, count: items.length });
1681
+ } else {
1682
+ activateNextListItem(ctx);
1683
+ }
1644
1684
  }
1645
1685
  return items.length;
1646
1686
  }
@@ -2001,8 +2041,8 @@ function sendLoopTurn(): void {
2001
2041
  if (!isLoopActive() || !extensionApi) return;
2002
2042
  const ctx = freshCtx();
2003
2043
  if (!ctx || !ctx.isIdle() || ctx.hasPendingMessages()) {
2004
- if (ctx) accountSendRearm(ctx, "loop"); // v0.28.5 (E3)
2005
- loopTimer = setTimeout(() => sendLoopTurn(), BACKOFF_IDLE_RETRY_MS);
2044
+ if (ctx) accountSendRearm(ctx, "loop");
2045
+ loopTimer = setTimeout(() => sendLoopTurn(), sendRearmDelayMs(loopRearmStreak)); // v0.28.29: backing-off cadence
2006
2046
  loopTimer.unref?.();
2007
2047
  return;
2008
2048
  }
@@ -2049,7 +2089,7 @@ function sendLoopTurn(): void {
2049
2089
  }, { triggerTurn: true, deliverAs: "followUp" });
2050
2090
  // v0.26.1: the send path is ledgered — the hegemon zombie spun 619
2051
2091
  // refires with zero visibility into whether sends were landing.
2052
- loopRearmStreak = 0; // v0.28.5 (E3): a landed turn clears the storm
2092
+ loopRearmStreak = 0; loopRearmSince = 0; // v0.28.5 (E3): a landed turn clears the storm
2053
2093
  appendLedger(ctx.cwd, "loop_turn_sent", { iteration: loop.iteration });
2054
2094
  } catch (err) {
2055
2095
  // stale API — next agent_end reschedules (but if none comes, the
@@ -3167,13 +3207,34 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
3167
3207
  persistState(liveCtx);
3168
3208
  appendLedger(liveCtx.cwd, "list_added", { id: item.id, objective: item.objective, drafted: true });
3169
3209
  if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
3210
+ // v0.28.28: an AUTO-ACCEPTED draft does not auto-start unless
3211
+ // autoResume is on — accepting a draft is not consent to start.
3212
+ if (autoAccept && loadSettings(liveCtx.cwd).autoResume !== true) {
3213
+ liveCtx.ui.notify(`Auto-accepted and QUEUED (autoResume off — not auto-started): ${extracted.objective.slice(0, 80)} — /list next when ready.`, "info");
3214
+ appendLedger(liveCtx.cwd, "list_autoactivation_held", { source: "draft-autoaccepted", count: 1 });
3215
+ return { content: [{ type: "text", text: "Draft accepted and added to the list, but NOT started (the user's autoResume setting is off — auto-accepted drafts queue, they don't auto-start). Do NOT begin work. Tell the user: /list next starts it." }], details: {} };
3216
+ }
3170
3217
  activateNextListItem(liveCtx);
3171
3218
  return { content: [{ type: "text", text: "Confirmed and activated (list was empty). Begin work now." }], details: {} };
3172
3219
  }
3173
3220
  return { content: [{ type: "text", text: `Confirmed and added to the list (${listQueue().length} waiting). It activates when the current goal completes.` }], details: {} };
3174
3221
  }
3175
3222
  const goal = createGoal(full, liveCtx);
3176
- setGoal(goal, liveCtx);
3223
+ setGoal(goal, liveCtx, autoAccept ? "draft-autoaccepted" : "draft-confirmed");
3224
+ // v0.28.28: auto-accepted goal drafts are created HELD when autoResume
3225
+ // is off — auto-accept delegates the Confirm click, not the decision
3226
+ // to start. Explicit user-confirmed drafts still start immediately.
3227
+ if (autoAccept && loadSettings(liveCtx.cwd).autoResume !== true) {
3228
+ updateGoal({
3229
+ status: "paused",
3230
+ pauseKind: "blocked",
3231
+ pauseReason: "auto-accepted draft — held for the user's go-ahead (autoResume off)",
3232
+ pauseSuggestedAction: "/goal resume to start · /goal cancel to drop · /glla autoresume=on starts auto-accepted drafts automatically",
3233
+ }, liveCtx);
3234
+ appendLedger(liveCtx.cwd, "draft_held", { goalId: goal.id, reason: "autoaccept-autoresume-off" });
3235
+ liveCtx.ui.notify(`Draft auto-accepted and HELD (autoResume off): ${goal.objective.slice(0, 80)} — /goal resume to start, /goal cancel to drop.`, "info");
3236
+ return { content: [{ type: "text", text: "Goal accepted but HELD (the user's autoResume setting is off — auto-accepted drafts do not auto-start). Do NOT begin work. Tell the user: /goal resume starts it, /goal cancel drops it." }], details: {} };
3237
+ }
3177
3238
  iterationCounter = 0;
3178
3239
  consecutiveErrorIterations = 0;
3179
3240
  consecutiveAbortIterations = 0;
@@ -4085,6 +4146,42 @@ function cmdStats(args: string, ctx: ExtensionContext): void {
4085
4146
  * log (.pi-glla/audits.jsonl). Default: last 10 verdicts, one line each.
4086
4147
  * "full" prints the latest report in full.
4087
4148
  */
4149
+ /**
4150
+ * v0.28.28: /glla log [N] — human-readable tail of the event ledger (the
4151
+ * forensic trail: who created/resumed/paused goals, from where). Skips the
4152
+ * high-frequency noise entries (state snapshots, re-arm internals) unless
4153
+ * "all" is passed. N defaults to 15.
4154
+ */
4155
+ const LOG_NOISE = new Set(["state", "send_rearm_start", "heartbeat_suppressed_tick"]);
4156
+ function cmdLog(args: string, ctx: ExtensionContext): void {
4157
+ const all = /\ball\b/.test(args);
4158
+ const nMatch = args.match(/\b(\d+)\b/);
4159
+ const n = Math.min(Math.max(parseInt(nMatch?.[1] ?? "15", 10) || 15, 1), 100);
4160
+ let entries: Array<{ type: string; at?: string; value?: any }> = [];
4161
+ try {
4162
+ entries = parseLedgerEntries(fs.readFileSync(ledgerPath(ctx.cwd), "utf-8"));
4163
+ } catch {
4164
+ ctx.ui.notify("No ledger yet — .pi-glla/active.jsonl doesn't exist.", "info");
4165
+ return;
4166
+ }
4167
+ const visible = all ? entries : entries.filter((e) => !LOG_NOISE.has(e.type));
4168
+ const tail = visible.slice(-n);
4169
+ if (tail.length === 0) {
4170
+ ctx.ui.notify("Ledger is empty (no non-noise events yet).", "info");
4171
+ return;
4172
+ }
4173
+ const lines = tail.map((e) => {
4174
+ const t = (e.at ?? "").slice(11, 19);
4175
+ const v = e.value ?? {};
4176
+ const detail = Object.entries(v)
4177
+ .filter(([k]) => k !== "goalId" && k !== "report")
4178
+ .map(([k, val]) => `${k}=${typeof val === "string" ? val.slice(0, 60) : JSON.stringify(val)?.slice(0, 60)}`)
4179
+ .join(" ");
4180
+ return `${t} ${e.type}${detail ? ` ${detail}` : ""}`;
4181
+ });
4182
+ ctx.ui.notify(`Ledger tail (last ${tail.length}${all ? "" : " non-noise"} events — /glla log <N> for more, /glla log all to include noise):\n${lines.join("\n")}`, "info");
4183
+ }
4184
+
4088
4185
  function cmdAudits(args: string, ctx: ExtensionContext): void {
4089
4186
  const full = /\bfull\b/.test(args);
4090
4187
  const all = /\b(?:all|global|log)\b/.test(args);
@@ -4136,6 +4233,12 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
4136
4233
  cmdAudits(trimmed.slice("audits".length).trim(), ctx);
4137
4234
  return;
4138
4235
  }
4236
+ // v0.28.28: /glla log [N] — the raw event trail, human-readable. "Log it
4237
+ // so we can look back and see where we are doing things wrong."
4238
+ if (/^log\b/.test(trimmed)) {
4239
+ cmdLog(trimmed.slice("log".length).trim(), ctx);
4240
+ return;
4241
+ }
4139
4242
  if (/^reviewer\b/.test(trimmed)) {
4140
4243
  await cmdReviewerSettings(ctx);
4141
4244
  return;
@@ -4526,6 +4629,7 @@ export default function (pi: ExtensionAPI): void {
4526
4629
  ["autoresume=", "default: hold when a session is loaded, auto-resume on reload/fork; on: always auto-resume; off: never"],
4527
4630
  ["decisionpopup=", "on|off: decision pauses pop the select() picker (default on; the widget card always lists the options, /goal decide reopens the picker)"],
4528
4631
  ["auditcap=", "N: pause goal after N consecutive auditor disapprovals (default 5, 0 = unlimited)"],
4632
+ ["log", "event-trail tail: /glla log [N] — who created/resumed/paused what, from where (v0.28.28)"],
4529
4633
  ["auditfeedbackchars=", "cap on executor-visible disapproval report chars (0 = full report, the default)"],
4530
4634
  ["aggressivemode=", "on: keep-going defaults — autoResume, cap 10, stuck 10, wedge off, quota auto-retry, cap→TODOs"],
4531
4635
  ["quotaretryminutes=", "N: minutes before auto-retrying a quota-exhausted auditor (default 60)"],
@@ -4628,8 +4732,8 @@ export default function (pi: ExtensionAPI): void {
4628
4732
  // v0.28.24: a compaction is LEGITIMATE busy time — reset the send-rearm
4629
4733
  // storm streaks (π-web nearly escalated a "send-retry storm" pause during
4630
4734
  // a 3.5-minute compact) and open the post-compaction stall grace.
4631
- continuationRearmStreak = 0;
4632
- loopRearmStreak = 0;
4735
+ continuationRearmStreak = 0; continuationRearmSince = 0;
4736
+ loopRearmStreak = 0; loopRearmSince = 0;
4633
4737
  compactionGraceUntil = Date.now() + COMPACTION_GRACE_MS;
4634
4738
  const settle = setTimeout(() => {
4635
4739
  const c = freshCtx();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.28.27",
3
+ "version": "0.28.29",
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",
@@ -54,6 +54,7 @@
54
54
  "interruptedReason": { "type": "string" },
55
55
  "auditInfraStreak": { "type": "number" },
56
56
  "pendingCompletion": { "type": "object" },
57
+ "createdVia": { "type": "string" },
57
58
  "activePath": { "type": "string" },
58
59
  "archivedPath": { "type": "string" },
59
60
  "usage": {