flavor-code 1.3.19 → 1.3.20

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 dirname25, join as join48, resolve as resolve40 } from "path";
5
+ import { basename as basename11, dirname as dirname25, join as join48, resolve as resolve41 } 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
 
@@ -272,7 +272,8 @@ var FlavorConfigSchema = z2.object({
272
272
  autoStoreThreshold: z2.number().int().min(0).max(12).default(11),
273
273
  ignoreStreakLimit: z2.number().int().min(2).max(100).default(5),
274
274
  reviewAutoDismissSeconds: z2.number().int().min(0).max(300).default(5),
275
- maxCandidatesPerTask: z2.number().int().min(1).max(10).default(1),
275
+ // One candidate per task is a deliberate memory-quality boundary, not a tunable range.
276
+ maxCandidatesPerTask: z2.literal(1).default(1),
276
277
  retrievalTopK: z2.number().int().min(1).max(20).default(5),
277
278
  maxEntries: z2.number().int().min(1).max(1e4).default(200),
278
279
  maxEntryChars: z2.number().int().min(32).max(2e4).default(1e3),
@@ -1989,7 +1990,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
1989
1990
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
1990
1991
  if (ready === "complete" || ready === "interactive") return candidate;
1991
1992
  }
1992
- await new Promise((resolve41) => setTimeout(resolve41, pollIntervalMs));
1993
+ await new Promise((resolve42) => setTimeout(resolve42, pollIntervalMs));
1993
1994
  }
1994
1995
  throw new Error(`D2C embedded preview did not navigate within ${navigationTimeoutMs} ms: ${url}${observedUrl === void 0 ? "" : `; last observed ${observedUrl}`}${observedFrames.length === 0 ? "; no live frames" : `; live frames ${observedFrames.join(", ")}`}`);
1995
1996
  };
@@ -2004,13 +2005,13 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2004
2005
  while (Date.now() <= deadline) {
2005
2006
  last = await frame(baseUrl).executeJavaScript(assertionScript(step), true);
2006
2007
  if (last.passed) return last;
2007
- await new Promise((resolve41) => setTimeout(resolve41, 50));
2008
+ await new Promise((resolve42) => setTimeout(resolve42, 50));
2008
2009
  }
2009
2010
  return last;
2010
2011
  };
