flavor-code 1.4.0-beta.4 → 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.
@@ -10,7 +10,7 @@ import {
10
10
  parseMemoryRestartMarker,
11
11
  pendingContinuation,
12
12
  rotationCooldownActive
13
- } from "./chunk-LKQTBL3R.js";
13
+ } from "./chunk-N5MXHJKD.js";
14
14
  import {
15
15
  PERMISSION_MODES,
16
16
  PermissionModeSchema,
@@ -1301,6 +1301,11 @@ function appendThinkingText(previous, delta) {
1301
1301
  // src/ui/transcript.ts
1302
1302
  var MAX_TRANSCRIPT_TURNS = 200;
1303
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;
1304
1309
  function createTranscriptState() {
1305
1310
  return { completed: [], nextId: 1 };
1306
1311
  }
@@ -1661,11 +1666,9 @@ function applyTaskSnapshot(turn, snapshot4, includeTerminal = true) {
1661
1666
  else blocks[index] = block;
1662
1667
  }
1663
1668
  return {
1664
- ...turn,
1669
+ ...withBlocks(turn, blocks),
1665
1670
  taskSnapshot: snapshot4,
1666
- suppressedTaskIds: [...suppressed],
1667
- blocks,
1668
- statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
1671
+ suppressedTaskIds: [...suppressed]
1669
1672
  };
1670
1673
  }
1671
1674
  function terminalTaskIds(snapshot4) {
@@ -1679,13 +1682,10 @@ function upsertStatus(state, block) {
1679
1682
  if (state.active === void 0) return state;
1680
1683
  const blocks = [...state.active.blocks];
1681
1684
  const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
1682
- if (index < 0) blocks.push(block);
1683
- else blocks[index] = block;
1684
- return { ...state, active: {
1685
- ...state.active,
1686
- statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text),
1687
- blocks
1688
- } };
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) };
1689
1689
  }
1690
1690
  function finishActive(state) {
1691
1691
  if (state.active === void 0) return state;
@@ -1709,16 +1709,12 @@ function attachThinking(turn, text) {
1709
1709
  ...block,
1710
1710
  thinkingText: appendThinkingText(block.thinkingText, text)
1711
1711
  };
1712
- return { ...turn, blocks };
1712
+ return withBlocks(turn, blocks);
1713
1713
  }
1714
1714
  function withoutModelActivity(turn, id) {
1715
1715
  const blocks = turn.blocks.filter((block) => block.kind !== "status" || block.activity !== "model" || id !== void 0 && block.id !== id);
1716
1716
  if (blocks.length === turn.blocks.length) return turn;
1717
- return {
1718
- ...turn,
1719
- blocks,
1720
- statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
1721
- };
1717
+ return withBlocks(turn, blocks);
1722
1718
  }
1723
1719
  function restoreTranscriptState(state) {
1724
1720
  const completed = state.completed.map(cloneTurn);
@@ -1820,15 +1816,18 @@ function cloneTurn(turn) {
1820
1816
  function upsertTurnStatus(turn, block) {
1821
1817
  const blocks = [...turn.blocks];
1822
1818
  const index = blocks.findIndex((item) => item.kind === "status" && item.id === block.id);
1823
- if (index < 0) blocks.push(block);
1824
- else blocks[index] = block;
1819
+ const bounded2 = boundedTranscriptBlock(block);
1820
+ if (index < 0) blocks.push(bounded2);
1821
+ else blocks[index] = bounded2;
1825
1822
  return withBlocks(turn, blocks);
1826
1823
  }
1827
1824
  function withBlocks(turn, blocks) {
1825
+ const bounded2 = blocks.length <= MAX_ACTIVE_TRANSCRIPT_BLOCKS ? blocks : blocks.slice(-MAX_ACTIVE_TRANSCRIPT_BLOCKS);
1828
1826
  return {
1829
1827
  ...turn,
1830
- blocks,
1831
- 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)
1832
1831
  };
1833
1832
  }
1834
1833
  function appendLine(text, line) {
@@ -1842,19 +1841,95 @@ function stripRetryBlocks(turn) {
1842
1841
  if (block.id.startsWith("structured-retry:")) return false;
1843
1842
  return true;
1844
1843
  });
1845
- return {
1846
- ...turn,
1847
- blocks,
1848
- statusLines: blocks.filter((item) => item.kind === "status").map((item) => item.text)
1849
- };
1844
+ return withBlocks(turn, blocks);
1850
1845
  }
