jeo-code 0.8.26 → 0.8.27

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
@@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
8
8
 
9
+ ## [0.8.27] - 2026-07-16
10
+ _Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops._
11
+
12
+ ### Fixed
13
+ - **Exact repeat-read recovery after context elision** (`src/agent/engine.ts`) — after either provider context-overflow or refusal-result elision, the engine may replay its exact prior all-read-only tool batch once; mutation batches remain covered by the anti-spin guard.
14
+
15
+ ### Added
16
+ - **Targeted regression coverage** (`test/repeat-bounce-and-trim.test.ts`) — covers one-time exact all-read-only replay after both elision paths and mutation anti-spin preservation.
17
+
18
+ ### Verified
19
+ - Full `bun test` — 3079 pass / 0 fail across 304 files.
20
+ - `bun run typecheck` — no errors.
21
+
9
22
  ## [0.8.26] - 2026-07-14
10
23
  _Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed._
11
24
 
package/README.ja.md CHANGED
@@ -283,11 +283,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
283
283
  ## 変更履歴 (Changelog)
284
284
 
285
285
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
286
+ - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
286
287
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
287
288
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
288
289
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
289
290
  - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
290
- - **[0.8.22]** (2026-07-11) — A rescan of the working tree turned up a resume-fidelity gap plus two unwired TUI safety-net helpers left by a concurrent session; each was traced, wired where needed, and verified before shipping.
291
291
 
292
292
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
293
293
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -284,11 +284,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
284
284
  ## 변경 이력 (Changelog)
285
285
 
286
286
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
287
+ - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
287
288
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
288
289
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
289
290
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
290
291
  - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
291
- - **[0.8.22]** (2026-07-11) — A rescan of the working tree turned up a resume-fidelity gap plus two unwired TUI safety-net helpers left by a concurrent session; each was traced, wired where needed, and verified before shipping.
292
292
 
293
293
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
294
294
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -291,11 +291,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
291
291
  ## Changelog
292
292
 
293
293
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
294
+ - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
294
295
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
295
296
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
296
297
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
297
298
  - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
298
- - **[0.8.22]** (2026-07-11) — A rescan of the working tree turned up a resume-fidelity gap plus two unwired TUI safety-net helpers left by a concurrent session; each was traced, wired where needed, and verified before shipping.
299
299
 
300
300
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
301
301
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -283,11 +283,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
283
283
  ## 更新日志 (Changelog)
284
284
 
285
285
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
286
+ - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
286
287
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
287
288
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
288
289
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
289
290
  - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
290
- - **[0.8.22]** (2026-07-11) — A rescan of the working tree turned up a resume-fidelity gap plus two unwired TUI safety-net helpers left by a concurrent session; each was traced, wired where needed, and verified before shipping.
291
291
 
292
292
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
293
293
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.8.26",
3
+ "version": "0.8.27",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -108,6 +108,16 @@ export const DEFAULT_TOOLS: Record<string, ToolHandler> = {
108
108
  browser: createBrowserTool(),
109
109
  bisect: (a, cwd) => bisectTool(a.good, a.bad, a.run, !!a.invert, a.maxSteps, a.stepTimeoutMs, cwd),
110
110
  };
111
+
112
+ /** Tool names whose calls have no workspace side effects. Shared by execution,
113
+ * failure accounting, and the refusal-recovery replay exemption. */
114
+ const READONLY_TOOL_NAMES: Record<string, true> = {
115
+ read: true,
116
+ find: true,
117
+ search: true,
118
+ ls: true,
119
+ web_search: true,
120
+ };
111
121
  /** Tool-protocol description injected into the system prompt. */
