flavor-code 1.4.0-beta.3 → 1.4.0-beta.5

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.
Files changed (37) hide show
  1. package/dist/agent/loop.d.ts +4 -2
  2. package/dist/{app-H7RYDMD3.js → app-SXNJDSI2.js} +3 -3
  3. package/dist/{chunk-NIKCPCJG.js → chunk-J7TLT6LR.js} +1098 -366
  4. package/dist/{chunk-3BHKW5A7.js → chunk-JZVC4477.js} +8 -2
  5. package/dist/{chunk-KBHWS7M6.js → chunk-KQMKY7C5.js} +4 -0
  6. package/dist/chunk-N5MXHJKD.js +107 -0
  7. package/dist/cli-main.js +20 -13
  8. package/dist/cli.js +51 -10
  9. package/dist/config/schema.d.ts +4 -0
  10. package/dist/context/manager.d.ts +9 -0
  11. package/dist/desktop/main.js +1442 -625
  12. package/dist/desktop-renderer/assets/index-nR67Z0XT.js +154 -0
  13. package/dist/desktop-renderer/assets/{interactive-terminal-j52Ty3QO.js → interactive-terminal-D2J4feTJ.js} +1 -1
  14. package/dist/desktop-renderer/index.html +1 -1
  15. package/dist/goal/classifier.d.ts +2 -0
  16. package/dist/goal/orchestrator.d.ts +23 -2
  17. package/dist/goal/planner.d.ts +2 -0
  18. package/dist/goal/store.d.ts +2 -0
  19. package/dist/goal/types.d.ts +14 -0
  20. package/dist/harness/journal.d.ts +2 -0
  21. package/dist/jobs/registry.d.ts +6 -0
  22. package/dist/{load-SLSL3IWR.js → load-F4AYLCF6.js} +1 -1
  23. package/dist/loop/orchestrator.d.ts +20 -0
  24. package/dist/loop/types.d.ts +7 -0
  25. package/dist/loop/verifier.d.ts +2 -0
  26. package/dist/production.d.ts +17 -0
  27. package/dist/sdk/index.js +4 -4
  28. package/dist/session/tree.d.ts +2 -0
  29. package/dist/ui/session.d.ts +11 -0
  30. package/dist/ui/transcript.d.ts +9 -0
  31. package/dist/utils/abort.d.ts +16 -0
  32. package/dist/utils/heap.d.ts +58 -0
  33. package/dist/utils/memory-restart.d.ts +42 -3
  34. package/dist/utils/rotation-census.d.ts +36 -0
  35. package/package.json +1 -1
  36. package/dist/chunk-CIVPKHF4.js +0 -53
  37. package/dist/desktop-renderer/assets/index-CYd5JkTR.js +0 -154
@@ -2,10 +2,15 @@
2
2
  import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