1851
1846
  function addText(turn, text, onNewLine = false) {
1852
- 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
+ );
1853
1851
  const blocks = [...turn.blocks];
1854
1852
  const last = blocks[blocks.length - 1];
1855
- if (!onNewLine && last?.kind === "text") blocks[blocks.length - 1] = { kind: "text", text: last.text + text };
1856
- else blocks.push({ kind: "text", text });
1857
- 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
+ };
1858
1933
  }
1859
1934
 
1860
1935
  // src/session/checkpoint.ts
@@ -3621,6 +3696,11 @@ var HELP = [
3621
3696
  "/co-work <pal> <goal> | status [id] | cancel <id> [reason]"
3622
3697
  ].join("\n");
3623
3698
  var MAX_STOP_DENIALS = 2;
3699
+ var RESUMABLE_TURN_BOUNDARIES = /* @__PURE__ */ new Set([
3700
+ "iteration_limit",
3701
+ "output_limit",
3702
+ "context_overflow"
3703
+ ]);
3624
3704
  var FlavorSession = class {
3625
3705
  #services;
3626
3706
  #active;
@@ -3852,6 +3932,7 @@ var FlavorSession = class {
3852
3932
  if (!initial) this.#outputQueuedSubmission(submission);
3853
3933
  initial = false;
3854
3934
  await this.#runSubmission(submission);
3935
+ if (this.#services.rotationPending?.() === true) return;
3855
3936
  pending.push(...this.#drainMessages("steer"), ...this.#drainMessages("followUp"));
3856
3937
  }
3857
3938
  }
@@ -3875,6 +3956,7 @@ var FlavorSession = class {
3875
3956
  this.#activeSubmission = submission;
3876
3957
  this.#interrupted = false;
3877
3958
  let outcome = "completed";
3959
+ let resumableBoundary;
3878
3960
  let assistantSummary = "";
3879
3961
  let deliverables = [];
3880
3962
  try {
@@ -3911,7 +3993,10 @@ var FlavorSession = class {
3911
3993
  this.#services.output(event);
3912
3994
  if (event.type === "text") assistantSummary = `${assistantSummary}${event.text}`.slice(-2e3);
3913
3995
  if (event.type === "deliverables") deliverables = event.files.slice(0, 100);
3914
- 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
+ }
3915
4000
  }
3916
4001
  if (controller.signal.aborted) outcome = "cancelled";
3917
4002
  } catch (error) {
@@ -3952,6 +4037,13 @@ var FlavorSession = class {
3952
4037
  }
3953
4038
  }
3954
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
+ }
3955
4047
  if (stopDecision?.decision === "deny" && typeof stopDecision.reason === "string" && stopDecision.reason.length > 0 && !this.#closed && outcome !== "cancelled" && outcome !== "denied") {
3956
4048
  if (this.#stopDenials < MAX_STOP_DENIALS) {
3957
4049
  this.#stopDenials += 1;
@@ -4148,7 +4240,7 @@ Address this now (for example run the required verification or fix), then finish
4148
4240
  });
4149
4241
  }
4150
4242
  #resumeDurableQueue() {
