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

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 (33) hide show
  1. package/dist/agent/loop.d.ts +4 -2
  2. package/dist/{app-H7RYDMD3.js → app-UWIEIWBQ.js} +3 -3
  3. package/dist/{chunk-3BHKW5A7.js → chunk-4RR5VVX4.js} +8 -2
  4. package/dist/{chunk-NIKCPCJG.js → chunk-DCNUBQCH.js} +826 -273
  5. package/dist/{chunk-KBHWS7M6.js → chunk-KQMKY7C5.js} +4 -0
  6. package/dist/chunk-LKQTBL3R.js +107 -0
  7. package/dist/cli-main.js +20 -13
  8. package/dist/cli.js +24 -5
  9. package/dist/config/schema.d.ts +4 -0
  10. package/dist/context/manager.d.ts +9 -0
  11. package/dist/desktop/main.js +1170 -532
  12. package/dist/desktop-renderer/assets/{index-CYd5JkTR.js → index-C0t1nGih.js} +2 -2
  13. package/dist/desktop-renderer/assets/{interactive-terminal-j52Ty3QO.js → interactive-terminal-BJqe96mZ.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 +18 -0
  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 +8 -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 +18 -0
  24. package/dist/loop/types.d.ts +7 -0
  25. package/dist/production.d.ts +17 -0
  26. package/dist/sdk/index.js +4 -4
  27. package/dist/session/tree.d.ts +2 -0
  28. package/dist/ui/session.d.ts +9 -0
  29. package/dist/utils/heap.d.ts +58 -0
  30. package/dist/utils/memory-restart.d.ts +38 -3
  31. package/dist/utils/rotation-census.d.ts +36 -0
  32. package/package.json +1 -1
  33. package/dist/chunk-CIVPKHF4.js +0 -53
@@ -1,8 +1,8 @@
1
1
  // src/desktop/main.ts
2
2
  import { appendFileSync } from "fs";
3
3
  import { createHash as createHash28, randomUUID as randomUUID29 } from "crypto";
4
- import { readFile as readFile40, stat as stat19, writeFile as writeFile19 } from "fs/promises";
5
- import { basename as basename12, dirname as dirname23, join as join50, resolve as resolve43 } from "path";
4
+ import { readFile as readFile41, stat as stat20, writeFile as writeFile19 } from "fs/promises";
5
+ import { basename as basename12, dirname as dirname23, join as join51, resolve as resolve43 } from "path";
6
6
  import { fileURLToPath as fileURLToPath3 } from "url";
7
7
  import { app, BrowserWindow as BrowserWindow2, clipboard, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
8
8
 
@@ -303,6 +303,10 @@ var FlavorConfigSchema = z2.object({
303
303
  maxTokens: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).default(5e5),
304
304
  isolation: z2.literal("auto").default("auto")
305
305
  }).prefault({}),
306
+ goal: z2.object({
307
+ maxRounds: z2.number().int().positive().max(1e4).default(100),
308
+ maxStallStreak: z2.number().int().positive().max(100).default(3)
309
+ }).prefault({}),
306
310
  context: z2.object({
307
311
  windowTokens: z2.number().int().positive().default(2e5),
308
312
  reservedOutputTokens: z2.number().int().nonnegative().default(2e4),
@@ -4225,11 +4229,11 @@ ${summarizeReport(report2)}`).join("\n\n")
4225
4229
  }
4226
4230
 
4227
4231
  // src/production.ts
4228
- import { readFile as readFile28 } from "fs/promises";
4229
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
4232
+ import { readFile as readFile29 } from "fs/promises";
4233
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
4230
4234
  import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
4231
4235
  import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
4232
- import { basename as basename9, dirname as dirname22, join as join37, relative as relative25, resolve as resolve36, sep as sep25 } from "path";
4236
+ import { basename as basename9, dirname as dirname22, join as join38, relative as relative25, resolve as resolve36, sep as sep25 } from "path";
4233
4237
 
4234
4238
  // src/agent/subagents.ts
4235
4239
  import { z as z10 } from "zod";
@@ -5572,6 +5576,18 @@ var ContextManager = class _ContextManager {
5572
5576
  estimatedTokens() {
5573
5577
  return estimateMessageTokens(this.messagesForModel());
5574
5578
  }
5579
+ /** Retained sizes for the rotation census: what this context holds right now. */
5580
+ census() {
5581
+ const chars = (messages) => messages.reduce((sum, entry2) => sum + modelContentText(entry2.content).length, 0);
5582
+ return {
5583
+ messages: this.#messages.length,
5584
+ messageChars: chars(this.#messages),
5585
+ visibilityEntries: this.#visibilityLog.length,
5586
+ visibilityChars: this.#visibilityLog.reduce((sum, record3) => sum + record3.content.length, 0),
5587
+ transientEntries: this.#activeTransientSystem.size,
5588
+ transientChars: chars([...this.#activeTransientSystem.values()])
5589
+ };
5590
+ }
5575
5591
  needsCompaction() {
5576
5592
  if (modelVisibleText(this.messagesForModel()).length >= this.#compactAtChars) return true;
5577
5593
  return calculateContextPressure(this.#currentTokenUsage(), this.#compaction).shouldAutoCompact;
@@ -6140,8 +6156,75 @@ function reportProgress(callback, percentage) {
6140
6156
  }
6141
6157
  }
6142
6158
 
6143
- // src/agent/loop.ts
6159
+ // src/utils/heap.ts
6144
6160
  import { getHeapStatistics } from "v8";
6161
+ import { totalmem } from "os";
6162
+ var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
6163
+ var MEMORY_ROTATION_HEAP_RATIO = 0.6;
6164
+ var MEMORY_PRESSURE_RSS_RATIO = 0.75;
6165
+ var MEMORY_ROTATION_RSS_RATIO = 0.6;
6166
+ var ROTATION_PRECHECK_RATIO = 0.45;
6167
+ function readHeap() {
6168
+ const heapLimit = getHeapStatistics().heap_size_limit;
6169
+ const heapUsed = process.memoryUsage().heapUsed;
6170
+ return { heapUsed, heapLimit, ratio: heapLimit > 0 ? heapUsed / heapLimit : 0 };
6171
+ }
6172
+ function forceFullGc() {
6173
+ const gc = globalThis.gc;
6174
+ if (typeof gc !== "function") return false;
6175
+ gc();
6176
+ gc();
6177
+ return true;
6178
+ }
6179
+ function ratioOf(heapLimit, heapUsed) {
6180
+ return heapLimit > 0 ? heapUsed / heapLimit : 0;
6181
+ }
6182
+ function effectiveMemoryLimit() {
6183
+ const physical = totalmem();
6184
+ const constrained = process.constrainedMemory?.() ?? 0;
6185
+ return constrained > 0 ? Math.min(physical, constrained) : physical;
6186
+ }
6187
+ function withRss(reading, gcVerified) {
6188
+ const usage = process.memoryUsage();
6189
+ const memoryLimit = effectiveMemoryLimit();
6190
+ return {
6191
+ ...reading,
6192
+ gcVerified,
6193
+ rss: usage.rss,
6194
+ memoryLimit,
6195
+ rssRatio: ratioOf(memoryLimit, usage.rss),
6196
+ external: usage.external,
6197
+ arrayBuffers: usage.arrayBuffers
6198
+ };
6199
+ }
6200
+ function verifiedHeapPressure(threshold) {
6201
+ const raw = readHeap();
6202
+ const before = withRss(raw, false);
6203
+ const rssThreshold = threshold >= MEMORY_PRESSURE_HEAP_RATIO ? MEMORY_PRESSURE_RSS_RATIO : threshold;
6204
+ if ((raw.heapLimit <= 0 || raw.ratio < threshold) && (before.rssRatio ?? 0) < rssThreshold) return void 0;
6205
+ if (!forceFullGc()) return before;
6206
+ const heapUsed = process.memoryUsage().heapUsed;
6207
+ const ratio = ratioOf(raw.heapLimit, heapUsed);
6208
+ const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
6209
+ if (ratio < threshold && (verified.rssRatio ?? 0) < rssThreshold) return void 0;
6210
+ return verified;
6211
+ }
6212
+ function heapRotationNeeded() {
6213
+ const raw = readHeap();
6214
+ const before = withRss(raw, false);
6215
+ if ((raw.heapLimit <= 0 || raw.ratio < ROTATION_PRECHECK_RATIO) && (before.rssRatio ?? 0) < ROTATION_PRECHECK_RATIO) return void 0;
6216
+ if (!forceFullGc()) {
6217
+ return raw.ratio >= MEMORY_PRESSURE_HEAP_RATIO || (before.rssRatio ?? 0) >= MEMORY_PRESSURE_RSS_RATIO ? before : void 0;
6218
+ }
6219
+ const heapUsed = process.memoryUsage().heapUsed;
6220
+ const ratio = ratioOf(raw.heapLimit, heapUsed);
6221
+ const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
6222
+ if (ratio < MEMORY_ROTATION_HEAP_RATIO && (verified.rssRatio ?? 0) < MEMORY_ROTATION_RSS_RATIO) return void 0;
6223
+ return verified;
6224
+ }
6225
+ function heapSnapshotWarranted(_reading) {
6226
+ return process.env.FLAVOR_HEAP_SNAPSHOT_ON_ROTATE === "1";
6227
+ }
6145
6228
 
6146
6229
  // src/models/structured.ts
6147
6230
  import { z as z12 } from "zod";
@@ -6560,7 +6643,6 @@ function envMaxIterations() {
6560
6643
  if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
6561
6644
  return parsed;
6562
6645
  }
6563
- var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
6564
6646
  function memoryPressureError(heapUsed, heapLimit) {
6565
6647
  if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
6566
6648
  const mb = (bytes) => Math.round(bytes / 1048576);
@@ -6570,7 +6652,12 @@ function memoryPressureError(heapUsed, heapLimit) {
6570
6652
  };
6571
6653
  }
6572
6654
  function currentMemoryPressure() {
6573
- return memoryPressureError(process.memoryUsage().heapUsed, getHeapStatistics().heap_size_limit);
6655
+ const reading = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
6656
+ if (reading === void 0) return void 0;
6657
+ return memoryPressureError(reading.heapUsed, reading.heapLimit) ?? {
6658
+ code: "memory_pressure",
6659
+ message: `Process RSS reached ${Math.round((reading.rssRatio ?? 0) * 100)}% of the available memory limit; stopping before system OOM`
6660
+ };
6574
6661
  }
6575
6662
  var AgentLoop = class {
6576
6663
  #options;
@@ -8393,6 +8480,10 @@ var HarnessJournal = class {
8393
8480
  get records() {
8394
8481
  return this.#records.map((record3) => structuredClone(record3));
8395
8482
  }
8483
+ /** Retained record count for the rotation census. */
8484
+ get recordCount() {
8485
+ return this.#records.length;
8486
+ }
8396
8487
  admitQueue(kind, payload) {
8397
8488
  QueueKindSchema.parse(kind);
8398
8489
  const id = randomUUID8();
@@ -8669,19 +8760,38 @@ function jsonSafeRecord(value) {
8669
8760
  return JSON.parse(json);
8670
8761
  }
8671
8762
  function hashJson(value) {
8672
- return createHash6("sha256").update(stableJson(value)).digest("hex");
8673
- }
8674
- function recordHash(record3) {
8675
- const { hash: _hash, ...unsigned } = record3;
8676
- return hashJson(unsigned);
8763
+ const hash = createHash6("sha256");
8764
+ stableHashInto(hash, value);
8765
+ return hash.digest("hex");
8677
8766
  }
8678
- function stableJson(value) {
8679
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
8767
+ function stableHashInto(hash, value) {
8768
+ if (Array.isArray(value)) {
8769
+ hash.update("[");
8770
+ for (let index = 0; index < value.length; index += 1) {
8771
+ if (index > 0) hash.update(",");
8772
+ stableHashInto(hash, value[index]);
8773
+ }
8774
+ hash.update("]");
8775
+ return;
8776
+ }
8680
8777
  if (typeof value === "object" && value !== null) {
8681
8778
  const object = value;
8682
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
8779
+ hash.update("{");
8780
+ const keys = Object.keys(object).sort();
8781
+ for (let index = 0; index < keys.length; index += 1) {
8782
+ const key = keys[index];
8783
+ if (index > 0) hash.update(",");
8784
+ hash.update(`${JSON.stringify(key)}:`);
8785
+ stableHashInto(hash, object[key]);
8786
+ }
8787
+ hash.update("}");
8788
+ return;
8683
8789
  }
8684
- return JSON.stringify(value) ?? String(value);
8790
+ hash.update(JSON.stringify(value) ?? String(value));
8791
+ }
8792
+ function recordHash(record3) {
8793
+ const { hash: _hash, ...unsigned } = record3;
8794
+ return hashJson(unsigned);
8685
8795
  }
8686
8796
  function assertSessionId(value) {
8687
8797
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
@@ -11387,6 +11497,81 @@ async function initializeFlavor(cwd) {
11387
11497
  return { path, created: existing === void 0, content, facts };
11388
11498
  }
11389
11499
 
11500
+ // src/utils/memory-restart.ts
11501
+ import { join as join14 } from "path";
11502
+ var MEMORY_RESTART_EXIT_CODE = 75;
11503
+ var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
11504
+ var MEMORY_RESTART_WINDOW_MS = 60 * 60 * 1e3;
11505
+ var MEMORY_RESTART_MIN_GAP_MS = 5 * 60 * 1e3;
11506
+ var MEMORY_RESTART_CONTINUATION_TTL_MS = 10 * 60 * 1e3;
11507
+ var rotationActive = false;
11508
+ function markMemoryRotation() {
11509
+ rotationActive = true;
11510
+ }
11511
+ function memoryRotationActive() {
11512
+ return rotationActive;
11513
+ }
11514
+ function clearMemoryRotation() {
11515
+ rotationActive = false;
11516
+ }
11517
+ function memoryRestartMarkerPath(workspace) {
11518
+ return join14(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
11519
+ }
11520
+ function nextMemoryRestartMarker(previous, sessionId, now, continuation) {
11521
+ const previousWindowStart = previous?.windowStartedAt ?? previous?.requestedAt;
11522
+ const reusable = previous !== void 0 && previous.sessionId === sessionId && previousWindowStart !== void 0 && now.getTime() - Date.parse(previousWindowStart) < MEMORY_RESTART_WINDOW_MS && Number.isInteger(previous.attempts) && previous.attempts > 0;
11523
+ return {
11524
+ sessionId,
11525
+ windowStartedAt: reusable ? previousWindowStart : now.toISOString(),
11526
+ requestedAt: now.toISOString(),
11527
+ attempts: reusable ? previous.attempts + 1 : 1,
11528
+ ...continuation === void 0 ? {} : { continuation }
11529
+ };
11530
+ }
11531
+ function consumedMemoryRestartMarker(marker) {
11532
+ return {
11533
+ sessionId: marker.sessionId,
11534
+ ...marker.windowStartedAt === void 0 ? {} : { windowStartedAt: marker.windowStartedAt },
11535
+ requestedAt: marker.requestedAt,
11536
+ attempts: marker.attempts
11537
+ };
11538
+ }
11539
+ function rotationCooldownActive(marker, sessionId, now) {
11540
+ return marker !== void 0 && marker.sessionId === sessionId && now.getTime() - Date.parse(marker.requestedAt) < MEMORY_RESTART_MIN_GAP_MS;
11541
+ }
11542
+ function pendingContinuation(marker, sessionId, now) {
11543
+ if (marker === void 0 || marker.sessionId !== sessionId) return void 0;
11544
+ if (now.getTime() - Date.parse(marker.requestedAt) > MEMORY_RESTART_CONTINUATION_TTL_MS) return void 0;
11545
+ const continuation = marker.continuation;
11546
+ if (continuation === void 0) return void 0;
11547
+ if (continuation.kind !== "loop" && continuation.kind !== "goal") return void 0;
11548
+ if (typeof continuation.id !== "string" || continuation.id.length === 0) return void 0;
11549
+ return continuation;
11550
+ }
11551
+ function parseMemoryRestartMarker(json) {
11552
+ try {
11553
+ const value = JSON.parse(json);
11554
+ if (typeof value.sessionId !== "string" || typeof value.requestedAt !== "string" || typeof value.attempts !== "number") {
11555
+ return void 0;
11556
+ }
11557
+ const continuation = value.continuation;
11558
+ if (continuation !== void 0 && (typeof continuation !== "object" || continuation === null || continuation.kind !== "loop" && continuation.kind !== "goal" || typeof continuation.id !== "string")) {
11559
+ return void 0;
11560
+ }
11561
+ const windowStartedAt = value.windowStartedAt;
11562
+ if (windowStartedAt !== void 0 && typeof windowStartedAt !== "string") return void 0;
11563
+ return {
11564
+ sessionId: value.sessionId,
11565
+ ...windowStartedAt === void 0 ? {} : { windowStartedAt },
11566
+ requestedAt: value.requestedAt,
11567
+ attempts: value.attempts,
11568
+ ...continuation === void 0 ? {} : { continuation }
11569
+ };
11570
+ } catch {
11571
+ return void 0;
11572
+ }
11573
+ }
11574
+
11390
11575
  // src/skills/builtin-loop.ts
11391
11576
  function buildLoopCyclePrompt(input) {
11392
11577
  return [
@@ -11467,6 +11652,14 @@ var LoopStateSchema = z20.object({
11467
11652
  approvals: z20.array(LoopApprovalSchema)
11468
11653
  }).strict(),
11469
11654
  cycles: z20.array(LoopCycleEvidenceSchema),
11655
+ /** Completed worker awaiting host verification; avoids replaying tool work after rotation. */
11656
+ pendingCycle: z20.object({
11657
+ cycle: z20.number().int().positive(),
11658
+ startedAt: z20.string().datetime({ offset: true }),
11659
+ inputTokens: z20.number().int().nonnegative(),
11660
+ outputTokens: z20.number().int().nonnegative(),
11661
+ workerText: z20.string().max(16e3)
11662
+ }).strict().nullable().optional(),
11470
11663
  terminalReason: z20.string().optional()
11471
11664
  }).strict();
11472
11665
  var LoopEventSchema = z20.object({
@@ -11555,15 +11748,45 @@ var LoopOrchestrator = class {
11555
11748
  tokenCheckpoint: this.#options.config.maxTokens,
11556
11749
  approvals: []
11557
11750
  },
11558
- cycles: []
11751
+ cycles: [],
11752
+ pendingCycle: null
11559
11753
  });
11560
11754
  await this.#options.persistence.create(state);
11755
+ yield* this.#execute(state, request.goal, request.signal);
11756
+ }
11757
+ /**
11758
+ * Continue a loop whose process rotated onto a fresh heap. The persisted
11759
+ * state decides where to pick up; an interrupted cycle simply runs again.
11760
+ */
11761
+ async *resume(request) {
11762
+ const persistence = this.#options.persistence;
11763
+ if (persistence.load === void 0) throw new Error("Loop persistence does not support resume");
11764
+ let state;
11765
+ try {
11766
+ state = await persistence.load(request.loopId);
11767
+ } catch (error) {
11768
+ yield { type: "loop-terminal", loopId: request.loopId, status: "failed", reason: message(error) };
11769
+ return;
11770
+ }
11771
+ if (state.status !== "running") {
11772
+ const status = state.status;
11773
+ yield { type: "loop-terminal", loopId: state.loopId, status, reason: state.terminalReason ?? "The loop is not running." };
11774
+ return;
11775
+ }
11776
+ yield* this.#execute(state, state.goal, request.signal);
11777
+ }
11778
+ async *#execute(initialState, goal, signal) {
11779
+ const now = this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
11780
+ const loopId = initialState.loopId;
11781
+ let state = initialState;
11561
11782
  try {
11783
+ await this.#options.onMemoryCheckpoint?.(loopId);
11784
+ if (memoryRotationActive()) return;
11562
11785
  const workspaceResolution = await this.#options.prepareWorkspace({
11563
11786
  root: this.#options.workspace,
11564
11787
  loopId,
11565
- goal: request.goal,
11566
- signal: request.signal
11788
+ goal,
11789
+ signal
11567
11790
  });
11568
11791
  if (workspaceResolution.kind === "needs_human") {
11569
11792
  state = await this.#terminal(state, "needs_human", workspaceResolution.reason, now());
@@ -11581,67 +11804,87 @@ var LoopOrchestrator = class {
11581
11804
  cycleCheckpoint: state.budget.cycleCheckpoint,
11582
11805
  tokenCheckpoint: state.budget.tokenCheckpoint
11583
11806
  };
11584
- let previousVerification = plan.commands.length === 0 ? {
11807
+ const lastCompletedCycle = state.cycles.at(-1);
11808
+ let previousVerification = lastCompletedCycle?.verification ?? (plan.commands.length === 0 ? {
11585
11809
  passed: false,
11586
11810
  commands: [],
11587
11811
  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.`
11588
- } : void 0;
11589
- let previousFailureSignature;
11590
- let repeatedFailures = 0;
11812
+ } : void 0);
11813
+ let previousFailureSignature = lastCompletedCycle === void 0 ? void 0 : `${lastCompletedCycle.workspaceFingerprint}
11814
+ ${lastCompletedCycle.verification.summary}`;
11815
+ let repeatedFailures = previousFailureSignature === void 0 ? 0 : countTrailingFailureSignature(state.cycles, previousFailureSignature);
11591
11816
  while (state.status === "running") {
11592
- request.signal.throwIfAborted();
11593
- const cycle = state.budget.cyclesUsed + 1;
11594
- const startedAt = now();
11595
- await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
11596
- yield { type: "loop-cycle-start", loopId, cycle };
11597
- const prompt = buildLoopCyclePrompt({
11598
- goal: request.goal,
11599
- cycle,
11600
- ...previousVerification === void 0 ? {} : {
11601
- memory: `Previous host verification: ${previousVerification.summary}`,
11602
- verification: previousVerification
11603
- }
11604
- });
11605
- let cycleInputTokens = 0;
11606
- let cycleOutputTokens = 0;
11607
- let workerError;
11608
- let workerText = "";
11609
- for await (const event of this.#options.runWorker({
11610
- goal: request.goal,
11611
- cycle,
11612
- workspace: executionWorkspace.root,
11613
- prompt,
11614
- signal: request.signal
11615
- })) {
11616
- if (event.type === "usage") {
11617
- cycleInputTokens += event.inputTokens;
11618
- cycleOutputTokens += event.outputTokens;
11619
- }
11620
- if (event.type === "error") workerError = event.error.message;
11621
- if (event.type === "text") workerText += event.text;
11622
- if (this.#options.hallucinationGuard !== void 0) {
11623
- if (event.type === "tool-start") {
11624
- this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
11625
- } else if (event.type === "tool-end") {
11626
- this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
11817
+ signal.throwIfAborted();
11818
+ await this.#options.onCycleBoundary?.(loopId);
11819
+ if (memoryRotationActive()) return;
11820
+ const pendingCycle = state.pendingCycle;
11821
+ const cycle = pendingCycle?.cycle ?? state.budget.cyclesUsed + 1;
11822
+ const startedAt = pendingCycle?.startedAt ?? now();
11823
+ let cycleInputTokens = pendingCycle?.inputTokens ?? 0;
11824
+ let cycleOutputTokens = pendingCycle?.outputTokens ?? 0;
11825
+ let workerText = pendingCycle?.workerText ?? "";
11826
+ if (pendingCycle === void 0 || pendingCycle === null) {
11827
+ await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
11828
+ yield { type: "loop-cycle-start", loopId, cycle };
11829
+ const prompt = buildLoopCyclePrompt({
11830
+ goal,
11831
+ cycle,
11832
+ ...previousVerification === void 0 ? {} : {
11833
+ memory: `Previous host verification: ${previousVerification.summary}`,
11834
+ verification: previousVerification
11835
+ }
11836
+ });
11837
+ let workerError;
11838
+ let workerMemoryPressure = false;
11839
+ for await (const event of this.#options.runWorker({
11840
+ goal,
11841
+ cycle,
11842
+ workspace: executionWorkspace.root,
11843
+ prompt,
11844
+ signal
11845
+ })) {
11846
+ if (event.type === "usage") {
11847
+ cycleInputTokens += event.inputTokens;
11848
+ cycleOutputTokens += event.outputTokens;
11849
+ }
11850
+ if (event.type === "error") {
11851
+ workerError = event.error.message;
11852
+ if (event.error.code === "memory_pressure") workerMemoryPressure = true;
11627
11853
  }
11854
+ if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
11855
+ if (this.#options.hallucinationGuard !== void 0) {
11856
+ if (event.type === "tool-start") {
11857
+ this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
11858
+ } else if (event.type === "tool-end") {
11859
+ this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
11860
+ }
11861
+ }
11862
+ yield { type: "worker-event", event };
11863
+ }
11864
+ if (workerMemoryPressure) return;
11865
+ if (workerError !== void 0) {
11866
+ state = LoopStateSchema.parse({
11867
+ ...state,
11868
+ budget: {
11869
+ ...state.budget,
11870
+ cyclesUsed: cycle,
11871
+ inputTokens: state.budget.inputTokens + cycleInputTokens,
11872
+ outputTokens: state.budget.outputTokens + cycleOutputTokens
11873
+ }
11874
+ });
11875
+ state = await this.#terminal(state, "failed", workerError, now());
11876
+ yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
11877
+ return;
11628
11878
  }
11629
- yield { type: "worker-event", event };
11630
- }
11631
- if (workerError !== void 0) {
11632
11879
  state = LoopStateSchema.parse({
11633
11880
  ...state,
11634
- budget: {
11635
- ...state.budget,
11636
- cyclesUsed: cycle,
11637
- inputTokens: state.budget.inputTokens + cycleInputTokens,
11638
- outputTokens: state.budget.outputTokens + cycleOutputTokens
11639
- }
11881
+ pendingCycle: { cycle, startedAt, inputTokens: cycleInputTokens, outputTokens: cycleOutputTokens, workerText },
11882
+ updatedAt: now()
11640
11883
  });
11641
- state = await this.#terminal(state, "failed", workerError, now());
11642
- yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
11643
- return;
11884
+ await this.#options.persistence.save(state);
11644
11885
  }
11886
+ await this.#options.onMemoryCheckpoint?.(loopId);
11887
+ if (memoryRotationActive()) return;
11645
11888
  if (plan.commands.length === 0) {
11646
11889
  plan = await this.#options.inferVerification(executionWorkspace.root);
11647
11890
  if (plan.commands.length > 0) {
@@ -11661,8 +11904,28 @@ var LoopOrchestrator = class {
11661
11904
  passed: false,
11662
11905
  commands: [],
11663
11906
  summary: plan.needsHumanReason ?? "No deterministic verification command was found after the discovery cycle."
11664
- } : await this.#options.runVerifier(plan, executionWorkspace.root, request.signal);
11907
+ } : await this.#options.runVerifier(plan, executionWorkspace.root, signal);
11908
+ await this.#options.onMemoryCheckpoint?.(loopId);
11909
+ if (memoryRotationActive()) return;
11665
11910
  const fingerprint2 = await this.#options.fingerprint(executionWorkspace.root);
11911
+ let guardWarnings = [];
11912
+ if (evidence.passed && this.#options.hallucinationGuard !== void 0) {
11913
+ try {
11914
+ await this.#options.onMemoryCheckpoint?.(loopId);
11915
+ if (memoryRotationActive()) return;
11916
+ const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
11917
+ await this.#options.onMemoryCheckpoint?.(loopId);
11918
+ if (memoryRotationActive()) return;
11919
+ if (!report.passed) {
11920
+ const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
11921
+ state = await this.#terminal(state, "failed", guardReason, now());
11922
+ yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
11923
+ return;
11924
+ }
11925
+ guardWarnings = report.warnings;
11926
+ } catch {
11927
+ }
11928
+ }
11666
11929
  const completedAt = now();
11667
11930
  state = LoopStateSchema.parse({
11668
11931
  ...state,
@@ -11681,7 +11944,8 @@ var LoopOrchestrator = class {
11681
11944
  outputTokens: cycleOutputTokens,
11682
11945
  workspaceFingerprint: fingerprint2,
11683
11946
  verification: evidence
11684
- }]
11947
+ }],
11948
+ pendingCycle: null
11685
11949
  });
