flavor-code 1.3.15 → 1.3.17

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.
@@ -2,7 +2,7 @@
2
2
  import { appendFileSync } from "fs";
3
3
  import { createHash as createHash28, randomUUID as randomUUID29 } from "crypto";
4
4
  import { readFile as readFile39, stat as stat18, writeFile as writeFile19 } from "fs/promises";
5
- import { basename as basename11, dirname as dirname24, join as join47, resolve as resolve40 } from "path";
5
+ import { basename as basename11, dirname as dirname25, join as join48, resolve as resolve40 } from "path";
6
6
  import { fileURLToPath as fileURLToPath4 } from "url";
7
7
  import { app, BrowserWindow as BrowserWindow2, clipboard, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
8
8
 
@@ -305,7 +305,7 @@ var FlavorConfigSchema = z2.object({
305
305
  context: z2.object({
306
306
  windowTokens: z2.number().int().positive().default(2e5),
307
307
  reservedOutputTokens: z2.number().int().nonnegative().default(2e4),
308
- autoCompactBufferTokens: z2.number().int().nonnegative().default(13e3),
308
+ autoCompactBufferTokens: z2.number().int().nonnegative().default(27e3),
309
309
  warningBufferTokens: z2.number().int().nonnegative().default(2e4),
310
310
  blockingBufferTokens: z2.number().int().nonnegative().default(3e3),
311
311
  microcompactKeepRecentToolResults: z2.number().int().nonnegative().default(5),
@@ -4225,9 +4225,10 @@ ${summarizeReport(report2)}`).join("\n\n")
4225
4225
 
4226
4226
  // src/production.ts
4227
4227
  import { readFile as readFile27 } from "fs/promises";
4228
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
4228
4229
  import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
4229
4230
  import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
4230
- import { basename as basename8, join as join33, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
4231
+ import { basename as basename8, dirname as dirname24, join as join34, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
4231
4232
 
4232
4233
  // src/agent/subagents.ts
4233
4234
  import { z as z10 } from "zod";
@@ -5161,7 +5162,9 @@ import { createHash as createHash4, randomUUID as randomUUID6 } from "crypto";
5161
5162
  var DEFAULT_COMPACTION_POLICY = {
5162
5163
  windowTokens: 2e5,
5163
5164
  reservedOutputTokens: 2e4,
5164
- autoCompactBufferTokens: 13e3,
5165
+ // 85% of the effective window (180_000 - 27_000 = 153_000): compaction used
5166
+ // to trigger at ~92.8%, which let sessions ride the threshold into heap OOM.
5167
+ autoCompactBufferTokens: 27e3,
5165
5168
  warningBufferTokens: 2e4,
5166
5169
  blockingBufferTokens: 3e3,
5167
5170
  microcompactKeepRecentToolResults: 5,
@@ -5371,6 +5374,13 @@ var ContextManager = class _ContextManager {
5371
5374
  #epoch;
5372
5375
  #visibilityLog = [];
5373
5376
  #activeTransientSystem = /* @__PURE__ */ new Map();
5377
+ /**
5378
+ * The visibility audit log persists every transient system admission and is
5379
+ * serialized in full on every session save. Bound both dimensions or a long
5380
+ * subagent-driven run grows it into hundreds of megabytes (heap OOM).
5381
+ */
5382
+ static VISIBILITY_LOG_MAX_ENTRIES = 1e3;
5383
+ static VISIBILITY_RECORD_CONTENT_LIMIT = 2048;
5374
5384
  constructor(options) {
5375
5385
  if (options.compactAtChars !== void 0 && options.compactAtChars <= 0) throw new Error("compactAtChars must be positive");
5376
5386
  if (options.toolOutputChars <= 0) throw new Error("toolOutputChars must be positive");
@@ -5466,11 +5476,14 @@ var ContextManager = class _ContextManager {
5466
5476
  const record3 = {
5467
5477
  id,
5468
5478
  role: "system",
5469
- content,
5479
+ content: limitVisibilityContent(content),
5470
5480
  admittedAt: (/* @__PURE__ */ new Date()).toISOString(),
5471
5481
  scope: "run"
5472
5482
  };
5473
5483
  this.#visibilityLog.push(record3);
5484
+ if (this.#visibilityLog.length > _ContextManager.VISIBILITY_LOG_MAX_ENTRIES) {
5485
+ this.#visibilityLog = this.#visibilityLog.slice(-_ContextManager.VISIBILITY_LOG_MAX_ENTRIES);
5486
+ }
5474
5487
  this.#activeTransientSystem.set(id, { role: "system", content });
5475
5488
  return id;
5476
5489
  }
@@ -5506,7 +5519,7 @@ var ContextManager = class _ContextManager {
5506
5519
  for (const key of DYNAMIC_SOURCE_ORDER) {
5507
5520
  const value = next[key];
5508
5521
  if (value === void 0 || value === this.#epoch.sources[key]) continue;
5509
- updates.push(contextUpdateMessage(key, displaySourceValue(key, value)));
5522
+ updates.push(contextUpdateMessage(key, deltaSourceValue(this.#epoch.sources[key], displaySourceValue(key, value))));
5510
5523
  this.#epoch.sources[key] = value;
5511
5524
  }
5512
5525
  for (const key of DYNAMIC_SOURCE_ORDER) {
@@ -5516,8 +5529,6 @@ var ContextManager = class _ContextManager {
5516
5529
  }
5517
5530
  if (updates.length === 0) return false;
5518
5531
  this.#messages.push(...updates);
5519
- this.#lastRecordedInputTokens = void 0;
5520
- this.#estimatedTokensAtLastRecordedUsage = void 0;
5521
5532
  return true;
5522
5533
  }
5523
5534
  snapshot() {
@@ -5533,7 +5544,7 @@ var ContextManager = class _ContextManager {
5533
5544
  const legacySummary = snapshot4.summary?.role === "system" && snapshot4.summary.content.startsWith("Conversation summary\n") ? snapshot4.summary.content.slice("Conversation summary\n".length) : void 0;
5534
5545
  this.#compact = snapshot4.compact === void 0 ? legacySummary === void 0 ? void 0 : { summary: legacySummary, compactedAt: (/* @__PURE__ */ new Date(0)).toISOString() } : { ...snapshot4.compact };
5535
5546
  if (snapshot4.epoch !== void 0) this.#epoch = cloneEpoch(snapshot4.epoch);
5536
- this.#visibilityLog = snapshot4.visibilityLog?.map((item) => ({ ...item })) ?? [];
5547
+ this.#visibilityLog = boundVisibilityLog(snapshot4.visibilityLog);
5537
5548
  this.#activeTransientSystem.clear();
5538
5549
  this.#messages = messages.map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
5539
5550
  this.#lastRecordedInputTokens = void 0;
@@ -5577,6 +5588,7 @@ var ContextManager = class _ContextManager {
5577
5588
  async prepareForModelCall(signal = new AbortController().signal) {
5578
5589
  signal.throwIfAborted();
5579
5590
  this.refreshContextSources();
5591
+ this.#dropStaleContextUpdates();
5580
5592
  if (!this.needsCompaction()) return false;
5581
5593
  const originalMessages = this.#messages;
5582
5594
  const originalRecordedUsage = this.#lastRecordedInputTokens;
@@ -5589,8 +5601,6 @@ var ContextManager = class _ContextManager {
5589
5601
  const microcompact = microcompactMessages(this.#messages, this.#compaction.microcompactKeepRecentToolResults);
5590
5602
  if (microcompact.changed) {
5591
5603
  this.#messages = microcompact.messages;
5592
- this.#lastRecordedInputTokens = void 0;
5593
- this.#estimatedTokensAtLastRecordedUsage = void 0;
5594
5604
  }
5595
5605
  if (!this.needsCompaction()) {
5596
5606
  if (microcompact.changed) {
@@ -5676,6 +5686,26 @@ var ContextManager = class _ContextManager {
5676
5686
  this.#reportCompactProgress(100);
5677
5687
  return true;
5678
5688
  }
5689
+ #dropStaleContextUpdates() {
5690
+ const latestBySource = /* @__PURE__ */ new Map();
5691
+ for (let index = this.#messages.length - 1; index >= 0; index -= 1) {
5692
+ const source = contextUpdateSource(this.#messages[index]);
5693
+ if (source === void 0 || latestBySource.has(source)) continue;
5694
+ latestBySource.set(source, index);
5695
+ }
5696
+ const next = [];
5697
+ let changed = false;
5698
+ this.#messages.forEach((message2, index) => {
5699
+ const source = contextUpdateSource(message2);
5700
+ if (source !== void 0 && latestBySource.get(source) !== index) {
5701
+ changed = true;
5702
+ return;
5703
+ }
5704
+ next.push(message2);
5705
+ });
5706
+ if (changed) this.#messages = next;
5707
+ return changed;
5708
+ }
5679
5709
  #reportCompactProgress(percentage) {
5680
5710
  const normalized = Math.max(0, Math.min(100, Math.floor(percentage / 10) * 10));
5681
5711
  if (normalized === this.#lastCompactProgress) return;
@@ -5787,6 +5817,33 @@ function contextUpdateMessage(source, content) {
5787
5817
  return { role: "system", content: `Context update [${source}]
5788
5818
  ${content}` };
5789
5819
  }
5820
+ var CONTEXT_UPDATE_PREFIX = "Context update [";
5821
+ function contextUpdateSource(message2) {
5822
+ if (message2.role !== "system" || typeof message2.content !== "string") return void 0;
5823
+ if (!message2.content.startsWith(CONTEXT_UPDATE_PREFIX)) return void 0;
5824
+ const end = message2.content.indexOf("]\n", CONTEXT_UPDATE_PREFIX.length);
5825
+ if (end < 0) return void 0;
5826
+ return message2.content.slice(CONTEXT_UPDATE_PREFIX.length, end);
5827
+ }
5828
+ function deltaSourceValue(previous, display) {
5829
+ if (previous === void 0 || !display.startsWith(previous)) return display;
5830
+ return display.slice(previous.length).replace(/^\s+/, "") || display;
5831
+ }
5832
+ function limitVisibilityContent(content) {
5833
+ const limit = ContextManager.VISIBILITY_RECORD_CONTENT_LIMIT;
5834
+ if (content.length <= limit) return content;
5835
+ return `${content.slice(0, limit)}
5836
+ ...[truncated; original length: ${content.length} characters]...`;
5837
+ }
5838
+ function boundVisibilityLog(records) {
5839
+ if (records === void 0) return [];
5840
+ const limit = ContextManager.VISIBILITY_RECORD_CONTENT_LIMIT;
5841
+ const bounded2 = records.map((item) => ({
5842
+ ...item,
5843
+ ...item.content.length <= limit ? {} : { content: limitVisibilityContent(item.content) }
5844
+ }));
5845
+ return bounded2.length > ContextManager.VISIBILITY_LOG_MAX_ENTRIES ? bounded2.slice(-ContextManager.VISIBILITY_LOG_MAX_ENTRIES) : bounded2;
5846
+ }
5790
5847
  function cloneEpoch(epoch) {
5791
5848
  return {
5792
5849
  ...epoch,
@@ -6081,6 +6138,9 @@ function reportProgress(callback, percentage) {
6081
6138
  }
6082
6139
  }
6083
6140
 
6141
+ // src/agent/loop.ts
6142
+ import { getHeapStatistics } from "v8";
6143
+
6084
6144
  // src/models/structured.ts
6085
6145
  import { z as z12 } from "zod";
6086
6146
  var DEFAULT_RETRY_DELAYS = [1e3, 2e3, 4e3];
@@ -6498,6 +6558,18 @@ function envMaxIterations() {
6498
6558
  if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
6499
6559
  return parsed;
6500
6560
  }
6561
+ var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
6562
+ function memoryPressureError(heapUsed, heapLimit) {
6563
+ if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
6564
+ const mb = (bytes) => Math.round(bytes / 1048576);
6565
+ return {
6566
+ code: "memory_pressure",
6567
+ message: `Heap usage reached ${Math.round(heapUsed / heapLimit * 100)}% of the V8 limit (${mb(heapUsed)}MB of ${mb(heapLimit)}MB); stopping the current turn to avoid a crash. The saved session will reopen after restart.`
6568
+ };
6569
+ }
6570
+ function currentMemoryPressure() {
6571
+ return memoryPressureError(process.memoryUsage().heapUsed, getHeapStatistics().heap_size_limit);
6572
+ }
6501
6573
  var AgentLoop = class {
6502
6574
  #options;
6503
6575
  #iterationLimitMode = "standard";
@@ -6568,6 +6640,11 @@ var AgentLoop = class {
6568
6640
  yield { type: "error", error: { code: "cancelled", message: abortMessage(request.signal) } };
6569
6641
  return;
6570
6642
  }
6643
+ const pressure = currentMemoryPressure();
6644
+ if (pressure !== void 0) {
6645
+ yield { type: "error", error: pressure };
6646
+ return;
6647
+ }
6571
6648
  const softLimit = Math.floor(maxIterations * this.#options.softLimitFactor);
6572
6649
  if (!warned && iteration >= softLimit) {
6573
6650
  warned = true;
@@ -6607,6 +6684,11 @@ var AgentLoop = class {
6607
6684
  yield { type: "error", error: normalizeProviderError(error) };
6608
6685
  return;
6609
6686
  }
6687
+ const preparedPressure = currentMemoryPressure();
6688
+ if (preparedPressure !== void 0) {
6689
+ yield { type: "error", error: preparedPressure };
6690
+ return;
6691
+ }
6610
6692
  let assistantText = "";
6611
6693
  const collectedToolCalls = [];
6612
6694
  const collectedThinking = [];
@@ -6630,6 +6712,11 @@ var AgentLoop = class {
6630
6712
  tools: [...this.#options.tools],
6631
6713
  ...request.signal === void 0 ? {} : { signal: request.signal }
6632
6714
  };
6715
+ const requestPressure = currentMemoryPressure();
6716
+ if (requestPressure !== void 0) {
6717
+ yield { type: "error", error: requestPressure };
6718
+ return;
6719
+ }
6633
6720
  let before;
6634
6721
  try {
6635
6722
  before = await this.#options.hooks.emit({
@@ -12927,7 +13014,8 @@ var BoundedOutput = class {
12927
13014
  #headLimit;
12928
13015
  #tailLimit;
12929
13016
  #head = Buffer.alloc(0);
12930
- #tail = Buffer.alloc(0);
13017
+ #tailChunks = [];
13018
+ #tailBytes = 0;
12931
13019
  #complete = Buffer.alloc(0);
12932
13020
  #bytes = 0;
12933
13021
  constructor(limit) {
@@ -12947,15 +13035,24 @@ var BoundedOutput = class {
12947
13035
  const count = Math.min(this.#headLimit - this.#head.length, chunk.length);
12948
13036
  this.#head = Buffer.concat([this.#head, chunk.subarray(0, count)]);
12949
13037
  }
12950
- if (this.#tailLimit > 0) this.#tail = Buffer.concat([this.#tail, chunk]).subarray(-this.#tailLimit);
13038
+ if (this.#tailLimit > 0) {
13039
+ this.#tailChunks.push(chunk);
13040
+ this.#tailBytes += chunk.length;
13041
+ if (this.#tailBytes >= this.#tailLimit * 2) {
13042
+ const merged = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
13043
+ this.#tailChunks = [merged];
13044
+ this.#tailBytes = merged.length;
13045
+ }
13046
+ }
12951
13047
  }
12952
13048
  metadata() {
12953
13049
  return { truncated: this.truncated, originalBytes: this.#bytes, limitBytes: this.#limit };
12954
13050
  }
12955
13051
  text() {
12956
13052
  if (!this.truncated) return decodeOutput(this.#complete);
13053
+ const tail = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
12957
13054
  const encoding = classifyUtf8(this.#complete) === "invalid" && process.platform === "win32" ? "gb18030" : "utf-8";
12958
- return `${decodePrefix(this.#head, encoding)}${ELLIPSIS}${decodeSuffix(this.#tail, encoding)}`;
13055
+ return `${decodePrefix(this.#head, encoding)}${ELLIPSIS}${decodeSuffix(tail, encoding)}`;
12959
13056
  }
12960
13057
  };
12961
13058
  var AdaptiveOutputDecoder = class {
@@ -15406,6 +15503,8 @@ function appendThinkingText(previous, delta) {
15406
15503
  }
15407
15504
 
15408
15505
  // src/ui/transcript.ts
15506
+ var MAX_TRANSCRIPT_TURNS = 200;
15507
+ var MAX_TRANSCRIPT_CHARS = 2e6;
15409
15508
  function createTranscriptState() {
15410
15509
  return { completed: [], nextId: 1 };
15411
15510
  }
@@ -15702,7 +15801,7 @@ function hydrateHistory(messages, compact) {
15702
15801
  }
15703
15802
  }
15704
15803
  if (turn !== void 0) completed.push(turn);
15705
- return { completed, nextId: completed.length + 1 };
15804
+ return { completed: boundedCompleted(completed), nextId: completed.length + 1 };
15706
15805
  }
15707
15806
  function transcriptFromLegacyConversation(messages, compact) {
15708
15807
  return hydrateHistory(messages, compact);
@@ -15796,10 +15895,10 @@ function finishActive(state) {
15796
15895
  if (state.active === void 0) return state;
15797
15896
  const active = withoutModelActivity(state.active);
15798
15897
  return {
15799
- completed: [...state.completed, {
15898
+ completed: boundedCompleted([...state.completed, {
15800
15899
  ...active,
15801
15900
  completedAt: active.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
15802
- }],
15901
+ }]),
15803
15902
  nextId: state.nextId,
15804
15903
  ...state.taskSnapshot === void 0 ? {} : { taskSnapshot: state.taskSnapshot }
15805
15904
  };
@@ -15828,12 +15927,26 @@ function withoutModelActivity(turn, id) {
15828
15927
  function restoreTranscriptState(state) {
15829
15928
  const completed = state.completed.map(cloneTurn);
15830
15929
  if (state.active !== void 0) completed.push(normalizeInterruptedTurn(cloneTurn(state.active)));
15930
+ const nextId = Math.max(state.nextId, ...completed.map((turn) => turn.id + 1), 1);
15831
15931
  return {
15832
- completed,
15833
- nextId: Math.max(state.nextId, ...completed.map((turn) => turn.id + 1), 1),
15932
+ completed: boundedCompleted(completed),
15933
+ nextId,
15834
15934
  ...state.taskSnapshot === void 0 ? {} : { taskSnapshot: state.taskSnapshot }
15835
15935
  };
15836
15936
  }
15937
+ function boundedCompleted(input) {
15938
+ const recent = input.slice(-MAX_TRANSCRIPT_TURNS);
15939
+ let chars = 0;
15940
+ let start = recent.length;
15941
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
15942
+ const turn = recent[index];
15943
+ const size = JSON.stringify(turn).length;
15944
+ if (start < recent.length && chars + size > MAX_TRANSCRIPT_CHARS) break;
15945
+ chars += size;
15946
+ start = index;
15947
+ }
15948
+ return recent.slice(start);
15949
+ }
15837
15950
  function compactionTurn(compact) {
15838
15951
  const text = `Original execution steps before ${compact.compactedAt} are unavailable.`;
15839
15952
  return {
@@ -16657,6 +16770,8 @@ function isCode9(error, code) {
16657
16770
  }
16658
16771
 
16659
16772
  // src/session/tree.ts
16773
+ var MAX_SESSION_HISTORY_NODES = 100;
16774
+ var MAX_SESSION_HISTORY_CONTEXT_CHARS = 2e6;
16660
16775
  var SessionHistory = class _SessionHistory {
16661
16776
  #path;
16662
16777
  #checkpoints;
@@ -16668,8 +16783,8 @@ var SessionHistory = class _SessionHistory {
16668
16783
  this.#path = join25(resolve21(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
16669
16784
  this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
16670
16785
  this.#restoreContext = options.restoreContext;
16671
- this.#nodes = document.nodes;
16672
- this.#leafId = document.leafId;
16786
+ this.#nodes = boundedNodes(document.nodes);
16787
+ this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
16673
16788
  }
16674
16789
  static async open(options) {
16675
16790
  const path = join25(resolve21(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
@@ -16681,7 +16796,9 @@ var SessionHistory = class _SessionHistory {
16681
16796
  } catch (error) {
16682
16797
  if (!isCode10(error, "ENOENT")) throw error;
16683
16798
  }
16684
- return new _SessionHistory(options, document);
16799
+ const history = new _SessionHistory(options, document);
16800
+ if (history.#nodes.length !== document.nodes.length) await history.#persist();
16801
+ return history;
16685
16802
  }
16686
16803
  get leafId() {
16687
16804
  return this.#leafId;
@@ -16701,6 +16818,7 @@ var SessionHistory = class _SessionHistory {
16701
16818
  };
16702
16819
  this.#nodes.push(node);
16703
16820
  this.#leafId = node.id;
16821
+ this.#boundNodes();
16704
16822
  await this.#persist();
16705
16823
  return structuredClone(node);
16706
16824
  }
@@ -16742,6 +16860,13 @@ var SessionHistory = class _SessionHistory {
16742
16860
  if (node === void 0) throw new Error(`Unknown session tree node: ${id}`);
16743
16861
  return node;
16744
16862
  }
16863
+ #boundNodes() {
16864
+ const bounded2 = boundedNodes(this.#nodes);
16865
+ this.#nodes.splice(0, this.#nodes.length, ...bounded2);
16866
+ if (this.#leafId !== null && !this.#nodes.some((node) => node.id === this.#leafId)) {
16867
+ this.#leafId = this.#nodes.at(-1)?.id ?? null;
16868
+ }
16869
+ }
16745
16870
  async #persist() {
16746
16871
  const document = {
16747
16872
  version: 1,
@@ -16756,6 +16881,24 @@ var SessionHistory = class _SessionHistory {
16756
16881
  await rename8(temporary, this.#path);
16757
16882
  }
16758
16883
  };
16884
+ function boundedNodes(input) {
16885
+ const recent = input.slice(-MAX_SESSION_HISTORY_NODES);
16886
+ let contextChars = 0;
16887
+ let start = recent.length;
16888
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
16889
+ const node = recent[index];
16890
+ const size = JSON.stringify(node.context).length;
16891
+ if (start < recent.length && contextChars + size > MAX_SESSION_HISTORY_CONTEXT_CHARS) break;
16892
+ contextChars += size;
16893
+ start = index;
16894
+ }
16895
+ const retained = recent.slice(start).map((node) => structuredClone(node));
16896
+ const ids = new Set(retained.map((node) => node.id));
16897
+ for (const node of retained) {
16898
+ if (node.parentId !== null && !ids.has(node.parentId)) node.parentId = null;
16899
+ }
16900
+ return retained;
16901
+ }
16759
16902
  function validate(value, sessionId) {
16760
16903
  if (value.version !== 1 || value.sessionId !== sessionId || !Array.isArray(value.nodes)) {
16761
16904
  throw new Error("Invalid session tree");
@@ -16774,11 +16917,35 @@ function isCode10(error, code) {
16774
16917
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
16775
16918
  }
16776
16919
 
16920
+ // src/utils/memory-restart.ts
16921
+ import { join as join26 } from "path";
16922
+ var MEMORY_RESTART_EXIT_CODE = 75;
16923
+ var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
16924
+ var MEMORY_RESTART_WINDOW_MS = 30 * 60 * 1e3;
16925
+ function memoryRestartMarkerPath(workspace) {
16926
+ return join26(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
16927
+ }
16928
+ function nextMemoryRestartMarker(previous, sessionId, now) {
16929
+ 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;
16930
+ return { sessionId, requestedAt: now.toISOString(), attempts: reusable ? previous.attempts + 1 : 1 };
16931
+ }
16932
+ function parseMemoryRestartMarker(json) {
16933
+ try {
16934
+ const value = JSON.parse(json);
16935
+ if (typeof value.sessionId !== "string" || typeof value.requestedAt !== "string" || typeof value.attempts !== "number") {
16936
+ return void 0;
16937
+ }
16938
+ return { sessionId: value.sessionId, requestedAt: value.requestedAt, attempts: value.attempts };
16939
+ } catch {
16940
+ return void 0;
16941
+ }
16942
+ }
16943
+
16777
16944
  // src/sleep/organizer.ts
16778
16945
  import { constants as constants7 } from "fs";
16779
16946
  import { randomUUID as randomUUID16 } from "crypto";
16780
16947
  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";
16781
- import { isAbsolute as isAbsolute10, join as join26, relative as relative15, resolve as resolve22, sep as sep15 } from "path";
16948
+ import { isAbsolute as isAbsolute10, join as join27, relative as relative15, resolve as resolve22, sep as sep15 } from "path";
16782
16949
  import { z as z28 } from "zod";
16783
16950
  var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
16784
16951
  var ReviewSchema = z28.object({
@@ -16833,7 +17000,7 @@ var ProjectSleepOrganizer = class {
16833
17000
  #generate;
16834
17001
  constructor(options) {
16835
17002
  this.#workspace = resolve22(options.workspace);
16836
- this.#sleepDirectory = join26(this.#workspace, ".flavor", "sleep");
17003
+ this.#sleepDirectory = join27(this.#workspace, ".flavor", "sleep");
16837
17004
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
16838
17005
  this.#generate = options.generate;
16839
17006
  }
@@ -16842,7 +17009,7 @@ var ProjectSleepOrganizer = class {
16842
17009
  signal.throwIfAborted();
16843
17010
  await this.#prepareDirectory();
16844
17011
  if (await this.#hasReport(date)) return { status: "exists", date };
16845
- const lockPath = join26(this.#sleepDirectory, `.${date}.lock`);
17012
+ const lockPath = join27(this.#sleepDirectory, `.${date}.lock`);
16846
17013
  let lock;
16847
17014
  try {
16848
17015
  lock = await open9(lockPath, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
@@ -16877,7 +17044,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
16877
17044
  }
16878
17045
  if (review === void 0) throw lastError;
16879
17046
  const filename2 = `${date}-${filenameSummary(review.title)}.md`;
16880
- const path = join26(this.#sleepDirectory, filename2);
17047
+ const path = join27(this.#sleepDirectory, filename2);
16881
17048
  const markdown = renderSleepReport(date, review, documents);
16882
17049
  await this.#atomicWrite(path, markdown);
16883
17050
  return { status: "written", date, path, sessionCount: documents.length };
@@ -16919,7 +17086,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
16919
17086
  });
16920
17087
  }
16921
17088
  async #prepareDirectory() {
16922
- const flavorDirectory = join26(this.#workspace, ".flavor");
17089
+ const flavorDirectory = join27(this.#workspace, ".flavor");
16923
17090
  await assertNotSymlink(flavorDirectory);
16924
17091
  await mkdir18(flavorDirectory, { recursive: true, mode: 448 });
16925
17092
  await assertNotSymlink(this.#sleepDirectory);
@@ -16933,7 +17100,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
16933
17100
  return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
16934
17101
  }
16935
17102
  async #atomicWrite(path, content) {
16936
- const temporary = join26(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
17103
+ const temporary = join27(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
16937
17104
  let handle;
16938
17105
  try {
16939
17106
  handle = await open9(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
@@ -19837,12 +20004,12 @@ ${explanation}` : "";
19837
20004
  // src/desktop/astgraph-service.ts
19838
20005
  import { stat as stat10 } from "fs/promises";
19839
20006
  import { createRequire as nodeCreateRequire } from "module";
19840
- import { join as join27, resolve as resolve26 } from "path";
20007
+ import { join as join28, resolve as resolve26 } from "path";
19841
20008
  var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
19842
20009
  var DesktopAstGraphService = class {
19843
20010
  #path;
19844
20011
  constructor(workspace) {
19845
- this.#path = join27(resolve26(workspace), ".flavor", "astgraph", "index.db");
20012
+ this.#path = join28(resolve26(workspace), ".flavor", "astgraph", "index.db");
19846
20013
  }
19847
20014
  async status() {
19848
20015
  if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
@@ -20111,7 +20278,7 @@ function createTodoWriteTool() {
20111
20278
  // src/tools/managed.ts
20112
20279
  import { randomUUID as randomUUID18 } from "crypto";
20113
20280
  import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
20114
- import { dirname as dirname21, join as join28, resolve as resolve27 } from "path";
20281
+ import { dirname as dirname21, join as join29, resolve as resolve27 } from "path";
20115
20282
  import { z as z37 } from "zod";
20116
20283
  var MANAGED_TOOL_VERSION = 1;
20117
20284
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -20154,8 +20321,8 @@ var ManagedToolStore = class {
20154
20321
  #diagnostics = [];
20155
20322
  constructor(options) {
20156
20323
  this.#workspace = resolve27(options.workspace);
20157
- this.#projectRoot = join28(this.#workspace, ".flavor", "tools");
20158
- this.#globalRoot = join28(resolve27(options.home), ".flavor-code", "tools");
20324
+ this.#projectRoot = join29(this.#workspace, ".flavor", "tools");
20325
+ this.#globalRoot = join29(resolve27(options.home), ".flavor-code", "tools");
20159
20326
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
20160
20327
  }
20161
20328
  get diagnostics() {
@@ -20202,7 +20369,7 @@ var ManagedToolStore = class {
20202
20369
  version: MANAGED_TOOL_VERSION,
20203
20370
  createdAt: this.#now()
20204
20371
  });
20205
- const path = join28(this.directory(input.scope), filename(input.name));
20372
+ const path = join29(this.directory(input.scope), filename(input.name));
20206
20373
  const definition = compileDefinition(record3, input.scope, this.#workspace);
20207
20374
  await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
20208
20375
  `);
@@ -20227,7 +20394,7 @@ var ManagedToolStore = class {
20227
20394
  if (!parsed.success) return [];
20228
20395
  const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
20229
20396
  if (matches.length > 0) return matches.map((entry2) => entry2.path);
20230
- return [join28(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
20397
+ return [join29(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
20231
20398
  }
20232
20399
  async #loadDirectory(scope2) {
20233
20400
  const root = this.directory(scope2);
@@ -20240,7 +20407,7 @@ var ManagedToolStore = class {
20240
20407
  return;
20241
20408
  }
20242
20409
  for (const name of names) {
20243
- const path = join28(root, name);
20410
+ const path = join29(root, name);
20244
20411
  try {
20245
20412
  const info = await lstat11(path);
20246
20413
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
@@ -22558,7 +22725,7 @@ ${total}`;
22558
22725
  // src/memory/store.ts
22559
22726
  import { createHash as createHash17 } from "crypto";
22560
22727
  import { readFile as readFile23, rm as rm12 } from "fs/promises";
22561
- import { resolve as resolve28, join as join29, relative as relative19, sep as sep19 } from "path";
22728
+ import { resolve as resolve28, join as join30, relative as relative19, sep as sep19 } from "path";
22562
22729
 
22563
22730
  // src/memory/similarity.ts
22564
22731
  function normalizeForSimilarity(value) {
@@ -22661,9 +22828,9 @@ var MemoryStore = class {
22661
22828
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
22662
22829
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
22663
22830
  this.workspace = resolve28(options.workspace);
22664
- this.#memoryRoot = join29(this.workspace, ".flavor", "memory");
22665
- this.path = join29(this.#memoryRoot, "MEMORY.md");
22666
- this.#behaviorPath = join29(this.#memoryRoot, "behavior.json");
22831
+ this.#memoryRoot = join30(this.workspace, ".flavor", "memory");
22832
+ this.path = join30(this.#memoryRoot, "MEMORY.md");
22833
+ this.#behaviorPath = join30(this.#memoryRoot, "behavior.json");
22667
22834
  this.#maxEntries = options.maxEntries;
22668
22835
  this.#maxEntryChars = options.maxEntryChars;
22669
22836
  }
@@ -23452,7 +23619,7 @@ function createExecutionEnvironment(workspace, config) {
23452
23619
 
23453
23620
  // src/ide/client.ts
23454
23621
  import { readFile as readFile24, readdir as readdir10, stat as stat11 } from "fs/promises";
23455
- import { basename as basename7, join as join30, relative as relative21, resolve as resolve30, sep as sep21 } from "path";
23622
+ import { basename as basename7, join as join31, relative as relative21, resolve as resolve30, sep as sep21 } from "path";
23456
23623
  var FlavorIdeClient = class {
23457
23624
  #workspace;
23458
23625
  #home;
@@ -23548,7 +23715,7 @@ var FlavorIdeClient = class {
23548
23715
  };
23549
23716
  if (await this.#healthy(configured)) return configured;
23550
23717
  }
23551
- const directory = join30(this.#home, ".flavor-code", "ide");
23718
+ const directory = join31(this.#home, ".flavor-code", "ide");
23552
23719
  let entries;
23553
23720
  try {
23554
23721
  entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
@@ -23556,7 +23723,7 @@ var FlavorIdeClient = class {
23556
23723
  return void 0;
23557
23724
  }
23558
23725
  const candidates = (await Promise.all(entries.map(async (name) => {
23559
- const path = join30(directory, name);
23726
+ const path = join31(directory, name);
23560
23727
  try {
23561
23728
  const [contents, info] = await Promise.all([readFile24(path, "utf8"), stat11(path)]);
23562
23729
  const lock = parseLockfile(contents);
@@ -24063,7 +24230,7 @@ function palSocketAddress(options) {
24063
24230
  import { createHash as createHash20 } from "crypto";
24064
24231
  import { Socket as Socket2 } from "net";
24065
24232
  import { tmpdir as tmpdir3 } from "os";
24066
- import { join as join32 } from "path";
24233
+ import { join as join33 } from "path";
24067
24234
  import { spawn as spawn7 } from "child_process";
24068
24235
 
24069
24236
  // src/pals/broker.ts
@@ -24076,16 +24243,16 @@ import { dirname as dirname23 } from "path";
24076
24243
  import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
24077
24244
  import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink4 } from "fs/promises";
24078
24245
  import { homedir } from "os";
24079
- import { join as join31 } from "path";
24246
+ import { join as join32 } from "path";
24080
24247
  var PAL_AUTH_TOKEN_BYTES = 32;
24081
24248
  var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
24082
24249
  async function loadOrCreatePalAuthToken(options = {}) {
24083
- const directory = join31(options.home ?? homedir(), ".flavor-code", "pals");
24084
- const tokenPath = join31(directory, "auth-token");
24250
+ const directory = join32(options.home ?? homedir(), ".flavor-code", "pals");
24251
+ const tokenPath = join32(directory, "auth-token");
24085
24252
  await mkdir21(directory, { recursive: true, mode: 448 });
24086
24253
  await chmod2(directory, 448);
24087
24254
  const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
24088
- const temporaryPath = join31(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
24255
+ const temporaryPath = join32(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
24089
24256
  let handle;
24090
24257
  try {
24091
24258
  handle = await open12(temporaryPath, "wx", 384);
@@ -24215,7 +24382,7 @@ function canConnect(address) {
24215
24382
  }
24216
24383
  function startupLockPath(address) {
24217
24384
  const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
24218
- return join32(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
24385
+ return join33(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
24219
24386
  }
24220
24387
  async function spawnProductionBroker(address) {
24221
24388
  const entry2 = process.argv[1];
@@ -25151,8 +25318,8 @@ async function createProductionRuntime(options) {
25151
25318
  const registeredProviders = registration.registered;
25152
25319
  let effectiveLlm = registration.effectiveLlm;
25153
25320
  const pluginHost = new PluginHost({
25154
- globalPluginDirs: [join33(home, ".flavor-code", "plugins")],
25155
- projectPluginDirs: [join33(workspace, ".flavor", "plugins")],
25321
+ globalPluginDirs: [join34(home, ".flavor-code", "plugins")],
25322
+ projectPluginDirs: [join34(workspace, ".flavor", "plugins")],
25156
25323
  config,
25157
25324
  // Existing project plugins (including astgraph and superharness) use Node.js
25158
25325
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -25260,7 +25427,7 @@ async function createProductionRuntime(options) {
25260
25427
  const address = palSocketAddress({
25261
25428
  platform: process.platform,
25262
25429
  userScope: home,
25263
- runtimeDir: join33(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
25430
+ runtimeDir: join34(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
25264
25431
  });
25265
25432
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
25266
25433
  instanceId: collaboration.instanceId,
@@ -25371,8 +25538,8 @@ async function createProductionRuntime(options) {
25371
25538
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
25372
25539
  });
25373
25540
  const skills = new SkillRegistry({
25374
- globalRoots: [join33(home, ".flavor-code", "skills")],
25375
- projectRoots: [join33(workspace, ".flavor", "skills"), ...pluginSkillRoots],
25541
+ globalRoots: [join34(home, ".flavor-code", "skills")],
25542
+ projectRoots: [join34(workspace, ".flavor", "skills"), ...pluginSkillRoots],
25376
25543
  authorizeResource: async () => true,
25377
25544
  disabledNames: config.skills.disabled
25378
25545
  });
@@ -25380,7 +25547,7 @@ async function createProductionRuntime(options) {
25380
25547
  skillsReady.catch(() => void 0);
25381
25548
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
25382
25549
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
25383
- const flavor = await optionalText2(join33(workspace, "FLAVOR.md"));
25550
+ const flavor = await optionalText2(join34(workspace, "FLAVOR.md"));
25384
25551
  const instructionBaseline = await workspaceInstructions.baseline();
25385
25552
  let memoryContext;
25386
25553
  if (memoryStore !== void 0) {
@@ -25961,7 +26128,7 @@ async function createProductionRuntime(options) {
25961
26128
  evolveService.toolDefinition(),
25962
26129
  ...mcpTools
25963
26130
  ];
25964
- const loopFlavor = await optionalText2(join33(input.workspace, "FLAVOR.md"));
26131
+ const loopFlavor = await optionalText2(join34(input.workspace, "FLAVOR.md"));
25965
26132
  const loopInstructionBaseline = await loopInstructions.baseline();
25966
26133
  const loopEnvironment = createPromptEnvironment({
25967
26134
  now: /* @__PURE__ */ new Date(),
@@ -26206,7 +26373,7 @@ async function createProductionRuntime(options) {
26206
26373
  contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
26207
26374
  });
26208
26375
  const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
26209
- return durableTurn(persistAndCheckpointAfter(runMain(
26376
+ return monitorMemoryRestart(durableTurn(persistAfter(runMain(
26210
26377
  harness,
26211
26378
  skills,
26212
26379
  prompt,
@@ -26222,11 +26389,7 @@ async function createProductionRuntime(options) {
26222
26389
  runOptions?.initialUserMessage,
26223
26390
  runOptions?.additionalContext,
26224
26391
  ide
26225
- ), persist, () => sessionHistory.append({
26226
- prompt,
26227
- context: harness.main.context.snapshot(),
26228
- label: `turn: ${prompt.slice(0, 80)}`
26229
- })), harnessJournal, turnId, turnConfig);
26392
+ ), persist), harnessJournal, turnId, turnConfig), () => requestMemoryRestart(workspace, sessionId, emitOutput));
26230
26393
  },
26231
26394
  runSkill: (skill, prompt, signal) => persistAfter(
26232
26395
  runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
@@ -26526,7 +26689,7 @@ ${renderMemoryDocument(entries)}`;
26526
26689
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
26527
26690
  }
26528
26691
  try {
26529
- const tokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
26692
+ const tokenStore = createFileTokenStore(join34(home, ".flavor-code", "auth.json"));
26530
26693
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
26531
26694
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
26532
26695
  const oauth = new OAuthCallbackAuthProvider({
@@ -26573,7 +26736,7 @@ ${renderMemoryDocument(entries)}`;
26573
26736
  }
26574
26737
  },
26575
26738
  async logout() {
26576
- const tokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
26739
+ const tokenStore = createFileTokenStore(join34(home, ".flavor-code", "auth.json"));
26577
26740
  const tokens2 = await tokenStore.load();
26578
26741
  const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
26579
26742
  if (!hadCredentials) {
@@ -26936,14 +27099,6 @@ async function* persistAfter(source, persist) {
26936
27099
  await persist();
26937
27100
  }
26938
27101
  }
26939
- async function* persistAndCheckpointAfter(source, persist, checkpoint) {
26940
- try {
26941
- for await (const item of source) yield item;
26942
- await checkpoint();
26943
- } finally {
26944
- await persist();
26945
- }
26946
- }
26947
27102
  async function* persistEach(source, persist) {
26948
27103
  try {
26949
27104
  for await (const item of source) {
@@ -26975,6 +27130,31 @@ async function* durableTurn(source, journal, turnId, config) {
26975
27130
  }
26976
27131
  }
26977
27132
  }
27133
+ async function* monitorMemoryRestart(source, onRestartNeeded) {
27134
+ let pressure = false;
27135
+ for await (const event of source) {
27136
+ if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
27137
+ yield event;
27138
+ }
27139
+ if (pressure) onRestartNeeded();
27140
+ }
27141
+ function requestMemoryRestart(workspace, sessionId, emitOutput) {
27142
+ try {
27143
+ const markerPath = memoryRestartMarkerPath(workspace);
27144
+ let previous;
27145
+ try {
27146
+ previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
27147
+ } catch {
27148
+ }
27149
+ mkdirSync3(dirname24(markerPath), { recursive: true });
27150
+ writeFileSync(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
27151
+ `, "utf8");
27152
+ } catch {
27153
+ }
27154
+ process.exitCode = MEMORY_RESTART_EXIT_CODE;
27155
+ emitOutput({ type: "notice", message: "Memory watermark reached; the session is saved and Flavor is restarting with a fresh heap." });
27156
+ emitOutput({ type: "exit" });
27157
+ }
26978
27158
  async function runGitCommit(deps, hint, signal) {
26979
27159
  if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
26980
27160
  let summary2 = await changeSummary(deps.workspace);
@@ -27068,7 +27248,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
27068
27248
  const configured = { ...providers };
27069
27249
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
27070
27250
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
27071
- const oauthTokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
27251
+ const oauthTokenStore = createFileTokenStore(join34(home, ".flavor-code", "auth.json"));
27072
27252
  const registered = [];
27073
27253
  let effectiveLlm;
27074
27254
  for (const [name, provider] of Object.entries(configured)) {
@@ -27501,11 +27681,11 @@ function createMemoryTaskId() {
27501
27681
  import { createHash as createHash27 } from "crypto";
27502
27682
  import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as stat15, writeFile as writeFile17 } from "fs/promises";
27503
27683
  import { homedir as homedir4 } from "os";
27504
- import { basename as basename9, join as join43, resolve as resolve36 } from "path";
27684
+ import { basename as basename9, join as join44, resolve as resolve36 } from "path";
27505
27685
 
27506
27686
  // src/d2c/workflow.ts
27507
27687
  import { createHash as createHash22 } from "crypto";
27508
- import { join as join34 } from "path";
27688
+ import { join as join35 } from "path";
27509
27689
  import { z as z42 } from "zod";
27510
27690
 
27511
27691
  // src/d2c/workflow-shared.ts
@@ -27651,7 +27831,7 @@ function reviewsFor(report, previous) {
27651
27831
  function implementationDirectory(report) {
27652
27832
  const source = report.implementation.source;
27653
27833
  if (!/^https?:\/\//i.test(source)) return source;
27654
- return join34("src", "d2c-output", report.task);
27834
+ return join35("src", "d2c-output", report.task);
27655
27835
  }
27656
27836
  function createWorkflow(report, framework) {
27657
27837
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -27775,7 +27955,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
27775
27955
  };
27776
27956
  }
27777
27957
  function workflowPath(workspace, task) {
27778
- return join34(taskDir(workspace, task), "workflow.json");
27958
+ return join35(taskDir(workspace, task), "workflow.json");
27779
27959
  }
27780
27960
  async function readWorkflow(workspace, task) {
27781
27961
  return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
@@ -28029,7 +28209,7 @@ function confirmApiMapping(mapping, operationKey, operations) {
28029
28209
 
28030
28210
  // src/d2c/modules.ts
28031
28211
  import { readFile as readFile28 } from "fs/promises";
28032
- import { isAbsolute as isAbsolute15, join as join35, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
28212
+ import { isAbsolute as isAbsolute15, join as join36, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
28033
28213
  import { z as z43 } from "zod";
28034
28214
  var ModuleSchema = z43.object({
28035
28215
  id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
@@ -28048,13 +28228,13 @@ function safeSource(path) {
28048
28228
  }
28049
28229
  function d2cOutputDirectory(workspace, task) {
28050
28230
  if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
28051
- return join35(workspace, "src", "d2c-output", task);
28231
+ return join36(workspace, "src", "d2c-output", task);
28052
28232
  }
28053
28233
  async function readD2cModules(workspace, task) {
28054
28234
  const project = d2cOutputDirectory(workspace, task);
28055
28235
  let raw;
28056
28236
  try {
28057
- raw = await readFile28(join35(project, "d2c.modules.json"), "utf8");
28237
+ raw = await readFile28(join36(project, "d2c.modules.json"), "utf8");
28058
28238
  } catch {
28059
28239
  return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
28060
28240
  }
@@ -28075,7 +28255,7 @@ async function readD2cModules(workspace, task) {
28075
28255
 
28076
28256
  // src/d2c/integration.ts
28077
28257
  import { mkdir as mkdir24, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
28078
- import { join as join36 } from "path";
28258
+ import { join as join37 } from "path";
28079
28259
  function record2(value) {
28080
28260
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
28081
28261
  }
@@ -28211,7 +28391,7 @@ async def reset_acceptance(request: Request):
28211
28391
  }
28212
28392
  async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
28213
28393
  if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
28214
- const packagePath = join36(projectDir, "package.json");
28394
+ const packagePath = join37(projectDir, "package.json");
28215
28395
  let pkg;
28216
28396
  try {
28217
28397
  pkg = JSON.parse(await readFile29(packagePath, "utf8"));
@@ -28221,9 +28401,9 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
28221
28401
  const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
28222
28402
  const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
28223
28403
  const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
28224
- const apiDir = join36(projectDir, "src", "api");
28225
- const mockDir = join36(projectDir, "mock");
28226
- const serverDir = join36(projectDir, "server");
28404
+ const apiDir = join37(projectDir, "src", "api");
28405
+ const mockDir = join37(projectDir, "mock");
28406
+ const serverDir = join37(projectDir, "server");
28227
28407
  await Promise.all([
28228
28408
  mkdir24(apiDir, { recursive: true }),
28229
28409
  ...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
@@ -28233,23 +28413,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
28233
28413
  if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
28234
28414
  else files.push("mock/server.mjs");
28235
28415
  await Promise.all([
28236
- writeFile13(join36(apiDir, "http.js"), `import axios from "axios";
28416
+ writeFile13(join37(apiDir, "http.js"), `import axios from "axios";
28237
28417
 
28238
28418
  export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
28239
28419
 
28240
28420
  http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
28241
28421
  `),
28242
- writeFile13(join36(apiDir, "d2c-api.js"), apiModule(selected)),
28243
- writeFile13(join36(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
28422
+ writeFile13(join37(apiDir, "d2c-api.js"), apiModule(selected)),
28423
+ writeFile13(join37(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
28244
28424
  `),
28245
- ...options.pythonServer ? [] : [writeFile13(join36(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
28425
+ ...options.pythonServer ? [] : [writeFile13(join37(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
28246
28426
  writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
28247
28427
  `),
28248
28428
  ...options.pythonServer ? [
28249
- writeFile13(join36(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
28250
- writeFile13(join36(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
28251
- writeFile13(join36(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"),
28252
- writeFile13(join36(serverDir, "flavor-runtime.json"), `${JSON.stringify({
28429
+ writeFile13(join37(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
28430
+ writeFile13(join37(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
28431
+ writeFile13(join37(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"),
28432
+ writeFile13(join37(serverDir, "flavor-runtime.json"), `${JSON.stringify({
28253
28433
  schema: 1,
28254
28434
  kind: "python-fastapi",
28255
28435
  cwd: "server",
@@ -28270,7 +28450,7 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
28270
28450
  import { spawn as spawn8 } from "child_process";
28271
28451
  import { access as access3, readFile as readFile30 } from "fs/promises";
28272
28452
  import { createServer as createServer5 } from "net";
28273
- import { join as join37 } from "path";
28453
+ import { join as join38 } from "path";
28274
28454
  async function exists3(path) {
28275
28455
  try {
28276
28456
  await access3(path);
@@ -28378,7 +28558,7 @@ ${output.trim()}` : ""}`);
28378
28558
  }
28379
28559
  }
28380
28560
  async function runPythonBackend(projectDir, options) {
28381
- const serverDir = join37(projectDir, "server");
28561
+ const serverDir = join38(projectDir, "server");
28382
28562
  if (options.installDependencies !== false) await installPython(serverDir, options.signal);
28383
28563
  const selectedPort = await port();
28384
28564
  const url = `http://127.0.0.1:${selectedPort}`;
@@ -28424,7 +28604,7 @@ ${managed.output().slice(-4e3)}`);
28424
28604
  };
28425
28605
  }
28426
28606
  async function runD2cMockServer(projectDir, options = {}) {
28427
- const runtimePath = join37(projectDir, "server", "flavor-runtime.json");
28607
+ const runtimePath = join38(projectDir, "server", "flavor-runtime.json");
28428
28608
  if (await exists3(runtimePath)) {
28429
28609
  let runtime;
28430
28610
  try {
@@ -28435,9 +28615,9 @@ async function runD2cMockServer(projectDir, options = {}) {
28435
28615
  if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
28436
28616
  return runPythonBackend(projectDir, options);
28437
28617
  }
28438
- const serverPath = join37(projectDir, "mock", "server.mjs");
28618
+ const serverPath = join38(projectDir, "mock", "server.mjs");
28439
28619
  if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
28440
- if (options.installDependencies === false && !await exists3(join37(projectDir, "node_modules", "express", "package.json"))) {
28620
+ if (options.installDependencies === false && !await exists3(join38(projectDir, "node_modules", "express", "package.json"))) {
28441
28621
  throw new Error("D2C mock server dependencies are not installed");
28442
28622
  }
28443
28623
  if (options.installDependencies !== false) await install(projectDir, options.signal);
@@ -28480,7 +28660,7 @@ ${managed.output().slice(-4e3)}`);
28480
28660
  // src/d2c/product.ts
28481
28661
  import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
28482
28662
  import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as stat13, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
28483
- import { join as join38 } from "path";
28663
+ import { join as join39 } from "path";
28484
28664
  import { z as z44 } from "zod";
28485
28665
 
28486
28666
  // src/e2e/prd-governance.ts
@@ -28635,13 +28815,13 @@ var PlanSchema2 = z44.object({
28635
28815
  }).strict();
28636
28816
  function d2cProductDirectory(workspace, task) {
28637
28817
  if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
28638
- return join38(taskDir(workspace, task), "product");
28818
+ return join39(taskDir(workspace, task), "product");
28639
28819
  }
28640
28820
  function d2cProductPrototypeDirectory(workspace, task) {
28641
- return join38(d2cProductDirectory(workspace, task), "prototype");
28821
+ return join39(d2cProductDirectory(workspace, task), "prototype");
28642
28822
  }
28643
28823
  function planPath(workspace, task) {
28644
- return join38(taskDir(workspace, task), "product-plan.json");
28824
+ return join39(taskDir(workspace, task), "product-plan.json");
28645
28825
  }
28646
28826
  function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
28647
28827
  const frontendCandidates = [
@@ -28824,16 +29004,16 @@ async function readD2cProductPlan(workspace, task) {
28824
29004
  }
28825
29005
  async function discoverD2cProductArtifacts(workspace, plan) {
28826
29006
  const product = d2cProductDirectory(workspace, plan.task);
28827
- const prdUpdatedAt = await fileTimestamp(join38(product, "prd.md"));
28828
- const prdHash = await fileHash(join38(product, "prd.md"));
28829
- const prototypeUpdatedAt = await fileTimestamp(join38(product, "prototype", "index.html"));
28830
- const prototypeHash = await fileHash(join38(product, "prototype", "index.html"));
28831
- const expectedManifest = join38(product, "prototype", "interaction-manifest.json");
29007
+ const prdUpdatedAt = await fileTimestamp(join39(product, "prd.md"));
29008
+ const prdHash = await fileHash(join39(product, "prd.md"));
29009
+ const prototypeUpdatedAt = await fileTimestamp(join39(product, "prototype", "index.html"));
29010
+ const prototypeHash = await fileHash(join39(product, "prototype", "index.html"));
29011
+ const expectedManifest = join39(product, "prototype", "interaction-manifest.json");
28832
29012
  let manifestUpdatedAt = await fileTimestamp(expectedManifest);
28833
29013
  if (manifestUpdatedAt === void 0) {
28834
- const misplacedManifest = join38(product, "interaction-manifest.json");
29014
+ const misplacedManifest = join39(product, "interaction-manifest.json");
28835
29015
  if (await fileTimestamp(misplacedManifest) !== void 0) {
28836
- await mkdir25(join38(product, "prototype"), { recursive: true });
29016
+ await mkdir25(join39(product, "prototype"), { recursive: true });
28837
29017
  await copyFile2(misplacedManifest, expectedManifest);
28838
29018
  manifestUpdatedAt = await fileTimestamp(expectedManifest);
28839
29019
  }
@@ -28866,8 +29046,8 @@ async function readD2cProductPlanView(workspace, task) {
28866
29046
  if (stored === void 0) return void 0;
28867
29047
  const plan = await discoverD2cProductArtifacts(workspace, stored);
28868
29048
  if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
28869
- const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join38(taskDir(workspace, task), plan.prd.path), "utf8");
28870
- const manifestPath = join38(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
29049
+ const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join39(taskDir(workspace, task), plan.prd.path), "utf8");
29050
+ const manifestPath = join39(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
28871
29051
  const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
28872
29052
  return {
28873
29053
  plan,
@@ -29026,7 +29206,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
29026
29206
 
29027
29207
  // src/e2e/delivery-run.ts
29028
29208
  import { createHash as createHash26 } from "crypto";
29029
- import { join as join39 } from "path";
29209
+ import { join as join40 } from "path";
29030
29210
  import { z as z45 } from "zod";
29031
29211
  var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
29032
29212
  var ArtifactSchema = z45.object({
@@ -29143,7 +29323,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
29143
29323
  return { ...run, nodes, updatedAt: timestamp2 };
29144
29324
  }
29145
29325
  function deliveryRunPath(workspace, task) {
29146
- return join39(taskDir(workspace, task), "delivery-run.json");
29326
+ return join40(taskDir(workspace, task), "delivery-run.json");
29147
29327
  }
29148
29328
  function decodeDeliveryRun(raw) {
29149
29329
  return DeliveryRunSchema.parse(JSON.parse(raw));
@@ -29186,7 +29366,7 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
29186
29366
  // src/skills/manager.ts
29187
29367
  import { lstat as lstat13, mkdir as mkdir26, readFile as readFile34, readdir as readdir11, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
29188
29368
  import { homedir as homedir3 } from "os";
29189
- import { isAbsolute as isAbsolute16, join as join40, relative as relative24, resolve as resolve34, sep as sep25 } from "path";
29369
+ import { isAbsolute as isAbsolute16, join as join41, relative as relative24, resolve as resolve34, sep as sep25 } from "path";
29190
29370
  import { stringify } from "yaml";
29191
29371
  var SkillManager = class {
29192
29372
  #workspace;
@@ -29209,7 +29389,7 @@ var SkillManager = class {
29209
29389
  async create(draft) {
29210
29390
  const value = parseDraft(draft);
29211
29391
  const projectRoot = this.#projectRoot();
29212
- const directory = join40(projectRoot, value.name);
29392
+ const directory = join41(projectRoot, value.name);
29213
29393
  await mkdir26(projectRoot, { recursive: true, mode: 448 });
29214
29394
  try {
29215
29395
  await mkdir26(directory, { mode: 448 });
@@ -29218,7 +29398,7 @@ var SkillManager = class {
29218
29398
  throw error;
29219
29399
  }
29220
29400
  try {
29221
- await writeFile16(join40(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
29401
+ await writeFile16(join41(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
29222
29402
  await this.#persistEnabled(value.name, true);
29223
29403
  return this.get(value.name);
29224
29404
  } catch (error) {
@@ -29236,7 +29416,7 @@ var SkillManager = class {
29236
29416
  throw error;
29237
29417
  });
29238
29418
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
29239
- const skillFile = join40(directory, "SKILL.md");
29419
+ const skillFile = join41(directory, "SKILL.md");
29240
29420
  const fileInfo = await lstat13(skillFile);
29241
29421
  if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
29242
29422
  await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
@@ -29268,12 +29448,12 @@ var SkillManager = class {
29268
29448
  async #registry() {
29269
29449
  const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
29270
29450
  const [globalPluginRoots, projectPluginRoots] = await Promise.all([
29271
- discoverPluginSkillRoots(join40(this.#home, ".flavor-code", "plugins")),
29272
- discoverPluginSkillRoots(join40(this.#workspace, ".flavor", "plugins"))
29451
+ discoverPluginSkillRoots(join41(this.#home, ".flavor-code", "plugins")),
29452
+ discoverPluginSkillRoots(join41(this.#workspace, ".flavor", "plugins"))
29273
29453
  ]);
29274
29454
  return {
29275
29455
  registry: new SkillRegistry({
29276
- globalRoots: [join40(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
29456
+ globalRoots: [join41(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
29277
29457
  projectRoots: [this.#projectRoot(), ...projectPluginRoots]
29278
29458
  }),
29279
29459
  disabled: new Set(loaded.config.skills.disabled)
@@ -29289,10 +29469,10 @@ var SkillManager = class {
29289
29469
  };
29290
29470
  }
29291
29471
  #projectRoot() {
29292
- return join40(this.#workspace, ".flavor", "skills");
29472
+ return join41(this.#workspace, ".flavor", "skills");
29293
29473
  }
29294
29474
  #projectSkillDirectory(name) {
29295
- return join40(this.#projectRoot(), name);
29475
+ return join41(this.#projectRoot(), name);
29296
29476
  }
29297
29477
  };
29298
29478
  function parseName(value) {
@@ -29333,9 +29513,9 @@ async function discoverPluginSkillRoots(directory) {
29333
29513
  const roots = [];
29334
29514
  for (const entry2 of entries) {
29335
29515
  if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
29336
- const pluginRoot = await realpath13(join40(directory, entry2.name));
29516
+ const pluginRoot = await realpath13(join41(directory, entry2.name));
29337
29517
  try {
29338
- const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join40(pluginRoot, "flavor-plugin.json"), "utf8")));
29518
+ const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join41(pluginRoot, "flavor-plugin.json"), "utf8")));
29339
29519
  for (const contribution of manifest.contributes.skillRoots) {
29340
29520
  const candidate = resolve34(pluginRoot, contribution.path);
29341
29521
  const info = await lstat13(candidate);
@@ -29410,11 +29590,11 @@ async function createProjectMemoryManager(options) {
29410
29590
  }
29411
29591
 
29412
29592
  // src/mcp/config-manager.ts
29413
- import { join as join41 } from "path";
29593
+ import { join as join42 } from "path";
29414
29594
  var ProjectMcpConfigManager = class {
29415
29595
  path;
29416
29596
  constructor(workspace) {
29417
- this.path = join41(workspace, ".flavor", "flavor.json");
29597
+ this.path = join42(workspace, ".flavor", "flavor.json");
29418
29598
  }
29419
29599
  async list() {
29420
29600
  const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
@@ -29511,7 +29691,7 @@ function isRecord7(value) {
29511
29691
  }
29512
29692
 
29513
29693
  // src/desktop/model-config.ts
29514
- import { join as join42 } from "path";
29694
+ import { join as join43 } from "path";
29515
29695
  var DEFAULT_DESKTOP_MODELS = [
29516
29696
  {
29517
29697
  id: "anthropic:deepseek-v4-pro",
@@ -29532,7 +29712,7 @@ var DEFAULT_DESKTOP_MODELS = [
29532
29712
  ];
29533
29713
  async function loadDesktopModels(workspace, home) {
29534
29714
  try {
29535
- const tokens2 = await createFileTokenStore(join42(home, ".flavor-code", "auth.json")).load();
29715
+ const tokens2 = await createFileTokenStore(join43(home, ".flavor-code", "auth.json")).load();
29536
29716
  const pkceModels = [];
29537
29717
  const knownPkce = /* @__PURE__ */ new Set();
29538
29718
  for (const token of Object.values(tokens2)) {
@@ -29677,33 +29857,33 @@ function pngDataUrl(png) {
29677
29857
  }
29678
29858
  async function detectD2cFramework(workspace, task) {
29679
29859
  try {
29680
- const pkg = JSON.parse(await readFile35(join43(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
29860
+ const pkg = JSON.parse(await readFile35(join44(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
29681
29861
  return pkg.dependencies?.react === void 0 ? "vue" : "react";
29682
29862
  } catch {
29683
29863
  return "vue";
29684
29864
  }
29685
29865
  }
29686
29866
  function integrationDirectory(workspace, task) {
29687
- return join43(workspace, ".flavor", "d2c", task, "integration");
29867
+ return join44(workspace, ".flavor", "d2c", task, "integration");
29688
29868
  }
29689
29869
  var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
29690
29870
  var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
29691
29871
  async function d2cBackendSourceFingerprint(project) {
29692
- const runtimeManifest = join43(project, "server", "flavor-runtime.json");
29872
+ const runtimeManifest = join44(project, "server", "flavor-runtime.json");
29693
29873
  const realBackend = (await stat15(runtimeManifest).catch(() => void 0))?.isFile() === true;
29694
- const root = realBackend ? join43(project, "server") : join43(project, "mock");
29874
+ const root = realBackend ? join44(project, "server") : join44(project, "mock");
29695
29875
  if ((await stat15(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
29696
29876
  const files = [];
29697
29877
  const visit = async (directory) => {
29698
29878
  const entries = await readdir12(directory, { withFileTypes: true });
29699
29879
  for (const entry2 of entries) {
29700
29880
  if (entry2.isDirectory()) {
29701
- if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join43(directory, entry2.name));
29881
+ if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join44(directory, entry2.name));
29702
29882
  continue;
29703
29883
  }
29704
29884
  if (!entry2.isFile()) continue;
29705
29885
  const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
29706
- if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join43(directory, entry2.name));
29886
+ if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join44(directory, entry2.name));
29707
29887
  }
29708
29888
  };
29709
29889
  await visit(root);
@@ -30257,7 +30437,7 @@ var DesktopRuntimeController = class {
30257
30437
  if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
30258
30438
  throw new Error("Complete visual review before importing OpenAPI");
30259
30439
  }
30260
- const moduleManifest = join43(d2cOutputDirectory(workspace, task), "d2c.modules.json");
30440
+ const moduleManifest = join44(d2cOutputDirectory(workspace, task), "d2c.modules.json");
30261
30441
  const moduleManifestInfo = await stat15(moduleManifest).catch(() => void 0);
30262
30442
  if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
30263
30443
  throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
@@ -30270,8 +30450,8 @@ var DesktopRuntimeController = class {
30270
30450
  await mkdir27(dir, { recursive: true });
30271
30451
  const hash = createHash27("sha256").update(raw).digest("hex");
30272
30452
  await Promise.all([
30273
- writeFile17(join43(dir, "swagger.json"), raw),
30274
- writeFile17(join43(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
30453
+ writeFile17(join44(dir, "swagger.json"), raw),
30454
+ writeFile17(join44(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
30275
30455
  `)
30276
30456
  ]);
30277
30457
  const next = {
@@ -30306,7 +30486,7 @@ var DesktopRuntimeController = class {
30306
30486
  let workflow = await readWorkflow(workspace, task);
30307
30487
  if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
30308
30488
  const modules = await readD2cModules(workspace, task);
30309
- const productOpenApiPath = join43(taskDir(workspace, task), "product", "openapi.json");
30489
+ const productOpenApiPath = join44(taskDir(workspace, task), "product", "openapi.json");
30310
30490
  const authored = await readFile35(productOpenApiPath, "utf8").catch(() => void 0);
30311
30491
  const generated = authored === void 0;
30312
30492
  const prepared = await this.#storeD2cOpenApi(
@@ -30319,7 +30499,7 @@ var DesktopRuntimeController = class {
30319
30499
  return prepared;
30320
30500
  }
30321
30501
  if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
30322
- const document = JSON.parse(await readFile35(join43(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
30502
+ const document = JSON.parse(await readFile35(join44(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
30323
30503
  return { workflow, document, mappings: workflow.mappings };
30324
30504
  }
30325
30505
  async confirmD2cMapping(task, moduleId, operationKey) {
@@ -30386,7 +30566,7 @@ var DesktopRuntimeController = class {
30386
30566
  ].join("\n");
30387
30567
  const delivery = await readDeliveryRun(workspace, task);
30388
30568
  if (delivery?.nodes.api.status === "running") {
30389
- const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join43(project, file)))));
30569
+ const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join44(project, file)))));
30390
30570
  const completed = await updateDeliveryRun(
30391
30571
  workspace,
30392
30572
  task,
@@ -30413,7 +30593,7 @@ var DesktopRuntimeController = class {
30413
30593
  try {
30414
30594
  const fingerprint2 = await d2cBackendSourceFingerprint(project);
30415
30595
  if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
30416
- const environmentPath = join43(project, ".env.local");
30596
+ const environmentPath = join44(project, ".env.local");
30417
30597
  const existingEnvironment = await readFile35(environmentPath, "utf8").catch(() => "");
30418
30598
  const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
30419
30599
  VITE_API_BASE_URL=${running.url}
@@ -30494,7 +30674,7 @@ VITE_API_BASE_URL=${running.url}
30494
30674
  const readyPreview = this.#d2cPreviews.get(task);
30495
30675
  const readyMock = this.#d2cMocks.get(task);
30496
30676
  if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
30497
- const manifestPath = join43(taskDir(workspace, task), "design", "interaction-manifest.json");
30677
+ const manifestPath = join44(taskDir(workspace, task), "design", "interaction-manifest.json");
30498
30678
  const manifest = parseInteractionManifest(await readFile35(manifestPath, "utf8"));
30499
30679
  const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
30500
30680
  const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
@@ -30506,7 +30686,7 @@ VITE_API_BASE_URL=${running.url}
30506
30686
  await mkdir27(planDir, { recursive: true });
30507
30687
  try {
30508
30688
  const observations = await this.#observeD2cPages(manifest, readyPreview.url);
30509
- const prd = await readFile35(join43(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
30689
+ const prd = await readFile35(join44(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
30510
30690
  const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
30511
30691
  const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
30512
30692
  executableManifest = mergeInteractionManifests(manifest, plan.manifest);
@@ -30517,9 +30697,9 @@ VITE_API_BASE_URL=${running.url}
30517
30697
  plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
30518
30698
  pageAnalyses: plan.pageAnalyses
30519
30699
  };
30520
- await writeFile17(join43(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
30700
+ await writeFile17(join44(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
30521
30701
  `, { encoding: "utf8", mode: 384 });
30522
- await rm15(join43(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
30702
+ await rm15(join44(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
30523
30703
  } catch (cause) {
30524
30704
  const detail = message(cause);
30525
30705
  review = {
@@ -30527,8 +30707,8 @@ VITE_API_BASE_URL=${running.url}
30527
30707
  plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
30528
30708
  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}`
30529
30709
  };
30530
- await rm15(join43(planDir, "autonomous-interaction-plan.json"), { force: true });
30531
- await writeFile17(join43(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
30710
+ await rm15(join44(planDir, "autonomous-interaction-plan.json"), { force: true });
30711
+ await writeFile17(join44(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
30532
30712
  schema: 1,
30533
30713
  stage: "planning",
30534
30714
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -30542,13 +30722,13 @@ VITE_API_BASE_URL=${running.url}
30542
30722
  let result = { ...rawResult, evidenceMode: review.mode };
30543
30723
  if (coverage !== void 0) {
30544
30724
  const evidence = createAcceptanceEvidence(coverage, result);
30545
- await writeFile17(join43(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
30725
+ await writeFile17(join44(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
30546
30726
  `, { encoding: "utf8", mode: 384 });
30547
30727
  if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
30548
30728
  }
30549
30729
  const dir = integrationDirectory(workspace, task);
30550
30730
  await mkdir27(dir, { recursive: true });
30551
- await writeFile17(join43(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
30731
+ await writeFile17(join44(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
30552
30732
  `);
30553
30733
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
30554
30734
  return { workflow: next, result, review };
@@ -30561,7 +30741,7 @@ VITE_API_BASE_URL=${running.url}
30561
30741
  if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
30562
30742
  const output = mock.output().trim();
30563
30743
  try {
30564
- const dir = join43(d2cOutputDirectory(workspace, task), "mock-server.log");
30744
+ const dir = join44(d2cOutputDirectory(workspace, task), "mock-server.log");
30565
30745
  await writeFile17(dir, `${output}
30566
30746
  `);
30567
30747
  } catch {
@@ -30656,7 +30836,7 @@ VITE_API_BASE_URL=${running.url}
30656
30836
  if (current === void 0) throw new Error("D2C product plan is unavailable");
30657
30837
  if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
30658
30838
  if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
30659
- const path = join43(taskDir(workspace, task), "product", "prd.md");
30839
+ const path = join44(taskDir(workspace, task), "product", "prd.md");
30660
30840
  const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
30661
30841
  const now = (/* @__PURE__ */ new Date()).toISOString();
30662
30842
  await writeD2cProductPlan(workspace, {
@@ -30676,15 +30856,15 @@ VITE_API_BASE_URL=${running.url}
30676
30856
  async #assertProductPrdLocked(workspace, task) {
30677
30857
  const current = await readD2cProductPlanView(workspace, task);
30678
30858
  if (current?.plan.approvedPrd === void 0) return;
30679
- await assertApprovedPrd(join43(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
30859
+ await assertApprovedPrd(join44(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
30680
30860
  }
30681
30861
  async #acceptancePaths(workspace, task) {
30682
- const product = join43(taskDir(workspace, task), "product");
30683
- const openapi = join43(product, "openapi.json");
30862
+ const product = join44(taskDir(workspace, task), "product");
30863
+ const openapi = join44(product, "openapi.json");
30684
30864
  return {
30685
- prd: join43(product, "prd.md"),
30686
- prototype: join43(product, "prototype", "index.html"),
30687
- interaction: join43(product, "prototype", "interaction-manifest.json"),
30865
+ prd: join44(product, "prd.md"),
30866
+ prototype: join44(product, "prototype", "index.html"),
30867
+ interaction: join44(product, "prototype", "interaction-manifest.json"),
30688
30868
  ...await stat15(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
30689
30869
  };
30690
30870
  }
@@ -30692,17 +30872,17 @@ VITE_API_BASE_URL=${running.url}
30692
30872
  const product = await readD2cProductPlanView(workspace, task);
30693
30873
  if (product?.plan.approvedPrd === void 0) return void 0;
30694
30874
  await this.#assertProductPrdLocked(workspace, task);
30695
- const baseline = JSON.parse(await readFile35(join43(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
30875
+ const baseline = JSON.parse(await readFile35(join44(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
30696
30876
  await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
30697
- const design = join43(taskDir(workspace, task), "design");
30877
+ const design = join44(taskDir(workspace, task), "design");
30698
30878
  await Promise.all([
30699
- verifyAcceptanceArtifact(join43(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
30700
- verifyAcceptanceArtifact(join43(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
30879
+ verifyAcceptanceArtifact(join44(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
30880
+ verifyAcceptanceArtifact(join44(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
30701
30881
  ]);
30702
30882
  const workflow = await readWorkflow(workspace, task);
30703
30883
  if (workflow?.openapi !== void 0) {
30704
30884
  await verifyAcceptanceArtifact(
30705
- join43(taskDir(workspace, task), "integration", "swagger.json"),
30885
+ join44(taskDir(workspace, task), "integration", "swagger.json"),
30706
30886
  { hash: workflow.openapi.hash },
30707
30887
  "runtime OpenAPI contract"
30708
30888
  );
@@ -30723,14 +30903,14 @@ VITE_API_BASE_URL=${running.url}
30723
30903
  throw new Error("D2C PRD is empty and cannot be accepted");
30724
30904
  }
30725
30905
  if (stage === "design" && accepted) {
30726
- const manifest = join43(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
30906
+ const manifest = join44(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
30727
30907
  const normalizedManifest = parseInteractionManifest(await readFile35(manifest, "utf8"));
30728
30908
  if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
30729
30909
  verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
30730
30910
  await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
30731
30911
  `, { encoding: "utf8", mode: 384 });
30732
30912
  const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
30733
- await writeFile17(join43(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
30913
+ await writeFile17(join44(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
30734
30914
  `, { encoding: "utf8", mode: 384 });
30735
30915
  }
30736
30916
  const plan = await writeD2cProductPlan(
@@ -30764,8 +30944,8 @@ VITE_API_BASE_URL=${running.url}
30764
30944
  const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
30765
30945
  const delivery = await readDeliveryRun(workspace, task);
30766
30946
  if (delivery !== void 0) {
30767
- const prototype = await readFile35(join43(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
30768
- const interaction = await readFile35(join43(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
30947
+ const prototype = await readFile35(join44(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
30948
+ const interaction = await readFile35(join44(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
30769
30949
  const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
30770
30950
  artifactRef("product/prototype/index.html", prototype),
30771
30951
  artifactRef("product/prototype/interaction-manifest.json", interaction)
@@ -30849,10 +31029,10 @@ VITE_API_BASE_URL=${running.url}
30849
31029
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
30850
31030
  const qualityContent = `${JSON.stringify(judgment, null, 2)}
30851
31031
  `;
30852
- await writeFile17(join43(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
31032
+ await writeFile17(join44(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
30853
31033
  const delivery = await readDeliveryRun(workspace, task);
30854
31034
  if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
30855
- const interactionContent = await readFile35(join43(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
31035
+ const interactionContent = await readFile35(join44(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
30856
31036
  const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
30857
31037
  artifactRef("integration/interaction-results.json", interactionContent),
30858
31038
  artifactRef("quality-judge.json", qualityContent)
@@ -31033,15 +31213,15 @@ function desktopWindowChrome(platform = process.platform) {
31033
31213
  }
31034
31214
 
31035
31215
  // src/utils/crash-guard.ts
31036
- import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
31037
- import { join as join44 } from "path";
31216
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
31217
+ import { join as join45 } from "path";
31038
31218
  var installed = false;
31039
31219
  function installCrashGuard(options = {}) {
31040
31220
  if (installed) return;
31041
31221
  installed = true;
31042
31222
  const crashLogDir = () => {
31043
31223
  try {
31044
- return join44((options.workspace ?? process.cwd)(), ".flavor");
31224
+ return join45((options.workspace ?? process.cwd)(), ".flavor");
31045
31225
  } catch {
31046
31226
  return process.cwd();
31047
31227
  }
@@ -31049,9 +31229,9 @@ function installCrashGuard(options = {}) {
31049
31229
  const writeCrashLog = (kind, detail) => {
31050
31230
  try {
31051
31231
  const dir = crashLogDir();
31052
- mkdirSync3(dir, { recursive: true });
31053
- const path = join44(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
31054
- writeFileSync(path, [
31232
+ mkdirSync4(dir, { recursive: true });
31233
+ const path = join45(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
31234
+ writeFileSync2(path, [
31055
31235
  `kind: ${kind}`,
31056
31236
  `version: ${process.version}`,
31057
31237
  `platform: ${process.platform}`,
@@ -31243,7 +31423,7 @@ function safeGitTarget(value) {
31243
31423
  // src/desktop/worktree-manager.ts
31244
31424
  import { randomUUID as randomUUID28 } from "crypto";
31245
31425
  import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat16, writeFile as writeFile18 } from "fs/promises";
31246
- import { basename as basename10, isAbsolute as isAbsolute19, join as join45, relative as relative27, resolve as resolve38, sep as sep27 } from "path";
31426
+ import { basename as basename10, isAbsolute as isAbsolute19, join as join46, relative as relative27, resolve as resolve38, sep as sep27 } from "path";
31247
31427
  var DesktopWorktreeManager = class {
31248
31428
  #repository;
31249
31429
  #storage;
@@ -31289,7 +31469,7 @@ var DesktopWorktreeManager = class {
31289
31469
  }
31290
31470
  const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
31291
31471
  try {
31292
- const stored = validateStored(JSON.parse(await readFile37(join45(this.#storage, name), "utf8")), this.#repository, this.#storage);
31472
+ const stored = validateStored(JSON.parse(await readFile37(join46(this.#storage, name), "utf8")), this.#repository, this.#storage);
31293
31473
  if ((await stat16(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
31294
31474
  const status = await git2(stored.path, ["status", "--porcelain"]);
31295
31475
  const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
@@ -31353,7 +31533,7 @@ function validateStored(value, repository, storage) {
31353
31533
  // src/desktop/workbench-service.ts
31354
31534
  import { readFile as readFile38, readdir as readdir14, stat as stat17 } from "fs/promises";
31355
31535
  import { homedir as homedir5 } from "os";
31356
- import { join as join46, resolve as resolve39 } from "path";
31536
+ import { join as join47, resolve as resolve39 } from "path";
31357
31537
  var MAX_TEXT = 128e3;
31358
31538
  var MAX_ITEMS = 200;
31359
31539
  var DesktopWorkbenchService = class {
@@ -31386,11 +31566,11 @@ var DesktopWorkbenchService = class {
31386
31566
  return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
31387
31567
  }
31388
31568
  async #goals() {
31389
- const root = join46(this.#workspace, ".flavor", "goals");
31569
+ const root = join47(this.#workspace, ".flavor", "goals");
31390
31570
  const names = await readdir14(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
31391
31571
  const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
31392
31572
  try {
31393
- return GoalStateSchema.parse(JSON.parse(await boundedRead2(join46(root, name))));
31573
+ return GoalStateSchema.parse(JSON.parse(await boundedRead2(join47(root, name))));
31394
31574
  } catch {
31395
31575
  return void 0;
31396
31576
  }
@@ -31400,7 +31580,7 @@ var DesktopWorkbenchService = class {
31400
31580
  async #instructions() {
31401
31581
  const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
31402
31582
  const items = await Promise.all(names.map(async (name) => {
31403
- const path = join46(this.#workspace, name);
31583
+ const path = join47(this.#workspace, name);
31404
31584
  try {
31405
31585
  return { name, path, content: redactErrorText(await boundedRead2(path)) };
31406
31586
  } catch (error) {
@@ -31413,9 +31593,9 @@ var DesktopWorkbenchService = class {
31413
31593
  async #permissionFiles() {
31414
31594
  const candidates = [
31415
31595
  ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve39(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
31416
- { tier: "user", path: join46(this.#home, ".flavor-code", "permissions.json"), content: "" },
31417
- { tier: "project", path: join46(this.#workspace, ".flavor", "permissions.json"), content: "" },
31418
- { tier: "local", path: join46(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
31596
+ { tier: "user", path: join47(this.#home, ".flavor-code", "permissions.json"), content: "" },
31597
+ { tier: "project", path: join47(this.#workspace, ".flavor", "permissions.json"), content: "" },
31598
+ { tier: "local", path: join47(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
31419
31599
  ];
31420
31600
  const values = await Promise.all(candidates.map(async (item) => {
31421
31601
  try {
@@ -31430,7 +31610,7 @@ var DesktopWorkbenchService = class {
31430
31610
  async #audit() {
31431
31611
  let raw;
31432
31612
  try {
31433
- raw = await boundedRead2(join46(this.#workspace, ".flavor", "audit.jsonl"));
31613
+ raw = await boundedRead2(join47(this.#workspace, ".flavor", "audit.jsonl"));
31434
31614
  } catch (error) {
31435
31615
  if (error.code === "ENOENT") return [];
31436
31616
  throw error;
@@ -31445,7 +31625,7 @@ var DesktopWorkbenchService = class {
31445
31625
  }).reverse();
31446
31626
  }
31447
31627
  async #context(sessionId) {
31448
- const usage = await readJsonLines2(join46(this.#workspace, ".flavor", "usage.jsonl"));
31628
+ const usage = await readJsonLines2(join47(this.#workspace, ".flavor", "usage.jsonl"));
31449
31629
  if (sessionId === void 0) return { visibility: [], usage };
31450
31630
  try {
31451
31631
  const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
@@ -31485,14 +31665,14 @@ function redact2(value, key = "") {
31485
31665
 
31486
31666
  // src/desktop/main.ts
31487
31667
  installCrashGuard();
31488
- var moduleDirectory = dirname24(fileURLToPath4(import.meta.url));
31668
+ var moduleDirectory = dirname25(fileURLToPath4(import.meta.url));
31489
31669
  var developmentUrl = process.env.FLAVOR_DESKTOP_DEV_URL;
31490
31670
  var mainWindow;
31491
31671
  var appMenu;
31492
31672
  var quitting = false;
31493
31673
  function logStartup(step, detail) {
31494
31674
  try {
31495
- const logPath = join47(dirname24(process.execPath), "flavor-code-startup.log");
31675
+ const logPath = join48(dirname25(process.execPath), "flavor-code-startup.log");
31496
31676
  const ts = (/* @__PURE__ */ new Date()).toISOString();
31497
31677
  appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
31498
31678
  `);
@@ -31517,7 +31697,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
31517
31697
  };
31518
31698
  });
31519
31699
  var d2cJudgeClient = createD2cJudgeClient();
31520
- var judgeStore = () => createD2cJudgeConfigStore(join47(app.getPath("userData"), "d2c-judge.json"));
31700
+ var judgeStore = () => createD2cJudgeConfigStore(join48(app.getPath("userData"), "d2c-judge.json"));
31521
31701
  function judgeImage(png) {
31522
31702
  const image = nativeImage.createFromBuffer(png);
31523
31703
  const size = image.getSize();
@@ -31543,7 +31723,7 @@ function sessionMetaKey(workspace, sessionId) {
31543
31723
  }
31544
31724
  function worktreeManager(workspace) {
31545
31725
  const key = createHash28("sha256").update(resolve40(workspace)).digest("hex").slice(0, 20);
31546
- return new DesktopWorktreeManager({ repository: workspace, storage: join47(app.getPath("userData"), "task-worktrees", key) });
31726
+ return new DesktopWorktreeManager({ repository: workspace, storage: join48(app.getPath("userData"), "task-worktrees", key) });
31547
31727
  }
31548
31728
  function activeWorkingDirectory() {
31549
31729
  if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
@@ -31688,7 +31868,7 @@ function createDesktopController(workspace, owner2) {
31688
31868
  alias: `desktop-${basename11(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
31689
31869
  optional: true,
31690
31870
  startBroker: (address) => {
31691
- const entry2 = app.isPackaged ? join47(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join47(moduleDirectory, "pals-broker-entry.js");
31871
+ const entry2 = app.isPackaged ? join48(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join48(moduleDirectory, "pals-broker-entry.js");
31692
31872
  return spawnPalBrokerProcess({
31693
31873
  address,
31694
31874
  executable: process.execPath,
@@ -31710,7 +31890,7 @@ function createDesktopController(workspace, owner2) {
31710
31890
  var detachedController = createDesktopController();
31711
31891
  var controller = detachedController;
31712
31892
  function statePath() {
31713
- return join47(app.getPath("userData"), "desktop-state.json");
31893
+ return join48(app.getPath("userData"), "desktop-state.json");
31714
31894
  }
31715
31895
  async function loadPersistedProjects() {
31716
31896
  try {
@@ -31883,7 +32063,7 @@ async function chooseAndOpenWorkspace() {
31883
32063
  var cachedAppIconDataUrl;
31884
32064
  function appIconDataUrl() {
31885
32065
  if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
31886
- const icon = nativeImage.createFromPath(join47(app.getAppPath(), "assets", "icon.png"));
32066
+ const icon = nativeImage.createFromPath(join48(app.getAppPath(), "assets", "icon.png"));
31887
32067
  if (icon.isEmpty()) return void 0;
31888
32068
  cachedAppIconDataUrl = icon.toDataURL();
31889
32069
  return cachedAppIconDataUrl;
@@ -32301,7 +32481,7 @@ function applicationMenu() {
32301
32481
  ]);
32302
32482
  }
32303
32483
  async function createWindow() {
32304
- const rendererPath = join47(app.getAppPath(), "dist", "desktop-renderer", "index.html");
32484
+ const rendererPath = join48(app.getAppPath(), "dist", "desktop-renderer", "index.html");
32305
32485
  mainWindow = new BrowserWindow2({
32306
32486
  title: "Flavor Code",
32307
32487
  width: 1280,
@@ -32312,14 +32492,14 @@ async function createWindow() {
32312
32492
  show: false,
32313
32493
  ...desktopWindowChrome(),
32314
32494
  webPreferences: {
32315
- preload: join47(moduleDirectory, "preload.cjs"),
32495
+ preload: join48(moduleDirectory, "preload.cjs"),
32316
32496
  contextIsolation: true,
32317
32497
  nodeIntegration: false,
32318
32498
  sandbox: false,
32319
32499
  webSecurity: true
32320
32500
  }
32321
32501
  });
32322
- const iconPath = join47(app.getAppPath(), "assets", "icon.png");
32502
+ const iconPath = join48(app.getAppPath(), "assets", "icon.png");
32323
32503
  const icon = nativeImage.createFromPath(iconPath);
32324
32504
  if (!icon.isEmpty()) {
32325
32505
  mainWindow.setIcon(icon);