4151
- 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;
4152
4244
  if (!this.#queue.hasPending) return;
4153
4245
  this.#durableResumeStarted = true;
4154
4246
  const submissions = [...this.#drainMessages("steer"), ...this.#drainMessages("followUp")];
@@ -4299,6 +4391,7 @@ function recoveredSubmission(value) {
4299
4391
  if (typeof input.text !== "string" || input.text.trim().length === 0) return void 0;
4300
4392
  if (typeof input.displayText !== "string") return void 0;
4301
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;
4302
4395
  if (input.coWorkPlanningKey !== void 0 && typeof input.coWorkPlanningKey !== "string") return void 0;
4303
4396
  if (input.remoteOrigin !== void 0) {
4304
4397
  const origin = input.remoteOrigin;
@@ -7772,10 +7865,6 @@ var ContextManager = class _ContextManager {
7772
7865
  }
7773
7866
  return microcompact.changed;
7774
7867
  }
7775
- if (this.#consecutiveAutoCompactFailures >= 3) {
7776
- rollbackMicrocompact();
7777
- return false;
7778
- }
7779
7868
  try {
7780
7869
  const compacted = await this.#compactConversation(signal);
7781
7870
  if (compacted) {
@@ -8239,10 +8328,10 @@ function within(root, candidate) {
8239
8328
 
8240
8329
  // src/context/summarizer.ts
8241
8330
  async function summarizeWithModel(options) {
8242
- const maxAttempts = options.maxPromptTooLongAttempts ?? 3;
8243
- if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) throw new Error("maxPromptTooLongAttempts must be positive");
8244
8331
  options.signal?.throwIfAborted();
8245
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");
8246
8335
  let lastOverflow;
8247
8336
  let progress = 20;
8248
8337
  reportProgress(options.onProgress, progress);
@@ -8283,7 +8372,7 @@ async function summarizeWithModel(options) {
8283
8372
  if (terminalError.code !== "context_overflow") throw terminalError;
8284
8373
  lastOverflow = terminalError;
8285
8374
  if (attempt >= maxAttempts || groups.length <= 1) throw terminalError;
8286
- groups = groups.slice(1);
8375
+ groups = groups.slice(Math.max(1, Math.floor(groups.length / 2)));
8287
8376
  }
8288
8377
  throw lastOverflow ?? new Error("Compact summary failed");
8289
8378
  }
@@ -12663,7 +12752,7 @@ function extendBudget(input, dimensions, approvedAt) {
12663
12752
  return LoopStateSchema.parse({
12664
12753
  ...state,
12665
12754
  updatedAt: approvedAt,
12666
- budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals }
12755
+ budget: { ...state.budget, cycleCheckpoint, tokenCheckpoint, approvals: approvals.slice(-64) }
12667
12756
  });
12668
12757
  }
12669
12758
  function rejectBudget(input, terminalReason) {
@@ -12790,6 +12879,7 @@ ${lastCompletedCycle.verification.summary}`;
12790
12879
  });
12791
12880
  let workerError;
12792
12881
  let workerMemoryPressure = false;
12882
+ let workerIterationLimit = false;
12793
12883
  for await (const event of this.#options.runWorker({
12794
12884
  goal,
12795
12885
  cycle,
@@ -12802,8 +12892,9 @@ ${lastCompletedCycle.verification.summary}`;
12802
12892
  cycleOutputTokens += event.outputTokens;
12803
12893
  }
12804
12894
  if (event.type === "error") {
12805
- workerError = event.error.message;
12895
+ workerError = event.error;
12806
12896
  if (event.error.code === "memory_pressure") workerMemoryPressure = true;
12897
+ if (event.error.code === "iteration_limit") workerIterationLimit = true;
12807
12898
  }
12808
12899
  if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
12809
12900
  if (this.#options.hallucinationGuard !== void 0) {
@@ -12816,7 +12907,8 @@ ${lastCompletedCycle.verification.summary}`;
12816
12907
  yield { type: "worker-event", event };
12817
12908
  }
12818
12909
  if (workerMemoryPressure) return;
12819
- if (workerError !== void 0) {
12910
+ if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError(workerError)) {
12911
+ const status = workerError.code === "cancelled" ? "cancelled" : "failed";
12820
12912
  state = LoopStateSchema.parse({
12821
12913
  ...state,
12822
12914
  budget: {
@@ -12826,8 +12918,8 @@ ${lastCompletedCycle.verification.summary}`;
12826
12918
  outputTokens: state.budget.outputTokens + cycleOutputTokens
12827
12919
  }
12828
12920
  });
12829
- state = await this.#terminal(state, "failed", workerError, now());
12830
- yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
12921
+ state = await this.#terminal(state, status, workerError.message, now());
12922
+ yield { type: "loop-terminal", loopId, status, reason: workerError.message };
12831
12923
  return;
12832
12924
  }
12833
12925
  state = LoopStateSchema.parse({
@@ -12870,13 +12962,7 @@ ${lastCompletedCycle.verification.summary}`;
12870
12962
  const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
12871
12963
  await this.#options.onMemoryCheckpoint?.(loopId);
12872
12964
  if (memoryRotationActive()) return;
12873
- if (!report.passed) {
12874
- const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
12875
- state = await this.#terminal(state, "failed", guardReason, now());
12876
- yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
12877
- return;
12878
- }
12879
- guardWarnings = report.warnings;
12965
+ guardWarnings = [...report.blockingReasons, ...report.warnings];
12880
12966
  } catch {
12881
12967
  }
12882
12968
  }
@@ -12898,7 +12984,7 @@ ${lastCompletedCycle.verification.summary}`;
12898
12984
  outputTokens: cycleOutputTokens,
12899
12985
  workspaceFingerprint: fingerprint2,
12900
12986
  verification: evidence
12901
- }],
12987
+ }].slice(-64),
12902
12988
  pendingCycle: null