11686
11950
  await this.#options.persistence.save(state);
11687
11951
  await this.#options.persistence.append({
@@ -11699,20 +11963,6 @@ var LoopOrchestrator = class {
11699
11963
  return;
11700
11964
  }
11701
11965
  if (evidence.passed) {
11702
- let guardWarnings = [];
11703
- if (this.#options.hallucinationGuard !== void 0) {
11704
- try {
11705
- const report = await this.#options.hallucinationGuard.evaluate(request.goal, workerText);
11706
- if (!report.passed) {
11707
- const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
11708
- state = await this.#terminal(state, "failed", guardReason, now());
11709
- yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
11710
- return;
11711
- }
11712
- guardWarnings = report.warnings;
11713
- } catch {
11714
- }
11715
- }
11716
11966
  const reason = [evidence.summary, ...guardWarnings].filter(Boolean).join("; ");
11717
11967
  state = await this.#terminal(state, "succeeded", reason, now());
11718
11968
  yield { type: "loop-terminal", loopId, status: "succeeded", reason };
@@ -11738,7 +11988,7 @@ ${evidence.summary}`;
11738
11988
  cycleCheckpoint: state.budget.cycleCheckpoint,
11739
11989
  tokenCheckpoint: state.budget.tokenCheckpoint
11740
11990
  };
11741
- const confirmation = await this.#options.confirmBudget(state, decision.dimensions, request.signal);
11991
+ const confirmation = await this.#options.confirmBudget(state, decision.dimensions, signal);
11742
11992
  if (confirmation === "unavailable") {
11743
11993
  const reason = "Loop budget extension requires user confirmation.";
11744
11994
  state = await this.#terminal(state, "needs_human", reason, now());
@@ -11765,8 +12015,9 @@ ${evidence.summary}`;
11765
12015
  }
11766
12016
  }
11767
12017
  } catch (error) {
11768
- const status = request.signal.aborted ? "cancelled" : "failed";
11769
- const reason = request.signal.aborted ? "Loop cancelled by user." : message(error);
12018
+ if (memoryRotationActive()) return;
12019
+ const status = signal.aborted ? "cancelled" : "failed";
12020
+ const reason = signal.aborted ? "Loop cancelled by user." : message(error);
11770
12021
  state = await this.#terminal(state, status, reason, now());
11771
12022
  yield { type: "loop-terminal", loopId, status, reason };
11772
12023
  }
@@ -11784,11 +12035,21 @@ ${evidence.summary}`;
11784
12035
  return state;
11785
12036
  }
11786
12037
  };
12038
+ function countTrailingFailureSignature(cycles, signature) {
12039
+ let count = 0;
12040
+ for (let index = cycles.length - 1; index >= 0; index -= 1) {
12041
+ const cycle = cycles[index];
12042
+ if (`${cycle.workspaceFingerprint}
12043
+ ${cycle.verification.summary}` !== signature) break;
12044
+ count += 1;
12045
+ }
12046
+ return count;
12047
+ }
11787
12048
 
11788
12049
  // src/goal/orchestrator.ts
11789
12050
  import { createHash as createHash11 } from "crypto";
11790
12051
  import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
11791
- import { join as join15 } from "path";
12052
+ import { join as join16 } from "path";
11792
12053
 
11793
12054
  // src/goal/types.ts
11794
12055
  import { z as z21 } from "zod";
@@ -11851,6 +12112,11 @@ var GoalStateSchema = z21.object({
11851
12112
  planPath: z21.string().nullable(),
11852
12113
  verifyRounds: z21.number().int().min(0),
11853
12114
  workerRounds: z21.number().int().min(0),
12115
+ /** Worker output checkpoint used to resume verification without rerunning tools. */
12116
+ pendingVerification: z21.object({
12117
+ round: z21.number().int().positive(),
12118
+ finalResponse: z21.string().max(16e3)
12119
+ }).strict().nullable().optional(),
11854
12120
  lastGaps: z21.array(GapSchema),
11855
12121
  gapFingerprint: z21.string(),
11856
12122
  stallStreak: z21.number().int().min(0),
@@ -11896,6 +12162,7 @@ ${objective}`
11896
12162
  ].join("\n");
11897
12163
  }
