pi-goal-list-loop-audit 0.38.91 → 0.38.92

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.92 — Deterministic 400s hold; dethroned sessions named (2026-09-22)
4
+
5
+ - Main-model recovery no longer blind-retries deterministic provider refusals (HTTP 400 — e.g. 12 images against a 4-image model cap, which burned 7.5h of "retrying automatically"). Identical retries cannot succeed, so it holds for manual resume with fix directions (switch model or trim the request) in every mode; model switches retire the old episode's diagnostic so failover still probes (audit/DETERMINISTIC-400-AND-DETHRONED-2026-09-22.md).
6
+ - A session that loses the state-root race is now told exactly that ("owned by another session … not a subagent refusal") instead of being misdiagnosed as a subagent session.
7
+
3
8
  ## 0.38.91 — Tool descriptions enumerate plan/audit/verify/add (2026-09-21)
4
9
 
5
10
  - `/goal plan`, `/goal audit`, `/goal verify`, `/list plan`, and `/list add` existed but were undiscoverable from the tool descriptions. Both descriptions now enumerate them with one-line roles; a pin keeps the enumerations (audit/POST-090-IMPROVEMENT-SWEEP-2026-09-21.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.91 continue through the supervisor freeze (`/glla pause`),
19
+ v0.35.14–v0.38.92 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
@@ -31,6 +31,7 @@ import {
31
31
  mainModelFailureDelayMs,
32
32
  mainModelPrimaryProbeDelayMs,
33
33
  mainModelRetryDelayMs,
34
+ isDeterministicProviderError,
34
35
  isQuotaHorizonExempt,
35
36
  quotaResetSleepMs,
36
37
  isCompactionInFlightSince,
@@ -800,6 +801,15 @@ export function setMainModelRecoveryPause(ctx: ExtensionContext, recovery: MainM
800
801
  };
801
802
  const now = Date.now();
802
803
  const deadlineMs = normalized.autoRetryUntil ? Date.parse(normalized.autoRetryUntil) : Number.NaN;
804
+ // v0.38.92: deterministic 400s never schedule — an identical retry cannot
805
+ // succeed, so the ladder would burn (forever under aggressiveMode) while
806
+ // promising "retrying automatically". Hold for manual resume with the fix
807
+ // directions; the user's resume re-probes a CHANGED request. Checked
808
+ // before the horizon/quota logic so it wins in every mode.
809
+ if (isDeterministicProviderError(normalized.providerErrorDiagnostic ?? normalized.reason)) {
810
+ holdMainModelRecovery(ctx, normalized, "the provider refused the request itself (deterministic client error — e.g. too many images for this model). Switch model or trim the request, then resume");
811
+ return false;
812
+ }
803
813
  const requestedDelayMs = Math.max(1_000, delayMs);
804
814
  // v0.38.69 (Antigravity port): quota waits never park at the horizon —
805
815
  // a rate-limit/plan-quota wall is transient, so the hold below is
@@ -1627,7 +1637,11 @@ export async function recoverMainModelFromSendStorm(ctx: ExtensionContext, kind:
1627
1637
  if (!current) return;
1628
1638
  const recovery = state.mainModelRecovery;
1629
1639
  if (!recovery) return;
1630
- if (setMainModelRecoveryPause(ctx, { ...recovery, kind: kind === "loop" ? "loop" : "goal", active: current, resumeCurrent: true }, 1_000)) {
1640
+ // v0.38.92: the switch retires the old episode's diagnostic — it
1641
+ // describes the previous model's failure, and a stale deterministic
1642
+ // marker must not hold the fresh model's probe. Reason restarts from
1643
+ // the storm failure that triggered the switch.
1644
+ if (setMainModelRecoveryPause(ctx, { ...recovery, kind: kind === "loop" ? "loop" : "goal", active: current, resumeCurrent: true, reason: mainModelRecoveryReason(failure), providerErrorDiagnostic: undefined }, 1_000)) {
1631
1645
  flags.mainModelAbortForRecovery = true;
1632
1646
  try { ctx.abort(); } catch { /* best effort; recovery guard prevents re-send storms */ }
1633
1647
  scheduleMainModelRecoveryTimer(ctx, 1_000);
@@ -1884,8 +1884,15 @@ function isHostLifecycleSessionStart(event: unknown): boolean {
1884
1884
 
1885
1885
  const FOREIGN_SESSION_TOOL_MESSAGE =
1886
1886
  "This tool changes goal/loop/list state, which only the MAIN session owns — you are running in a subagent session. Report back to the main agent; it owns the goal and can call this tool.";
1887
- const FOREIGN_SESSION_TOOL_MESSAGE_WITH_STATE_ROOT_HINT =
1888
- `${FOREIGN_SESSION_TOOL_MESSAGE} If you also see the state-root read-only warning, close the other host or switch to sessionDir via /glla settings → State root, then start a fresh session.`;
1887
+ /** Refusal when the caller lost the workingDir state-root race (field
1888
+ * 2026-09-21: a dethroned MAIN session got the bare subagent message,
1889
+ * which misdiagnoses — it is not a worker, it lost the root to another
1890
+ * live session). Keeps the v0.35.72 pinned contract (MAIN-owner clause,
1891
+ * "state-root read-only", sessionDir cure) but replaces the false
1892
+ * "subagent session" diagnosis with the dethroned one. Replaces
1893
+ * FOREIGN_SESSION_TOOL_MESSAGE_WITH_STATE_ROOT_HINT. */
1894
+ const DETHRONED_SESSION_TOOL_MESSAGE =
1895
+ "This tool changes goal/loop/list state, which only the MAIN session owns — but this is not a subagent refusal: another live session holds this folder's state root, so this session is state-root read-only. Close the other session, start a fresh session here to take the root back (/glla owner inspects the holder), or switch to sessionDir via /glla settings → State root.";
1889
1896
 
1890
1897
  /** Refusal message when a state-mutating tool is called from a subagent session, else null. */
1891
1898
  function foreignToolGuard(execCtx: unknown): string | null {
@@ -1901,9 +1908,10 @@ function foreignToolGuard(execCtx: unknown): string | null {
1901
1908
  if (tryAbsorbHostSuccessor(c, "tool-call")) return null;
1902
1909
  if (isForeignCtx(c)) {
1903
1910
  // v0.35.72: when the cwd is also denied by the workingDir state-root
1904
- // owner, the two warnings co-occur. Correlate them so "MAIN became
1905
- // subagent and cannot be cured" has an actionable cure.
1906
- if (c && processOwnerDeniedCwd === c.cwd) return FOREIGN_SESSION_TOOL_MESSAGE_WITH_STATE_ROOT_HINT;
1911
+ // owner, the two warnings co-occur. v0.38.92: name the dethroned
1912
+ // diagnosis, not the subagent one — the caller is usually a former
1913
+ // main session that lost the root race (field 2026-09-21).
1914
+ if (c && processOwnerDeniedCwd === c.cwd) return DETHRONED_SESSION_TOOL_MESSAGE;
1907
1915
  return FOREIGN_SESSION_TOOL_MESSAGE;
1908
1916
  }
1909
1917
  // Post-park the owner is nulled; the dead-owner record means only the
@@ -323,6 +323,21 @@ export function isQuotaHorizonExempt(raw: string | undefined): boolean {
323
323
  return signal === "rate-limit" || signal === "plan-quota";
324
324
  }
325
325
 
326
+ // v0.38.92 (field 2026-09-21: 12 images vs a 4-image model cap retried 7.5h):
327
+ // a deterministic HTTP 400 client error can never succeed on an identical
328
+ // retry — only a changed request (fewer images, different model, fixed
329
+ // params) can. Retrying it on the ladder burns the horizon (or loops
330
+ // forever under aggressiveMode) while telling the user "retrying
331
+ // automatically". Match the error-type markers, never a bare "400" (token
332
+ // counts and limits quote 400s constantly). Callers hold for manual resume
333
+ // with the fix directions instead of scheduling.
334
+ const DETERMINISTIC_CLIENT_ERROR = /badrequesterror|invalid_request_error|"code"\s*:\s*"400"/i;
335
+
336
+ export function isDeterministicProviderError(raw: string | undefined): boolean {
337
+ if (typeof raw !== "string" || !raw.trim()) return false;
338
+ return DETERMINISTIC_CLIENT_ERROR.test(raw);
339
+ }
340
+
326
341
  /** v0.38.69 (Antigravity port): sleep-until-reset for quota-class failures
327
342
  * with an explicit upstream reset hint. Returns undefined (keep the blind
328
343
  * ladder) unless ALL hold: a rate-limit/plan-quota signal, an upstream
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.91",
3
+ "version": "0.38.92",
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",