12903
12989
  });
12904
12990
  await this.#options.persistence.save(state);
@@ -12927,8 +13013,8 @@ ${evidence.summary}`;
12927
13013
  repeatedFailures = failureSignature === previousFailureSignature ? repeatedFailures + 1 : 1;
12928
13014
  previousFailureSignature = failureSignature;
12929
13015
  previousVerification = evidence;
12930
- if (repeatedFailures >= 3) {
12931
- 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.`;
12932
13018
  state = await this.#terminal(state, "no_progress", reason, now());
12933
13019
  yield { type: "loop-terminal", loopId, status: "no_progress", reason };
12934
13020
  return;
@@ -12989,6 +13075,9 @@ ${evidence.summary}`;
12989
13075
  return state;
12990
13076
  }
12991
13077
  };
13078
+ function isResumableWorkerError(error) {
13079
+ return error.code !== "authentication" && error.code !== "model_not_found" && error.code !== "cancelled" && error.code !== "memory_pressure";
13080
+ }
12992
13081
  function countTrailingFailureSignature(cycles, signature) {
12993
13082
  let count = 0;
12994
13083
  for (let index = cycles.length - 1; index >= 0; index -= 1) {
@@ -13477,26 +13566,37 @@ var GoalOrchestrator = class {
13477
13566
  let plan;
13478
13567
  let planPath;
13479
13568
  if (state.plan === null) {
13480
- try {
13481
- signal.throwIfAborted();
13482
- await memoryCheckpoint();
13483
- plan = await runPlanner({
13484
- registry: this.#options.registry,
13485
- modelId: this.#options.plannerModelId,
13486
- objective: state.objective,
13487
- signal,
13488
- onProgress: memoryCheckpoint
13489
- });
13490
- planPath = await writePlanFile(workspace, plan);
13491
- await persistState({ phase: "executing", plan, planPath, contractHash: contractHash(state.objective, plan) });
13492
- yield { type: "goal-plan-created", plan, planPath };
13493
- } catch (error) {
13494
- if (memoryRotationActive()) return;
13495
- const reason = `Goal planning failed: ${message(error)}`;
13496
- await persistState({ phase: "complete", status: "failed" });
13497
- yield { type: "goal-plan-failed", reason };
13498
- yield { type: "goal-failed", reason };
13499
- return;
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;
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);
13599
+ }
13500
13600
  }
13501
13601
  } else {
13502
13602
  plan = state.plan;
@@ -13507,7 +13607,7 @@ var GoalOrchestrator = class {
13507
13607
  let priorFingerprint = state.gapFingerprint;
13508
13608
  let stallStreak = state.stallStreak;
13509
13609
  const startRound = resumeStartRound(state);
13510
- for (let round = startRound; round <= this.#options.maxRounds; round++) {
13610
+ for (let round = startRound; this.#options.maxRounds === void 0 || round <= this.#options.maxRounds; round++) {
13511
13611
  signal.throwIfAborted();
13512
13612
  await this.#options.onRoundBoundary?.(state.id);
13513
13613
  if (memoryRotationActive()) return;
@@ -13527,6 +13627,7 @@ var GoalOrchestrator = class {
13527
13627
  yield { type: "goal-worker-start", round };
13528
13628
  let workerError;
13529
13629
  let workerMemoryPressure = false;
13630
+ let workerIterationLimit = false;
13530
13631
  try {
13531
13632
  for await (const event of this.#options.runWorker({
13532
13633
  goal: state.objective,
@@ -13539,18 +13640,19 @@ var GoalOrchestrator = class {
13539
13640
  yield { type: "goal-worker-event", round, event };
13540
13641
  if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
13541
13642
  if (event.type === "error") {
13542
- workerError = event.error.message;
13643
+ workerError = event.error;
13543
13644
  if (event.error.code === "memory_pressure") workerMemoryPressure = true;
13645
+ if (event.error.code === "iteration_limit") workerIterationLimit = true;
13544
13646
  break;
13545
13647
  }
13546
13648
  }
13547
13649
  } catch (error) {
13548
13650
  if (memoryRotationActive()) return;
13549
- workerError = message(error);
13651
+ workerError = { code: "unknown", message: message(error) };
13550
13652
  }
13551
13653
  if (workerMemoryPressure) return;
13552
- if (workerError !== void 0) {
13553
- const reason = `Worker error in round ${round}: ${workerError}`;
13654
+ if (workerError !== void 0 && !workerIterationLimit && !isResumableWorkerError2(workerError)) {
13655
+ const reason = `Worker error in round ${round}: ${workerError.message}`;
13554
13656
  await persistState({ phase: "complete", status: "failed" });
13555
13657
  yield { type: "goal-failed", reason };
13556
13658
  return;
@@ -13580,7 +13682,7 @@ var GoalOrchestrator = class {
13580
13682
  round,
13581
13683
  workspaceDiffHash: evidence.workspaceDiffHash,
13582
13684
  ...hostVerification === void 0 ? {} : { hostVerification }
13583
- }] });
13685
+ }].slice(-64) });
13584
13686
  let outcome;
13585
13687
  if (plan.kind === "code-change" && hostVerification !== void 0 && !hostVerification.passed) {
13586
13688
  if (hostVerification.commands.length === 0) {
@@ -13594,21 +13696,32 @@ var GoalOrchestrator = class {
13594
13696
  fingerprint: createHash11("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
13595
13697
  };
13596
13698
  }
13597
- } else try {
13598
- outcome = await runClassifier(evidence, plan, {
13599
- registry: this.#options.registry,
13600
- modelId: this.#options.classifierModelId,
13601
- skepticCount: this.#options.skepticCount,
13602
- workspace,
13603
- signal,
13604
- onProgress: memoryCheckpoint
13605
- });
13606
- } catch (error) {
13607
- if (memoryRotationActive()) return;
13608
- outcome = {
13609
- type: "blocked",
13610
- reason: `Classifier infrastructure error: ${message(error)}`
13611
- };
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
+ }
13612
13725
  }
13613
13726
  await persistState({ verifyRounds: round, pendingVerification: null });
13614
13727
  yield {
@@ -13630,7 +13743,7 @@ var GoalOrchestrator = class {
13630
13743
  }
13631
13744
  if (outcome.fingerprint === priorFingerprint) {
13632
13745
  stallStreak++;
13633
- if (stallStreak >= this.#options.maxStallStreak) {
13746
+ if (this.#options.maxStallStreak !== void 0 && stallStreak >= this.#options.maxStallStreak) {
13634
13747
  await persistState({
13635
13748
  phase: "complete",
13636
13749
  status: "failed",
@@ -13676,6 +13789,31 @@ function resumeStartRound(state) {
13676
13789
  }
13677
13790
  return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
13678
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
+ }
13679
13817
  async function writePlanFile(workspace, plan) {
13680
13818
  const dir = join17(workspace, ".flavor");
13681
13819
  const planPath = join17(dir, "goal-plan.md");
@@ -14056,6 +14194,7 @@ function isCode4(error, code) {
14056
14194
  import { readFile as readFile14 } from "fs/promises";
14057
14195
  import { join as join21 } from "path";
14058
14196
  var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
14197
+ var MAX_PERSISTED_VERIFICATION_STREAM_BYTES = 1024;
14059
14198
  async function inferVerificationPlan(workspace) {
14060
14199
  const commands = [];
14061
14200
  const seen = /* @__PURE__ */ new Set();
@@ -14091,9 +14230,9 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
14091
14230
  command: item.command,
14092
14231
  args: item.args,
14093
14232
  exitCode: result.exitCode,
14094
- stdout: result.stdout,
14095
- stderr: result.stderr,
14096
- 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
14097
14236
  });
14098
14237
  if (result.exitCode !== 0) {
14099
14238
  return { passed: false, commands, summary: `${item.label} failed with exit code ${String(result.exitCode)}.` };
@@ -14101,6 +14240,14 @@ async function runVerificationPlan(plan, workspace, signal, executionEnvironment
14101
14240
  }
14102
14241
  return { passed: true, commands, summary: `Passed ${commands.length} deterministic verification command(s).` };
14103
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
+ }
14104
14251
  function npmScript(name) {
14105
14252
  return name === "test" ? { label: name, command: "npm", args: ["test"] } : { label: name, command: "npm", args: ["run", name] };
14106
14253
  }
@@ -14194,6 +14341,35 @@ function normalizeToolCallInput(input, maxDepth = 3) {
14194
14341
  throw new Error(`Tool-call input must decode to an object; received ${kind}`);
14195
14342
  }
14196
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
+
14197
14373
  // src/models/anthropic.ts
14198
14374
  var CLAUDE_CLIENT_HEADERS = {
14199
14375
  "User-Agent": "claude-cli/2.1.0 (external, cli)",
@@ -14316,6 +14492,7 @@ var AnthropicModelAdapter = class {
14316
14492
  void appendUsageLog(line);
14317
14493
  }
14318
14494
  async *stream(request) {
14495
+ const requestAbort = createScopedAbortSignal(request.signal);
14319
14496
  let inputTokens = 0;
14320
14497
  let outputTokens = 0;
14321
14498
  let hasUsage = false;
@@ -14439,13 +14616,13 @@ var AnthropicModelAdapter = class {
14439
14616
  };
14440
14617
  let stream;
14441
14618
  try {
14442
- stream = await this.client.messages.create(body, { signal: request.signal });
14619
+ stream = await this.client.messages.create(body, { signal: requestAbort.signal });
14443
14620
  } catch (error) {
14444
14621
  if (this.thinkingBudget > 0 && !this.#thinkingRejected && isThinkingParamRejected(error)) {
14445
14622
  this.#thinkingRejected = true;
14446
14623
  delete body.thinking;
14447
14624
  stripThinkingBlocks(body.messages);
14448
- stream = await this.client.messages.create(body, { signal: request.signal });
14625
+ stream = await this.client.messages.create(body, { signal: requestAbort.signal });
14449
14626
  } else {
14450
14627
  throw error;
14451
14628
  }
@@ -14557,6 +14734,8 @@ var AnthropicModelAdapter = class {
14557
14734
  };
14558
14735
  }
14559
14736
  yield { type: "error", error: normalizeProviderError(error) };
14737
+ } finally {
14738
+ requestAbort.dispose();
14560
14739
  }
14561
14740
  }
14562
14741
  };
@@ -14701,6 +14880,7 @@ var OpenAIModelAdapter = class {
14701
14880
  void appendUsageLog(line);
14702
14881
  }
14703
14882
  async *stream(request) {
14883
+ const requestAbort = createScopedAbortSignal(request.signal);
14704
14884
  const callIds = /* @__PURE__ */ new Map();
14705
14885
  const pendingCalls = /* @__PURE__ */ new Map();
14706
14886
  const emittedCalls = /* @__PURE__ */ new Set();
@@ -14717,7 +14897,7 @@ var OpenAIModelAdapter = class {
14717
14897
  strict: tool.strict ?? true
14718
14898
  }))
14719
14899
  };
14720
- const stream = this.client.responses.stream(body, { signal: request.signal });
14900
+ const stream = this.client.responses.stream(body, { signal: requestAbort.signal });
14721
14901
  for await (const event of stream) {
14722
14902
  if (event.type === "response.output_item.added" && event.item?.type === "function_call" && event.output_index !== void 0 && event.item.call_id) {
14723
14903
  callIds.set(event.output_index, event.item.call_id);
@@ -14811,6 +14991,8 @@ var OpenAIModelAdapter = class {
14811
14991
  }
14812
14992
  } catch (error) {
14813
14993
  yield { type: "error", error: normalizeProviderError(error) };
14994
+ } finally {
14995
+ requestAbort.dispose();
14814
14996
  }
14815
14997
  }
14816
14998
  };
@@ -18219,19 +18401,6 @@ import { isIP } from "net";
18219
18401
  import { request as httpRequest } from "http";
18220
18402
  import { request as httpsRequest } from "https";
18221
18403
  import { z as z25 } from "zod";
18222
-
18223
- // src/utils/abort.ts
18224
- function waitWithSignal(operation, signal) {
18225
- if (signal === void 0) return operation;
18226
- return new Promise((resolve32, reject) => {
18227
- const abort2 = () => reject(signal.reason);
18228
- if (signal.aborted) abort2();
18229
- else signal.addEventListener("abort", abort2, { once: true });
18230
- operation.then(resolve32, reject).finally(() => signal.removeEventListener("abort", abort2));
18231
- });
18232
- }
18233
-
18234
- // src/tools/web.ts
18235
18404
  var WebFetchInput = z25.object({
18236
18405
  url: z25.string().url().max(8192),
18237
18406
  timeoutMs: z25.coerce.number().int().positive().max(12e4).optional(),
@@ -24063,6 +24232,12 @@ async function createProductionRuntime(options) {
24063
24232
  holders.sessionTree = { entries: sessionHistory.nodeCount };
24064
24233
  } catch {
24065
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
+ }
24066
24241
  return holders;
24067
24242
  };
24068
24243
  const rotateForHeap = (continuation, reading) => {
@@ -24120,21 +24295,9 @@ async function createProductionRuntime(options) {
24120
24295
  await verifierEnvironment?.dispose();
24121
24296
  }
24122
24297
  },
24123
- confirmBudget: async (state, dimensions, signal) => {
24124
- if (options.approvalPolicy === "deny") return "unavailable";
24125
- const reached = dimensions.map((dimension) => dimension === "cycles" ? `${state.budget.cyclesUsed} cycles` : `${state.budget.inputTokens + state.budget.outputTokens} tokens`).join(" and ");
24126
- const next = dimensions.map((dimension) => dimension === "cycles" ? `${state.budget.cycleCheckpoint + state.config.cycleStep} cycles` : `${state.budget.tokenCheckpoint + state.config.tokenStep} tokens`).join(" and ");
24127
- const latestVerification = state.cycles.at(-1)?.verification.summary ?? "No host verification evidence yet.";
24128
- const answers = await questions.ask([{
24129
- header: "Loop budget",
24130
- question: `Loop ${state.loopId} reached ${reached}. Latest verification: ${latestVerification} Continue until the next checkpoint (${next})?`,
24131
- options: [
24132
- { label: "Continue", description: "Extend only the reached budget tranche and keep looping." },
24133
- { label: "Stop", description: "End this loop as budget exhausted." }
24134
- ]
24135
- }], signal);
24136
- return answers[0] === "Continue" ? "approved" : "rejected";
24137
- },
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",
24138
24301
  fingerprint: workspaceFingerprint,
24139
24302
  idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`