2011
2012
  const settleAfterAction = async (previousUrl) => {
2012
2013
  const origin = new URL(previousUrl).origin;
2013
- await new Promise((resolve41) => setTimeout(resolve41, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
2014
+ await new Promise((resolve42) => setTimeout(resolve42, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
2014
2015
  const deadline = Date.now() + Math.min(2e3, navigationTimeoutMs);
2015
2016
  while (Date.now() <= deadline) {
2016
2017
  const candidate = host().mainFrame.framesInSubtree.find((item) => !item.isDestroyed() && sameOrigin(item.url, origin));
@@ -2018,7 +2019,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2018
2019
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
2019
2020
  if (ready === "complete" || ready === "interactive") return candidate;
2020
2021
  }
2021
- await new Promise((resolve41) => setTimeout(resolve41, pollIntervalMs));
2022
+ await new Promise((resolve42) => setTimeout(resolve42, pollIntervalMs));
2022
2023
  }
2023
2024
  throw new Error("D2C preview frame was unavailable after an interaction action");
2024
2025
  };
@@ -2105,7 +2106,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2105
2106
  return result;
2106
2107
  },
2107
2108
  settle: async () => {
2108
- await new Promise((resolve41) => setTimeout(resolve41, 150));
2109
+ await new Promise((resolve42) => setTimeout(resolve42, 150));
2109
2110
  requests = await frame(activeUrl).executeJavaScript(`performance.getEntriesByType("resource").filter((entry) => {
2110
2111
  try { return ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && new URL(entry.name).origin === ${JSON.stringify(mockOrigin)}; } catch { return false; }
2111
2112
  }).length`, false).catch(() => 0);
@@ -2191,7 +2192,7 @@ function responseText(protocol, value) {
2191
2192
  }
2192
2193
  throw new Error("D2C Judge response did not contain text output");
2193
2194
  }
2194
- function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve41) => setTimeout(resolve41, ms))) {
2195
+ function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve42) => setTimeout(resolve42, ms))) {
2195
2196
  const request = async (config, prompt, images, maxTokens) => {
2196
2197
  const openAi = config.protocol === "openai-compatible";
2197
2198
  const imageContent = openAi ? images.map((image) => ({ type: "image_url", image_url: { url: dataUrl(image), detail: "high" } })) : images.map((image) => ({ type: "image", source: { type: "base64", media_type: "image/png", data: image.toString("base64") } }));
@@ -2550,7 +2551,7 @@ import { z as z7 } from "zod";
2550
2551
  import { Worker } from "worker_threads";
2551
2552
  function comparePngsInWorker(left, right, signal) {
2552
2553
  signal?.throwIfAborted();
2553
- return new Promise((resolve41, reject) => {
2554
+ return new Promise((resolve42, reject) => {
2554
2555
  const worker = new Worker(new URL("./pixel-worker.js", import.meta.url));
2555
2556
  let settled = false;
2556
2557
  const finish = (callback) => {
@@ -2568,7 +2569,7 @@ function comparePngsInWorker(left, right, signal) {
2568
2569
  finish(() => reject(new Error(value.error ?? "D2C pixel worker failed")));
2569
2570
  return;
2570
2571
  }
2571
- finish(() => resolve41({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2572
+ finish(() => resolve42({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2572
2573
  });
2573
2574
  worker.once("error", (error) => finish(() => reject(error)));
2574
2575
  worker.once("exit", (code) => {
@@ -4225,10 +4226,10 @@ ${summarizeReport(report2)}`).join("\n\n")
4225
4226
 
4226
4227
  // src/production.ts
4227
4228
  import { readFile as readFile27 } from "fs/promises";
4228
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
4229
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
4229
4230
  import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
4230
4231
  import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
4231
- import { basename as basename8, dirname as dirname24, join as join34, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
4232
+ import { basename as basename8, dirname as dirname24, join as join35, relative as relative23, resolve as resolve34, sep as sep23 } from "path";
4232
4233
 
4233
4234
  // src/agent/subagents.ts
4234
4235
  import { z as z10 } from "zod";
@@ -4441,12 +4442,12 @@ function hasCycle(nodes) {
4441
4442
 
4442
4443
  // src/utils/async.ts
4443
4444
  function withTimeout(promise, ms) {
4444
- return new Promise((resolve41, reject) => {
4445
+ return new Promise((resolve42, reject) => {
4445
4446
  let settled = false;
4446
4447
  const timer = setTimeout(() => {
4447
4448
  if (settled) return;
4448
4449
  settled = true;
4449
- resolve41({ timedOut: true, value: void 0 });
4450
+ resolve42({ timedOut: true, value: void 0 });
4450
4451
  }, ms);
4451
4452
  timer.unref?.();
4452
4453
  promise.then(
@@ -4454,7 +4455,7 @@ function withTimeout(promise, ms) {
4454
4455
  if (!settled) {
4455
4456
  settled = true;
4456
4457
  clearTimeout(timer);
4457
- resolve41({ timedOut: false, value });
4458
+ resolve42({ timedOut: false, value });
4458
4459
  }
4459
4460
  },
4460
4461
  (error) => {
@@ -4469,13 +4470,13 @@ function withTimeout(promise, ms) {
4469
4470
  }
4470
4471
  function awaitWithSignal3(promise, signal) {
4471
4472
  signal.throwIfAborted();
4472
- return new Promise((resolve41, reject) => {
4473
+ return new Promise((resolve42, reject) => {
4473
4474
  const onAbort = () => reject(signal.reason);
4474
4475
  signal.addEventListener("abort", onAbort, { once: true });
4475
4476
  promise.then(
4476
4477
  (value) => {
4477
4478
  signal.removeEventListener("abort", onAbort);
4478
- resolve41(value);
4479
+ resolve42(value);
4479
4480
  },
4480
4481
  (error) => {
4481
4482
  signal.removeEventListener("abort", onAbort);
@@ -6524,10 +6525,10 @@ function sanitize(message2, secrets) {
6524
6525
  }
6525
6526
  function waitForRetry(delayMs, signal) {
6526
6527
  if (signal?.aborted) return Promise.reject(signal.reason);
6527
- return new Promise((resolve41, reject) => {
6528
+ return new Promise((resolve42, reject) => {
6528
6529
  const timer = setTimeout(() => {
6529
6530
  signal?.removeEventListener("abort", onAbort);
6530
- resolve41();
6531
+ resolve42();
6531
6532
  }, delayMs);
6532
6533
  const onAbort = () => {
6533
6534
  clearTimeout(timer);
@@ -7165,10 +7166,10 @@ function serializeToolInput(input) {
7165
7166
  }
7166
7167
  function waitForRetry2(delayMs, signal) {
7167
7168
  if (signal?.aborted) return Promise.reject(signal.reason);
7168
- return new Promise((resolve41, reject) => {
7169
+ return new Promise((resolve42, reject) => {
7169
7170
  const timer = setTimeout(() => {
7170
7171
  signal?.removeEventListener("abort", onAbort);
7171
- resolve41();
7172
+ resolve42();
7172
7173
  }, delayMs);
7173
7174
  const onAbort = () => {
7174
7175
  clearTimeout(timer);
@@ -8713,6 +8714,64 @@ var HookDecisionSchema = z16.object({
8713
8714
  additionalContext: z16.string().optional()
8714
8715
  });
8715
8716
 
8717
+ // src/utils/spawn-executable.ts
8718
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
8719
+ import { extname as extname2, isAbsolute as isAbsolute6, resolve as resolve9 } from "path";
8720
+ var WINDOWS_EXECUTABLE = /\.(?:com|exe)$/iu;
8721
+ var WINDOWS_CMD_SHIM = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/iu;
8722
+ var CMD_META = /([()\][%!^"`<>&|;, *?])/gu;
8723
+ function prepareSpawnInvocation(command, args, options = {}) {
8724
+ if ((options.platform ?? process.platform) !== "win32") return { command, args: [...args] };
8725
+ const env = options.env ?? process.env;
8726
+ const commandFile = resolveWindowsCommand(command, options.cwd, env);
8727
+ if (commandFile === void 0 || WINDOWS_EXECUTABLE.test(commandFile)) {
8728
+ return { command: commandFile ?? command, args: [...args] };
8729
+ }
8730
+ const doubleEscape = WINDOWS_CMD_SHIM.test(commandFile);
8731
+ const shellCommand = [escapeCmdCommand(commandFile), ...args.map((arg) => escapeCmdArgument(arg, doubleEscape))].join(" ");
8732
+ return {
8733
+ command: environmentValue(env, "ComSpec") ?? "cmd.exe",
8734
+ args: ["/d", "/s", "/c", `"${shellCommand}"`],
8735
+ windowsVerbatimArguments: true
8736
+ };
8737
+ }
8738
+ function resolveWindowsCommand(command, cwd, env) {
8739
+ const pathValue = environmentValue(env, "PATH") ?? "";
8740
+ const pathExt = (environmentValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
8741
+ const hasPath = isAbsolute6(command) || /[\\/]/u.test(command);
8742
+ const bases = hasPath ? [isAbsolute6(command) ? command : resolve9(cwd ?? process.cwd(), command)] : pathValue.split(";").filter(Boolean).map((entry2) => resolve9(unquote(entry2), command));
8743
+ for (const base of bases) {
8744
+ const candidates = extname2(base).length > 0 ? [base] : [base, ...pathExt.map((extension2) => `${base}${extension2}`)];
8745
+ for (const candidate of candidates) {
8746
+ if (isRegularFile(candidate)) return resolve9(candidate);
8747
+ }
8748
+ }
8749
+ return void 0;
8750
+ }
8751
+ function environmentValue(env, name) {
8752
+ const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
8753
+ return key === void 0 ? void 0 : env[key];
8754
+ }
8755
+ function unquote(value) {
8756
+ const trimmed = value.trim();
8757
+ return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
8758
+ }
8759
+ function isRegularFile(path) {
8760
+ try {
8761
+ return existsSync3(path) && statSync2(path).isFile();
8762
+ } catch {
8763
+ return false;
8764
+ }
8765
+ }
8766
+ function escapeCmdCommand(value) {
8767
+ return value.replace(CMD_META, "^$1");
8768
+ }
8769
+ function escapeCmdArgument(value, doubleEscapeMetaCharacters) {
8770
+ let escaped = value.replace(/(?=(\\+?)?)\1"/gu, '$1$1\\"').replace(/(?=(\\+?)?)\1$/gu, "$1$1");
8771
+ escaped = `"${escaped}"`.replace(CMD_META, "^$1");
8772
+ return doubleEscapeMetaCharacters ? escaped.replace(CMD_META, "^$1") : escaped;
8773
+ }
8774
+
8716
8775
  // src/hooks/bus.ts
8717
8776
  var DEFAULT_TIMEOUT_MS = 1e4;
8718
8777
  var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
@@ -8819,13 +8878,16 @@ function decoratePayload(payload, envelope) {
8819
8878
  return envelope === void 0 ? payload : { ...payload, ...envelope };
8820
8879
  }
8821
8880
  function runShellHandler(descriptor, event, signal) {
8822
- return new Promise((resolve41, reject) => {
8823
- const child = spawn(descriptor.command, [...descriptor.args ?? []], {
8824
- env: { ...process.env, ...descriptor.env },
8881
+ return new Promise((resolve42, reject) => {
8882
+ const env = { ...process.env, ...descriptor.env };
8883
+ const invocation = prepareSpawnInvocation(descriptor.command, descriptor.args ?? [], { env });
8884
+ const child = spawn(invocation.command, invocation.args, {
8885
+ env,
8825
8886
  shell: false,
8826
8887
  detached: process.platform !== "win32",
8827
8888
  windowsHide: true,
8828
- stdio: ["pipe", "pipe", "pipe"]
8889
+ stdio: ["pipe", "pipe", "pipe"],
8890
+ ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
8829
8891
  });
8830
8892
  const maxOutputBytes = descriptor.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
8831
8893
  let stdout = "";
@@ -8862,7 +8924,7 @@ function runShellHandler(descriptor, event, signal) {
8862
8924
  return;
8863
8925
  }
8864
8926
  try {
8865
- resolve41(HookDecisionSchema.parse(JSON.parse(stdout)));
8927
+ resolve42(HookDecisionSchema.parse(JSON.parse(stdout)));
8866
8928
  } catch (error) {
8867
8929
  reject(error);
8868
8930
  }
@@ -8901,7 +8963,7 @@ function zodRecord(payload) {
8901
8963
  // src/utils/execFileNoThrow.ts
8902
8964
  import { execFile } from "child_process";
8903
8965
  function execFileNoThrow(file, args, options = {}) {
8904
- return new Promise((resolve41) => {
8966
+ return new Promise((resolve42) => {
8905
8967
  const child = execFile(file, args, {
8906
8968
  timeout: options.timeout,
8907
8969
  cwd: options.useCwd === false ? void 0 : process.cwd(),
@@ -8909,7 +8971,7 @@ function execFileNoThrow(file, args, options = {}) {
8909
8971
  encoding: "utf8",
8910
8972
  windowsHide: true
8911
8973
  }, (error, stdout, stderr) => {
8912
- resolve41({
8974
+ resolve42({
8913
8975
  stdout,
8914
8976
  stderr,
8915
8977
  code: typeof error?.code === "number" ? error.code : error ? 1 : 0,
@@ -9081,14 +9143,14 @@ async function createIslandControlServer(options) {
9081
9143
  void dispatch(line, socket, options, token);
9082
9144
  });
9083
9145
  });
9084
- await new Promise((resolve41, reject) => {
9146
+ await new Promise((resolve42, reject) => {
9085
9147
  const onError = (error) => {
9086
9148
  server.removeListener("listening", onListening);
9087
9149
  reject(error);
9088
9150
  };
9089
9151
  const onListening = () => {
9090
9152
  server.removeListener("error", onError);
9091
- resolve41();
9153
+ resolve42();
9092
9154
  };
9093
9155
  server.once("error", onError);
9094
9156
  server.once("listening", onListening);
@@ -9159,11 +9221,11 @@ function write(socket, payload) {
9159
9221
  }
9160
9222
  }
9161
9223
  function closeServer(server) {
9162
- return new Promise((resolve41) => {
9224
+ return new Promise((resolve42) => {
9163
9225
  try {
9164
- server.close(() => resolve41());
9226
+ server.close(() => resolve42());
9165
9227
  } catch {
9166
- resolve41();
9228
+ resolve42();
9167
9229
  }
9168
9230
  });
9169
9231
  }
@@ -9177,13 +9239,13 @@ import { z as z19 } from "zod";
9177
9239
  // src/evolve/loader.ts
9178
9240
  import { cp as cp2, mkdir as mkdir7, mkdtemp, readdir as readdir3, rm as rm4, stat as stat5, writeFile as writeFile5 } from "fs/promises";
9179
9241
  import { tmpdir as tmpdir2 } from "os";
9180
- import { join as join10, resolve as resolve10 } from "path";
9242
+ import { join as join10, resolve as resolve11 } from "path";
9181
9243
 
9182
9244
  // src/plugins/host.ts
9183
9245
  import { createHash as createHash8 } from "crypto";
9184
9246
  import { lstatSync, realpathSync as realpathSync3 } from "fs";
9185
9247
  import { lstat as lstat3, open as open4, readFile as readFile7, readdir as readdir2, realpath as realpath6, stat as stat4, writeFile as writeFile4 } from "fs/promises";
9186
- import { isAbsolute as isAbsolute6, relative as relative7, resolve as resolve9, sep as sep7 } from "path";
9248
+ import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve10, sep as sep7 } from "path";
9187
9249
  import { pathToFileURL as pathToFileURL2 } from "url";
9188
9250
  import { z as z18 } from "zod";
9189
9251
 
@@ -9501,7 +9563,7 @@ var PluginHost = class {
9501
9563
  const roots = [];
9502
9564
  for (const directory of directories) {
9503
9565
  try {
9504
- for (const entry2 of await readdir2(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve9(directory, entry2.name));
9566
+ for (const entry2 of await readdir2(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve10(directory, entry2.name));
9505
9567
  } catch (error) {
9506
9568
  this.#diagnose(directory, error, directory);
9507
9569
  }
@@ -9534,7 +9596,7 @@ var PluginHost = class {
9534
9596
  const rootInfo = await lstat3(inputRoot);
9535
9597
  if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
9536
9598
  const root = await realpath6(inputRoot);
9537
- const rawManifest = await readManifest2(resolve9(root, MANIFEST));
9599
+ const rawManifest = await readManifest2(resolve10(root, MANIFEST));
9538
9600
  if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
9539
9601
  diagnosticLabel = rawManifest.name;
9540
9602
  }
@@ -9720,7 +9782,7 @@ var PluginHost = class {
9720
9782
  assertActive();
9721
9783
  const permission = `filesystem:${operation}`;
9722
9784
  if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
9723
- const path = resolve9(rawPath);
9785
+ const path = resolve10(rawPath);
9724
9786
  const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
9725
9787
  assertActive();
9726
9788
  if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
@@ -9851,14 +9913,14 @@ function assertSnapshot(expected, actual, label) {
9851
9913
  for (const key of ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"]) if (expected[key] !== actual[key]) throw new Error(`${label} changed during validation`);
9852
9914
  }
9853
9915
  function resolveContained(root, value, label) {
9854
- if (isAbsolute6(value)) throw new Error(`${label} must be relative to the plugin root`);
9855
- const candidate = resolve9(root, value);
9916
+ if (isAbsolute7(value)) throw new Error(`${label} must be relative to the plugin root`);
9917
+ const candidate = resolve10(root, value);
9856
9918
  assertContained2(root, candidate, label);
9857
9919
  return candidate;
9858
9920
  }
9859
9921
  function assertContained2(root, candidate, label) {
9860
9922
  const delta = relative7(root, candidate);
9861
- if (delta === ".." || delta.startsWith(`..${sep7}`) || isAbsolute6(delta)) throw new Error(`${label} escapes the plugin root`);
9923
+ if (delta === ".." || delta.startsWith(`..${sep7}`) || isAbsolute7(delta)) throw new Error(`${label} escapes the plugin root`);
9862
9924
  }
9863
9925
  function scopedLogger(logger, plugin) {
9864
9926
  const prefix = `[plugin:${plugin}] `;
@@ -9908,10 +9970,10 @@ function sanitizePluginName(tool) {
9908
9970
  return base.startsWith("fix-") ? base : `fix-${base}`;
9909
9971
  }
9910
9972
  function fixPluginDir(workspace, name) {
9911
- return resolve10(workspace, PLUGIN_DIR, name);
9973
+ return resolve11(workspace, PLUGIN_DIR, name);
9912
9974
  }
9913
9975
  function versionsDirFor(workspace, name) {
9914
- return resolve10(workspace, VERSIONS_DIR, name);
9976
+ return resolve11(workspace, VERSIONS_DIR, name);
9915
9977
  }
9916
9978
  function timestamp() {
9917
9979
  return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
@@ -10348,7 +10410,7 @@ var USAGE = [
10348
10410
  " clear reset signals, done markers, and verified markers"
10349
10411
  ].join("\n");
10350
10412
  function runCommand(command, cwd, timeoutMs) {
10351
- return new Promise((resolve41) => {
10413
+ return new Promise((resolve42) => {
10352
10414
  const isWindows = process.platform === "win32";
10353
10415
  const child = spawn2(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
10354
10416
  cwd,
@@ -10358,7 +10420,7 @@ function runCommand(command, cwd, timeoutMs) {
10358
10420
  let stderr = "";
10359
10421
  const timer = setTimeout(() => {
10360
10422
  child.kill();
10361
- resolve41({ ok: false, stdout, stderr: `${stderr}
10423
+ resolve42({ ok: false, stdout, stderr: `${stderr}
10362
10424
  [killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
10363
10425
  }, timeoutMs);
10364
10426
  child.stdout.on("data", (chunk) => {
@@ -10369,12 +10431,12 @@ function runCommand(command, cwd, timeoutMs) {
10369
10431
  });
10370
10432
  child.on("error", (error) => {
10371
10433
  clearTimeout(timer);
10372
- resolve41({ ok: false, stdout, stderr: `${stderr}
10434
+ resolve42({ ok: false, stdout, stderr: `${stderr}
10373
10435
  ${error.message}`, code: "spawn" });
10374
10436
  });
10375
10437
  child.on("close", (code) => {
10376
10438
  clearTimeout(timer);
10377
- resolve41({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10439
+ resolve42({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10378
10440
  });
10379
10441
  });
10380
10442
  }
@@ -10784,7 +10846,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
10784
10846
  // src/init/project.ts
10785
10847
  import { lstat as lstat4, mkdir as mkdir9, readdir as readdir4, readFile as readFile9, realpath as realpath7, writeFile as writeFile8 } from "fs/promises";
10786
10848
  import { copyFileSync, mkdirSync as mkdirSync2, readdirSync } from "fs";
10787
- import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute7, join as join13, relative as relative8, sep as sep8 } from "path";
10849
+ import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute8, join as join13, relative as relative8, sep as sep8 } from "path";
10788
10850
  import { fileURLToPath } from "url";
10789
10851
  var GENERATED_START = "<!-- flavor-code:start -->";
10790
10852
  var GENERATED_END = "<!-- flavor-code:end -->";
@@ -10834,7 +10896,7 @@ var CONFIG_FILES = [
10834
10896
  ];
10835
10897
  function isContainedPath(root, candidate) {
10836
10898
  const pathFromRoot = relative8(root, candidate);
10837
- return pathFromRoot === "" || pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep8}`) && !isAbsolute7(pathFromRoot);
10899
+ return pathFromRoot === "" || pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep8}`) && !isAbsolute8(pathFromRoot);
10838
10900
  }
10839
10901
  async function pathKind(path, root) {
10840
10902
  try {
@@ -12353,14 +12415,14 @@ function formatPriorGaps(gaps) {
12353
12415
  import { constants as constants3 } from "fs";
12354
12416
  import { randomUUID as randomUUID11 } from "crypto";
12355
12417
  import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
12356
- import { dirname as dirname10, join as join16, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
12418
+ import { dirname as dirname10, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
12357
12419
  var DEFAULT_MAX_BYTES = 1024 * 1024;
12358
12420
  var GoalStore = class {
12359
12421
  #workspace;
12360
12422
  #root;
12361
12423
  #maxBytes;
12362
12424
  constructor(options) {
12363
- this.#workspace = resolve11(options.workspace);
12425
+ this.#workspace = resolve12(options.workspace);
12364
12426
  this.#root = join16(this.#workspace, ".flavor", "goals");
12365
12427
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
12366
12428
  }
@@ -12391,8 +12453,8 @@ var GoalStore = class {
12391
12453
  }
12392
12454
  };
12393
12455
  async function assertNoSymlink(root, target) {
12394
- let cursor = resolve11(target);
12395
- const stop = resolve11(root);
12456
+ let cursor = resolve12(target);
12457
+ const stop = resolve12(root);
12396
12458
  const value = relative9(stop, cursor);
12397
12459
  if (value === ".." || value.startsWith(`..${sep9}`) || value.includes(":")) {
12398
12460
  throw new Error("Goal path escapes workspace");
@@ -12416,7 +12478,7 @@ function isCode4(error, code) {
12416
12478
 
12417
12479
  // src/loop/isolation.ts
12418
12480
  import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
12419
- import { join as join17, resolve as resolve12 } from "path";
12481
+ import { join as join17, resolve as resolve13 } from "path";
12420
12482
  var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
12421
12483
  var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
12422
12484
  function inferGoalIntent(goal) {
@@ -12426,7 +12488,7 @@ function inferGoalIntent(goal) {
12426
12488
  }
12427
12489
  async function prepareLoopWorkspace(options) {
12428
12490
  options.signal?.throwIfAborted();
12429
- const root = resolve12(options.root);
12491
+ const root = resolve13(options.root);
12430
12492
  const intent = inferGoalIntent(options.goal);
12431
12493
  if (intent === "read_only") return { kind: "ready", workspace: { root, mode: "current" } };
12432
12494
  const inside = await git(root, ["rev-parse", "--is-inside-work-tree"]);
@@ -12469,7 +12531,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
12469
12531
  if (ignored2.code === 0) return;
12470
12532
  const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
12471
12533
  if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
12472
- const gitDirectory = resolve12(root, commonDirectory.stdout.trim());
12534
+ const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
12473
12535
  const infoDirectory = join17(gitDirectory, "info");
12474
12536
  const excludePath = join17(infoDirectory, "exclude");
12475
12537
  await mkdir12(infoDirectory, { recursive: true });
@@ -12487,7 +12549,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
12487
12549
  // src/loop/store.ts
12488
12550
  import { constants as constants4 } from "fs";
12489
12551
  import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile12, realpath as realpath8, rename as rename5, rm as rm7, stat as stat6 } from "fs/promises";
12490
- import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
12552
+ import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
12491
12553
  import { randomUUID as randomUUID12 } from "crypto";
12492
12554
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
12493
12555
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
@@ -12497,7 +12559,7 @@ var LoopStore = class {
12497
12559
  #maxBytes;
12498
12560
  #appendTails = /* @__PURE__ */ new Map();
12499
12561
  constructor(options) {
12500
- this.#workspace = resolve13(options.workspace);
12562
+ this.#workspace = resolve14(options.workspace);
12501
12563
  this.#root = join18(this.#workspace, ".flavor", "loops");
12502
12564
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
12503
12565
  }
@@ -12596,8 +12658,8 @@ function validateId(loopId) {
12596
12658
  if (!LOOP_ID.test(loopId)) throw new Error("Invalid loop id");
12597
12659
  }
12598
12660
  async function assertNoSymlink2(root, target) {
12599
- let cursor = resolve13(target);
12600
- const stop = resolve13(root);
12661
+ let cursor = resolve14(target);
12662
+ const stop = resolve14(root);
12601
12663
  if (!isWithin3(stop, cursor)) throw new Error("Loop path escapes workspace");
12602
12664
  const chain = [];
12603
12665
  while (cursor !== stop) {
@@ -12613,14 +12675,14 @@ async function assertNoSymlink2(root, target) {
12613
12675
  }
12614
12676
  }
12615
12677
  function isWithin3(root, path) {
12616
- const value = relative10(resolve13(root), resolve13(path));
12678
+ const value = relative10(resolve14(root), resolve14(path));
12617
12679
  return value === "" || !value.startsWith(`..${sep10}`) && value !== ".." && !value.includes(":");
12618
12680
  }
12619
12681
  async function canonical(path) {
12620
12682
  try {
12621
- return await realpath8(resolve13(path));
12683
+ return await realpath8(resolve14(path));
12622
12684
  } catch {
12623
- return resolve13(path);
12685
+ return resolve14(path);
12624
12686
  }
12625
12687
  }
12626
12688
  function isCode5(error, code) {
@@ -12633,7 +12695,7 @@ import { join as join19 } from "path";
12633
12695
 
12634
12696
  // src/tools/shell.ts
12635
12697
  import { spawn as spawn3, spawnSync } from "child_process";
12636
- import { isAbsolute as isAbsolute8, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
12698
+ import { isAbsolute as isAbsolute9, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
12637
12699
  import { z as z23 } from "zod";
12638
12700
 
12639
12701
  // src/tools/jobs.ts
@@ -12786,7 +12848,7 @@ function buildWindowsCommandLine(kind, commandLine, command, args) {
12786
12848
  return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
12787
12849
  }
12788
12850
  function createShellTool(workspace, options = {}) {
12789
- const root = resolve14(workspace);
12851
+ const root = resolve15(workspace);
12790
12852
  const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
12791
12853
  const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
12792
12854
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
@@ -13085,9 +13147,9 @@ function backgroundFlag(value) {
13085
13147
  return value === true || value === "true";
13086
13148
  }
13087
13149
  function workingDirectory(root, cwd = ".") {
13088
- const candidate = resolve14(root, cwd);
13150
+ const candidate = resolve15(root, cwd);
13089
13151
  const delta = relative11(root, candidate);
13090
- if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute8(delta)) throw new Error("Working directory is outside the workspace");
13152
+ if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute9(delta)) throw new Error("Working directory is outside the workspace");
13091
13153
  return candidate;
13092
13154
  }
13093
13155
  async function terminateTree(pid) {
@@ -13340,7 +13402,7 @@ function isEnvTruthy(value) {
13340
13402
 
13341
13403
  // src/utils/log.ts
13342
13404
  import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
13343
- import { dirname as dirname12, join as join20, resolve as resolve15 } from "path";
13405
+ import { dirname as dirname12, join as join20, resolve as resolve16 } from "path";
13344
13406
  var pending = Promise.resolve();
13345
13407
  var usagePending = Promise.resolve();
13346
13408
  function usageLogPath() {
@@ -13373,7 +13435,7 @@ var AuditLogger = class {
13373
13435
  #path;
13374
13436
  #closed = false;
13375
13437
  constructor(workspace) {
13376
- this.#path = join20(resolve15(workspace), ".flavor", "audit.jsonl");
13438
+ this.#path = join20(resolve16(workspace), ".flavor", "audit.jsonl");
13377
13439
  }
13378
13440
  get path() {
13379
13441
  return this.#path;
@@ -14235,7 +14297,7 @@ function isRecord4(value) {
14235
14297
  }
14236
14298
 
14237
14299
  // src/mcp/sdk.ts
14238
- import { resolve as resolve16 } from "path";
14300
+ import { resolve as resolve17 } from "path";
14239
14301
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
14240
14302
  import {
14241
14303
  getDefaultEnvironment,
@@ -14283,7 +14345,7 @@ async function connectSdkMcpClient(input, dependencies2 = DEFAULT_DEPENDENCIES)
14283
14345
  command: config.command,
14284
14346
  args: [...config.args],
14285
14347
  env: { ...dependencies2.defaultEnvironment(), ...config.env },
14286
- cwd: resolve16(input.workspace, config.cwd ?? "."),
14348
+ cwd: resolve17(input.workspace, config.cwd ?? "."),
14287
14349
  // MCP servers frequently log startup banners to stderr. Inheriting that
14288
14350
  // stream corrupts Ink's input area, so connection errors are surfaced
14289
14351
  // through McpManager diagnostics instead.
@@ -14488,8 +14550,8 @@ var OAuthCallbackAuthProvider = class {
14488
14550
  async #startCallbackServer(expectedState, signal) {
14489
14551
  let serverResolve;
14490
14552
  let serverReject;
14491
- const codePromise = new Promise((resolve41, reject) => {
14492
- serverResolve = resolve41;
14553
+ const codePromise = new Promise((resolve42, reject) => {
14554
+ serverResolve = resolve42;
14493
14555
  serverReject = reject;
14494
14556
  });
14495
14557
  const server = createServer3((req, res) => {
@@ -14523,7 +14585,7 @@ var OAuthCallbackAuthProvider = class {
14523
14585
  );
14524
14586
  serverResolve(code);
14525
14587
  });
14526
- return new Promise((resolve41, reject) => {
14588
+ return new Promise((resolve42, reject) => {
14527
14589
  server.once("error", (error) => {
14528
14590
  reject(new Error(`OAuth callback server failed: ${error.message}`));
14529
14591
  });
@@ -14540,7 +14602,7 @@ var OAuthCallbackAuthProvider = class {
14540
14602
  signal.addEventListener("abort", () => {
14541
14603
  serverReject(new Error("OAuth flow aborted"));
14542
14604
  }, { once: true });
14543
- resolve41({ redirectUri, promise: codePromise });
14605
+ resolve42({ redirectUri, promise: codePromise });
14544
14606
  });
14545
14607
  });
14546
14608
  }
@@ -14683,7 +14745,7 @@ function isStoredToken(value) {
14683
14745
 
14684
14746
  // src/permissions/policy.ts
14685
14747
  import { readFile as readFile16 } from "fs/promises";
14686
- import { join as join22, resolve as resolve17 } from "path";
14748
+ import { join as join22, resolve as resolve18 } from "path";
14687
14749
  import { z as z25 } from "zod";
14688
14750
  var DecisionSchema2 = z25.enum(["allow", "ask", "deny"]);
14689
14751
  var PrefixSchema = z25.array(z25.string().min(1).max(1024)).min(1).max(64);
@@ -14719,10 +14781,10 @@ var CompiledPermissionPolicy = class {
14719
14781
  };
14720
14782
  async function loadPermissionPolicy(options) {
14721
14783
  const sources = [
14722
- ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve17(options.managedPath) }],
14723
- { tier: "user", path: join22(resolve17(options.home), ".flavor-code", "permissions.json") },
14724
- { tier: "project", path: join22(resolve17(options.workspace), ".flavor", "permissions.json") },
14725
- { tier: "local", path: join22(resolve17(options.workspace), ".flavor", "permissions.local.json") }
14784
+ ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve18(options.managedPath) }],
14785
+ { tier: "user", path: join22(resolve18(options.home), ".flavor-code", "permissions.json") },
14786
+ { tier: "project", path: join22(resolve18(options.workspace), ".flavor", "permissions.json") },
14787
+ { tier: "local", path: join22(resolve18(options.workspace), ".flavor", "permissions.local.json") }
14726
14788
  ];
14727
14789
  const rules = [];
14728
14790
  for (const source of sources) {
@@ -14944,7 +15006,7 @@ function data(value) {
14944
15006
  // src/skills/registry.ts
14945
15007
  import { constants as constants5 } from "fs";
14946
15008
  import { lstat as lstat7, open as defaultOpen, readdir as readdir5, realpath as realpath9 } from "fs/promises";
14947
- import { isAbsolute as isAbsolute9, relative as relative12, resolve as resolve18, sep as sep12 } from "path";
15009
+ import { isAbsolute as isAbsolute10, relative as relative12, resolve as resolve19, sep as sep12 } from "path";
14948
15010
  import { marked } from "marked";
14949
15011
  import { parseDocument } from "yaml";
14950
15012
  var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
@@ -15069,13 +15131,13 @@ var SkillRegistry = class {
15069
15131
  const record3 = await this.#recordFor(skill);
15070
15132
  const normalized = normalizeReference(reference);
15071
15133
  const parts = normalized.split("/");
15072
- if (isAbsolute9(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
15134
+ if (isAbsolute10(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
15073
15135
  throw new Error(`Resource reference escapes the skill root or is not a direct resource: ${reference}`);
15074
15136
  }
15075
15137
  const body = await this.loadBody(record3.metadata);
15076
15138
  const references = extractResourceReferences(body);
15077
15139
  if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
15078
- const candidate = resolve18(record3.metadata.root, ...parts);
15140
+ const candidate = resolve19(record3.metadata.root, ...parts);
15079
15141
  const file = await openVerifiedFile(candidate, record3.metadata.root, "Resource", this.#options.openFile);
15080
15142
  try {
15081
15143
  if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
@@ -15104,7 +15166,7 @@ var SkillRegistry = class {
15104
15166
  async #discoverSource(roots, source) {
15105
15167
  const found = [];
15106
15168
  for (const configuredRoot of roots) {
15107
- const root = resolve18(configuredRoot);
15169
+ const root = resolve19(configuredRoot);
15108
15170
  let entries;
15109
15171
  let physicalRoot = root;
15110
15172
  try {
@@ -15119,13 +15181,13 @@ var SkillRegistry = class {
15119
15181
  }
15120
15182
  entries.sort((left, right) => compareCodePoints(left.name, right.name));
15121
15183
  for (const entry2 of entries) {
15122
- const skillRoot = resolve18(root, entry2.name);
15184
+ const skillRoot = resolve19(root, entry2.name);
15123
15185
  try {
15124
15186
  if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
15125
15187
  if (!entry2.isDirectory()) continue;
15126
15188
  const physicalSkillRoot = await realpath9(skillRoot);
15127
15189
  if (!isWithin4(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
15128
- const skillFile = resolve18(skillRoot, "SKILL.md");
15190
+ const skillFile = resolve19(skillRoot, "SKILL.md");
15129
15191
  const parsed = await readFrontmatterFile(
15130
15192
  skillFile,
15131
15193
  physicalSkillRoot,
@@ -15327,7 +15389,7 @@ function decodeUtf8(content, label) {
15327
15389
  }
15328
15390
  function isWithin4(root, candidate) {
15329
15391
  const delta = relative12(root, candidate);
15330
- return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute9(delta);
15392
+ return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute10(delta);
15331
15393
  }
15332
15394
  function isMissing(error) {
15333
15395
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
@@ -15491,7 +15553,7 @@ function createSkillResourceTool(registry) {
15491
15553
  // src/session/store.ts
15492
15554
  import { constants as constants6 } from "fs";
15493
15555
  import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile17, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat7 } from "fs/promises";
15494
- import { basename as basename5, dirname as dirname15, join as join23, relative as relative13, resolve as resolve19, sep as sep13 } from "path";
15556
+ import { basename as basename5, dirname as dirname15, join as join23, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
15495
15557
  import { randomUUID as randomUUID13 } from "crypto";
15496
15558
  import { z as z27 } from "zod";
15497
15559
 
@@ -16179,13 +16241,14 @@ var SessionDocumentSchema = SessionBaseSchema.extend({
16179
16241
  }).strict(),
16180
16242
  timeline: TimelineSchema
16181
16243
  }).strict();
16244
+ var INVALID_SESSION_LOCK_STALE_MS = 3e4;
16182
16245
  var SessionStore = class {
16183
16246
  #workspace;
16184
16247
  #sessions;
16185
16248
  #maxBytes;
16186
16249
  #maxSessions;
16187
16250
  constructor(options) {
16188
- this.#workspace = resolve19(options.workspace);
16251
+ this.#workspace = resolve20(options.workspace);
16189
16252
  this.#sessions = join23(this.#workspace, ".flavor", "sessions");
16190
16253
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
16191
16254
  if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
@@ -16233,6 +16296,47 @@ var SessionStore = class {
16233
16296
  }
16234
16297
  await this.#prune().catch(() => void 0);
16235
16298
  }
16299
+ /** Hold this lease for the full lifetime of a writable session runtime. */
16300
+ async acquireLease(sessionId) {
16301
+ SessionIdSchema.parse(sessionId);
16302
+ await this.#prepareDirectory();
16303
+ const path = join23(this.#sessions, `.${sessionId}.lock`);
16304
+ const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
16305
+ for (let attempt = 0; attempt < 3; attempt += 1) {
16306
+ let handle;
16307
+ let created = false;
16308
+ try {
16309
+ handle = await open7(path, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
16310
+ created = true;
16311
+ await handle.writeFile(token, "utf8");
16312
+ await handle.sync();
16313
+ await handle.close();
16314
+ handle = void 0;
16315
+ let released = false;
16316
+ return {
16317
+ sessionId,
16318
+ release: async () => {
16319
+ if (released) return;
16320
+ try {
16321
+ if (await readFile17(path, "utf8") === token) await rm8(path, { force: true });
16322
+ } catch (error) {
16323
+ if (!isCode8(error, "ENOENT")) throw error;
16324
+ }
16325
+ released = true;
16326
+ }
16327
+ };
16328
+ } catch (error) {
16329
+ await handle?.close().catch(() => void 0);
16330
+ if (created) await rm8(path, { force: true }).catch(() => void 0);
16331
+ if (!isCode8(error, "EEXIST")) throw error;
16332
+ const owner2 = await reclaimStaleSessionLock(path);
16333
+ if (owner2 !== void 0) {
16334
+ throw new Error(`Session "${sessionId}" is already active in process ${owner2}. Close that session before resuming it here.`);
16335
+ }
16336
+ }
16337
+ }
16338
+ throw new Error(`Session "${sessionId}" could not acquire its writer lease because the lock kept changing`);
16339
+ }
16236
16340
  async load(sessionId) {
16237
16341
  if (sessionId === void 0) {
16238
16342
  const [latest] = await this.list();
@@ -16330,6 +16434,8 @@ var SessionStore = class {
16330
16434
  if (isCode8(error, "ENOENT")) return;
16331
16435
  throw error;
16332
16436
  }
16437
+ const owner2 = await reclaimStaleSessionLock(join23(this.#sessions, `.${sessionId}.lock`));
16438
+ if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
16333
16439
  const path = this.#path(sessionId);
16334
16440
  try {
16335
16441
  const metadata = await lstat8(path);
@@ -16342,15 +16448,21 @@ var SessionStore = class {
16342
16448
  }
16343
16449
  #path(sessionId) {
16344
16450
  SessionIdSchema.parse(sessionId);
16345
- const path = resolve19(this.#sessions, `${sessionId}.jsonl`);
16451
+ const path = resolve20(this.#sessions, `${sessionId}.jsonl`);
16346
16452
  if (!isWithin5(this.#sessions, path)) throw new Error("Invalid session id");
16347
16453
  return path;
16348
16454
  }
16349
16455
  async #prune() {
16350
16456
  const entries = await this.list();
16351
16457
  if (entries.length <= this.#maxSessions) return;
16352
- const excess = entries.slice(this.#maxSessions);
16353
- for (const entry2 of excess) {
16458
+ let retained = 0;
16459
+ for (const entry2 of entries) {
16460
+ const owner2 = await reclaimStaleSessionLock(join23(this.#sessions, `.${entry2.sessionId}.lock`));
16461
+ if (owner2 !== void 0) continue;
16462
+ if (retained < this.#maxSessions) {
16463
+ retained += 1;
16464
+ continue;
16465
+ }
16354
16466
  await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
16355
16467
  await rm8(join23(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
16356
16468
  }
@@ -16398,6 +16510,47 @@ function storedSessionVersion(raw) {
16398
16510
  const candidate = first.__meta === true ? first.version : JSON.parse(trimmed).version;
16399
16511
  return typeof candidate === "number" && Number.isInteger(candidate) ? candidate : 0;
16400
16512
  }
16513
+ async function reclaimStaleSessionLock(path) {
16514
+ try {
16515
+ const token = await readFile17(path, "utf8");
16516
+ const metadata = await stat7(path);
16517
+ const owner2 = parseSessionLockOwner(token);
16518
+ if (owner2 !== void 0 && isProcessAlive2(owner2)) return owner2;
16519
+ if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
16520
+ throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
16521
+ }
16522
+ if (await readFile17(path, "utf8") !== token) return void 0;
16523
+ const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
16524
+ try {
16525
+ await rename6(path, stale);
16526
+ await rm8(stale, { force: true });
16527
+ } catch (error) {
16528
+ if (!isCode8(error, "ENOENT")) throw error;
16529
+ }
16530
+ return void 0;
16531
+ } catch (error) {
16532
+ if (isCode8(error, "ENOENT")) return void 0;
16533
+ throw error;
16534
+ }
16535
+ }
16536
+ function parseSessionLockOwner(token) {
16537
+ try {
16538
+ const value = JSON.parse(token);
16539
+ if (typeof value !== "object" || value === null || !("pid" in value)) return void 0;
16540
+ const pid = value.pid;
16541
+ return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
16542
+ } catch {
16543
+ return void 0;
16544
+ }
16545
+ }
16546
+ function isProcessAlive2(pid) {
16547
+ try {
16548
+ process.kill(pid, 0);
16549
+ return true;
16550
+ } catch (error) {
16551
+ return isCode8(error, "EPERM");
16552
+ }
16553
+ }
16401
16554
  async function boundedRead(path, maxBytes) {
16402
16555
  const metadata = await stat7(path);
16403
16556
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
@@ -16544,8 +16697,8 @@ function isRecord6(value) {
16544
16697
  return typeof value === "object" && value !== null && !Array.isArray(value);
16545
16698
  }
16546
16699
  async function assertNoSymlink3(root, target) {
16547
- let cursor = resolve19(target);
16548
- const stop = resolve19(root);
16700
+ let cursor = resolve20(target);
16701
+ const stop = resolve20(root);
16549
16702
  if (!isWithin5(stop, cursor)) throw new Error("Session path escapes the workspace");
16550
16703
  const chain = [];
16551
16704
  while (cursor !== stop) {
@@ -16561,14 +16714,14 @@ async function assertNoSymlink3(root, target) {
16561
16714
  }
16562
16715
  }
16563
16716
  function isWithin5(root, path) {
16564
- const value = relative13(resolve19(root), resolve19(path));
16717
+ const value = relative13(resolve20(root), resolve20(path));
16565
16718
  return value === "" || !value.startsWith(`..${sep13}`) && value !== ".." && !value.includes(":");
16566
16719
  }
16567
16720
  async function canonical2(path) {
16568
16721
  try {
16569
- return await realpath10(resolve19(path));
16722
+ return await realpath10(resolve20(path));
16570
16723
  } catch {
16571
- return resolve19(path);
16724
+ return resolve20(path);
16572
16725
  }
16573
16726
  }
16574
16727
  function isCode8(error, code) {
@@ -16595,13 +16748,13 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
16595
16748
  // src/session/tree.ts
16596
16749
  import { randomUUID as randomUUID15 } from "crypto";
16597
16750
  import { mkdir as mkdir17, readFile as readFile19, rename as rename8, writeFile as writeFile11 } from "fs/promises";
16598
- import { dirname as dirname17, join as join25, resolve as resolve21 } from "path";
16751
+ import { dirname as dirname17, join as join25, resolve as resolve22 } from "path";
16599
16752
 
16600
16753
  // src/session/checkpoint.ts
16601
16754
  import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
16602
16755
  import { execFile as execFile2 } from "child_process";
16603
16756
  import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile18, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
16604
- import { dirname as dirname16, join as join24, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
16757
+ import { dirname as dirname16, join as join24, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
16605
16758
  var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
16606
16759
  var MAX_FILE_BYTES = 16 * 1024 * 1024;
16607
16760
  var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
@@ -16613,7 +16766,7 @@ var WorkspaceCheckpointStore = class {
16613
16766
  #maxFileBytes;
16614
16767
  #maxTotalBytes;
16615
16768
  constructor(options) {
16616
- this.#workspace = resolve20(options.workspace);
16769
+ this.#workspace = resolve21(options.workspace);
16617
16770
  this.#root = join24(this.#workspace, ".flavor", "checkpoints");
16618
16771
  this.#objects = join24(this.#root, "objects");
16619
16772
  this.#manifests = join24(this.#root, "manifests");
@@ -16751,9 +16904,9 @@ function normalizeRelative(path) {
16751
16904
  }
16752
16905
  function contained(workspace, path) {
16753
16906
  if (path.length === 0 || path.includes("\0")) throw new Error("Invalid checkpoint path");
16754
- const absolute = resolve20(workspace, path);
16907
+ const absolute = resolve21(workspace, path);
16755
16908
  const delta = relative14(workspace, absolute);
16756
- if (delta === ".." || delta.startsWith(`..${sep14}`) || delta === "" || resolve20(absolute) !== absolute) {
16909
+ if (delta === ".." || delta.startsWith(`..${sep14}`) || delta === "" || resolve21(absolute) !== absolute) {
16757
16910
  throw new Error(`Checkpoint path escapes workspace: ${path}`);
16758
16911
  }
16759
16912
  return absolute;
@@ -16780,14 +16933,14 @@ var SessionHistory = class _SessionHistory {
16780
16933
  #leafId;
16781
16934
  #unrevert;
16782
16935
  constructor(options, document) {
16783
- this.#path = join25(resolve21(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
16936
+ this.#path = join25(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
16784
16937
  this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
16785
16938
  this.#restoreContext = options.restoreContext;
16786
16939
  this.#nodes = boundedNodes(document.nodes);
16787
16940
  this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
16788
16941
  }
16789
16942
  static async open(options) {
16790
- const path = join25(resolve21(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
16943
+ const path = join25(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
16791
16944
  let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
16792
16945
  try {
16793
16946
  const parsed = JSON.parse(await readFile19(path, "utf8"));
@@ -16945,7 +17098,7 @@ function parseMemoryRestartMarker(json) {
16945
17098
  import { constants as constants7 } from "fs";
16946
17099
  import { randomUUID as randomUUID16 } from "crypto";
16947
17100
  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";
16948
- import { isAbsolute as isAbsolute10, join as join27, relative as relative15, resolve as resolve22, sep as sep15 } from "path";
17101
+ import { isAbsolute as isAbsolute11, join as join27, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
16949
17102
  import { z as z28 } from "zod";
16950
17103
  var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
16951
17104
  var ReviewSchema = z28.object({
@@ -16999,7 +17152,7 @@ var ProjectSleepOrganizer = class {
16999
17152
  #sessions;
17000
17153
  #generate;
17001
17154
  constructor(options) {
17002
- this.#workspace = resolve22(options.workspace);
17155
+ this.#workspace = resolve23(options.workspace);
17003
17156
  this.#sleepDirectory = join27(this.#workspace, ".flavor", "sleep");
17004
17157
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
17005
17158
  this.#generate = options.generate;
@@ -17424,8 +17577,8 @@ async function assertNotSymlink(path) {
17424
17577
  function isWithin6(root, candidate) {
17425
17578
  const path = relative15(root, candidate);
17426
17579
  if (path === "") return true;
17427
- if (path === ".." || path.startsWith(`..${sep15}`) || isAbsolute10(path)) return false;
17428
- return process.platform !== "win32" || resolve22(root, path).toLowerCase().startsWith(`${resolve22(root).toLowerCase()}${sep15}`);
17580
+ if (path === ".." || path.startsWith(`..${sep15}`) || isAbsolute11(path)) return false;
17581
+ return process.platform !== "win32" || resolve23(root, path).toLowerCase().startsWith(`${resolve23(root).toLowerCase()}${sep15}`);
17429
17582
  }
17430
17583
  function hasCode(error, code) {
17431
17584
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
@@ -17475,7 +17628,7 @@ function computeSessionStats(documents) {
17475
17628
  // src/tools/files.ts
17476
17629
  import { constants as constants8 } from "fs";
17477
17630
  import { mkdir as mkdir19, open as open10, readFile as readFile20, realpath as realpath12, rename as rename10, rm as rm11, stat as stat8 } from "fs/promises";
17478
- import { dirname as dirname18, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve23, sep as sep16 } from "path";
17631
+ import { dirname as dirname18, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
17479
17632
  import { randomUUID as randomUUID17 } from "crypto";
17480
17633
  import { z as z29 } from "zod";
17481
17634
 
@@ -17792,9 +17945,9 @@ function createApplyPatchTool(workspace, options = {}) {
17792
17945
  };
17793
17946
  }
17794
17947
  function createPathGuard(workspace) {
17795
- const root = resolve23(workspace);
17948
+ const root = resolve24(workspace);
17796
17949
  const lexical = (input) => {
17797
- const candidate = resolve23(root, input);
17950
+ const candidate = resolve24(root, input);
17798
17951
  if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
17799
17952
  return candidate;
17800
17953
  };
@@ -17964,7 +18117,7 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
17964
18117
  }
17965
18118
  function within2(root, candidate) {
17966
18119
  const delta = relative16(root, candidate);
17967
- return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute11(delta);
18120
+ return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute12(delta);
17968
18121
  }
17969
18122
  function forceFlag(value) {
17970
18123
  return value === true || value === "true";
@@ -18188,7 +18341,7 @@ function matchesLines(source, start, expected) {
18188
18341
  // src/tools/search.ts
18189
18342
  import { spawn as spawn4 } from "child_process";
18190
18343
  import { open as open11, opendir, stat as stat9 } from "fs/promises";
18191
- import { dirname as dirname19, isAbsolute as isAbsolute12, relative as relative17, resolve as resolve24, sep as sep17 } from "path";
18344
+ import { dirname as dirname19, isAbsolute as isAbsolute13, relative as relative17, resolve as resolve25, sep as sep17 } from "path";
18192
18345
  import { rgPath as bundledRgPath } from "@vscode/ripgrep";
18193
18346
  import createIgnore from "ignore";
18194
18347
  import { z as z30 } from "zod";
@@ -18288,7 +18441,7 @@ var GrepInput = z30.object({
18288
18441
  limit: z30.coerce.number().int().positive().max(1e5).nullable().optional()
18289
18442
  });
18290
18443
  function createGlobTool(workspace, options = {}) {
18291
- const root = resolve24(workspace);
18444
+ const root = resolve25(workspace);
18292
18445
  const resources = searchResources(options);
18293
18446
  return {
18294
18447
  name: "Glob",
@@ -18325,7 +18478,7 @@ function createGlobTool(workspace, options = {}) {
18325
18478
  };
18326
18479
  }
18327
18480
  function createGrepTool(workspace, options = {}) {
18328
- const root = resolve24(workspace);
18481
+ const root = resolve25(workspace);
18329
18482
  const resources = searchResources(options);
18330
18483
  return {
18331
18484
  name: "Grep",
@@ -18371,7 +18524,7 @@ async function rgFiles(root, start, pattern, signal, executable, argsPrefix, lim
18371
18524
  const paths = [];
18372
18525
  const parser = delimitedParser(0, (record3) => {
18373
18526
  if (record3.length === 0) return true;
18374
- const path = normalizedRelative(root, resolve24(root, decodeUtf82(record3, "ripgrep path")));
18527
+ const path = normalizedRelative(root, resolve25(root, decodeUtf82(record3, "ripgrep path")));
18375
18528
  if (!matcher.test(path) || ignored(path, false, ignoreLayers)) return true;
18376
18529
  retainTopK(paths, path, limit, comparePaths);
18377
18530
  return true;
@@ -18436,7 +18589,7 @@ async function hydrateContext(root, matches, context, signal, maxFileBytes, maxT
18436
18589
  let totalBytes = 0;
18437
18590
  for (const [path, pathMatches] of byPath) {
18438
18591
  abort(signal);
18439
- const absolute = resolve24(root, path);
18592
+ const absolute = resolve25(root, path);
18440
18593
  const contents = await readBoundedFile(absolute, maxFileBytes, signal);
18441
18594
  if (contents === void 0) continue;
18442
18595
  totalBytes += contents.length;
@@ -18463,7 +18616,7 @@ async function nodeGrep(root, start, glob, type, expression, context, signal, ma
18463
18616
  if (searchedBytes >= maxSearchBytes) break;
18464
18617
  if (matcher !== void 0 && !matcher.test(path)) continue;
18465
18618
  if (extension2 !== void 0 && !extension2.some((suffix) => path.endsWith(suffix))) continue;
18466
- const absolute = resolve24(root, path);
18619
+ const absolute = resolve25(root, path);
18467
18620
  const bytes = await readBoundedFile(absolute, Math.min(maxFileBytes, maxSearchBytes - searchedBytes), signal);
18468
18621
  if (bytes === void 0) continue;
18469
18622
  searchedBytes += bytes.length;
@@ -18503,7 +18656,7 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
18503
18656
  for (let index = 0; index < segments.length; index += 1) {
18504
18657
  noteIgnoreDirectory(ignoreBudget);
18505
18658
  await loadIgnoreFiles(ancestor, normalizedRelative(root, ancestor), ignores, ignoreBudget, signal);
18506
- ancestor = resolve24(ancestor, segments[index]);
18659
+ ancestor = resolve25(ancestor, segments[index]);
18507
18660
  }
18508
18661
  }
18509
18662
  async function walk(directory) {
@@ -18522,7 +18675,7 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
18522
18675
  entries.sort((a, b) => comparePaths(a.isDirectory() ? `${a.name}/` : a.name, b.isDirectory() ? `${b.name}/` : b.name));
18523
18676
  for (const entry2 of entries) {
18524
18677
  abort(signal);
18525
- const absolute = resolve24(directory, entry2.name);
18678
+ const absolute = resolve25(directory, entry2.name);
18526
18679
  const path = normalizedRelative(root, absolute);
18527
18680
  if (entry2.name === ".git" || !includeIgnored && ignored(path, entry2.isDirectory(), ignores)) continue;
18528
18681
  if (entry2.isDirectory()) await walk(absolute);
@@ -18542,7 +18695,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
18542
18695
  for (let index = 0; index < segments.length; index += 1) {
18543
18696
  noteIgnoreDirectory(ignoreBudget);
18544
18697
  await loadIgnoreFiles(ancestor, normalizedRelative(root, ancestor), layers, ignoreBudget, signal);
18545
- ancestor = resolve24(ancestor, segments[index]);
18698
+ ancestor = resolve25(ancestor, segments[index]);
18546
18699
  }
18547
18700
  async function walk(directory) {
18548
18701
  abort(signal);
@@ -18556,7 +18709,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
18556
18709
  noteIgnoreEntry(ignoreBudget);
18557
18710
  if (count > resources.maxEntriesPerDirectory) throw new Error(`Per-directory entry limit of ${resources.maxEntriesPerDirectory} exceeded: ${relativeDirectory}`);
18558
18711
  if (!entry2.isDirectory() || entry2.name === ".git") continue;
18559
- const absolute = resolve24(directory, entry2.name);
18712
+ const absolute = resolve25(directory, entry2.name);
18560
18713
  const path = normalizedRelative(root, absolute);
18561
18714
  if (!ignored(path, true, layers)) await walk(absolute);
18562
18715
  }
@@ -18566,7 +18719,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
18566
18719
  }
18567
18720
  async function loadIgnoreFiles(directory, base, rules, budget, signal) {
18568
18721
  for (const name of [".gitignore", ".ignore"]) {
18569
- const path = resolve24(directory, name);
18722
+ const path = resolve25(directory, name);
18570
18723
  const contents = await readOptionalIgnoreFile(path, budget.limits.maxIgnoreFileBytes, signal);
18571
18724
  if (contents === void 0) continue;
18572
18725
  budget.totalBytes += contents.length;
@@ -18672,9 +18825,9 @@ function searchPath(path) {
18672
18825
  return path?.trim().toLowerCase() === "null" ? void 0 : path ?? void 0;
18673
18826
  }
18674
18827
  function scope(root, path = ".") {
18675
- const candidate = resolve24(root, path);
18828
+ const candidate = resolve25(root, path);
18676
18829
  const delta = relative17(root, candidate);
18677
- if (delta === ".." || delta.startsWith(`..${sep17}`) || isAbsolute12(delta)) throw new Error("Path is outside the workspace");
18830
+ if (delta === ".." || delta.startsWith(`..${sep17}`) || isAbsolute13(delta)) throw new Error("Path is outside the workspace");
18678
18831
  return candidate;
18679
18832
  }
18680
18833
  async function resolveGrepPath(root, path = ".") {
@@ -18723,7 +18876,7 @@ var SearchSpawnError = class extends Error {
18723
18876
  }
18724
18877
  fallbackSafe;
18725
18878
  };
18726
- function delimitedParser(delimiter, onRecord) {
18879
+ function delimitedParser(delimiter2, onRecord) {
18727
18880
  let pending2 = Buffer.alloc(0);
18728
18881
  let stopped = false;
18729
18882
  return {
@@ -18732,7 +18885,7 @@ function delimitedParser(delimiter, onRecord) {
18732
18885
  pending2 = pending2.length === 0 ? chunk : Buffer.concat([pending2, chunk]);
18733
18886
  let offset = 0;
18734
18887
  while (true) {
18735
- const end = pending2.indexOf(delimiter, offset);
18888
+ const end = pending2.indexOf(delimiter2, offset);
18736
18889
  if (end < 0) break;
18737
18890
  if (!onRecord(pending2.subarray(offset, end))) {
18738
18891
  stopped = true;
@@ -19184,9 +19337,9 @@ function charset(contentType) {
19184
19337
 
19185
19338
  // src/tools/lsp.ts
19186
19339
  import { spawn as spawn5 } from "child_process";
19187
- import { existsSync as existsSync3 } from "fs";
19340
+ import { existsSync as existsSync4 } from "fs";
19188
19341
  import { access as access2, constants as constants9, readFile as readFile21 } from "fs/promises";
19189
- import { basename as basename6, dirname as dirname20, extname as extname2, isAbsolute as isAbsolute13, relative as relative18, resolve as resolve25, sep as sep18 } from "path";
19342
+ import { basename as basename6, dirname as dirname20, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
19190
19343
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "url";
19191
19344
  import { z as z32 } from "zod";
19192
19345
  var LSP_REQUEST_TIMEOUT_MS = 15e3;
@@ -19200,10 +19353,12 @@ var LspConnection = class {
19200
19353
  #disposed = false;
19201
19354
  #initError;
19202
19355
  constructor(command, args, cwd) {
19203
- this.#process = spawn5(command, args, {
19356
+ const invocation = prepareSpawnInvocation(command, args, { cwd });
19357
+ this.#process = spawn5(invocation.command, invocation.args, {
19204
19358
  cwd,
19205
19359
  stdio: ["pipe", "pipe", "pipe"],
19206
- windowsHide: true
19360
+ windowsHide: true,
19361
+ ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
19207
19362
  });
19208
19363
  this.#process.on("error", (err) => {
19209
19364
  this.#initError = new Error(`LSP server "${command}" failed to start: ${err.message}`);
@@ -19285,8 +19440,8 @@ var LspConnection = class {
19285
19440
  const id = this.#nextId++;
19286
19441
  const request = { jsonrpc: "2.0", id, method, params };
19287
19442
  const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
19288
- const promise = new Promise((resolve41, reject) => {
19289
- this.#pending.set(id, { resolve: resolve41, reject });
19443
+ const promise = new Promise((resolve42, reject) => {
19444
+ this.#pending.set(id, { resolve: resolve42, reject });
19290
19445
  timeout.addEventListener("abort", () => {
19291
19446
  if (this.#pending.has(id)) {
19292
19447
  this.#pending.delete(id);
@@ -19402,7 +19557,7 @@ var LspBuffer = class {
19402
19557
  }
19403
19558
  }
19404
19559
  };
19405
- var TYPESCRIPT_CLI = resolve25(
19560
+ var TYPESCRIPT_CLI = resolve26(
19406
19561
  dirname20(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
19407
19562
  "lib",
19408
19563
  "tsc.js"
@@ -19444,7 +19599,7 @@ var RealLspManager = class {
19444
19599
  #serverConfigs;
19445
19600
  #onStatus;
19446
19601
  constructor(options) {
19447
- this.#workspace = resolve25(options.workspace);
19602
+ this.#workspace = resolve26(options.workspace);
19448
19603
  this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
19449
19604
  this.#onStatus = options.onStatus;
19450
19605
  }
@@ -19493,7 +19648,7 @@ var RealLspManager = class {
19493
19648
  if (pending2 !== void 0) return pending2;
19494
19649
  const config = this.#serverConfigs.find((c) => c.language === language);
19495
19650
  if (config === void 0) return void 0;
19496
- const hasRoot = config.rootFiles.some((file) => existsSync3(resolve25(this.#workspace, file)));
19651
+ const hasRoot = config.rootFiles.some((file) => existsSync4(resolve26(this.#workspace, file)));
19497
19652
  if (!hasRoot) return void 0;
19498
19653
  const startPromise = this.#startServer(config);
19499
19654
  this.#pendingStarts.set(language, startPromise);
@@ -19536,10 +19691,10 @@ var LspDiagnosticsInput = z32.object({
19536
19691
  file: z32.string().min(1)
19537
19692
  });
19538
19693
  function createLspTools(workspace, options = {}) {
19539
- const root = resolve25(workspace);
19694
+ const root = resolve26(workspace);
19540
19695
  const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
19541
19696
  const guard = (input) => {
19542
- const candidate = resolve25(root, input);
19697
+ const candidate = resolve26(root, input);
19543
19698
  if (!isWithin7(root, candidate)) throw new Error("Path is outside the workspace");
19544
19699
  return candidate;
19545
19700
  };
@@ -19603,7 +19758,7 @@ function createLspTools(workspace, options = {}) {
19603
19758
  return [findRefs, hover, diagnostics];
19604
19759
  }
19605
19760
  function languageIdForPath(filePath) {
19606
- switch (extname2(filePath).toLowerCase()) {
19761
+ switch (extname3(filePath).toLowerCase()) {
19607
19762
  case ".tsx":
19608
19763
  return "typescriptreact";
19609
19764
  case ".mts":
@@ -19616,7 +19771,7 @@ function languageIdForPath(filePath) {
19616
19771
  }
19617
19772
  function isWithin7(root, candidate) {
19618
19773
  const delta = relative18(root, candidate);
19619
- return delta === "" || !delta.startsWith(`..${sep18}`) && delta !== ".." && !isAbsolute13(delta);
19774
+ return delta === "" || !delta.startsWith(`..${sep18}`) && delta !== ".." && !isAbsolute14(delta);
19620
19775
  }
19621
19776
  function uriToPath(uri) {
19622
19777
  try {
@@ -20004,12 +20159,12 @@ ${explanation}` : "";
20004
20159
  // src/desktop/astgraph-service.ts
20005
20160
  import { stat as stat10 } from "fs/promises";
20006
20161
  import { createRequire as nodeCreateRequire } from "module";
20007
- import { join as join28, resolve as resolve26 } from "path";
20162
+ import { join as join28, resolve as resolve27 } from "path";
20008
20163
  var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
20009
20164
  var DesktopAstGraphService = class {
20010
20165
  #path;
20011
20166
  constructor(workspace) {
20012
- this.#path = join28(resolve26(workspace), ".flavor", "astgraph", "index.db");
20167
+ this.#path = join28(resolve27(workspace), ".flavor", "astgraph", "index.db");
20013
20168
  }
20014
20169
  async status() {
20015
20170
  if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
@@ -20171,7 +20326,7 @@ var QuestionBridge = class {
20171
20326
  }
20172
20327
  }
20173
20328
  #askLocally(questions, signal) {
20174
- return new Promise((resolve41, reject) => {
20329
+ return new Promise((resolve42, reject) => {
20175
20330
  const onAbort = () => {
20176
20331
  this.#pending = void 0;
20177
20332
  this.#active = false;
@@ -20185,7 +20340,7 @@ var QuestionBridge = class {
20185
20340
  this.#pending = void 0;
20186
20341
  this.#active = false;
20187
20342
  this.#onChange?.();
20188
- resolve41(answers);
20343
+ resolve42(answers);
20189
20344
  },
20190
20345
  reject: (error) => {
20191
20346
  signal.removeEventListener("abort", onAbort);
@@ -20278,7 +20433,7 @@ function createTodoWriteTool() {
20278
20433
  // src/tools/managed.ts
20279
20434
  import { randomUUID as randomUUID18 } from "crypto";
20280
20435
  import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
20281
- import { dirname as dirname21, join as join29, resolve as resolve27 } from "path";
20436
+ import { dirname as dirname21, join as join29, resolve as resolve28 } from "path";
20282
20437
  import { z as z37 } from "zod";
20283
20438
  var MANAGED_TOOL_VERSION = 1;
20284
20439
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -20320,9 +20475,9 @@ var ManagedToolStore = class {
20320
20475
  #entries = /* @__PURE__ */ new Map();
20321
20476
  #diagnostics = [];
20322
20477
  constructor(options) {
20323
- this.#workspace = resolve27(options.workspace);
20478
+ this.#workspace = resolve28(options.workspace);
20324
20479
  this.#projectRoot = join29(this.#workspace, ".flavor", "tools");
20325
- this.#globalRoot = join29(resolve27(options.home), ".flavor-code", "tools");
20480
+ this.#globalRoot = join29(resolve28(options.home), ".flavor-code", "tools");
20326
20481
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
20327
20482
  }
20328
20483
  get diagnostics() {
@@ -21874,9 +22029,9 @@ Address this now (for example run the required verification or fix), then finish
21874
22029
  }
21875
22030
  #waitForPlanningIdle(key) {
21876
22031
  if (!this.#activePlanning.has(key)) return Promise.resolve();
21877
- return new Promise((resolve41) => {
22032
+ return new Promise((resolve42) => {
21878
22033
  const check2 = () => {
21879
- if (!this.#activePlanning.has(key)) resolve41();
22034
+ if (!this.#activePlanning.has(key)) resolve42();
21880
22035
  else this.#planningIdleWaiters.add(check2);
21881
22036
  };
21882
22037
  this.#planningIdleWaiters.add(check2);
@@ -22025,6 +22180,101 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
22025
22180
  ["id-ID", "Indonesian (id-ID)"]
22026
22181
  ]);
22027
22182
 
22183
+ // src/utils/crash-guard.ts
22184
+ import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
22185
+ import { join as join30 } from "path";
22186
+ var installed = false;
22187
+ var crashing = false;
22188
+ var crashCleanups = /* @__PURE__ */ new Set();
22189
+ function registerCrashCleanup(cleanup) {
22190
+ crashCleanups.add(cleanup);
22191
+ return () => {
22192
+ crashCleanups.delete(cleanup);
22193
+ };
22194
+ }
22195
+ async function runCrashCleanups(timeoutMs) {
22196
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new Error("Crash cleanup timeout must be non-negative");
22197
+ const cleanup = Promise.allSettled([...crashCleanups].map(async (callback) => callback()));
22198
+ if (timeoutMs === 0) return;
22199
+ let timer;
22200
+ try {
22201
+ await Promise.race([
22202
+ cleanup,
22203
+ new Promise((resolvePromise) => {
22204
+ timer = setTimeout(resolvePromise, timeoutMs);
22205
+ })
22206
+ ]);
22207
+ } finally {
22208
+ if (timer !== void 0) clearTimeout(timer);
22209
+ }
22210
+ }
22211
+ function installCrashGuard(options = {}) {
22212
+ if (installed) return;
22213
+ installed = true;
22214
+ const crashLogDir = () => {
22215
+ try {
22216
+ return join30((options.workspace ?? process.cwd)(), ".flavor");
22217
+ } catch {
22218
+ return process.cwd();
22219
+ }
22220
+ };
22221
+ const writeCrashLog = (kind, detail) => {
22222
+ try {
22223
+ const dir = crashLogDir();
22224
+ mkdirSync3(dir, { recursive: true });
22225
+ const path = join30(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
22226
+ writeFileSync(path, [
22227
+ `kind: ${kind}`,
22228
+ `version: ${process.version}`,
22229
+ `platform: ${process.platform}`,
22230
+ `time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
22231
+ "",
22232
+ detail
22233
+ ].join("\n"), { encoding: "utf8", mode: 384 });
22234
+ return path;
22235
+ } catch {
22236
+ return void 0;
22237
+ }
22238
+ };
22239
+ const restoreTerminal = () => {
22240
+ try {
22241
+ if (process.stdout.isTTY === true) process.stdout.write("\x1B[?25h\x1B[?1049l");
22242
+ } catch {
22243
+ }
22244
+ };
22245
+ const describe = (reason) => {
22246
+ if (reason instanceof Error) return `${reason.name}: ${reason.message}
22247
+ ${reason.stack ?? ""}`;
22248
+ return String(reason);
22249
+ };
22250
+ const crash = (kind, reason) => {
22251
+ if (crashing) {
22252
+ process.exit(1);
22253
+ return;
22254
+ }
22255
+ crashing = true;
22256
+ const detail = describe(reason);
22257
+ const logPath = writeCrashLog(kind, detail);
22258
+ restoreTerminal();
22259
+ try {
22260
+ const safe = redactErrorText(detail.split("\n").slice(0, 3).join("\n"));
22261
+ process.stderr.write(`flavor crashed (${kind}): ${safe}
22262
+ `);
22263
+ if (logPath !== void 0) process.stderr.write(`crash log: ${logPath}
22264
+ `);
22265
+ } catch {
22266
+ }
22267
+ const cleanupTimeoutMs = options.cleanupTimeoutMs ?? 1e3;
22268
+ const watchdog = setTimeout(() => process.exit(1), cleanupTimeoutMs + 50);
22269
+ void runCrashCleanups(cleanupTimeoutMs).finally(() => {
22270
+ clearTimeout(watchdog);
22271
+ process.exit(1);
22272
+ });
22273
+ };
22274
+ process.on("unhandledRejection", (reason) => crash("unhandledRejection", reason));
22275
+ process.on("uncaughtException", (error) => crash("uncaughtException", error));
22276
+ }
22277
+
22028
22278
  // src/hallucination/confidence.ts
22029
22279
  import { z as z39 } from "zod";
22030
22280
 
@@ -22744,7 +22994,7 @@ ${total}`;
22744
22994
  // src/memory/store.ts
22745
22995
  import { createHash as createHash17 } from "crypto";
22746
22996
  import { readFile as readFile23, rm as rm12 } from "fs/promises";
22747
- import { resolve as resolve28, join as join30, relative as relative19, sep as sep19 } from "path";
22997
+ import { resolve as resolve29, join as join31, relative as relative19, sep as sep19 } from "path";
22748
22998
 
22749
22999
  // src/memory/similarity.ts
22750
23000
  function normalizeForSimilarity(value) {
@@ -22846,10 +23096,10 @@ var MemoryStore = class {
22846
23096
  constructor(options) {
22847
23097
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
22848
23098
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
22849
- this.workspace = resolve28(options.workspace);
22850
- this.#memoryRoot = join30(this.workspace, ".flavor", "memory");
22851
- this.path = join30(this.#memoryRoot, "MEMORY.md");
22852
- this.#behaviorPath = join30(this.#memoryRoot, "behavior.json");
23099
+ this.workspace = resolve29(options.workspace);
23100
+ this.#memoryRoot = join31(this.workspace, ".flavor", "memory");
23101
+ this.path = join31(this.#memoryRoot, "MEMORY.md");
23102
+ this.#behaviorPath = join31(this.#memoryRoot, "behavior.json");
22853
23103
  this.#maxEntries = options.maxEntries;
22854
23104
  this.#maxEntryChars = options.maxEntryChars;
22855
23105
  }
@@ -23072,7 +23322,7 @@ var MemoryStore = class {
23072
23322
  }
23073
23323
  #resolveContentPath(contentPath) {
23074
23324
  if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
23075
- const path = resolve28(this.#memoryRoot, ...contentPath.split("/"));
23325
+ const path = resolve29(this.#memoryRoot, ...contentPath.split("/"));
23076
23326
  const rel = relative19(this.#memoryRoot, path);
23077
23327
  if (rel === ".." || rel.startsWith(`..${sep19}`)) throw new Error("Memory content path escapes the memory directory");
23078
23328
  return path;
@@ -23532,12 +23782,12 @@ var MemoryReviewBridge = class {
23532
23782
 
23533
23783
  // src/execution/docker.ts
23534
23784
  import { spawn as spawn6 } from "child_process";
23535
- import { relative as relative20, resolve as resolve29, sep as sep20, win32 } from "path";
23785
+ import { relative as relative20, resolve as resolve30, sep as sep20, win32 } from "path";
23536
23786
  function buildDockerInvocation(options) {
23537
23787
  const workspace = options.workspace;
23538
23788
  const cwd = options.request.cwd;
23539
23789
  const windows = /^[A-Za-z]:[\\/]/.test(workspace);
23540
- const delta = windows ? win32.relative(workspace, cwd) : relative20(resolve29(workspace), resolve29(cwd));
23790
+ const delta = windows ? win32.relative(workspace, cwd) : relative20(resolve30(workspace), resolve30(cwd));
23541
23791
  if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep20}`)) throw new Error("Docker cwd escapes workspace");
23542
23792
  const containerCwd = delta.length === 0 ? "/workspace" : `/workspace/${delta.replaceAll("\\", "/")}`;
23543
23793
  const args = [
@@ -23638,7 +23888,7 @@ function createExecutionEnvironment(workspace, config) {
23638
23888
 
23639
23889
  // src/ide/client.ts
23640
23890
  import { readFile as readFile24, readdir as readdir10, stat as stat11 } from "fs/promises";
23641
- import { basename as basename7, join as join31, relative as relative21, resolve as resolve30, sep as sep21 } from "path";
23891
+ import { basename as basename7, join as join32, relative as relative21, resolve as resolve31, sep as sep21 } from "path";
23642
23892
  var FlavorIdeClient = class {
23643
23893
  #workspace;
23644
23894
  #home;
@@ -23649,8 +23899,8 @@ var FlavorIdeClient = class {
23649
23899
  #sessionId;
23650
23900
  #eventTail = Promise.resolve();
23651
23901
  constructor(options) {
23652
- this.#workspace = resolve30(options.workspace);
23653
- this.#home = resolve30(options.home);
23902
+ this.#workspace = resolve31(options.workspace);
23903
+ this.#home = resolve31(options.home);
23654
23904
  this.#environment = options.environment ?? process.env;
23655
23905
  this.#fetch = options.fetch ?? fetch;
23656
23906
  }
@@ -23734,7 +23984,7 @@ var FlavorIdeClient = class {
23734
23984
  };
23735
23985
  if (await this.#healthy(configured)) return configured;
23736
23986
  }
23737
- const directory = join31(this.#home, ".flavor-code", "ide");
23987
+ const directory = join32(this.#home, ".flavor-code", "ide");
23738
23988
  let entries;
23739
23989
  try {
23740
23990
  entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
@@ -23742,7 +23992,7 @@ var FlavorIdeClient = class {
23742
23992
  return void 0;
23743
23993
  }
23744
23994
  const candidates = (await Promise.all(entries.map(async (name) => {
23745
- const path = join31(directory, name);
23995
+ const path = join32(directory, name);
23746
23996
  try {
23747
23997
  const [contents, info] = await Promise.all([readFile24(path, "utf8"), stat11(path)]);
23748
23998
  const lock = parseLockfile(contents);
@@ -23848,8 +24098,8 @@ function parsePort(value) {
23848
24098
  return Number.isInteger(port2) && port2 > 0 && port2 <= 65535 ? port2 : void 0;
23849
24099
  }
23850
24100
  function isWithin8(parent, child) {
23851
- const base = normalize(resolve30(parent));
23852
- const target = normalize(resolve30(child));
24101
+ const base = normalize(resolve31(parent));
24102
+ const target = normalize(resolve31(child));
23853
24103
  return target === base || target.startsWith(`${base}${sep21}`);
23854
24104
  }
23855
24105
  function normalize(path) {
@@ -23879,8 +24129,8 @@ var JobRegistry = class {
23879
24129
  const id = `job-${randomUUID19().slice(0, 12)}`;
23880
24130
  const now = (/* @__PURE__ */ new Date()).toISOString();
23881
24131
  let settle;
23882
- const done = new Promise((resolve41) => {
23883
- settle = resolve41;
24132
+ const done = new Promise((resolve42) => {
24133
+ settle = resolve42;
23884
24134
  });
23885
24135
  const record3 = {
23886
24136
  id,
@@ -24004,7 +24254,7 @@ function snapshot2(job) {
24004
24254
  // src/terminal/service.ts
24005
24255
  import { randomUUID as randomUUID20 } from "crypto";
24006
24256
  import { createRequire as nodeCreateRequire2 } from "module";
24007
- import { isAbsolute as isAbsolute14, relative as relative22, resolve as resolve31, sep as sep22 } from "path";
24257
+ import { isAbsolute as isAbsolute15, relative as relative22, resolve as resolve32, sep as sep22 } from "path";
24008
24258
  var TerminalService = class {
24009
24259
  #root;
24010
24260
  #factory;
@@ -24012,7 +24262,7 @@ var TerminalService = class {
24012
24262
  #maxOutputChars;
24013
24263
  #sessions = /* @__PURE__ */ new Map();
24014
24264
  constructor(workspace, options = {}) {
24015
- this.#root = resolve31(workspace);
24265
+ this.#root = resolve32(workspace);
24016
24266
  this.#factory = options.factory ?? defaultFactory;
24017
24267
  this.#jobs = options.jobs;
24018
24268
  this.#maxOutputChars = options.maxOutputChars ?? 2e5;
@@ -24130,15 +24380,15 @@ function cleanEnvironment(environment) {
24130
24380
  return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
24131
24381
  }
24132
24382
  function workspacePath(root, input) {
24133
- const path = resolve31(root, input);
24383
+ const path = resolve32(root, input);
24134
24384
  const delta = relative22(root, path);
24135
- if (delta === ".." || delta.startsWith(`..${sep22}`) || isAbsolute14(delta)) throw new Error("Terminal cwd is outside the workspace");
24385
+ if (delta === ".." || delta.startsWith(`..${sep22}`) || isAbsolute15(delta)) throw new Error("Terminal cwd is outside the workspace");
24136
24386
  return path;
24137
24387
  }
24138
24388
 
24139
24389
  // src/tools/terminal.ts
24140
24390
  import { z as z40 } from "zod";
24141
- import { resolve as resolve32 } from "path";
24391
+ import { resolve as resolve33 } from "path";
24142
24392
  var Id = z40.string().regex(/^term-[a-f0-9-]+$/);
24143
24393
  var OpenInput = z40.object({ cwd: z40.string().optional(), shell: z40.string().optional(), args: z40.array(z40.string()).optional(), columns: z40.coerce.number().int().min(10).max(500).optional(), rows: z40.coerce.number().int().min(2).max(300).optional() });
24144
24394
  var OptionalBoolean = z40.union([z40.boolean(), z40.string().refine((value) => value === "true" || value === "false")]);
@@ -24152,8 +24402,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
24152
24402
  name: "TerminalOpen",
24153
24403
  description: "Open a persistent interactive pseudo-terminal in the workspace",
24154
24404
  inputSchema: OpenInput,
24155
- paths: (input) => [resolve32(workspace, input.cwd ?? ".")],
24156
- permissions: (input) => ({ paths: [resolve32(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve32(workspace, input.cwd ?? ".") }),
24405
+ paths: (input) => [resolve33(workspace, input.cwd ?? ".")],
24406
+ permissions: (input) => ({ paths: [resolve33(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve33(workspace, input.cwd ?? ".") }),
24157
24407
  execute: async (input, _signal, context) => terminals.open({
24158
24408
  owner: owner(context),
24159
24409
  ...input.cwd === void 0 ? {} : { cwd: input.cwd },
@@ -24249,7 +24499,7 @@ function palSocketAddress(options) {
24249
24499
  import { createHash as createHash20 } from "crypto";
24250
24500
  import { Socket as Socket2 } from "net";
24251
24501
  import { tmpdir as tmpdir3 } from "os";
24252
- import { join as join33 } from "path";
24502
+ import { join as join34 } from "path";
24253
24503
  import { spawn as spawn7 } from "child_process";
24254
24504
 
24255
24505
  // src/pals/broker.ts
@@ -24262,16 +24512,16 @@ import { dirname as dirname23 } from "path";
24262
24512
  import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
24263
24513
  import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink4 } from "fs/promises";
24264
24514
  import { homedir } from "os";
24265
- import { join as join32 } from "path";
24515
+ import { join as join33 } from "path";
24266
24516
  var PAL_AUTH_TOKEN_BYTES = 32;
24267
24517
  var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
24268
24518
  async function loadOrCreatePalAuthToken(options = {}) {
24269
- const directory = join32(options.home ?? homedir(), ".flavor-code", "pals");
24270
- const tokenPath = join32(directory, "auth-token");
24519
+ const directory = join33(options.home ?? homedir(), ".flavor-code", "pals");
24520
+ const tokenPath = join33(directory, "auth-token");
24271
24521
  await mkdir21(directory, { recursive: true, mode: 448 });
24272
24522
  await chmod2(directory, 448);
24273
24523
  const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
24274
- const temporaryPath = join32(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
24524
+ const temporaryPath = join33(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
24275
24525
  let handle;
24276
24526
  try {
24277
24527
  handle = await open12(temporaryPath, "wx", 384);
@@ -24384,15 +24634,15 @@ async function acquirePalFileLock(options) {
24384
24634
  // src/pals/broker-cli.ts
24385
24635
  var startupAttempts = /* @__PURE__ */ new Map();
24386
24636
  function delay3(milliseconds) {
24387
- return new Promise((resolve41) => setTimeout(resolve41, milliseconds));
24637
+ return new Promise((resolve42) => setTimeout(resolve42, milliseconds));
24388
24638
  }
24389
24639
  function canConnect(address) {
24390
- return new Promise((resolve41) => {
24640
+ return new Promise((resolve42) => {
24391
24641
  const socket = new Socket2();
24392
24642
  const finish = (connected) => {
24393
24643
  socket.removeAllListeners();
24394
24644
  socket.destroy();
24395
- resolve41(connected);
24645
+ resolve42(connected);
24396
24646
  };
24397
24647
  socket.once("connect", () => finish(true));
24398
24648
  socket.once("error", () => finish(false));
@@ -24401,7 +24651,7 @@ function canConnect(address) {
24401
24651
  }
24402
24652
  function startupLockPath(address) {
24403
24653
  const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
24404
- return join33(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
24654
+ return join34(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
24405
24655
  }
24406
24656
  async function spawnProductionBroker(address) {
24407
24657
  const entry2 = process.argv[1];
@@ -24583,8 +24833,8 @@ var PalClient = class {
24583
24833
  let disconnectTimer;
24584
24834
  await Promise.race([
24585
24835
  this.request({ version: 1, type: "disconnect", requestId: randomUUID24() }).catch(() => void 0),
24586
- new Promise((resolve41) => {
24587
- disconnectTimer = setTimeout(resolve41, Math.min(this.requestTimeoutMs, 250));
24836
+ new Promise((resolve42) => {
24837
+ disconnectTimer = setTimeout(resolve42, Math.min(this.requestTimeoutMs, 250));
24588
24838
  })
24589
24839
  ]);
24590
24840
  if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
@@ -24630,7 +24880,7 @@ var PalClient = class {
24630
24880
  return presence;
24631
24881
  }
24632
24882
  openSocket() {
24633
- return new Promise((resolve41, reject) => {
24883
+ return new Promise((resolve42, reject) => {
24634
24884
  let settled = false;
24635
24885
  const rejectConnection = (error) => {
24636
24886
  if (settled) return;
@@ -24653,7 +24903,7 @@ var PalClient = class {
24653
24903
  settled = true;
24654
24904
  if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
24655
24905
  socket.off("error", onInitialError);
24656
- resolve41();
24906
+ resolve42();
24657
24907
  };
24658
24908
  const onInitialError = (error) => {
24659
24909
  socket.off("connect", onConnect);
@@ -24684,12 +24934,12 @@ var PalClient = class {
24684
24934
  if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
24685
24935
  return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
24686
24936
  }
24687
- return new Promise((resolve41, reject) => {
24937
+ return new Promise((resolve42, reject) => {
24688
24938
  const timer = setTimeout(() => {
24689
24939
  this.pending.delete(request.requestId);
24690
24940
  reject(new Error(`Pal request '${request.type}' timed out`));
24691
24941
  }, this.requestTimeoutMs);
24692
- this.pending.set(request.requestId, { resolve: resolve41, reject, timer });
24942
+ this.pending.set(request.requestId, { resolve: resolve42, reject, timer });
24693
24943
  let frame;
24694
24944
  try {
24695
24945
  frame = encodeControlFrame(BrokerRequestSchema.parse(request));
@@ -25093,8 +25343,8 @@ var ApprovalBridge = class {
25093
25343
  }
25094
25344
  };
25095
25345
  async function createProductionRuntime(options) {
25096
- const workspace = resolve33(options.workspace ?? process.cwd());
25097
- const home = resolve33(options.home ?? homedir2());
25346
+ const workspace = resolve34(options.workspace ?? process.cwd());
25347
+ const home = resolve34(options.home ?? homedir2());
25098
25348
  const environment = options.environment ?? process.env;
25099
25349
  const ide = new FlavorIdeClient({ workspace, home, environment });
25100
25350
  const ideReady = ide.initialize();
@@ -25337,8 +25587,8 @@ async function createProductionRuntime(options) {
25337
25587
  const registeredProviders = registration.registered;
25338
25588
  let effectiveLlm = registration.effectiveLlm;
25339
25589
  const pluginHost = new PluginHost({
25340
- globalPluginDirs: [join34(home, ".flavor-code", "plugins")],
25341
- projectPluginDirs: [join34(workspace, ".flavor", "plugins")],
25590
+ globalPluginDirs: [join35(home, ".flavor-code", "plugins")],
25591
+ projectPluginDirs: [join35(workspace, ".flavor", "plugins")],
25342
25592
  config,
25343
25593
  // Existing project plugins (including astgraph and superharness) use Node.js
25344
25594
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -25413,6 +25663,7 @@ async function createProductionRuntime(options) {
25413
25663
  let collaborationEventPump;
25414
25664
  let collaborationEventsOpen = true;
25415
25665
  let islandControlServer;
25666
+ let sessionLease;
25416
25667
  const pumpCollaborationEvents = () => {
25417
25668
  if (collaborationEventPump !== void 0 || collaborationSession === void 0 || deliverCollaborationEvent === void 0) return;
25418
25669
  collaborationEventPump = (async () => {
@@ -25446,7 +25697,7 @@ async function createProductionRuntime(options) {
25446
25697
  const address = palSocketAddress({
25447
25698
  platform: process.platform,
25448
25699
  userScope: home,
25449
- runtimeDir: join34(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
25700
+ runtimeDir: join35(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
25450
25701
  });
25451
25702
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
25452
25703
  instanceId: collaboration.instanceId,
@@ -25557,8 +25808,8 @@ async function createProductionRuntime(options) {
25557
25808
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
25558
25809
  });
25559
25810
  const skills = new SkillRegistry({
25560
- globalRoots: [join34(home, ".flavor-code", "skills")],
25561
- projectRoots: [join34(workspace, ".flavor", "skills"), ...pluginSkillRoots],
25811
+ globalRoots: [join35(home, ".flavor-code", "skills")],
25812
+ projectRoots: [join35(workspace, ".flavor", "skills"), ...pluginSkillRoots],
25562
25813
  authorizeResource: async () => true,
25563
25814
  disabledNames: config.skills.disabled
25564
25815
  });
@@ -25566,7 +25817,7 @@ async function createProductionRuntime(options) {
25566
25817
  skillsReady.catch(() => void 0);
25567
25818
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
25568
25819
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
25569
- const flavor = await optionalText2(join34(workspace, "FLAVOR.md"));
25820
+ const flavor = await optionalText2(join35(workspace, "FLAVOR.md"));
25570
25821
  const instructionBaseline = await workspaceInstructions.baseline();
25571
25822
  let memoryContext;
25572
25823
  if (memoryStore !== void 0) {
@@ -25591,6 +25842,7 @@ async function createProductionRuntime(options) {
25591
25842
  const subagentStartedAt = {};
25592
25843
  const subagentElapsedMs = {};
25593
25844
  let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`;
25845
+ sessionLease = await sessionStore.acquireLease(sessionId);
25594
25846
  hookSessionId = sessionId;
25595
25847
  let harnessJournal = new HarnessJournal({ workspace, sessionId });
25596
25848
  const harnessRecovery = harnessJournal.recover();
@@ -25908,7 +26160,7 @@ async function createProductionRuntime(options) {
25908
26160
  maxEntryChars: config.memory.maxEntryChars,
25909
26161
  scoreThreshold: config.memory.scoreThreshold,
25910
26162
  autoStoreThreshold: config.memory.autoStoreThreshold,
25911
- maxCandidates: Math.min(config.memory.maxCandidatesPerTask, 1),
26163
+ maxCandidates: config.memory.maxCandidatesPerTask,
25912
26164
  ...config.language === void 0 ? {} : { language: config.language },
25913
26165
  generate: (prompt, signal) => generateMemoryExtraction(registry, childModel, prompt, signal)
25914
26166
  }) : void 0;
@@ -26147,7 +26399,7 @@ async function createProductionRuntime(options) {
26147
26399
  evolveService.toolDefinition(),
26148
26400
  ...mcpTools
26149
26401
  ];
26150
- const loopFlavor = await optionalText2(join34(input.workspace, "FLAVOR.md"));
26402
+ const loopFlavor = await optionalText2(join35(input.workspace, "FLAVOR.md"));
26151
26403
  const loopInstructionBaseline = await loopInstructions.baseline();
26152
26404
  const loopEnvironment = createPromptEnvironment({
26153
26405
  now: /* @__PURE__ */ new Date(),
@@ -26506,8 +26758,8 @@ async function createProductionRuntime(options) {
26506
26758
  explain: (query, focus, signal, onText) => runExplain({
26507
26759
  graph: new DesktopAstGraphService(workspace),
26508
26760
  readFile: async (path) => {
26509
- const root = resolve33(workspace);
26510
- const abs = resolve33(root, path);
26761
+ const root = resolve34(workspace);
26762
+ const abs = resolve34(root, path);
26511
26763
  if (abs !== root && !abs.startsWith(root + sep23)) throw new Error(`path outside workspace: ${path}`);
26512
26764
  return readFile27(abs, "utf8");
26513
26765
  },
@@ -26588,7 +26840,12 @@ ${summary2}`;
26588
26840
  for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
26589
26841
  for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
26590
26842
  const previousIdeSessionId = ideSessionId;
26591
- sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`;
26843
+ const nextSessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID25().slice(0, 8)}`;
26844
+ const nextLease = await sessionStore.acquireLease(nextSessionId);
26845
+ const previousLease = sessionLease;
26846
+ sessionLease = nextLease;
26847
+ sessionId = nextSessionId;
26848
+ await previousLease?.release();
26592
26849
  hookSessionId = sessionId;
26593
26850
  harnessJournal = new HarnessJournal({ workspace, sessionId });
26594
26851
  setUsageSession(sessionId);
@@ -26708,7 +26965,7 @@ ${renderMemoryDocument(entries)}`;
26708
26965
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
26709
26966
  }
26710
26967
  try {
26711
- const tokenStore = createFileTokenStore(join34(home, ".flavor-code", "auth.json"));
26968
+ const tokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
26712
26969
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
26713
26970
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
26714
26971
  const oauth = new OAuthCallbackAuthProvider({
@@ -26755,7 +27012,7 @@ ${renderMemoryDocument(entries)}`;
26755
27012
  }
26756
27013
  },
26757
27014
  async logout() {
26758
- const tokenStore = createFileTokenStore(join34(home, ".flavor-code", "auth.json"));
27015
+ const tokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
26759
27016
  const tokens2 = await tokenStore.load();
26760
27017
  const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
26761
27018
  if (!hadCredentials) {
@@ -26835,24 +27092,13 @@ ${renderMemoryDocument(entries)}`;
26835
27092
  setPermissionProfile: (profile) => harness.setPermissionProfile(profile)
26836
27093
  };
26837
27094
  let disposed = false;
26838
- return {
26839
- session,
26840
- services,
26841
- authorization,
26842
- approvals,
26843
- memoryReviews,
26844
- restoredTranscript,
26845
- jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
26846
- get sessionId() {
26847
- return sessionId;
26848
- },
26849
- get diagnostics() {
26850
- return diagnostics.map((item) => redactSecrets(item, secrets));
26851
- },
26852
- async dispose() {
26853
- if (disposed) return;
26854
- disposed = true;
26855
- const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
27095
+ let unregisterCrashCleanup = () => void 0;
27096
+ const disposeRuntime = async () => {
27097
+ if (disposed) return;
27098
+ disposed = true;
27099
+ unregisterCrashCleanup();
27100
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
27101
+ try {
26856
27102
  collaborationEventsOpen = false;
26857
27103
  unsubscribeCollaboration?.();
26858
27104
  if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
@@ -26871,8 +27117,29 @@ ${renderMemoryDocument(entries)}`;
26871
27117
  memoryReviews.dispose();
26872
27118
  if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
26873
27119
  await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, { stepTimeoutMs });
27120
+ } finally {
27121
+ const lease = sessionLease;
27122
+ sessionLease = void 0;
27123
+ await lease?.release();
26874
27124
  }
26875
27125
  };
27126
+ unregisterCrashCleanup = registerCrashCleanup(disposeRuntime);
27127
+ return {
27128
+ session,
27129
+ services,
27130
+ authorization,
27131
+ approvals,
27132
+ memoryReviews,
27133
+ restoredTranscript,
27134
+ jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
27135
+ get sessionId() {
27136
+ return sessionId;
27137
+ },
27138
+ get diagnostics() {
27139
+ return diagnostics.map((item) => redactSecrets(item, secrets));
27140
+ },
27141
+ dispose: disposeRuntime
27142
+ };
26876
27143
  } catch (primaryError) {
26877
27144
  collaborationEventsOpen = false;
26878
27145
  mcpDiscarded = true;
@@ -26893,6 +27160,12 @@ ${renderMemoryDocument(entries)}`;
26893
27160
  } catch (cleanupError) {
26894
27161
  attachCleanupError(primaryError, cleanupError);
26895
27162
  }
27163
+ try {
27164
+ await sessionLease?.release();
27165
+ } catch (leaseError) {
27166
+ attachCleanupError(primaryError, leaseError);
27167
+ }
27168
+ sessionLease = void 0;
26896
27169
  throw primaryError;
26897
27170
  }
26898
27171
  }
@@ -27031,8 +27304,8 @@ async function workspaceFingerprint(workspace) {
27031
27304
  let remaining = 5 * 1024 * 1024;
27032
27305
  for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
27033
27306
  if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
27034
- const path = resolve33(workspace, name);
27035
- const relativePath = relative23(resolve33(workspace), path);
27307
+ const path = resolve34(workspace, name);
27308
+ const relativePath = relative23(resolve34(workspace), path);
27036
27309
  if (relativePath === ".." || relativePath.startsWith(`..${sep23}`)) continue;
27037
27310
  hash.update(name);
27038
27311
  if (remaining <= 0) continue;
@@ -27165,8 +27438,8 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
27165
27438
  previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
27166
27439
  } catch {
27167
27440
  }
27168
- mkdirSync3(dirname24(markerPath), { recursive: true });
27169
- writeFileSync(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
27441
+ mkdirSync4(dirname24(markerPath), { recursive: true });
27442
+ writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
27170
27443
  `, "utf8");
27171
27444
  } catch {
27172
27445
  }
@@ -27267,7 +27540,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
27267
27540
  const configured = { ...providers };
27268
27541
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
27269
27542
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
27270
- const oauthTokenStore = createFileTokenStore(join34(home, ".flavor-code", "auth.json"));
27543
+ const oauthTokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
27271
27544
  const registered = [];
27272
27545
  let effectiveLlm;
27273
27546
  for (const [name, provider] of Object.entries(configured)) {
@@ -27700,11 +27973,11 @@ function createMemoryTaskId() {
27700
27973
  import { createHash as createHash27 } from "crypto";
27701
27974
  import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as stat15, writeFile as writeFile17 } from "fs/promises";
27702
27975
  import { homedir as homedir4 } from "os";
27703
- import { basename as basename9, join as join44, resolve as resolve36 } from "path";
27976
+ import { basename as basename9, join as join45, resolve as resolve37 } from "path";
27704
27977
 
27705
27978
  // src/d2c/workflow.ts
27706
27979
  import { createHash as createHash22 } from "crypto";
27707
- import { join as join35 } from "path";
27980
+ import { join as join36 } from "path";
27708
27981
  import { z as z42 } from "zod";
27709
27982
 
27710
27983
  // src/d2c/workflow-shared.ts
@@ -27850,7 +28123,7 @@ function reviewsFor(report, previous) {
27850
28123
  function implementationDirectory(report) {
27851
28124
  const source = report.implementation.source;
27852
28125
  if (!/^https?:\/\//i.test(source)) return source;
27853
- return join35("src", "d2c-output", report.task);
28126
+ return join36("src", "d2c-output", report.task);
27854
28127
  }
27855
28128
  function createWorkflow(report, framework) {
27856
28129
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -27974,7 +28247,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
27974
28247
  };
27975
28248
  }
27976
28249
  function workflowPath(workspace, task) {
27977
- return join35(taskDir(workspace, task), "workflow.json");
28250
+ return join36(taskDir(workspace, task), "workflow.json");
27978
28251
  }
27979
28252
  async function readWorkflow(workspace, task) {
27980
28253
  return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
@@ -28228,7 +28501,7 @@ function confirmApiMapping(mapping, operationKey, operations) {
28228
28501
 
28229
28502
  // src/d2c/modules.ts
28230
28503
  import { readFile as readFile28 } from "fs/promises";
28231
- import { isAbsolute as isAbsolute15, join as join36, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
28504
+ import { isAbsolute as isAbsolute16, join as join37, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
28232
28505
  import { z as z43 } from "zod";
28233
28506
  var ModuleSchema = z43.object({
28234
28507
  id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
@@ -28240,20 +28513,20 @@ var ModuleSchema = z43.object({
28240
28513
  }).strict();
28241
28514
  var ManifestSchema3 = z43.object({ schema: z43.literal(1), modules: z43.array(ModuleSchema).min(1).max(500) }).passthrough();
28242
28515
  function safeSource(path) {
28243
- if (isAbsolute15(path) || posix.isAbsolute(path) || win322.isAbsolute(path) || path.includes("\0")) return false;
28516
+ if (isAbsolute16(path) || posix.isAbsolute(path) || win322.isAbsolute(path) || path.includes("\0")) return false;
28244
28517
  const value = normalize2(path);
28245
28518
  const portableSegments = path.replaceAll("\\", "/").split("/");
28246
28519
  return value !== ".." && !value.startsWith(`..${sep24}`) && !portableSegments.includes("..");
28247
28520
  }
28248
28521
  function d2cOutputDirectory(workspace, task) {
28249
28522
  if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
28250
- return join36(workspace, "src", "d2c-output", task);
28523
+ return join37(workspace, "src", "d2c-output", task);
28251
28524
  }
28252
28525
  async function readD2cModules(workspace, task) {
28253
28526
  const project = d2cOutputDirectory(workspace, task);
28254
28527
  let raw;
28255
28528
  try {
28256
- raw = await readFile28(join36(project, "d2c.modules.json"), "utf8");
28529
+ raw = await readFile28(join37(project, "d2c.modules.json"), "utf8");
28257
28530
  } catch {
28258
28531
  return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
28259
28532
  }
@@ -28274,7 +28547,7 @@ async function readD2cModules(workspace, task) {
28274
28547
 
28275
28548
  // src/d2c/integration.ts
28276
28549
  import { mkdir as mkdir24, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
28277
- import { join as join37 } from "path";
28550
+ import { join as join38 } from "path";
28278
28551
  function record2(value) {
28279
28552
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
28280
28553
  }
@@ -28410,7 +28683,7 @@ async def reset_acceptance(request: Request):
28410
28683
  }
28411
28684
  async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
28412
28685
  if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
28413
- const packagePath = join37(projectDir, "package.json");
28686
+ const packagePath = join38(projectDir, "package.json");
28414
28687
  let pkg;
28415
28688
  try {
28416
28689
  pkg = JSON.parse(await readFile29(packagePath, "utf8"));
@@ -28420,9 +28693,9 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
28420
28693
  const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
28421
28694
  const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
28422
28695
  const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
28423
- const apiDir = join37(projectDir, "src", "api");
28424
- const mockDir = join37(projectDir, "mock");
28425
- const serverDir = join37(projectDir, "server");
28696
+ const apiDir = join38(projectDir, "src", "api");
28697
+ const mockDir = join38(projectDir, "mock");
28698
+ const serverDir = join38(projectDir, "server");
28426
28699
  await Promise.all([
28427
28700
  mkdir24(apiDir, { recursive: true }),
28428
28701
  ...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
@@ -28432,23 +28705,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
28432
28705
  if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
28433
28706
  else files.push("mock/server.mjs");
28434
28707
  await Promise.all([
28435
- writeFile13(join37(apiDir, "http.js"), `import axios from "axios";
28708
+ writeFile13(join38(apiDir, "http.js"), `import axios from "axios";
28436
28709
 
28437
28710
  export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
28438
28711
 
28439
28712
  http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
28440
28713
  `),
28441
- writeFile13(join37(apiDir, "d2c-api.js"), apiModule(selected)),
28442
- writeFile13(join37(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
28714
+ writeFile13(join38(apiDir, "d2c-api.js"), apiModule(selected)),
28715
+ writeFile13(join38(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
28443
28716
  `),
28444
- ...options.pythonServer ? [] : [writeFile13(join37(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
28717
+ ...options.pythonServer ? [] : [writeFile13(join38(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
28445
28718
  writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
28446
28719
  `),
28447
28720
  ...options.pythonServer ? [
28448
- writeFile13(join37(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
28449
- writeFile13(join37(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
28450
- 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"),
28451
- writeFile13(join37(serverDir, "flavor-runtime.json"), `${JSON.stringify({
28721
+ writeFile13(join38(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
28722
+ writeFile13(join38(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
28723
+ writeFile13(join38(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"),
28724
+ writeFile13(join38(serverDir, "flavor-runtime.json"), `${JSON.stringify({
28452
28725
  schema: 1,
28453
28726
  kind: "python-fastapi",
28454
28727
  cwd: "server",
@@ -28469,7 +28742,7 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
28469
28742
  import { spawn as spawn8 } from "child_process";
28470
28743
  import { access as access3, readFile as readFile30 } from "fs/promises";
28471
28744
  import { createServer as createServer5 } from "net";
28472
- import { join as join38 } from "path";
28745
+ import { join as join39 } from "path";
28473
28746
  async function exists3(path) {
28474
28747
  try {
28475
28748
  await access3(path);
@@ -28577,7 +28850,7 @@ ${output.trim()}` : ""}`);
28577
28850
  }
28578
28851
  }
28579
28852
  async function runPythonBackend(projectDir, options) {
28580
- const serverDir = join38(projectDir, "server");
28853
+ const serverDir = join39(projectDir, "server");
28581
28854
  if (options.installDependencies !== false) await installPython(serverDir, options.signal);
28582
28855
  const selectedPort = await port();
28583
28856
  const url = `http://127.0.0.1:${selectedPort}`;
@@ -28623,7 +28896,7 @@ ${managed.output().slice(-4e3)}`);
28623
28896
  };
28624
28897
  }
28625
28898
  async function runD2cMockServer(projectDir, options = {}) {
28626
- const runtimePath = join38(projectDir, "server", "flavor-runtime.json");
28899
+ const runtimePath = join39(projectDir, "server", "flavor-runtime.json");
28627
28900
  if (await exists3(runtimePath)) {
28628
28901
  let runtime;
28629
28902
  try {
@@ -28634,9 +28907,9 @@ async function runD2cMockServer(projectDir, options = {}) {
28634
28907
  if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
28635
28908
  return runPythonBackend(projectDir, options);
28636
28909
  }
28637
- const serverPath = join38(projectDir, "mock", "server.mjs");
28910
+ const serverPath = join39(projectDir, "mock", "server.mjs");
28638
28911
  if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
28639
- if (options.installDependencies === false && !await exists3(join38(projectDir, "node_modules", "express", "package.json"))) {
28912
+ if (options.installDependencies === false && !await exists3(join39(projectDir, "node_modules", "express", "package.json"))) {
28640
28913
  throw new Error("D2C mock server dependencies are not installed");
28641
28914
  }
28642
28915
  if (options.installDependencies !== false) await install(projectDir, options.signal);
@@ -28679,7 +28952,7 @@ ${managed.output().slice(-4e3)}`);
28679
28952
  // src/d2c/product.ts
28680
28953
  import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
28681
28954
  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";
28682
- import { join as join39 } from "path";
28955
+ import { join as join40 } from "path";
28683
28956
  import { z as z44 } from "zod";
28684
28957
 
28685
28958
  // src/e2e/prd-governance.ts
@@ -28834,13 +29107,13 @@ var PlanSchema2 = z44.object({
28834
29107
  }).strict();
28835
29108
  function d2cProductDirectory(workspace, task) {
28836
29109
  if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
28837
- return join39(taskDir(workspace, task), "product");
29110
+ return join40(taskDir(workspace, task), "product");
28838
29111
  }
28839
29112
  function d2cProductPrototypeDirectory(workspace, task) {
28840
- return join39(d2cProductDirectory(workspace, task), "prototype");
29113
+ return join40(d2cProductDirectory(workspace, task), "prototype");
28841
29114
  }
28842
29115
  function planPath(workspace, task) {
28843
- return join39(taskDir(workspace, task), "product-plan.json");
29116
+ return join40(taskDir(workspace, task), "product-plan.json");
28844
29117
  }
28845
29118
  function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
28846
29119
  const frontendCandidates = [
@@ -29023,16 +29296,16 @@ async function readD2cProductPlan(workspace, task) {
29023
29296
  }
29024
29297
  async function discoverD2cProductArtifacts(workspace, plan) {
29025
29298
  const product = d2cProductDirectory(workspace, plan.task);
29026
- const prdUpdatedAt = await fileTimestamp(join39(product, "prd.md"));
29027
- const prdHash = await fileHash(join39(product, "prd.md"));
29028
- const prototypeUpdatedAt = await fileTimestamp(join39(product, "prototype", "index.html"));
29029
- const prototypeHash = await fileHash(join39(product, "prototype", "index.html"));
29030
- const expectedManifest = join39(product, "prototype", "interaction-manifest.json");
29299
+ const prdUpdatedAt = await fileTimestamp(join40(product, "prd.md"));
29300
+ const prdHash = await fileHash(join40(product, "prd.md"));
29301
+ const prototypeUpdatedAt = await fileTimestamp(join40(product, "prototype", "index.html"));
29302
+ const prototypeHash = await fileHash(join40(product, "prototype", "index.html"));
29303
+ const expectedManifest = join40(product, "prototype", "interaction-manifest.json");
29031
29304
  let manifestUpdatedAt = await fileTimestamp(expectedManifest);
29032
29305
  if (manifestUpdatedAt === void 0) {
29033
- const misplacedManifest = join39(product, "interaction-manifest.json");
29306
+ const misplacedManifest = join40(product, "interaction-manifest.json");
29034
29307
  if (await fileTimestamp(misplacedManifest) !== void 0) {
29035
- await mkdir25(join39(product, "prototype"), { recursive: true });
29308
+ await mkdir25(join40(product, "prototype"), { recursive: true });
29036
29309
  await copyFile2(misplacedManifest, expectedManifest);
29037
29310
  manifestUpdatedAt = await fileTimestamp(expectedManifest);
29038
29311
  }
@@ -29065,8 +29338,8 @@ async function readD2cProductPlanView(workspace, task) {
29065
29338
  if (stored === void 0) return void 0;
29066
29339
  const plan = await discoverD2cProductArtifacts(workspace, stored);
29067
29340
  if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
29068
- const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join39(taskDir(workspace, task), plan.prd.path), "utf8");
29069
- const manifestPath = join39(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
29341
+ const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join40(taskDir(workspace, task), plan.prd.path), "utf8");
29342
+ const manifestPath = join40(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
29070
29343
  const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
29071
29344
  return {
29072
29345
  plan,
@@ -29225,7 +29498,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
29225
29498
 
29226
29499
  // src/e2e/delivery-run.ts
29227
29500
  import { createHash as createHash26 } from "crypto";
29228
- import { join as join40 } from "path";
29501
+ import { join as join41 } from "path";
29229
29502
  import { z as z45 } from "zod";
29230
29503
  var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
29231
29504
  var ArtifactSchema = z45.object({
@@ -29342,7 +29615,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
29342
29615
  return { ...run, nodes, updatedAt: timestamp2 };
29343
29616
  }
29344
29617
  function deliveryRunPath(workspace, task) {
29345
- return join40(taskDir(workspace, task), "delivery-run.json");
29618
+ return join41(taskDir(workspace, task), "delivery-run.json");
29346
29619
  }
29347
29620
  function decodeDeliveryRun(raw) {
29348
29621
  return DeliveryRunSchema.parse(JSON.parse(raw));
@@ -29385,14 +29658,14 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
29385
29658
  // src/skills/manager.ts
29386
29659
  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";
29387
29660
  import { homedir as homedir3 } from "os";
29388
- import { isAbsolute as isAbsolute16, join as join41, relative as relative24, resolve as resolve34, sep as sep25 } from "path";
29661
+ import { isAbsolute as isAbsolute17, join as join42, relative as relative24, resolve as resolve35, sep as sep25 } from "path";
29389
29662
  import { stringify } from "yaml";
29390
29663
  var SkillManager = class {
29391
29664
  #workspace;
29392
29665
  #home;
29393
29666
  constructor(options) {
29394
- this.#workspace = resolve34(options.workspace);
29395
- this.#home = resolve34(options.home ?? homedir3());
29667
+ this.#workspace = resolve35(options.workspace);
29668
+ this.#home = resolve35(options.home ?? homedir3());
29396
29669
  }
29397
29670
  async list() {
29398
29671
  const { registry, disabled } = await this.#registry();
@@ -29408,7 +29681,7 @@ var SkillManager = class {
29408
29681
  async create(draft) {
29409
29682
  const value = parseDraft(draft);
29410
29683
  const projectRoot = this.#projectRoot();
29411
- const directory = join41(projectRoot, value.name);
29684
+ const directory = join42(projectRoot, value.name);
29412
29685
  await mkdir26(projectRoot, { recursive: true, mode: 448 });
29413
29686
  try {
29414
29687
  await mkdir26(directory, { mode: 448 });
@@ -29417,7 +29690,7 @@ var SkillManager = class {
29417
29690
  throw error;
29418
29691
  }
29419
29692
  try {
29420
- await writeFile16(join41(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
29693
+ await writeFile16(join42(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
29421
29694
  await this.#persistEnabled(value.name, true);
29422
29695
  return this.get(value.name);
29423
29696
  } catch (error) {
@@ -29435,7 +29708,7 @@ var SkillManager = class {
29435
29708
  throw error;
29436
29709
  });
29437
29710
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
29438
- const skillFile = join41(directory, "SKILL.md");
29711
+ const skillFile = join42(directory, "SKILL.md");
29439
29712
  const fileInfo = await lstat13(skillFile);
29440
29713
  if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
29441
29714
  await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
@@ -29467,12 +29740,12 @@ var SkillManager = class {
29467
29740
  async #registry() {
29468
29741
  const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
29469
29742
  const [globalPluginRoots, projectPluginRoots] = await Promise.all([
29470
- discoverPluginSkillRoots(join41(this.#home, ".flavor-code", "plugins")),
29471
- discoverPluginSkillRoots(join41(this.#workspace, ".flavor", "plugins"))
29743
+ discoverPluginSkillRoots(join42(this.#home, ".flavor-code", "plugins")),
29744
+ discoverPluginSkillRoots(join42(this.#workspace, ".flavor", "plugins"))
29472
29745
  ]);
29473
29746
  return {
29474
29747
  registry: new SkillRegistry({
29475
- globalRoots: [join41(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
29748
+ globalRoots: [join42(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
29476
29749
  projectRoots: [this.#projectRoot(), ...projectPluginRoots]
29477
29750
  }),
29478
29751
  disabled: new Set(loaded.config.skills.disabled)
@@ -29488,10 +29761,10 @@ var SkillManager = class {
29488
29761
  };
29489
29762
  }
29490
29763
  #projectRoot() {
29491
- return join41(this.#workspace, ".flavor", "skills");
29764
+ return join42(this.#workspace, ".flavor", "skills");
29492
29765
  }
29493
29766
  #projectSkillDirectory(name) {
29494
- return join41(this.#projectRoot(), name);
29767
+ return join42(this.#projectRoot(), name);
29495
29768
  }
29496
29769
  };
29497
29770
  function parseName(value) {
@@ -29532,16 +29805,16 @@ async function discoverPluginSkillRoots(directory) {
29532
29805
  const roots = [];
29533
29806
  for (const entry2 of entries) {
29534
29807
  if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
29535
- const pluginRoot = await realpath13(join41(directory, entry2.name));
29808
+ const pluginRoot = await realpath13(join42(directory, entry2.name));
29536
29809
  try {
29537
- const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join41(pluginRoot, "flavor-plugin.json"), "utf8")));
29810
+ const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join42(pluginRoot, "flavor-plugin.json"), "utf8")));
29538
29811
  for (const contribution of manifest.contributes.skillRoots) {
29539
- const candidate = resolve34(pluginRoot, contribution.path);
29812
+ const candidate = resolve35(pluginRoot, contribution.path);
29540
29813
  const info = await lstat13(candidate);
29541
29814
  if (!info.isDirectory() || info.isSymbolicLink()) continue;
29542
29815
  const physical = await realpath13(candidate);
29543
29816
  const relation = relative24(pluginRoot, physical);
29544
- if (relation === ".." || relation.startsWith(`..${sep25}`) || isAbsolute16(relation)) continue;
29817
+ if (relation === ".." || relation.startsWith(`..${sep25}`) || isAbsolute17(relation)) continue;
29545
29818
  roots.push(physical);
29546
29819
  }
29547
29820
  } catch {
@@ -29609,11 +29882,11 @@ async function createProjectMemoryManager(options) {
29609
29882
  }
29610
29883
 
29611
29884
  // src/mcp/config-manager.ts
29612
- import { join as join42 } from "path";
29885
+ import { join as join43 } from "path";
29613
29886
  var ProjectMcpConfigManager = class {
29614
29887
  path;
29615
29888
  constructor(workspace) {
29616
- this.path = join42(workspace, ".flavor", "flavor.json");
29889
+ this.path = join43(workspace, ".flavor", "flavor.json");
29617
29890
  }
29618
29891
  async list() {
29619
29892
  const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
@@ -29710,7 +29983,7 @@ function isRecord7(value) {
29710
29983
  }
29711
29984
 
29712
29985
  // src/desktop/model-config.ts
29713
- import { join as join43 } from "path";
29986
+ import { join as join44 } from "path";
29714
29987
  var DEFAULT_DESKTOP_MODELS = [
29715
29988
  {
29716
29989
  id: "anthropic:deepseek-v4-pro",
@@ -29731,7 +30004,7 @@ var DEFAULT_DESKTOP_MODELS = [
29731
30004
  ];
29732
30005
  async function loadDesktopModels(workspace, home) {
29733
30006
  try {
29734
- const tokens2 = await createFileTokenStore(join43(home, ".flavor-code", "auth.json")).load();
30007
+ const tokens2 = await createFileTokenStore(join44(home, ".flavor-code", "auth.json")).load();
29735
30008
  const pkceModels = [];
29736
30009
  const knownPkce = /* @__PURE__ */ new Set();
29737
30010
  for (const token of Object.values(tokens2)) {
@@ -29801,7 +30074,7 @@ function protocolLabel(type) {
29801
30074
  import { createReadStream as createReadStream2 } from "fs";
29802
30075
  import { realpath as realpath14, stat as stat14 } from "fs/promises";
29803
30076
  import { createServer as createServer6 } from "http";
29804
- import { extname as extname3, isAbsolute as isAbsolute17, relative as relative25, resolve as resolve35, sep as sep26 } from "path";
30077
+ import { extname as extname4, isAbsolute as isAbsolute18, relative as relative25, resolve as resolve36, sep as sep26 } from "path";
29805
30078
  var MIME_TYPES = {
29806
30079
  ".css": "text/css; charset=utf-8",
29807
30080
  ".gif": "image/gif",
@@ -29820,10 +30093,10 @@ function sendNotFound(response2) {
29820
30093
  }
29821
30094
  function isInside(root, candidate) {
29822
30095
  const delta = relative25(root, candidate);
29823
- return delta === "" || !delta.startsWith(`..${sep26}`) && delta !== ".." && !isAbsolute17(delta);
30096
+ return delta === "" || !delta.startsWith(`..${sep26}`) && delta !== ".." && !isAbsolute18(delta);
29824
30097
  }
29825
30098
  async function startD2cProductPreview(rootDirectory) {
29826
- const root = await realpath14(resolve35(rootDirectory));
30099
+ const root = await realpath14(resolve36(rootDirectory));
29827
30100
  const rootInfo = await stat14(root);
29828
30101
  if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
29829
30102
  const server = createServer6(async (request, response2) => {
@@ -29835,7 +30108,7 @@ async function startD2cProductPreview(rootDirectory) {
29835
30108
  let pathname = decodeURIComponent(url.pathname);
29836
30109
  if (pathname === "/") pathname = "/index.html";
29837
30110
  if (pathname.includes("\0")) return sendNotFound(response2);
29838
- const unresolved = resolve35(root, `.${pathname}`);
30111
+ const unresolved = resolve36(root, `.${pathname}`);
29839
30112
  if (!isInside(root, unresolved)) return sendNotFound(response2);
29840
30113
  const target = await realpath14(unresolved).catch(() => void 0);
29841
30114
  if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
@@ -29843,7 +30116,7 @@ async function startD2cProductPreview(rootDirectory) {
29843
30116
  if (!info.isFile()) return sendNotFound(response2);
29844
30117
  response2.writeHead(200, {
29845
30118
  "content-length": info.size,
29846
- "content-type": MIME_TYPES[extname3(target).toLowerCase()] ?? "application/octet-stream"
30119
+ "content-type": MIME_TYPES[extname4(target).toLowerCase()] ?? "application/octet-stream"
29847
30120
  });
29848
30121
  createReadStream2(target).on("error", () => response2.destroy()).pipe(response2);
29849
30122
  } catch {
@@ -29876,33 +30149,33 @@ function pngDataUrl(png) {
29876
30149
  }
29877
30150
  async function detectD2cFramework(workspace, task) {
29878
30151
  try {
29879
- const pkg = JSON.parse(await readFile35(join44(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
30152
+ const pkg = JSON.parse(await readFile35(join45(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
29880
30153
  return pkg.dependencies?.react === void 0 ? "vue" : "react";
29881
30154
  } catch {
29882
30155
  return "vue";
29883
30156
  }
29884
30157
  }
29885
30158
  function integrationDirectory(workspace, task) {
29886
- return join44(workspace, ".flavor", "d2c", task, "integration");
30159
+ return join45(workspace, ".flavor", "d2c", task, "integration");
29887
30160
  }
29888
30161
  var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
29889
30162
  var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
29890
30163
  async function d2cBackendSourceFingerprint(project) {
29891
- const runtimeManifest = join44(project, "server", "flavor-runtime.json");
30164
+ const runtimeManifest = join45(project, "server", "flavor-runtime.json");
29892
30165
  const realBackend = (await stat15(runtimeManifest).catch(() => void 0))?.isFile() === true;
29893
- const root = realBackend ? join44(project, "server") : join44(project, "mock");
30166
+ const root = realBackend ? join45(project, "server") : join45(project, "mock");
29894
30167
  if ((await stat15(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
29895
30168
  const files = [];
29896
30169
  const visit = async (directory) => {
29897
30170
  const entries = await readdir12(directory, { withFileTypes: true });
29898
30171
  for (const entry2 of entries) {
29899
30172
  if (entry2.isDirectory()) {
29900
- if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join44(directory, entry2.name));
30173
+ if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join45(directory, entry2.name));
29901
30174
  continue;
29902
30175
  }
29903
30176
  if (!entry2.isFile()) continue;
29904
30177
  const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
29905
- if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join44(directory, entry2.name));
30178
+ if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join45(directory, entry2.name));
29906
30179
  }
29907
30180
  };
29908
30181
  await visit(root);
@@ -29954,7 +30227,7 @@ var DesktopRuntimeController = class {
29954
30227
  /** Backend source digest observed when the managed process was started. */
29955
30228
  #d2cMockSourceFingerprints = /* @__PURE__ */ new Map();
29956
30229
  constructor(options) {
29957
- this.#home = resolve36(options.home ?? homedir4());
30230
+ this.#home = resolve37(options.home ?? homedir4());
29958
30231
  this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
29959
30232
  this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
29960
30233
  this.#listSessions = options.listSessions ?? (async (workspace) => {
@@ -30029,7 +30302,7 @@ var DesktopRuntimeController = class {
30029
30302
  };
30030
30303
  }
30031
30304
  async openWorkspace(path) {
30032
- const workspace = resolve36(path);
30305
+ const workspace = resolve37(path);
30033
30306
  if (workspace !== this.#workspace) {
30034
30307
  await this.#disposeRuntime();
30035
30308
  await this.#stopAllD2cPreviews();
@@ -30456,7 +30729,7 @@ var DesktopRuntimeController = class {
30456
30729
  if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
30457
30730
  throw new Error("Complete visual review before importing OpenAPI");
30458
30731
  }
30459
- const moduleManifest = join44(d2cOutputDirectory(workspace, task), "d2c.modules.json");
30732
+ const moduleManifest = join45(d2cOutputDirectory(workspace, task), "d2c.modules.json");
30460
30733
  const moduleManifestInfo = await stat15(moduleManifest).catch(() => void 0);
30461
30734
  if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
30462
30735
  throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
@@ -30469,8 +30742,8 @@ var DesktopRuntimeController = class {
30469
30742
  await mkdir27(dir, { recursive: true });
30470
30743
  const hash = createHash27("sha256").update(raw).digest("hex");
30471
30744
  await Promise.all([
30472
- writeFile17(join44(dir, "swagger.json"), raw),
30473
- writeFile17(join44(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
30745
+ writeFile17(join45(dir, "swagger.json"), raw),
30746
+ writeFile17(join45(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
30474
30747
  `)
30475
30748
  ]);
30476
30749
  const next = {
@@ -30505,7 +30778,7 @@ var DesktopRuntimeController = class {
30505
30778
  let workflow = await readWorkflow(workspace, task);
30506
30779
  if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
30507
30780
  const modules = await readD2cModules(workspace, task);
30508
- const productOpenApiPath = join44(taskDir(workspace, task), "product", "openapi.json");
30781
+ const productOpenApiPath = join45(taskDir(workspace, task), "product", "openapi.json");
30509
30782
  const authored = await readFile35(productOpenApiPath, "utf8").catch(() => void 0);
30510
30783
  const generated = authored === void 0;
30511
30784
  const prepared = await this.#storeD2cOpenApi(
@@ -30518,7 +30791,7 @@ var DesktopRuntimeController = class {
30518
30791
  return prepared;
30519
30792
  }
30520
30793
  if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
30521
- const document = JSON.parse(await readFile35(join44(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
30794
+ const document = JSON.parse(await readFile35(join45(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
30522
30795
  return { workflow, document, mappings: workflow.mappings };
30523
30796
  }
30524
30797
  async confirmD2cMapping(task, moduleId, operationKey) {
@@ -30585,7 +30858,7 @@ var DesktopRuntimeController = class {
30585
30858
  ].join("\n");
30586
30859
  const delivery = await readDeliveryRun(workspace, task);
30587
30860
  if (delivery?.nodes.api.status === "running") {
30588
- const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join44(project, file)))));
30861
+ const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join45(project, file)))));
30589
30862
  const completed = await updateDeliveryRun(
30590
30863
  workspace,
30591
30864
  task,
@@ -30612,7 +30885,7 @@ var DesktopRuntimeController = class {
30612
30885
  try {
30613
30886
  const fingerprint2 = await d2cBackendSourceFingerprint(project);
30614
30887
  if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
30615
- const environmentPath = join44(project, ".env.local");
30888
+ const environmentPath = join45(project, ".env.local");
30616
30889
  const existingEnvironment = await readFile35(environmentPath, "utf8").catch(() => "");
30617
30890
  const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
30618
30891
  VITE_API_BASE_URL=${running.url}
@@ -30693,7 +30966,7 @@ VITE_API_BASE_URL=${running.url}
30693
30966
  const readyPreview = this.#d2cPreviews.get(task);
30694
30967
  const readyMock = this.#d2cMocks.get(task);
30695
30968
  if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
30696
- const manifestPath = join44(taskDir(workspace, task), "design", "interaction-manifest.json");
30969
+ const manifestPath = join45(taskDir(workspace, task), "design", "interaction-manifest.json");
30697
30970
  const manifest = parseInteractionManifest(await readFile35(manifestPath, "utf8"));
30698
30971
  const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
30699
30972
  const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
@@ -30705,7 +30978,7 @@ VITE_API_BASE_URL=${running.url}
30705
30978
  await mkdir27(planDir, { recursive: true });
30706
30979
  try {
30707
30980
  const observations = await this.#observeD2cPages(manifest, readyPreview.url);
30708
- const prd = await readFile35(join44(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
30981
+ const prd = await readFile35(join45(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
30709
30982
  const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
30710
30983
  const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
30711
30984
  executableManifest = mergeInteractionManifests(manifest, plan.manifest);
@@ -30716,9 +30989,9 @@ VITE_API_BASE_URL=${running.url}
30716
30989
  plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
30717
30990
  pageAnalyses: plan.pageAnalyses
30718
30991
  };
30719
- await writeFile17(join44(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
30992
+ await writeFile17(join45(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
30720
30993
  `, { encoding: "utf8", mode: 384 });
30721
- await rm15(join44(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
30994
+ await rm15(join45(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
30722
30995
  } catch (cause) {
30723
30996
  const detail = message(cause);
30724
30997
  review = {
@@ -30726,8 +30999,8 @@ VITE_API_BASE_URL=${running.url}
30726
30999
  plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
30727
31000
  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}`
30728
31001
  };
30729
- await rm15(join44(planDir, "autonomous-interaction-plan.json"), { force: true });
30730
- await writeFile17(join44(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
31002
+ await rm15(join45(planDir, "autonomous-interaction-plan.json"), { force: true });
31003
+ await writeFile17(join45(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
30731
31004
  schema: 1,
30732
31005
  stage: "planning",
30733
31006
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -30741,13 +31014,13 @@ VITE_API_BASE_URL=${running.url}
30741
31014
  let result = { ...rawResult, evidenceMode: review.mode };
30742
31015
  if (coverage !== void 0) {
30743
31016
  const evidence = createAcceptanceEvidence(coverage, result);
30744
- await writeFile17(join44(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
31017
+ await writeFile17(join45(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
30745
31018
  `, { encoding: "utf8", mode: 384 });
30746
31019
  if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
30747
31020
  }
30748
31021
  const dir = integrationDirectory(workspace, task);
30749
31022
  await mkdir27(dir, { recursive: true });
30750
- await writeFile17(join44(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
31023
+ await writeFile17(join45(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
30751
31024
  `);
30752
31025
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
30753
31026
  return { workflow: next, result, review };
@@ -30760,7 +31033,7 @@ VITE_API_BASE_URL=${running.url}
30760
31033
  if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
30761
31034
  const output = mock.output().trim();
30762
31035
  try {
30763
- const dir = join44(d2cOutputDirectory(workspace, task), "mock-server.log");
31036
+ const dir = join45(d2cOutputDirectory(workspace, task), "mock-server.log");
30764
31037
  await writeFile17(dir, `${output}
30765
31038
  `);
30766
31039
  } catch {
@@ -30855,7 +31128,7 @@ VITE_API_BASE_URL=${running.url}
30855
31128
  if (current === void 0) throw new Error("D2C product plan is unavailable");
30856
31129
  if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
30857
31130
  if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
30858
- const path = join44(taskDir(workspace, task), "product", "prd.md");
31131
+ const path = join45(taskDir(workspace, task), "product", "prd.md");
30859
31132
  const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
30860
31133
  const now = (/* @__PURE__ */ new Date()).toISOString();
30861
31134
  await writeD2cProductPlan(workspace, {
@@ -30875,15 +31148,15 @@ VITE_API_BASE_URL=${running.url}
30875
31148
  async #assertProductPrdLocked(workspace, task) {
30876
31149
  const current = await readD2cProductPlanView(workspace, task);
30877
31150
  if (current?.plan.approvedPrd === void 0) return;
30878
- await assertApprovedPrd(join44(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
31151
+ await assertApprovedPrd(join45(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
30879
31152
  }
30880
31153
  async #acceptancePaths(workspace, task) {
30881
- const product = join44(taskDir(workspace, task), "product");
30882
- const openapi = join44(product, "openapi.json");
31154
+ const product = join45(taskDir(workspace, task), "product");
31155
+ const openapi = join45(product, "openapi.json");
30883
31156
  return {
30884
- prd: join44(product, "prd.md"),
30885
- prototype: join44(product, "prototype", "index.html"),
30886
- interaction: join44(product, "prototype", "interaction-manifest.json"),
31157
+ prd: join45(product, "prd.md"),
31158
+ prototype: join45(product, "prototype", "index.html"),
31159
+ interaction: join45(product, "prototype", "interaction-manifest.json"),
30887
31160
  ...await stat15(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
30888
31161
  };
30889
31162
  }
@@ -30891,17 +31164,17 @@ VITE_API_BASE_URL=${running.url}
30891
31164
  const product = await readD2cProductPlanView(workspace, task);
30892
31165
  if (product?.plan.approvedPrd === void 0) return void 0;
30893
31166
  await this.#assertProductPrdLocked(workspace, task);
30894
- const baseline = JSON.parse(await readFile35(join44(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
31167
+ const baseline = JSON.parse(await readFile35(join45(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
30895
31168
  await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
30896
- const design = join44(taskDir(workspace, task), "design");
31169
+ const design = join45(taskDir(workspace, task), "design");
30897
31170
  await Promise.all([
30898
- verifyAcceptanceArtifact(join44(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
30899
- verifyAcceptanceArtifact(join44(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
31171
+ verifyAcceptanceArtifact(join45(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
31172
+ verifyAcceptanceArtifact(join45(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
30900
31173
  ]);
30901
31174
  const workflow = await readWorkflow(workspace, task);
30902
31175
  if (workflow?.openapi !== void 0) {
30903
31176
  await verifyAcceptanceArtifact(
30904
- join44(taskDir(workspace, task), "integration", "swagger.json"),
31177
+ join45(taskDir(workspace, task), "integration", "swagger.json"),
30905
31178
  { hash: workflow.openapi.hash },
30906
31179
  "runtime OpenAPI contract"
30907
31180
  );
@@ -30922,14 +31195,14 @@ VITE_API_BASE_URL=${running.url}
30922
31195
  throw new Error("D2C PRD is empty and cannot be accepted");
30923
31196
  }
30924
31197
  if (stage === "design" && accepted) {
30925
- const manifest = join44(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
31198
+ const manifest = join45(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
30926
31199
  const normalizedManifest = parseInteractionManifest(await readFile35(manifest, "utf8"));
30927
31200
  if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
30928
31201
  verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
30929
31202
  await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
30930
31203
  `, { encoding: "utf8", mode: 384 });
30931
31204
  const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
30932
- await writeFile17(join44(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
31205
+ await writeFile17(join45(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
30933
31206
  `, { encoding: "utf8", mode: 384 });
30934
31207
  }
30935
31208
  const plan = await writeD2cProductPlan(
@@ -30963,8 +31236,8 @@ VITE_API_BASE_URL=${running.url}
30963
31236
  const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
30964
31237
  const delivery = await readDeliveryRun(workspace, task);
30965
31238
  if (delivery !== void 0) {
30966
- const prototype = await readFile35(join44(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
30967
- const interaction = await readFile35(join44(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
31239
+ const prototype = await readFile35(join45(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
31240
+ const interaction = await readFile35(join45(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
30968
31241
  const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
30969
31242
  artifactRef("product/prototype/index.html", prototype),
30970
31243
  artifactRef("product/prototype/interaction-manifest.json", interaction)
@@ -31048,10 +31321,10 @@ VITE_API_BASE_URL=${running.url}
31048
31321
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
31049
31322
  const qualityContent = `${JSON.stringify(judgment, null, 2)}
31050
31323
  `;
31051
- await writeFile17(join44(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
31324
+ await writeFile17(join45(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
31052
31325
  const delivery = await readDeliveryRun(workspace, task);
31053
31326
  if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
31054
- const interactionContent = await readFile35(join44(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
31327
+ const interactionContent = await readFile35(join45(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
31055
31328
  const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
31056
31329
  artifactRef("integration/interaction-results.json", interactionContent),
31057
31330
  artifactRef("quality-judge.json", qualityContent)
@@ -31231,70 +31504,9 @@ function desktopWindowChrome(platform = process.platform) {
31231
31504
  };
31232
31505
  }
31233
31506
 
31234
- // src/utils/crash-guard.ts
31235
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
31236
- import { join as join45 } from "path";
31237
- var installed = false;
31238
- function installCrashGuard(options = {}) {
31239
- if (installed) return;
31240
- installed = true;
31241
- const crashLogDir = () => {
31242
- try {
31243
- return join45((options.workspace ?? process.cwd)(), ".flavor");
31244
- } catch {
31245
- return process.cwd();
31246
- }
31247
- };
31248
- const writeCrashLog = (kind, detail) => {
31249
- try {
31250
- const dir = crashLogDir();
31251
- mkdirSync4(dir, { recursive: true });
31252
- const path = join45(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
31253
- writeFileSync2(path, [
31254
- `kind: ${kind}`,
31255
- `version: ${process.version}`,
31256
- `platform: ${process.platform}`,
31257
- `time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
31258
- "",
31259
- detail
31260
- ].join("\n"), { encoding: "utf8", mode: 384 });
31261
- return path;
31262
- } catch {
31263
- return void 0;
31264
- }
31265
- };
31266
- const restoreTerminal = () => {
31267
- try {
31268
- if (process.stdout.isTTY === true) process.stdout.write("\x1B[?25h\x1B[?1049l");
31269
- } catch {
31270
- }
31271
- };
31272
- const describe = (reason) => {
31273
- if (reason instanceof Error) return `${reason.name}: ${reason.message}
31274
- ${reason.stack ?? ""}`;
31275
- return String(reason);
31276
- };
31277
- const crash = (kind, reason) => {
31278
- const detail = describe(reason);
31279
- const logPath = writeCrashLog(kind, detail);
31280
- restoreTerminal();
31281
- try {
31282
- const safe = redactErrorText(detail.split("\n").slice(0, 3).join("\n"));
31283
- process.stderr.write(`flavor crashed (${kind}): ${safe}
31284
- `);
31285
- if (logPath !== void 0) process.stderr.write(`crash log: ${logPath}
31286
- `);
31287
- } catch {
31288
- }
31289
- process.exit(1);
31290
- };
31291
- process.on("unhandledRejection", (reason) => crash("unhandledRejection", reason));
31292
- process.on("uncaughtException", (error) => crash("uncaughtException", error));
31293
- }
31294
-
31295
31507
  // src/desktop/git-manager.ts
31296
31508
  import { readFile as readFile36, rm as rm16 } from "fs/promises";
31297
- import { isAbsolute as isAbsolute18, relative as relative26, resolve as resolve37 } from "path";
31509
+ import { isAbsolute as isAbsolute19, relative as relative26, resolve as resolve38 } from "path";
31298
31510
  function failure(action, stderr, stdout) {
31299
31511
  return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
31300
31512
  }
@@ -31387,13 +31599,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
31387
31599
  return path.replaceAll("\\", "/");
31388
31600
  }));
31389
31601
  try {
31390
- const tree = JSON.parse(await readFile36(resolve37(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
31602
+ const tree = JSON.parse(await readFile36(resolve38(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
31391
31603
  const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
31392
31604
  const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
31393
31605
  if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
31394
31606
  const load = async (id) => {
31395
31607
  if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
31396
- return JSON.parse(await readFile36(resolve37(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
31608
+ return JSON.parse(await readFile36(resolve38(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
31397
31609
  };
31398
31610
  const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
31399
31611
  const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
@@ -31402,7 +31614,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
31402
31614
  const object = async (digest) => {
31403
31615
  if (digest === void 0) return "";
31404
31616
  if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
31405
- const value = await readFile36(resolve37(workspace, ".flavor", "checkpoints", "objects", digest));
31617
+ const value = await readFile36(resolve38(workspace, ".flavor", "checkpoints", "objects", digest));
31406
31618
  return value.includes(0) ? "[binary content]" : value.toString("utf8");
31407
31619
  };
31408
31620
  const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
@@ -31426,11 +31638,11 @@ async function assertListed(workspace, path) {
31426
31638
  return file;
31427
31639
  }
31428
31640
  function safeWorkspacePath(workspace, path) {
31429
- if (isAbsolute18(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
31430
- const root = resolve37(workspace);
31431
- const target = resolve37(root, path);
31641
+ if (isAbsolute19(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
31642
+ const root = resolve38(workspace);
31643
+ const target = resolve38(root, path);
31432
31644
  const child = relative26(root, target);
31433
- if (child === "" || child.startsWith("..") || isAbsolute18(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
31645
+ if (child === "" || child.startsWith("..") || isAbsolute19(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
31434
31646
  return target;
31435
31647
  }
31436
31648
  function safeGitTarget(value) {
@@ -31442,13 +31654,13 @@ function safeGitTarget(value) {
31442
31654
  // src/desktop/worktree-manager.ts
31443
31655
  import { randomUUID as randomUUID28 } from "crypto";
31444
31656
  import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat16, writeFile as writeFile18 } from "fs/promises";
31445
- import { basename as basename10, isAbsolute as isAbsolute19, join as join46, relative as relative27, resolve as resolve38, sep as sep27 } from "path";
31657
+ import { basename as basename10, isAbsolute as isAbsolute20, join as join46, relative as relative27, resolve as resolve39, sep as sep27 } from "path";
31446
31658
  var DesktopWorktreeManager = class {
31447
31659
  #repository;
31448
31660
  #storage;
31449
31661
  constructor(options) {
31450
- this.#repository = resolve38(options.repository);
31451
- this.#storage = resolve38(options.storage);
31662
+ this.#repository = resolve39(options.repository);
31663
+ this.#storage = resolve39(options.storage);
31452
31664
  }
31453
31665
  async create(label) {
31454
31666
  if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
@@ -31529,21 +31741,21 @@ var DesktopWorktreeManager = class {
31529
31741
  return this.#safeStoragePath(`${id}.json`);
31530
31742
  }
31531
31743
  #safeStoragePath(name) {
31532
- const target = resolve38(this.#storage, name);
31744
+ const target = resolve39(this.#storage, name);
31533
31745
  const delta = relative27(this.#storage, target);
31534
- if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta)) throw new Error("Worktree path escapes storage");
31746
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute20(delta)) throw new Error("Worktree path escapes storage");
31535
31747
  return target;
31536
31748
  }
31537
31749
  };
31538
31750
  function validateStored(value, repository, storage) {
31539
31751
  if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
31540
31752
  const item = value;
31541
- if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve38(item.repository ?? "") !== repository) {
31753
+ if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve39(item.repository ?? "") !== repository) {
31542
31754
  throw new Error("Invalid worktree metadata");
31543
31755
  }
31544
- const path = resolve38(item.path);
31756
+ const path = resolve39(item.path);
31545
31757
  const delta = relative27(storage, path);
31546
- if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta) || basename10(path) !== item.id) {
31758
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute20(delta) || basename10(path) !== item.id) {
31547
31759
  throw new Error("Worktree metadata escapes storage");
31548
31760
  }
31549
31761
  return { version: 1, id: item.id, mode: "worktree", repository, path, branch: item.branch, createdAt: item.createdAt };
@@ -31552,15 +31764,15 @@ function validateStored(value, repository, storage) {
31552
31764
  // src/desktop/workbench-service.ts
31553
31765
  import { readFile as readFile38, readdir as readdir14, stat as stat17 } from "fs/promises";
31554
31766
  import { homedir as homedir5 } from "os";
31555
- import { join as join47, resolve as resolve39 } from "path";
31767
+ import { join as join47, resolve as resolve40 } from "path";
31556
31768
  var MAX_TEXT = 128e3;
31557
31769
  var MAX_ITEMS = 200;
31558
31770
  var DesktopWorkbenchService = class {
31559
31771
  #workspace;
31560
31772
  #home;
31561
31773
  constructor(workspace, home = homedir5()) {
31562
- this.#workspace = resolve39(workspace);
31563
- this.#home = resolve39(home);
31774
+ this.#workspace = resolve40(workspace);
31775
+ this.#home = resolve40(home);
31564
31776
  }
31565
31777
  async inspect(sessionId) {
31566
31778
  const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
@@ -31611,7 +31823,7 @@ var DesktopWorkbenchService = class {
31611
31823
  }
31612
31824
  async #permissionFiles() {
31613
31825
  const candidates = [
31614
- ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve39(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
31826
+ ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve40(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
31615
31827
  { tier: "user", path: join47(this.#home, ".flavor-code", "permissions.json"), content: "" },
31616
31828
  { tier: "project", path: join47(this.#workspace, ".flavor", "permissions.json"), content: "" },
31617
31829
  { tier: "local", path: join47(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
@@ -31741,7 +31953,7 @@ function sessionMetaKey(workspace, sessionId) {
31741
31953
  return `${workspace}\0${sessionId}`;
31742
31954
  }
31743
31955
  function worktreeManager(workspace) {
31744
- const key = createHash28("sha256").update(resolve40(workspace)).digest("hex").slice(0, 20);
31956
+ const key = createHash28("sha256").update(resolve41(workspace)).digest("hex").slice(0, 20);
31745
31957
  return new DesktopWorktreeManager({ repository: workspace, storage: join48(app.getPath("userData"), "task-worktrees", key) });
31746
31958
  }
31747
31959
  function activeWorkingDirectory() {
@@ -31947,6 +32159,9 @@ async function savePersistedProjects() {
31947
32159
  await writeFile19(statePath(), `${JSON.stringify({ workspace: activeWorkspace, projects: projectOrder, ...workbench })}
31948
32160
  `, { encoding: "utf8", mode: 384 });
31949
32161
  }
32162
+ function savePersistedProjectsInBackground() {
32163
+ void savePersistedProjects().catch((error) => logStartup("persist-projects-failed", String(error)));
32164
+ }
31950
32165
  function validRecord(value) {
31951
32166
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
31952
32167
  }
@@ -31954,7 +32169,7 @@ async function assertDirectory(path) {
31954
32169
  if (!(await stat18(path)).isDirectory()) throw new Error("The selected project is not a directory");
31955
32170
  }
31956
32171
  async function openWorkspace(path) {
31957
- const workspace = resolve40(path);
32172
+ const workspace = resolve41(path);
31958
32173
  await assertDirectory(workspace);
31959
32174
  let project = managedProjects.get(workspace);
31960
32175
  if (project === void 0) {
@@ -31965,10 +32180,10 @@ async function openWorkspace(path) {
31965
32180
  activeWorkspace = workspace;
31966
32181
  controller = nextController;
31967
32182
  project.snapshot = await nextController.openWorkspace(workspace);
31968
- for (const saved of workbench.taskCheckouts.filter((item) => resolve40(item.workspace) === workspace)) {
32183
+ for (const saved of workbench.taskCheckouts.filter((item) => resolve41(item.workspace) === workspace)) {
31969
32184
  try {
31970
32185
  await assertDirectory(saved.workingDirectory);
31971
- const task = { environment: "worktree", workingDirectory: resolve40(saved.workingDirectory), worktreeId: saved.worktreeId };
32186
+ const task = { environment: "worktree", workingDirectory: resolve41(saved.workingDirectory), worktreeId: saved.worktreeId };
31972
32187
  task.controller = createDesktopController(workspace, task);
31973
32188
  task.snapshot = await task.controller.openWorkspace(task.workingDirectory);
31974
32189
  task.sessionId = saved.sessionId;
@@ -32002,7 +32217,7 @@ function trackTaskTransition(workspace, task, previous, next) {
32002
32217
  const needsAttention = next.approval !== void 0 || (next.questions?.length ?? 0) > 0;
32003
32218
  const hadAttention = previous?.approval !== void 0 || (previous?.questions?.length ?? 0) > 0;
32004
32219
  if (needsAttention && !hadAttention) addActivity(workspace, sessionId, "attention", "\u4EFB\u52A1\u7B49\u5F85\u4F60\u7684\u786E\u8BA4");
32005
- void savePersistedProjects();
32220
+ savePersistedProjectsInBackground();
32006
32221
  }
32007
32222
  function addActivity(workspace, sessionId, kind, title, detail) {
32008
32223
  const item = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, workspace, ...sessionId === void 0 ? {} : { sessionId }, kind, title, ...detail === void 0 ? {} : { detail }, createdAt: (/* @__PURE__ */ new Date()).toISOString(), unread: true };
@@ -32014,7 +32229,7 @@ function addActivity(workspace, sessionId, kind, title, detail) {
32014
32229
  });
32015
32230
  notification.show();
32016
32231
  }
32017
- void savePersistedProjects();
32232
+ savePersistedProjectsInBackground();
32018
32233
  }
32019
32234
  async function startManagedSession(resumeSession, environment = "local") {
32020
32235
  if (activeWorkspace === void 0) throw new Error("Open a project before starting a session");
@@ -32098,7 +32313,7 @@ function installIpcHandlers() {
32098
32313
  } catch {
32099
32314
  }
32100
32315
  }
32101
- if (persisted.workspace !== void 0 && managedProjects.has(resolve40(persisted.workspace))) {
32316
+ if (persisted.workspace !== void 0 && managedProjects.has(resolve41(persisted.workspace))) {
32102
32317
  try {
32103
32318
  return await openWorkspace(persisted.workspace);
32104
32319
  } catch {
@@ -32159,7 +32374,7 @@ function installIpcHandlers() {
32159
32374
  });
32160
32375
  ipcMain.handle(DESKTOP_CHANNELS.updateProject, async (_event, value) => {
32161
32376
  const input = UpdateProjectInputSchema.parse(value);
32162
- const workspace = resolve40(input.workspace);
32377
+ const workspace = resolve41(input.workspace);
32163
32378
  if (!managedProjects.has(workspace)) throw new Error("\u9879\u76EE\u672A\u6253\u5F00");
32164
32379
  workbench.projectMeta[workspace] = { ...workbench.projectMeta[workspace], ...input.label === void 0 ? {} : { label: input.label || void 0 }, ...input.pinned === void 0 ? {} : { pinned: input.pinned } };
32165
32380
  await savePersistedProjects();
@@ -32167,7 +32382,7 @@ function installIpcHandlers() {
32167
32382
  });
32168
32383
  ipcMain.handle(DESKTOP_CHANNELS.closeProject, async (_event, value) => {
32169
32384
  const input = CloseProjectInputSchema.parse(value);
32170
- const workspace = resolve40(input.workspace);
32385
+ const workspace = resolve41(input.workspace);
32171
32386
  const project = managedProjects.get(workspace);
32172
32387
  if (project === void 0) return decorateSnapshot(controller.snapshot());
32173
32388
  if ([...project.tasks].some((task) => task.snapshot.activeSession?.busy) && !input.force) throw new Error("\u9879\u76EE\u4E2D\u4ECD\u6709\u4EFB\u52A1\u8FD0\u884C\uFF1B\u786E\u8BA4\u540E\u518D\u5173\u95ED\u9879\u76EE");
@@ -32183,12 +32398,12 @@ function installIpcHandlers() {
32183
32398
  return decorateSnapshot(controller.snapshot());
32184
32399
  });
32185
32400
  ipcMain.handle(DESKTOP_CHANNELS.revealProject, async (_event, value) => {
32186
- const workspace = resolve40(ProjectPathInputSchema.parse(value).workspace);
32401
+ const workspace = resolve41(ProjectPathInputSchema.parse(value).workspace);
32187
32402
  await assertDirectory(workspace);
32188
32403
  await shell.openPath(workspace);
32189
32404
  });
32190
32405
  ipcMain.handle(DESKTOP_CHANNELS.copyProjectPath, async (_event, value) => {
32191
- clipboard.writeText(resolve40(ProjectPathInputSchema.parse(value).workspace));
32406
+ clipboard.writeText(resolve41(ProjectPathInputSchema.parse(value).workspace));
32192
32407
  });
32193
32408
  ipcMain.handle(DESKTOP_CHANNELS.dismissRecovery, async (_event, value) => {
32194
32409
  const { sessionId } = DeleteSessionInputSchema.parse(value);