11898
12164
  async function runPlanner(options) {
12165
+ await options.onProgress?.();
11899
12166
  const { adapter, model } = options.registry.get(options.modelId);
11900
12167
  const prompt = buildPlannerPrompt(options.objective);
11901
12168
  const messages = [{ role: "user", content: prompt }];
@@ -11907,6 +12174,7 @@ async function runPlanner(options) {
11907
12174
  tools: [],
11908
12175
  ...options.signal === void 0 ? {} : { signal: options.signal }
11909
12176
  })) {
12177
+ await options.onProgress?.();
11910
12178
  if (event.type === "text") text += event.text;
11911
12179
  else if (event.type === "error") throw event.error;
11912
12180
  else if (event.type === "done") {
@@ -11953,7 +12221,7 @@ function extractJson(text) {
11953
12221
  // src/goal/classifier.ts
11954
12222
  import { createHash as createHash10 } from "crypto";
11955
12223
  import { readFile as readFile10 } from "fs/promises";
11956
- import { join as join14 } from "path";
12224
+ import { join as join15 } from "path";
11957
12225
  function buildSkepticPrompt(evidence, plan, index) {
11958
12226
  const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
11959
12227
  return [
@@ -12013,7 +12281,7 @@ async function runClassifier(evidence, plan, options) {
12013
12281
  const skepticResults = await Promise.all(
12014
12282
  Array.from(
12015
12283
  { length: n },
12016
- (_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal)
12284
+ (_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal, options.onProgress)
12017
12285
  )
12018
12286
  );
12019
12287
  const verdicts = [];
@@ -12030,7 +12298,8 @@ async function runClassifier(evidence, plan, options) {
12030
12298
  }
12031
12299
  return aggregate(verdicts);
12032
12300
  }
12033
- async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
12301
+ async function runOneSkeptic(evidence, plan, index, adapter, model, signal, onProgress) {
12302
+ await onProgress?.();
12034
12303
  const prompt = buildSkepticPrompt(evidence, plan, index);
12035
12304
  const messages = [{ role: "user", content: prompt }];
12036
12305
  let text = "";
@@ -12040,6 +12309,7 @@ async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
12040
12309
  tools: [],
12041
12310
  ...signal === void 0 ? {} : { signal }
12042
12311
  })) {
12312
+ await onProgress?.();
12043
12313
  if (event.type === "text") text += event.text;
12044
12314
  else if (event.type === "error") throw event.error;
12045
12315
  else if (event.type === "done") break;
@@ -12130,7 +12400,7 @@ function computeFingerprint(gaps) {
12130
12400
  }
12131
12401
  async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
12132
12402
  const workspaceState = await collectWorkspaceState(workspace);
12133
- const planPath2 = join14(workspace, ".flavor", "goal-plan.md");
12403
+ const planPath2 = join15(workspace, ".flavor", "goal-plan.md");
12134
12404
  let planFile = null;
12135
12405
  try {
12136
12406
  planFile = await readFile10(planPath2, "utf8");
@@ -12194,7 +12464,7 @@ var GoalOrchestrator = class {
12194
12464
  async *run(request) {
12195
12465
  const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
12196
12466
  const createdAt = timestamp2();
12197
- let state = {
12467
+ const state = {
12198
12468
  id: this.#options.idFactory?.() ?? `goal-${Date.now().toString(36)}`,
12199
12469
  objective: request.goal,
12200
12470
  phase: "planning",
@@ -12203,6 +12473,7 @@ var GoalOrchestrator = class {
12203
12473
  planPath: null,
12204
12474
  verifyRounds: 0,
12205
12475
  workerRounds: 0,
12476
+ pendingVerification: null,
12206
12477
  lastGaps: [],
12207
12478
  gapFingerprint: "",
12208
12479
  stallStreak: 0,
@@ -12211,87 +12482,147 @@ var GoalOrchestrator = class {
12211
12482
  createdAt,
12212
12483
  updatedAt: createdAt
12213
12484
  };
12485
+ yield* this.#execute(state, request.signal);
12486
+ }
12487
+ /**
12488
+ * Continue a goal whose process rotated onto a fresh heap. The persisted
12489
+ * state decides where to pick up; an interrupted round simply runs again.
12490
+ */
12491
+ async *resume(request) {
12492
+ const persistence = this.#options.persistence;
12493
+ if (persistence === void 0 || persistence.load === void 0) {
12494
+ throw new Error("Goal persistence does not support resume");
12495
+ }
12496
+ let state;
12497
+ try {
12498
+ state = await persistence.load(request.goalId);
12499
+ } catch (error) {
12500
+ yield { type: "goal-failed", reason: message(error) };
12501
+ return;
12502
+ }
12503
+ if (state.status !== "active" && state.status !== "not_achieved") {
12504
+ yield { type: "goal-failed", reason: `Goal "${state.id}" is ${state.status}; it cannot be resumed.` };
12505
+ return;
12506
+ }
12507
+ yield { type: "goal-resumed", goalId: state.id, round: resumeStartRound(state) };
12508
+ yield* this.#execute(state, request.signal);
12509
+ }
12510
+ async *#execute(initialState, signal) {
12511
+ const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
12512
+ let state = initialState;
12214
12513
  const persistState = async (patch) => {
12215
12514
  state = { ...state, ...patch, updatedAt: timestamp2() };
12216
12515
  await this.#options.persistence?.save(state);
12217
12516
  };
12517
+ const memoryCheckpoint = async () => {
12518
+ await this.#options.onMemoryCheckpoint?.(state.id);
12519
+ if (memoryRotationActive()) throw new Error("Heap rotation requested");
12520
+ };
12218
12521
  await persistState();
12219
12522
  const workspace = this.#options.workspace;
12220
12523
  let plan;
12221
12524
  let planPath2;
12222
- try {
12223
- request.signal.throwIfAborted();
12224
- plan = await runPlanner({
12225
- registry: this.#options.registry,
12226
- modelId: this.#options.plannerModelId,
12227
- objective: request.goal,
12228
- signal: request.signal
12229
- });
12230
- planPath2 = await writePlanFile(workspace, plan);
12231
- await persistState({ plan, planPath: planPath2, contractHash: contractHash(request.goal, plan) });
12232
- yield { type: "goal-plan-created", plan, planPath: planPath2 };
12233
- } catch (error) {
12234
- const reason = `Goal planning failed: ${message(error)}`;
12235
- await persistState({ phase: "complete", status: "failed" });
12236
- yield { type: "goal-plan-failed", reason };
12237
- yield { type: "goal-failed", reason };
12238
- return;
12239
- }
12240
- let priorGaps = [];
12241
- let priorFingerprint = "";
12242
- let stallStreak = 0;
12243
- for (let round = 1; round <= this.#options.maxRounds; round++) {
12244
- request.signal.throwIfAborted();
12245
- const workerPrompt = buildWorkerPrompt(request.goal, plan, priorGaps, round);
12246
- await persistState({
12247
- phase: "executing",
12248
- status: "active",
12249
- workerRounds: round,
12250
- lastGaps: priorGaps,
12251
- gapFingerprint: priorFingerprint,
12252
- stallStreak
12253
- });
12254
- yield { type: "goal-worker-start", round };
12255
- let finalResponse = "";
12256
- let workerError;
12525
+ if (state.plan === null) {
12257
12526
  try {
12258
- for await (const event of this.#options.runWorker({
12259
- goal: request.goal,
12260
- round,
12261
- workspace,
12262
- prompt: workerPrompt,
12263
- priorGaps: formatPriorGaps(priorGaps),
12264
- signal: request.signal
12265
- })) {
12266
- yield { type: "goal-worker-event", round, event };
12267
- if (event.type === "text") finalResponse += event.text;
12268
- if (event.type === "error") {
12269
- workerError = event.error.message;
12270
- break;
12271
- }
12272
- }
12527
+ signal.throwIfAborted();
12528
+ await memoryCheckpoint();
12529
+ plan = await runPlanner({
12530
+ registry: this.#options.registry,
12531
+ modelId: this.#options.plannerModelId,
12532
+ objective: state.objective,
12533
+ signal,
12534
+ onProgress: memoryCheckpoint
12535
+ });
12536
+ planPath2 = await writePlanFile(workspace, plan);
12537
+ await persistState({ phase: "executing", plan, planPath: planPath2, contractHash: contractHash(state.objective, plan) });
12538
+ yield { type: "goal-plan-created", plan, planPath: planPath2 };
12273
12539
  } catch (error) {
12274
- workerError = message(error);
12275
- }
12276
- if (workerError !== void 0) {
12277
- const reason = `Worker error in round ${round}: ${workerError}`;
12540
+ if (memoryRotationActive()) return;
12541
+ const reason = `Goal planning failed: ${message(error)}`;
12278
12542
  await persistState({ phase: "complete", status: "failed" });
12543
+ yield { type: "goal-plan-failed", reason };
12279
12544
  yield { type: "goal-failed", reason };
12280
12545
  return;
12281
12546
  }
12282
- await persistState({ phase: "verifying", status: "active", verifyRounds: round });
12547
+ } else {
12548
+ plan = state.plan;
12549
+ planPath2 = state.planPath ?? await writePlanFile(workspace, plan);
12550
+ await persistState({ phase: "executing", status: "active", ...state.planPath === null ? { planPath: planPath2 } : {} });
12551
+ }
12552
+ let priorGaps = state.lastGaps;
12553
+ let priorFingerprint = state.gapFingerprint;
12554
+ let stallStreak = state.stallStreak;
12555
+ const startRound = resumeStartRound(state);
12556
+ for (let round = startRound; round <= this.#options.maxRounds; round++) {
12557
+ signal.throwIfAborted();
12558
+ await this.#options.onRoundBoundary?.(state.id);
12559
+ if (memoryRotationActive()) return;
12560
+ const pendingVerification = state.pendingVerification?.round === round ? state.pendingVerification : void 0;
12561
+ let finalResponse = pendingVerification?.finalResponse ?? "";
12562
+ if (pendingVerification === void 0) {
12563
+ const workerPrompt = buildWorkerPrompt(state.objective, plan, priorGaps, round);
12564
+ await persistState({
12565
+ phase: "executing",
12566
+ status: "active",
12567
+ workerRounds: round,
12568
+ pendingVerification: null,
12569
+ lastGaps: priorGaps,
12570
+ gapFingerprint: priorFingerprint,
12571
+ stallStreak
12572
+ });
12573
+ yield { type: "goal-worker-start", round };
12574
+ let workerError;
12575
+ let workerMemoryPressure = false;
12576
+ try {
12577
+ for await (const event of this.#options.runWorker({
12578
+ goal: state.objective,
12579
+ round,
12580
+ workspace,
12581
+ prompt: workerPrompt,
12582
+ priorGaps: formatPriorGaps(priorGaps),
12583
+ signal
12584
+ })) {
12585
+ yield { type: "goal-worker-event", round, event };
12586
+ if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
12587
+ if (event.type === "error") {
12588
+ workerError = event.error.message;
12589
+ if (event.error.code === "memory_pressure") workerMemoryPressure = true;
12590
+ break;
12591
+ }
12592
+ }
12593
+ } catch (error) {
12594
+ if (memoryRotationActive()) return;
12595
+ workerError = message(error);
12596
+ }
12597
+ if (workerMemoryPressure) return;
12598
+ if (workerError !== void 0) {
12599
+ const reason = `Worker error in round ${round}: ${workerError}`;
12600
+ await persistState({ phase: "complete", status: "failed" });
12601
+ yield { type: "goal-failed", reason };
12602
+ return;
12603
+ }
12604
+ await persistState({
12605
+ phase: "verifying",
12606
+ status: "active",
12607
+ pendingVerification: { round, finalResponse }
12608
+ });
12609
+ }
12283
12610
  yield { type: "goal-verification-start", round };
12284
- const hostVerification = await this.#options.verifyHost?.(request.signal);
12611
+ await this.#options.onMemoryCheckpoint?.(state.id);
12612
+ if (memoryRotationActive()) return;
12613
+ const hostVerification = await this.#options.verifyHost?.(signal);
12614
+ await this.#options.onMemoryCheckpoint?.(state.id);
12615
+ if (memoryRotationActive()) return;
12285
12616
  const evidence = await collectEvidence(
12286
12617
  workspace,
12287
- request.goal,
12618
+ state.objective,
12288
12619
  finalResponse.slice(-4e3),
12289
12620
  // Last 4000 chars of response
12290
12621
  formatPriorGaps(priorGaps),
12291
12622
  state.contractHash,
12292
12623
  hostVerification
12293
12624
  );
12294
- await persistState({ evidenceRounds: [...state.evidenceRounds, {
12625
+ await persistState({ evidenceRounds: [...state.evidenceRounds.filter((item) => item.round !== round), {
12295
12626
  round,
12296
12627
  workspaceDiffHash: evidence.workspaceDiffHash,
12297
12628
  ...hostVerification === void 0 ? {} : { hostVerification }
@@ -12315,14 +12646,17 @@ var GoalOrchestrator = class {
12315
12646
  modelId: this.#options.classifierModelId,
12316
12647
  skepticCount: this.#options.skepticCount,
12317
12648
  workspace,
12318
- signal: request.signal
12649
+ signal,
12650
+ onProgress: memoryCheckpoint
12319
12651
  });
12320
12652
  } catch (error) {
12653
+ if (memoryRotationActive()) return;
12321
12654
  outcome = {
12322
12655
  type: "blocked",
12323
12656
  reason: `Classifier infrastructure error: ${message(error)}`
12324
12657
  };
12325
12658
  }
12659
+ await persistState({ verifyRounds: round, pendingVerification: null });
12326
12660
  yield {
12327
12661
  type: "goal-verdict",
12328
12662
  round,
@@ -12382,9 +12716,15 @@ var GoalOrchestrator = class {
12382
12716
  function contractHash(objective, plan) {
12383
12717
  return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
12384
12718
  }
12719
+ function resumeStartRound(state) {
12720
+ if (state.pendingVerification !== void 0 && state.pendingVerification !== null) {
12721
+ return state.pendingVerification.round;
12722
+ }
12723
+ return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
12724
+ }
12385
12725
  async function writePlanFile(workspace, plan) {
12386
- const dir = join15(workspace, ".flavor");
12387
- const planPath2 = join15(dir, "goal-plan.md");
12726
+ const dir = join16(workspace, ".flavor");
12727
+ const planPath2 = join16(dir, "goal-plan.md");
12388
12728
  await mkdir10(dir, { recursive: true });
12389
12729
  const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
12390
12730
  const content = [
@@ -12461,8 +12801,8 @@ function formatPriorGaps(gaps) {
12461
12801
  // src/goal/store.ts
12462
12802
  import { constants as constants4 } from "fs";
12463
12803
  import { randomUUID as randomUUID11 } from "crypto";
12464
- import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
12465
- import { dirname as dirname9, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
12804
+ import { lstat as lstat5, mkdir as mkdir11, open as open5, readFile as readFile11, rename as rename4, rm as rm6, stat as stat6 } from "fs/promises";
12805
+ import { dirname as dirname9, join as join17, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
12466
12806
  var DEFAULT_MAX_BYTES = 1024 * 1024;
12467
12807
  var GoalStore = class {
12468
12808
  #workspace;
@@ -12470,7 +12810,7 @@ var GoalStore = class {
12470
12810
  #maxBytes;
12471
12811
  constructor(options) {
12472
12812
  this.#workspace = resolve12(options.workspace);
12473
- this.#root = join16(this.#workspace, ".flavor", "goals");
12813
+ this.#root = join17(this.#workspace, ".flavor", "goals");
12474
12814
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
12475
12815
  }
12476
12816
  async save(input) {
@@ -12478,13 +12818,13 @@ var GoalStore = class {
12478
12818
  await assertNoSymlink(this.#workspace, dirname9(this.#root));
12479
12819
  await mkdir11(this.#root, { recursive: true, mode: 448 });
12480
12820
  await assertNoSymlink(this.#workspace, this.#root);
12481
- const target = join16(this.#root, `${state.id}.json`);
12821
+ const target = join17(this.#root, `${state.id}.json`);
12482
12822
  const body = `${JSON.stringify(state, null, 2)}
12483
12823
  `;
12484
12824
  if (Buffer.byteLength(body) > this.#maxBytes) {
12485
12825
  throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
12486
12826
  }
12487
- const temporary = join16(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
12827
+ const temporary = join17(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
12488
12828
  let handle;
12489
12829
  try {
12490
12830
  handle = await open5(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
@@ -12498,6 +12838,28 @@ var GoalStore = class {
12498
12838
  await rm6(temporary, { force: true }).catch(() => void 0);
12499
12839
  }
12500
12840
  }
12841
+ /** Reload a persisted goal so a rotated process can resume it. */
12842
+ async load(goalId) {
12843
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(goalId)) {
12844
+ throw new Error(`Goal "${goalId}" was not found`);
12845
+ }
12846
+ await assertNoSymlink(this.#workspace, dirname9(this.#root));
12847
+ await assertNoSymlink(this.#workspace, this.#root);
12848
+ const target = join17(this.#root, `${goalId}.json`);
12849
+ try {
12850
+ const metadata = await stat6(target);
12851
+ if (!metadata.isFile() || metadata.size > this.#maxBytes) {
12852
+ throw new Error("Goal state is not a bounded regular file");
12853
+ }
12854
+ const raw = await readFile11(target, "utf8");
12855
+ return GoalStateSchema.parse(JSON.parse(raw));
12856
+ } catch (error) {
12857
+ if (isCode4(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
12858
+ const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
12859
+ await rename4(target, quarantine).catch(() => void 0);
12860
+ throw new Error(`Goal "${goalId}" is corrupt and was quarantined`);
12861
+ }
12862
+ }
12501
12863
  };
12502
12864
  async function assertNoSymlink(root, target) {
12503
12865
  let cursor = resolve12(target);
@@ -12524,8 +12886,8 @@ function isCode4(error, code) {
12524
12886
  }
12525
12887
 
12526
12888
  // src/loop/isolation.ts
12527
- import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
12528
- import { join as join17, resolve as resolve13 } from "path";
12889
+ import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile12 } from "fs/promises";
12890
+ import { join as join18, resolve as resolve13 } from "path";
12529
12891
  var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
12530
12892
  var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
12531
12893
  function inferGoalIntent(goal) {
@@ -12548,8 +12910,8 @@ async function prepareLoopWorkspace(options) {
12548
12910
  return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
12549
12911
  }
12550
12912
  options.signal?.throwIfAborted();
12551
- const worktrees = join17(root, ".worktrees");
12552
- const path = join17(worktrees, `loop-${options.loopId}`);
12913
+ const worktrees = join18(root, ".worktrees");
12914
+ const path = join18(worktrees, `loop-${options.loopId}`);
12553
12915
  const branch = `loop/${options.loopId}`;
12554
12916
  try {
12555
12917
  await ensureWorktreeDirectoryIgnored(root);
@@ -12579,12 +12941,12 @@ async function ensureWorktreeDirectoryIgnored(root) {
12579
12941
  const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
12580
12942
  if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
12581
12943
  const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
12582
- const infoDirectory = join17(gitDirectory, "info");
12583
- const excludePath = join17(infoDirectory, "exclude");
12944
+ const infoDirectory = join18(gitDirectory, "info");
12945
+ const excludePath = join18(infoDirectory, "exclude");
12584
12946
  await mkdir12(infoDirectory, { recursive: true });
12585
12947
  let existing = "";
12586
12948
  try {
12587
- existing = await readFile11(excludePath, "utf8");
12949
+ existing = await readFile12(excludePath, "utf8");
12588
12950
  } catch (error) {
12589
12951
  if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
12590
12952
  }
@@ -12595,8 +12957,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
12595
12957
 
12596
12958
  // src/loop/store.ts
12597
12959
  import { constants as constants5 } from "fs";
12598
- import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile12, realpath as realpath8, rename as rename5, rm as rm7, stat as stat6 } from "fs/promises";
12599
- import { dirname as dirname10, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
12960
+ import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile13, realpath as realpath8, rename as rename5, rm as rm7, stat as stat7 } from "fs/promises";
12961
+ import { dirname as dirname10, join as join19, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
12600
12962
  import { randomUUID as randomUUID12 } from "crypto";
12601
12963
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
12602
12964
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
@@ -12607,7 +12969,7 @@ var LoopStore = class {
12607
12969
  #appendTails = /* @__PURE__ */ new Map();
12608
12970
  constructor(options) {
12609
12971
  this.#workspace = resolve14(options.workspace);
12610
- this.#root = join18(this.#workspace, ".flavor", "loops");
12972
+ this.#root = join19(this.#workspace, ".flavor", "loops");
12611
12973
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
12612
12974
  }
12613
12975
  async create(state) {
@@ -12624,11 +12986,11 @@ var LoopStore = class {
12624
12986
  const state = LoopStateSchema.parse(input);
12625
12987
  await this.#assertWorkspace(state.workspace);
12626
12988
  const directory = await this.#prepare(state.loopId);
12627
- const target = join18(directory, "state.json");
12989
+ const target = join19(directory, "state.json");
12628
12990
  const body = `${JSON.stringify(state, null, 2)}
12629
12991
  `;
12630
12992
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
12631
- const temporary = join18(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
12993
+ const temporary = join19(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
12632
12994
  let handle;
12633
12995
  try {
12634
12996
  handle = await open6(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
@@ -12644,11 +13006,11 @@ var LoopStore = class {
12644
13006
  }
12645
13007
  async load(loopId) {
12646
13008
  const directory = await this.#safeExisting(loopId);
12647
- const target = join18(directory, "state.json");
13009
+ const target = join19(directory, "state.json");
12648
13010
  try {
12649
- const metadata = await stat6(target);
13011
+ const metadata = await stat7(target);
12650
13012
  if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
12651
- const raw = await readFile12(target, "utf8");
13013
+ const raw = await readFile13(target, "utf8");
12652
13014
  const state = LoopStateSchema.parse(JSON.parse(raw));
12653
13015
  await this.#assertWorkspace(state.workspace);
12654
13016
  return state;
@@ -12664,7 +13026,7 @@ var LoopStore = class {
12664
13026
  const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
12665
13027
  const next = previous.catch(() => void 0).then(async () => {
12666
13028
  const directory = await this.#prepare(event.loopId);
12667
- const path = join18(directory, "events.jsonl");
13029
+ const path = join19(directory, "events.jsonl");
12668
13030
  const handle = await open6(path, constants5.O_CREAT | constants5.O_APPEND | constants5.O_WRONLY, 384);
12669
13031
  try {
12670
13032
  await handle.writeFile(`${JSON.stringify(event)}
@@ -12681,7 +13043,7 @@ var LoopStore = class {
12681
13043
  await assertNoSymlink2(this.#workspace, dirname10(this.#root));
12682
13044
  await mkdir13(this.#root, { recursive: true, mode: 448 });
12683
13045
  await assertNoSymlink2(this.#workspace, this.#root);
12684
- const directory = join18(this.#root, loopId);
13046
+ const directory = join19(this.#root, loopId);
12685
13047
  await mkdir13(directory, { recursive: true, mode: 448 });
12686
13048
  await assertNoSymlink2(this.#workspace, directory);
12687
13049
  return directory;
@@ -12689,7 +13051,7 @@ var LoopStore = class {
12689
13051
  async #safeExisting(loopId) {
12690
13052
  validateId(loopId);
12691
13053
  await assertNoSymlink2(this.#workspace, this.#root);
12692
- const directory = join18(this.#root, loopId);
13054
+ const directory = join19(this.#root, loopId);
12693
13055
  await assertNoSymlink2(this.#workspace, directory);
12694
13056
  return directory;
12695
13057
  }
@@ -12737,13 +13099,13 @@ function isCode5(error, code) {
12737
13099
  }
12738
13100
 
12739
13101
  // src/loop/verifier.ts
12740
- import { readFile as readFile13 } from "fs/promises";
12741
- import { join as join20 } from "path";
13102
+ import { readFile as readFile14 } from "fs/promises";
13103
+ import { join as join21 } from "path";
12742
13104
 
12743
13105
  // src/tools/shell.ts
12744
13106
  import { spawn as spawn4 } from "child_process";
12745
13107
  import { existsSync as existsSync4 } from "fs";
12746
- import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as join19, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
13108
+ import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as join20, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
12747
13109
  import { z as z23 } from "zod";
12748
13110
 
12749
13111
  // src/tools/jobs.ts
@@ -13406,7 +13768,7 @@ function decodeOutput(buffer) {
13406
13768
  return new TextDecoder(encoding).decode(buffer);
13407
13769
  }
13408
13770
  function shellEnvironment(root, environment) {
13409
- const localBin = join19(root, "node_modules", ".bin");
13771
+ const localBin = join20(root, "node_modules", ".bin");
13410
13772
  if (!existsSync4(localBin)) return environment;
13411
13773
  const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
13412
13774
  const current = environment[pathKey] ?? "";
@@ -13509,13 +13871,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
13509
13871
  async function inferVerificationPlan(workspace) {
13510
13872
  const commands = [];
13511
13873
  const seen = /* @__PURE__ */ new Set();
13512
- const packageJson = await optionalJson(join20(workspace, "package.json"));
13874
+ const packageJson = await optionalJson(join21(workspace, "package.json"));
13513
13875
  const scripts = isRecord3(packageJson?.scripts) ? packageJson.scripts : {};
13514
13876
  for (const name of SCRIPT_PRIORITY) {
13515
13877
  if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
13516
13878
  add(commands, seen, npmScript(name));
13517
13879
  }
13518
- const flavor = await optionalText(join20(workspace, "FLAVOR.md"));
13880
+ const flavor = await optionalText(join21(workspace, "FLAVOR.md"));
13519
13881
  if (flavor !== void 0) {
13520
13882
  for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
13521
13883
  const parsed = parseTrustedCommand(match[1] ?? "");
@@ -13582,7 +13944,7 @@ function isUnconditionalPassThrough(script) {
13582
13944
  }
13583
13945
  async function optionalText(path) {
13584
13946
  try {
13585
- return await readFile13(path, "utf8");
13947
+ return await readFile14(path, "utf8");
13586
13948
  } catch (error) {
13587
13949
  if (isCode6(error, "ENOENT")) return void 0;
13588
13950
  throw error;
@@ -13603,7 +13965,7 @@ function isCode6(error, code) {
13603
13965
 
13604
13966
  // src/models/anthropic.ts
13605
13967
  import Anthropic from "@anthropic-ai/sdk";
13606
- import { readFile as readFile14 } from "fs/promises";
13968
+ import { readFile as readFile15 } from "fs/promises";
13607
13969
 
13608
13970
  // src/utils/json.ts
13609
13971
  function safeParseJson(input) {
@@ -13652,11 +14014,11 @@ function isEnvTruthy(value) {
13652
14014
 
13653
14015
  // src/utils/log.ts
13654
14016
  import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
13655
- import { dirname as dirname11, join as join21, resolve as resolve16 } from "path";
14017
+ import { dirname as dirname11, join as join22, resolve as resolve16 } from "path";
13656
14018
  var pending = Promise.resolve();
13657
14019
  var usagePending = Promise.resolve();
13658
14020
  function usageLogPath() {
13659
- return process.env.FLAVOR_USAGE_FILE ?? join21(process.cwd(), ".flavor", "usage.jsonl");
14021
+ return process.env.FLAVOR_USAGE_FILE ?? join22(process.cwd(), ".flavor", "usage.jsonl");
13660
14022
  }
13661
14023
  var usageSessionId = "unknown";
13662
14024
  var truncateOnNextAppend = false;
@@ -13685,7 +14047,7 @@ var AuditLogger = class {
13685
14047
  #path;
13686
14048
  #closed = false;
13687
14049
  constructor(workspace) {
13688
- this.#path = join21(resolve16(workspace), ".flavor", "audit.jsonl");
14050
+ this.#path = join22(resolve16(workspace), ".flavor", "audit.jsonl");
13689
14051
  }
13690
14052
  get path() {
13691
14053
  return this.#path;
@@ -13905,7 +14267,7 @@ var AnthropicModelAdapter = class {
13905
14267
  source: {
13906
14268
  type: "base64",
13907
14269
  media_type: block.mediaType,
13908
- data: (await readFile14(block.source.path)).toString("base64")
14270
+ data: (await readFile15(block.source.path)).toString("base64")
13909
14271
  }
13910
14272
  }));
13911
14273
  if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
@@ -14125,7 +14487,7 @@ function resolveCacheProfile(input) {
14125
14487
 
14126
14488
  // src/models/openai.ts
14127
14489
  import OpenAI from "openai";
14128
- import { readFile as readFile15 } from "fs/promises";
14490
+ import { readFile as readFile16 } from "fs/promises";
14129
14491
  async function toInput(message2) {
14130
14492
  if (message2.role === "tool") {
14131
14493
  if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
@@ -14137,7 +14499,7 @@ async function toInput(message2) {
14137
14499
  }
14138
14500
  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 } : {
14139
14501
  type: "input_image",
14140
- image_url: `data:${block.mediaType};base64,${(await readFile15(block.source.path)).toString("base64")}`,
14502
+ image_url: `data:${block.mediaType};base64,${(await readFile16(block.source.path)).toString("base64")}`,
14141
14503
  detail: "auto"
14142
14504
  }));
14143
14505
  return [
@@ -14559,7 +14921,7 @@ import {
14559
14921
 
14560
14922
  // src/utils/version.ts
14561
14923
  import { readFileSync as readFileSync2 } from "fs";
14562
- import { dirname as dirname12, join as join22 } from "path";
14924
+ import { dirname as dirname12, join as join23 } from "path";
14563
14925
  import { fileURLToPath } from "url";
14564
14926
  var cached;
14565
14927
  function packageVersion() {
@@ -14567,7 +14929,7 @@ function packageVersion() {
14567
14929
  let directory = dirname12(fileURLToPath(import.meta.url));
14568
14930
  for (; ; ) {
14569
14931
  try {
14570
- const manifest = JSON.parse(readFileSync2(join22(directory, "package.json"), "utf8"));
14932
+ const manifest = JSON.parse(readFileSync2(join23(directory, "package.json"), "utf8"));
14571
14933
  if (typeof manifest.version === "string" && manifest.version.length > 0) {
14572
14934
  cached = manifest.version;
14573
14935
  return cached;
@@ -14994,8 +15356,8 @@ function isStoredToken(value) {
14994
15356
  }
14995
15357
 
14996
15358
  // src/permissions/policy.ts
14997
- import { readFile as readFile16 } from "fs/promises";
14998
- import { join as join23, resolve as resolve18 } from "path";
15359
+ import { readFile as readFile17 } from "fs/promises";
15360
+ import { join as join24, resolve as resolve18 } from "path";
14999
15361
  import { z as z25 } from "zod";
15000
15362
  var DecisionSchema2 = z25.enum(["allow", "ask", "deny"]);
15001
15363
  var PrefixSchema = z25.array(z25.string().min(1).max(1024)).min(1).max(64);
@@ -15032,15 +15394,15 @@ var CompiledPermissionPolicy = class {
15032
15394
  async function loadPermissionPolicy(options) {
15033
15395
  const sources = [
15034
15396
  ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve18(options.managedPath) }],
15035
- { tier: "user", path: join23(resolve18(options.home), ".flavor-code", "permissions.json") },
15036
- { tier: "project", path: join23(resolve18(options.workspace), ".flavor", "permissions.json") },
15037
- { tier: "local", path: join23(resolve18(options.workspace), ".flavor", "permissions.local.json") }
15397
+ { tier: "user", path: join24(resolve18(options.home), ".flavor-code", "permissions.json") },
15398
+ { tier: "project", path: join24(resolve18(options.workspace), ".flavor", "permissions.json") },
15399
+ { tier: "local", path: join24(resolve18(options.workspace), ".flavor", "permissions.local.json") }
15038
15400
  ];
15039
15401
  const rules = [];
15040
15402
  for (const source of sources) {
15041
15403
  let raw;
15042
15404
  try {
15043
- raw = await readFile16(source.path, "utf8");
15405
+ raw = await readFile17(source.path, "utf8");
15044
15406
  } catch (error) {
15045
15407
  if (isCode7(error, "ENOENT")) continue;
15046
15408
  throw error;
@@ -15806,8 +16168,8 @@ function createSkillResourceTool(registry) {
15806
16168
 
15807
16169
  // src/session/store.ts
15808
16170
  import { constants as constants7 } from "fs";
15809
- import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile17, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat7 } from "fs/promises";
15810
- import { basename as basename6, dirname as dirname14, join as join24, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
16171
+ import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile18, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat8 } from "fs/promises";
16172
+ import { basename as basename6, dirname as dirname14, join as join25, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
15811
16173
  import { randomUUID as randomUUID13 } from "crypto";
15812
16174
  import { z as z27 } from "zod";
15813
16175
 
@@ -16503,7 +16865,7 @@ var SessionStore = class {
16503
16865
  #maxSessions;
16504
16866
  constructor(options) {
16505
16867
  this.#workspace = resolve20(options.workspace);
16506
- this.#sessions = join24(this.#workspace, ".flavor", "sessions");
16868
+ this.#sessions = join25(this.#workspace, ".flavor", "sessions");
16507
16869
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
16508
16870
  if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
16509
16871
  this.#maxSessions = options.maxSessions ?? 50;
@@ -16535,7 +16897,7 @@ var SessionStore = class {
16535
16897
  const body = `${lines.join("\n")}
16536
16898
  `;
16537
16899
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
16538
- const temporary = join24(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
16900
+ const temporary = join25(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
16539
16901
  let handle;
16540
16902
  try {
16541
16903
  handle = await open7(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
@@ -16554,7 +16916,7 @@ var SessionStore = class {
16554
16916
  async acquireLease(sessionId) {
16555
16917
  SessionIdSchema.parse(sessionId);
16556
16918
  await this.#prepareDirectory();
16557
- const path = join24(this.#sessions, `.${sessionId}.lock`);
16919
+ const path = join25(this.#sessions, `.${sessionId}.lock`);
16558
16920
  const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
16559
16921
  for (let attempt = 0; attempt < 3; attempt += 1) {
16560
16922
  let handle;
@@ -16572,7 +16934,7 @@ var SessionStore = class {
16572
16934
  release: async () => {
16573
16935
  if (released) return;
16574
16936
  try {
16575
- if (await readFile17(path, "utf8") === token) await rm8(path, { force: true });
16937
+ if (await readFile18(path, "utf8") === token) await rm8(path, { force: true });
16576
16938
  } catch (error) {
16577
16939
  if (!isCode8(error, "ENOENT")) throw error;
16578
16940
  }
@@ -16688,14 +17050,14 @@ var SessionStore = class {
16688
17050
  if (isCode8(error, "ENOENT")) return;
16689
17051
  throw error;
16690
17052
  }
16691
- const owner2 = await reclaimStaleSessionLock(join24(this.#sessions, `.${sessionId}.lock`));
17053
+ const owner2 = await reclaimStaleSessionLock(join25(this.#sessions, `.${sessionId}.lock`));
16692
17054
  if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
16693
17055
  const path = this.#path(sessionId);
16694
17056
  try {
16695
17057
  const metadata = await lstat8(path);
16696
17058
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
16697
17059
  await rm8(path);
16698
- await rm8(join24(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
17060
+ await rm8(join25(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
16699
17061
  } catch (error) {
16700
17062
  if (!isCode8(error, "ENOENT")) throw error;
16701
17063
  }
@@ -16711,14 +17073,14 @@ var SessionStore = class {
16711
17073
  if (entries.length <= this.#maxSessions) return;
16712
17074
  let retained = 0;
16713
17075
  for (const entry2 of entries) {
16714
- const owner2 = await reclaimStaleSessionLock(join24(this.#sessions, `.${entry2.sessionId}.lock`));
17076
+ const owner2 = await reclaimStaleSessionLock(join25(this.#sessions, `.${entry2.sessionId}.lock`));
16715
17077
  if (owner2 !== void 0) continue;
16716
17078
  if (retained < this.#maxSessions) {
16717
17079
  retained += 1;
16718
17080
  continue;
16719
17081
  }
16720
17082
  await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
16721
- await rm8(join24(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
17083
+ await rm8(join25(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
16722
17084
  }
16723
17085
  }
16724
17086
  async #assertWorkspace(stored) {
@@ -16766,14 +17128,14 @@ function storedSessionVersion(raw) {
16766
17128
  }
16767
17129
  async function reclaimStaleSessionLock(path) {
16768
17130
  try {
16769
- const token = await readFile17(path, "utf8");
16770
- const metadata = await stat7(path);
17131
+ const token = await readFile18(path, "utf8");
17132
+ const metadata = await stat8(path);
16771
17133
  const owner2 = parseSessionLockOwner(token);
16772
17134
  if (owner2 !== void 0 && isProcessAlive2(owner2)) return owner2;
16773
17135
  if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
16774
17136
  throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
16775
17137
  }
16776
- if (await readFile17(path, "utf8") !== token) return void 0;
17138
+ if (await readFile18(path, "utf8") !== token) return void 0;
16777
17139
  const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
16778
17140
  try {
16779
17141
  await rename6(path, stale);
@@ -16806,10 +17168,10 @@ function isProcessAlive2(pid) {
16806
17168
  }
16807
17169
  }
16808
17170
  async function boundedRead(path, maxBytes) {
16809
- const metadata = await stat7(path);
17171
+ const metadata = await stat8(path);
16810
17172
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
16811
17173
  if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
16812
- const value = await readFile17(path);
17174
+ const value = await readFile18(path);
16813
17175
  if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
16814
17176
  return value.toString("utf8");
16815
17177
  }
@@ -17001,14 +17363,14 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
17001
17363
 
17002
17364
  // src/session/tree.ts
17003
17365
  import { randomUUID as randomUUID15 } from "crypto";
17004
- import { mkdir as mkdir17, readFile as readFile19, rename as rename8, writeFile as writeFile11 } from "fs/promises";
17005
- import { dirname as dirname16, join as join26, resolve as resolve22 } from "path";
17366
+ import { mkdir as mkdir17, readFile as readFile20, rename as rename8, writeFile as writeFile11 } from "fs/promises";
17367
+ import { dirname as dirname16, join as join27, resolve as resolve22 } from "path";
17006
17368
 
17007
17369
  // src/session/checkpoint.ts
17008
17370
  import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
17009
17371
  import { execFile as execFile2 } from "child_process";
17010
- import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile18, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
17011
- import { dirname as dirname15, join as join25, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
17372
+ import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile19, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
17373
+ import { dirname as dirname15, join as join26, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
17012
17374
  var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
17013
17375
  var MAX_FILE_BYTES = 16 * 1024 * 1024;
17014
17376
  var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
@@ -17021,9 +17383,9 @@ var WorkspaceCheckpointStore = class {
17021
17383
  #maxTotalBytes;
17022
17384
  constructor(options) {
17023
17385
  this.#workspace = resolve21(options.workspace);
17024
- this.#root = join25(this.#workspace, ".flavor", "checkpoints");
17025
- this.#objects = join25(this.#root, "objects");
17026
- this.#manifests = join25(this.#root, "manifests");
17386
+ this.#root = join26(this.#workspace, ".flavor", "checkpoints");
17387
+ this.#objects = join26(this.#root, "objects");
17388
+ this.#manifests = join26(this.#root, "manifests");
17027
17389
  this.#maxFileBytes = options.maxFileBytes ?? MAX_FILE_BYTES;
17028
17390
  this.#maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
17029
17391
  }
@@ -17041,9 +17403,9 @@ var WorkspaceCheckpointStore = class {
17041
17403
  if (info.size > this.#maxFileBytes) throw new Error(`Checkpoint file exceeds size limit: ${path}`);
17042
17404
  total += info.size;
17043
17405
  if (total > this.#maxTotalBytes) throw new Error("Checkpoint exceeds total size limit");
17044
- const content = await readFile18(absolute);
17406
+ const content = await readFile19(absolute);
17045
17407
  const digest = createHash15("sha256").update(content).digest("hex");
17046
- const objectPath = join25(this.#objects, digest);
17408
+ const objectPath = join26(this.#objects, digest);
17047
17409
  try {
17048
17410
  const handle = await open8(objectPath, "wx");
17049
17411
  try {
@@ -17063,12 +17425,12 @@ var WorkspaceCheckpointStore = class {
17063
17425
  ...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
17064
17426
  files
17065
17427
  };
17066
- await atomicJson(join25(this.#manifests, `${checkpoint.id}.json`), checkpoint);
17428
+ await atomicJson(join26(this.#manifests, `${checkpoint.id}.json`), checkpoint);
17067
17429
  return checkpoint;
17068
17430
  }
17069
17431
  async load(id) {
17070
17432
  if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
17071
- const value = JSON.parse(await readFile18(join25(this.#manifests, `${id}.json`), "utf8"));
17433
+ const value = JSON.parse(await readFile19(join26(this.#manifests, `${id}.json`), "utf8"));
17072
17434
  if (value.version !== 1 || value.id !== id || !Array.isArray(value.files)) throw new Error("Invalid checkpoint manifest");
17073
17435
  for (const file of value.files) {
17074
17436
  contained(this.#workspace, file.path);
@@ -17080,7 +17442,7 @@ var WorkspaceCheckpointStore = class {
17080
17442
  const checkpoint = await this.load(id);
17081
17443
  const objects = /* @__PURE__ */ new Map();
17082
17444
  for (const file of checkpoint.files) {
17083
- const object = await readFile18(join25(this.#objects, file.digest));
17445
+ const object = await readFile19(join26(this.#objects, file.digest));
17084
17446
  if (object.length !== file.size || createHash15("sha256").update(object).digest("hex") !== file.digest) {
17085
17447
  throw new Error(`Checkpoint object is corrupt: ${file.digest}`);
17086
17448
  }
@@ -17103,7 +17465,7 @@ var WorkspaceCheckpointStore = class {
17103
17465
  }
17104
17466
  };
17105
17467
  async function discoverFiles(workspace) {
17106
- if (await exists2(join25(workspace, ".git"))) {
17468
+ if (await exists2(join26(workspace, ".git"))) {
17107
17469
  const listed = await gitFiles(workspace);
17108
17470
  return listed.filter((path) => !EXCLUDED.has(path.split("/")[0] ?? "")).sort();
17109
17471
  }
@@ -17112,7 +17474,7 @@ async function discoverFiles(workspace) {
17112
17474
  const entries = await readdir7(directory, { withFileTypes: true });
17113
17475
  for (const entry2 of entries) {
17114
17476
  if (EXCLUDED.has(entry2.name)) continue;
17115
- const absolute = join25(directory, entry2.name);
17477
+ const absolute = join26(directory, entry2.name);
17116
17478
  const path = normalizeRelative(relative14(workspace, absolute));
17117
17479
  if (entry2.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path}`);
17118
17480
  if (entry2.isDirectory()) await walk(absolute);
@@ -17187,17 +17549,17 @@ var SessionHistory = class _SessionHistory {
17187
17549
  #leafId;
17188
17550
  #unrevert;
17189
17551
  constructor(options, document) {
17190
- this.#path = join26(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
17552
+ this.#path = join27(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
17191
17553
  this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
17192
17554
  this.#restoreContext = options.restoreContext;
17193
17555
  this.#nodes = boundedNodes(document.nodes);
17194
17556
  this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
17195
17557
  }
17196
17558
  static async open(options) {
17197
- const path = join26(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
17559
+ const path = join27(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
17198
17560
  let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
17199
17561
  try {
17200
- const parsed = JSON.parse(await readFile19(path, "utf8"));
17562
+ const parsed = JSON.parse(await readFile20(path, "utf8"));
17201
17563
  validate(parsed, options.sessionId);
17202
17564
  document = parsed;
17203
17565
  } catch (error) {
@@ -17210,6 +17572,10 @@ var SessionHistory = class _SessionHistory {
17210
17572
  get leafId() {
17211
17573
  return this.#leafId;
17212
17574
  }
17575
+ /** Cheap retained-node count for the rotation census (no cloning). */
17576
+ get nodeCount() {
17577
+ return this.#nodes.length;
17578
+ }
17213
17579
  tree() {
17214
17580
  return this.#nodes.map((node) => structuredClone(node));
17215
17581
  }
@@ -17324,35 +17690,70 @@ function isCode10(error, code) {
17324
17690
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
17325
17691
  }
17326
17692
 
17327
- // src/utils/memory-restart.ts
17328
- import { join as join27 } from "path";
17329
- var MEMORY_RESTART_EXIT_CODE = 75;
17330
- var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
17331
- var MEMORY_RESTART_WINDOW_MS = 30 * 60 * 1e3;
17332
- function memoryRestartMarkerPath(workspace) {
17333
- return join27(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
17693
+ // src/utils/rotation-census.ts
17694
+ import { mkdirSync as mkdirSync2, readdirSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
17695
+ import { join as join28 } from "path";
17696
+ import { writeHeapSnapshot } from "v8";
17697
+ var CENSUS_PREFIX = "rotation-census-";
17698
+ var SNAPSHOT_PREFIX = "heap-";
17699
+ var KEEP_CENSUS_FILES = 5;
17700
+ var KEEP_SNAPSHOT_FILES = 2;
17701
+ function censusHeapBlock(reading) {
17702
+ const usage = process.memoryUsage();
17703
+ const mb = (bytes) => Math.round(bytes / 1048576);
17704
+ return {
17705
+ heapUsedMb: mb(reading.heapUsed),
17706
+ heapLimitMb: mb(reading.heapLimit),
17707
+ rawRatio: Number(reading.ratio.toFixed(3)),
17708
+ ...reading.verifiedRatio === void 0 ? {} : { verifiedRatio: Number(reading.verifiedRatio.toFixed(3)) },
17709
+ gcVerified: reading.gcVerified,
17710
+ rssMb: mb(usage.rss),
17711
+ externalMb: mb(usage.external),
17712
+ arrayBuffersMb: mb(usage.arrayBuffers),
17713
+ ...reading.memoryLimit === void 0 ? {} : { memoryLimitMb: mb(reading.memoryLimit) },
17714
+ ...reading.rssRatio === void 0 ? {} : { rssRatio: Number(reading.rssRatio.toFixed(3)) }
17715
+ };
17334
17716
  }
17335
- function nextMemoryRestartMarker(previous, sessionId, now) {
17336
- const reusable = previous !== void 0 && previous.sessionId === sessionId && now.getTime() - Date.parse(previous.requestedAt) < MEMORY_RESTART_WINDOW_MS && Number.isInteger(previous.attempts) && previous.attempts > 0;
17337
- return { sessionId, requestedAt: now.toISOString(), attempts: reusable ? previous.attempts + 1 : 1 };
17717
+ function writeRotationCensus(workspace, census) {
17718
+ try {
17719
+ const directory = join28(workspace, ".flavor", "tmp");
17720
+ mkdirSync2(directory, { recursive: true });
17721
+ const path = join28(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
17722
+ writeFileSync(path, `${JSON.stringify(census, null, 2)}
17723
+ `, { encoding: "utf8", mode: 384 });
17724
+ prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
17725
+ return path;
17726
+ } catch {
17727
+ return void 0;
17728
+ }
17338
17729
  }
17339
- function parseMemoryRestartMarker(json) {
17730
+ function writeRotationHeapSnapshot(workspace) {
17340
17731
  try {
17341
- const value = JSON.parse(json);
17342
- if (typeof value.sessionId !== "string" || typeof value.requestedAt !== "string" || typeof value.attempts !== "number") {
17343
- return void 0;
17344
- }
17345
- return { sessionId: value.sessionId, requestedAt: value.requestedAt, attempts: value.attempts };
17732
+ const directory = join28(workspace, ".flavor", "tmp");
17733
+ mkdirSync2(directory, { recursive: true });
17734
+ const path = join28(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
17735
+ writeHeapSnapshot(path);
17736
+ prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
17737
+ return path;
17346
17738
  } catch {
17347
17739
  return void 0;
17348
17740
  }
17349
17741
  }
17742
+ function prune(directory, prefix, suffix, keep) {
17743
+ const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
17744
+ for (const name of stale) {
17745
+ try {
17746
+ unlinkSync2(join28(directory, name));
17747
+ } catch {
17748
+ }
17749
+ }
17750
+ }
17350
17751
 
17351
17752
  // src/sleep/organizer.ts
17352
17753
  import { constants as constants8 } from "fs";
17353
17754
  import { randomUUID as randomUUID16 } from "crypto";
17354
17755
  import { lstat as lstat10, mkdir as mkdir18, open as open9, readdir as readdir8, realpath as realpath11, rename as rename9, rm as rm10 } from "fs/promises";
17355
- import { isAbsolute as isAbsolute11, join as join28, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
17756
+ import { isAbsolute as isAbsolute11, join as join29, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
17356
17757
  import { z as z28 } from "zod";
17357
17758
  var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
17358
17759
  var ReviewSchema = z28.object({
@@ -17407,7 +17808,7 @@ var ProjectSleepOrganizer = class {
17407
17808
  #generate;
17408
17809
  constructor(options) {
17409
17810
  this.#workspace = resolve23(options.workspace);
17410
- this.#sleepDirectory = join28(this.#workspace, ".flavor", "sleep");
17811
+ this.#sleepDirectory = join29(this.#workspace, ".flavor", "sleep");
17411
17812
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
17412
17813
  this.#generate = options.generate;
17413
17814
  }
@@ -17416,7 +17817,7 @@ var ProjectSleepOrganizer = class {
17416
17817
  signal.throwIfAborted();
17417
17818
  await this.#prepareDirectory();
17418
17819
  if (await this.#hasReport(date)) return { status: "exists", date };
17419
- const lockPath = join28(this.#sleepDirectory, `.${date}.lock`);
17820
+ const lockPath = join29(this.#sleepDirectory, `.${date}.lock`);
17420
17821
  let lock;
17421
17822
  try {
17422
17823
  lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
@@ -17451,7 +17852,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
17451
17852
  }
17452
17853
  if (review === void 0) throw lastError;
17453
17854
  const filename2 = `${date}-${filenameSummary(review.title)}.md`;
17454
- const path = join28(this.#sleepDirectory, filename2);
17855
+ const path = join29(this.#sleepDirectory, filename2);
17455
17856
  const markdown = renderSleepReport(date, review, documents);
17456
17857
  await this.#atomicWrite(path, markdown);
17457
17858
  return { status: "written", date, path, sessionCount: documents.length };
@@ -17493,7 +17894,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
17493
17894
  });
17494
17895
  }
17495
17896
  async #prepareDirectory() {
17496
- const flavorDirectory = join28(this.#workspace, ".flavor");
17897
+ const flavorDirectory = join29(this.#workspace, ".flavor");
17497
17898
  await assertNotSymlink(flavorDirectory);
17498
17899
  await mkdir18(flavorDirectory, { recursive: true, mode: 448 });
17499
17900
  await assertNotSymlink(this.#sleepDirectory);
@@ -17507,7 +17908,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
17507
17908
  return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
17508
17909
  }
17509
17910
  async #atomicWrite(path, content) {
17510
- const temporary = join28(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
17911
+ const temporary = join29(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
17511
17912
  let handle;
17512
17913
  try {
17513
17914
  handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
@@ -17881,7 +18282,7 @@ function computeSessionStats(documents) {
17881
18282
 
17882
18283
  // src/tools/files.ts
17883
18284
  import { constants as constants9 } from "fs";
17884
- import { mkdir as mkdir19, open as open10, readFile as readFile20, realpath as realpath12, rename as rename10, rm as rm11, stat as stat8 } from "fs/promises";
18285
+ import { mkdir as mkdir19, open as open10, readFile as readFile21, realpath as realpath12, rename as rename10, rm as rm11, stat as stat9 } from "fs/promises";
17885
18286
  import { dirname as dirname17, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
17886
18287
  import { randomUUID as randomUUID17 } from "crypto";
17887
18288
  import { z as z29 } from "zod";
@@ -18034,7 +18435,7 @@ function createReadTool(workspace, options = {}) {
18034
18435
  execute: async (input, signal) => {
18035
18436
  abortIfNeeded(signal);
18036
18437
  const path = await guard.existing(input.path);
18037
- const info = await stat8(path);
18438
+ const info = await stat9(path);
18038
18439
  const maxBytes = input.maxBytes ?? MAX_READ_BYTES;
18039
18440
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_READ_BYTES) {
18040
18441
  throw new Error(`maxBytes must be a positive integer no greater than ${MAX_READ_BYTES}`);
@@ -18243,7 +18644,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
18243
18644
  const temporary = `${path}.${randomUUID17()}.tmp`;
18244
18645
  let mode;
18245
18646
  try {
18246
- mode = (await stat8(path)).mode & 511;
18647
+ mode = (await stat9(path)).mode & 511;
18247
18648
  } catch (error) {
18248
18649
  if (!isMissing2(error)) throw error;
18249
18650
  }
@@ -18266,7 +18667,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
18266
18667
  }
18267
18668
  }
18268
18669
  async function readText(path) {
18269
- const contents = await readFile20(path);
18670
+ const contents = await readFile21(path);
18270
18671
  if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
18271
18672
  return decodeFileText(contents);
18272
18673
  }
@@ -18292,7 +18693,7 @@ async function assertMutationVersion(path, expected, observations) {
18292
18693
  }
18293
18694
  async function fileVersion(path) {
18294
18695
  try {
18295
- return versionOfStat(await stat8(path));
18696
+ return versionOfStat(await stat9(path));
18296
18697
  } catch (error) {
18297
18698
  if (isMissing2(error)) return ABSENT_VERSION;
18298
18699
  throw error;
@@ -18303,7 +18704,7 @@ function versionOfStat(info) {
18303
18704
  }
18304
18705
  async function readOptionalPresentationText(path) {
18305
18706
  try {
18306
- const contents = await readFile20(path);
18707
+ const contents = await readFile21(path);
18307
18708
  return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
18308
18709
  } catch (error) {
18309
18710
  return isMissing2(error) ? { exists: false } : { exists: true };
@@ -18614,7 +19015,7 @@ function matchesLines(source, start, expected) {
18614
19015
 
18615
19016
  // src/tools/search.ts
18616
19017
  import { spawn as spawn5 } from "child_process";
18617
- import { open as open11, opendir, stat as stat9 } from "fs/promises";
19018
+ import { open as open11, opendir, stat as stat10 } from "fs/promises";
18618
19019
  import { isAbsolute as isAbsolute13, relative as relative17, resolve as resolve25, sep as sep17 } from "path";
18619
19020
  import { rgPath as bundledRgPath } from "@vscode/ripgrep";
18620
19021
  import createIgnore from "ignore";
@@ -18962,7 +19363,7 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
18962
19363
  }
18963
19364
  ignores.length = ruleCount;
18964
19365
  }
18965
- if ((await stat9(start)).isFile()) {
19366
+ if ((await stat10(start)).isFile()) {
18966
19367
  const path = normalizedRelative(root, start);
18967
19368
  if (!isGitMetadataPath(root, start) && (includeIgnored || !ignored(path, false, ignores)) && accept(path)) output.push(path);
18968
19369
  } else {
@@ -18997,7 +19398,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
18997
19398
  if (!ignored(path, true, layers)) await walk(absolute);
18998
19399
  }
18999
19400
  }
19000
- if (!(await stat9(start)).isFile()) await walk(start);
19401
+ if (!(await stat10(start)).isFile()) await walk(start);
19001
19402
  return layers;
19002
19403
  }
19003
19404
  async function loadIgnoreFiles(directory, base, rules, budget, signal) {
@@ -19116,7 +19517,7 @@ function scope(root, path = ".") {
19116
19517
  async function resolveGrepPath(root, path = ".") {
19117
19518
  const candidate = scope(root, path);
19118
19519
  try {
19119
- const info = await stat9(candidate);
19520
+ const info = await stat10(candidate);
19120
19521
  if (!info.isFile() && !info.isDirectory()) throw new Error("Search path must be a file or directory");
19121
19522
  } catch (error) {
19122
19523
  if (error.code === "ENOENT") {
@@ -19657,7 +20058,7 @@ function charset(contentType) {
19657
20058
  // src/tools/lsp.ts
19658
20059
  import { spawn as spawn6 } from "child_process";
19659
20060
  import { existsSync as existsSync5 } from "fs";
19660
- import { access as access2, constants as constants10, readFile as readFile21 } from "fs/promises";
20061
+ import { access as access2, constants as constants10, readFile as readFile22 } from "fs/promises";
19661
20062
  import { basename as basename7, dirname as dirname18, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
19662
20063
  import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL3 } from "url";
19663
20064
  import { z as z32 } from "zod";
@@ -19851,7 +20252,7 @@ var LspConnection = class {
19851
20252
  const url = new URL(uri);
19852
20253
  if (url.protocol !== "file:") return;
19853
20254
  const filePath = fileURLToPath2(url);
19854
- const text = await readFile21(filePath, "utf8");
20255
+ const text = await readFile22(filePath, "utf8");
19855
20256
  const current = this.#documents.get(uri);
19856
20257
  if (current === void 0) {
19857
20258
  this.#documents.set(uri, { text, version: 1 });
@@ -20217,19 +20618,19 @@ async function changeSummary(workspace) {
20217
20618
  };
20218
20619
  }
20219
20620
  async function stagedDiff(workspace, maxChars = 96e3) {
20220
- const stat20 = await git2(workspace, ["diff", "--staged", "--stat"]);
20621
+ const stat21 = await git2(workspace, ["diff", "--staged", "--stat"]);
20221
20622
  const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
20222
20623
  if (!diff.ok) throw gitFailure(diff, "diff --staged");
20223
20624
  const text = diff.stdout;
20224
20625
  return {
20225
20626
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
20226
20627
  \u2026[diff truncated]` : text,
20227
- stat: stat20.stdout.trim(),
20628
+ stat: stat21.stdout.trim(),
20228
20629
  truncated: text.length > maxChars
20229
20630
  };
20230
20631
  }
20231
20632
  async function uncommittedDiff(workspace, maxChars = 96e3) {
20232
- const stat20 = await git2(workspace, ["diff", "HEAD", "--stat"]);
20633
+ const stat21 = await git2(workspace, ["diff", "HEAD", "--stat"]);
20233
20634
  const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
20234
20635
  if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
20235
20636
  const summary2 = await changeSummary(workspace);
@@ -20237,7 +20638,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
20237
20638
  return {
20238
20639
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
20239
20640
  \u2026[diff truncated]` : text,
20240
- stat: stat20.stdout.trim(),
20641
+ stat: stat21.stdout.trim(),
20241
20642
  untracked: summary2.untrackedFiles
20242
20643
  };
20243
20644
  }
@@ -20534,17 +20935,17 @@ ${explanation}` : "";
20534
20935
  }
20535
20936
 
20536
20937
  // src/desktop/astgraph-service.ts
20537
- import { stat as stat10 } from "fs/promises";
20938
+ import { stat as stat11 } from "fs/promises";
20538
20939
  import { createRequire as nodeCreateRequire } from "module";
20539
- import { join as join29, resolve as resolve27 } from "path";
20940
+ import { join as join30, resolve as resolve27 } from "path";
20540
20941
  var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
20541
20942
  var DesktopAstGraphService = class {
20542
20943
  #path;
20543
20944
  constructor(workspace) {
20544
- this.#path = join29(resolve27(workspace), ".flavor", "astgraph", "index.db");
20945
+ this.#path = join30(resolve27(workspace), ".flavor", "astgraph", "index.db");
20545
20946
  }
20546
20947
  async status() {
20547
- if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
20948
+ if ((await stat11(this.#path).catch(() => void 0))?.isFile() !== true) {
20548
20949
  return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
20549
20950
  }
20550
20951
  return this.#read((db) => {
@@ -20824,8 +21225,8 @@ function createTodoWriteTool() {
20824
21225
 
20825
21226
  // src/tools/managed.ts
20826
21227
  import { randomUUID as randomUUID18 } from "crypto";
20827
- import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
20828
- import { dirname as dirname19, join as join30, resolve as resolve29 } from "path";
21228
+ import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile23, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
21229
+ import { dirname as dirname19, join as join31, resolve as resolve29 } from "path";
20829
21230
  import { z as z37 } from "zod";
20830
21231
  var MANAGED_TOOL_VERSION = 1;
20831
21232
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -20868,8 +21269,8 @@ var ManagedToolStore = class {
20868
21269
  #diagnostics = [];
20869
21270
  constructor(options) {
20870
21271
  this.#workspace = resolve29(options.workspace);
20871
- this.#projectRoot = join30(this.#workspace, ".flavor", "tools");
20872
- this.#globalRoot = join30(resolve29(options.home), ".flavor-code", "tools");
21272
+ this.#projectRoot = join31(this.#workspace, ".flavor", "tools");
21273
+ this.#globalRoot = join31(resolve29(options.home), ".flavor-code", "tools");
20873
21274
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
20874
21275
  }
20875
21276
  get diagnostics() {
@@ -20916,7 +21317,7 @@ var ManagedToolStore = class {
20916
21317
  version: MANAGED_TOOL_VERSION,
20917
21318
  createdAt: this.#now()
20918
21319
  });
20919
- const path = join30(this.directory(input.scope), filename(input.name));
21320
+ const path = join31(this.directory(input.scope), filename(input.name));
20920
21321
  const definition = compileDefinition(record3, input.scope, this.#workspace);
20921
21322
  await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
20922
21323
  `);
@@ -20941,7 +21342,7 @@ var ManagedToolStore = class {
20941
21342
  if (!parsed.success) return [];
20942
21343
  const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
20943
21344
  if (matches.length > 0) return matches.map((entry2) => entry2.path);
20944
- return [join30(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
21345
+ return [join31(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
20945
21346
  }
20946
21347
  async #loadDirectory(scope2) {
20947
21348
  const root = this.directory(scope2);
@@ -20954,12 +21355,12 @@ var ManagedToolStore = class {
20954
21355
  return;
20955
21356
  }
20956
21357
  for (const name of names) {
20957
- const path = join30(root, name);
21358
+ const path = join31(root, name);
20958
21359
  try {
20959
21360
  const info = await lstat11(path);
20960
21361
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
20961
21362
  if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
20962
- const record3 = ManagedToolRecordSchema.parse(JSON.parse(await readFile22(path, "utf8")));
21363
+ const record3 = ManagedToolRecordSchema.parse(JSON.parse(await readFile23(path, "utf8")));
20963
21364
  if (record3.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
20964
21365
  const key = scopedKey(scope2, record3.name);
20965
21366
  if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record3.name}`);
@@ -21868,6 +22269,7 @@ var FlavorSession = class {
21868
22269
  #pendingCoWorkStarts = [];
21869
22270
  #activeSteeringDurableIds = /* @__PURE__ */ new Set();
21870
22271
  #durableResumeStarted = false;
22272
+ #continuationResumeStarted = false;
21871
22273
  #permissionBaseline;
21872
22274
  #activeSubmission;
21873
22275
  constructor(services) {
@@ -21888,6 +22290,10 @@ var FlavorSession = class {
21888
22290
  queueSnapshot() {
21889
22291
  return this.#queue.snapshot();
21890
22292
  }
22293
+ /** True when start() admitted a persisted /loop or /goal continuation. */
22294
+ get rotationContinuationResumed() {
22295
+ return this.#continuationResumeStarted;
22296
+ }
21891
22297
  clearQueue() {
21892
22298
  const snapshot4 = this.#queue.clear();
21893
22299
  for (const kind of ["steer", "followUp"]) {
@@ -22037,6 +22443,7 @@ var FlavorSession = class {
22037
22443
  if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
22038
22444
  this.#started = true;
22039
22445
  this.#resumeDurableQueue();
22446
+ this.#resumeRotationContinuation();
22040
22447
  });
22041
22448
  return this.#startPromise;
22042
22449
  }
@@ -22119,7 +22526,8 @@ var FlavorSession = class {
22119
22526
  skillNames,
22120
22527
  this.#services.managedToolCommands().map(({ name }) => name)
22121
22528
  );
22122
- if (command !== null) await this.#dispatch(command, controller2.signal);
22529
+ if (submission.continuation !== void 0) await this.#runContinuation(submission.continuation, controller2.signal);
22530
+ else if (command !== null) await this.#dispatch(command, controller2.signal);
22123
22531
  else for await (const event of this.#services.run(prompt, controller2.signal, {
22124
22532
  getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
22125
22533
  ...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
@@ -22377,6 +22785,33 @@ Address this now (for example run the required verification or fix), then finish
22377
22785
  });
22378
22786
  }
22379
22787
  }
22788
+ /**
22789
+ * After a heap rotation the relaunched process inherits the same session and
22790
+ * a marker continuation naming the /loop or /goal that was in flight. Resume
22791
+ * it once here so a multi-day autonomous run survives the rotation instead of
22792
+ * silently stopping at the boundary.
22793
+ */
22794
+ #resumeRotationContinuation() {
22795
+ if (this.#continuationResumeStarted || this.#closed) return;
22796
+ const continuation = this.#services.continuation?.();
22797
+ if (continuation === void 0) return;
22798
+ const supported = continuation.kind === "loop" ? this.#services.resumeLoop !== void 0 : this.#services.resumeGoal !== void 0;
22799
+ if (!supported) return;
22800
+ this.#continuationResumeStarted = true;
22801
+ const displayText = `Resuming the interrupted ${continuation.kind} ${continuation.id} after a heap rotation.`;
22802
+ this.#notice(displayText);
22803
+ this.#enqueueNormalizedSubmission({ text: displayText, displayText, continuation }).catch((error) => {
22804
+ this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
22805
+ });
22806
+ }
22807
+ async #runContinuation(continuation, signal) {
22808
+ const stream = continuation.kind === "loop" ? this.#services.resumeLoop?.(continuation.id, signal) : this.#services.resumeGoal?.(continuation.id, signal);
22809
+ if (stream === void 0) {
22810
+ this.#notice(`The runtime cannot resume the interrupted ${continuation.kind} ${continuation.id}; start it again with /${continuation.kind}.`);
22811
+ return;
22812
+ }
22813
+ for await (const event of stream) this.#services.output(event);
22814
+ }
22380
22815
  #ackDurable(submission) {
22381
22816
  if (submission.durableId === void 0) return;
22382
22817
  this.#services.durableQueue?.ack(submission.durableId);
@@ -22534,9 +22969,9 @@ function requiredPals(service) {
22534
22969
 
22535
22970
  // src/doctor.ts
22536
22971
  import { constants as constants11 } from "fs";
22537
- import { access as access3, lstat as lstat12, readFile as readFile23, readdir as readdir10, stat as stat11 } from "fs/promises";
22972
+ import { access as access3, lstat as lstat12, readFile as readFile24, readdir as readdir10, stat as stat12 } from "fs/promises";
22538
22973
  import { homedir } from "os";
22539
- import { isAbsolute as isAbsolute16, join as join31, relative as relative20, resolve as resolve30, sep as sep20 } from "path";
22974
+ import { isAbsolute as isAbsolute16, join as join32, relative as relative20, resolve as resolve30, sep as sep20 } from "path";
22540
22975
  import { rgPath as bundledRgPath2 } from "@vscode/ripgrep";
22541
22976
 
22542
22977
  // src/utils/semver.ts
@@ -22583,7 +23018,7 @@ async function runDoctor(options = {}, dependencies2 = {}) {
22583
23018
  const checks = [];
22584
23019
  checks.push(runtimeCheck(nodeVersion, platform, arch));
22585
23020
  checks.push(await directoryCheck("workspace", workspace, false));
22586
- checks.push(await directoryCheck("state directory", join31(home, ".flavor-code"), true, home));
23021
+ checks.push(await directoryCheck("state directory", join32(home, ".flavor-code"), true, home));
22587
23022
  let loaded;
22588
23023
  try {
22589
23024
  loaded = await (dependencies2.loadConfig ?? loadConfig)({
@@ -22609,8 +23044,8 @@ async function runDoctor(options = {}, dependencies2 = {}) {
22609
23044
  checks.push({ name: "shell", status: "fail", message: errorMessage3(error) });
22610
23045
  }
22611
23046
  checks.push(await pluginCheck([
22612
- join31(home, ".flavor-code", "plugins"),
22613
- join31(workspace, ".flavor", "plugins")
23047
+ join32(home, ".flavor-code", "plugins"),
23048
+ join32(workspace, ".flavor", "plugins")
22614
23049
  ]));
22615
23050
  try {
22616
23051
  const latest = await (dependencies2.fetchLatestVersion ?? fetchLatestVersion)({
@@ -22658,7 +23093,7 @@ function runtimeCheck(version, platform, arch) {
22658
23093
  }
22659
23094
  async function directoryCheck(name, path, mayBeCreated, parent = path) {
22660
23095
  try {
22661
- const info = await stat11(path);
23096
+ const info = await stat12(path);
22662
23097
  if (!info.isDirectory()) return { name, status: "fail", message: `${path} is not a directory` };
22663
23098
  await access3(path, constants11.R_OK | constants11.W_OK);
22664
23099
  return { name, status: "pass", message: `${path} is readable and writable` };
@@ -22727,7 +23162,7 @@ async function pluginCheck(directories) {
22727
23162
  try {
22728
23163
  const rootInfo = await lstat12(root);
22729
23164
  if (rootInfo.isSymbolicLink()) throw new Error("plugin directory must not be a symbolic link");
22730
- const manifest = PluginManifestSchema.parse(JSON.parse(await readFile23(join31(root, "flavor-plugin.json"), "utf8")));
23165
+ const manifest = PluginManifestSchema.parse(JSON.parse(await readFile24(join32(root, "flavor-plugin.json"), "utf8")));
22731
23166
  const main = resolve30(root, manifest.main);
22732
23167
  const delta = relative20(root, main);
22733
23168
  if (delta === ".." || delta.startsWith(`..${sep20}`) || isAbsolute16(delta)) throw new Error("plugin entry escapes its root");
@@ -22794,8 +23229,8 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
22794
23229
  ]);
22795
23230
 
22796
23231
  // src/utils/crash-guard.ts
22797
- import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
22798
- import { join as join32 } from "path";
23232
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
23233
+ import { join as join33 } from "path";
22799
23234
  var installed = false;
22800
23235
  var crashing = false;
22801
23236
  var crashCleanups = /* @__PURE__ */ new Set();
@@ -22826,7 +23261,7 @@ function installCrashGuard(options = {}) {
22826
23261
  installed = true;
22827
23262
  const crashLogDir = () => {
22828
23263
  try {
22829
- return join32((options.workspace ?? process.cwd)(), ".flavor");
23264
+ return join33((options.workspace ?? process.cwd)(), ".flavor");
22830
23265
  } catch {
22831
23266
  return process.cwd();
22832
23267
  }
@@ -22834,9 +23269,9 @@ function installCrashGuard(options = {}) {
22834
23269
  const writeCrashLog = (kind, detail) => {
22835
23270
  try {
22836
23271
  const dir = crashLogDir();
22837
- mkdirSync2(dir, { recursive: true });
22838
- const path = join32(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
22839
- writeFileSync(path, [
23272
+ mkdirSync3(dir, { recursive: true });
23273
+ const path = join33(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
23274
+ writeFileSync2(path, [
22840
23275
  `kind: ${kind}`,
22841
23276
  `version: ${process.version}`,
22842
23277
  `platform: ${process.platform}`,
@@ -23606,8 +24041,8 @@ ${total}`;
23606
24041
 
23607
24042
  // src/memory/store.ts
23608
24043
  import { createHash as createHash17 } from "crypto";
23609
- import { readFile as readFile24, rm as rm12 } from "fs/promises";
23610
- import { resolve as resolve31, join as join33, relative as relative21, sep as sep21 } from "path";
24044
+ import { readFile as readFile25, rm as rm12 } from "fs/promises";
24045
+ import { resolve as resolve31, join as join34, relative as relative21, sep as sep21 } from "path";
23611
24046
 
23612
24047
  // src/memory/similarity.ts
23613
24048
  function normalizeForSimilarity(value) {
@@ -23710,9 +24145,9 @@ var MemoryStore = class {
23710
24145
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
23711
24146
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
23712
24147
  this.workspace = resolve31(options.workspace);
23713
- this.#memoryRoot = join33(this.workspace, ".flavor", "memory");
23714
- this.path = join33(this.#memoryRoot, "MEMORY.md");
23715
- this.#behaviorPath = join33(this.#memoryRoot, "behavior.json");
24148
+ this.#memoryRoot = join34(this.workspace, ".flavor", "memory");
24149
+ this.path = join34(this.#memoryRoot, "MEMORY.md");
24150
+ this.#behaviorPath = join34(this.#memoryRoot, "behavior.json");
23716
24151
  this.#maxEntries = options.maxEntries;
23717
24152
  this.#maxEntryChars = options.maxEntryChars;
23718
24153
  }
@@ -23930,7 +24365,7 @@ var MemoryStore = class {
23930
24365
  });
23931
24366
  }
23932
24367
  async #readTaskItem(reference) {
23933
- const document = decodeTask(await readFile24(this.#resolveContentPath(reference.contentPath), "utf8"));
24368
+ const document = decodeTask(await readFile25(this.#resolveContentPath(reference.contentPath), "utf8"));
23934
24369
  return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
23935
24370
  }
23936
24371
  #resolveContentPath(contentPath) {
@@ -24570,8 +25005,8 @@ function createExecutionEnvironment(workspace, config) {
24570
25005
  }
24571
25006
 
24572
25007
  // src/ide/client.ts
24573
- import { readFile as readFile25, readdir as readdir11, stat as stat12 } from "fs/promises";
24574
- import { basename as basename8, join as join34, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
25008
+ import { readFile as readFile26, readdir as readdir11, stat as stat13 } from "fs/promises";
25009
+ import { basename as basename8, join as join35, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
24575
25010
  var FlavorIdeClient = class {
24576
25011
  #workspace;
24577
25012
  #home;
@@ -24667,7 +25102,7 @@ var FlavorIdeClient = class {
24667
25102
  };
24668
25103
  if (await this.#healthy(configured)) return configured;
24669
25104
  }
24670
- const directory = join34(this.#home, ".flavor-code", "ide");
25105
+ const directory = join35(this.#home, ".flavor-code", "ide");
24671
25106
  let entries;
24672
25107
  try {
24673
25108
  entries = (await readdir11(directory)).filter((name) => name.endsWith(".lock"));
@@ -24675,9 +25110,9 @@ var FlavorIdeClient = class {
24675
25110
  return void 0;
24676
25111
  }
24677
25112
  const candidates = (await Promise.all(entries.map(async (name) => {
24678
- const path = join34(directory, name);
25113
+ const path = join35(directory, name);
24679
25114
  try {
24680
- const [contents, info] = await Promise.all([readFile25(path, "utf8"), stat12(path)]);
25115
+ const [contents, info] = await Promise.all([readFile26(path, "utf8"), stat13(path)]);
24681
25116
  const lock = parseLockfile(contents);
24682
25117
  return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
24683
25118
  } catch {
@@ -24796,6 +25231,7 @@ function relativeDisplay(workspace, filePath) {
24796
25231
 
24797
25232
  // src/jobs/registry.ts
24798
25233
  import { randomUUID as randomUUID19 } from "crypto";
25234
+ var OUTPUT_SLICE_SLACK_CHARS = 64 * 1024;
24799
25235
  var JobRegistry = class {
24800
25236
  #jobs = /* @__PURE__ */ new Map();
24801
25237
  #maxOutputChars;
@@ -24824,7 +25260,8 @@ var JobRegistry = class {
24824
25260
  updatedAt: now,
24825
25261
  outputChars: 0,
24826
25262
  truncated: false,
24827
- output: "",
25263
+ chunks: [],
25264
+ windowChars: 0,
24828
25265
  outputStart: 0,
24829
25266
  settle,
24830
25267
  done
@@ -24835,11 +25272,16 @@ var JobRegistry = class {
24835
25272
  id,
24836
25273
  append: (text) => {
24837
25274
  if (record3.state !== "running" || text === "") return;
24838
- record3.output += text;
25275
+ record3.chunks.push(text);
25276
+ record3.windowChars += text.length;
24839
25277
  record3.outputChars += text.length;
24840
- if (record3.output.length > this.#maxOutputChars) {
24841
- const removed = record3.output.length - this.#maxOutputChars;
24842
- record3.output = record3.output.slice(removed);
25278
+ if (record3.outputChars > this.#maxOutputChars) record3.truncated = true;
25279
+ if (record3.windowChars > this.#maxOutputChars + OUTPUT_SLICE_SLACK_CHARS) {
25280
+ const joined = record3.chunks.join("");
25281
+ const kept = joined.slice(joined.length - this.#maxOutputChars);
25282
+ const removed = joined.length - kept.length;
25283
+ record3.chunks = [kept];
25284
+ record3.windowChars = kept.length;
24843
25285
  record3.outputStart += removed;
24844
25286
  record3.truncated = true;
24845
25287
  }
@@ -24860,8 +25302,20 @@ var JobRegistry = class {
24860
25302
  }
24861
25303
  read(id, owner2, cursor = 0) {
24862
25304
  const job = this.#owned(id, owner2);
24863
- const effective = Math.max(cursor, job.outputStart);
24864
- return { ...snapshot2(job), output: job.output.slice(effective - job.outputStart), cursor: job.outputStart + job.output.length };
25305
+ const window = job.chunks.join("");
25306
+ const dropped = Math.max(0, window.length - this.#maxOutputChars);
25307
+ const effective = Math.max(cursor, job.outputStart + dropped);
25308
+ return { ...snapshot2(job), output: window.slice(effective - job.outputStart), cursor: job.outputStart + window.length };
25309
+ }
25310
+ /** Retained sizes for the rotation census. */
25311
+ census() {
25312
+ let windowChars = 0;
25313
+ let running = 0;
25314
+ for (const job of this.#jobs.values()) {
25315
+ windowChars += job.windowChars;
25316
+ if (job.state === "running") running += 1;
25317
+ }
25318
+ return { jobs: this.#jobs.size, running, windowChars };
24865
25319
  }
24866
25320
  async wait(id, owner2, signal) {
24867
25321
  signal?.throwIfAborted();
@@ -25198,7 +25652,7 @@ function palSocketAddress(options) {
25198
25652
  import { createHash as createHash20 } from "crypto";
25199
25653
  import { Socket as Socket2 } from "net";
25200
25654
  import { tmpdir as tmpdir3 } from "os";
25201
- import { join as join36 } from "path";
25655
+ import { join as join37 } from "path";
25202
25656
  import { spawn as spawn8 } from "child_process";
25203
25657
 
25204
25658
  // src/pals/broker.ts
@@ -25209,18 +25663,18 @@ import { dirname as dirname21 } from "path";
25209
25663
 
25210
25664
  // src/pals/auth.ts
25211
25665
  import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
25212
- import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile26, unlink as unlink4 } from "fs/promises";
25666
+ import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile27, unlink as unlink4 } from "fs/promises";
25213
25667
  import { homedir as homedir2 } from "os";
25214
- import { join as join35 } from "path";
25668
+ import { join as join36 } from "path";
25215
25669
  var PAL_AUTH_TOKEN_BYTES = 32;
25216
25670
  var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
25217
25671
  async function loadOrCreatePalAuthToken(options = {}) {
25218
- const directory = join35(options.home ?? homedir2(), ".flavor-code", "pals");
25219
- const tokenPath = join35(directory, "auth-token");
25672
+ const directory = join36(options.home ?? homedir2(), ".flavor-code", "pals");
25673
+ const tokenPath = join36(directory, "auth-token");
25220
25674
  await mkdir21(directory, { recursive: true, mode: 448 });
25221
25675
  await chmod2(directory, 448);
25222
25676
  const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
25223
- const temporaryPath = join35(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
25677
+ const temporaryPath = join36(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
25224
25678
  let handle;
25225
25679
  try {
25226
25680
  handle = await open12(temporaryPath, "wx", 384);
@@ -25243,7 +25697,7 @@ async function loadOrCreatePalAuthToken(options = {}) {
25243
25697
  if (error.code !== "ENOENT") throw error;
25244
25698
  });
25245
25699
  }
25246
- const stored = (await readFile26(tokenPath, "utf8")).trim();
25700
+ const stored = (await readFile27(tokenPath, "utf8")).trim();
25247
25701
  if (!TOKEN_PATTERN.test(stored)) throw new Error("Pals authentication token store is invalid");
25248
25702
  await chmod2(tokenPath, 384);
25249
25703
  return stored;
@@ -25255,7 +25709,7 @@ function validatePalAuthToken(token) {
25255
25709
 
25256
25710
  // src/pals/lifecycle.ts
25257
25711
  import { randomUUID as randomUUID22 } from "crypto";
25258
- import { mkdir as mkdir22, open as open13, readFile as readFile27, rename as rename11, stat as stat13, unlink as unlink5 } from "fs/promises";
25712
+ import { mkdir as mkdir22, open as open13, readFile as readFile28, rename as rename11, stat as stat14, unlink as unlink5 } from "fs/promises";
25259
25713
  import { dirname as dirname20 } from "path";
25260
25714
  function defaultProcessAlive(pid) {
25261
25715
  if (!Number.isSafeInteger(pid) || pid <= 0) return true;
@@ -25287,7 +25741,7 @@ async function acquirePalFileLock(options) {
25287
25741
  handle = await open13(options.path, "wx", 384);
25288
25742
  } catch (error) {
25289
25743
  if (error.code !== "EEXIST") throw error;
25290
- const [contents, info] = await Promise.all([readFile27(options.path, "utf8").catch(() => ""), stat13(options.path).catch(() => void 0)]);
25744
+ const [contents, info] = await Promise.all([readFile28(options.path, "utf8").catch(() => ""), stat14(options.path).catch(() => void 0)]);
25291
25745
  if (await options.endpointLive()) return void 0;
25292
25746
  const existing = parseMetadata(contents);
25293
25747
  if (existing !== void 0 && (options.processAlive ?? defaultProcessAlive)(existing.pid)) return void 0;
@@ -25317,7 +25771,7 @@ async function acquirePalFileLock(options) {
25317
25771
  await handle.close();
25318
25772
  }
25319
25773
  const release = async () => {
25320
- const current = parseMetadata(await readFile27(options.path, "utf8").catch(() => ""));
25774
+ const current = parseMetadata(await readFile28(options.path, "utf8").catch(() => ""));
25321
25775
  if (current?.nonce !== nonce) return;
25322
25776
  await unlink5(options.path).catch((error) => {
25323
25777
  if (error.code !== "ENOENT") throw error;
@@ -25350,7 +25804,7 @@ function canConnect(address) {
25350
25804
  }
25351
25805
  function startupLockPath(address) {
25352
25806
  const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
25353
- return join36(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
25807
+ return join37(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
25354
25808
  }
25355
25809
  async function spawnProductionBroker(address) {
25356
25810
  const entry2 = process.argv[1];
@@ -26293,8 +26747,8 @@ async function createProductionRuntime(options) {
26293
26747
  const registeredProviders = registration.registered;
26294
26748
  let effectiveLlm = registration.effectiveLlm;
26295
26749
  const pluginHost = new PluginHost({
26296
- globalPluginDirs: [join37(home, ".flavor-code", "plugins")],
26297
- projectPluginDirs: [join37(workspace, ".flavor", "plugins")],
26750
+ globalPluginDirs: [join38(home, ".flavor-code", "plugins")],
26751
+ projectPluginDirs: [join38(workspace, ".flavor", "plugins")],
26298
26752
  config,
26299
26753
  // Existing project plugins (including astgraph and superharness) use Node.js
26300
26754
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -26403,7 +26857,7 @@ async function createProductionRuntime(options) {
26403
26857
  const address = palSocketAddress({
26404
26858
  platform: process.platform,
26405
26859
  userScope: home,
26406
- runtimeDir: join37(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
26860
+ runtimeDir: join38(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
26407
26861
  });
26408
26862
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
26409
26863
  instanceId: collaboration.instanceId,
@@ -26514,8 +26968,8 @@ async function createProductionRuntime(options) {
26514
26968
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
26515
26969
  });
26516
26970
  const skills = new SkillRegistry({
26517
- globalRoots: [join37(home, ".flavor-code", "skills")],
26518
- projectRoots: [join37(workspace, ".flavor", "skills"), ...pluginSkillRoots],
26971
+ globalRoots: [join38(home, ".flavor-code", "skills")],
26972
+ projectRoots: [join38(workspace, ".flavor", "skills"), ...pluginSkillRoots],
26519
26973
  authorizeResource: async () => true,
26520
26974
  disabledNames: config.skills.disabled
26521
26975
  });
@@ -26523,7 +26977,7 @@ async function createProductionRuntime(options) {
26523
26977
  skillsReady.catch(() => void 0);
26524
26978
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
26525
26979
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
26526
- const flavor = await optionalText2(join37(workspace, "FLAVOR.md"));
26980
+ const flavor = await optionalText2(join38(workspace, "FLAVOR.md"));
26527
26981
  const instructionBaseline = await workspaceInstructions.baseline();
26528
26982
  let memoryContext;
26529
26983
  if (memoryStore !== void 0) {
@@ -27108,7 +27562,7 @@ async function createProductionRuntime(options) {
27108
27562
  evolveService.toolDefinition(),
27109
27563
  ...mcpTools
27110
27564
  ];
27111
- const loopFlavor = await optionalText2(join37(input.workspace, "FLAVOR.md"));
27565
+ const loopFlavor = await optionalText2(join38(input.workspace, "FLAVOR.md"));
27112
27566
  const loopInstructionBaseline = await loopInstructions.baseline();
27113
27567
  const loopEnvironment = createPromptEnvironment({
27114
27568
  now: /* @__PURE__ */ new Date(),
@@ -27216,12 +27670,83 @@ async function createProductionRuntime(options) {
27216
27670
  await loopExecutionEnvironment?.dispose();
27217
27671
  }
27218
27672
  };
27673
+ let activeLongTask;
27674
+ const readMemoryRestartMarker = () => {
27675
+ try {
27676
+ return parseMemoryRestartMarker(readFileSync3(memoryRestartMarkerPath(workspace), "utf8"));
27677
+ } catch {
27678
+ return void 0;
27679
+ }
27680
+ };
27681
+ const currentPressureReading = () => verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO) ?? { ...readHeap(), gcVerified: false };
27682
+ const collectRotationHolders = () => {
27683
+ const holders = {};
27684
+ try {
27685
+ const context = harness.main.context.census();
27686
+ holders.context = { entries: context.messages, chars: context.messageChars };
27687
+ holders.contextVisibility = { entries: context.visibilityEntries, chars: context.visibilityChars };
27688
+ holders.contextTransient = { entries: context.transientEntries, chars: context.transientChars };
27689
+ } catch {
27690
+ }
27691
+ try {
27692
+ holders.journal = { entries: harnessJournal.recordCount };
27693
+ } catch {
27694
+ }
27695
+ try {
27696
+ const jobCensus = jobs.census();
27697
+ holders.jobs = { entries: jobCensus.jobs, chars: jobCensus.windowChars };
27698
+ } catch {
27699
+ }
27700
+ try {
27701
+ holders.sessionTree = { entries: sessionHistory.nodeCount };
27702
+ } catch {
27703
+ }
27704
+ return holders;
27705
+ };
27706
+ const rotateForHeap = (continuation, reading) => {
27707
+ requestMemoryRestart(workspace, sessionId, emitOutput, {
27708
+ ...continuation === void 0 ? {} : { continuation },
27709
+ ...reading === void 0 ? {} : { reading },
27710
+ holders: collectRotationHolders()
27711
+ }, options.onMemoryRestartRequested);
27712
+ };
27713
+ const maybeRotateAtBoundary = (continuation) => {
27714
+ activeLongTask = continuation;
27715
+ const pressure = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
27716
+ if (pressure !== void 0) {
27717
+ rotateForHeap(continuation, pressure);
27718
+ return;
27719
+ }
27720
+ const reading = heapRotationNeeded();
27721
+ if (reading === void 0) return;
27722
+ if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
27723
+ rotateForHeap(continuation, reading);
27724
+ };
27725
+ const maybeRotateAtTurnBoundary = () => {
27726
+ const reading = heapRotationNeeded();
27727
+ if (reading === void 0) return;
27728
+ if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
27729
+ rotateForHeap(void 0, reading);
27730
+ };
27731
+ const consumePendingContinuation = () => {
27732
+ const marker = readMemoryRestartMarker();
27733
+ const continuation = pendingContinuation(marker, sessionId, /* @__PURE__ */ new Date());
27734
+ if (continuation === void 0 || marker === void 0) return void 0;
27735
+ try {
27736
+ const markerPath = memoryRestartMarkerPath(workspace);
27737
+ writeMarkerAtomically(markerPath, consumedMemoryRestartMarker(marker));
27738
+ } catch {
27739
+ }
27740
+ return continuation;
27741
+ };
27219
27742
  const loopStore = new LoopStore({ workspace });
27220
27743
  const loopOrchestrator = new LoopOrchestrator({
27221
27744
  workspace,
27222
27745
  config: config.loop,
27223
27746
  persistence: loopStore,
27224
27747
  hallucinationGuard,
27748
+ onCycleBoundary: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
27749
+ onMemoryCheckpoint: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
27225
27750
  prepareWorkspace: (input) => prepareLoopWorkspace(input),
27226
27751
  inferVerification: inferVerificationPlan,
27227
27752
  runWorker: ({ workspace: executionWorkspace, prompt, signal }) => runLoopWorker({ workspace: executionWorkspace, prompt, signal }),
@@ -27258,9 +27783,11 @@ async function createProductionRuntime(options) {
27258
27783
  plannerModelId: mainModel,
27259
27784
  classifierModelId: mainModel,
27260
27785
  skepticCount: 3,
27261
- maxRounds: 5,
27262
- maxStallStreak: 2,
27786
+ maxRounds: config.goal.maxRounds,
27787
+ maxStallStreak: config.goal.maxStallStreak,
27263
27788
  persistence: goalStore,
27789
+ onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
27790
+ onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
27264
27791
  verifyHost: async (signal) => runVerificationPlan(
27265
27792
  await inferVerificationPlan(workspace),
27266
27793
  workspace,
@@ -27355,30 +27882,49 @@ async function createProductionRuntime(options) {
27355
27882
  contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
27356
27883
  });
27357
27884
  const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
27358
- return monitorMemoryRestart(durableTurn(persistAfter(runMain(
27359
- harness,
27360
- skills,
27361
- prompt,
27362
- signal,
27363
- selectedModels.mainError,
27364
- memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
27365
- store: memoryStore,
27366
- taskId: memoryLifecycle.taskId ?? sessionId,
27367
- topK: config.memory.retrievalTopK,
27368
- maxChars: config.memory.maxPromptChars
27369
- },
27370
- runOptions?.getSteeringMessages,
27371
- runOptions?.initialUserMessage,
27372
- runOptions?.additionalContext,
27373
- ide
27374
- ), persist), harnessJournal, turnId, turnConfig), () => requestMemoryRestart(workspace, sessionId, emitOutput));
27885
+ return monitorTurnMemory(
27886
+ durableTurn(persistAfter(runMain(
27887
+ harness,
27888
+ skills,
27889
+ prompt,
27890
+ signal,
27891
+ selectedModels.mainError,
27892
+ memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
27893
+ store: memoryStore,
27894
+ taskId: memoryLifecycle.taskId ?? sessionId,
27895
+ topK: config.memory.retrievalTopK,
27896
+ maxChars: config.memory.maxPromptChars
27897
+ },
27898
+ runOptions?.getSteeringMessages,
27899
+ runOptions?.initialUserMessage,
27900
+ runOptions?.additionalContext,
27901
+ ide
27902
+ ), persist), harnessJournal, turnId, turnConfig),
27903
+ () => rotateForHeap(void 0, currentPressureReading()),
27904
+ () => maybeRotateAtTurnBoundary()
27905
+ );
27375
27906
  },
27376
27907
  runSkill: (skill, prompt, signal) => persistAfter(
27377
27908
  runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
27378
27909
  persist
27379
27910
  ),
27380
- runLoop: (goal, signal) => runLoopSession(loopOrchestrator, hooks, goal, signal),
27381
- runGoal: (goal, signal) => persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
27911
+ runLoop: (goal, signal) => monitorLongTaskRestart(
27912
+ runLoopSession(loopOrchestrator, hooks, goal, signal),
27913
+ () => rotateForHeap(activeLongTask, currentPressureReading())
27914
+ ),
27915
+ runGoal: (goal, signal) => monitorLongTaskRestart(
27916
+ persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
27917
+ () => rotateForHeap(activeLongTask, currentPressureReading())
27918
+ ),
27919
+ resumeLoop: (loopId, signal) => monitorLongTaskRestart(
27920
+ runResumeLoopSession(loopOrchestrator, hooks, loopId, signal),
27921
+ () => rotateForHeap(activeLongTask ?? { kind: "loop", id: loopId }, currentPressureReading())
27922
+ ),
27923
+ resumeGoal: (goalId, signal) => monitorLongTaskRestart(
27924
+ persistEach(runResumeGoalSession(goalOrchestrator, goalId, signal), persist),
27925
+ () => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
27926
+ ),
27927
+ continuation: () => consumePendingContinuation(),
27382
27928
  mcp: async (command, signal) => {
27383
27929
  signal.throwIfAborted();
27384
27930
  const manager = await mcpReady;
@@ -27472,7 +28018,7 @@ async function createProductionRuntime(options) {
27472
28018
  const root = resolve36(workspace);
27473
28019
  const abs = resolve36(root, path);
27474
28020
  if (abs !== root && !abs.startsWith(root + sep25)) throw new Error(`path outside workspace: ${path}`);
27475
- return readFile28(abs, "utf8");
28021
+ return readFile29(abs, "utf8");
27476
28022
  },
27477
28023
  history: async (path, limit) => fileHistory(workspace, path, limit),
27478
28024
  questions,
@@ -27484,7 +28030,7 @@ async function createProductionRuntime(options) {
27484
28030
  }, query, focus, signal),
27485
28031
  audit: async (toolFilter) => {
27486
28032
  try {
27487
- const raw = await readFile28(auditLogger.path, "utf8");
28033
+ const raw = await readFile29(auditLogger.path, "utf8");
27488
28034
  const lines = raw.trim().split("\n").filter((line) => line.length > 0);
27489
28035
  const entries = lines.map((line) => {
27490
28036
  try {
@@ -27524,7 +28070,7 @@ ${summary2}`;
27524
28070
  doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
27525
28071
  usage: async () => {
27526
28072
  try {
27527
- const raw = await readFile28(usageLogPath(), "utf8");
28073
+ const raw = await readFile29(usageLogPath(), "utf8");
27528
28074
  return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
27529
28075
  } catch (error) {
27530
28076
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -27677,7 +28223,7 @@ ${renderMemoryDocument(entries)}`;
27677
28223
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
27678
28224
  }
27679
28225
  try {
27680
- const tokenStore = createFileTokenStore(join37(home, ".flavor-code", "auth.json"));
28226
+ const tokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
27681
28227
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
27682
28228
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
27683
28229
  const oauth = new OAuthCallbackAuthProvider({
@@ -27724,7 +28270,7 @@ ${renderMemoryDocument(entries)}`;
27724
28270
  }
27725
28271
  },
27726
28272
  async logout() {
27727
- const tokenStore = createFileTokenStore(join37(home, ".flavor-code", "auth.json"));
28273
+ const tokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
27728
28274
  const tokens2 = await tokenStore.load();
27729
28275
  const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
27730
28276
  if (!hadCredentials) {
@@ -27917,10 +28463,16 @@ function formatMcpReconnect(server) {
27917
28463
  return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
27918
28464
  }
27919
28465
  async function* runLoopSession(orchestrator, hooks, goal, signal) {
28466
+ yield* loopSessionEvents(orchestrator.run({ goal, signal }), hooks);
28467
+ }
28468
+ async function* runResumeLoopSession(orchestrator, hooks, loopId, signal) {
28469
+ yield* loopSessionEvents(orchestrator.resume({ loopId, signal }), hooks);
28470
+ }
28471
+ async function* loopSessionEvents(events, hooks) {
27920
28472
  let totalInputTokens = 0;
27921
28473
  let totalOutputTokens = 0;
27922
28474
  let latestVerification;
27923
- for await (const event of orchestrator.run({ goal, signal })) {
28475
+ for await (const event of events) {
27924
28476
  if (event.type === "worker-event") {
27925
28477
  if (event.event.type === "usage") {
27926
28478
  totalInputTokens += event.event.inputTokens;
@@ -28024,7 +28576,7 @@ async function workspaceFingerprint(workspace) {
28024
28576
  hash.update(name);
28025
28577
  if (remaining <= 0) continue;
28026
28578
  try {
28027
- const content = await readFile28(path);
28579
+ const content = await readFile29(path);
28028
28580
  const slice = content.subarray(0, remaining);
28029
28581
  hash.update(slice);
28030
28582
  remaining -= slice.length;
@@ -28136,7 +28688,16 @@ async function* durableTurn(source, journal, turnId, config) {
28136
28688
  }
28137
28689
  }
28138
28690
  }
28139
- async function* monitorMemoryRestart(source, onRestartNeeded) {
28691
+ async function* monitorTurnMemory(source, onPressure, onBoundary) {
28692
+ let pressure = false;
28693
+ for await (const event of source) {
28694
+ if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
28695
+ yield event;
28696
+ }
28697
+ if (pressure) onPressure();
28698
+ else onBoundary();
28699
+ }
28700
+ async function* monitorLongTaskRestart(source, onRestartNeeded) {
28140
28701
  let pressure = false;
28141
28702
  for await (const event of source) {
28142
28703
  if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
@@ -28144,7 +28705,10 @@ async function* monitorMemoryRestart(source, onRestartNeeded) {
28144
28705
  }
28145
28706
  if (pressure) onRestartNeeded();
28146
28707
  }
28147
- function requestMemoryRestart(workspace, sessionId, emitOutput) {
28708
+ function requestMemoryRestart(workspace, sessionId, emitOutput, rotation, onRestartRequested) {
28709
+ if (memoryRotationActive()) return;
28710
+ markMemoryRotation();
28711
+ const continuation = rotation?.continuation;
28148
28712
  try {
28149
28713
  const markerPath = memoryRestartMarkerPath(workspace);
28150
28714
  let previous;
@@ -28152,14 +28716,43 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
28152
28716
  previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
28153
28717
  } catch {
28154
28718
  }
28155
- mkdirSync3(dirname22(markerPath), { recursive: true });
28156
- writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
28157
- `, "utf8");
28719
+ writeMarkerAtomically(markerPath, nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date(), continuation));
28158
28720
  } catch {
28159
28721
  }
28160
- process.exitCode = MEMORY_RESTART_EXIT_CODE;
28161
- emitOutput({ type: "notice", message: "Memory watermark reached; the session is saved and Flavor is restarting with a fresh heap." });
28162
- emitOutput({ type: "exit" });
28722
+ if (rotation?.reading !== void 0) {
28723
+ const census = {
28724
+ at: (/* @__PURE__ */ new Date()).toISOString(),
28725
+ reason: continuation === void 0 ? "memory-pressure" : `heap-rotation:${continuation.kind}`,
28726
+ sessionId,
28727
+ heap: censusHeapBlock(rotation.reading),
28728
+ holders: rotation.holders ?? {}
28729
+ };
28730
+ writeRotationCensus(workspace, census);
28731
+ if (heapSnapshotWarranted(rotation.reading)) writeRotationHeapSnapshot(workspace);
28732
+ }
28733
+ emitOutput({
28734
+ type: "notice",
28735
+ 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.`
28736
+ });
28737
+ if (onRestartRequested !== void 0) onRestartRequested(sessionId);
28738
+ else {
28739
+ process.exitCode = MEMORY_RESTART_EXIT_CODE;
28740
+ emitOutput({ type: "exit" });
28741
+ }
28742
+ }
28743
+ function writeMarkerAtomically(path, marker) {
28744
+ mkdirSync4(dirname22(path), { recursive: true });
28745
+ const temporary = `${path}.${process.pid}.${randomUUID25()}.tmp`;
28746
+ try {
28747
+ writeFileSync3(temporary, `${JSON.stringify(marker)}
28748
+ `, { encoding: "utf8", mode: 384 });
28749
+ renameSync2(temporary, path);
28750
+ } finally {
28751
+ try {
28752
+ unlinkSync3(temporary);
28753
+ } catch {
28754
+ }
28755
+ }
28163
28756
  }
28164
28757
  async function runGitCommit(deps, hint, signal) {
28165
28758
  if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
@@ -28254,7 +28847,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
28254
28847
  const configured = { ...providers };
28255
28848
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
28256
28849
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
28257
- const oauthTokenStore = createFileTokenStore(join37(home, ".flavor-code", "auth.json"));
28850
+ const oauthTokenStore = createFileTokenStore(join38(home, ".flavor-code", "auth.json"));
28258
28851
  const registered = [];
28259
28852
  let effectiveLlm;
28260
28853
  for (const [name, provider] of Object.entries(configured)) {
@@ -28523,7 +29116,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
28523
29116
  }
28524
29117
  async function optionalText2(path) {
28525
29118
  try {
28526
- return await readFile28(path, "utf8");
29119
+ return await readFile29(path, "utf8");
28527
29120
  } catch (error) {
28528
29121
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
28529
29122
  throw error;
@@ -28608,7 +29201,17 @@ function attachCleanupError(primary, cleanup) {
28608
29201
  }
28609
29202
  }
28610
29203
  async function* runGoalSession(orchestrator, goal, signal) {
28611
- for await (const event of orchestrator.run({ goal, signal })) {
29204
+ yield* goalSessionEvents(orchestrator.run({ goal, signal }));
29205
+ }
29206
+ async function* runResumeGoalSession(orchestrator, goalId, signal) {
29207
+ yield* goalSessionEvents(orchestrator.resume({ goalId, signal }));
29208
+ }
29209
+ async function* goalSessionEvents(events) {
29210
+ for await (const event of events) {
29211
+ if (event.type === "goal-resumed") {
29212
+ yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
29213
+ continue;
29214
+ }
28612
29215
  if (event.type === "goal-plan-created") {
28613
29216
  yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
28614
29217
  yield { type: "notice", message: `Plan file: ${event.planPath}` };
@@ -28685,13 +29288,13 @@ function createMemoryTaskId() {
28685
29288
 
28686
29289
  // src/desktop/runtime-controller.ts
28687
29290
  import { createHash as createHash27 } from "crypto";
28688
- import { mkdir as mkdir27, readFile as readFile36, readdir as readdir13, rm as rm15, stat as stat16, writeFile as writeFile17 } from "fs/promises";
29291
+ import { mkdir as mkdir27, readFile as readFile37, readdir as readdir13, rm as rm15, stat as stat17, writeFile as writeFile17 } from "fs/promises";
28689
29292
  import { homedir as homedir5 } from "os";
28690
- import { basename as basename10, join as join47, resolve as resolve39 } from "path";
29293
+ import { basename as basename10, join as join48, resolve as resolve39 } from "path";
28691
29294
 
28692
29295
  // src/d2c/workflow.ts
28693
29296
  import { createHash as createHash22 } from "crypto";
28694
- import { join as join38 } from "path";
29297
+ import { join as join39 } from "path";
28695
29298
  import { z as z42 } from "zod";
28696
29299
 
28697
29300
  // src/d2c/workflow-shared.ts
@@ -28837,7 +29440,7 @@ function reviewsFor(report, previous) {
28837
29440
  function implementationDirectory(report) {
28838
29441
  const source = report.implementation.source;
28839
29442
  if (!/^https?:\/\//i.test(source)) return source;
28840
- return join38("src", "d2c-output", report.task);
29443
+ return join39("src", "d2c-output", report.task);
28841
29444
  }
28842
29445
  function createWorkflow(report, framework) {
28843
29446
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -28961,7 +29564,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
28961
29564
  };
28962
29565
  }
28963
29566
  function workflowPath(workspace, task) {
28964
- return join38(taskDir(workspace, task), "workflow.json");
29567
+ return join39(taskDir(workspace, task), "workflow.json");
28965
29568
  }
28966
29569
  async function readWorkflow(workspace, task) {
28967
29570
  return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
@@ -29214,8 +29817,8 @@ function confirmApiMapping(mapping, operationKey, operations) {
29214
29817
  }
29215
29818
 
29216
29819
  // src/d2c/modules.ts
29217
- import { readFile as readFile29 } from "fs/promises";
29218
- import { isAbsolute as isAbsolute18, join as join39, normalize as normalize2, posix, sep as sep26, win32 as win322 } from "path";
29820
+ import { readFile as readFile30 } from "fs/promises";
29821
+ import { isAbsolute as isAbsolute18, join as join40, normalize as normalize2, posix, sep as sep26, win32 as win322 } from "path";
29219
29822
  import { z as z43 } from "zod";
29220
29823
  var ModuleSchema = z43.object({
29221
29824
  id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
@@ -29234,13 +29837,13 @@ function safeSource(path) {
29234
29837
  }
29235
29838
  function d2cOutputDirectory(workspace, task) {
29236
29839
  if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
29237
- return join39(workspace, "src", "d2c-output", task);
29840
+ return join40(workspace, "src", "d2c-output", task);
29238
29841
  }
29239
29842
  async function readD2cModules(workspace, task) {
29240
29843
  const project = d2cOutputDirectory(workspace, task);
29241
29844
  let raw;
29242
29845
  try {
29243
- raw = await readFile29(join39(project, "d2c.modules.json"), "utf8");
29846
+ raw = await readFile30(join40(project, "d2c.modules.json"), "utf8");
29244
29847
  } catch {
29245
29848
  return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
29246
29849
  }
@@ -29260,8 +29863,8 @@ async function readD2cModules(workspace, task) {
29260
29863
  }
29261
29864
 
29262
29865
  // src/d2c/integration.ts
29263
- import { mkdir as mkdir24, readFile as readFile30, writeFile as writeFile13 } from "fs/promises";
29264
- import { join as join40 } from "path";
29866
+ import { mkdir as mkdir24, readFile as readFile31, writeFile as writeFile13 } from "fs/promises";
29867
+ import { join as join41 } from "path";
29265
29868
  function record2(value) {
29266
29869
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
29267
29870
  }
@@ -29397,19 +30000,19 @@ async def reset_acceptance(request: Request):
29397
30000
  }
29398
30001
  async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
29399
30002
  if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
29400
- const packagePath = join40(projectDir, "package.json");
30003
+ const packagePath = join41(projectDir, "package.json");
29401
30004
  let pkg;
29402
30005
  try {
29403
- pkg = JSON.parse(await readFile30(packagePath, "utf8"));
30006
+ pkg = JSON.parse(await readFile31(packagePath, "utf8"));
29404
30007
  } catch {
29405
30008
  throw new Error(`D2C integration requires a valid package.json at ${packagePath}`);
29406
30009
  }
29407
30010
  const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
29408
30011
  const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
29409
30012
  const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
29410
- const apiDir = join40(projectDir, "src", "api");
29411
- const mockDir = join40(projectDir, "mock");
29412
- const serverDir = join40(projectDir, "server");
30013
+ const apiDir = join41(projectDir, "src", "api");
30014
+ const mockDir = join41(projectDir, "mock");
30015
+ const serverDir = join41(projectDir, "server");
29413
30016
  await Promise.all([
29414
30017
  mkdir24(apiDir, { recursive: true }),
29415
30018
  ...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
@@ -29419,23 +30022,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
29419
30022
  if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
29420
30023
  else files.push("mock/server.mjs");
29421
30024
  await Promise.all([
29422
- writeFile13(join40(apiDir, "http.js"), `import axios from "axios";
30025
+ writeFile13(join41(apiDir, "http.js"), `import axios from "axios";
29423
30026
 
29424
30027
  export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
29425
30028
 
29426
30029
  http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
29427
30030
  `),
29428
- writeFile13(join40(apiDir, "d2c-api.js"), apiModule(selected)),
29429
- writeFile13(join40(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
30031
+ writeFile13(join41(apiDir, "d2c-api.js"), apiModule(selected)),
30032
+ writeFile13(join41(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
29430
30033
  `),
29431
- ...options.pythonServer ? [] : [writeFile13(join40(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
30034
+ ...options.pythonServer ? [] : [writeFile13(join41(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
29432
30035
  writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
29433
30036
  `),
29434
30037
  ...options.pythonServer ? [
29435
- writeFile13(join40(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
29436
- writeFile13(join40(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
29437
- writeFile13(join40(serverDir, "README.md"), "# Python API service\n\nThis is the real integration service, not a mock. Development defaults to SQLite through `DATABASE_URL=sqlite:///./data/app.db`. Set the same environment variable to a SQLAlchemy MySQL or PostgreSQL URL for deployment; keep schema changes in migrations instead of database-specific SQL. Flavor Code starts the service automatically for acceptance.\n"),
29438
- writeFile13(join40(serverDir, "flavor-runtime.json"), `${JSON.stringify({
30038
+ writeFile13(join41(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
30039
+ writeFile13(join41(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
30040
+ writeFile13(join41(serverDir, "README.md"), "# Python API service\n\nThis is the real integration service, not a mock. Development defaults to SQLite through `DATABASE_URL=sqlite:///./data/app.db`. Set the same environment variable to a SQLAlchemy MySQL or PostgreSQL URL for deployment; keep schema changes in migrations instead of database-specific SQL. Flavor Code starts the service automatically for acceptance.\n"),
30041
+ writeFile13(join41(serverDir, "flavor-runtime.json"), `${JSON.stringify({
29439
30042
  schema: 1,
29440
30043
  kind: "python-fastapi",
29441
30044
  cwd: "server",
@@ -29454,9 +30057,9 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
29454
30057
 
29455
30058
  // src/d2c/mock-runner.ts
29456
30059
  import { spawn as spawn9 } from "child_process";
29457
- import { access as access4, readFile as readFile31 } from "fs/promises";
30060
+ import { access as access4, readFile as readFile32 } from "fs/promises";
29458
30061
  import { createServer as createServer5 } from "net";
29459
- import { join as join41 } from "path";
30062
+ import { join as join42 } from "path";
29460
30063
  async function exists3(path) {
29461
30064
  try {
29462
30065
  await access4(path);
@@ -29564,7 +30167,7 @@ ${output.trim()}` : ""}`);
29564
30167
  }
29565
30168
  }
29566
30169
  async function runPythonBackend(projectDir, options) {
29567
- const serverDir = join41(projectDir, "server");
30170
+ const serverDir = join42(projectDir, "server");
29568
30171
  if (options.installDependencies !== false) await installPython(serverDir, options.signal);
29569
30172
  const selectedPort = await port();
29570
30173
  const url = `http://127.0.0.1:${selectedPort}`;
@@ -29610,20 +30213,20 @@ ${managed.output().slice(-4e3)}`);
29610
30213
  };
29611
30214
  }
29612
30215
  async function runD2cMockServer(projectDir, options = {}) {
29613
- const runtimePath = join41(projectDir, "server", "flavor-runtime.json");
30216
+ const runtimePath = join42(projectDir, "server", "flavor-runtime.json");
29614
30217
  if (await exists3(runtimePath)) {
29615
30218
  let runtime;
29616
30219
  try {
29617
- runtime = JSON.parse(await readFile31(runtimePath, "utf8"));
30220
+ runtime = JSON.parse(await readFile32(runtimePath, "utf8"));
29618
30221
  } catch {
29619
30222
  throw new Error(`E2E backend runtime manifest is invalid at ${runtimePath}`);
29620
30223
  }
29621
30224
  if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
29622
30225
  return runPythonBackend(projectDir, options);
29623
30226
  }
29624
- const serverPath = join41(projectDir, "mock", "server.mjs");
30227
+ const serverPath = join42(projectDir, "mock", "server.mjs");
29625
30228
  if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
29626
- if (options.installDependencies === false && !await exists3(join41(projectDir, "node_modules", "express", "package.json"))) {
30229
+ if (options.installDependencies === false && !await exists3(join42(projectDir, "node_modules", "express", "package.json"))) {
29627
30230
  throw new Error("D2C mock server dependencies are not installed");
29628
30231
  }
29629
30232
  if (options.installDependencies !== false) await install(projectDir, options.signal);
@@ -29665,13 +30268,13 @@ ${managed.output().slice(-4e3)}`);
29665
30268
 
29666
30269
  // src/d2c/product.ts
29667
30270
  import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
29668
- import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile33, rename as rename13, stat as stat14, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
29669
- import { join as join42 } from "path";
30271
+ import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile34, rename as rename13, stat as stat15, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
30272
+ import { join as join43 } from "path";
29670
30273
  import { z as z44 } from "zod";
29671
30274
 
29672
30275
  // src/e2e/prd-governance.ts
29673
30276
  import { createHash as createHash23, randomUUID as randomUUID26 } from "crypto";
29674
- import { readFile as readFile32, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
30277
+ import { readFile as readFile33, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
29675
30278
  function hashPrd(markdown) {
29676
30279
  return createHash23("sha256").update(markdown, "utf8").digest("hex");
29677
30280
  }
@@ -29748,14 +30351,14 @@ function approvePrd(markdown, now = /* @__PURE__ */ new Date()) {
29748
30351
  return { hash: hashPrd(markdown), approvedAt: now.toISOString(), criteria };
29749
30352
  }
29750
30353
  async function assertApprovedPrd(path, approved) {
29751
- const current = await readFile32(path, "utf8");
30354
+ const current = await readFile33(path, "utf8");
29752
30355
  if (hashPrd(current) !== approved.hash) {
29753
30356
  throw new Error("PRD_LOCK_VIOLATION: confirmed PRD content changed after approval");
29754
30357
  }
29755
30358
  return approved;
29756
30359
  }
29757
30360
  async function updatePrdSectionFile(path, input) {
29758
- const current = await readFile32(path, "utf8");
30361
+ const current = await readFile33(path, "utf8");
29759
30362
  if (hashPrd(current) !== input.expectedHash) {
29760
30363
  throw new Error("PRD_EDIT_CONFLICT: the PRD changed; refresh before saving this section");
29761
30364
  }
@@ -29821,13 +30424,13 @@ var PlanSchema2 = z44.object({
29821
30424
  }).strict();
29822
30425
  function d2cProductDirectory(workspace, task) {
29823
30426
  if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
29824
- return join42(taskDir(workspace, task), "product");
30427
+ return join43(taskDir(workspace, task), "product");
29825
30428
  }
29826
30429
  function d2cProductPrototypeDirectory(workspace, task) {
29827
- return join42(d2cProductDirectory(workspace, task), "prototype");
30430
+ return join43(d2cProductDirectory(workspace, task), "prototype");
29828
30431
  }
29829
30432
  function planPath(workspace, task) {
29830
- return join42(taskDir(workspace, task), "product-plan.json");
30433
+ return join43(taskDir(workspace, task), "product-plan.json");
29831
30434
  }
29832
30435
  function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
29833
30436
  const frontendCandidates = [
@@ -29915,18 +30518,18 @@ function buildD2cProductOpenApi(task, modules) {
29915
30518
  `;
29916
30519
  }
29917
30520
  async function fileTimestamp(path) {
29918
- const info = await stat14(path).catch(() => void 0);
30521
+ const info = await stat15(path).catch(() => void 0);
29919
30522
  return info?.isFile() ? info.mtime.toISOString() : void 0;
29920
30523
  }
29921
30524
  async function fileHash(path) {
29922
- const buffer = await readFile33(path).catch((error) => {
30525
+ const buffer = await readFile34(path).catch((error) => {
29923
30526
  if (error.code === "ENOENT") return void 0;
29924
30527
  throw error;
29925
30528
  });
29926
30529
  return buffer === void 0 ? void 0 : createHash24("sha256").update(buffer).digest("hex");
29927
30530
  }
29928
30531
  async function validateAndNormalizePrototypeManifest(path) {
29929
- const raw = await readFile33(path, "utf8").catch((error) => {
30532
+ const raw = await readFile34(path, "utf8").catch((error) => {
29930
30533
  if (error.code === "ENOENT") return void 0;
29931
30534
  throw error;
29932
30535
  });
@@ -30002,7 +30605,7 @@ async function createD2cProductPlan(workspace, input, now = () => /* @__PURE__ *
30002
30605
  });
30003
30606
  }
30004
30607
  async function readD2cProductPlan(workspace, task) {
30005
- const raw = await readFile33(planPath(workspace, task), "utf8").catch((error) => {
30608
+ const raw = await readFile34(planPath(workspace, task), "utf8").catch((error) => {
30006
30609
  if (error.code === "ENOENT") return void 0;
30007
30610
  throw error;
30008
30611
  });
@@ -30010,16 +30613,16 @@ async function readD2cProductPlan(workspace, task) {
30010
30613
  }
30011
30614
  async function discoverD2cProductArtifacts(workspace, plan) {
30012
30615
  const product = d2cProductDirectory(workspace, plan.task);
30013
- const prdUpdatedAt = await fileTimestamp(join42(product, "prd.md"));
30014
- const prdHash = await fileHash(join42(product, "prd.md"));
30015
- const prototypeUpdatedAt = await fileTimestamp(join42(product, "prototype", "index.html"));
30016
- const prototypeHash = await fileHash(join42(product, "prototype", "index.html"));
30017
- const expectedManifest = join42(product, "prototype", "interaction-manifest.json");
30616
+ const prdUpdatedAt = await fileTimestamp(join43(product, "prd.md"));
30617
+ const prdHash = await fileHash(join43(product, "prd.md"));
30618
+ const prototypeUpdatedAt = await fileTimestamp(join43(product, "prototype", "index.html"));
30619
+ const prototypeHash = await fileHash(join43(product, "prototype", "index.html"));
30620
+ const expectedManifest = join43(product, "prototype", "interaction-manifest.json");
30018
30621
  let manifestUpdatedAt = await fileTimestamp(expectedManifest);
30019
30622
  if (manifestUpdatedAt === void 0) {
30020
- const misplacedManifest = join42(product, "interaction-manifest.json");
30623
+ const misplacedManifest = join43(product, "interaction-manifest.json");
30021
30624
  if (await fileTimestamp(misplacedManifest) !== void 0) {
30022
- await mkdir25(join42(product, "prototype"), { recursive: true });
30625
+ await mkdir25(join43(product, "prototype"), { recursive: true });
30023
30626
  await copyFile2(misplacedManifest, expectedManifest);
30024
30627
  manifestUpdatedAt = await fileTimestamp(expectedManifest);
30025
30628
  }
@@ -30052,8 +30655,8 @@ async function readD2cProductPlanView(workspace, task) {
30052
30655
  if (stored === void 0) return void 0;
30053
30656
  const plan = await discoverD2cProductArtifacts(workspace, stored);
30054
30657
  if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
30055
- const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile33(join42(taskDir(workspace, task), plan.prd.path), "utf8");
30056
- const manifestPath = join42(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
30658
+ const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile34(join43(taskDir(workspace, task), plan.prd.path), "utf8");
30659
+ const manifestPath = join43(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
30057
30660
  const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
30058
30661
  return {
30059
30662
  plan,
@@ -30143,9 +30746,9 @@ ${prdMarkdown.slice(0, 2e4)}`
30143
30746
 
30144
30747
  // src/e2e/acceptance-baseline.ts
30145
30748
  import { createHash as createHash25 } from "crypto";
30146
- import { readFile as readFile34 } from "fs/promises";
30749
+ import { readFile as readFile35 } from "fs/promises";
30147
30750
  async function fileArtifact(path) {
30148
- const content = await readFile34(path);
30751
+ const content = await readFile35(path);
30149
30752
  return { path, hash: createHash25("sha256").update(content).digest("hex"), bytes: content.byteLength };
30150
30753
  }
30151
30754
  async function verifyAcceptanceArtifact(path, expected, label) {
@@ -30212,7 +30815,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
30212
30815
 
30213
30816
  // src/e2e/delivery-run.ts
30214
30817
  import { createHash as createHash26 } from "crypto";
30215
- import { join as join43 } from "path";
30818
+ import { join as join44 } from "path";
30216
30819
  import { z as z45 } from "zod";
30217
30820
  var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
30218
30821
  var ArtifactSchema = z45.object({
@@ -30329,7 +30932,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
30329
30932
  return { ...run, nodes, updatedAt: timestamp2 };
30330
30933
  }
30331
30934
  function deliveryRunPath(workspace, task) {
30332
- return join43(taskDir(workspace, task), "delivery-run.json");
30935
+ return join44(taskDir(workspace, task), "delivery-run.json");
30333
30936
  }
30334
30937
  function decodeDeliveryRun(raw) {
30335
30938
  return DeliveryRunSchema.parse(JSON.parse(raw));
@@ -30370,9 +30973,9 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
30370
30973
  }
30371
30974
 
30372
30975
  // src/skills/manager.ts
30373
- import { lstat as lstat14, mkdir as mkdir26, readFile as readFile35, readdir as readdir12, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
30976
+ import { lstat as lstat14, mkdir as mkdir26, readFile as readFile36, readdir as readdir12, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
30374
30977
  import { homedir as homedir4 } from "os";
30375
- import { isAbsolute as isAbsolute19, join as join44, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
30978
+ import { isAbsolute as isAbsolute19, join as join45, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
30376
30979
  import { stringify } from "yaml";
30377
30980
  var SkillManager = class {
30378
30981
  #workspace;
@@ -30395,7 +30998,7 @@ var SkillManager = class {
30395
30998
  async create(draft) {
30396
30999
  const value = parseDraft(draft);
30397
31000
  const projectRoot = this.#projectRoot();
30398
- const directory = join44(projectRoot, value.name);
31001
+ const directory = join45(projectRoot, value.name);
30399
31002
  await mkdir26(projectRoot, { recursive: true, mode: 448 });
30400
31003
  try {
30401
31004
  await mkdir26(directory, { mode: 448 });
@@ -30404,7 +31007,7 @@ var SkillManager = class {
30404
31007
  throw error;
30405
31008
  }
30406
31009
  try {
30407
- await writeFile16(join44(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
31010
+ await writeFile16(join45(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
30408
31011
  await this.#persistEnabled(value.name, true);
30409
31012
  return this.get(value.name);
30410
31013
  } catch (error) {
@@ -30422,7 +31025,7 @@ var SkillManager = class {
30422
31025
  throw error;
30423
31026
  });
30424
31027
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
30425
- const skillFile = join44(directory, "SKILL.md");
31028
+ const skillFile = join45(directory, "SKILL.md");
30426
31029
  const fileInfo = await lstat14(skillFile);
30427
31030
  if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
30428
31031
  await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
@@ -30454,12 +31057,12 @@ var SkillManager = class {
30454
31057
  async #registry() {
30455
31058
  const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
30456
31059
  const [globalPluginRoots, projectPluginRoots] = await Promise.all([
30457
- discoverPluginSkillRoots(join44(this.#home, ".flavor-code", "plugins")),
30458
- discoverPluginSkillRoots(join44(this.#workspace, ".flavor", "plugins"))
31060
+ discoverPluginSkillRoots(join45(this.#home, ".flavor-code", "plugins")),
31061
+ discoverPluginSkillRoots(join45(this.#workspace, ".flavor", "plugins"))
30459
31062
  ]);
30460
31063
  return {
30461
31064
  registry: new SkillRegistry({
30462
- globalRoots: [join44(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
31065
+ globalRoots: [join45(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
30463
31066
  projectRoots: [this.#projectRoot(), ...projectPluginRoots]
30464
31067
  }),
30465
31068
  disabled: new Set(loaded.config.skills.disabled)
@@ -30475,10 +31078,10 @@ var SkillManager = class {
30475
31078
  };
30476
31079
  }
30477
31080
  #projectRoot() {
30478
- return join44(this.#workspace, ".flavor", "skills");
31081
+ return join45(this.#workspace, ".flavor", "skills");
30479
31082
  }
30480
31083
  #projectSkillDirectory(name) {
30481
- return join44(this.#projectRoot(), name);
31084
+ return join45(this.#projectRoot(), name);
30482
31085
  }
30483
31086
  };
30484
31087
  function parseName(value) {
@@ -30519,9 +31122,9 @@ async function discoverPluginSkillRoots(directory) {
30519
31122
  const roots = [];
30520
31123
  for (const entry2 of entries) {
30521
31124
  if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
30522
- const pluginRoot = await realpath13(join44(directory, entry2.name));
31125
+ const pluginRoot = await realpath13(join45(directory, entry2.name));
30523
31126
  try {
30524
- const manifest = PluginManifestSchema.parse(JSON.parse(await readFile35(join44(pluginRoot, "flavor-plugin.json"), "utf8")));
31127
+ const manifest = PluginManifestSchema.parse(JSON.parse(await readFile36(join45(pluginRoot, "flavor-plugin.json"), "utf8")));
30525
31128
  for (const contribution of manifest.contributes.skillRoots) {
30526
31129
  const candidate = resolve37(pluginRoot, contribution.path);
30527
31130
  const info = await lstat14(candidate);
@@ -30596,11 +31199,11 @@ async function createProjectMemoryManager(options) {
30596
31199
  }
30597
31200
 
30598
31201
  // src/mcp/config-manager.ts
30599
- import { join as join45 } from "path";
31202
+ import { join as join46 } from "path";
30600
31203
  var ProjectMcpConfigManager = class {
30601
31204
  path;
30602
31205
  constructor(workspace) {
30603
- this.path = join45(workspace, ".flavor", "flavor.json");
31206
+ this.path = join46(workspace, ".flavor", "flavor.json");
30604
31207
  }
30605
31208
  async list() {
30606
31209
  const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
@@ -30697,7 +31300,7 @@ function isRecord7(value) {
30697
31300
  }
30698
31301
 
30699
31302
  // src/desktop/model-config.ts
30700
- import { join as join46 } from "path";
31303
+ import { join as join47 } from "path";
30701
31304
  var DEFAULT_DESKTOP_MODELS = [
30702
31305
  {
30703
31306
  id: "anthropic:deepseek-v4-pro",
@@ -30718,7 +31321,7 @@ var DEFAULT_DESKTOP_MODELS = [
30718
31321
  ];
30719
31322
  async function loadDesktopModels(workspace, home) {
30720
31323
  try {
30721
- const tokens2 = await createFileTokenStore(join46(home, ".flavor-code", "auth.json")).load();
31324
+ const tokens2 = await createFileTokenStore(join47(home, ".flavor-code", "auth.json")).load();
30722
31325
  const pkceModels = [];
30723
31326
  const knownPkce = /* @__PURE__ */ new Set();
30724
31327
  for (const token of Object.values(tokens2)) {
@@ -30786,7 +31389,7 @@ function protocolLabel(type) {
30786
31389
 
30787
31390
  // src/desktop/d2c-product-preview.ts
30788
31391
  import { createReadStream as createReadStream2 } from "fs";
30789
- import { realpath as realpath14, stat as stat15 } from "fs/promises";
31392
+ import { realpath as realpath14, stat as stat16 } from "fs/promises";
30790
31393
  import { createServer as createServer6 } from "http";
30791
31394
  import { extname as extname4, isAbsolute as isAbsolute20, relative as relative27, resolve as resolve38, sep as sep28 } from "path";
30792
31395
  var MIME_TYPES = {
@@ -30811,7 +31414,7 @@ function isInside(root, candidate) {
30811
31414
  }
30812
31415
  async function startD2cProductPreview(rootDirectory) {
30813
31416
  const root = await realpath14(resolve38(rootDirectory));
30814
- const rootInfo = await stat15(root);
31417
+ const rootInfo = await stat16(root);
30815
31418
  if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
30816
31419
  const server = createServer6(async (request, response2) => {
30817
31420
  response2.setHeader("cache-control", "no-store");
@@ -30826,7 +31429,7 @@ async function startD2cProductPreview(rootDirectory) {
30826
31429
  if (!isInside(root, unresolved)) return sendNotFound(response2);
30827
31430
  const target = await realpath14(unresolved).catch(() => void 0);
30828
31431
  if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
30829
- const info = await stat15(target);
31432
+ const info = await stat16(target);
30830
31433
  if (!info.isFile()) return sendNotFound(response2);
30831
31434
  response2.writeHead(200, {
30832
31435
  "content-length": info.size,
@@ -30863,33 +31466,33 @@ function pngDataUrl(png) {
30863
31466
  }
30864
31467
  async function detectD2cFramework(workspace, task) {
30865
31468
  try {
30866
- const pkg = JSON.parse(await readFile36(join47(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
31469
+ const pkg = JSON.parse(await readFile37(join48(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
30867
31470
  return pkg.dependencies?.react === void 0 ? "vue" : "react";
30868
31471
  } catch {
30869
31472
  return "vue";
30870
31473
  }
30871
31474
  }
30872
31475
  function integrationDirectory(workspace, task) {
30873
- return join47(workspace, ".flavor", "d2c", task, "integration");
31476
+ return join48(workspace, ".flavor", "d2c", task, "integration");
30874
31477
  }
30875
31478
  var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
30876
31479
  var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
30877
31480
  async function d2cBackendSourceFingerprint(project) {
30878
- const runtimeManifest = join47(project, "server", "flavor-runtime.json");
30879
- const realBackend = (await stat16(runtimeManifest).catch(() => void 0))?.isFile() === true;
30880
- const root = realBackend ? join47(project, "server") : join47(project, "mock");
30881
- if ((await stat16(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
31481
+ const runtimeManifest = join48(project, "server", "flavor-runtime.json");
31482
+ const realBackend = (await stat17(runtimeManifest).catch(() => void 0))?.isFile() === true;
31483
+ const root = realBackend ? join48(project, "server") : join48(project, "mock");
31484
+ if ((await stat17(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
30882
31485
  const files = [];
30883
31486
  const visit = async (directory) => {
30884
31487
  const entries = await readdir13(directory, { withFileTypes: true });
30885
31488
  for (const entry2 of entries) {
30886
31489
  if (entry2.isDirectory()) {
30887
- if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join47(directory, entry2.name));
31490
+ if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join48(directory, entry2.name));
30888
31491
  continue;
30889
31492
  }
30890
31493
  if (!entry2.isFile()) continue;
30891
31494
  const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
30892
- if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join47(directory, entry2.name));
31495
+ if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join48(directory, entry2.name));
30893
31496
  }
30894
31497
  };
30895
31498
  await visit(root);
@@ -30897,7 +31500,7 @@ async function d2cBackendSourceFingerprint(project) {
30897
31500
  for (const path of files.sort()) {
30898
31501
  hash.update(path.slice(root.length));
30899
31502
  hash.update("\0");
30900
- hash.update(await readFile36(path));
31503
+ hash.update(await readFile37(path));
30901
31504
  hash.update("\0");
30902
31505
  }
30903
31506
  return hash.digest("hex");
@@ -30933,6 +31536,7 @@ var DesktopRuntimeController = class {
30933
31536
  #busy = false;
30934
31537
  #tasks;
30935
31538
  #disposeJobSubscription;
31539
+ #memoryRestartPromise;
30936
31540
  #d2cMocks = /* @__PURE__ */ new Map();
30937
31541
  #d2cPreviews = /* @__PURE__ */ new Map();
30938
31542
  #d2cProductPreviews = /* @__PURE__ */ new Map();
@@ -31059,6 +31663,7 @@ var DesktopRuntimeController = class {
31059
31663
  this.#captureSessionOutput(event);
31060
31664
  this.#emit({ type: "session-output", sessionId: outputSessionId, event });
31061
31665
  },
31666
+ onMemoryRestartRequested: (sessionId) => this.#scheduleMemoryRestart(sessionId),
31062
31667
  onApprovalChange: () => {
31063
31668
  if (this.#runtime !== void 0) this.#publishSnapshot();
31064
31669
  },
@@ -31443,8 +32048,8 @@ var DesktopRuntimeController = class {
31443
32048
  if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
31444
32049
  throw new Error("Complete visual review before importing OpenAPI");
31445
32050
  }
31446
- const moduleManifest = join47(d2cOutputDirectory(workspace, task), "d2c.modules.json");
31447
- const moduleManifestInfo = await stat16(moduleManifest).catch(() => void 0);
32051
+ const moduleManifest = join48(d2cOutputDirectory(workspace, task), "d2c.modules.json");
32052
+ const moduleManifestInfo = await stat17(moduleManifest).catch(() => void 0);
31448
32053
  if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
31449
32054
  throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
31450
32055
  }
@@ -31456,8 +32061,8 @@ var DesktopRuntimeController = class {
31456
32061
  await mkdir27(dir, { recursive: true });
31457
32062
  const hash = createHash27("sha256").update(raw).digest("hex");
31458
32063
  await Promise.all([
31459
- writeFile17(join47(dir, "swagger.json"), raw),
31460
- writeFile17(join47(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
32064
+ writeFile17(join48(dir, "swagger.json"), raw),
32065
+ writeFile17(join48(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
31461
32066
  `)
31462
32067
  ]);
31463
32068
  const next = {
@@ -31481,9 +32086,9 @@ var DesktopRuntimeController = class {
31481
32086
  const workspace = this.#workspace;
31482
32087
  if (workspace === void 0) throw new Error("Open a project before importing OpenAPI");
31483
32088
  await this.#assertProductPrdLocked(workspace, task);
31484
- const info = await stat16(sourcePath);
32089
+ const info = await stat17(sourcePath);
31485
32090
  if (!info.isFile() || info.size > 8 * 1024 * 1024) throw new Error("OpenAPI JSON must be a file no larger than 8 MiB");
31486
- return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await readFile36(sourcePath, "utf8"), false);
32091
+ return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await readFile37(sourcePath, "utf8"), false);
31487
32092
  }
31488
32093
  async getD2cIntegration(task) {
31489
32094
  const workspace = this.#workspace;
@@ -31492,8 +32097,8 @@ var DesktopRuntimeController = class {
31492
32097
  let workflow = await readWorkflow(workspace, task);
31493
32098
  if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
31494
32099
  const modules = await readD2cModules(workspace, task);
31495
- const productOpenApiPath = join47(taskDir(workspace, task), "product", "openapi.json");
31496
- const authored = await readFile36(productOpenApiPath, "utf8").catch(() => void 0);
32100
+ const productOpenApiPath = join48(taskDir(workspace, task), "product", "openapi.json");
32101
+ const authored = await readFile37(productOpenApiPath, "utf8").catch(() => void 0);
31497
32102
  const generated = authored === void 0;
31498
32103
  const prepared = await this.#storeD2cOpenApi(
31499
32104
  workspace,
@@ -31505,7 +32110,7 @@ var DesktopRuntimeController = class {
31505
32110
  return prepared;
31506
32111
  }
31507
32112
  if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
31508
- const document = JSON.parse(await readFile36(join47(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
32113
+ const document = JSON.parse(await readFile37(join48(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
31509
32114
  return { workflow, document, mappings: workflow.mappings };
31510
32115
  }
31511
32116
  async confirmD2cMapping(task, moduleId, operationKey) {
@@ -31572,7 +32177,7 @@ var DesktopRuntimeController = class {
31572
32177
  ].join("\n");
31573
32178
  const delivery = await readDeliveryRun(workspace, task);
31574
32179
  if (delivery?.nodes.api.status === "running") {
31575
- const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile36(join47(project, file)))));
32180
+ const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile37(join48(project, file)))));
31576
32181
  const completed = await updateDeliveryRun(
31577
32182
  workspace,
31578
32183
  task,
@@ -31599,8 +32204,8 @@ var DesktopRuntimeController = class {
31599
32204
  try {
31600
32205
  const fingerprint2 = await d2cBackendSourceFingerprint(project);
31601
32206
  if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
31602
- const environmentPath = join47(project, ".env.local");
31603
- const existingEnvironment = await readFile36(environmentPath, "utf8").catch(() => "");
32207
+ const environmentPath = join48(project, ".env.local");
32208
+ const existingEnvironment = await readFile37(environmentPath, "utf8").catch(() => "");
31604
32209
  const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
31605
32210
  VITE_API_BASE_URL=${running.url}
31606
32211
  `.replace(/^\n/, "");
@@ -31680,8 +32285,8 @@ VITE_API_BASE_URL=${running.url}
31680
32285
  const readyPreview = this.#d2cPreviews.get(task);
31681
32286
  const readyMock = this.#d2cMocks.get(task);
31682
32287
  if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
31683
- const manifestPath = join47(taskDir(workspace, task), "design", "interaction-manifest.json");
31684
- const manifest = parseInteractionManifest(await readFile36(manifestPath, "utf8"));
32288
+ const manifestPath = join48(taskDir(workspace, task), "design", "interaction-manifest.json");
32289
+ const manifest = parseInteractionManifest(await readFile37(manifestPath, "utf8"));
31685
32290
  const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
31686
32291
  const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
31687
32292
  let executableManifest = manifest;
@@ -31692,7 +32297,7 @@ VITE_API_BASE_URL=${running.url}
31692
32297
  await mkdir27(planDir, { recursive: true });
31693
32298
  try {
31694
32299
  const observations = await this.#observeD2cPages(manifest, readyPreview.url);
31695
- const prd = await readFile36(join47(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
32300
+ const prd = await readFile37(join48(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
31696
32301
  const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
31697
32302
  const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
31698
32303
  executableManifest = mergeInteractionManifests(manifest, plan.manifest);
@@ -31703,9 +32308,9 @@ VITE_API_BASE_URL=${running.url}
31703
32308
  plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
31704
32309
  pageAnalyses: plan.pageAnalyses
31705
32310
  };
31706
- await writeFile17(join47(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
32311
+ await writeFile17(join48(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
31707
32312
  `, { encoding: "utf8", mode: 384 });
31708
- await rm15(join47(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
32313
+ await rm15(join48(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
31709
32314
  } catch (cause) {
31710
32315
  const detail = message(cause);
31711
32316
  review = {
@@ -31713,8 +32318,8 @@ VITE_API_BASE_URL=${running.url}
31713
32318
  plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
31714
32319
  warning: `\u591A\u6A21\u6001\u81EA\u4E3B\u89C4\u5212\u5931\u8D25\uFF0C\u5DF2\u7EE7\u7EED\u6267\u884C\u5DF2\u786E\u8BA4\u7684\u8BBE\u8BA1\u5951\u7EA6\uFF1A${detail}`
31715
32320
  };
31716
- await rm15(join47(planDir, "autonomous-interaction-plan.json"), { force: true });
31717
- await writeFile17(join47(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
32321
+ await rm15(join48(planDir, "autonomous-interaction-plan.json"), { force: true });
32322
+ await writeFile17(join48(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
31718
32323
  schema: 1,
31719
32324
  stage: "planning",
31720
32325
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -31728,13 +32333,13 @@ VITE_API_BASE_URL=${running.url}
31728
32333
  let result = { ...rawResult, evidenceMode: review.mode };
31729
32334
  if (coverage !== void 0) {
31730
32335
  const evidence = createAcceptanceEvidence(coverage, result);
31731
- await writeFile17(join47(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
32336
+ await writeFile17(join48(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
31732
32337
  `, { encoding: "utf8", mode: 384 });
31733
32338
  if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
31734
32339
  }
31735
32340
  const dir = integrationDirectory(workspace, task);
31736
32341
  await mkdir27(dir, { recursive: true });
31737
- await writeFile17(join47(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
32342
+ await writeFile17(join48(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
31738
32343
  `);
31739
32344
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
31740
32345
  return { workflow: next, result, review };
@@ -31747,7 +32352,7 @@ VITE_API_BASE_URL=${running.url}
31747
32352
  if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
31748
32353
  const output = mock.output().trim();
31749
32354
  try {
31750
- const dir = join47(d2cOutputDirectory(workspace, task), "mock-server.log");
32355
+ const dir = join48(d2cOutputDirectory(workspace, task), "mock-server.log");
31751
32356
  await writeFile17(dir, `${output}
31752
32357
  `);
31753
32358
  } catch {
@@ -31842,7 +32447,7 @@ VITE_API_BASE_URL=${running.url}
31842
32447
  if (current === void 0) throw new Error("D2C product plan is unavailable");
31843
32448
  if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
31844
32449
  if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
31845
- const path = join47(taskDir(workspace, task), "product", "prd.md");
32450
+ const path = join48(taskDir(workspace, task), "product", "prd.md");
31846
32451
  const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
31847
32452
  const now = (/* @__PURE__ */ new Date()).toISOString();
31848
32453
  await writeD2cProductPlan(workspace, {
@@ -31862,33 +32467,33 @@ VITE_API_BASE_URL=${running.url}
31862
32467
  async #assertProductPrdLocked(workspace, task) {
31863
32468
  const current = await readD2cProductPlanView(workspace, task);
31864
32469
  if (current?.plan.approvedPrd === void 0) return;
31865
- await assertApprovedPrd(join47(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
32470
+ await assertApprovedPrd(join48(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
31866
32471
  }
31867
32472
  async #acceptancePaths(workspace, task) {
31868
- const product = join47(taskDir(workspace, task), "product");
31869
- const openapi = join47(product, "openapi.json");
32473
+ const product = join48(taskDir(workspace, task), "product");
32474
+ const openapi = join48(product, "openapi.json");
31870
32475
  return {
31871
- prd: join47(product, "prd.md"),
31872
- prototype: join47(product, "prototype", "index.html"),
31873
- interaction: join47(product, "prototype", "interaction-manifest.json"),
31874
- ...await stat16(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
32476
+ prd: join48(product, "prd.md"),
32477
+ prototype: join48(product, "prototype", "index.html"),
32478
+ interaction: join48(product, "prototype", "interaction-manifest.json"),
32479
+ ...await stat17(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
31875
32480
  };
31876
32481
  }
31877
32482
  async #verifyAcceptanceBaseline(workspace, task) {
31878
32483
  const product = await readD2cProductPlanView(workspace, task);
31879
32484
  if (product?.plan.approvedPrd === void 0) return void 0;
31880
32485
  await this.#assertProductPrdLocked(workspace, task);
31881
- const baseline = JSON.parse(await readFile36(join47(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
32486
+ const baseline = JSON.parse(await readFile37(join48(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
31882
32487
  await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
31883
- const design = join47(taskDir(workspace, task), "design");
32488
+ const design = join48(taskDir(workspace, task), "design");
31884
32489
  await Promise.all([
31885
- verifyAcceptanceArtifact(join47(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
31886
- verifyAcceptanceArtifact(join47(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
32490
+ verifyAcceptanceArtifact(join48(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
32491
+ verifyAcceptanceArtifact(join48(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
31887
32492
  ]);
31888
32493
  const workflow = await readWorkflow(workspace, task);
31889
32494
  if (workflow?.openapi !== void 0) {
31890
32495
  await verifyAcceptanceArtifact(
31891
- join47(taskDir(workspace, task), "integration", "swagger.json"),
32496
+ join48(taskDir(workspace, task), "integration", "swagger.json"),
31892
32497
  { hash: workflow.openapi.hash },
31893
32498
  "runtime OpenAPI contract"
31894
32499
  );
@@ -31909,14 +32514,14 @@ VITE_API_BASE_URL=${running.url}
31909
32514
  throw new Error("D2C PRD is empty and cannot be accepted");
31910
32515
  }
31911
32516
  if (stage === "design" && accepted) {
31912
- const manifest = join47(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
31913
- const normalizedManifest = parseInteractionManifest(await readFile36(manifest, "utf8"));
32517
+ const manifest = join48(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
32518
+ const normalizedManifest = parseInteractionManifest(await readFile37(manifest, "utf8"));
31914
32519
  if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
31915
32520
  verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
31916
32521
  await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
31917
32522
  `, { encoding: "utf8", mode: 384 });
31918
32523
  const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
31919
- await writeFile17(join47(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
32524
+ await writeFile17(join48(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
31920
32525
  `, { encoding: "utf8", mode: 384 });
31921
32526
  }
31922
32527
  const plan = await writeD2cProductPlan(
@@ -31950,8 +32555,8 @@ VITE_API_BASE_URL=${running.url}
31950
32555
  const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
31951
32556
  const delivery = await readDeliveryRun(workspace, task);
31952
32557
  if (delivery !== void 0) {
31953
- const prototype = await readFile36(join47(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
31954
- const interaction = await readFile36(join47(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
32558
+ const prototype = await readFile37(join48(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
32559
+ const interaction = await readFile37(join48(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
31955
32560
  const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
31956
32561
  artifactRef("product/prototype/index.html", prototype),
31957
32562
  artifactRef("product/prototype/interaction-manifest.json", interaction)
@@ -32035,10 +32640,10 @@ VITE_API_BASE_URL=${running.url}
32035
32640
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
32036
32641
  const qualityContent = `${JSON.stringify(judgment, null, 2)}
32037
32642
  `;
32038
- await writeFile17(join47(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
32643
+ await writeFile17(join48(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
32039
32644
  const delivery = await readDeliveryRun(workspace, task);
32040
32645
  if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
32041
- const interactionContent = await readFile36(join47(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
32646
+ const interactionContent = await readFile37(join48(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
32042
32647
  const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
32043
32648
  artifactRef("integration/interaction-results.json", interactionContent),
32044
32649
  artifactRef("quality-judge.json", qualityContent)
@@ -32120,6 +32725,39 @@ VITE_API_BASE_URL=${running.url}
32120
32725
  await runtime.session.close();
32121
32726
  await runtime.dispose();
32122
32727
  }
32728
+ /**
32729
+ * Electron owns the process, so a heap rotation rebuilds only the production
32730
+ * runtime. Disposing every holder and forcing GC gives the resumed /loop or
32731
+ * /goal a clean live set without terminating the desktop application.
32732
+ */
32733
+ #scheduleMemoryRestart(sessionId) {
32734
+ if (this.#memoryRestartPromise !== void 0) return;
32735
+ const previous = this.#runtime;
32736
+ this.#memoryRestartPromise = (async () => {
32737
+ await previous?.session.whenIdle?.().catch(() => void 0);
32738
+ if (previous === void 0 || this.#runtime !== previous) return;
32739
+ await this.#disposeRuntime();
32740
+ clearMemoryRotation();
32741
+ const gc = globalThis.gc;
32742
+ if (typeof gc === "function") {
32743
+ gc();
32744
+ gc();
32745
+ }
32746
+ await this.startSession(sessionId);
32747
+ const resumed = this.#runtime;
32748
+ if (resumed !== void 0) {
32749
+ this.#busy = true;
32750
+ this.#publishSnapshot();
32751
+ await resumed.session.whenIdle?.();
32752
+ }
32753
+ })().catch((error) => {
32754
+ this.#emit({ type: "runtime-error", sessionId, message: `Memory restart failed: ${message(error)}` });
32755
+ }).finally(() => {
32756
+ this.#memoryRestartPromise = void 0;
32757
+ this.#busy = false;
32758
+ this.#publishSnapshot();
32759
+ });
32760
+ }
32123
32761
  async #stopAllD2cMocks() {
32124
32762
  const running = [...this.#d2cMocks.values()];
32125
32763
  this.#d2cMocks.clear();
@@ -32237,7 +32875,7 @@ async function loginShellEnvironment(environment, platform = process.platform, d
32237
32875
  }
32238
32876
 
32239
32877
  // src/desktop/git-manager.ts
32240
- import { readFile as readFile37, rm as rm16 } from "fs/promises";
32878
+ import { readFile as readFile38, rm as rm16 } from "fs/promises";
32241
32879
  import { isAbsolute as isAbsolute21, relative as relative28, resolve as resolve40 } from "path";
32242
32880
  function failure(action, stderr, stdout) {
32243
32881
  return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
@@ -32269,7 +32907,7 @@ async function desktopGitDiff(workspace, path, staged = false) {
32269
32907
  if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
32270
32908
  if (file.untracked) {
32271
32909
  const target = safeWorkspacePath(workspace, path);
32272
- const value = await readFile37(target, "utf8");
32910
+ const value = await readFile38(target, "utf8");
32273
32911
  return value.length > 12e4 ? `${value.slice(0, 12e4)}
32274
32912
  \u2026[\u5185\u5BB9\u5DF2\u622A\u65AD]` : value;
32275
32913
  }
@@ -32331,13 +32969,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
32331
32969
  return path.replaceAll("\\", "/");
32332
32970
  }));
32333
32971
  try {
32334
- const tree = JSON.parse(await readFile37(resolve40(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
32972
+ const tree = JSON.parse(await readFile38(resolve40(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
32335
32973
  const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
32336
32974
  const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
32337
32975
  if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
32338
32976
  const load = async (id) => {
32339
32977
  if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
32340
- return JSON.parse(await readFile37(resolve40(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
32978
+ return JSON.parse(await readFile38(resolve40(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
32341
32979
  };
32342
32980
  const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
32343
32981
  const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
@@ -32346,7 +32984,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
32346
32984
  const object = async (digest) => {
32347
32985
  if (digest === void 0) return "";
32348
32986
  if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
32349
- const value = await readFile37(resolve40(workspace, ".flavor", "checkpoints", "objects", digest));
32987
+ const value = await readFile38(resolve40(workspace, ".flavor", "checkpoints", "objects", digest));
32350
32988
  return value.includes(0) ? "[binary content]" : value.toString("utf8");
32351
32989
  };
32352
32990
  const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
@@ -32385,8 +33023,8 @@ function safeGitTarget(value) {
32385
33023
 
32386
33024
  // src/desktop/worktree-manager.ts
32387
33025
  import { randomUUID as randomUUID28 } from "crypto";
32388
- import { mkdir as mkdir28, readFile as readFile38, readdir as readdir14, rm as rm17, stat as stat17, writeFile as writeFile18 } from "fs/promises";
32389
- import { basename as basename11, isAbsolute as isAbsolute22, join as join48, relative as relative29, resolve as resolve41, sep as sep29 } from "path";
33026
+ import { mkdir as mkdir28, readFile as readFile39, readdir as readdir14, rm as rm17, stat as stat18, writeFile as writeFile18 } from "fs/promises";
33027
+ import { basename as basename11, isAbsolute as isAbsolute22, join as join49, relative as relative29, resolve as resolve41, sep as sep29 } from "path";
32390
33028
  var DesktopWorktreeManager = class {
32391
33029
  #repository;
32392
33030
  #storage;
@@ -32432,8 +33070,8 @@ var DesktopWorktreeManager = class {
32432
33070
  }
32433
33071
  const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
32434
33072
  try {
32435
- const stored = validateStored(JSON.parse(await readFile38(join48(this.#storage, name), "utf8")), this.#repository, this.#storage);
32436
- if ((await stat17(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
33073
+ const stored = validateStored(JSON.parse(await readFile39(join49(this.#storage, name), "utf8")), this.#repository, this.#storage);
33074
+ if ((await stat18(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
32437
33075
  const status = await git2(stored.path, ["status", "--porcelain"]);
32438
33076
  const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
32439
33077
  const { version: _version, ...item } = stored;
@@ -32466,7 +33104,7 @@ var DesktopWorktreeManager = class {
32466
33104
  }
32467
33105
  async #load(id) {
32468
33106
  const path = this.#metadataPath(id);
32469
- return validateStored(JSON.parse(await readFile38(path, "utf8")), this.#repository, this.#storage);
33107
+ return validateStored(JSON.parse(await readFile39(path, "utf8")), this.#repository, this.#storage);
32470
33108
  }
32471
33109
  #metadataPath(id) {
32472
33110
  if (!/^worktree-[a-z0-9._-]{1,80}$/.test(id)) throw new Error("Invalid worktree id");
@@ -32494,9 +33132,9 @@ function validateStored(value, repository, storage) {
32494
33132
  }
32495
33133
 
32496
33134
  // src/desktop/workbench-service.ts
32497
- import { readFile as readFile39, readdir as readdir15, stat as stat18 } from "fs/promises";
33135
+ import { readFile as readFile40, readdir as readdir15, stat as stat19 } from "fs/promises";
32498
33136
  import { homedir as homedir6 } from "os";
32499
- import { join as join49, resolve as resolve42 } from "path";
33137
+ import { join as join50, resolve as resolve42 } from "path";
32500
33138
  var MAX_TEXT = 128e3;
32501
33139
  var MAX_ITEMS = 200;
32502
33140
  var DesktopWorkbenchService = class {
@@ -32529,11 +33167,11 @@ var DesktopWorkbenchService = class {
32529
33167
  return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
32530
33168
  }
32531
33169
  async #goals() {
32532
- const root = join49(this.#workspace, ".flavor", "goals");
33170
+ const root = join50(this.#workspace, ".flavor", "goals");
32533
33171
  const names = await readdir15(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
32534
33172
  const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
32535
33173
  try {
32536
- return GoalStateSchema.parse(JSON.parse(await boundedRead2(join49(root, name))));
33174
+ return GoalStateSchema.parse(JSON.parse(await boundedRead2(join50(root, name))));
32537
33175
  } catch {
32538
33176
  return void 0;
32539
33177
  }
@@ -32543,7 +33181,7 @@ var DesktopWorkbenchService = class {
32543
33181
  async #instructions() {
32544
33182
  const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
32545
33183
  const items = await Promise.all(names.map(async (name) => {
32546
- const path = join49(this.#workspace, name);
33184
+ const path = join50(this.#workspace, name);
32547
33185
  try {
32548
33186
  return { name, path, content: redactErrorText(await boundedRead2(path)) };
32549
33187
  } catch (error) {
@@ -32556,9 +33194,9 @@ var DesktopWorkbenchService = class {
32556
33194
  async #permissionFiles() {
32557
33195
  const candidates = [
32558
33196
  ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve42(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
32559
- { tier: "user", path: join49(this.#home, ".flavor-code", "permissions.json"), content: "" },
32560
- { tier: "project", path: join49(this.#workspace, ".flavor", "permissions.json"), content: "" },
32561
- { tier: "local", path: join49(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
33197
+ { tier: "user", path: join50(this.#home, ".flavor-code", "permissions.json"), content: "" },
33198
+ { tier: "project", path: join50(this.#workspace, ".flavor", "permissions.json"), content: "" },
33199
+ { tier: "local", path: join50(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
32562
33200
  ];
32563
33201
  const values = await Promise.all(candidates.map(async (item) => {
32564
33202
  try {
@@ -32573,7 +33211,7 @@ var DesktopWorkbenchService = class {
32573
33211
  async #audit() {
32574
33212
  let raw;
32575
33213
  try {
32576
- raw = await boundedRead2(join49(this.#workspace, ".flavor", "audit.jsonl"));
33214
+ raw = await boundedRead2(join50(this.#workspace, ".flavor", "audit.jsonl"));
32577
33215
  } catch (error) {
32578
33216
  if (error.code === "ENOENT") return [];
32579
33217
  throw error;
@@ -32588,7 +33226,7 @@ var DesktopWorkbenchService = class {
32588
33226
  }).reverse();
32589
33227
  }
32590
33228
  async #context(sessionId) {
32591
- const usage = await readJsonLines2(join49(this.#workspace, ".flavor", "usage.jsonl"));
33229
+ const usage = await readJsonLines2(join50(this.#workspace, ".flavor", "usage.jsonl"));
32592
33230
  if (sessionId === void 0) return { visibility: [], usage };
32593
33231
  try {
32594
33232
  const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
@@ -32599,10 +33237,10 @@ var DesktopWorkbenchService = class {
32599
33237
  }
32600
33238
  };
32601
33239
  async function boundedRead2(path) {
32602
- const info = await stat18(path);
33240
+ const info = await stat19(path);
32603
33241
  if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
32604
33242
  if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
32605
- return readFile39(path, "utf8");
33243
+ return readFile40(path, "utf8");
32606
33244
  }
32607
33245
  async function readJsonLines2(path) {
32608
33246
  try {
@@ -32635,7 +33273,7 @@ var appMenu;
32635
33273
  var quitting = false;
32636
33274
  function logStartup(step, detail) {
32637
33275
  try {
32638
- const logPath = join50(dirname23(process.execPath), "flavor-code-startup.log");
33276
+ const logPath = join51(dirname23(process.execPath), "flavor-code-startup.log");
32639
33277
  const ts = (/* @__PURE__ */ new Date()).toISOString();
32640
33278
  appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
32641
33279
  `);
@@ -32660,7 +33298,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
32660
33298
  };
32661
33299
  });
32662
33300
  var d2cJudgeClient = createD2cJudgeClient();
32663
- var judgeStore = () => createD2cJudgeConfigStore(join50(app.getPath("userData"), "d2c-judge.json"));
33301
+ var judgeStore = () => createD2cJudgeConfigStore(join51(app.getPath("userData"), "d2c-judge.json"));
32664
33302
  function judgeImage(png) {
32665
33303
  const image = nativeImage.createFromBuffer(png);
32666
33304
  const size = image.getSize();
@@ -32686,7 +33324,7 @@ function sessionMetaKey(workspace, sessionId) {
32686
33324
  }
32687
33325
  function worktreeManager(workspace) {
32688
33326
  const key = createHash28("sha256").update(resolve43(workspace)).digest("hex").slice(0, 20);
32689
- return new DesktopWorktreeManager({ repository: workspace, storage: join50(app.getPath("userData"), "task-worktrees", key) });
33327
+ return new DesktopWorktreeManager({ repository: workspace, storage: join51(app.getPath("userData"), "task-worktrees", key) });
32690
33328
  }
32691
33329
  function activeWorkingDirectory() {
32692
33330
  if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
@@ -32831,7 +33469,7 @@ function createDesktopController(workspace, owner2) {
32831
33469
  alias: `desktop-${basename12(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
32832
33470
  optional: true,
32833
33471
  startBroker: (address) => {
32834
- const entry2 = app.isPackaged ? join50(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join50(moduleDirectory, "pals-broker-entry.js");
33472
+ const entry2 = app.isPackaged ? join51(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join51(moduleDirectory, "pals-broker-entry.js");
32835
33473
  return spawnPalBrokerProcess({
32836
33474
  address,
32837
33475
  executable: process.execPath,
@@ -32853,11 +33491,11 @@ function createDesktopController(workspace, owner2) {
32853
33491
  var detachedController = createDesktopController();
32854
33492
  var controller = detachedController;
32855
33493
  function statePath() {
32856
- return join50(app.getPath("userData"), "desktop-state.json");
33494
+ return join51(app.getPath("userData"), "desktop-state.json");
32857
33495
  }
32858
33496
  async function loadPersistedProjects() {
32859
33497
  try {
32860
- const raw = await readFile40(statePath(), "utf8");
33498
+ const raw = await readFile41(statePath(), "utf8");
32861
33499
  if (raw.length > 1e5) return { projects: [] };
32862
33500
  const parsed = JSON.parse(raw);
32863
33501
  Object.assign(workbench, {
@@ -32898,7 +33536,7 @@ function validRecord(value) {
32898
33536
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
32899
33537
  }
32900
33538
  async function assertDirectory(path) {
32901
- if (!(await stat19(path)).isDirectory()) throw new Error("The selected project is not a directory");
33539
+ if (!(await stat20(path)).isDirectory()) throw new Error("The selected project is not a directory");
32902
33540
  }
32903
33541
  async function openWorkspace(path) {
32904
33542
  const workspace = resolve43(path);
@@ -33029,7 +33667,7 @@ async function chooseAndOpenWorkspace() {
33029
33667
  var cachedAppIconDataUrl;
33030
33668
  function appIconDataUrl() {
33031
33669
  if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
33032
- const icon = nativeImage.createFromPath(join50(app.getAppPath(), "assets", "icon.png"));
33670
+ const icon = nativeImage.createFromPath(join51(app.getAppPath(), "assets", "icon.png"));
33033
33671
  if (icon.isEmpty()) return void 0;
33034
33672
  cachedAppIconDataUrl = icon.toDataURL();
33035
33673
  return cachedAppIconDataUrl;
@@ -33447,7 +34085,7 @@ function applicationMenu() {
33447
34085
  ]);
33448
34086
  }
33449
34087
  async function createWindow() {
33450
- const rendererPath = join50(app.getAppPath(), "dist", "desktop-renderer", "index.html");
34088
+ const rendererPath = join51(app.getAppPath(), "dist", "desktop-renderer", "index.html");
33451
34089
  mainWindow = new BrowserWindow2({
33452
34090
  title: "Flavor Code",
33453
34091
  width: 1280,
@@ -33458,14 +34096,14 @@ async function createWindow() {
33458
34096
  show: false,
33459
34097
  ...desktopWindowChrome(),
33460
34098
  webPreferences: {
33461
- preload: join50(moduleDirectory, "preload.cjs"),
34099
+ preload: join51(moduleDirectory, "preload.cjs"),
33462
34100
  contextIsolation: true,
33463
34101
  nodeIntegration: false,
33464
34102
  sandbox: false,
33465
34103
  webSecurity: true
33466
34104
  }
33467
34105
  });
33468
- const iconPath = join50(app.getAppPath(), "assets", "icon.png");
34106
+ const iconPath = join51(app.getAppPath(), "assets", "icon.png");
33469
34107
  const icon = nativeImage.createFromPath(iconPath);
33470
34108
  if (!icon.isEmpty()) {
33471
34109
  mainWindow.setIcon(icon);