24140
24303
  });
@@ -24145,8 +24308,9 @@ async function createProductionRuntime(options) {
24145
24308
  plannerModelId: mainModel,
24146
24309
  classifierModelId: mainModel,
24147
24310
  skepticCount: 3,
24148
- maxRounds: config.goal.maxRounds,
24149
- maxStallStreak: config.goal.maxStallStreak,
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.
24150
24314
  persistence: goalStore,
24151
24315
  onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
24152
24316
  onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
@@ -24287,6 +24451,7 @@ async function createProductionRuntime(options) {
24287
24451
  () => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
24288
24452
  ),
24289
24453
  continuation: () => consumePendingContinuation(),
24454
+ rotationPending: () => memoryRotationActive(),
24290
24455
  mcp: async (command, signal) => {
24291
24456
  signal.throwIfAborted();
24292
24457
  const manager = await mcpReady;
@@ -24844,6 +25009,8 @@ async function* loopSessionEvents(events, hooks) {
24844
25009
  totalInputTokens,
24845
25010
  totalOutputTokens
24846
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.` };
24847
25014
  } else if (event.event.type !== "done") yield event.event;
24848
25015
  continue;
24849
25016
  }
@@ -24898,7 +25065,7 @@ function loopProgressEvent(event) {
24898
25065
  loopId: event.loopId,
24899
25066
  phase: "budget",
24900
25067
  state: "info",
24901
- message: `Confirmation required for ${event.dimensions.join(" and ")} budget.`
25068
+ message: `${event.dimensions.join(" and ")} checkpoint reached; continuing automatically.`
24902
25069
  };
24903
25070
  }
24904
25071
  return {
@@ -24915,6 +25082,9 @@ function terminalProgressState(status) {
24915
25082
  if (status === "needs_human") return "info";
24916
25083
  return "failed";
24917
25084
  }
25085
+ function isResumableLongTaskError(code) {
25086
+ return code !== "authentication" && code !== "model_not_found" && code !== "cancelled" && code !== "memory_pressure";
25087
+ }
24918
25088
  async function workspaceFingerprint(workspace) {
24919
25089
  const hash = createHash18("sha256");
24920
25090
  const diff = await execFileNoThrow(
@@ -25574,6 +25744,13 @@ async function* goalSessionEvents(events) {
25574
25744
  yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
25575
25745
  continue;
25576
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
+ }
25577
25754
  if (event.type === "goal-plan-created") {
25578
25755
  yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
25579
25756
  yield { type: "notice", message: `Plan file: ${event.planPath}` };
@@ -25600,6 +25777,8 @@ async function* goalSessionEvents(events) {
25600
25777
  totalInputTokens: event.event.usage.inputTokens,
25601
25778
  totalOutputTokens: event.event.usage.outputTokens
25602
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.` };
25603
25782
  } else {
25604
25783
  yield event.event;
25605
25784
  }