112
122
  export const TOOL_PROTOCOL = [
113
123
  "You have these tools (call exactly ONE per step, or batch multiple independent calls):",
@@ -617,6 +627,10 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
617
627
  // resend is exactly as safe as the refusal ladder's rung-1 free resend below.
618
628
  let transientNetworkRetries = 0;
619
629
  let lastSig = "";
630
+ // Context recovery can erase evidence the model was explicitly told it may
631
+ // re-run. Consume this exact prior signature at the next valid tool decision;
632
+ // only an identical all-read-only batch can bypass the normal repeat correction once.
633
+ let elidedReadReplaySig: string | null = null;
620
634
  let repeatCount = 0;
621
635
  // Cycle guard (the A↔B ping-pong the exact-repeat guard cannot see): the recent
622
636
  // executed step signatures, as fixed-size digests. When a full window cycles
@@ -793,6 +807,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
793
807
  // this prompt — freeing real space beats keeping evidence that can be re-run.
794
808
  const res = trimToolResultsInPlace(history, { budgetTokens: Math.max(1, Math.floor(maxHistoryTokens / 2)), keepRecent: 2 });
795
809
  if (res.trimmed > 0) {
810
+ if (lastSig) elidedReadReplaySig = lastSig;
796
811
  ev.onNotice?.(`provider reported context overflow — elided ${res.trimmed} older tool result(s), retrying once`);
797
812
  continue; // free retry: the step counter is unchanged
798
813
  }
@@ -839,6 +854,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
839
854
  const strippedTurns = stripReasoningArtifactsInPlace(history);
840
855
  const parts: string[] = [];
841
856
  if (res.trimmed > 0) parts.push(`reset ${res.trimmed} tool result(s)`);
857
+ if (res.trimmed > 0 && lastSig) elidedReadReplaySig = lastSig;
842
858
  if (strippedTurns > 0) parts.push(`dropped thinking replay from ${strippedTurns} turn(s)`);
843
859
  ev.onNotice?.(
844
860
  parts.length
@@ -1161,7 +1177,16 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1161
1177
 
1162
1178
 
1163
1179
  const sig = callSigs.length === 1 ? callSigs[0] : hashSignature(callSigs.join(" | "));
1164
- if (sig === lastSig) repeatCount++;
1180
+ const replayingElidedReadOnlyBatch =
1181
+ elidedReadReplaySig === sig &&
1182
+ toolCalls.every(call => READONLY_TOOL_NAMES[call.tool] === true);
1183
+ // The exemption is tied to the next valid tool-call decision, not to a later
1184
+ // matching call. A changed, mutating, or unknown-tool batch consumes it too.
1185
+ elidedReadReplaySig = null;
1186
+ if (replayingElidedReadOnlyBatch) {
1187
+ repeatCount = 1;
1188
+ lastSig = sig;
1189
+ } else if (sig === lastSig) repeatCount++;
1165
1190
  else {
1166
1191
  repeatCount = 1;
1167
1192
  lastSig = sig;
@@ -1261,13 +1286,10 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1261
1286
  return { success, output };
1262
1287
  };
1263
1288
 
1264
- const READONLY_TOOLS = new Set(["read", "find", "search", "ls", "web_search"]);
1265
1289
  const WRITE_TOOLS = new Set(["write", "edit"]);
1266
1290
  // Batch grouping → concurrency plan (plan/gjc-inheritance.md cycle 12):
1267
1291
  // read group — consecutive read-only calls run in parallel (safe).
1268
1292
  // write group — consecutive write/edit calls to DISTINCT files run in
1269
- // parallel; a same-file (or path-less) collision opens a
1270
- // sequential boundary so ordered edits to one file stay ordered.
1271
1293
  // exclusive group — bash (and anything else) always runs alone, in order.
1272
1294
  // Reads and writes never share a group, so a read can never race a write.
1273
1295
  type ToolGroup = {
@@ -1288,7 +1310,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1288
1310
  for (let i = 0; i < toolCalls.length; i++) {
1289
1311
  const entry = { ...toolCalls[i], index: i };
1290
1312
  const last = groups[groups.length - 1];
1291
- if (READONLY_TOOLS.has(entry.tool)) {
1313
+ if (READONLY_TOOL_NAMES[entry.tool] === true) {
1292
1314
  if (last && last.kind === "read") last.calls.push(entry);
1293
1315
  else groups.push({ kind: "read", calls: [entry] });
1294
1316
  } else if (WRITE_TOOLS.has(entry.tool)) {
@@ -1442,11 +1464,10 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1442
1464
  // Read-only-only steps keep the old any-success rule.
1443
1465
  const nonTrivial = toolCalls
1444
1466
  .map((c, i) => ({ tool: c.tool, r: results[i] }))
1445
- .filter(x => !READONLY_TOOLS.has(x.tool) && x.r.executed);
1467
+ .filter(x => READONLY_TOOL_NAMES[x.tool] !== true && x.r.executed);
1446
1468
  const stepSuccess = nonTrivial.length > 0
1447
1469
  ? nonTrivial.some(x => x.r.success)
1448
1470
  : results.some(r => r.success);
1449
-
1450
1471
  if (stepSuccess) {
1451
1472
  consecutiveFailures = 0;
1452
1473
  } else if (++consecutiveFailures >= MAX_FAILURES) {
@@ -1696,7 +1696,12 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
1696
1696
  ? Math.floor(routeFallbackMaxRaw)
1697
1697
  : 3;
1698
1698
  for (let routeFallbackAttempt = 0; routeFallbackAttempt < routeFallbackMax; routeFallbackAttempt++) {
1699
- const routedFailureReason = !result.done ? routeFailureReason(result.doneReason) : null;
1699
+ // A loop guard is a local safety boundary, not a provider failure. A
1700
+ // fresh loop on an equivalent model loses the guard state and could
1701
+ // re-execute a previously skipped mutation.
1702
+ const routedFailureReason = !result.done && !result.stopClass
1703
+ ? routeFailureReason(result.doneReason)
1704
+ : null;
1700
1705
  if (!routedFailureReason) break;
1701
1706
  const fallback = await equivalentRouteFallback(fallbackBaseDecision, routedFailureReason, [...attemptedRouteModels]);
1702
1707
  if (!fallback) break;
@@ -0,0 +1,15 @@
1
+ // src/types/playwriter.d.ts
2
+ // Declaration file to allow TypeScript to recognize the optional Playwriter CLI module.
3
+ // This is only needed for the example automation script in scripts/playwriter-apply.ts.
4
+ // The actual Playwriter package is expected to be installed globally or run via npx.
5
+
6
+ declare module "playwriter" {
7
+ export class Playwriter {
8
+ /** Connect to the running Chrome extension bridge */
9
+ connect(): Promise<void>;
10
+ /** Open a new page (tab) in the connected browser */
11
+ newPage(): Promise<any>;
12
+ /** Disconnect from the bridge */
13
+ disconnect(): Promise<void>;
14
+ }
15
+ }