3
3
  import {
4
4
  MEMORY_RESTART_EXIT_CODE,
5
+ consumedMemoryRestartMarker,
6
+ markMemoryRotation,
5
7
  memoryRestartMarkerPath,
8
+ memoryRotationActive,
6
9
  nextMemoryRestartMarker,
7
- parseMemoryRestartMarker
8
- } from "./chunk-CIVPKHF4.js";
10
+ parseMemoryRestartMarker,
11
+ pendingContinuation,
12
+ rotationCooldownActive
13
+ } from "./chunk-N5MXHJKD.js";
9
14
  import {
10
15
  PERMISSION_MODES,
11
16
  PermissionModeSchema,
@@ -18,7 +23,7 @@ import {
18
23
  redactConfig,
19
24
  setProjectMcpServerDisabled,
20
25
  updateProtectedFile
21
- } from "./chunk-KBHWS7M6.js";
26
+ } from "./chunk-KQMKY7C5.js";
22
27
  import {
23
28
  AuditLogger,
24
29
  appendUsageLog,
@@ -1296,6 +1301,11 @@ function appendThinkingText(previous, delta) {
1296
1301
  // src/ui/transcript.ts
1297
1302
  var MAX_TRANSCRIPT_TURNS = 200;
1298
1303
  var MAX_TRANSCRIPT_CHARS = 2e6;
1304
+ var MAX_ACTIVE_TRANSCRIPT_BLOCKS = 160;
1305
+ var MAX_ACTIVE_ASSISTANT_CHARS = 64e3;
1306
+ var MAX_TRANSCRIPT_BLOCK_TEXT_CHARS = 16e3;
1307
+ var MAX_TRANSCRIPT_TOOL_VALUE_CHARS = 16e3;
1308
+ var MAX_TRANSCRIPT_PRESENTATION_CHARS = 32e3;
1299
1309
  function createTranscriptState() {
1300
1310
  return { completed: [], nextId: 1 };
1301
1311
  }
@@ -1656,11 +1666,9 @@ function applyTaskSnapshot(turn, snapshot4, includeTerminal = true) {
1656
1666
  else blocks[index] = block;
1657
1667
  }
1658
1668
  return {
1659
- ...turn,
1669
+ ...withBlocks(turn, blocks),
1660
1670
  taskSnapshot: snapshot4,
1661
- suppressedTaskIds: [...suppressed],
1662
- blocks,
1663
- statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
1671
+ suppressedTaskIds: [...suppressed]
1664
1672
  };
1665
1673
  }
1666
1674
  function terminalTaskIds(snapshot4) {
@@ -1674,13 +1682,10 @@ function upsertStatus(state, block) {
1674
1682
  if (state.active === void 0) return state;
1675
1683
  const blocks = [...state.active.blocks];
1676
1684
  const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
1677
- if (index < 0) blocks.push(block);
1678
- else blocks[index] = block;
1679
- return { ...state, active: {
1680
- ...state.active,
1681
- statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text),
1682
- blocks
1683
- } };
1685
+ const bounded2 = boundedTranscriptBlock(block);
1686
+ if (index < 0) blocks.push(bounded2);
1687
+ else blocks[index] = bounded2;
1688
+ return { ...state, active: withBlocks(state.active, blocks) };
1684
1689
  }
1685
1690
  function finishActive(state) {
1686
1691
  if (state.active === void 0) return state;
@@ -1704,16 +1709,12 @@ function attachThinking(turn, text) {
1704
1709
  ...block,
1705
1710
  thinkingText: appendThinkingText(block.thinkingText, text)
1706
1711
  };
1707
- return { ...turn, blocks };
1712
+ return withBlocks(turn, blocks);
1708
1713
  }
1709
1714
  function withoutModelActivity(turn, id) {
1710
1715
  const blocks = turn.blocks.filter((block) => block.kind !== "status" || block.activity !== "model" || id !== void 0 && block.id !== id);
1711
1716
  if (blocks.length === turn.blocks.length) return turn;
1712
- return {
1713
- ...turn,
1714
- blocks,
1715
- statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
1716
- };
1717
+ return withBlocks(turn, blocks);
1717
1718
  }
1718
1719
  function restoreTranscriptState(state) {
1719
1720
  const completed = state.completed.map(cloneTurn);
@@ -1815,15 +1816,18 @@ function cloneTurn(turn) {
1815
1816
  function upsertTurnStatus(turn, block) {
1816
1817
  const blocks = [...turn.blocks];
1817
1818
  const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
1818
- if (index < 0) blocks.push(block);
1819
- else blocks[index] = block;
1819
+ const bounded2 = boundedTranscriptBlock(block);
1820
+ if (index < 0) blocks.push(bounded2);
1821
+ else blocks[index] = bounded2;
1820
1822
  return withBlocks(turn, blocks);
1821
1823
  }
1822
1824
  function withBlocks(turn, blocks) {
1825
+ const bounded2 = blocks.length <= MAX_ACTIVE_TRANSCRIPT_BLOCKS ? blocks : blocks.slice(-MAX_ACTIVE_TRANSCRIPT_BLOCKS);
1823
1826
  return {
1824
1827
  ...turn,
1825
- blocks,
1826
- statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
1828
+ assistantText: tailText(turn.assistantText, MAX_ACTIVE_ASSISTANT_CHARS),
1829
+ blocks: bounded2,
1830
+ statusLines: bounded2.filter((block) => block.kind === "status").map((block) => block.text)
1827
1831
  };
1828
1832
  }
1829
1833
  function appendLine(text, line) {
@@ -1837,19 +1841,95 @@ function stripRetryBlocks(turn) {
1837
1841
  if (block.id.startsWith("structured-retry:")) return false;
1838
1842
  return true;
1839
1843
  });
1840
- return {
1841
- ...turn,
1842
- blocks,
1843
- statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text)
1844
- };
1844
+ return withBlocks(turn, blocks);
1845
1845
  }
1846
1846
  function addText(turn, text, onNewLine = false) {
1847
- const assistantText = onNewLine ? appendLine(turn.assistantText, text) : turn.assistantText + text;
1847
+ const assistantText = tailText(
1848
+ onNewLine ? appendLine(turn.assistantText, text) : turn.assistantText + text,
1849
+ MAX_ACTIVE_ASSISTANT_CHARS
1850
+ );
1848
1851
  const blocks = [...turn.blocks];
1849
1852
  const last = blocks[blocks.length - 1];
1850
- if (!onNewLine && last?.kind === "text") blocks[blocks.length - 1] = { kind: "text", text: last.text + text };
1851
- else blocks.push({ kind: "text", text });
1852
- return { ...turn, assistantText, blocks };
1853
+ if (!onNewLine && last?.kind === "text") {
1854
+ blocks[blocks.length - 1] = { kind: "text", text: tailText(last.text + text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) };
1855
+ } else {
1856
+ blocks.push({ kind: "text", text: tailText(text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) });
1857
+ }
1858
+ return withBlocks({ ...turn, assistantText }, blocks);
1859
+ }
1860
+ function boundedTranscriptBlock(block) {
1861
+ const presentation = block.presentation === void 0 ? void 0 : boundedTranscriptPresentation(block.presentation);
1862
+ const tool = block.tool === void 0 ? void 0 : {
1863
+ name: block.tool.name,
1864
+ input: boundedTranscriptValue(block.tool.input),
1865
+ ...block.tool.result === void 0 ? {} : {
1866
+ result: {
1867
+ ok: block.tool.result.ok,
1868
+ ...block.tool.result.output === void 0 ? {} : { output: boundedTranscriptValue(block.tool.result.output) },
1869
+ ...block.tool.result.content === void 0 ? {} : {
1870
+ content: tailText(block.tool.result.content, MAX_TRANSCRIPT_TOOL_VALUE_CHARS)
1871
+ },
1872
+ ...block.tool.result.presentation === void 0 ? {} : {
1873
+ presentation: boundedTranscriptPresentation(block.tool.result.presentation)
1874
+ },
1875
+ ...block.tool.result.additionalContext === void 0 ? {} : {
1876
+ additionalContext: block.tool.result.additionalContext.slice(-20).map((item) => tailText(item, MAX_TRANSCRIPT_TOOL_VALUE_CHARS))
1877
+ },
1878
+ ...block.tool.result.error === void 0 ? {} : { error: {
1879
+ code: block.tool.result.error.code,
1880
+ message: tailText(block.tool.result.error.message, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS)
1881
+ } }
1882
+ }
1883
+ }
1884
+ };
1885
+ return {
1886
+ ...block,
1887
+ text: tailText(block.text, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS),
1888
+ ...block.hint === void 0 ? {} : { hint: tailText(block.hint, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) },
1889
+ ...block.details === void 0 ? {} : { details: tailText(block.details, MAX_TRANSCRIPT_BLOCK_TEXT_CHARS) },
1890
+ ...presentation === void 0 ? {} : { presentation },
1891
+ ...tool === void 0 ? {} : { tool }
1892
+ };
1893
+ }
1894
+ function boundedTranscriptPresentation(presentation) {
1895
+ return jsonSize(presentation) <= MAX_TRANSCRIPT_PRESENTATION_CHARS ? presentation : {
1896
+ kind: "generic",
1897
+ title: "Tool output",
1898
+ summary: "Large presentation omitted from the live transcript; the model-facing result remains available in context."
1899
+ };
1900
+ }
1901
+ function boundedTranscriptValue(value) {
1902
+ const chars = jsonSize(value);
1903
+ if (chars <= MAX_TRANSCRIPT_TOOL_VALUE_CHARS) return value;
1904
+ const record = typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1905
+ const summary2 = { truncated: true, originalChars: chars };
1906
+ for (const key of ["path", "command", "id", "name", "exitCode", "bytes", "replacements"]) {
1907
+ const item = record?.[key];
1908
+ if (typeof item === "string") summary2[key] = tailText(item, 1e3);
1909
+ else if (typeof item === "number" || typeof item === "boolean" || item === null) summary2[key] = item;
1910
+ }
1911
+ return summary2;
1912
+ }
1913
+ function jsonSize(value) {
1914
+ try {
1915
+ return JSON.stringify(value)?.length ?? 0;
1916
+ } catch {
1917
+ return Number.POSITIVE_INFINITY;
1918
+ }
1919
+ }
1920
+ function tailText(text, limit) {
1921
+ if (text.length <= limit) return text;
1922
+ const marker = `\u2026 ${text.length - limit} earlier characters omitted \u2026
1923
+ `;
1924
+ return marker.length >= limit ? text.slice(-limit) : marker + text.slice(-(limit - marker.length));
1925
+ }
1926
+ function transcriptCensus(state) {
1927
+ const turns = [...state.completed, ...state.active === void 0 ? [] : [state.active]];
1928
+ return {
1929
+ turns: turns.length,
1930
+ blocks: turns.reduce((total, turn) => total + turn.blocks.length, 0),
1931
+ chars: jsonSize(state)
1932
+ };
1853
1933
  }
1854
1934
 
1855
1935
  // src/session/checkpoint.ts
@@ -2061,6 +2141,10 @@ var SessionHistory = class _SessionHistory {
2061
2141
  get leafId() {
2062
2142
  return this.#leafId;
2063
2143
  }
2144
+ /** Cheap retained-node count for the rotation census (no cloning). */
2145
+ get nodeCount() {
2146
+ return this.#nodes.length;
2147
+ }
2064
2148
  tree() {
2065
2149
  return this.#nodes.map((node) => structuredClone(node));
2066
2150
  }
@@ -3612,6 +3696,11 @@ var HELP = [
3612
3696
  "/co-work <pal> <goal> | status [id] | cancel <id> [reason]"
3613
3697
  ].join("\n");
3614
3698
  var MAX_STOP_DENIALS = 2;
3699
+ var RESUMABLE_TURN_BOUNDARIES = /* @__PURE__ */ new Set([
3700
+ "iteration_limit",
3701
+ "output_limit",
3702
+ "context_overflow"
3703
+ ]);
3615
3704
  var FlavorSession = class {
3616
3705
  #services;
3617
3706
  #active;
@@ -3635,6 +3724,7 @@ var FlavorSession = class {
3635
3724
  #pendingCoWorkStarts = [];
3636
3725
  #activeSteeringDurableIds = /* @__PURE__ */ new Set();
3637
3726
  #durableResumeStarted = false;
3727
+ #continuationResumeStarted = false;
3638
3728
  #permissionBaseline;
3639
3729
  #activeSubmission;
3640
3730
  constructor(services) {
@@ -3655,6 +3745,10 @@ var FlavorSession = class {
3655
3745
  queueSnapshot() {
3656
3746
  return this.#queue.snapshot();
3657
3747
  }
3748
+ /** True when start() admitted a persisted /loop or /goal continuation. */
3749
+ get rotationContinuationResumed() {
3750
+ return this.#continuationResumeStarted;
3751
+ }
3658
3752
  clearQueue() {
3659
3753
  const snapshot4 = this.#queue.clear();
3660
3754
  for (const kind of ["steer", "followUp"]) {
@@ -3804,6 +3898,7 @@ var FlavorSession = class {
3804
3898
  if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
3805
3899
  this.#started = true;
3806
3900
  this.#resumeDurableQueue();
3901
+ this.#resumeRotationContinuation();
3807
3902
  });
3808
3903
  return this.#startPromise;
3809
3904
  }
@@ -3837,6 +3932,7 @@ var FlavorSession = class {
3837
3932
  if (!initial) this.#outputQueuedSubmission(submission);
3838
3933
  initial = false;
3839
3934
  await this.#runSubmission(submission);
3935
+ if (this.#services.rotationPending?.() === true) return;
3840
3936
  pending.push(...this.#drainMessages("steer"), ...this.#drainMessages("followUp"));
3841
3937
  }
3842
3938
  }
@@ -3860,6 +3956,7 @@ var FlavorSession = class {
3860
3956
  this.#activeSubmission = submission;
3861
3957
  this.#interrupted = false;
3862
3958
  let outcome = "completed";
3959
+ let resumableBoundary;
3863
3960
  let assistantSummary = "";
3864
3961
  let deliverables = [];
3865
3962
  try {
@@ -3886,7 +3983,8 @@ var FlavorSession = class {
3886
3983
  skillNames,
3887
3984
  this.#services.managedToolCommands().map(({ name }) => name)
3888
3985
  );
3889
- if (command !== null) await this.#dispatch(command, controller.signal);
3986
+ if (submission.continuation !== void 0) await this.#runContinuation(submission.continuation, controller.signal);
3987
+ else if (command !== null) await this.#dispatch(command, controller.signal);
3890
3988
  else for await (const event of this.#services.run(prompt, controller.signal, {
3891
3989
  getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
3892
3990
  ...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
@@ -3895,7 +3993,10 @@ var FlavorSession = class {
3895
3993
  this.#services.output(event);
3896
3994
  if (event.type === "text") assistantSummary = `${assistantSummary}${event.text}`.slice(-2e3);
3897
3995
  if (event.type === "deliverables") deliverables = event.files.slice(0, 100);
3898
- if (event.type === "error") outcome = "failed";
3996
+ if (event.type === "error") {
3997
+ outcome = "failed";
3998
+ if (RESUMABLE_TURN_BOUNDARIES.has(event.error.code)) resumableBoundary = event.error.code;
3999
+ }
3899
4000
  }
3900
4001
  if (controller.signal.aborted) outcome = "cancelled";
3901
4002
  } catch (error) {
@@ -3936,6 +4037,13 @@ var FlavorSession = class {
3936
4037
  }
3937
4038
  }
3938
4039
  try {
4040
+ if (resumableBoundary !== void 0 && !this.#closed && !controller.signal.aborted) {
4041
+ const attempt = (submission.iterationContinuation ?? 0) + 1;
4042
+ const text = `[automatic continuation after ${resumableBoundary} boundary; segment ${attempt}]
4043
+ Continue the unfinished task from the current conversation and workspace state. Do not repeat completed operations. Inspect existing results, finish the remaining work, and verify it before stopping.`;
4044
+ this.#enqueueMessage("followUp", { text, displayText: text, iterationContinuation: attempt });
4045
+ this.#notice(`Agent ${resumableBoundary} boundary reached; durable continuation ${attempt} queued.`);
4046
+ }
3939
4047
  if (stopDecision?.decision === "deny" && typeof stopDecision.reason === "string" && stopDecision.reason.length > 0 && !this.#closed && outcome !== "cancelled" && outcome !== "denied") {
3940
4048
  if (this.#stopDenials < MAX_STOP_DENIALS) {
3941
4049
  this.#stopDenials += 1;
@@ -4132,7 +4240,7 @@ Address this now (for example run the required verification or fix), then finish
4132
4240
  });
4133
4241
  }
4134
4242
  #resumeDurableQueue() {
4135
- if (this.#durableResumeStarted || this.#closed || this.active || this.#pendingSubmissions > 0) return;
4243
+ if (this.#durableResumeStarted || this.#closed || this.active || this.#pendingSubmissions > 0 || this.#services.rotationPending?.() === true) return;
4136
4244
  if (!this.#queue.hasPending) return;
4137
4245
  this.#durableResumeStarted = true;
4138
4246
  const submissions = [...this.#drainMessages("steer"), ...this.#drainMessages("followUp")];
@@ -4144,6 +4252,33 @@ Address this now (for example run the required verification or fix), then finish
4144
4252
  });
4145
4253
  }
4146
4254
  }
4255
+ /**
4256
+ * After a heap rotation the relaunched process inherits the same session and
4257
+ * a marker continuation naming the /loop or /goal that was in flight. Resume
4258
+ * it once here so a multi-day autonomous run survives the rotation instead of
4259
+ * silently stopping at the boundary.
4260
+ */
4261
+ #resumeRotationContinuation() {
4262
+ if (this.#continuationResumeStarted || this.#closed) return;
4263
+ const continuation = this.#services.continuation?.();
4264
+ if (continuation === void 0) return;
4265
+ const supported = continuation.kind === "loop" ? this.#services.resumeLoop !== void 0 : this.#services.resumeGoal !== void 0;
4266
+ if (!supported) return;
4267
+ this.#continuationResumeStarted = true;
4268
+ const displayText = `Resuming the interrupted ${continuation.kind} ${continuation.id} after a heap rotation.`;
4269
+ this.#notice(displayText);
4270
+ this.#enqueueNormalizedSubmission({ text: displayText, displayText, continuation }).catch((error) => {
4271
+ this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
4272
+ });
4273
+ }
4274
+ async #runContinuation(continuation, signal) {
4275
+ const stream = continuation.kind === "loop" ? this.#services.resumeLoop?.(continuation.id, signal) : this.#services.resumeGoal?.(continuation.id, signal);
4276
+ if (stream === void 0) {
4277
+ this.#notice(`The runtime cannot resume the interrupted ${continuation.kind} ${continuation.id}; start it again with /${continuation.kind}.`);
4278
+ return;
4279
+ }
4280
+ for await (const event of stream) this.#services.output(event);
4281
+ }
4147
4282
  #ackDurable(submission) {
4148
4283
  if (submission.durableId === void 0) return;
4149
4284
  this.#services.durableQueue?.ack(submission.durableId);
@@ -4256,6 +4391,7 @@ function recoveredSubmission(value) {
4256
4391
  if (typeof input.text !== "string" || input.text.trim().length === 0) return void 0;
4257
4392
  if (typeof input.displayText !== "string") return void 0;
4258
4393
  if (input.controlOnly !== void 0 && typeof input.controlOnly !== "boolean") return void 0;
4394
+ if (input.iterationContinuation !== void 0 && (!Number.isSafeInteger(input.iterationContinuation) || input.iterationContinuation < 1)) return void 0;
4259
4395
  if (input.coWorkPlanningKey !== void 0 && typeof input.coWorkPlanningKey !== "string") return void 0;
4260
4396
  if (input.remoteOrigin !== void 0) {
4261
4397
  const origin = input.remoteOrigin;
@@ -5973,8 +6109,8 @@ var PalBrokerServer = class {
5973
6109
  });
5974
6110
  if (this.endpointLock === void 0) throw new Error(`Pals endpoint '${this.address}' is active or owned`);
5975
6111
  try {
5976
- const stat11 = await lstat4(this.address);
5977
- if (!stat11.isSocket()) throw new Error(`Refusing to unlink '${this.address}' because it is not a socket`);
6112
+ const stat12 = await lstat4(this.address);
6113
+ if (!stat12.isSocket()) throw new Error(`Refusing to unlink '${this.address}' because it is not a socket`);
5978
6114
  await unlink3(this.address);
5979
6115
  } catch (error) {
5980
6116
  if (error.code !== "ENOENT") throw error;
@@ -5987,8 +6123,8 @@ var PalBrokerServer = class {
5987
6123
  }
5988
6124
  async unlinkKnownSocket() {
5989
6125
  try {
5990
- const stat11 = await lstat4(this.address);
5991
- if (stat11.isSocket()) await unlink3(this.address);
6126
+ const stat12 = await lstat4(this.address);
6127
+ if (stat12.isSocket()) await unlink3(this.address);
5992
6128
  } catch (error) {
5993
6129
  if (error.code !== "ENOENT") throw error;
5994
6130
  }
@@ -6715,11 +6851,11 @@ async function ensureOnce(options) {
6715
6851
  }
6716
6852
 
6717
6853
  // src/production.ts
6718
- import { readFile as readFile23 } from "fs/promises";
6719
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
6854
+ import { readFile as readFile24 } from "fs/promises";
6855
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
6720
6856
  import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
6721
6857
  import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
6722
- import { basename as basename7, dirname as dirname17, join as join29, relative as relative22, resolve as resolve31, sep as sep22 } from "path";
6858
+ import { basename as basename7, dirname as dirname17, join as join30, relative as relative22, resolve as resolve31, sep as sep22 } from "path";
6723
6859
 
6724
6860
  // src/agent/subagents.ts
6725
6861
  import { z as z9 } from "zod";
@@ -7677,6 +7813,18 @@ var ContextManager = class _ContextManager {
7677
7813
  estimatedTokens() {
7678
7814
  return estimateMessageTokens(this.messagesForModel());
7679
7815
  }
7816
+ /** Retained sizes for the rotation census: what this context holds right now. */
7817
+ census() {
7818
+ const chars = (messages) => messages.reduce((sum, entry2) => sum + modelContentText(entry2.content).length, 0);
7819
+ return {
7820
+ messages: this.#messages.length,
7821
+ messageChars: chars(this.#messages),
7822
+ visibilityEntries: this.#visibilityLog.length,
7823
+ visibilityChars: this.#visibilityLog.reduce((sum, record) => sum + record.content.length, 0),
7824
+ transientEntries: this.#activeTransientSystem.size,
7825
+ transientChars: chars([...this.#activeTransientSystem.values()])
7826
+ };
7827
+ }
7680
7828
  needsCompaction() {
7681
7829
  if (modelVisibleText(this.messagesForModel()).length >= this.#compactAtChars) return true;
7682
7830
  return calculateContextPressure(this.#currentTokenUsage(), this.#compaction).shouldAutoCompact;
@@ -7717,10 +7865,6 @@ var ContextManager = class _ContextManager {
7717
7865
  }
7718
7866
  return microcompact.changed;
7719
7867
  }
7720
- if (this.#consecutiveAutoCompactFailures >= 3) {
7721
- rollbackMicrocompact();
7722
- return false;
7723
- }
7724
7868
  try {
7725
7869
  const compacted = await this.#compactConversation(signal);
7726
7870
  if (compacted) {
@@ -8184,10 +8328,10 @@ function within(root, candidate) {
8184
8328
 
8185
8329
  // src/context/summarizer.ts
8186
8330
  async function summarizeWithModel(options) {
8187
- const maxAttempts = options.maxPromptTooLongAttempts ?? 3;
8188
- if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) throw new Error("maxPromptTooLongAttempts must be positive");
8189
8331
  options.signal?.throwIfAborted();
8190
8332
  let groups = groupMessagesByApiRound(options.messages);
8333
+ const maxAttempts = options.maxPromptTooLongAttempts ?? Math.max(1, Math.ceil(Math.log2(Math.max(1, groups.length))) + 1);
8334
+ if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) throw new Error("maxPromptTooLongAttempts must be positive");
8191
8335
  let lastOverflow;
8192
8336
  let progress = 20;
8193
8337
  reportProgress(options.onProgress, progress);
@@ -8228,7 +8372,7 @@ async function summarizeWithModel(options) {
8228
8372
  if (terminalError.code !== "context_overflow") throw terminalError;
8229
8373
  lastOverflow = terminalError;
8230
8374
  if (attempt >= maxAttempts || groups.length <= 1) throw terminalError;
8231
- groups = groups.slice(1);
8375
+ groups = groups.slice(Math.max(1, Math.floor(groups.length / 2)));
8232
8376
  }
8233
8377
  throw lastOverflow ?? new Error("Compact summary failed");
8234
8378
  }
@@ -8245,8 +8389,75 @@ function reportProgress(callback, percentage) {
8245
8389
  }
8246
8390
  }
8247
8391
 
8248
- // src/agent/loop.ts
8392
+ // src/utils/heap.ts
8249
8393
  import { getHeapStatistics } from "v8";
8394
+ import { totalmem } from "os";
8395
+ var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
8396
+ var MEMORY_ROTATION_HEAP_RATIO = 0.6;
8397
+ var MEMORY_PRESSURE_RSS_RATIO = 0.75;
8398
+ var MEMORY_ROTATION_RSS_RATIO = 0.6;
8399
+ var ROTATION_PRECHECK_RATIO = 0.45;
8400
+ function readHeap() {
8401
+ const heapLimit = getHeapStatistics().heap_size_limit;
8402
+ const heapUsed = process.memoryUsage().heapUsed;
8403
+ return { heapUsed, heapLimit, ratio: heapLimit > 0 ? heapUsed / heapLimit : 0 };
8404
+ }
8405
+ function forceFullGc() {
8406
+ const gc = globalThis.gc;
8407
+ if (typeof gc !== "function") return false;
8408
+ gc();
8409
+ gc();
8410
+ return true;
8411
+ }
8412
+ function ratioOf(heapLimit, heapUsed) {
8413
+ return heapLimit > 0 ? heapUsed / heapLimit : 0;
8414
+ }
8415
+ function effectiveMemoryLimit() {
8416
+ const physical = totalmem();
8417
+ const constrained = process.constrainedMemory?.() ?? 0;
8418
+ return constrained > 0 ? Math.min(physical, constrained) : physical;
8419
+ }
8420
+ function withRss(reading, gcVerified) {
8421
+ const usage = process.memoryUsage();
8422
+ const memoryLimit = effectiveMemoryLimit();
8423
+ return {
8424
+ ...reading,
8425
+ gcVerified,
8426
+ rss: usage.rss,
8427
+ memoryLimit,
8428
+ rssRatio: ratioOf(memoryLimit, usage.rss),
8429
+ external: usage.external,
8430
+ arrayBuffers: usage.arrayBuffers
8431
+ };
8432
+ }
8433
+ function verifiedHeapPressure(threshold) {
8434
+ const raw = readHeap();
8435
+ const before = withRss(raw, false);
8436
+ const rssThreshold = threshold >= MEMORY_PRESSURE_HEAP_RATIO ? MEMORY_PRESSURE_RSS_RATIO : threshold;
8437
+ if ((raw.heapLimit <= 0 || raw.ratio < threshold) && (before.rssRatio ?? 0) < rssThreshold) return void 0;
8438
+ if (!forceFullGc()) return before;
8439
+ const heapUsed = process.memoryUsage().heapUsed;
8440
+ const ratio = ratioOf(raw.heapLimit, heapUsed);
8441
+ const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
8442
+ if (ratio < threshold && (verified.rssRatio ?? 0) < rssThreshold) return void 0;
8443
+ return verified;
8444
+ }
8445
+ function heapRotationNeeded() {
8446
+ const raw = readHeap();
8447
+ const before = withRss(raw, false);
8448
+ if ((raw.heapLimit <= 0 || raw.ratio < ROTATION_PRECHECK_RATIO) && (before.rssRatio ?? 0) < ROTATION_PRECHECK_RATIO) return void 0;
8449
+ if (!forceFullGc()) {
8450
+ return raw.ratio >= MEMORY_PRESSURE_HEAP_RATIO || (before.rssRatio ?? 0) >= MEMORY_PRESSURE_RSS_RATIO ? before : void 0;
8451
+ }
8452
+ const heapUsed = process.memoryUsage().heapUsed;
8453
+ const ratio = ratioOf(raw.heapLimit, heapUsed);
8454
+ const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
8455
+ if (ratio < MEMORY_ROTATION_HEAP_RATIO && (verified.rssRatio ?? 0) < MEMORY_ROTATION_RSS_RATIO) return void 0;
8456
+ return verified;
8457
+ }
8458
+ function heapSnapshotWarranted(_reading) {
8459
+ return process.env.FLAVOR_HEAP_SNAPSHOT_ON_ROTATE === "1";
8460
+ }
8250
8461
 
8251
8462
  // src/models/structured.ts
8252
8463
  import { z as z11 } from "zod";
@@ -8665,7 +8876,6 @@ function envMaxIterations() {
8665
8876
  if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
8666
8877
  return parsed;
8667
8878
  }
8668
- var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
8669
8879
  function memoryPressureError(heapUsed, heapLimit) {
8670
8880
  if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
8671
8881
  const mb = (bytes) => Math.round(bytes / 1048576);
@@ -8675,7 +8885,12 @@ function memoryPressureError(heapUsed, heapLimit) {
8675
8885
  };
8676
8886
  }
8677
8887
  function currentMemoryPressure() {
8678
- return memoryPressureError(process.memoryUsage().heapUsed, getHeapStatistics().heap_size_limit);
8888
+ const reading = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
8889
+ if (reading === void 0) return void 0;
8890
+ return memoryPressureError(reading.heapUsed, reading.heapLimit) ?? {
8891
+ code: "memory_pressure",
8892
+ message: `Process RSS reached ${Math.round((reading.rssRatio ?? 0) * 100)}% of the available memory limit; stopping before system OOM`
8893
+ };
8679
8894
  }
8680
8895
  var AgentLoop = class {
8681
8896
  #options;
@@ -10053,6 +10268,10 @@ var HarnessJournal = class {
10053
10268
  get records() {
10054
10269
  return this.#records.map((record) => structuredClone(record));
10055
10270
  }
10271
+ /** Retained record count for the rotation census. */
10272
+ get recordCount() {
10273
+ return this.#records.length;
10274
+ }
10056
10275
  admitQueue(kind, payload) {
10057
10276
  QueueKindSchema.parse(kind);
10058
10277
  const id = randomUUID8();
@@ -10329,19 +10548,38 @@ function jsonSafeRecord(value) {
10329
10548
  return JSON.parse(json);
10330
10549
  }
10331
10550
  function hashJson(value) {
10332
- return createHash6("sha256").update(stableJson(value)).digest("hex");
10333
- }
10334
- function recordHash(record) {
10335
- const { hash: _hash, ...unsigned } = record;
10336
- return hashJson(unsigned);
10551
+ const hash = createHash6("sha256");
10552
+ stableHashInto(hash, value);
10553
+ return hash.digest("hex");
10337
10554
  }
10338
- function stableJson(value) {
10339
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
10555
+ function stableHashInto(hash, value) {
10556
+ if (Array.isArray(value)) {
10557
+ hash.update("[");
10558
+ for (let index = 0; index < value.length; index += 1) {
10559
+ if (index > 0) hash.update(",");
10560
+ stableHashInto(hash, value[index]);
10561
+ }
10562
+ hash.update("]");
10563
+ return;
10564
+ }
10340
10565
  if (typeof value === "object" && value !== null) {
10341
10566
  const object = value;
10342
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
10567
+ hash.update("{");
10568
+ const keys = Object.keys(object).sort();
10569
+ for (let index = 0; index < keys.length; index += 1) {
10570
+ const key = keys[index];
10571
+ if (index > 0) hash.update(",");
10572
+ hash.update(`${JSON.stringify(key)}:`);
10573
+ stableHashInto(hash, object[key]);
10574
+ }
10575
+ hash.update("}");
10576
+ return;
10343
10577
  }
10344
- return JSON.stringify(value) ?? String(value);
10578
+ hash.update(JSON.stringify(value) ?? String(value));
10579
+ }
10580
+ function recordHash(record) {
10581
+ const { hash: _hash, ...unsigned } = record;
10582
+ return hashJson(unsigned);
10345
10583
  }
10346
10584
  function assertSessionId(value) {
10347
10585
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
@@ -12457,6 +12695,14 @@ var LoopStateSchema = z17.object({
12457
12695
  approvals: z17.array(LoopApprovalSchema)
12458
12696
  }).strict(),
12459
12697
  cycles: z17.array(LoopCycleEvidenceSchema),
12698
+ /** Completed worker awaiting host verification; avoids replaying tool work after rotation. */
12699
+ pendingCycle: z17.object({
12700
+ cycle: z17.number().int().positive(),
12701
+ startedAt: z17.string().datetime({ offset: true }),
12702
+ inputTokens: z17.number().int().nonnegative(),
12703
+ outputTokens: z17.number().int().nonnegative(),
12704
+ workerText: z17.string().max(16e3)
12705
+ }).strict().nullable().optional(),
12460
12706
  terminalReason: z17.string().optional()
12461
12707
  }).strict();
12462
12708
  var LoopEventSchema = z17.object({
@@ -12506,7 +12752,7 @@ function extendBudget(input, dimensions, approvedAt) {
12506
12752
  return LoopStateSchema.parse({
12507
12753
  ...state,
12508
12754
  updatedAt: approvedAt,
12509
- budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals }
12755
+ budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals: approvals.slice(-64) }
12510
12756
  });
12511
12757
  }
12512
12758
  function rejectBudget(input, terminalReason) {
@@ -12545,15 +12791,45 @@ var LoopOrchestrator = class {
12545
12791
  tokenCheckpoint: this.#options.config.maxTokens,
12546
12792
  approvals: []
12547
12793
  },
12548
- cycles: []
12794
+ cycles: [],
12795
+ pendingCycle: null
12549
12796
  });
12550
12797
  await this.#options.persistence.create(state);
12798
+ yield* this.#execute(state, request.goal, request.signal);
12799
+ }
12800
+ /**
12801
+ * Continue a loop whose process rotated onto a fresh heap. The persisted
12802
+ * state decides where to pick up; an interrupted cycle simply runs again.
12803
+ */
12804
+ async *resume(request) {
12805
+ const persistence = this.#options.persistence;
12806
+ if (persistence.load === void 0) throw new Error("Loop persistence does not support resume");
12807
+ let state;
12551
12808
  try {
12809
+ state = await persistence.load(request.loopId);
12810
+ } catch (error) {
12811
+ yield { type: "loop-terminal", loopId: request.loopId, status: "failed", reason: message(error) };
12812
+ return;
12813
+ }
12814
+ if (state.status !== "running") {
12815
+ const status = state.status;
12816
+ yield { type: "loop-terminal", loopId: state.loopId, status, reason: state.terminalReason ?? "The loop is not running." };
12817
+ return;
12818
+ }
12819
+ yield* this.#execute(state, state.goal, request.signal);
12820
+ }
12821
+ async *#execute(initialState, goal, signal) {
12822
+ const now = this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
12823
+ const loopId = initialState.loopId;
12824
+ let state = initialState;
12825
+ try {
12826
+ await this.#options.onMemoryCheckpoint?.(loopId);
12827
+ if (memoryRotationActive()) return;
12552
12828
  const workspaceResolution = await this.#options.prepareWorkspace({
12553
12829
  root: this.#options.workspace,
12554
12830
  loopId,
12555
- goal: request.goal,
12556
- signal: request.signal
12831
+ goal,
12832
+ signal
12557
12833
  });
12558
12834
  if (workspaceResolution.kind === "needs_human") {
12559
12835
  state = await this.#terminal(state, "needs_human", workspaceResolution.reason, now());
@@ -12571,67 +12847,90 @@ var LoopOrchestrator = class {
12571
12847
  cycleCheckpoint: state.budget.cycleCheckpoint,
12572
12848
  tokenCheckpoint: state.budget.tokenCheckpoint
12573
12849
  };
12574
- let previousVerification = plan.commands.length === 0 ? {
12850
+ const lastCompletedCycle = state.cycles.at(-1);
12851
+ let previousVerification = lastCompletedCycle?.verification ?? (plan.commands.length === 0 ? {
12575
12852
  passed: false,
12576
12853
  commands: [],
12577
12854
  summary: `${plan.needsHumanReason ?? "No deterministic verification command was found."} Inspect the project and establish a meaningful project-native deterministic check; never add a trivial pass-through verifier.`
12578
- } : void 0;
12579
- let previousFailureSignature;
12580
- let repeatedFailures = 0;
12855
+ } : void 0);
12856
+ let previousFailureSignature = lastCompletedCycle === void 0 ? void 0 : `${lastCompletedCycle.workspaceFingerprint}
12857
+ ${lastCompletedCycle.verification.summary}`;
12858
+ let repeatedFailures = previousFailureSignature === void 0 ? 0 : countTrailingFailureSignature(state.cycles, previousFailureSignature);
12581
12859
  while (state.status === "running") {
12582
- request.signal.throwIfAborted();
12583
- const cycle = state.budget.cyclesUsed + 1;
12584
- const startedAt = now();
12585
- await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
12586
- yield { type: "loop-cycle-start", loopId, cycle };
12587
- const prompt = buildLoopCyclePrompt({
12588
- goal: request.goal,
12589
- cycle,
12590
- ...previousVerification === void 0 ? {} : {
12591
- memory: `Previous host verification: ${previousVerification.summary}`,
12592
- verification: previousVerification
12593
- }
12594
- });
12595
- let cycleInputTokens = 0;
12596
- let cycleOutputTokens = 0;
12597
- let workerError;
12598
- let workerText = "";
12599
- for await (const event of this.#options.runWorker({
12600
- goal: request.goal,
12601
- cycle,
12602
- workspace: executionWorkspace.root,
12603
- prompt,
12604
- signal: request.signal
12605
- })) {
12606
- if (event.type === "usage") {
12607
- cycleInputTokens += event.inputTokens;
12608
- cycleOutputTokens += event.outputTokens;
12609
- }
12610
- if (event.type === "error") workerError = event.error.message;
12611
- if (event.type === "text") workerText += event.text;
12612
- if (this.#options.hallucinationGuard !== void 0) {
12613
- if (event.type === "tool-start") {
12614
- this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
12615
- } else if (event.type === "tool-end") {
12616
- this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
12860
+ signal.throwIfAborted();
12861
+ await this.#options.onCycleBoundary?.(loopId);
12862
+ if (memoryRotationActive()) return;
12863
+ const pendingCycle = state.pendingCycle;
12864
+ const cycle = pendingCycle?.cycle ?? state.budget.cyclesUsed + 1;
12865
+ const startedAt = pendingCycle?.startedAt ?? now();
12866
+ let cycleInputTokens = pendingCycle?.inputTokens ?? 0;
12867
+ let cycleOutputTokens = pendingCycle?.outputTokens ?? 0;
12868
+ let workerText = pendingCycle?.workerText ?? "";
12869
+ if (pendingCycle === void 0 || pendingCycle === null) {
12870
+ await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
12871
+ yield { type: "loop-cycle-start", loopId, cycle };
12872
+ const prompt = buildLoopCyclePrompt({
12873
+ goal,
12874
+ cycle,
12875
+ ...previousVerification === void 0 ? {} : {
12876
+ memory: `Previous host verification: ${previousVerification.summary}`,
12877
+ verification: previousVerification
12617
12878
  }
12879
+ });
12880
+ let workerError;
12881
+ let workerMemoryPressure = false;
12882
+ let workerIterationLimit = false;
12883
+ for await (const event of this.#options.runWorker({
12884
+ goal,
12885
+ cycle,
12886
+ workspace: executionWorkspace.root,
12887
+ prompt,
12888
+ signal
12889
+ })) {
12890
+ if (event.type === "usage") {
12891
+ cycleInputTokens += event.inputTokens;
12892
+ cycleOutputTokens += event.outputTokens;
12893
+ }
12894
+ if (event.type === "error") {
12895
+ workerError = event.error;
12896
+ if (event.error.code === "memory_pressure") workerMemoryPressure = true;
12897
+ if (event.error.code === "iteration_limit") workerIterationLimit = true;
12898
+ }
12899
+ if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
12900
+ if (this.#options.hallucinationGuard !== void 0) {
12901
+ if (event.type === "tool-start") {
12902
+ this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
12903
+ } else if (event.type === "tool-end") {
12904
+ this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
12905
+ }
12906
+ }
12907
+ yield { type: "worker-event", event };
12908
+ }
12909
+ if (workerMemoryPressure) return;
12910
+ if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError(workerError)) {
12911
+ const status = workerError.code === "cancelled" ? "cancelled" : "failed";
12912
+ state = LoopStateSchema.parse({
12913
+ ...state,
12914
+ budget: {
12915
+ ...state.budget,
12916
+ cyclesUsed: cycle,
12917
+ inputTokens: state.budget.inputTokens + cycleInputTokens,
12918
+ outputTokens: state.budget.outputTokens + cycleOutputTokens
12919
+ }
12920
+ });
12921
+ state = await this.#terminal(state, status, workerError.message, now());
12922
+ yield { type: "loop-terminal", loopId, status, reason: workerError.message };
12923
+ return;
12618
12924
  }
12619
- yield { type: "worker-event", event };
12620
- }
12621
- if (workerError !== void 0) {
12622
12925
  state = LoopStateSchema.parse({
12623
12926
  ...state,
12624
- budget: {
12625
- ...state.budget,
12626
- cyclesUsed: cycle,
12627
- inputTokens: state.budget.inputTokens + cycleInputTokens,
12628
- outputTokens: state.budget.outputTokens + cycleOutputTokens
12629
- }
12927
+ pendingCycle: { cycle, startedAt, inputTokens: cycleInputTokens, outputTokens: cycleOutputTokens, workerText },
12928
+ updatedAt: now()
12630
12929
  });
12631
- state = await this.#terminal(state, "failed", workerError, now());
12632
- yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
12633
- return;
12930
+ await this.#options.persistence.save(state);
12634
12931
  }
12932
+ await this.#options.onMemoryCheckpoint?.(loopId);
12933
+ if (memoryRotationActive()) return;
12635
12934
  if (plan.commands.length === 0) {
12636
12935
  plan = await this.#options.inferVerification(executionWorkspace.root);
12637
12936
  if (plan.commands.length > 0) {
@@ -12651,8 +12950,22 @@ var LoopOrchestrator = class {
12651
12950
  passed: false,
12652
12951
  commands: [],
12653
12952
  summary: plan.needsHumanReason ?? "No deterministic verification command was found after the discovery cycle."
12654
- } : await this.#options.runVerifier(plan, executionWorkspace.root, request.signal);
12953
+ } : await this.#options.runVerifier(plan, executionWorkspace.root, signal);
12954
+ await this.#options.onMemoryCheckpoint?.(loopId);
12955
+ if (memoryRotationActive()) return;
12655
12956
  const fingerprint2 = await this.#options.fingerprint(executionWorkspace.root);
12957
+ let guardWarnings = [];
12958
+ if (evidence.passed && this.#options.hallucinationGuard !== void 0) {
12959
+ try {
12960
+ await this.#options.onMemoryCheckpoint?.(loopId);
12961
+ if (memoryRotationActive()) return;
12962
+ const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
12963
+ await this.#options.onMemoryCheckpoint?.(loopId);
12964
+ if (memoryRotationActive()) return;
12965
+ guardWarnings = [...report.blockingReasons, ...report.warnings];
12966
+ } catch {
12967
+ }
12968
+ }
12656
12969
  const completedAt = now();
12657
12970
  state = LoopStateSchema.parse({
12658
12971
  ...state,
@@ -12671,7 +12984,8 @@ var LoopOrchestrator = class {
12671
12984
  outputTokens: cycleOutputTokens,
12672
12985
  workspaceFingerprint: fingerprint2,
12673
12986
  verification: evidence
12674
- }]
12987
+ }].slice(-64),
12988
+ pendingCycle: null
12675
12989
  });
12676
12990
  await this.#options.persistence.save(state);
12677
12991
  await this.#options.persistence.append({
@@ -12689,20 +13003,6 @@ var LoopOrchestrator = class {
12689
13003
  return;
12690
13004
  }
12691
13005
  if (evidence.passed) {
12692
- let guardWarnings = [];
12693
- if (this.#options.hallucinationGuard !== void 0) {
12694
- try {
12695
- const report = await this.#options.hallucinationGuard.evaluate(request.goal, workerText);
12696
- if (!report.passed) {
12697
- const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
12698
- state = await this.#terminal(state, "failed", guardReason, now());
12699
- yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
12700
- return;
12701
- }
12702
- guardWarnings = report.warnings;
12703
- } catch {
12704
- }
12705
- }
12706
13006
  const reason = [evidence.summary, ...guardWarnings].filter(Boolean).join("; ");
12707
13007
  state = await this.#terminal(state, "succeeded", reason, now());
12708
13008
  yield { type: "loop-terminal", loopId, status: "succeeded", reason };
@@ -12713,8 +13013,8 @@ ${evidence.summary}`;
12713
13013
  repeatedFailures = failureSignature === previousFailureSignature ? repeatedFailures + 1 : 1;
12714
13014
  previousFailureSignature = failureSignature;
12715
13015
  previousVerification = evidence;
12716
- if (repeatedFailures >= 3) {
12717
- const reason = "The same verification failure repeated three times without material workspace progress.";
13016
+ if (this.#options.maxNoProgressCycles !== void 0 && repeatedFailures >= this.#options.maxNoProgressCycles) {
13017
+ const reason = `The same verification failure repeated ${this.#options.maxNoProgressCycles} times without material workspace progress.`;
12718
13018
  state = await this.#terminal(state, "no_progress", reason, now());
12719
13019
  yield { type: "loop-terminal", loopId, status: "no_progress", reason };
12720
13020
  return;
@@ -12728,7 +13028,7 @@ ${evidence.summary}`;
12728
13028
  cycleCheckpoint: state.budget.cycleCheckpoint,
12729
13029
  tokenCheckpoint: state.budget.tokenCheckpoint
12730
13030
  };
12731
- const confirmation = await this.#options.confirmBudget(state, decision.dimensions, request.signal);
13031
+ const confirmation = await this.#options.confirmBudget(state, decision.dimensions, signal);
12732
13032
  if (confirmation === "unavailable") {
12733
13033
  const reason = "Loop budget extension requires user confirmation.";
12734
13034
  state = await this.#terminal(state, "needs_human", reason, now());
@@ -12755,8 +13055,9 @@ ${evidence.summary}`;
12755
13055
  }
12756
13056
  }
12757
13057
  } catch (error) {
12758
- const status = request.signal.aborted ? "cancelled" : "failed";
12759
- const reason = request.signal.aborted ? "Loop cancelled by user." : message(error);
13058
+ if (memoryRotationActive()) return;
13059
+ const status = signal.aborted ? "cancelled" : "failed";
13060
+ const reason = signal.aborted ? "Loop cancelled by user." : message(error);
12760
13061
  state = await this.#terminal(state, status, reason, now());
12761
13062
  yield { type: "loop-terminal", loopId, status, reason };
12762
13063
  }
@@ -12774,6 +13075,19 @@ ${evidence.summary}`;
12774
13075
  return state;
12775
13076
  }
12776
13077
  };
13078
+ function isResumableWorkerError(error) {
13079
+ return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
13080
+ }
13081
+ function countTrailingFailureSignature(cycles, signature) {
13082
+ let count = 0;
13083
+ for (let index = cycles.length - 1; index >= 0; index -= 1) {
13084
+ const cycle = cycles[index];
13085
+ if (`${cycle.workspaceFingerprint}
13086
+ ${cycle.verification.summary}` !== signature) break;
13087
+ count += 1;
13088
+ }
13089
+ return count;
13090
+ }
12777
13091
 
12778
13092
  // src/goal/orchestrator.ts
12779
13093
  import { createHash as createHash11 } from "crypto";
@@ -12841,6 +13155,11 @@ var GoalStateSchema = z18.object({
12841
13155
  planPath: z18.string().nullable(),
12842
13156
  verifyRounds: z18.number().int().min(0),
12843
13157
  workerRounds: z18.number().int().min(0),
13158
+ /** Worker output checkpoint used to resume verification without rerunning tools. */
13159
+ pendingVerification: z18.object({
13160
+ round: z18.number().int().positive(),
13161
+ finalResponse: z18.string().max(16e3)
13162
+ }).strict().nullable().optional(),
12844
13163
  lastGaps: z18.array(GapSchema),
12845
13164
  gapFingerprint: z18.string(),
12846
13165
  stallStreak: z18.number().int().min(0),
@@ -12886,6 +13205,7 @@ ${objective}`
12886
13205
  ].join("\n");
12887
13206
  }
12888
13207
  async function runPlanner(options) {
13208
+ await options.onProgress?.();
12889
13209
  const { adapter, model } = options.registry.get(options.modelId);
12890
13210
  const prompt = buildPlannerPrompt(options.objective);
12891
13211
  const messages = [{ role: "user", content: prompt }];
@@ -12897,6 +13217,7 @@ async function runPlanner(options) {
12897
13217
  tools: [],
12898
13218
  ...options.signal === void 0 ? {} : { signal: options.signal }
12899
13219
  })) {
13220
+ await options.onProgress?.();
12900
13221
  if (event.type === "text") text += event.text;
12901
13222
  else if (event.type === "error") throw event.error;
12902
13223
  else if (event.type === "done") {
@@ -13003,7 +13324,7 @@ async function runClassifier(evidence, plan, options) {
13003
13324
  const skepticResults = await Promise.all(
13004
13325
  Array.from(
13005
13326
  { length: n },
13006
- (_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal)
13327
+ (_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal, options.onProgress)
13007
13328
  )
13008
13329
  );
13009
13330
  const verdicts = [];
@@ -13020,7 +13341,8 @@ async function runClassifier(evidence, plan, options) {
13020
13341
  }
13021
13342
  return aggregate(verdicts);
13022
13343
  }
13023
- async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
13344
+ async function runOneSkeptic(evidence, plan, index, adapter, model, signal, onProgress) {
13345
+ await onProgress?.();
13024
13346
  const prompt = buildSkepticPrompt(evidence, plan, index);
13025
13347
  const messages = [{ role: "user", content: prompt }];
13026
13348
  let text = "";
@@ -13030,6 +13352,7 @@ async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
13030
13352
  tools: [],
13031
13353
  ...signal === void 0 ? {} : { signal }
13032
13354
  })) {
13355
+ await onProgress?.();
13033
13356
  if (event.type === "text") text += event.text;
13034
13357
  else if (event.type === "error") throw event.error;
13035
13358
  else if (event.type === "done") break;
@@ -13184,7 +13507,7 @@ var GoalOrchestrator = class {
13184
13507
  async *run(request) {
13185
13508
  const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
13186
13509
  const createdAt = timestamp2();
13187
- let state = {
13510
+ const state = {
13188
13511
  id: this.#options.idFactory?.() ?? `goal-${Date.now().toString(36)}`,
13189
13512
  objective: request.goal,
13190
13513
  phase: "planning",
@@ -13193,6 +13516,7 @@ var GoalOrchestrator = class {
13193
13516
  planPath: null,
13194
13517
  verifyRounds: 0,
13195
13518
  workerRounds: 0,
13519
+ pendingVerification: null,
13196
13520
  lastGaps: [],
13197
13521
  gapFingerprint: "",
13198
13522
  stallStreak: 0,
@@ -13201,91 +13525,164 @@ var GoalOrchestrator = class {
13201
13525
  createdAt,
13202
13526
  updatedAt: createdAt
13203
13527
  };
13528
+ yield* this.#execute(state, request.signal);
13529
+ }
13530
+ /**
13531
+ * Continue a goal whose process rotated onto a fresh heap. The persisted
13532
+ * state decides where to pick up; an interrupted round simply runs again.
13533
+ */
13534
+ async *resume(request) {
13535
+ const persistence = this.#options.persistence;
13536
+ if (persistence === void 0 || persistence.load === void 0) {
13537
+ throw new Error("Goal persistence does not support resume");
13538
+ }
13539
+ let state;
13540
+ try {
13541
+ state = await persistence.load(request.goalId);
13542
+ } catch (error) {
13543
+ yield { type: "goal-failed", reason: message(error) };
13544
+ return;
13545
+ }
13546
+ if (state.status !== "active" && state.status !== "not_achieved") {
13547
+ yield { type: "goal-failed", reason: `Goal "${state.id}" is ${state.status}; it cannot be resumed.` };
13548
+ return;
13549
+ }
13550
+ yield { type: "goal-resumed", goalId: state.id, round: resumeStartRound(state) };
13551
+ yield* this.#execute(state, request.signal);
13552
+ }
13553
+ async *#execute(initialState, signal) {
13554
+ const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
13555
+ let state = initialState;
13204
13556
  const persistState = async (patch) => {
13205
13557
  state = { ...state, ...patch, updatedAt: timestamp2() };
13206
13558
  await this.#options.persistence?.save(state);
13207
13559
  };
13560
+ const memoryCheckpoint = async () => {
13561
+ await this.#options.onMemoryCheckpoint?.(state.id);
13562
+ if (memoryRotationActive()) throw new Error("Heap rotation requested");
13563
+ };
13208
13564
  await persistState();
13209
13565
  const workspace = this.#options.workspace;
13210
13566
  let plan;
13211
13567
  let planPath;
13212
- try {
13213
- request.signal.throwIfAborted();
13214
- plan = await runPlanner({
13215
- registry: this.#options.registry,
13216
- modelId: this.#options.plannerModelId,
13217
- objective: request.goal,
13218
- signal: request.signal
13219
- });
13220
- planPath = await writePlanFile(workspace, plan);
13221
- await persistState({ plan, planPath, contractHash: contractHash(request.goal, plan) });
13222
- yield { type: "goal-plan-created", plan, planPath };
13223
- } catch (error) {
13224
- const reason = `Goal planning failed: ${message(error)}`;
13225
- await persistState({ phase: "complete", status: "failed" });
13226
- yield { type: "goal-plan-failed", reason };
13227
- yield { type: "goal-failed", reason };
13228
- return;
13229
- }
13230
- let priorGaps = [];
13231
- let priorFingerprint = "";
13232
- let stallStreak = 0;
13233
- for (let round = 1; round <= this.#options.maxRounds; round++) {
13234
- request.signal.throwIfAborted();
13235
- const workerPrompt = buildWorkerPrompt(request.goal, plan, priorGaps, round);
13236
- await persistState({
13237
- phase: "executing",
13238
- status: "active",
13239
- workerRounds: round,
13240
- lastGaps: priorGaps,
13241
- gapFingerprint: priorFingerprint,
13242
- stallStreak
13243
- });
13244
- yield { type: "goal-worker-start", round };
13245
- let finalResponse = "";
13246
- let workerError;
13247
- try {
13248
- for await (const event of this.#options.runWorker({
13249
- goal: request.goal,
13250
- round,
13251
- workspace,
13252
- prompt: workerPrompt,
13253
- priorGaps: formatPriorGaps(priorGaps),
13254
- signal: request.signal
13255
- })) {
13256
- yield { type: "goal-worker-event", round, event };
13257
- if (event.type === "text") finalResponse += event.text;
13258
- if (event.type === "error") {
13259
- workerError = event.error.message;
13260
- break;
13568
+ if (state.plan === null) {
13569
+ let planningAttempt = 0;
13570
+ while (true) {
13571
+ try {
13572
+ signal.throwIfAborted();
13573
+ await memoryCheckpoint();
13574
+ plan = await runPlanner({
13575
+ registry: this.#options.registry,
13576
+ modelId: this.#options.plannerModelId,
13577
+ objective: state.objective,
13578
+ signal,
13579
+ onProgress: memoryCheckpoint
13580
+ });
13581
+ planPath = await writePlanFile(workspace, plan);
13582
+ await persistState({ phase: "executing", plan, planPath, contractHash: contractHash(state.objective, plan) });
13583
+ yield { type: "goal-plan-created", plan, planPath };
13584
+ break;
13585
+ } catch (error) {
13586
+ if (memoryRotationActive()) return;
13587
+ const normalized = normalizeProviderError(error);
13588
+ if (!isResumableStageError(normalized)) {
13589
+ const reason = `Goal planning failed: ${normalized.message}`;
13590
+ await persistState({ phase: "complete", status: "failed" });
13591
+ yield { type: "goal-plan-failed", reason };
13592
+ yield { type: "goal-failed", reason };
13593
+ return;
13261
13594
  }
13595
+ planningAttempt += 1;
13596
+ const delayMs = stageRetryDelay(this.#options, planningAttempt);
13597
+ yield { type: "goal-stage-retry", stage: "planning", attempt: planningAttempt, reason: normalized.message, delayMs };
13598
+ await waitForStageRetry(delayMs, signal);
13262
13599
  }
13263
- } catch (error) {
13264
- workerError = message(error);
13265
13600
  }
13266
- if (workerError !== void 0) {
13267
- const reason = `Worker error in round ${round}: ${workerError}`;
13268
- await persistState({ phase: "complete", status: "failed" });
13269
- yield { type: "goal-failed", reason };
13270
- return;
13601
+ } else {
13602
+ plan = state.plan;
13603
+ planPath = state.planPath ?? await writePlanFile(workspace, plan);
13604
+ await persistState({ phase: "executing", status: "active", ...state.planPath === null ? { planPath } : {} });
13605
+ }
13606
+ let priorGaps = state.lastGaps;
13607
+ let priorFingerprint = state.gapFingerprint;
13608
+ let stallStreak = state.stallStreak;
13609
+ const startRound = resumeStartRound(state);
13610
+ for (let round = startRound; this.#options.maxRounds === void 0 || round <= this.#options.maxRounds; round++) {
13611
+ signal.throwIfAborted();
13612
+ await this.#options.onRoundBoundary?.(state.id);
13613
+ if (memoryRotationActive()) return;
13614
+ const pendingVerification = state.pendingVerification?.round === round ? state.pendingVerification : void 0;
13615
+ let finalResponse = pendingVerification?.finalResponse ?? "";
13616
+ if (pendingVerification === void 0) {
13617
+ const workerPrompt = buildWorkerPrompt(state.objective, plan, priorGaps, round);
13618
+ await persistState({
13619
+ phase: "executing",
13620
+ status: "active",
13621
+ workerRounds: round,
13622
+ pendingVerification: null,
13623
+ lastGaps: priorGaps,
13624
+ gapFingerprint: priorFingerprint,
13625
+ stallStreak
13626
+ });
13627
+ yield { type: "goal-worker-start", round };
13628
+ let workerError;
13629
+ let workerMemoryPressure = false;
13630
+ let workerIterationLimit = false;
13631
+ try {
13632
+ for await (const event of this.#options.runWorker({
13633
+ goal: state.objective,
13634
+ round,
13635
+ workspace,
13636
+ prompt: workerPrompt,
13637
+ priorGaps: formatPriorGaps(priorGaps),
13638
+ signal
13639
+ })) {
13640
+ yield { type: "goal-worker-event", round, event };
13641
+ if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
13642
+ if (event.type === "error") {
13643
+ workerError = event.error;
13644
+ if (event.error.code === "memory_pressure") workerMemoryPressure = true;
13645
+ if (event.error.code === "iteration_limit") workerIterationLimit = true;
13646
+ break;
13647
+ }
13648
+ }
13649
+ } catch (error) {
13650
+ if (memoryRotationActive()) return;
13651
+ workerError = { code: "unknown", message: message(error) };
13652
+ }
13653
+ if (workerMemoryPressure) return;
13654
+ if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError2(workerError)) {
13655
+ const reason = `Worker error in round ${round}: ${workerError.message}`;
13656
+ await persistState({ phase: "complete", status: "failed" });
13657
+ yield { type: "goal-failed", reason };
13658
+ return;
13659
+ }
13660
+ await persistState({
13661
+ phase: "verifying",
13662
+ status: "active",
13663
+ pendingVerification: { round, finalResponse }
13664
+ });
13271
13665
  }
13272
- await persistState({ phase: "verifying", status: "active", verifyRounds: round });
13273
13666
  yield { type: "goal-verification-start", round };
13274
- const hostVerification = await this.#options.verifyHost?.(request.signal);
13667
+ await this.#options.onMemoryCheckpoint?.(state.id);
13668
+ if (memoryRotationActive()) return;
13669
+ const hostVerification = await this.#options.verifyHost?.(signal);
13670
+ await this.#options.onMemoryCheckpoint?.(state.id);
13671
+ if (memoryRotationActive()) return;
13275
13672
  const evidence = await collectEvidence(
13276
13673
  workspace,
13277
- request.goal,
13674
+ state.objective,
13278
13675
  finalResponse.slice(-4e3),
13279
13676
  // Last 4000 chars of response
13280
13677
  formatPriorGaps(priorGaps),
13281
13678
  state.contractHash,
13282
13679
  hostVerification
13283
13680
  );
13284
- await persistState({ evidenceRounds: [...state.evidenceRounds, {
13681
+ await persistState({ evidenceRounds: [...state.evidenceRounds.filter((item) => item.round !== round), {
13285
13682
  round,
13286
13683
  workspaceDiffHash: evidence.workspaceDiffHash,
13287
13684
  ...hostVerification === void 0 ? {} : { hostVerification }
13288
- }] });
13685
+ }].slice(-64) });
13289
13686
  let outcome;
13290
13687
  if (plan.kind === "code-change" && hostVerification !== void 0 && !hostVerification.passed) {
13291
13688
  if (hostVerification.commands.length === 0) {
@@ -13299,20 +13696,34 @@ var GoalOrchestrator = class {
13299
13696
  fingerprint: createHash11("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
13300
13697
  };
13301
13698
  }
13302
- } else try {
13303
- outcome = await runClassifier(evidence, plan, {
13304
- registry: this.#options.registry,
13305
- modelId: this.#options.classifierModelId,
13306
- skepticCount: this.#options.skepticCount,
13307
- workspace,
13308
- signal: request.signal
13309
- });
13310
- } catch (error) {
13311
- outcome = {
13312
- type: "blocked",
13313
- reason: `Classifier infrastructure error: ${message(error)}`
13314
- };
13699
+ } else {
13700
+ let classifierAttempt = 0;
13701
+ while (true) {
13702
+ try {
13703
+ outcome = await runClassifier(evidence, plan, {
13704
+ registry: this.#options.registry,
13705
+ modelId: this.#options.classifierModelId,
13706
+ skepticCount: this.#options.skepticCount,
13707
+ workspace,
13708
+ signal,
13709
+ onProgress: memoryCheckpoint
13710
+ });
13711
+ break;
13712
+ } catch (error) {
13713
+ if (memoryRotationActive()) return;
13714
+ const normalized = normalizeProviderError(error);
13715
+ if (!isResumableStageError(normalized)) {
13716
+ outcome = { type: "blocked", reason: `Classifier infrastructure error: ${normalized.message}` };
13717
+ break;
13718
+ }
13719
+ classifierAttempt += 1;
13720
+ const delayMs = stageRetryDelay(this.#options, classifierAttempt);
13721
+ yield { type: "goal-stage-retry", stage: "classification", attempt: classifierAttempt, reason: normalized.message, delayMs };
13722
+ await waitForStageRetry(delayMs, signal);
13723
+ }
13724
+ }
13315
13725
  }
13726
+ await persistState({ verifyRounds: round, pendingVerification: null });
13316
13727
  yield {
13317
13728
  type: "goal-verdict",
13318
13729
  round,
@@ -13332,7 +13743,7 @@ var GoalOrchestrator = class {
13332
13743
  }
13333
13744
  if (outcome.fingerprint === priorFingerprint) {
13334
13745
  stallStreak++;
13335
- if (stallStreak >= this.#options.maxStallStreak) {
13746
+ if (this.#options.maxStallStreak !== void 0 && stallStreak >= this.#options.maxStallStreak) {
13336
13747
  await persistState({
13337
13748
  phase: "complete",
13338
13749
  status: "failed",
@@ -13372,6 +13783,37 @@ var GoalOrchestrator = class {
13372
13783
  function contractHash(objective, plan) {
13373
13784
  return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
13374
13785
  }
13786
+ function resumeStartRound(state) {
13787
+ if (state.pendingVerification !== void 0 && state.pendingVerification !== null) {
13788
+ return state.pendingVerification.round;
13789
+ }
13790
+ return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
13791
+ }
13792
+ function isResumableWorkerError2(error) {
13793
+ return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
13794
+ }
13795
+ function isResumableStageError(error) {
13796
+ return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
13797
+ }
13798
+ function stageRetryDelay(options, attempt) {
13799
+ const delayMs = options.stageRetryDelayMs?.(attempt) ?? Math.min(6e4, 1e3 * 2 ** Math.min(attempt - 1, 6));
13800
+ return Number.isSafeInteger(delayMs) && delayMs >= 0 ? delayMs : 1e3;
13801
+ }
13802
+ function waitForStageRetry(delayMs, signal) {
13803
+ signal.throwIfAborted();
13804
+ if (delayMs === 0) return Promise.resolve();
13805
+ return new Promise((resolvePromise, reject) => {
13806
+ const onAbort = () => {
13807
+ clearTimeout(timer);
13808
+ reject(signal.reason instanceof Error ? signal.reason : new Error("Goal cancelled"));
13809
+ };
13810
+ const timer = setTimeout(() => {
13811
+ signal.removeEventListener("abort", onAbort);
13812
+ resolvePromise();
13813
+ }, delayMs);
13814
+ signal.addEventListener("abort", onAbort, { once: true });
13815
+ });
13816
+ }
13375
13817
  async function writePlanFile(workspace, plan) {
13376
13818
  const dir = join17(workspace, ".flavor");
13377
13819
  const planPath = join17(dir, "goal-plan.md");
@@ -13451,7 +13893,7 @@ function formatPriorGaps(gaps) {
13451
13893
  // src/goal/store.ts
13452
13894
  import { constants as constants4 } from "fs";
13453
13895
  import { randomUUID as randomUUID11 } from "crypto";
13454
- import { lstat as lstat7, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
13896
+ import { lstat as lstat7, mkdir as mkdir11, open as open6, readFile as readFile11, rename as rename4, rm as rm4, stat as stat6 } from "fs/promises";
13455
13897
  import { dirname as dirname10, join as join18, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
13456
13898
  var DEFAULT_MAX_BYTES = 1024 * 1024;
13457
13899
  var GoalStore = class {
@@ -13488,6 +13930,28 @@ var GoalStore = class {
13488
13930
  await rm4(temporary, { force: true }).catch(() => void 0);
13489
13931
  }
13490
13932
  }
13933
+ /** Reload a persisted goal so a rotated process can resume it. */
13934
+ async load(goalId) {
13935
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(goalId)) {
13936
+ throw new Error(`Goal "${goalId}" was not found`);
13937
+ }
13938
+ await assertNoSymlink(this.#workspace, dirname10(this.#root));
13939
+ await assertNoSymlink(this.#workspace, this.#root);
13940
+ const target = join18(this.#root, `${goalId}.json`);
13941
+ try {
13942
+ const metadata = await stat6(target);
13943
+ if (!metadata.isFile() || metadata.size > this.#maxBytes) {
13944
+ throw new Error("Goal state is not a bounded regular file");
13945
+ }
13946
+ const raw = await readFile11(target, "utf8");
13947
+ return GoalStateSchema.parse(JSON.parse(raw));
13948
+ } catch (error) {
13949
+ if (isCode3(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
13950
+ const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
13951
+ await rename4(target, quarantine).catch(() => void 0);
13952
+ throw new Error(`Goal "${goalId}" is corrupt and was quarantined`);
13953
+ }
13954
+ }
13491
13955
  };
13492
13956
  async function assertNoSymlink(root, target) {
13493
13957
  let cursor = resolve14(target);
@@ -13514,7 +13978,7 @@ function isCode3(error, code) {
13514
13978
  }
13515
13979
 
13516
13980
  // src/loop/isolation.ts
13517
- import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
13981
+ import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile12 } from "fs/promises";
13518
13982
  import { join as join19, resolve as resolve15 } from "path";
13519
13983
  var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
13520
13984
  var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
@@ -13574,7 +14038,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
13574
14038
  await mkdir12(infoDirectory, { recursive: true });
13575
14039
  let existing = "";
13576
14040
  try {
13577
- existing = await readFile11(excludePath, "utf8");
14041
+ existing = await readFile12(excludePath, "utf8");
13578
14042
  } catch (error) {
13579
14043
  if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
13580
14044
  }
@@ -13585,7 +14049,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
13585
14049
 
13586
14050
  // src/loop/store.ts
13587
14051
  import { constants as constants5 } from "fs";
13588
- import { lstat as lstat8, mkdir as mkdir13, open as open7, readFile as readFile12, realpath as realpath5, rename as rename5, rm as rm5, stat as stat6 } from "fs/promises";
14052
+ import { lstat as lstat8, mkdir as mkdir13, open as open7, readFile as readFile13, realpath as realpath5, rename as rename5, rm as rm5, stat as stat7 } from "fs/promises";
13589
14053
  import { dirname as dirname11, join as join20, relative as relative12, resolve as resolve16, sep as sep12 } from "path";
13590
14054
  import { randomUUID as randomUUID12 } from "crypto";
13591
14055
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
@@ -13636,9 +14100,9 @@ var LoopStore = class {
13636
14100
  const directory = await this.#safeExisting(loopId);
13637
14101
  const target = join20(directory, "state.json");
13638
14102
  try {
13639
- const metadata = await stat6(target);
14103
+ const metadata = await stat7(target);
13640
14104
  if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
13641
- const raw = await readFile12(target, "utf8");
14105
+ const raw = await readFile13(target, "utf8");
13642
14106
  const state = LoopStateSchema.parse(JSON.parse(raw));
13643
14107
  await this.#assertWorkspace(state.workspace);
13644
14108
  return state;
@@ -13727,9 +14191,10 @@ function isCode4(error, code) {
13727
14191
  }
13728
14192
 
13729
14193
  // src/loop/verifier.ts
13730
- import { readFile as readFile13 } from "fs/promises";
14194
+ import { readFile as readFile14 } from "fs/promises";
13731
14195
  import { join as join21 } from "path";
13732
14196
  var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
14197
+ var MAX_PERSISTED_VERIFICATION_STREAM_BYTES = 1024;
13733
14198
  async function inferVerificationPlan(workspace) {
13734
14199
  const commands = [];
13735
14200
  const seen = /* @__PURE__ */ new Set();
@@ -13765,9 +14230,9 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
13765
14230
  command: item.command,
13766
14231
  args: item.args,
13767
14232
  exitCode: result.exitCode,
13768
- stdout: result.stdout,
13769
- stderr: result.stderr,
13770
- truncated: result.truncated
14233
+ stdout: boundedEvidenceTail(result.stdout),
14234
+ stderr: boundedEvidenceTail(result.stderr),
14235
+ truncated: result.truncated || Buffer.byteLength(result.stdout, "utf8") > MAX_PERSISTED_VERIFICATION_STREAM_BYTES || Buffer.byteLength(result.stderr, "utf8") > MAX_PERSISTED_VERIFICATION_STREAM_BYTES
13771
14236
  });
13772
14237
  if (result.exitCode !== 0) {
13773
14238
  return { passed: false, commands, summary: `${item.label} failed with exit code ${String(result.exitCode)}.` };
@@ -13775,6 +14240,14 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
13775
14240
  }
13776
14241
  return { passed: true, commands, summary: `Passed ${commands.length} deterministic verification command(s).` };
13777
14242
  }
14243
+ function boundedEvidenceTail(value) {
14244
+ const encoded = Buffer.from(value, "utf8");
14245
+ if (encoded.byteLength <= MAX_PERSISTED_VERIFICATION_STREAM_BYTES) return value;
14246
+ const prefix = Buffer.from("\u2026\n", "utf8");
14247
+ let start = encoded.byteLength - (MAX_PERSISTED_VERIFICATION_STREAM_BYTES - prefix.byteLength);
14248
+ while (start < encoded.byteLength && (encoded[start] & 192) === 128) start += 1;
14249
+ return `${prefix.toString("utf8")}${encoded.subarray(start).toString("utf8")}`;
14250
+ }
13778
14251
  function npmScript(name) {
13779
14252
  return name === "test" ? { label: name, command: "npm", args: ["test"] } : { label: name, command: "npm", args: ["run", name] };
13780
14253
  }
@@ -13806,7 +14279,7 @@ function isUnconditionalPassThrough(script) {
13806
14279
  }
13807
14280
  async function optionalText(path) {
13808
14281
  try {
13809
- return await readFile13(path, "utf8");
14282
+ return await readFile14(path, "utf8");
13810
14283
  } catch (error) {
13811
14284
  if (isCode5(error, "ENOENT")) return void 0;
13812
14285
  throw error;
@@ -13827,7 +14300,7 @@ function isCode5(error, code) {
13827
14300
 
13828
14301
  // src/models/anthropic.ts
13829
14302
  import Anthropic from "@anthropic-ai/sdk";
13830
- import { readFile as readFile14 } from "fs/promises";
14303
+ import { readFile as readFile15 } from "fs/promises";
13831
14304
 
13832
14305
  // src/utils/json.ts
13833
14306
  function safeParseJson(input) {
@@ -13868,6 +14341,35 @@ function normalizeToolCallInput(input, maxDepth = 3) {
13868
14341
  throw new Error(`Tool-call input must decode to an object; received ${kind}`);
13869
14342
  }
13870
14343
 
14344
+ // src/utils/abort.ts
14345
+ function waitWithSignal(operation, signal) {
14346
+ if (signal === void 0) return operation;
14347
+ return new Promise((resolve32, reject) => {
14348
+ const abort2 = () => reject(signal.reason);
14349
+ if (signal.aborted) abort2();
14350
+ else signal.addEventListener("abort", abort2, { once: true });
14351
+ operation.then(resolve32, reject).finally(() => signal.removeEventListener("abort", abort2));
14352
+ });
14353
+ }
14354
+ function createScopedAbortSignal(parent) {
14355
+ const controller = new AbortController();
14356
+ let disposed = false;
14357
+ const abort2 = () => {
14358
+ if (!controller.signal.aborted) controller.abort(parent?.reason);
14359
+ };
14360
+ if (parent?.aborted) abort2();
14361
+ else parent?.addEventListener("abort", abort2, { once: true });
14362
+ return {
14363
+ signal: controller.signal,
14364
+ dispose: () => {
14365
+ if (disposed) return;
14366
+ disposed = true;
14367
+ parent?.removeEventListener("abort", abort2);
14368
+ abort2();
14369
+ }
14370
+ };
14371
+ }
14372
+
13871
14373
  // src/models/anthropic.ts
13872
14374
  var CLAUDE_CLIENT_HEADERS = {
13873
14375
  "User-Agent": "claude-cli/2.1.0 (external, cli)",
@@ -13990,6 +14492,7 @@ var AnthropicModelAdapter = class {
13990
14492
  void appendUsageLog(line);
13991
14493
  }
13992
14494
  async *stream(request) {
14495
+ const requestAbort = createScopedAbortSignal(request.signal);
13993
14496
  let inputTokens = 0;
13994
14497
  let outputTokens = 0;
13995
14498
  let hasUsage = false;
@@ -14064,7 +14567,7 @@ var AnthropicModelAdapter = class {
14064
14567
  source: {
14065
14568
  type: "base64",
14066
14569
  media_type: block.mediaType,
14067
- data: (await readFile14(block.source.path)).toString("base64")
14570
+ data: (await readFile15(block.source.path)).toString("base64")
14068
14571
  }
14069
14572
  }));
14070
14573
  if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
@@ -14113,13 +14616,13 @@ var AnthropicModelAdapter = class {
14113
14616
  };
14114
14617
  let stream;
14115
14618
  try {
14116
- stream = await this.client.messages.create(body, { signal: request.signal });
14619
+ stream = await this.client.messages.create(body, { signal: requestAbort.signal });
14117
14620
  } catch (error) {
14118
14621
  if (this.thinkingBudget > 0 && !this.#thinkingRejected && isThinkingParamRejected(error)) {
14119
14622
  this.#thinkingRejected = true;
14120
14623
  delete body.thinking;
14121
14624
  stripThinkingBlocks(body.messages);
14122
- stream = await this.client.messages.create(body, { signal: request.signal });
14625
+ stream = await this.client.messages.create(body, { signal: requestAbort.signal });
14123
14626
  } else {
14124
14627
  throw error;
14125
14628
  }
@@ -14231,6 +14734,8 @@ var AnthropicModelAdapter = class {
14231
14734
  };
14232
14735
  }
14233
14736
  yield { type: "error", error: normalizeProviderError(error) };
14737
+ } finally {
14738
+ requestAbort.dispose();
14234
14739
  }
14235
14740
  }
14236
14741
  };
@@ -14284,7 +14789,7 @@ function resolveCacheProfile(input) {
14284
14789
 
14285
14790
  // src/models/openai.ts
14286
14791
  import OpenAI from "openai";
14287
- import { readFile as readFile15 } from "fs/promises";
14792
+ import { readFile as readFile16 } from "fs/promises";
14288
14793
  async function toInput(message2) {
14289
14794
  if (message2.role === "tool") {
14290
14795
  if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
@@ -14296,7 +14801,7 @@ async function toInput(message2) {
14296
14801
  }
14297
14802
  const content = typeof message2.content === "string" ? message2.content : await Promise.all(message2.content.map(async (block) => block.type === "text" ? { type: "input_text", text: block.text } : {
14298
14803
  type: "input_image",
14299
- image_url: `data:${block.mediaType};base64,${(await readFile15(block.source.path)).toString("base64")}`,
14804
+ image_url: `data:${block.mediaType};base64,${(await readFile16(block.source.path)).toString("base64")}`,
14300
14805
  detail: "auto"
14301
14806
  }));
14302
14807
  return [
@@ -14375,6 +14880,7 @@ var OpenAIModelAdapter = class {
14375
14880
  void appendUsageLog(line);
14376
14881
  }
14377
14882
  async *stream(request) {
14883
+ const requestAbort = createScopedAbortSignal(request.signal);
14378
14884
  const callIds = /* @__PURE__ */ new Map();
14379
14885
  const pendingCalls = /* @__PURE__ */ new Map();
14380
14886
  const emittedCalls = /* @__PURE__ */ new Set();
@@ -14391,7 +14897,7 @@ var OpenAIModelAdapter = class {
14391
14897
  strict: tool.strict ?? true
14392
14898
  }))
14393
14899
  };
14394
- const stream = this.client.responses.stream(body, { signal: request.signal });
14900
+ const stream = this.client.responses.stream(body, { signal: requestAbort.signal });
14395
14901
  for await (const event of stream) {
14396
14902
  if (event.type === "response.output_item.added" && event.item?.type === "function_call" && event.output_index !== void 0 && event.item.call_id) {
14397
14903
  callIds.set(event.output_index, event.item.call_id);
@@ -14485,6 +14991,8 @@ var OpenAIModelAdapter = class {
14485
14991
  }
14486
14992
  } catch (error) {
14487
14993
  yield { type: "error", error: normalizeProviderError(error) };
14994
+ } finally {
14995
+ requestAbort.dispose();
14488
14996
  }
14489
14997
  }
14490
14998
  };
@@ -15127,7 +15635,7 @@ function isStoredToken(value) {
15127
15635
  }
15128
15636
 
15129
15637
  // src/permissions/policy.ts
15130
- import { readFile as readFile16 } from "fs/promises";
15638
+ import { readFile as readFile17 } from "fs/promises";
15131
15639
  import { join as join22, resolve as resolve18 } from "path";
15132
15640
  import { z as z20 } from "zod";
15133
15641
  var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
@@ -15173,7 +15681,7 @@ async function loadPermissionPolicy(options) {
15173
15681
  for (const source of sources) {
15174
15682
  let raw;
15175
15683
  try {
15176
- raw = await readFile16(source.path, "utf8");
15684
+ raw = await readFile17(source.path, "utf8");
15177
15685
  } catch (error) {
15178
15686
  if (isCode6(error, "ENOENT")) continue;
15179
15687
  throw error;
@@ -15939,7 +16447,7 @@ function createSkillResourceTool(registry) {
15939
16447
 
15940
16448
  // src/session/store.ts
15941
16449
  import { constants as constants7 } from "fs";
15942
- import { lstat as lstat10, mkdir as mkdir14, open as open8, readFile as readFile17, readdir as readdir7, realpath as realpath7, rename as rename6, rm as rm6, stat as stat7 } from "fs/promises";
16450
+ import { lstat as lstat10, mkdir as mkdir14, open as open8, readFile as readFile18, readdir as readdir7, realpath as realpath7, rename as rename6, rm as rm6, stat as stat8 } from "fs/promises";
15943
16451
  import { basename as basename4, dirname as dirname13, join as join23, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
15944
16452
  import { randomUUID as randomUUID13 } from "crypto";
15945
16453
  import { z as z22 } from "zod";
@@ -16137,7 +16645,7 @@ var SessionStore = class {
16137
16645
  release: async () => {
16138
16646
  if (released) return;
16139
16647
  try {
16140
- if (await readFile17(path, "utf8") === token) await rm6(path, { force: true });
16648
+ if (await readFile18(path, "utf8") === token) await rm6(path, { force: true });
16141
16649
  } catch (error) {
16142
16650
  if (!isCode7(error, "ENOENT")) throw error;
16143
16651
  }
@@ -16331,14 +16839,14 @@ function storedSessionVersion(raw) {
16331
16839
  }
16332
16840
  async function reclaimStaleSessionLock(path) {
16333
16841
  try {
16334
- const token = await readFile17(path, "utf8");
16335
- const metadata = await stat7(path);
16842
+ const token = await readFile18(path, "utf8");
16843
+ const metadata = await stat8(path);
16336
16844
  const owner2 = parseSessionLockOwner(token);
16337
16845
  if (owner2 !== void 0 && isProcessAlive(owner2)) return owner2;
16338
16846
  if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
16339
16847
  throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
16340
16848
  }
16341
- if (await readFile17(path, "utf8") !== token) return void 0;
16849
+ if (await readFile18(path, "utf8") !== token) return void 0;
16342
16850
  const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
16343
16851
  try {
16344
16852
  await rename6(path, stale);
@@ -16371,10 +16879,10 @@ function isProcessAlive(pid) {
16371
16879
  }
16372
16880
  }
16373
16881
  async function boundedRead(path, maxBytes) {
16374
- const metadata = await stat7(path);
16882
+ const metadata = await stat8(path);
16375
16883
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
16376
16884
  if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
16377
- const value = await readFile17(path);
16885
+ const value = await readFile18(path);
16378
16886
  if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
16379
16887
  return value.toString("utf8");
16380
16888
  }
@@ -16564,11 +17072,70 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
16564
17072
  return output;
16565
17073
  }
16566
17074
 
17075
+ // src/utils/rotation-census.ts
17076
+ import { mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
17077
+ import { join as join24 } from "path";
17078
+ import { writeHeapSnapshot } from "v8";
17079
+ var CENSUS_PREFIX = "rotation-census-";
17080
+ var SNAPSHOT_PREFIX = "heap-";
17081
+ var KEEP_CENSUS_FILES = 5;
17082
+ var KEEP_SNAPSHOT_FILES = 2;
17083
+ function censusHeapBlock(reading) {
17084
+ const usage = process.memoryUsage();
17085
+ const mb = (bytes) => Math.round(bytes / 1048576);
17086
+ return {
17087
+ heapUsedMb: mb(reading.heapUsed),
17088
+ heapLimitMb: mb(reading.heapLimit),
17089
+ rawRatio: Number(reading.ratio.toFixed(3)),
17090
+ ...reading.verifiedRatio === void 0 ? {} : { verifiedRatio: Number(reading.verifiedRatio.toFixed(3)) },
17091
+ gcVerified: reading.gcVerified,
17092
+ rssMb: mb(usage.rss),
17093
+ externalMb: mb(usage.external),
17094
+ arrayBuffersMb: mb(usage.arrayBuffers),
17095
+ ...reading.memoryLimit === void 0 ? {} : { memoryLimitMb: mb(reading.memoryLimit) },
17096
+ ...reading.rssRatio === void 0 ? {} : { rssRatio: Number(reading.rssRatio.toFixed(3)) }
17097
+ };
17098
+ }
17099
+ function writeRotationCensus(workspace, census) {
17100
+ try {
17101
+ const directory = join24(workspace, ".flavor", "tmp");
17102
+ mkdirSync3(directory, { recursive: true });
17103
+ const path = join24(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
17104
+ writeFileSync2(path, `${JSON.stringify(census, null, 2)}
17105
+ `, { encoding: "utf8", mode: 384 });
17106
+ prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
17107
+ return path;
17108
+ } catch {
17109
+ return void 0;
17110
+ }
17111
+ }
17112
+ function writeRotationHeapSnapshot(workspace) {
17113
+ try {
17114
+ const directory = join24(workspace, ".flavor", "tmp");
17115
+ mkdirSync3(directory, { recursive: true });
17116
+ const path = join24(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
17117
+ writeHeapSnapshot(path);
17118
+ prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
17119
+ return path;
17120
+ } catch {
17121
+ return void 0;
17122
+ }
17123
+ }
17124
+ function prune(directory, prefix, suffix, keep) {
17125
+ const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
17126
+ for (const name of stale) {
17127
+ try {
17128
+ unlinkSync2(join24(directory, name));
17129
+ } catch {
17130
+ }
17131
+ }
17132
+ }
17133
+
16567
17134
  // src/sleep/organizer.ts
16568
17135
  import { constants as constants8 } from "fs";
16569
17136
  import { randomUUID as randomUUID14 } from "crypto";
16570
17137
  import { lstat as lstat11, mkdir as mkdir15, open as open9, readdir as readdir8, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
16571
- import { isAbsolute as isAbsolute10, join as join24, relative as relative15, resolve as resolve21, sep as sep15 } from "path";
17138
+ import { isAbsolute as isAbsolute10, join as join25, relative as relative15, resolve as resolve21, sep as sep15 } from "path";
16572
17139
  import { z as z23 } from "zod";
16573
17140
  var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
16574
17141
  var ReviewSchema = z23.object({
@@ -16623,7 +17190,7 @@ var ProjectSleepOrganizer = class {
16623
17190
  #generate;
16624
17191
  constructor(options) {
16625
17192
  this.#workspace = resolve21(options.workspace);
16626
- this.#sleepDirectory = join24(this.#workspace, ".flavor", "sleep");
17193
+ this.#sleepDirectory = join25(this.#workspace, ".flavor", "sleep");
16627
17194
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
16628
17195
  this.#generate = options.generate;
16629
17196
  }
@@ -16632,7 +17199,7 @@ var ProjectSleepOrganizer = class {
16632
17199
  signal.throwIfAborted();
16633
17200
  await this.#prepareDirectory();
16634
17201
  if (await this.#hasReport(date)) return { status: "exists", date };
16635
- const lockPath = join24(this.#sleepDirectory, `.${date}.lock`);
17202
+ const lockPath = join25(this.#sleepDirectory, `.${date}.lock`);
16636
17203
  let lock;
16637
17204
  try {
16638
17205
  lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
@@ -16667,7 +17234,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
16667
17234
  }
16668
17235
  if (review === void 0) throw lastError;
16669
17236
  const filename2 = `${date}-${filenameSummary(review.title)}.md`;
16670
- const path = join24(this.#sleepDirectory, filename2);
17237
+ const path = join25(this.#sleepDirectory, filename2);
16671
17238
  const markdown = renderSleepReport(date, review, documents);
16672
17239
  await this.#atomicWrite(path, markdown);
16673
17240
  return { status: "written", date, path, sessionCount: documents.length };
@@ -16709,7 +17276,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
16709
17276
  });
16710
17277
  }
16711
17278
  async #prepareDirectory() {
16712
- const flavorDirectory = join24(this.#workspace, ".flavor");
17279
+ const flavorDirectory = join25(this.#workspace, ".flavor");
16713
17280
  await assertNotSymlink(flavorDirectory);
16714
17281
  await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
16715
17282
  await assertNotSymlink(this.#sleepDirectory);
@@ -16723,7 +17290,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
16723
17290
  return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
16724
17291
  }
16725
17292
  async #atomicWrite(path, content) {
16726
- const temporary = join24(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
17293
+ const temporary = join25(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
16727
17294
  let handle;
16728
17295
  try {
16729
17296
  handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
@@ -17097,7 +17664,7 @@ function computeSessionStats(documents) {
17097
17664
 
17098
17665
  // src/tools/files.ts
17099
17666
  import { constants as constants9 } from "fs";
17100
- import { mkdir as mkdir16, open as open10, readFile as readFile18, realpath as realpath9, rename as rename8, rm as rm8, stat as stat8 } from "fs/promises";
17667
+ import { mkdir as mkdir16, open as open10, readFile as readFile19, realpath as realpath9, rename as rename8, rm as rm8, stat as stat9 } from "fs/promises";
17101
17668
  import { dirname as dirname14, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve22, sep as sep16 } from "path";
17102
17669
  import { randomUUID as randomUUID15 } from "crypto";
17103
17670
  import { z as z24 } from "zod";
@@ -17250,7 +17817,7 @@ function createReadTool(workspace, options = {}) {
17250
17817
  execute: async (input, signal) => {
17251
17818
  abortIfNeeded(signal);
17252
17819
  const path = await guard.existing(input.path);
17253
- const info = await stat8(path);
17820
+ const info = await stat9(path);
17254
17821
  const maxBytes = input.maxBytes ?? MAX_READ_BYTES;
17255
17822
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_READ_BYTES) {
17256
17823
  throw new Error(`maxBytes must be a positive integer no greater than ${MAX_READ_BYTES}`);
@@ -17459,7 +18026,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
17459
18026
  const temporary = `${path}.${randomUUID15()}.tmp`;
17460
18027
  let mode;
17461
18028
  try {
17462
- mode = (await stat8(path)).mode & 511;
18029
+ mode = (await stat9(path)).mode & 511;
17463
18030
  } catch (error) {
17464
18031
  if (!isMissing3(error)) throw error;
17465
18032
  }
@@ -17482,7 +18049,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
17482
18049
  }
17483
18050
  }
17484
18051
  async function readText(path) {
17485
- const contents = await readFile18(path);
18052
+ const contents = await readFile19(path);
17486
18053
  if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
17487
18054
  return decodeFileText(contents);
17488
18055
  }
@@ -17508,7 +18075,7 @@ async function assertMutationVersion(path, expected, observations) {
17508
18075
  }
17509
18076
  async function fileVersion(path) {
17510
18077
  try {
17511
- return versionOfStat(await stat8(path));
18078
+ return versionOfStat(await stat9(path));
17512
18079
  } catch (error) {
17513
18080
  if (isMissing3(error)) return ABSENT_VERSION;
17514
18081
  throw error;
@@ -17519,7 +18086,7 @@ function versionOfStat(info) {
17519
18086
  }
17520
18087
  async function readOptionalPresentationText(path) {
17521
18088
  try {
17522
- const contents = await readFile18(path);
18089
+ const contents = await readFile19(path);
17523
18090
  return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
17524
18091
  } catch (error) {
17525
18092
  return isMissing3(error) ? { exists: false } : { exists: true };
@@ -17834,19 +18401,6 @@ import { isIP } from "net";
17834
18401
  import { request as httpRequest } from "http";
17835
18402
  import { request as httpsRequest } from "https";
17836
18403
  import { z as z25 } from "zod";
17837
-
17838
- // src/utils/abort.ts
17839
- function waitWithSignal(operation, signal) {
17840
- if (signal === void 0) return operation;
17841
- return new Promise((resolve32, reject) => {
17842
- const abort2 = () => reject(signal.reason);
17843
- if (signal.aborted) abort2();
17844
- else signal.addEventListener("abort", abort2, { once: true });
17845
- operation.then(resolve32, reject).finally(() => signal.removeEventListener("abort", abort2));
17846
- });
17847
- }
17848
-
17849
- // src/tools/web.ts
17850
18404
  var WebFetchInput = z25.object({
17851
18405
  url: z25.string().url().max(8192),
17852
18406
  timeoutMs: z25.coerce.number().int().positive().max(12e4).optional(),
@@ -18171,7 +18725,7 @@ function charset(contentType) {
18171
18725
  // src/tools/lsp.ts
18172
18726
  import { spawn as spawn8 } from "child_process";
18173
18727
  import { existsSync as existsSync5 } from "fs";
18174
- import { access as access2, constants as constants10, readFile as readFile19 } from "fs/promises";
18728
+ import { access as access2, constants as constants10, readFile as readFile20 } from "fs/promises";
18175
18729
  import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute12, relative as relative17, resolve as resolve23, sep as sep17 } from "path";
18176
18730
  import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "url";
18177
18731
  import { z as z26 } from "zod";
@@ -18365,7 +18919,7 @@ var LspConnection = class {
18365
18919
  const url = new URL(uri);
18366
18920
  if (url.protocol !== "file:") return;
18367
18921
  const filePath = fileURLToPath2(url);
18368
- const text = await readFile19(filePath, "utf8");
18922
+ const text = await readFile20(filePath, "utf8");
18369
18923
  const current = this.#documents.get(uri);
18370
18924
  if (current === void 0) {
18371
18925
  this.#documents.set(uri, { text, version: 1 });
@@ -18731,19 +19285,19 @@ async function changeSummary(workspace) {
18731
19285
  };
18732
19286
  }
18733
19287
  async function stagedDiff(workspace, maxChars = 96e3) {
18734
- const stat11 = await git2(workspace, ["diff", "--staged", "--stat"]);
19288
+ const stat12 = await git2(workspace, ["diff", "--staged", "--stat"]);
18735
19289
  const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
18736
19290
  if (!diff.ok) throw gitFailure(diff, "diff --staged");
18737
19291
  const text = diff.stdout;
18738
19292
  return {
18739
19293
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
18740
19294
  \u2026[diff truncated]` : text,
18741
- stat: stat11.stdout.trim(),
19295
+ stat: stat12.stdout.trim(),
18742
19296
  truncated: text.length > maxChars
18743
19297
  };
18744
19298
  }
18745
19299
  async function uncommittedDiff(workspace, maxChars = 96e3) {
18746
- const stat11 = await git2(workspace, ["diff", "HEAD", "--stat"]);
19300
+ const stat12 = await git2(workspace, ["diff", "HEAD", "--stat"]);
18747
19301
  const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
18748
19302
  if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
18749
19303
  const summary2 = await changeSummary(workspace);
@@ -18751,7 +19305,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
18751
19305
  return {
18752
19306
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
18753
19307
  \u2026[diff truncated]` : text,
18754
- stat: stat11.stdout.trim(),
19308
+ stat: stat12.stdout.trim(),
18755
19309
  untracked: summary2.untrackedFiles
18756
19310
  };
18757
19311
  }
@@ -19048,17 +19602,17 @@ ${explanation}` : "";
19048
19602
  }
19049
19603
 
19050
19604
  // src/desktop/astgraph-service.ts
19051
- import { stat as stat9 } from "fs/promises";
19605
+ import { stat as stat10 } from "fs/promises";
19052
19606
  import { createRequire as nodeCreateRequire } from "module";
19053
- import { join as join25, resolve as resolve24 } from "path";
19607
+ import { join as join26, resolve as resolve24 } from "path";
19054
19608
  var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
19055
19609
  var DesktopAstGraphService = class {
19056
19610
  #path;
19057
19611
  constructor(workspace) {
19058
- this.#path = join25(resolve24(workspace), ".flavor", "astgraph", "index.db");
19612
+ this.#path = join26(resolve24(workspace), ".flavor", "astgraph", "index.db");
19059
19613
  }
19060
19614
  async status() {
19061
- if ((await stat9(this.#path).catch(() => void 0))?.isFile() !== true) {
19615
+ if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
19062
19616
  return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
19063
19617
  }
19064
19618
  return this.#read((db) => {
@@ -19338,8 +19892,8 @@ function createTodoWriteTool() {
19338
19892
 
19339
19893
  // src/tools/managed.ts
19340
19894
  import { randomUUID as randomUUID16 } from "crypto";
19341
- import { link as link2, lstat as lstat12, mkdir as mkdir17, readFile as readFile20, readdir as readdir9, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
19342
- import { dirname as dirname16, join as join26, resolve as resolve26 } from "path";
19895
+ import { link as link2, lstat as lstat12, mkdir as mkdir17, readFile as readFile21, readdir as readdir9, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
19896
+ import { dirname as dirname16, join as join27, resolve as resolve26 } from "path";
19343
19897
  import { z as z31 } from "zod";
19344
19898
  var MANAGED_TOOL_VERSION = 1;
19345
19899
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -19382,8 +19936,8 @@ var ManagedToolStore = class {
19382
19936
  #diagnostics = [];
19383
19937
  constructor(options) {
19384
19938
  this.#workspace = resolve26(options.workspace);
19385
- this.#projectRoot = join26(this.#workspace, ".flavor", "tools");
19386
- this.#globalRoot = join26(resolve26(options.home), ".flavor-code", "tools");
19939
+ this.#projectRoot = join27(this.#workspace, ".flavor", "tools");
19940
+ this.#globalRoot = join27(resolve26(options.home), ".flavor-code", "tools");
19387
19941
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
19388
19942
  }
19389
19943
  get diagnostics() {
@@ -19430,7 +19984,7 @@ var ManagedToolStore = class {
19430
19984
  version: MANAGED_TOOL_VERSION,
19431
19985
  createdAt: this.#now()
19432
19986
  });
19433
- const path = join26(this.directory(input.scope), filename(input.name));
19987
+ const path = join27(this.directory(input.scope), filename(input.name));
19434
19988
  const definition = compileDefinition(record, input.scope, this.#workspace);
19435
19989
  await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
19436
19990
  `);
@@ -19455,7 +20009,7 @@ var ManagedToolStore = class {
19455
20009
  if (!parsed.success) return [];
19456
20010
  const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
19457
20011
  if (matches.length > 0) return matches.map((entry2) => entry2.path);
19458
- return [join26(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
20012
+ return [join27(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
19459
20013
  }
19460
20014
  async #loadDirectory(scope2) {
19461
20015
  const root = this.directory(scope2);
@@ -19468,12 +20022,12 @@ var ManagedToolStore = class {
19468
20022
  return;
19469
20023
  }
19470
20024
  for (const name of names) {
19471
- const path = join26(root, name);
20025
+ const path = join27(root, name);
19472
20026
  try {
19473
20027
  const info = await lstat12(path);
19474
20028
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
19475
20029
  if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
19476
- const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile20(path, "utf8")));
20030
+ const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile21(path, "utf8")));
19477
20031
  if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
19478
20032
  const key = scopedKey(scope2, record.name);
19479
20033
  if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
@@ -20386,8 +20940,8 @@ ${total}`;
20386
20940
 
20387
20941
  // src/memory/store.ts
20388
20942
  import { createHash as createHash16 } from "crypto";
20389
- import { readFile as readFile21, rm as rm9 } from "fs/promises";
20390
- import { resolve as resolve27, join as join27, relative as relative19, sep as sep19 } from "path";
20943
+ import { readFile as readFile22, rm as rm9 } from "fs/promises";
20944
+ import { resolve as resolve27, join as join28, relative as relative19, sep as sep19 } from "path";
20391
20945
 
20392
20946
  // src/memory/similarity.ts
20393
20947
  function normalizeForSimilarity(value) {
@@ -20490,9 +21044,9 @@ var MemoryStore = class {
20490
21044
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
20491
21045
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
20492
21046
  this.workspace = resolve27(options.workspace);
20493
- this.#memoryRoot = join27(this.workspace, ".flavor", "memory");
20494
- this.path = join27(this.#memoryRoot, "MEMORY.md");
20495
- this.#behaviorPath = join27(this.#memoryRoot, "behavior.json");
21047
+ this.#memoryRoot = join28(this.workspace, ".flavor", "memory");
21048
+ this.path = join28(this.#memoryRoot, "MEMORY.md");
21049
+ this.#behaviorPath = join28(this.#memoryRoot, "behavior.json");
20496
21050
  this.#maxEntries = options.maxEntries;
20497
21051
  this.#maxEntryChars = options.maxEntryChars;
20498
21052
  }
@@ -20710,7 +21264,7 @@ var MemoryStore = class {
20710
21264
  });
20711
21265
  }
20712
21266
  async #readTaskItem(reference) {
20713
- const document = decodeTask(await readFile21(this.#resolveContentPath(reference.contentPath), "utf8"));
21267
+ const document = decodeTask(await readFile22(this.#resolveContentPath(reference.contentPath), "utf8"));
20714
21268
  return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
20715
21269
  }
20716
21270
  #resolveContentPath(contentPath) {
@@ -21186,8 +21740,8 @@ function createExecutionEnvironment(workspace, config) {
21186
21740
  }
21187
21741
 
21188
21742
  // src/ide/client.ts
21189
- import { readFile as readFile22, readdir as readdir10, stat as stat10 } from "fs/promises";
21190
- import { basename as basename6, join as join28, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
21743
+ import { readFile as readFile23, readdir as readdir10, stat as stat11 } from "fs/promises";
21744
+ import { basename as basename6, join as join29, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
21191
21745
  var FlavorIdeClient = class {
21192
21746
  #workspace;
21193
21747
  #home;
@@ -21283,7 +21837,7 @@ var FlavorIdeClient = class {
21283
21837
  };
21284
21838
  if (await this.#healthy(configured)) return configured;
21285
21839
  }
21286
- const directory = join28(this.#home, ".flavor-code", "ide");
21840
+ const directory = join29(this.#home, ".flavor-code", "ide");
21287
21841
  let entries;
21288
21842
  try {
21289
21843
  entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
@@ -21291,9 +21845,9 @@ var FlavorIdeClient = class {
21291
21845
  return void 0;
21292
21846
  }
21293
21847
  const candidates = (await Promise.all(entries.map(async (name) => {
21294
- const path = join28(directory, name);
21848
+ const path = join29(directory, name);
21295
21849
  try {
21296
- const [contents, info] = await Promise.all([readFile22(path, "utf8"), stat10(path)]);
21850
+ const [contents, info] = await Promise.all([readFile23(path, "utf8"), stat11(path)]);
21297
21851
  const lock = parseLockfile(contents);
21298
21852
  return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
21299
21853
  } catch {
@@ -21412,6 +21966,7 @@ function relativeDisplay(workspace, filePath) {
21412
21966
 
21413
21967
  // src/jobs/registry.ts
21414
21968
  import { randomUUID as randomUUID17 } from "crypto";
21969
+ var OUTPUT_SLICE_SLACK_CHARS = 64 * 1024;
21415
21970
  var JobRegistry = class {
21416
21971
  #jobs = /* @__PURE__ */ new Map();
21417
21972
  #maxOutputChars;
@@ -21440,7 +21995,8 @@ var JobRegistry = class {
21440
21995
  updatedAt: now,
21441
21996
  outputChars: 0,
21442
21997
  truncated: false,
21443
- output: "",
21998
+ chunks: [],
21999
+ windowChars: 0,
21444
22000
  outputStart: 0,
21445
22001
  settle,
21446
22002
  done
@@ -21451,11 +22007,16 @@ var JobRegistry = class {
21451
22007
  id,
21452
22008
  append: (text) => {
21453
22009
  if (record.state !== "running" || text === "") return;
21454
- record.output += text;
22010
+ record.chunks.push(text);
22011
+ record.windowChars += text.length;
21455
22012
  record.outputChars += text.length;
21456
- if (record.output.length > this.#maxOutputChars) {
21457
- const removed = record.output.length - this.#maxOutputChars;
21458
- record.output = record.output.slice(removed);
22013
+ if (record.outputChars > this.#maxOutputChars) record.truncated = true;
22014
+ if (record.windowChars > this.#maxOutputChars + OUTPUT_SLICE_SLACK_CHARS) {
22015
+ const joined = record.chunks.join("");
22016
+ const kept = joined.slice(joined.length - this.#maxOutputChars);
22017
+ const removed = joined.length - kept.length;
22018
+ record.chunks = [kept];
22019
+ record.windowChars = kept.length;
21459
22020
  record.outputStart += removed;
21460
22021
  record.truncated = true;
21461
22022
  }
@@ -21476,8 +22037,20 @@ var JobRegistry = class {
21476
22037
  }
21477
22038
  read(id, owner2, cursor = 0) {
21478
22039
  const job = this.#owned(id, owner2);
21479
- const effective = Math.max(cursor, job.outputStart);
21480
- return { ...snapshot2(job), output: job.output.slice(effective - job.outputStart), cursor: job.outputStart + job.output.length };
22040
+ const window = job.chunks.join("");
22041
+ const dropped = Math.max(0, window.length - this.#maxOutputChars);
22042
+ const effective = Math.max(cursor, job.outputStart + dropped);
22043
+ return { ...snapshot2(job), output: window.slice(effective - job.outputStart), cursor: job.outputStart + window.length };
22044
+ }
22045
+ /** Retained sizes for the rotation census. */
22046
+ census() {
22047
+ let windowChars = 0;
22048
+ let running = 0;
22049
+ for (const job of this.#jobs.values()) {
22050
+ windowChars += job.windowChars;
22051
+ if (job.state === "running") running += 1;
22052
+ }
22053
+ return { jobs: this.#jobs.size, running, windowChars };
21481
22054
  }
21482
22055
  async wait(id, owner2, signal) {
21483
22056
  signal?.throwIfAborted();
@@ -22705,8 +23278,8 @@ async function createProductionRuntime(options) {
22705
23278
  const registeredProviders = registration.registered;
22706
23279
  let effectiveLlm = registration.effectiveLlm;
22707
23280
  const pluginHost = new PluginHost({
22708
- globalPluginDirs: [join29(home, ".flavor-code", "plugins")],
22709
- projectPluginDirs: [join29(workspace, ".flavor", "plugins")],
23281
+ globalPluginDirs: [join30(home, ".flavor-code", "plugins")],
23282
+ projectPluginDirs: [join30(workspace, ".flavor", "plugins")],
22710
23283
  config,
22711
23284
  // Existing project plugins (including astgraph and superharness) use Node.js
22712
23285
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -22815,7 +23388,7 @@ async function createProductionRuntime(options) {
22815
23388
  const address = palSocketAddress({
22816
23389
  platform: process.platform,
22817
23390
  userScope: home,
22818
- runtimeDir: join29(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
23391
+ runtimeDir: join30(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
22819
23392
  });
22820
23393
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
22821
23394
  instanceId: collaboration.instanceId,
@@ -22926,8 +23499,8 @@ async function createProductionRuntime(options) {
22926
23499
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
22927
23500
  });
22928
23501
  const skills = new SkillRegistry({
22929
- globalRoots: [join29(home, ".flavor-code", "skills")],
22930
- projectRoots: [join29(workspace, ".flavor", "skills"), ...pluginSkillRoots],
23502
+ globalRoots: [join30(home, ".flavor-code", "skills")],
23503
+ projectRoots: [join30(workspace, ".flavor", "skills"), ...pluginSkillRoots],
22931
23504
  authorizeResource: async () => true,
22932
23505
  disabledNames: config.skills.disabled
22933
23506
  });
@@ -22935,7 +23508,7 @@ async function createProductionRuntime(options) {
22935
23508
  skillsReady.catch(() => void 0);
22936
23509
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
22937
23510
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
22938
- const flavor = await optionalText2(join29(workspace, "FLAVOR.md"));
23511
+ const flavor = await optionalText2(join30(workspace, "FLAVOR.md"));
22939
23512
  const instructionBaseline = await workspaceInstructions.baseline();
22940
23513
  let memoryContext;
22941
23514
  if (memoryStore !== void 0) {
@@ -23520,7 +24093,7 @@ async function createProductionRuntime(options) {
23520
24093
  evolveService.toolDefinition(),
23521
24094
  ...mcpTools
23522
24095
  ];
23523
- const loopFlavor = await optionalText2(join29(input.workspace, "FLAVOR.md"));
24096
+ const loopFlavor = await optionalText2(join30(input.workspace, "FLAVOR.md"));
23524
24097
  const loopInstructionBaseline = await loopInstructions.baseline();
23525
24098
  const loopEnvironment = createPromptEnvironment({
23526
24099
  now: /* @__PURE__ */ new Date(),
@@ -23628,12 +24201,89 @@ async function createProductionRuntime(options) {
23628
24201
  await loopExecutionEnvironment?.dispose();
23629
24202
  }
23630
24203
  };
24204
+ let activeLongTask;
24205
+ const readMemoryRestartMarker = () => {
24206
+ try {
24207
+ return parseMemoryRestartMarker(readFileSync3(memoryRestartMarkerPath(workspace), "utf8"));
24208
+ } catch {
24209
+ return void 0;
24210
+ }
24211
+ };
24212
+ const currentPressureReading = () => verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO) ?? { ...readHeap(), gcVerified: false };
24213
+ const collectRotationHolders = () => {
24214
+ const holders = {};
24215
+ try {
24216
+ const context = harness.main.context.census();
24217
+ holders.context = { entries: context.messages, chars: context.messageChars };
24218
+ holders.contextVisibility = { entries: context.visibilityEntries, chars: context.visibilityChars };
24219
+ holders.contextTransient = { entries: context.transientEntries, chars: context.transientChars };
24220
+ } catch {
24221
+ }
24222
+ try {
24223
+ holders.journal = { entries: harnessJournal.recordCount };
24224
+ } catch {
24225
+ }
24226
+ try {
24227
+ const jobCensus = jobs.census();
24228
+ holders.jobs = { entries: jobCensus.jobs, chars: jobCensus.windowChars };
24229
+ } catch {
24230
+ }
24231
+ try {
24232
+ holders.sessionTree = { entries: sessionHistory.nodeCount };
24233
+ } catch {
24234
+ }
24235
+ try {
24236
+ const transcript = transcriptCensus(timelineState);
24237
+ holders.transcript = { entries: transcript.blocks, chars: transcript.chars };
24238
+ holders.transcriptTurns = { entries: transcript.turns };
24239
+ } catch {
24240
+ }
24241
+ return holders;
24242
+ };
24243
+ const rotateForHeap = (continuation, reading) => {
24244
+ requestMemoryRestart(workspace, sessionId, emitOutput, {
24245
+ ...continuation === void 0 ? {} : { continuation },
24246
+ ...reading === void 0 ? {} : { reading },
24247
+ holders: collectRotationHolders()
24248
+ }, options.onMemoryRestartRequested);
24249
+ };
24250
+ const maybeRotateAtBoundary = (continuation) => {
24251
+ activeLongTask = continuation;
24252
+ const pressure = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
24253
+ if (pressure !== void 0) {
24254
+ rotateForHeap(continuation, pressure);
24255
+ return;
24256
+ }
24257
+ const reading = heapRotationNeeded();
24258
+ if (reading === void 0) return;
24259
+ if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
24260
+ rotateForHeap(continuation, reading);
24261
+ };
24262
+ const maybeRotateAtTurnBoundary = () => {
24263
+ const reading = heapRotationNeeded();
24264
+ if (reading === void 0) return;
24265
+ if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
24266
+ rotateForHeap(void 0, reading);
24267
+ };
24268
+ const consumePendingContinuation = () => {
24269
+ const marker = readMemoryRestartMarker();
24270
+ const continuation = pendingContinuation(marker, sessionId, /* @__PURE__ */ new Date());
24271
+ if (continuation === void 0 || marker === void 0) return void 0;
24272
+ try {
24273
+ const markerPath = memoryRestartMarkerPath(workspace);
24274
+ writeMarkerAtomically(markerPath, consumedMemoryRestartMarker(marker));
24275
+ } catch {
24276
+ }
24277
+ return continuation;
24278
+ };
23631
24279
  const loopStore = new LoopStore({ workspace });
23632
24280
  const loopOrchestrator = new LoopOrchestrator({
23633
24281
  workspace,
23634
24282
  config: config.loop,
23635
24283
  persistence: loopStore,
23636
24284
  hallucinationGuard,
24285
+ onCycleBoundary: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
24286
+ onMemoryCheckpoint: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
23637
24287
  prepareWorkspace: (input) => prepareLoopWorkspace(input),
23638
24288
  inferVerification: inferVerificationPlan,
23639
24289
  runWorker: ({ workspace: executionWorkspace, prompt, signal }) => runLoopWorker({ workspace: executionWorkspace, prompt, signal }),
@@ -23645,21 +24295,9 @@ async function createProductionRuntime(options) {
23645
24295
  await verifierEnvironment?.dispose();
23646
24296
  }
23647
24297
  },
23648
- confirmBudget: async (state, dimensions, signal) => {
23649
- if (options.approvalPolicy === "deny") return "unavailable";
23650
- const reached = dimensions.map((dimension) => dimension === "cycles" ? `${state.budget.cyclesUsed} cycles` : `${state.budget.inputTokens + state.budget.outputTokens} tokens`).join(" and ");
23651
- const next = dimensions.map((dimension) => dimension === "cycles" ? `${state.budget.cycleCheckpoint + state.config.cycleStep} cycles` : `${state.budget.tokenCheckpoint + state.config.tokenStep} tokens`).join(" and ");
23652
- const latestVerification = state.cycles.at(-1)?.verification.summary ?? "No host verification evidence yet.";
23653
- const answers = await questions.ask([{
23654
- header: "Loop budget",
23655
- question: `Loop ${state.loopId} reached ${reached}. Latest verification: ${latestVerification} Continue until the next checkpoint (${next})?`,
23656
- options: [
23657
- { label: "Continue", description: "Extend only the reached budget tranche and keep looping." },
23658
- { label: "Stop", description: "End this loop as budget exhausted." }
23659
- ]
23660
- }], signal);
23661
- return answers[0] === "Continue" ? "approved" : "rejected";
23662
- },
24298
+ // Cycle/token values are telemetry checkpoint cadence, not an unattended
24299
+ // execution cap. The user can still cancel explicitly at any time.
24300
+ confirmBudget: async () => "approved",
23663
24301
  fingerprint: workspaceFingerprint,
23664
24302
  idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`
23665
24303
  });
@@ -23670,9 +24308,12 @@ async function createProductionRuntime(options) {
23670
24308
  plannerModelId: mainModel,
23671
24309
  classifierModelId: mainModel,
23672
24310
  skepticCount: 3,
23673
- maxRounds: 5,
23674
- maxStallStreak: 2,
24311
+ // Production goals run until verified, explicitly cancelled, or blocked
24312
+ // by a non-recoverable external condition. Numeric config values remain
24313
+ // accepted for compatibility but no longer terminate autonomous work.
23675
24314
  persistence: goalStore,
24315
+ onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
24316
+ onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
23676
24317
  verifyHost: async (signal) => runVerificationPlan(
23677
24318
  await inferVerificationPlan(workspace),
23678
24319
  workspace,
@@ -23767,30 +24408,50 @@ async function createProductionRuntime(options) {
23767
24408
  contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
23768
24409
  });
23769
24410
  const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
23770
- return monitorMemoryRestart(durableTurn(persistAfter(runMain(
23771
- harness,
23772
- skills,
23773
- prompt,
23774
- signal,
23775
- selectedModels.mainError,
23776
- memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
23777
- store: memoryStore,
23778
- taskId: memoryLifecycle.taskId ?? sessionId,
23779
- topK: config.memory.retrievalTopK,
23780
- maxChars: config.memory.maxPromptChars
23781
- },
23782
- runOptions?.getSteeringMessages,
23783
- runOptions?.initialUserMessage,
23784
- runOptions?.additionalContext,
23785
- ide
23786
- ), persist), harnessJournal, turnId, turnConfig), () => requestMemoryRestart(workspace, sessionId, emitOutput));
24411
+ return monitorTurnMemory(
24412
+ durableTurn(persistAfter(runMain(
24413
+ harness,
24414
+ skills,
24415
+ prompt,
24416
+ signal,
24417
+ selectedModels.mainError,
24418
+ memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
24419
+ store: memoryStore,
24420
+ taskId: memoryLifecycle.taskId ?? sessionId,
24421
+ topK: config.memory.retrievalTopK,
24422
+ maxChars: config.memory.maxPromptChars
24423
+ },
24424
+ runOptions?.getSteeringMessages,
24425
+ runOptions?.initialUserMessage,
24426
+ runOptions?.additionalContext,
24427
+ ide
24428
+ ), persist), harnessJournal, turnId, turnConfig),
24429
+ () => rotateForHeap(void 0, currentPressureReading()),
24430
+ () => maybeRotateAtTurnBoundary()
24431
+ );
23787
24432
  },
23788
24433
  runSkill: (skill, prompt, signal) => persistAfter(
23789
24434
  runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
23790
24435
  persist
23791
24436
  ),
23792
- runLoop: (goal, signal) => runLoopSession(loopOrchestrator, hooks, goal, signal),
23793
- runGoal: (goal, signal) => persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
24437
+ runLoop: (goal, signal) => monitorLongTaskRestart(
24438
+ runLoopSession(loopOrchestrator, hooks, goal, signal),
24439
+ () => rotateForHeap(activeLongTask, currentPressureReading())
24440
+ ),
24441
+ runGoal: (goal, signal) => monitorLongTaskRestart(
24442
+ persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
24443
+ () => rotateForHeap(activeLongTask, currentPressureReading())
24444
+ ),
24445
+ resumeLoop: (loopId, signal) => monitorLongTaskRestart(
24446
+ runResumeLoopSession(loopOrchestrator, hooks, loopId, signal),
24447
+ () => rotateForHeap(activeLongTask ?? { kind: "loop", id: loopId }, currentPressureReading())
24448
+ ),
24449
+ resumeGoal: (goalId, signal) => monitorLongTaskRestart(
24450
+ persistEach(runResumeGoalSession(goalOrchestrator, goalId, signal), persist),
24451
+ () => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
24452
+ ),
24453
+ continuation: () => consumePendingContinuation(),
24454
+ rotationPending: () => memoryRotationActive(),
23794
24455
  mcp: async (command, signal) => {
23795
24456
  signal.throwIfAborted();
23796
24457
  const manager = await mcpReady;
@@ -23884,7 +24545,7 @@ async function createProductionRuntime(options) {
23884
24545
  const root = resolve31(workspace);
23885
24546
  const abs = resolve31(root, path);
23886
24547
  if (abs !== root && !abs.startsWith(root + sep22)) throw new Error(`path outside workspace: ${path}`);
23887
- return readFile23(abs, "utf8");
24548
+ return readFile24(abs, "utf8");
23888
24549
  },
23889
24550
  history: async (path, limit) => fileHistory(workspace, path, limit),
23890
24551
  questions,
@@ -23896,7 +24557,7 @@ async function createProductionRuntime(options) {
23896
24557
  }, query, focus, signal),
23897
24558
  audit: async (toolFilter) => {
23898
24559
  try {
23899
- const raw = await readFile23(auditLogger.path, "utf8");
24560
+ const raw = await readFile24(auditLogger.path, "utf8");
23900
24561
  const lines = raw.trim().split("\n").filter((line) => line.length > 0);
23901
24562
  const entries = lines.map((line) => {
23902
24563
  try {
@@ -23936,7 +24597,7 @@ ${summary2}`;
23936
24597
  doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
23937
24598
  usage: async () => {
23938
24599
  try {
23939
- const raw = await readFile23(usageLogPath(), "utf8");
24600
+ const raw = await readFile24(usageLogPath(), "utf8");
23940
24601
  return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
23941
24602
  } catch (error) {
23942
24603
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -24089,7 +24750,7 @@ ${renderMemoryDocument(entries)}`;
24089
24750
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
24090
24751
  }
24091
24752
  try {
24092
- const tokenStore = createFileTokenStore(join29(home, ".flavor-code", "auth.json"));
24753
+ const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
24093
24754
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
24094
24755
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
24095
24756
  const oauth = new OAuthCallbackAuthProvider({
@@ -24136,7 +24797,7 @@ ${renderMemoryDocument(entries)}`;
24136
24797
  }
24137
24798
  },
24138
24799
  async logout() {
24139
- const tokenStore = createFileTokenStore(join29(home, ".flavor-code", "auth.json"));
24800
+ const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
24140
24801
  const tokens = await tokenStore.load();
24141
24802
  const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
24142
24803
  if (!hadCredentials) {
@@ -24329,10 +24990,16 @@ function formatMcpReconnect(server) {
24329
24990
  return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
24330
24991
  }
24331
24992
  async function* runLoopSession(orchestrator, hooks, goal, signal) {
24993
+ yield* loopSessionEvents(orchestrator.run({ goal, signal }), hooks);
24994
+ }
24995
+ async function* runResumeLoopSession(orchestrator, hooks, loopId, signal) {
24996
+ yield* loopSessionEvents(orchestrator.resume({ loopId, signal }), hooks);
24997
+ }
24998
+ async function* loopSessionEvents(events, hooks) {
24332
24999
  let totalInputTokens = 0;
24333
25000
  let totalOutputTokens = 0;
24334
25001
  let latestVerification;
24335
- for await (const event of orchestrator.run({ goal, signal })) {
25002
+ for await (const event of events) {
24336
25003
  if (event.type === "worker-event") {
24337
25004
  if (event.event.type === "usage") {
24338
25005
  totalInputTokens += event.event.inputTokens;
@@ -24342,6 +25009,8 @@ async function* runLoopSession(orchestrator, hooks, goal, signal) {
24342
25009
  totalInputTokens,
24343
25010
  totalOutputTokens
24344
25011
  };
25012
+ } else if (event.event.type === "error" && isResumableLongTaskError(event.event.error.code)) {
25013
+ yield { type: "warning", message: `Loop worker segment ended at ${event.event.error.code}; checkpointing and verifying before continuing.` };
24345
25014
  } else if (event.event.type !== "done") yield event.event;
24346
25015
  continue;
24347
25016
  }
@@ -24396,7 +25065,7 @@ function loopProgressEvent(event) {
24396
25065
  loopId: event.loopId,
24397
25066
  phase: "budget",
24398
25067
  state: "info",
24399
- message: `Confirmation required for ${event.dimensions.join(" and ")} budget.`
25068
+ message: `${event.dimensions.join(" and ")} checkpoint reached; continuing automatically.`
24400
25069
  };
24401
25070
  }
24402
25071
  return {
@@ -24413,6 +25082,9 @@ function terminalProgressState(status) {
24413
25082
  if (status === "needs_human") return "info";
24414
25083
  return "failed";
24415
25084
  }
25085
+ function isResumableLongTaskError(code) {
25086
+ return code !== "authentication" && code !== "model_not_found" && code !== "cancelled" && code !== "memory_pressure";
25087
+ }
24416
25088
  async function workspaceFingerprint(workspace) {
24417
25089
  const hash = createHash18("sha256");
24418
25090
  const diff = await execFileNoThrow(
@@ -24436,7 +25108,7 @@ async function workspaceFingerprint(workspace) {
24436
25108
  hash.update(name);
24437
25109
  if (remaining <= 0) continue;
24438
25110
  try {
24439
- const content = await readFile23(path);
25111
+ const content = await readFile24(path);
24440
25112
  const slice = content.subarray(0, remaining);
24441
25113
  hash.update(slice);
24442
25114
  remaining -= slice.length;
@@ -24548,7 +25220,16 @@ async function* durableTurn(source, journal, turnId, config) {
24548
25220
  }
24549
25221
  }
24550
25222
  }
24551
- async function* monitorMemoryRestart(source, onRestartNeeded) {
25223
+ async function* monitorTurnMemory(source, onPressure, onBoundary) {
25224
+ let pressure = false;
25225
+ for await (const event of source) {
25226
+ if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
25227
+ yield event;
25228
+ }
25229
+ if (pressure) onPressure();
25230
+ else onBoundary();
25231
+ }
25232
+ async function* monitorLongTaskRestart(source, onRestartNeeded) {
24552
25233
  let pressure = false;
24553
25234
  for await (const event of source) {
24554
25235
  if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
@@ -24556,7 +25237,10 @@ async function* monitorMemoryRestart(source, onRestartNeeded) {
24556
25237
  }
24557
25238
  if (pressure) onRestartNeeded();
24558
25239
  }
24559
- function requestMemoryRestart(workspace, sessionId, emitOutput) {
25240
+ function requestMemoryRestart(workspace, sessionId, emitOutput, rotation, onRestartRequested) {
25241
+ if (memoryRotationActive()) return;
25242
+ markMemoryRotation();
25243
+ const continuation = rotation?.continuation;
24560
25244
  try {
24561
25245
  const markerPath = memoryRestartMarkerPath(workspace);
24562
25246
  let previous;
@@ -24564,14 +25248,43 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
24564
25248
  previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
24565
25249
  } catch {
24566
25250
  }
24567
- mkdirSync3(dirname17(markerPath), { recursive: true });
24568
- writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
24569
- `, "utf8");
25251
+ writeMarkerAtomically(markerPath, nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date(), continuation));
24570
25252
  } catch {
24571
25253
  }
24572
- process.exitCode = MEMORY_RESTART_EXIT_CODE;
24573
- emitOutput({ type: "notice", message: "Memory watermark reached; the session is saved and Flavor is restarting with a fresh heap." });
24574
- emitOutput({ type: "exit" });
25254
+ if (rotation?.reading !== void 0) {
25255
+ const census = {
25256
+ at: (/* @__PURE__ */ new Date()).toISOString(),
25257
+ reason: continuation === void 0 ? "memory-pressure" : `heap-rotation:${continuation.kind}`,
25258
+ sessionId,
25259
+ heap: censusHeapBlock(rotation.reading),
25260
+ holders: rotation.holders ?? {}
25261
+ };
25262
+ writeRotationCensus(workspace, census);
25263
+ if (heapSnapshotWarranted(rotation.reading)) writeRotationHeapSnapshot(workspace);
25264
+ }
25265
+ emitOutput({
25266
+ type: "notice",
25267
+ message: continuation === void 0 ? "Memory watermark reached; the session is saved and Flavor is restarting with a fresh heap." : `Memory watermark reached; the session and the running ${continuation.kind} are saved, and Flavor is restarting with a fresh heap to continue it.`
25268
+ });
25269
+ if (onRestartRequested !== void 0) onRestartRequested(sessionId);
25270
+ else {
25271
+ process.exitCode = MEMORY_RESTART_EXIT_CODE;
25272
+ emitOutput({ type: "exit" });
25273
+ }
25274
+ }
25275
+ function writeMarkerAtomically(path, marker) {
25276
+ mkdirSync4(dirname17(path), { recursive: true });
25277
+ const temporary = `${path}.${process.pid}.${randomUUID20()}.tmp`;
25278
+ try {
25279
+ writeFileSync3(temporary, `${JSON.stringify(marker)}
25280
+ `, { encoding: "utf8", mode: 384 });
25281
+ renameSync2(temporary, path);
25282
+ } finally {
25283
+ try {
25284
+ unlinkSync3(temporary);
25285
+ } catch {
25286
+ }
25287
+ }
24575
25288
  }
24576
25289
  async function runGitCommit(deps, hint, signal) {
24577
25290
  if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
@@ -24666,7 +25379,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
24666
25379
  const configured = { ...providers };
24667
25380
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
24668
25381
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
24669
- const oauthTokenStore = createFileTokenStore(join29(home, ".flavor-code", "auth.json"));
25382
+ const oauthTokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
24670
25383
  const registered = [];
24671
25384
  let effectiveLlm;
24672
25385
  for (const [name, provider] of Object.entries(configured)) {
@@ -24935,7 +25648,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
24935
25648
  }
24936
25649
  async function optionalText2(path) {
24937
25650
  try {
24938
- return await readFile23(path, "utf8");
25651
+ return await readFile24(path, "utf8");
24939
25652
  } catch (error) {
24940
25653
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
24941
25654
  throw error;
@@ -25020,7 +25733,24 @@ function attachCleanupError(primary, cleanup) {
25020
25733
  }
25021
25734
  }
25022
25735
  async function* runGoalSession(orchestrator, goal, signal) {
25023
- for await (const event of orchestrator.run({ goal, signal })) {
25736
+ yield* goalSessionEvents(orchestrator.run({ goal, signal }));
25737
+ }
25738
+ async function* runResumeGoalSession(orchestrator, goalId, signal) {
25739
+ yield* goalSessionEvents(orchestrator.resume({ goalId, signal }));
25740
+ }
25741
+ async function* goalSessionEvents(events) {
25742
+ for await (const event of events) {
25743
+ if (event.type === "goal-resumed") {
25744
+ yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
25745
+ continue;
25746
+ }
25747
+ if (event.type === "goal-stage-retry") {
25748
+ yield {
25749
+ type: "warning",
25750
+ message: `Goal ${event.stage} attempt ${event.attempt} hit a recoverable boundary (${event.reason}); retrying in ${event.delayMs}ms.`
25751
+ };
25752
+ continue;
25753
+ }
25024
25754
  if (event.type === "goal-plan-created") {
25025
25755
  yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
25026
25756
  yield { type: "notice", message: `Plan file: ${event.planPath}` };
@@ -25047,6 +25777,8 @@ async function* runGoalSession(orchestrator, goal, signal) {
25047
25777
  totalInputTokens: event.event.usage.inputTokens,
25048
25778
  totalOutputTokens: event.event.usage.outputTokens
25049
25779
  };
25780
+ } else if (event.event.type === "error" && isResumableLongTaskError(event.event.error.code)) {
25781
+ yield { type: "warning", message: `Goal worker segment ended at ${event.event.error.code}; checkpointing and verifying before continuing.` };
25050
25782
  } else {
25051
25783
  yield event.event;
25052
25784
  }