flavor-code 1.3.18 → 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.
@@ -18,7 +18,7 @@ import {
18
18
  redactConfig,
19
19
  setProjectMcpServerDisabled,
20
20
  updateProtectedFile
21
- } from "./chunk-HJOCNKPN.js";
21
+ } from "./chunk-RPFWB45Y.js";
22
22
  import {
23
23
  AuditLogger,
24
24
  appendUsageLog,
@@ -72,12 +72,12 @@ function normalizeProviderError(error) {
72
72
 
73
73
  // src/utils/async.ts
74
74
  function withTimeout(promise, ms) {
75
- return new Promise((resolve29, reject) => {
75
+ return new Promise((resolve30, reject) => {
76
76
  let settled = false;
77
77
  const timer = setTimeout(() => {
78
78
  if (settled) return;
79
79
  settled = true;
80
- resolve29({ timedOut: true, value: void 0 });
80
+ resolve30({ timedOut: true, value: void 0 });
81
81
  }, ms);
82
82
  timer.unref?.();
83
83
  promise.then(
@@ -85,7 +85,7 @@ function withTimeout(promise, ms) {
85
85
  if (!settled) {
86
86
  settled = true;
87
87
  clearTimeout(timer);
88
- resolve29({ timedOut: false, value });
88
+ resolve30({ timedOut: false, value });
89
89
  }
90
90
  },
91
91
  (error) => {
@@ -100,13 +100,13 @@ function withTimeout(promise, ms) {
100
100
  }
101
101
  function awaitWithSignal(promise, signal) {
102
102
  signal.throwIfAborted();
103
- return new Promise((resolve29, reject) => {
103
+ return new Promise((resolve30, reject) => {
104
104
  const onAbort = () => reject(signal.reason);
105
105
  signal.addEventListener("abort", onAbort, { once: true });
106
106
  promise.then(
107
107
  (value) => {
108
108
  signal.removeEventListener("abort", onAbort);
109
- resolve29(value);
109
+ resolve30(value);
110
110
  },
111
111
  (error) => {
112
112
  signal.removeEventListener("abort", onAbort);
@@ -2733,7 +2733,7 @@ var SearchSpawnError = class extends Error {
2733
2733
  }
2734
2734
  fallbackSafe;
2735
2735
  };
2736
- function delimitedParser(delimiter, onRecord) {
2736
+ function delimitedParser(delimiter2, onRecord) {
2737
2737
  let pending = Buffer.alloc(0);
2738
2738
  let stopped = false;
2739
2739
  return {
@@ -2742,7 +2742,7 @@ function delimitedParser(delimiter, onRecord) {
2742
2742
  pending = pending.length === 0 ? chunk : Buffer.concat([pending, chunk]);
2743
2743
  let offset = 0;
2744
2744
  while (true) {
2745
- const end = pending.indexOf(delimiter, offset);
2745
+ const end = pending.indexOf(delimiter2, offset);
2746
2746
  if (end < 0) break;
2747
2747
  if (!onRecord(pending.subarray(offset, end))) {
2748
2748
  stopped = true;
@@ -4194,9 +4194,9 @@ Address this now (for example run the required verification or fix), then finish
4194
4194
  }
4195
4195
  #waitForPlanningIdle(key) {
4196
4196
  if (!this.#activePlanning.has(key)) return Promise.resolve();
4197
- return new Promise((resolve29) => {
4197
+ return new Promise((resolve30) => {
4198
4198
  const check = () => {
4199
- if (!this.#activePlanning.has(key)) resolve29();
4199
+ if (!this.#activePlanning.has(key)) resolve30();
4200
4200
  else this.#planningIdleWaiters.add(check);
4201
4201
  };
4202
4202
  this.#planningIdleWaiters.add(check);
@@ -4303,6 +4303,101 @@ function requiredPals(service) {
4303
4303
  return service;
4304
4304
  }
4305
4305
 
4306
+ // src/utils/crash-guard.ts
4307
+ import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
4308
+ import { join as join5 } from "path";
4309
+ var installed = false;
4310
+ var crashing = false;
4311
+ var crashCleanups = /* @__PURE__ */ new Set();
4312
+ function registerCrashCleanup(cleanup) {
4313
+ crashCleanups.add(cleanup);
4314
+ return () => {
4315
+ crashCleanups.delete(cleanup);
4316
+ };
4317
+ }
4318
+ async function runCrashCleanups(timeoutMs) {
4319
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new Error("Crash cleanup timeout must be non-negative");
4320
+ const cleanup = Promise.allSettled([...crashCleanups].map(async (callback) => callback()));
4321
+ if (timeoutMs === 0) return;
4322
+ let timer;
4323
+ try {
4324
+ await Promise.race([
4325
+ cleanup,
4326
+ new Promise((resolvePromise) => {
4327
+ timer = setTimeout(resolvePromise, timeoutMs);
4328
+ })
4329
+ ]);
4330
+ } finally {
4331
+ if (timer !== void 0) clearTimeout(timer);
4332
+ }
4333
+ }
4334
+ function installCrashGuard(options = {}) {
4335
+ if (installed) return;
4336
+ installed = true;
4337
+ const crashLogDir = () => {
4338
+ try {
4339
+ return join5((options.workspace ?? process.cwd)(), ".flavor");
4340
+ } catch {
4341
+ return process.cwd();
4342
+ }
4343
+ };
4344
+ const writeCrashLog = (kind, detail) => {
4345
+ try {
4346
+ const dir = crashLogDir();
4347
+ mkdirSync2(dir, { recursive: true });
4348
+ const path = join5(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
4349
+ writeFileSync(path, [
4350
+ `kind: ${kind}`,
4351
+ `version: ${process.version}`,
4352
+ `platform: ${process.platform}`,
4353
+ `time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
4354
+ "",
4355
+ detail
4356
+ ].join("\n"), { encoding: "utf8", mode: 384 });
4357
+ return path;
4358
+ } catch {
4359
+ return void 0;
4360
+ }
4361
+ };
4362
+ const restoreTerminal = () => {
4363
+ try {
4364
+ if (process.stdout.isTTY === true) process.stdout.write("\x1B[?25h\x1B[?1049l");
4365
+ } catch {
4366
+ }
4367
+ };
4368
+ const describe = (reason) => {
4369
+ if (reason instanceof Error) return `${reason.name}: ${reason.message}
4370
+ ${reason.stack ?? ""}`;
4371
+ return String(reason);
4372
+ };
4373
+ const crash = (kind, reason) => {
4374
+ if (crashing) {
4375
+ process.exit(1);
4376
+ return;
4377
+ }
4378
+ crashing = true;
4379
+ const detail = describe(reason);
4380
+ const logPath = writeCrashLog(kind, detail);
4381
+ restoreTerminal();
4382
+ try {
4383
+ const safe = redactErrorText(detail.split("\n").slice(0, 3).join("\n"));
4384
+ process.stderr.write(`flavor crashed (${kind}): ${safe}
4385
+ `);
4386
+ if (logPath !== void 0) process.stderr.write(`crash log: ${logPath}
4387
+ `);
4388
+ } catch {
4389
+ }
4390
+ const cleanupTimeoutMs = options.cleanupTimeoutMs ?? 1e3;
4391
+ const watchdog = setTimeout(() => process.exit(1), cleanupTimeoutMs + 50);
4392
+ void runCrashCleanups(cleanupTimeoutMs).finally(() => {
4393
+ clearTimeout(watchdog);
4394
+ process.exit(1);
4395
+ });
4396
+ };
4397
+ process.on("unhandledRejection", (reason) => crash("unhandledRejection", reason));
4398
+ process.on("uncaughtException", (error) => crash("uncaughtException", error));
4399
+ }
4400
+
4306
4401
  // src/execution/docker.ts
4307
4402
  import { spawn as spawn2 } from "child_process";
4308
4403
  import { relative as relative5, resolve as resolve5, sep as sep5, win32 } from "path";
@@ -4401,7 +4496,7 @@ function runInvocation(invocation, signal, timeoutMs) {
4401
4496
  import { createHash as createHash3 } from "crypto";
4402
4497
  import { Socket as Socket2 } from "net";
4403
4498
  import { tmpdir } from "os";
4404
- import { join as join6 } from "path";
4499
+ import { join as join7 } from "path";
4405
4500
  import { spawn as spawn3 } from "child_process";
4406
4501
 
4407
4502
  // src/pals/broker.ts
@@ -4414,16 +4509,16 @@ import { dirname as dirname8 } from "path";
4414
4509
  import { randomBytes, randomUUID as randomUUID3 } from "crypto";
4415
4510
  import { chmod, link, mkdir as mkdir4, open as open3, readFile as readFile4, unlink } from "fs/promises";
4416
4511
  import { homedir } from "os";
4417
- import { join as join5 } from "path";
4512
+ import { join as join6 } from "path";
4418
4513
  var PAL_AUTH_TOKEN_BYTES = 32;
4419
4514
  var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
4420
4515
  async function loadOrCreatePalAuthToken(options = {}) {
4421
- const directory = join5(options.home ?? homedir(), ".flavor-code", "pals");
4422
- const tokenPath = join5(directory, "auth-token");
4516
+ const directory = join6(options.home ?? homedir(), ".flavor-code", "pals");
4517
+ const tokenPath = join6(directory, "auth-token");
4423
4518
  await mkdir4(directory, { recursive: true, mode: 448 });
4424
4519
  await chmod(directory, 448);
4425
4520
  const token = (options.random ?? randomBytes)(PAL_AUTH_TOKEN_BYTES).toString("hex");
4426
- const temporaryPath = join5(directory, `.auth-token-${process.pid}-${randomUUID3()}.tmp`);
4521
+ const temporaryPath = join6(directory, `.auth-token-${process.pid}-${randomUUID3()}.tmp`);
4427
4522
  let handle;
4428
4523
  try {
4429
4524
  handle = await open3(temporaryPath, "wx", 384);
@@ -4544,12 +4639,12 @@ var BrokerStateError = class extends Error {
4544
4639
  };
4545
4640
  var WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
4546
4641
  function canConnect(address) {
4547
- return new Promise((resolve29) => {
4642
+ return new Promise((resolve30) => {
4548
4643
  const socket = new Socket();
4549
4644
  const finish = (connected) => {
4550
4645
  socket.removeAllListeners();
4551
4646
  socket.destroy();
4552
- resolve29(connected);
4647
+ resolve30(connected);
4553
4648
  };
4554
4649
  socket.once("connect", () => finish(true));
4555
4650
  socket.once("error", () => finish(false));
@@ -4598,7 +4693,7 @@ var PalBrokerServer = class {
4598
4693
  this.authToken ??= await loadOrCreatePalAuthToken(this.authHome === void 0 ? {} : { home: this.authHome });
4599
4694
  try {
4600
4695
  await this.prepareAddress();
4601
- await new Promise((resolve29, reject) => {
4696
+ await new Promise((resolve30, reject) => {
4602
4697
  const onError = (error) => {
4603
4698
  this.server.off("listening", onListening);
4604
4699
  reject(error);
@@ -4607,7 +4702,7 @@ var PalBrokerServer = class {
4607
4702
  this.server.off("error", onError);
4608
4703
  this.started = true;
4609
4704
  this.ownsAddress = true;
4610
- resolve29();
4705
+ resolve30();
4611
4706
  };
4612
4707
  this.server.once("error", onError);
4613
4708
  this.server.once("listening", onListening);
@@ -4635,7 +4730,7 @@ var PalBrokerServer = class {
4635
4730
  const ownsAddress = this.ownsAddress;
4636
4731
  for (const connection of this.connections) connection.socket.destroy();
4637
4732
  if (this.started) {
4638
- await new Promise((resolve29) => this.server.close(() => resolve29()));
4733
+ await new Promise((resolve30) => this.server.close(() => resolve30()));
4639
4734
  this.started = false;
4640
4735
  }
4641
4736
  this.ownsAddress = false;
@@ -5304,15 +5399,15 @@ var PalBrokerState = class {
5304
5399
  // src/pals/broker-cli.ts
5305
5400
  var startupAttempts = /* @__PURE__ */ new Map();
5306
5401
  function delay(milliseconds) {
5307
- return new Promise((resolve29) => setTimeout(resolve29, milliseconds));
5402
+ return new Promise((resolve30) => setTimeout(resolve30, milliseconds));
5308
5403
  }
5309
5404
  function canConnect2(address) {
5310
- return new Promise((resolve29) => {
5405
+ return new Promise((resolve30) => {
5311
5406
  const socket = new Socket2();
5312
5407
  const finish = (connected) => {
5313
5408
  socket.removeAllListeners();
5314
5409
  socket.destroy();
5315
- resolve29(connected);
5410
+ resolve30(connected);
5316
5411
  };
5317
5412
  socket.once("connect", () => finish(true));
5318
5413
  socket.once("error", () => finish(false));
@@ -5321,7 +5416,7 @@ function canConnect2(address) {
5321
5416
  }
5322
5417
  function startupLockPath(address) {
5323
5418
  const digest = createHash3("sha256").update(address, "utf8").digest("hex").slice(0, 24);
5324
- return join6(tmpdir(), `flavor-code-pals-${digest}.startup.lock`);
5419
+ return join7(tmpdir(), `flavor-code-pals-${digest}.startup.lock`);
5325
5420
  }
5326
5421
  async function spawnProductionBroker(address) {
5327
5422
  const entry2 = process.argv[1];
@@ -5355,8 +5450,8 @@ async function runPalBroker(options) {
5355
5450
  const sweepIntervalMs = options.sweepIntervalMs ?? 5e3;
5356
5451
  const idleShutdownMs = options.idleShutdownMs ?? 3e4;
5357
5452
  let resolveClosed;
5358
- const closed = new Promise((resolve29) => {
5359
- resolveClosed = resolve29;
5453
+ const closed = new Promise((resolve30) => {
5454
+ resolveClosed = resolve30;
5360
5455
  });
5361
5456
  let closing;
5362
5457
  const timer = setInterval(() => {
@@ -5397,10 +5492,10 @@ async function ensureOnce(options) {
5397
5492
 
5398
5493
  // src/production.ts
5399
5494
  import { readFile as readFile22 } from "fs/promises";
5400
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
5495
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5401
5496
  import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
5402
5497
  import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
5403
- import { basename as basename6, dirname as dirname19, join as join26, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
5498
+ import { basename as basename6, dirname as dirname19, join as join27, relative as relative20, resolve as resolve29, sep as sep20 } from "path";
5404
5499
 
5405
5500
  // src/agent/subagents.ts
5406
5501
  import { z as z5 } from "zod";
@@ -7311,10 +7406,10 @@ function sanitize(message2, secrets) {
7311
7406
  }
7312
7407
  function waitForRetry(delayMs, signal) {
7313
7408
  if (signal?.aborted) return Promise.reject(signal.reason);
7314
- return new Promise((resolve29, reject) => {
7409
+ return new Promise((resolve30, reject) => {
7315
7410
  const timer = setTimeout(() => {
7316
7411
  signal?.removeEventListener("abort", onAbort);
7317
- resolve29();
7412
+ resolve30();
7318
7413
  }, delayMs);
7319
7414
  const onAbort = () => {
7320
7415
  clearTimeout(timer);
@@ -7952,10 +8047,10 @@ function serializeToolInput(input) {
7952
8047
  }
7953
8048
  function waitForRetry2(delayMs, signal) {
7954
8049
  if (signal?.aborted) return Promise.reject(signal.reason);
7955
- return new Promise((resolve29, reject) => {
8050
+ return new Promise((resolve30, reject) => {
7956
8051
  const timer = setTimeout(() => {
7957
8052
  signal?.removeEventListener("abort", onAbort);
7958
- resolve29();
8053
+ resolve30();
7959
8054
  }, delayMs);
7960
8055
  const onAbort = () => {
7961
8056
  clearTimeout(timer);
@@ -8033,7 +8128,7 @@ function redactClassifierText(value) {
8033
8128
  // src/tools/runtime.ts
8034
8129
  import { randomUUID as randomUUID7 } from "crypto";
8035
8130
  import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
8036
- import { basename as basename2, join as join7, resolve as resolve7 } from "path";
8131
+ import { basename as basename2, join as join8, resolve as resolve7 } from "path";
8037
8132
  import { z as z9 } from "zod";
8038
8133
 
8039
8134
  // src/tools/types.ts
@@ -8340,10 +8435,10 @@ var ToolRuntime = class {
8340
8435
  const reason = perToolExceeded && turnExceeded ? "per_tool_and_turn_limit" : perToolExceeded ? "per_tool_limit" : "turn_limit";
8341
8436
  const preview = headTailPreview(serialized, previewChars);
8342
8437
  this.#turnOutputChars += preview.length;
8343
- const directory = join7(this.#workspace, ".flavor", "tool-results");
8438
+ const directory = join8(this.#workspace, ".flavor", "tool-results");
8344
8439
  await mkdir7(directory, { recursive: true });
8345
8440
  const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7()}-${safeFilename(tool)}.txt`;
8346
- const savedTo = join7(directory, filename2);
8441
+ const savedTo = join8(directory, filename2);
8347
8442
  await writeFile4(savedTo, serialized, "utf8");
8348
8443
  return {
8349
8444
  truncated: true,
@@ -8581,8 +8676,8 @@ function toModelTool(tool) {
8581
8676
 
8582
8677
  // src/harness/journal.ts
8583
8678
  import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
8584
- import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
8585
- import { join as join8, relative as relative7, resolve as resolve8, sep as sep7 } from "path";
8679
+ import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
8680
+ import { join as join9, relative as relative7, resolve as resolve8, sep as sep7 } from "path";
8586
8681
  import { z as z10 } from "zod";
8587
8682
 
8588
8683
  // src/harness/invariants.ts
@@ -8672,14 +8767,14 @@ var HarnessJournal = class {
8672
8767
  #records = [];
8673
8768
  constructor(options) {
8674
8769
  const workspace = resolve8(options.workspace);
8675
- const directory = join8(workspace, ".flavor", "sessions");
8770
+ const directory = join9(workspace, ".flavor", "sessions");
8676
8771
  assertSessionId(options.sessionId);
8677
- mkdirSync2(directory, { recursive: true, mode: 448 });
8772
+ mkdirSync3(directory, { recursive: true, mode: 448 });
8678
8773
  assertWithin(workspace, directory);
8679
8774
  const canonicalWorkspace = realpathSync2.native(workspace);
8680
8775
  const canonicalDirectory = realpathSync2.native(directory);
8681
8776
  if (!isWithin2(canonicalWorkspace, canonicalDirectory)) throw new Error("Harness journal directory escapes the workspace");
8682
- this.#path = join8(canonicalDirectory, `${options.sessionId}.events.jsonl`);
8777
+ this.#path = join9(canonicalDirectory, `${options.sessionId}.events.jsonl`);
8683
8778
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
8684
8779
  this.#maxBytes = options.maxBytes ?? MAX_JOURNAL_BYTES;
8685
8780
  if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 1024) throw new Error("maxBytes must be an integer of at least 1024");
@@ -9055,6 +9150,64 @@ var HookDecisionSchema = z11.object({
9055
9150
  additionalContext: z11.string().optional()
9056
9151
  });
9057
9152
 
9153
+ // src/utils/spawn-executable.ts
9154
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
9155
+ import { extname, isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
9156
+ var WINDOWS_EXECUTABLE = /\.(?:com|exe)$/iu;
9157
+ var WINDOWS_CMD_SHIM = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/iu;
9158
+ var CMD_META = /([()\][%!^"`<>&|;, *?])/gu;
9159
+ function prepareSpawnInvocation(command, args, options = {}) {
9160
+ if ((options.platform ?? process.platform) !== "win32") return { command, args: [...args] };
9161
+ const env = options.env ?? process.env;
9162
+ const commandFile = resolveWindowsCommand(command, options.cwd, env);
9163
+ if (commandFile === void 0 || WINDOWS_EXECUTABLE.test(commandFile)) {
9164
+ return { command: commandFile ?? command, args: [...args] };
9165
+ }
9166
+ const doubleEscape = WINDOWS_CMD_SHIM.test(commandFile);
9167
+ const shellCommand = [escapeCmdCommand(commandFile), ...args.map((arg) => escapeCmdArgument(arg, doubleEscape))].join(" ");
9168
+ return {
9169
+ command: environmentValue(env, "ComSpec") ?? "cmd.exe",
9170
+ args: ["/d", "/s", "/c", `"${shellCommand}"`],
9171
+ windowsVerbatimArguments: true
9172
+ };
9173
+ }
9174
+ function resolveWindowsCommand(command, cwd, env) {
9175
+ const pathValue = environmentValue(env, "PATH") ?? "";
9176
+ const pathExt = (environmentValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
9177
+ const hasPath = isAbsolute5(command) || /[\\/]/u.test(command);
9178
+ const bases = hasPath ? [isAbsolute5(command) ? command : resolve9(cwd ?? process.cwd(), command)] : pathValue.split(";").filter(Boolean).map((entry2) => resolve9(unquote(entry2), command));
9179
+ for (const base of bases) {
9180
+ const candidates = extname(base).length > 0 ? [base] : [base, ...pathExt.map((extension) => `${base}${extension}`)];
9181
+ for (const candidate of candidates) {
9182
+ if (isRegularFile(candidate)) return resolve9(candidate);
9183
+ }
9184
+ }
9185
+ return void 0;
9186
+ }
9187
+ function environmentValue(env, name) {
9188
+ const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
9189
+ return key === void 0 ? void 0 : env[key];
9190
+ }
9191
+ function unquote(value) {
9192
+ const trimmed = value.trim();
9193
+ return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
9194
+ }
9195
+ function isRegularFile(path) {
9196
+ try {
9197
+ return existsSync3(path) && statSync2(path).isFile();
9198
+ } catch {
9199
+ return false;
9200
+ }
9201
+ }
9202
+ function escapeCmdCommand(value) {
9203
+ return value.replace(CMD_META, "^$1");
9204
+ }
9205
+ function escapeCmdArgument(value, doubleEscapeMetaCharacters) {
9206
+ let escaped = value.replace(/(?=(\\+?)?)\1"/gu, '$1$1\\"').replace(/(?=(\\+?)?)\1$/gu, "$1$1");
9207
+ escaped = `"${escaped}"`.replace(CMD_META, "^$1");
9208
+ return doubleEscapeMetaCharacters ? escaped.replace(CMD_META, "^$1") : escaped;
9209
+ }
9210
+
9058
9211
  // src/hooks/bus.ts
9059
9212
  var DEFAULT_TIMEOUT_MS = 1e4;
9060
9213
  var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
@@ -9161,13 +9314,16 @@ function decoratePayload(payload, envelope) {
9161
9314
  return envelope === void 0 ? payload : { ...payload, ...envelope };
9162
9315
  }
9163
9316
  function runShellHandler(descriptor, event, signal) {
9164
- return new Promise((resolve29, reject) => {
9165
- const child = spawn4(descriptor.command, [...descriptor.args ?? []], {
9166
- env: { ...process.env, ...descriptor.env },
9317
+ return new Promise((resolve30, reject) => {
9318
+ const env = { ...process.env, ...descriptor.env };
9319
+ const invocation = prepareSpawnInvocation(descriptor.command, descriptor.args ?? [], { env });
9320
+ const child = spawn4(invocation.command, invocation.args, {
9321
+ env,
9167
9322
  shell: false,
9168
9323
  detached: process.platform !== "win32",
9169
9324
  windowsHide: true,
9170
- stdio: ["pipe", "pipe", "pipe"]
9325
+ stdio: ["pipe", "pipe", "pipe"],
9326
+ ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
9171
9327
  });
9172
9328
  const maxOutputBytes = descriptor.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
9173
9329
  let stdout = "";
@@ -9204,7 +9360,7 @@ function runShellHandler(descriptor, event, signal) {
9204
9360
  return;
9205
9361
  }
9206
9362
  try {
9207
- resolve29(HookDecisionSchema.parse(JSON.parse(stdout)));
9363
+ resolve30(HookDecisionSchema.parse(JSON.parse(stdout)));
9208
9364
  } catch (error) {
9209
9365
  reject(error);
9210
9366
  }
@@ -9356,7 +9512,7 @@ function createIncidentReporter(options) {
9356
9512
  import { createHash as createHash7, randomUUID as randomUUID10 } from "crypto";
9357
9513
  import { chmod as chmod3, unlink as unlink4 } from "fs/promises";
9358
9514
  import { tmpdir as tmpdir2 } from "os";
9359
- import { join as join9 } from "path";
9515
+ import { join as join10 } from "path";
9360
9516
  import { createServer as createServer2 } from "net";
9361
9517
  var MAX_REQUEST_BYTES = 64 * 1024;
9362
9518
  var SOCKET_TIMEOUT_MS = 1e4;
@@ -9364,7 +9520,7 @@ function islandControlEndpoint(sessionId, platform = process.platform) {
9364
9520
  const digest = createHash7("sha256").update(`${process.pid}\0${sessionId}`).digest("hex").slice(0, 20);
9365
9521
  if (platform === "win32") return `\\\\.\\pipe\\flavor-island-control-${digest}`;
9366
9522
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
9367
- return join9(tmpdir2(), `flavor-island-control-${uid}-${digest}.sock`);
9523
+ return join10(tmpdir2(), `flavor-island-control-${uid}-${digest}.sock`);
9368
9524
  }
9369
9525
  async function createIslandControlServer(options) {
9370
9526
  const platform = options.platform ?? process.platform;
@@ -9401,14 +9557,14 @@ async function createIslandControlServer(options) {
9401
9557
  void dispatch(line, socket, options, token);
9402
9558
  });
9403
9559
  });
9404
- await new Promise((resolve29, reject) => {
9560
+ await new Promise((resolve30, reject) => {
9405
9561
  const onError = (error) => {
9406
9562
  server.removeListener("listening", onListening);
9407
9563
  reject(error);
9408
9564
  };
9409
9565
  const onListening = () => {
9410
9566
  server.removeListener("error", onError);
9411
- resolve29();
9567
+ resolve30();
9412
9568
  };
9413
9569
  server.once("error", onError);
9414
9570
  server.once("listening", onListening);
@@ -9479,11 +9635,11 @@ function write(socket, payload) {
9479
9635
  }
9480
9636
  }
9481
9637
  function closeServer(server) {
9482
- return new Promise((resolve29) => {
9638
+ return new Promise((resolve30) => {
9483
9639
  try {
9484
- server.close(() => resolve29());
9640
+ server.close(() => resolve30());
9485
9641
  } catch {
9486
- resolve29();
9642
+ resolve30();
9487
9643
  }
9488
9644
  });
9489
9645
  }
@@ -9491,19 +9647,19 @@ function closeServer(server) {
9491
9647
  // src/evolve/service.ts
9492
9648
  import { spawn as spawn5 } from "child_process";
9493
9649
  import { writeFile as writeFile8 } from "fs/promises";
9494
- import { join as join12 } from "path";
9650
+ import { join as join13 } from "path";
9495
9651
  import { z as z14 } from "zod";
9496
9652
 
9497
9653
  // src/evolve/loader.ts
9498
9654
  import { cp, mkdir as mkdir8, mkdtemp, readdir as readdir4, rm as rm2, stat as stat4, writeFile as writeFile6 } from "fs/promises";
9499
9655
  import { tmpdir as tmpdir3 } from "os";
9500
- import { join as join10, resolve as resolve10 } from "path";
9656
+ import { join as join11, resolve as resolve11 } from "path";
9501
9657
 
9502
9658
  // src/plugins/host.ts
9503
9659
  import { createHash as createHash8 } from "crypto";
9504
9660
  import { lstatSync, realpathSync as realpathSync3 } from "fs";
9505
9661
  import { lstat as lstat5, open as open5, readFile as readFile7, readdir as readdir3, realpath as realpath4, stat as stat3, writeFile as writeFile5 } from "fs/promises";
9506
- import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve9, sep as sep8 } from "path";
9662
+ import { isAbsolute as isAbsolute6, relative as relative8, resolve as resolve10, sep as sep8 } from "path";
9507
9663
  import { pathToFileURL } from "url";
9508
9664
  import { z as z13 } from "zod";
9509
9665
 
@@ -9821,7 +9977,7 @@ var PluginHost = class {
9821
9977
  const roots = [];
9822
9978
  for (const directory of directories) {
9823
9979
  try {
9824
- for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve9(directory, entry2.name));
9980
+ for (const entry2 of await readdir3(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve10(directory, entry2.name));
9825
9981
  } catch (error) {
9826
9982
  this.#diagnose(directory, error, directory);
9827
9983
  }
@@ -9854,7 +10010,7 @@ var PluginHost = class {
9854
10010
  const rootInfo = await lstat5(inputRoot);
9855
10011
  if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
9856
10012
  const root = await realpath4(inputRoot);
9857
- const rawManifest = await readManifest(resolve9(root, MANIFEST));
10013
+ const rawManifest = await readManifest(resolve10(root, MANIFEST));
9858
10014
  if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
9859
10015
  diagnosticLabel = rawManifest.name;
9860
10016
  }
@@ -10040,7 +10196,7 @@ var PluginHost = class {
10040
10196
  assertActive();
10041
10197
  const permission = `filesystem:${operation}`;
10042
10198
  if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
10043
- const path = resolve9(rawPath);
10199
+ const path = resolve10(rawPath);
10044
10200
  const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
10045
10201
  assertActive();
10046
10202
  if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
@@ -10171,14 +10327,14 @@ function assertSnapshot(expected, actual, label) {
10171
10327
  for (const key of ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"]) if (expected[key] !== actual[key]) throw new Error(`${label} changed during validation`);
10172
10328
  }
10173
10329
  function resolveContained(root, value, label) {
10174
- if (isAbsolute5(value)) throw new Error(`${label} must be relative to the plugin root`);
10175
- const candidate = resolve9(root, value);
10330
+ if (isAbsolute6(value)) throw new Error(`${label} must be relative to the plugin root`);
10331
+ const candidate = resolve10(root, value);
10176
10332
  assertContained(root, candidate, label);
10177
10333
  return candidate;
10178
10334
  }
10179
10335
  function assertContained(root, candidate, label) {
10180
10336
  const delta = relative8(root, candidate);
10181
- if (delta === ".." || delta.startsWith(`..${sep8}`) || isAbsolute5(delta)) throw new Error(`${label} escapes the plugin root`);
10337
+ if (delta === ".." || delta.startsWith(`..${sep8}`) || isAbsolute6(delta)) throw new Error(`${label} escapes the plugin root`);
10182
10338
  }
10183
10339
  function scopedLogger(logger, plugin) {
10184
10340
  const prefix = `[plugin:${plugin}] `;
@@ -10221,17 +10377,17 @@ function compare(left, right) {
10221
10377
  }
10222
10378
 
10223
10379
  // src/evolve/loader.ts
10224
- var PLUGIN_DIR = join10(".flavor", "plugins");
10225
- var VERSIONS_DIR = join10(".flavor", "plugins", ".versions");
10380
+ var PLUGIN_DIR = join11(".flavor", "plugins");
10381
+ var VERSIONS_DIR = join11(".flavor", "plugins", ".versions");
10226
10382
  function sanitizePluginName(tool) {
10227
10383
  const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
10228
10384
  return base.startsWith("fix-") ? base : `fix-${base}`;
10229
10385
  }
10230
10386
  function fixPluginDir(workspace, name) {
10231
- return resolve10(workspace, PLUGIN_DIR, name);
10387
+ return resolve11(workspace, PLUGIN_DIR, name);
10232
10388
  }
10233
10389
  function versionsDirFor(workspace, name) {
10234
- return resolve10(workspace, VERSIONS_DIR, name);
10390
+ return resolve11(workspace, VERSIONS_DIR, name);
10235
10391
  }
10236
10392
  function timestamp() {
10237
10393
  return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
@@ -10266,8 +10422,8 @@ export function activate(ctx) {
10266
10422
  async function scaffoldFixPlugin(workspace, name) {
10267
10423
  const dir = fixPluginDir(workspace, name);
10268
10424
  await mkdir8(dir, { recursive: true });
10269
- const manifestPath = join10(dir, "flavor-plugin.json");
10270
- const entryPath = join10(dir, "index.js");
10425
+ const manifestPath = join11(dir, "flavor-plugin.json");
10426
+ const entryPath = join11(dir, "index.js");
10271
10427
  try {
10272
10428
  await writeFile6(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
10273
10429
  await writeFile6(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
@@ -10287,9 +10443,9 @@ async function verifyFixPlugin(workspace, name) {
10287
10443
  return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
10288
10444
  }
10289
10445
  const collected = { tools: [], commands: [] };
10290
- const sandbox = await mkdtemp(join10(tmpdir3(), "flavor-evolve-verify-"));
10446
+ const sandbox = await mkdtemp(join11(tmpdir3(), "flavor-evolve-verify-"));
10291
10447
  try {
10292
- await cp(pluginRoot, join10(sandbox, name), { recursive: true });
10448
+ await cp(pluginRoot, join11(sandbox, name), { recursive: true });
10293
10449
  const host = new PluginHost({
10294
10450
  projectPluginDirs: [sandbox],
10295
10451
  sandbox: true,
@@ -10340,7 +10496,7 @@ async function verifyFixPlugin(workspace, name) {
10340
10496
  }
10341
10497
  async function snapshotFixPlugin(workspace, name) {
10342
10498
  const pluginRoot = fixPluginDir(workspace, name);
10343
- const snapshotRoot = join10(versionsDirFor(workspace, name), timestamp());
10499
+ const snapshotRoot = join11(versionsDirFor(workspace, name), timestamp());
10344
10500
  await mkdir8(snapshotRoot, { recursive: true });
10345
10501
  await cp(pluginRoot, snapshotRoot, { recursive: true });
10346
10502
  return snapshotRoot;
@@ -10361,7 +10517,7 @@ async function revertFixPlugin(workspace, name) {
10361
10517
  }
10362
10518
  const snapshots = entries.filter((entry2) => !entry2.startsWith(".")).sort();
10363
10519
  if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
10364
- const latest = join10(versionsRoot, snapshots[snapshots.length - 1]);
10520
+ const latest = join11(versionsRoot, snapshots[snapshots.length - 1]);
10365
10521
  await rm2(pluginRoot, { recursive: true, force: true });
10366
10522
  await mkdir8(pluginRoot, { recursive: true });
10367
10523
  await cp(latest, pluginRoot, { recursive: true });
@@ -10371,7 +10527,7 @@ async function revertFixPlugin(workspace, name) {
10371
10527
  // src/evolve/store.ts
10372
10528
  import { createHash as createHash9 } from "crypto";
10373
10529
  import { appendFile, mkdir as mkdir9, readFile as readFile8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
10374
- import { dirname as dirname11, join as join11 } from "path";
10530
+ import { dirname as dirname11, join as join12 } from "path";
10375
10531
  function normalizeError(message2) {
10376
10532
  return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
10377
10533
  }
@@ -10444,12 +10600,12 @@ var EvolveStore = class {
10444
10600
  #queue = Promise.resolve();
10445
10601
  constructor(options) {
10446
10602
  const { workspace, maxSignals = 400, maxRules = 20 } = options;
10447
- this.dir = join11(workspace, ".flavor", "evolve");
10448
- this.signalsFile = join11(this.dir, "signals.jsonl");
10449
- this.reflectionsFile = join11(this.dir, "reflections.jsonl");
10450
- this.doneFile = join11(this.dir, "done.json");
10451
- this.verifiedFile = join11(this.dir, "verified.json");
10452
- this.rulesFile = join11(this.dir, "rules.json");
10603
+ this.dir = join12(workspace, ".flavor", "evolve");
10604
+ this.signalsFile = join12(this.dir, "signals.jsonl");
10605
+ this.reflectionsFile = join12(this.dir, "reflections.jsonl");
10606
+ this.doneFile = join12(this.dir, "done.json");
10607
+ this.verifiedFile = join12(this.dir, "verified.json");
10608
+ this.rulesFile = join12(this.dir, "rules.json");
10453
10609
  this.maxSignals = maxSignals;
10454
10610
  this.maxRules = maxRules;
10455
10611
  }
@@ -10668,7 +10824,7 @@ var USAGE = [
10668
10824
  " clear reset signals, done markers, and verified markers"
10669
10825
  ].join("\n");
10670
10826
  function runCommand(command, cwd, timeoutMs) {
10671
- return new Promise((resolve29) => {
10827
+ return new Promise((resolve30) => {
10672
10828
  const isWindows = process.platform === "win32";
10673
10829
  const child = spawn5(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
10674
10830
  cwd,
@@ -10678,7 +10834,7 @@ function runCommand(command, cwd, timeoutMs) {
10678
10834
  let stderr = "";
10679
10835
  const timer = setTimeout(() => {
10680
10836
  child.kill();
10681
- resolve29({ ok: false, stdout, stderr: `${stderr}
10837
+ resolve30({ ok: false, stdout, stderr: `${stderr}
10682
10838
  [killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
10683
10839
  }, timeoutMs);
10684
10840
  child.stdout.on("data", (chunk) => {
@@ -10689,12 +10845,12 @@ function runCommand(command, cwd, timeoutMs) {
10689
10845
  });
10690
10846
  child.on("error", (error) => {
10691
10847
  clearTimeout(timer);
10692
- resolve29({ ok: false, stdout, stderr: `${stderr}
10848
+ resolve30({ ok: false, stdout, stderr: `${stderr}
10693
10849
  ${error.message}`, code: "spawn" });
10694
10850
  });
10695
10851
  child.on("close", (code) => {
10696
10852
  clearTimeout(timer);
10697
- resolve29({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10853
+ resolve30({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10698
10854
  });
10699
10855
  });
10700
10856
  }
@@ -10988,7 +11144,7 @@ function createEvolveService(options) {
10988
11144
  if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
10989
11145
  const name = sanitizePluginName(suggestion.tool);
10990
11146
  const dir = await scaffoldFixPlugin(workspace, name);
10991
- await writeFile8(join12(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
11147
+ await writeFile8(join13(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
10992
11148
  await snapshotFixPlugin(workspace, name);
10993
11149
  return [
10994
11150
  `suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
@@ -11079,7 +11235,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
11079
11235
  }
11080
11236
  const name = sanitizePluginName(suggestion.tool);
11081
11237
  const dir = await scaffoldFixPlugin(workspace, name);
11082
- await writeFile8(join12(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
11238
+ await writeFile8(join13(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
11083
11239
  await snapshotFixPlugin(workspace, name);
11084
11240
  return [
11085
11241
  `Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
@@ -11502,7 +11658,7 @@ ${evidence.summary}`;
11502
11658
  // src/goal/orchestrator.ts
11503
11659
  import { createHash as createHash11 } from "crypto";
11504
11660
  import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
11505
- import { join as join14 } from "path";
11661
+ import { join as join15 } from "path";
11506
11662
 
11507
11663
  // src/goal/types.ts
11508
11664
  import { z as z16 } from "zod";
@@ -11667,7 +11823,7 @@ function extractJson(text) {
11667
11823
  // src/goal/classifier.ts
11668
11824
  import { createHash as createHash10 } from "crypto";
11669
11825
  import { readFile as readFile9 } from "fs/promises";
11670
- import { join as join13 } from "path";
11826
+ import { join as join14 } from "path";
11671
11827
  function buildSkepticPrompt(evidence, plan, index) {
11672
11828
  const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
11673
11829
  return [
@@ -11844,7 +12000,7 @@ function computeFingerprint(gaps) {
11844
12000
  }
11845
12001
  async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
11846
12002
  const workspaceState = await collectWorkspaceState(workspace);
11847
- const planPath = join13(workspace, ".flavor", "goal-plan.md");
12003
+ const planPath = join14(workspace, ".flavor", "goal-plan.md");
11848
12004
  let planFile = null;
11849
12005
  try {
11850
12006
  planFile = await readFile9(planPath, "utf8");
@@ -12097,8 +12253,8 @@ function contractHash(objective, plan) {
12097
12253
  return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
12098
12254
  }
12099
12255
  async function writePlanFile(workspace, plan) {
12100
- const dir = join14(workspace, ".flavor");
12101
- const planPath = join14(dir, "goal-plan.md");
12256
+ const dir = join15(workspace, ".flavor");
12257
+ const planPath = join15(dir, "goal-plan.md");
12102
12258
  await mkdir10(dir, { recursive: true });
12103
12259
  const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
12104
12260
  const content = [
@@ -12176,15 +12332,15 @@ function formatPriorGaps(gaps) {
12176
12332
  import { constants as constants2 } from "fs";
12177
12333
  import { randomUUID as randomUUID11 } from "crypto";
12178
12334
  import { lstat as lstat6, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
12179
- import { dirname as dirname12, join as join15, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
12335
+ import { dirname as dirname12, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
12180
12336
  var DEFAULT_MAX_BYTES = 1024 * 1024;
12181
12337
  var GoalStore = class {
12182
12338
  #workspace;
12183
12339
  #root;
12184
12340
  #maxBytes;
12185
12341
  constructor(options) {
12186
- this.#workspace = resolve11(options.workspace);
12187
- this.#root = join15(this.#workspace, ".flavor", "goals");
12342
+ this.#workspace = resolve12(options.workspace);
12343
+ this.#root = join16(this.#workspace, ".flavor", "goals");
12188
12344
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
12189
12345
  }
12190
12346
  async save(input) {
@@ -12192,13 +12348,13 @@ var GoalStore = class {
12192
12348
  await assertNoSymlink(this.#workspace, dirname12(this.#root));
12193
12349
  await mkdir11(this.#root, { recursive: true, mode: 448 });
12194
12350
  await assertNoSymlink(this.#workspace, this.#root);
12195
- const target = join15(this.#root, `${state.id}.json`);
12351
+ const target = join16(this.#root, `${state.id}.json`);
12196
12352
  const body = `${JSON.stringify(state, null, 2)}
12197
12353
  `;
12198
12354
  if (Buffer.byteLength(body) > this.#maxBytes) {
12199
12355
  throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
12200
12356
  }
12201
- const temporary = join15(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
12357
+ const temporary = join16(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
12202
12358
  let handle;
12203
12359
  try {
12204
12360
  handle = await open6(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
@@ -12214,8 +12370,8 @@ var GoalStore = class {
12214
12370
  }
12215
12371
  };
12216
12372
  async function assertNoSymlink(root, target) {
12217
- let cursor = resolve11(target);
12218
- const stop = resolve11(root);
12373
+ let cursor = resolve12(target);
12374
+ const stop = resolve12(root);
12219
12375
  const value = relative9(stop, cursor);
12220
12376
  if (value === ".." || value.startsWith(`..${sep9}`) || value.includes(":")) {
12221
12377
  throw new Error("Goal path escapes workspace");
@@ -12239,7 +12395,7 @@ function isCode3(error, code) {
12239
12395
 
12240
12396
  // src/loop/isolation.ts
12241
12397
  import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile10 } from "fs/promises";
12242
- import { join as join16, resolve as resolve12 } from "path";
12398
+ import { join as join17, resolve as resolve13 } from "path";
12243
12399
  var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
12244
12400
  var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
12245
12401
  function inferGoalIntent(goal) {
@@ -12249,7 +12405,7 @@ function inferGoalIntent(goal) {
12249
12405
  }
12250
12406
  async function prepareLoopWorkspace(options) {
12251
12407
  options.signal?.throwIfAborted();
12252
- const root = resolve12(options.root);
12408
+ const root = resolve13(options.root);
12253
12409
  const intent = inferGoalIntent(options.goal);
12254
12410
  if (intent === "read_only") return { kind: "ready", workspace: { root, mode: "current" } };
12255
12411
  const inside = await git(root, ["rev-parse", "--is-inside-work-tree"]);
@@ -12262,8 +12418,8 @@ async function prepareLoopWorkspace(options) {
12262
12418
  return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
12263
12419
  }
12264
12420
  options.signal?.throwIfAborted();
12265
- const worktrees = join16(root, ".worktrees");
12266
- const path = join16(worktrees, `loop-${options.loopId}`);
12421
+ const worktrees = join17(root, ".worktrees");
12422
+ const path = join17(worktrees, `loop-${options.loopId}`);
12267
12423
  const branch = `loop/${options.loopId}`;
12268
12424
  try {
12269
12425
  await ensureWorktreeDirectoryIgnored(root);
@@ -12292,9 +12448,9 @@ async function ensureWorktreeDirectoryIgnored(root) {
12292
12448
  if (ignored2.code === 0) return;
12293
12449
  const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
12294
12450
  if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
12295
- const gitDirectory = resolve12(root, commonDirectory.stdout.trim());
12296
- const infoDirectory = join16(gitDirectory, "info");
12297
- const excludePath = join16(infoDirectory, "exclude");
12451
+ const gitDirectory = resolve13(root, commonDirectory.stdout.trim());
12452
+ const infoDirectory = join17(gitDirectory, "info");
12453
+ const excludePath = join17(infoDirectory, "exclude");
12298
12454
  await mkdir12(infoDirectory, { recursive: true });
12299
12455
  let existing = "";
12300
12456
  try {
@@ -12310,7 +12466,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
12310
12466
  // src/loop/store.ts
12311
12467
  import { constants as constants3 } from "fs";
12312
12468
  import { lstat as lstat7, mkdir as mkdir13, open as open7, readFile as readFile11, realpath as realpath5, rename as rename5, rm as rm5, stat as stat5 } from "fs/promises";
12313
- import { dirname as dirname13, join as join17, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
12469
+ import { dirname as dirname13, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
12314
12470
  import { randomUUID as randomUUID12 } from "crypto";
12315
12471
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
12316
12472
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
@@ -12320,8 +12476,8 @@ var LoopStore = class {
12320
12476
  #maxBytes;
12321
12477
  #appendTails = /* @__PURE__ */ new Map();
12322
12478
  constructor(options) {
12323
- this.#workspace = resolve13(options.workspace);
12324
- this.#root = join17(this.#workspace, ".flavor", "loops");
12479
+ this.#workspace = resolve14(options.workspace);
12480
+ this.#root = join18(this.#workspace, ".flavor", "loops");
12325
12481
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
12326
12482
  }
12327
12483
  async create(state) {
@@ -12338,11 +12494,11 @@ var LoopStore = class {
12338
12494
  const state = LoopStateSchema.parse(input);
12339
12495
  await this.#assertWorkspace(state.workspace);
12340
12496
  const directory = await this.#prepare(state.loopId);
12341
- const target = join17(directory, "state.json");
12497
+ const target = join18(directory, "state.json");
12342
12498
  const body = `${JSON.stringify(state, null, 2)}
12343
12499
  `;
12344
12500
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
12345
- const temporary = join17(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
12501
+ const temporary = join18(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
12346
12502
  let handle;
12347
12503
  try {
12348
12504
  handle = await open7(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
@@ -12358,7 +12514,7 @@ var LoopStore = class {
12358
12514
  }
12359
12515
  async load(loopId) {
12360
12516
  const directory = await this.#safeExisting(loopId);
12361
- const target = join17(directory, "state.json");
12517
+ const target = join18(directory, "state.json");
12362
12518
  try {
12363
12519
  const metadata = await stat5(target);
12364
12520
  if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
@@ -12378,7 +12534,7 @@ var LoopStore = class {
12378
12534
  const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
12379
12535
  const next = previous.catch(() => void 0).then(async () => {
12380
12536
  const directory = await this.#prepare(event.loopId);
12381
- const path = join17(directory, "events.jsonl");
12537
+ const path = join18(directory, "events.jsonl");
12382
12538
  const handle = await open7(path, constants3.O_CREAT | constants3.O_APPEND | constants3.O_WRONLY, 384);
12383
12539
  try {
12384
12540
  await handle.writeFile(`${JSON.stringify(event)}
@@ -12395,7 +12551,7 @@ var LoopStore = class {
12395
12551
  await assertNoSymlink2(this.#workspace, dirname13(this.#root));
12396
12552
  await mkdir13(this.#root, { recursive: true, mode: 448 });
12397
12553
  await assertNoSymlink2(this.#workspace, this.#root);
12398
- const directory = join17(this.#root, loopId);
12554
+ const directory = join18(this.#root, loopId);
12399
12555
  await mkdir13(directory, { recursive: true, mode: 448 });
12400
12556
  await assertNoSymlink2(this.#workspace, directory);
12401
12557
  return directory;
@@ -12403,7 +12559,7 @@ var LoopStore = class {
12403
12559
  async #safeExisting(loopId) {
12404
12560
  validateId(loopId);
12405
12561
  await assertNoSymlink2(this.#workspace, this.#root);
12406
- const directory = join17(this.#root, loopId);
12562
+ const directory = join18(this.#root, loopId);
12407
12563
  await assertNoSymlink2(this.#workspace, directory);
12408
12564
  return directory;
12409
12565
  }
@@ -12419,8 +12575,8 @@ function validateId(loopId) {
12419
12575
  if (!LOOP_ID.test(loopId)) throw new Error("Invalid loop id");
12420
12576
  }
12421
12577
  async function assertNoSymlink2(root, target) {
12422
- let cursor = resolve13(target);
12423
- const stop = resolve13(root);
12578
+ let cursor = resolve14(target);
12579
+ const stop = resolve14(root);
12424
12580
  if (!isWithin3(stop, cursor)) throw new Error("Loop path escapes workspace");
12425
12581
  const chain = [];
12426
12582
  while (cursor !== stop) {
@@ -12436,14 +12592,14 @@ async function assertNoSymlink2(root, target) {
12436
12592
  }
12437
12593
  }
12438
12594
  function isWithin3(root, path) {
12439
- const value = relative10(resolve13(root), resolve13(path));
12595
+ const value = relative10(resolve14(root), resolve14(path));
12440
12596
  return value === "" || !value.startsWith(`..${sep10}`) && value !== ".." && !value.includes(":");
12441
12597
  }
12442
12598
  async function canonical(path) {
12443
12599
  try {
12444
- return await realpath5(resolve13(path));
12600
+ return await realpath5(resolve14(path));
12445
12601
  } catch {
12446
- return resolve13(path);
12602
+ return resolve14(path);
12447
12603
  }
12448
12604
  }
12449
12605
  function isCode4(error, code) {
@@ -12452,11 +12608,11 @@ function isCode4(error, code) {
12452
12608
 
12453
12609
  // src/loop/verifier.ts
12454
12610
  import { readFile as readFile12 } from "fs/promises";
12455
- import { join as join18 } from "path";
12611
+ import { join as join19 } from "path";
12456
12612
 
12457
12613
  // src/tools/shell.ts
12458
12614
  import { spawn as spawn6, spawnSync } from "child_process";
12459
- import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
12615
+ import { isAbsolute as isAbsolute7, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
12460
12616
  import { z as z18 } from "zod";
12461
12617
 
12462
12618
  // src/tools/jobs.ts
@@ -12609,7 +12765,7 @@ function buildWindowsCommandLine(kind, commandLine, command, args) {
12609
12765
  return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
12610
12766
  }
12611
12767
  function createShellTool(workspace, options = {}) {
12612
- const root = resolve14(workspace);
12768
+ const root = resolve15(workspace);
12613
12769
  const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
12614
12770
  const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
12615
12771
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
@@ -12908,9 +13064,9 @@ function backgroundFlag(value) {
12908
13064
  return value === true || value === "true";
12909
13065
  }
12910
13066
  function workingDirectory(root, cwd = ".") {
12911
- const candidate = resolve14(root, cwd);
13067
+ const candidate = resolve15(root, cwd);
12912
13068
  const delta = relative11(root, candidate);
12913
- if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute6(delta)) throw new Error("Working directory is outside the workspace");
13069
+ if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute7(delta)) throw new Error("Working directory is outside the workspace");
12914
13070
  return candidate;
12915
13071
  }
12916
13072
  async function terminateTree(pid) {
@@ -13020,13 +13176,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
13020
13176
  async function inferVerificationPlan(workspace) {
13021
13177
  const commands = [];
13022
13178
  const seen = /* @__PURE__ */ new Set();
13023
- const packageJson = await optionalJson(join18(workspace, "package.json"));
13179
+ const packageJson = await optionalJson(join19(workspace, "package.json"));
13024
13180
  const scripts = isRecord2(packageJson?.scripts) ? packageJson.scripts : {};
13025
13181
  for (const name of SCRIPT_PRIORITY) {
13026
13182
  if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
13027
13183
  add(commands, seen, npmScript(name));
13028
13184
  }
13029
- const flavor = await optionalText(join18(workspace, "FLAVOR.md"));
13185
+ const flavor = await optionalText(join19(workspace, "FLAVOR.md"));
13030
13186
  if (flavor !== void 0) {
13031
13187
  for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
13032
13188
  const parsed = parseTrustedCommand(match[1] ?? "");
@@ -13993,7 +14149,7 @@ function isRecord3(value) {
13993
14149
  }
13994
14150
 
13995
14151
  // src/mcp/sdk.ts
13996
- import { resolve as resolve15 } from "path";
14152
+ import { resolve as resolve16 } from "path";
13997
14153
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
13998
14154
  import {
13999
14155
  getDefaultEnvironment,
@@ -14015,7 +14171,7 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
14015
14171
  command: config.command,
14016
14172
  args: [...config.args],
14017
14173
  env: { ...dependencies.defaultEnvironment(), ...config.env },
14018
- cwd: resolve15(input.workspace, config.cwd ?? "."),
14174
+ cwd: resolve16(input.workspace, config.cwd ?? "."),
14019
14175
  // MCP servers frequently log startup banners to stderr. Inheriting that
14020
14176
  // stream corrupts Ink's input area, so connection errors are surfaced
14021
14177
  // through McpManager diagnostics instead.
@@ -14220,8 +14376,8 @@ var OAuthCallbackAuthProvider = class {
14220
14376
  async #startCallbackServer(expectedState, signal) {
14221
14377
  let serverResolve;
14222
14378
  let serverReject;
14223
- const codePromise = new Promise((resolve29, reject) => {
14224
- serverResolve = resolve29;
14379
+ const codePromise = new Promise((resolve30, reject) => {
14380
+ serverResolve = resolve30;
14225
14381
  serverReject = reject;
14226
14382
  });
14227
14383
  const server = createServer3((req, res) => {
@@ -14255,7 +14411,7 @@ var OAuthCallbackAuthProvider = class {
14255
14411
  );
14256
14412
  serverResolve(code);
14257
14413
  });
14258
- return new Promise((resolve29, reject) => {
14414
+ return new Promise((resolve30, reject) => {
14259
14415
  server.once("error", (error) => {
14260
14416
  reject(new Error(`OAuth callback server failed: ${error.message}`));
14261
14417
  });
@@ -14272,7 +14428,7 @@ var OAuthCallbackAuthProvider = class {
14272
14428
  signal.addEventListener("abort", () => {
14273
14429
  serverReject(new Error("OAuth flow aborted"));
14274
14430
  }, { once: true });
14275
- resolve29({ redirectUri, promise: codePromise });
14431
+ resolve30({ redirectUri, promise: codePromise });
14276
14432
  });
14277
14433
  });
14278
14434
  }
@@ -14415,7 +14571,7 @@ function isStoredToken(value) {
14415
14571
 
14416
14572
  // src/permissions/policy.ts
14417
14573
  import { readFile as readFile15 } from "fs/promises";
14418
- import { join as join19, resolve as resolve16 } from "path";
14574
+ import { join as join20, resolve as resolve17 } from "path";
14419
14575
  import { z as z20 } from "zod";
14420
14576
  var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
14421
14577
  var PrefixSchema = z20.array(z20.string().min(1).max(1024)).min(1).max(64);
@@ -14451,10 +14607,10 @@ var CompiledPermissionPolicy = class {
14451
14607
  };
14452
14608
  async function loadPermissionPolicy(options) {
14453
14609
  const sources = [
14454
- ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve16(options.managedPath) }],
14455
- { tier: "user", path: join19(resolve16(options.home), ".flavor-code", "permissions.json") },
14456
- { tier: "project", path: join19(resolve16(options.workspace), ".flavor", "permissions.json") },
14457
- { tier: "local", path: join19(resolve16(options.workspace), ".flavor", "permissions.local.json") }
14610
+ ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve17(options.managedPath) }],
14611
+ { tier: "user", path: join20(resolve17(options.home), ".flavor-code", "permissions.json") },
14612
+ { tier: "project", path: join20(resolve17(options.workspace), ".flavor", "permissions.json") },
14613
+ { tier: "local", path: join20(resolve17(options.workspace), ".flavor", "permissions.local.json") }
14458
14614
  ];
14459
14615
  const rules = [];
14460
14616
  for (const source of sources) {
@@ -14676,7 +14832,7 @@ function data(value) {
14676
14832
  // src/skills/registry.ts
14677
14833
  import { constants as constants4 } from "fs";
14678
14834
  import { lstat as lstat8, open as defaultOpen, readdir as readdir5, realpath as realpath6 } from "fs/promises";
14679
- import { isAbsolute as isAbsolute7, relative as relative12, resolve as resolve17, sep as sep12 } from "path";
14835
+ import { isAbsolute as isAbsolute8, relative as relative12, resolve as resolve18, sep as sep12 } from "path";
14680
14836
  import { marked } from "marked";
14681
14837
  import { parseDocument } from "yaml";
14682
14838
  var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
@@ -14801,13 +14957,13 @@ var SkillRegistry = class {
14801
14957
  const record = await this.#recordFor(skill);
14802
14958
  const normalized = normalizeReference(reference);
14803
14959
  const parts = normalized.split("/");
14804
- if (isAbsolute7(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
14960
+ if (isAbsolute8(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
14805
14961
  throw new Error(`Resource reference escapes the skill root or is not a direct resource: ${reference}`);
14806
14962
  }
14807
14963
  const body = await this.loadBody(record.metadata);
14808
14964
  const references = extractResourceReferences(body);
14809
14965
  if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
14810
- const candidate = resolve17(record.metadata.root, ...parts);
14966
+ const candidate = resolve18(record.metadata.root, ...parts);
14811
14967
  const file = await openVerifiedFile(candidate, record.metadata.root, "Resource", this.#options.openFile);
14812
14968
  try {
14813
14969
  if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
@@ -14836,7 +14992,7 @@ var SkillRegistry = class {
14836
14992
  async #discoverSource(roots, source) {
14837
14993
  const found = [];
14838
14994
  for (const configuredRoot of roots) {
14839
- const root = resolve17(configuredRoot);
14995
+ const root = resolve18(configuredRoot);
14840
14996
  let entries;
14841
14997
  let physicalRoot = root;
14842
14998
  try {
@@ -14851,13 +15007,13 @@ var SkillRegistry = class {
14851
15007
  }
14852
15008
  entries.sort((left, right) => compareCodePoints(left.name, right.name));
14853
15009
  for (const entry2 of entries) {
14854
- const skillRoot = resolve17(root, entry2.name);
15010
+ const skillRoot = resolve18(root, entry2.name);
14855
15011
  try {
14856
15012
  if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
14857
15013
  if (!entry2.isDirectory()) continue;
14858
15014
  const physicalSkillRoot = await realpath6(skillRoot);
14859
15015
  if (!isWithin4(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
14860
- const skillFile = resolve17(skillRoot, "SKILL.md");
15016
+ const skillFile = resolve18(skillRoot, "SKILL.md");
14861
15017
  const parsed = await readFrontmatterFile(
14862
15018
  skillFile,
14863
15019
  physicalSkillRoot,
@@ -15059,7 +15215,7 @@ function decodeUtf82(content, label) {
15059
15215
  }
15060
15216
  function isWithin4(root, candidate) {
15061
15217
  const delta = relative12(root, candidate);
15062
- return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute7(delta);
15218
+ return delta === "" || delta !== ".." && !delta.startsWith(`..${sep12}`) && !isAbsolute8(delta);
15063
15219
  }
15064
15220
  function isMissing2(error) {
15065
15221
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
@@ -15223,7 +15379,7 @@ function createSkillResourceTool(registry) {
15223
15379
  // src/session/store.ts
15224
15380
  import { constants as constants5 } from "fs";
15225
15381
  import { lstat as lstat9, mkdir as mkdir14, open as open8, readFile as readFile16, readdir as readdir6, realpath as realpath7, rename as rename6, rm as rm6, stat as stat6 } from "fs/promises";
15226
- import { basename as basename3, dirname as dirname15, join as join20, relative as relative13, resolve as resolve18, sep as sep13 } from "path";
15382
+ import { basename as basename3, dirname as dirname15, join as join21, relative as relative13, resolve as resolve19, sep as sep13 } from "path";
15227
15383
  import { randomUUID as randomUUID13 } from "crypto";
15228
15384
  import { z as z22 } from "zod";
15229
15385
  var SESSION_VERSION = 4;
@@ -15343,14 +15499,15 @@ var SessionDocumentSchema = SessionBaseSchema.extend({
15343
15499
  }).strict(),
15344
15500
  timeline: TimelineSchema
15345
15501
  }).strict();
15502
+ var INVALID_SESSION_LOCK_STALE_MS = 3e4;
15346
15503
  var SessionStore = class {
15347
15504
  #workspace;
15348
15505
  #sessions;
15349
15506
  #maxBytes;
15350
15507
  #maxSessions;
15351
15508
  constructor(options) {
15352
- this.#workspace = resolve18(options.workspace);
15353
- this.#sessions = join20(this.#workspace, ".flavor", "sessions");
15509
+ this.#workspace = resolve19(options.workspace);
15510
+ this.#sessions = join21(this.#workspace, ".flavor", "sessions");
15354
15511
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
15355
15512
  if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
15356
15513
  this.#maxSessions = options.maxSessions ?? 50;
@@ -15382,7 +15539,7 @@ var SessionStore = class {
15382
15539
  const body = `${lines.join("\n")}
15383
15540
  `;
15384
15541
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
15385
- const temporary = join20(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
15542
+ const temporary = join21(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
15386
15543
  let handle;
15387
15544
  try {
15388
15545
  handle = await open8(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
@@ -15397,6 +15554,47 @@ var SessionStore = class {
15397
15554
  }
15398
15555
  await this.#prune().catch(() => void 0);
15399
15556
  }
15557
+ /** Hold this lease for the full lifetime of a writable session runtime. */
15558
+ async acquireLease(sessionId) {
15559
+ SessionIdSchema.parse(sessionId);
15560
+ await this.#prepareDirectory();
15561
+ const path = join21(this.#sessions, `.${sessionId}.lock`);
15562
+ const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
15563
+ for (let attempt = 0; attempt < 3; attempt += 1) {
15564
+ let handle;
15565
+ let created = false;
15566
+ try {
15567
+ handle = await open8(path, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
15568
+ created = true;
15569
+ await handle.writeFile(token, "utf8");
15570
+ await handle.sync();
15571
+ await handle.close();
15572
+ handle = void 0;
15573
+ let released = false;
15574
+ return {
15575
+ sessionId,
15576
+ release: async () => {
15577
+ if (released) return;
15578
+ try {
15579
+ if (await readFile16(path, "utf8") === token) await rm6(path, { force: true });
15580
+ } catch (error) {
15581
+ if (!isCode7(error, "ENOENT")) throw error;
15582
+ }
15583
+ released = true;
15584
+ }
15585
+ };
15586
+ } catch (error) {
15587
+ await handle?.close().catch(() => void 0);
15588
+ if (created) await rm6(path, { force: true }).catch(() => void 0);
15589
+ if (!isCode7(error, "EEXIST")) throw error;
15590
+ const owner2 = await reclaimStaleSessionLock(path);
15591
+ if (owner2 !== void 0) {
15592
+ throw new Error(`Session "${sessionId}" is already active in process ${owner2}. Close that session before resuming it here.`);
15593
+ }
15594
+ }
15595
+ }
15596
+ throw new Error(`Session "${sessionId}" could not acquire its writer lease because the lock kept changing`);
15597
+ }
15400
15598
  async load(sessionId) {
15401
15599
  if (sessionId === void 0) {
15402
15600
  const [latest] = await this.list();
@@ -15494,29 +15692,37 @@ var SessionStore = class {
15494
15692
  if (isCode7(error, "ENOENT")) return;
15495
15693
  throw error;
15496
15694
  }
15695
+ const owner2 = await reclaimStaleSessionLock(join21(this.#sessions, `.${sessionId}.lock`));
15696
+ if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
15497
15697
  const path = this.#path(sessionId);
15498
15698
  try {
15499
15699
  const metadata = await lstat9(path);
15500
15700
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
15501
15701
  await rm6(path);
15502
- await rm6(join20(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
15702
+ await rm6(join21(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
15503
15703
  } catch (error) {
15504
15704
  if (!isCode7(error, "ENOENT")) throw error;
15505
15705
  }
15506
15706
  }
15507
15707
  #path(sessionId) {
15508
15708
  SessionIdSchema.parse(sessionId);
15509
- const path = resolve18(this.#sessions, `${sessionId}.jsonl`);
15709
+ const path = resolve19(this.#sessions, `${sessionId}.jsonl`);
15510
15710
  if (!isWithin5(this.#sessions, path)) throw new Error("Invalid session id");
15511
15711
  return path;
15512
15712
  }
15513
15713
  async #prune() {
15514
15714
  const entries = await this.list();
15515
15715
  if (entries.length <= this.#maxSessions) return;
15516
- const excess = entries.slice(this.#maxSessions);
15517
- for (const entry2 of excess) {
15716
+ let retained = 0;
15717
+ for (const entry2 of entries) {
15718
+ const owner2 = await reclaimStaleSessionLock(join21(this.#sessions, `.${entry2.sessionId}.lock`));
15719
+ if (owner2 !== void 0) continue;
15720
+ if (retained < this.#maxSessions) {
15721
+ retained += 1;
15722
+ continue;
15723
+ }
15518
15724
  await rm6(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
15519
- await rm6(join20(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
15725
+ await rm6(join21(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
15520
15726
  }
15521
15727
  }
15522
15728
  async #assertWorkspace(stored) {
@@ -15562,6 +15768,47 @@ function storedSessionVersion(raw) {
15562
15768
  const candidate = first.__meta === true ? first.version : JSON.parse(trimmed).version;
15563
15769
  return typeof candidate === "number" && Number.isInteger(candidate) ? candidate : 0;
15564
15770
  }
15771
+ async function reclaimStaleSessionLock(path) {
15772
+ try {
15773
+ const token = await readFile16(path, "utf8");
15774
+ const metadata = await stat6(path);
15775
+ const owner2 = parseSessionLockOwner(token);
15776
+ if (owner2 !== void 0 && isProcessAlive(owner2)) return owner2;
15777
+ if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
15778
+ throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
15779
+ }
15780
+ if (await readFile16(path, "utf8") !== token) return void 0;
15781
+ const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
15782
+ try {
15783
+ await rename6(path, stale);
15784
+ await rm6(stale, { force: true });
15785
+ } catch (error) {
15786
+ if (!isCode7(error, "ENOENT")) throw error;
15787
+ }
15788
+ return void 0;
15789
+ } catch (error) {
15790
+ if (isCode7(error, "ENOENT")) return void 0;
15791
+ throw error;
15792
+ }
15793
+ }
15794
+ function parseSessionLockOwner(token) {
15795
+ try {
15796
+ const value = JSON.parse(token);
15797
+ if (typeof value !== "object" || value === null || !("pid" in value)) return void 0;
15798
+ const pid = value.pid;
15799
+ return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
15800
+ } catch {
15801
+ return void 0;
15802
+ }
15803
+ }
15804
+ function isProcessAlive(pid) {
15805
+ try {
15806
+ process.kill(pid, 0);
15807
+ return true;
15808
+ } catch (error) {
15809
+ return isCode7(error, "EPERM");
15810
+ }
15811
+ }
15565
15812
  async function boundedRead(path, maxBytes) {
15566
15813
  const metadata = await stat6(path);
15567
15814
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
@@ -15708,8 +15955,8 @@ function isRecord5(value) {
15708
15955
  return typeof value === "object" && value !== null && !Array.isArray(value);
15709
15956
  }
15710
15957
  async function assertNoSymlink3(root, target) {
15711
- let cursor = resolve18(target);
15712
- const stop = resolve18(root);
15958
+ let cursor = resolve19(target);
15959
+ const stop = resolve19(root);
15713
15960
  if (!isWithin5(stop, cursor)) throw new Error("Session path escapes the workspace");
15714
15961
  const chain = [];
15715
15962
  while (cursor !== stop) {
@@ -15725,14 +15972,14 @@ async function assertNoSymlink3(root, target) {
15725
15972
  }
15726
15973
  }
15727
15974
  function isWithin5(root, path) {
15728
- const value = relative13(resolve18(root), resolve18(path));
15975
+ const value = relative13(resolve19(root), resolve19(path));
15729
15976
  return value === "" || !value.startsWith(`..${sep13}`) && value !== ".." && !value.includes(":");
15730
15977
  }
15731
15978
  async function canonical2(path) {
15732
15979
  try {
15733
- return await realpath7(resolve18(path));
15980
+ return await realpath7(resolve19(path));
15734
15981
  } catch {
15735
- return resolve18(path);
15982
+ return resolve19(path);
15736
15983
  }
15737
15984
  }
15738
15985
  function isCode7(error, code) {
@@ -15760,7 +16007,7 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
15760
16007
  import { constants as constants6 } from "fs";
15761
16008
  import { randomUUID as randomUUID14 } from "crypto";
15762
16009
  import { lstat as lstat10, mkdir as mkdir15, open as open9, readdir as readdir7, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
15763
- import { isAbsolute as isAbsolute8, join as join21, relative as relative14, resolve as resolve19, sep as sep14 } from "path";
16010
+ import { isAbsolute as isAbsolute9, join as join22, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
15764
16011
  import { z as z23 } from "zod";
15765
16012
  var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
15766
16013
  var ReviewSchema = z23.object({
@@ -15814,8 +16061,8 @@ var ProjectSleepOrganizer = class {
15814
16061
  #sessions;
15815
16062
  #generate;
15816
16063
  constructor(options) {
15817
- this.#workspace = resolve19(options.workspace);
15818
- this.#sleepDirectory = join21(this.#workspace, ".flavor", "sleep");
16064
+ this.#workspace = resolve20(options.workspace);
16065
+ this.#sleepDirectory = join22(this.#workspace, ".flavor", "sleep");
15819
16066
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
15820
16067
  this.#generate = options.generate;
15821
16068
  }
@@ -15824,7 +16071,7 @@ var ProjectSleepOrganizer = class {
15824
16071
  signal.throwIfAborted();
15825
16072
  await this.#prepareDirectory();
15826
16073
  if (await this.#hasReport(date)) return { status: "exists", date };
15827
- const lockPath = join21(this.#sleepDirectory, `.${date}.lock`);
16074
+ const lockPath = join22(this.#sleepDirectory, `.${date}.lock`);
15828
16075
  let lock;
15829
16076
  try {
15830
16077
  lock = await open9(lockPath, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
@@ -15859,7 +16106,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
15859
16106
  }
15860
16107
  if (review === void 0) throw lastError;
15861
16108
  const filename2 = `${date}-${filenameSummary(review.title)}.md`;
15862
- const path = join21(this.#sleepDirectory, filename2);
16109
+ const path = join22(this.#sleepDirectory, filename2);
15863
16110
  const markdown = renderSleepReport(date, review, documents);
15864
16111
  await this.#atomicWrite(path, markdown);
15865
16112
  return { status: "written", date, path, sessionCount: documents.length };
@@ -15901,7 +16148,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
15901
16148
  });
15902
16149
  }
15903
16150
  async #prepareDirectory() {
15904
- const flavorDirectory = join21(this.#workspace, ".flavor");
16151
+ const flavorDirectory = join22(this.#workspace, ".flavor");
15905
16152
  await assertNotSymlink(flavorDirectory);
15906
16153
  await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
15907
16154
  await assertNotSymlink(this.#sleepDirectory);
@@ -15915,7 +16162,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
15915
16162
  return (await readdir7(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
15916
16163
  }
15917
16164
  async #atomicWrite(path, content) {
15918
- const temporary = join21(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
16165
+ const temporary = join22(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
15919
16166
  let handle;
15920
16167
  try {
15921
16168
  handle = await open9(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
@@ -16239,8 +16486,8 @@ async function assertNotSymlink(path) {
16239
16486
  function isWithin6(root, candidate) {
16240
16487
  const path = relative14(root, candidate);
16241
16488
  if (path === "") return true;
16242
- if (path === ".." || path.startsWith(`..${sep14}`) || isAbsolute8(path)) return false;
16243
- return process.platform !== "win32" || resolve19(root, path).toLowerCase().startsWith(`${resolve19(root).toLowerCase()}${sep14}`);
16489
+ if (path === ".." || path.startsWith(`..${sep14}`) || isAbsolute9(path)) return false;
16490
+ return process.platform !== "win32" || resolve20(root, path).toLowerCase().startsWith(`${resolve20(root).toLowerCase()}${sep14}`);
16244
16491
  }
16245
16492
  function hasCode(error, code) {
16246
16493
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
@@ -16290,7 +16537,7 @@ function computeSessionStats(documents) {
16290
16537
  // src/tools/files.ts
16291
16538
  import { constants as constants7 } from "fs";
16292
16539
  import { mkdir as mkdir16, open as open10, readFile as readFile17, realpath as realpath9, rename as rename8, rm as rm8, stat as stat7 } from "fs/promises";
16293
- import { dirname as dirname16, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve20, sep as sep15 } from "path";
16540
+ import { dirname as dirname16, isAbsolute as isAbsolute10, relative as relative15, resolve as resolve21, sep as sep15 } from "path";
16294
16541
  import { randomUUID as randomUUID15 } from "crypto";
16295
16542
  import { z as z24 } from "zod";
16296
16543
 
@@ -16607,9 +16854,9 @@ function createApplyPatchTool(workspace, options = {}) {
16607
16854
  };
16608
16855
  }
16609
16856
  function createPathGuard(workspace) {
16610
- const root = resolve20(workspace);
16857
+ const root = resolve21(workspace);
16611
16858
  const lexical = (input) => {
16612
- const candidate = resolve20(root, input);
16859
+ const candidate = resolve21(root, input);
16613
16860
  if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
16614
16861
  return candidate;
16615
16862
  };
@@ -16779,7 +17026,7 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
16779
17026
  }
16780
17027
  function within2(root, candidate) {
16781
17028
  const delta = relative15(root, candidate);
16782
- return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute9(delta);
17029
+ return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute10(delta);
16783
17030
  }
16784
17031
  function forceFlag(value) {
16785
17032
  return value === true || value === "true";
@@ -17310,9 +17557,9 @@ function charset(contentType) {
17310
17557
 
17311
17558
  // src/tools/lsp.ts
17312
17559
  import { spawn as spawn7 } from "child_process";
17313
- import { existsSync as existsSync3 } from "fs";
17560
+ import { existsSync as existsSync4 } from "fs";
17314
17561
  import { access, constants as constants8, readFile as readFile18 } from "fs/promises";
17315
- import { basename as basename4, dirname as dirname17, extname, isAbsolute as isAbsolute10, relative as relative16, resolve as resolve21, sep as sep16 } from "path";
17562
+ import { basename as basename4, dirname as dirname17, extname as extname2, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve22, sep as sep16 } from "path";
17316
17563
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
17317
17564
  import { z as z26 } from "zod";
17318
17565
  var LSP_REQUEST_TIMEOUT_MS = 15e3;
@@ -17326,10 +17573,12 @@ var LspConnection = class {
17326
17573
  #disposed = false;
17327
17574
  #initError;
17328
17575
  constructor(command, args, cwd) {
17329
- this.#process = spawn7(command, args, {
17576
+ const invocation = prepareSpawnInvocation(command, args, { cwd });
17577
+ this.#process = spawn7(invocation.command, invocation.args, {
17330
17578
  cwd,
17331
17579
  stdio: ["pipe", "pipe", "pipe"],
17332
- windowsHide: true
17580
+ windowsHide: true,
17581
+ ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
17333
17582
  });
17334
17583
  this.#process.on("error", (err) => {
17335
17584
  this.#initError = new Error(`LSP server "${command}" failed to start: ${err.message}`);
@@ -17411,8 +17660,8 @@ var LspConnection = class {
17411
17660
  const id = this.#nextId++;
17412
17661
  const request = { jsonrpc: "2.0", id, method, params };
17413
17662
  const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
17414
- const promise = new Promise((resolve29, reject) => {
17415
- this.#pending.set(id, { resolve: resolve29, reject });
17663
+ const promise = new Promise((resolve30, reject) => {
17664
+ this.#pending.set(id, { resolve: resolve30, reject });
17416
17665
  timeout.addEventListener("abort", () => {
17417
17666
  if (this.#pending.has(id)) {
17418
17667
  this.#pending.delete(id);
@@ -17528,7 +17777,7 @@ var LspBuffer = class {
17528
17777
  }
17529
17778
  }
17530
17779
  };
17531
- var TYPESCRIPT_CLI = resolve21(
17780
+ var TYPESCRIPT_CLI = resolve22(
17532
17781
  dirname17(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
17533
17782
  "lib",
17534
17783
  "tsc.js"
@@ -17570,7 +17819,7 @@ var RealLspManager = class {
17570
17819
  #serverConfigs;
17571
17820
  #onStatus;
17572
17821
  constructor(options) {
17573
- this.#workspace = resolve21(options.workspace);
17822
+ this.#workspace = resolve22(options.workspace);
17574
17823
  this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
17575
17824
  this.#onStatus = options.onStatus;
17576
17825
  }
@@ -17619,7 +17868,7 @@ var RealLspManager = class {
17619
17868
  if (pending !== void 0) return pending;
17620
17869
  const config = this.#serverConfigs.find((c) => c.language === language);
17621
17870
  if (config === void 0) return void 0;
17622
- const hasRoot = config.rootFiles.some((file) => existsSync3(resolve21(this.#workspace, file)));
17871
+ const hasRoot = config.rootFiles.some((file) => existsSync4(resolve22(this.#workspace, file)));
17623
17872
  if (!hasRoot) return void 0;
17624
17873
  const startPromise = this.#startServer(config);
17625
17874
  this.#pendingStarts.set(language, startPromise);
@@ -17662,10 +17911,10 @@ var LspDiagnosticsInput = z26.object({
17662
17911
  file: z26.string().min(1)
17663
17912
  });
17664
17913
  function createLspTools(workspace, options = {}) {
17665
- const root = resolve21(workspace);
17914
+ const root = resolve22(workspace);
17666
17915
  const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
17667
17916
  const guard = (input) => {
17668
- const candidate = resolve21(root, input);
17917
+ const candidate = resolve22(root, input);
17669
17918
  if (!isWithin7(root, candidate)) throw new Error("Path is outside the workspace");
17670
17919
  return candidate;
17671
17920
  };
@@ -17729,7 +17978,7 @@ function createLspTools(workspace, options = {}) {
17729
17978
  return [findRefs, hover, diagnostics];
17730
17979
  }
17731
17980
  function languageIdForPath(filePath) {
17732
- switch (extname(filePath).toLowerCase()) {
17981
+ switch (extname2(filePath).toLowerCase()) {
17733
17982
  case ".tsx":
17734
17983
  return "typescriptreact";
17735
17984
  case ".mts":
@@ -17742,7 +17991,7 @@ function languageIdForPath(filePath) {
17742
17991
  }
17743
17992
  function isWithin7(root, candidate) {
17744
17993
  const delta = relative16(root, candidate);
17745
- return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute10(delta);
17994
+ return delta === "" || !delta.startsWith(`..${sep16}`) && delta !== ".." && !isAbsolute11(delta);
17746
17995
  }
17747
17996
  function uriToPath(uri) {
17748
17997
  try {
@@ -18130,12 +18379,12 @@ ${explanation}` : "";
18130
18379
  // src/desktop/astgraph-service.ts
18131
18380
  import { stat as stat8 } from "fs/promises";
18132
18381
  import { createRequire as nodeCreateRequire } from "module";
18133
- import { join as join22, resolve as resolve22 } from "path";
18382
+ import { join as join23, resolve as resolve23 } from "path";
18134
18383
  var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
18135
18384
  var DesktopAstGraphService = class {
18136
18385
  #path;
18137
18386
  constructor(workspace) {
18138
- this.#path = join22(resolve22(workspace), ".flavor", "astgraph", "index.db");
18387
+ this.#path = join23(resolve23(workspace), ".flavor", "astgraph", "index.db");
18139
18388
  }
18140
18389
  async status() {
18141
18390
  if ((await stat8(this.#path).catch(() => void 0))?.isFile() !== true) {
@@ -18297,7 +18546,7 @@ var QuestionBridge = class {
18297
18546
  }
18298
18547
  }
18299
18548
  #askLocally(questions, signal) {
18300
- return new Promise((resolve29, reject) => {
18549
+ return new Promise((resolve30, reject) => {
18301
18550
  const onAbort = () => {
18302
18551
  this.#pending = void 0;
18303
18552
  this.#active = false;
@@ -18311,7 +18560,7 @@ var QuestionBridge = class {
18311
18560
  this.#pending = void 0;
18312
18561
  this.#active = false;
18313
18562
  this.#onChange?.();
18314
- resolve29(answers);
18563
+ resolve30(answers);
18315
18564
  },
18316
18565
  reject: (error) => {
18317
18566
  signal.removeEventListener("abort", onAbort);
@@ -18404,7 +18653,7 @@ function createTodoWriteTool() {
18404
18653
  // src/tools/managed.ts
18405
18654
  import { randomUUID as randomUUID16 } from "crypto";
18406
18655
  import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile19, readdir as readdir8, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
18407
- import { dirname as dirname18, join as join23, resolve as resolve23 } from "path";
18656
+ import { dirname as dirname18, join as join24, resolve as resolve24 } from "path";
18408
18657
  import { z as z31 } from "zod";
18409
18658
  var MANAGED_TOOL_VERSION = 1;
18410
18659
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -18446,9 +18695,9 @@ var ManagedToolStore = class {
18446
18695
  #entries = /* @__PURE__ */ new Map();
18447
18696
  #diagnostics = [];
18448
18697
  constructor(options) {
18449
- this.#workspace = resolve23(options.workspace);
18450
- this.#projectRoot = join23(this.#workspace, ".flavor", "tools");
18451
- this.#globalRoot = join23(resolve23(options.home), ".flavor-code", "tools");
18698
+ this.#workspace = resolve24(options.workspace);
18699
+ this.#projectRoot = join24(this.#workspace, ".flavor", "tools");
18700
+ this.#globalRoot = join24(resolve24(options.home), ".flavor-code", "tools");
18452
18701
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
18453
18702
  }
18454
18703
  get diagnostics() {
@@ -18495,7 +18744,7 @@ var ManagedToolStore = class {
18495
18744
  version: MANAGED_TOOL_VERSION,
18496
18745
  createdAt: this.#now()
18497
18746
  });
18498
- const path = join23(this.directory(input.scope), filename(input.name));
18747
+ const path = join24(this.directory(input.scope), filename(input.name));
18499
18748
  const definition = compileDefinition(record, input.scope, this.#workspace);
18500
18749
  await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
18501
18750
  `);
@@ -18520,7 +18769,7 @@ var ManagedToolStore = class {
18520
18769
  if (!parsed.success) return [];
18521
18770
  const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
18522
18771
  if (matches.length > 0) return matches.map((entry2) => entry2.path);
18523
- return [join23(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
18772
+ return [join24(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
18524
18773
  }
18525
18774
  async #loadDirectory(scope2) {
18526
18775
  const root = this.directory(scope2);
@@ -18533,7 +18782,7 @@ var ManagedToolStore = class {
18533
18782
  return;
18534
18783
  }
18535
18784
  for (const name of names) {
18536
- const path = join23(root, name);
18785
+ const path = join24(root, name);
18537
18786
  try {
18538
18787
  const info = await lstat11(path);
18539
18788
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
@@ -19452,7 +19701,7 @@ ${total}`;
19452
19701
  // src/memory/store.ts
19453
19702
  import { createHash as createHash16 } from "crypto";
19454
19703
  import { readFile as readFile20, rm as rm9 } from "fs/promises";
19455
- import { resolve as resolve24, join as join24, relative as relative17, sep as sep17 } from "path";
19704
+ import { resolve as resolve25, join as join25, relative as relative17, sep as sep17 } from "path";
19456
19705
 
19457
19706
  // src/memory/similarity.ts
19458
19707
  function normalizeForSimilarity(value) {
@@ -19554,10 +19803,10 @@ var MemoryStore = class {
19554
19803
  constructor(options) {
19555
19804
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
19556
19805
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
19557
- this.workspace = resolve24(options.workspace);
19558
- this.#memoryRoot = join24(this.workspace, ".flavor", "memory");
19559
- this.path = join24(this.#memoryRoot, "MEMORY.md");
19560
- this.#behaviorPath = join24(this.#memoryRoot, "behavior.json");
19806
+ this.workspace = resolve25(options.workspace);
19807
+ this.#memoryRoot = join25(this.workspace, ".flavor", "memory");
19808
+ this.path = join25(this.#memoryRoot, "MEMORY.md");
19809
+ this.#behaviorPath = join25(this.#memoryRoot, "behavior.json");
19561
19810
  this.#maxEntries = options.maxEntries;
19562
19811
  this.#maxEntryChars = options.maxEntryChars;
19563
19812
  }
@@ -19780,7 +20029,7 @@ var MemoryStore = class {
19780
20029
  }
19781
20030
  #resolveContentPath(contentPath) {
19782
20031
  if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
19783
- const path = resolve24(this.#memoryRoot, ...contentPath.split("/"));
20032
+ const path = resolve25(this.#memoryRoot, ...contentPath.split("/"));
19784
20033
  const rel = relative17(this.#memoryRoot, path);
19785
20034
  if (rel === ".." || rel.startsWith(`..${sep17}`)) throw new Error("Memory content path escapes the memory directory");
19786
20035
  return path;
@@ -20252,7 +20501,7 @@ function createExecutionEnvironment(workspace, config) {
20252
20501
 
20253
20502
  // src/ide/client.ts
20254
20503
  import { readFile as readFile21, readdir as readdir9, stat as stat9 } from "fs/promises";
20255
- import { basename as basename5, join as join25, relative as relative18, resolve as resolve25, sep as sep18 } from "path";
20504
+ import { basename as basename5, join as join26, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
20256
20505
  var FlavorIdeClient = class {
20257
20506
  #workspace;
20258
20507
  #home;
@@ -20263,8 +20512,8 @@ var FlavorIdeClient = class {
20263
20512
  #sessionId;
20264
20513
  #eventTail = Promise.resolve();
20265
20514
  constructor(options) {
20266
- this.#workspace = resolve25(options.workspace);
20267
- this.#home = resolve25(options.home);
20515
+ this.#workspace = resolve26(options.workspace);
20516
+ this.#home = resolve26(options.home);
20268
20517
  this.#environment = options.environment ?? process.env;
20269
20518
  this.#fetch = options.fetch ?? fetch;
20270
20519
  }
@@ -20348,7 +20597,7 @@ var FlavorIdeClient = class {
20348
20597
  };
20349
20598
  if (await this.#healthy(configured)) return configured;
20350
20599
  }
20351
- const directory = join25(this.#home, ".flavor-code", "ide");
20600
+ const directory = join26(this.#home, ".flavor-code", "ide");
20352
20601
  let entries;
20353
20602
  try {
20354
20603
  entries = (await readdir9(directory)).filter((name) => name.endsWith(".lock"));
@@ -20356,7 +20605,7 @@ var FlavorIdeClient = class {
20356
20605
  return void 0;
20357
20606
  }
20358
20607
  const candidates = (await Promise.all(entries.map(async (name) => {
20359
- const path = join25(directory, name);
20608
+ const path = join26(directory, name);
20360
20609
  try {
20361
20610
  const [contents, info] = await Promise.all([readFile21(path, "utf8"), stat9(path)]);
20362
20611
  const lock = parseLockfile(contents);
@@ -20462,8 +20711,8 @@ function parsePort(value) {
20462
20711
  return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
20463
20712
  }
20464
20713
  function isWithin8(parent, child) {
20465
- const base = normalize(resolve25(parent));
20466
- const target = normalize(resolve25(child));
20714
+ const base = normalize(resolve26(parent));
20715
+ const target = normalize(resolve26(child));
20467
20716
  return target === base || target.startsWith(`${base}${sep18}`);
20468
20717
  }
20469
20718
  function normalize(path) {
@@ -20493,8 +20742,8 @@ var JobRegistry = class {
20493
20742
  const id = `job-${randomUUID17().slice(0, 12)}`;
20494
20743
  const now = (/* @__PURE__ */ new Date()).toISOString();
20495
20744
  let settle;
20496
- const done = new Promise((resolve29) => {
20497
- settle = resolve29;
20745
+ const done = new Promise((resolve30) => {
20746
+ settle = resolve30;
20498
20747
  });
20499
20748
  const record = {
20500
20749
  id,
@@ -20618,7 +20867,7 @@ function snapshot2(job) {
20618
20867
  // src/terminal/service.ts
20619
20868
  import { randomUUID as randomUUID18 } from "crypto";
20620
20869
  import { createRequire as nodeCreateRequire2 } from "module";
20621
- import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve26, sep as sep19 } from "path";
20870
+ import { isAbsolute as isAbsolute12, relative as relative19, resolve as resolve27, sep as sep19 } from "path";
20622
20871
  var TerminalService = class {
20623
20872
  #root;
20624
20873
  #factory;
@@ -20626,7 +20875,7 @@ var TerminalService = class {
20626
20875
  #maxOutputChars;
20627
20876
  #sessions = /* @__PURE__ */ new Map();
20628
20877
  constructor(workspace, options = {}) {
20629
- this.#root = resolve26(workspace);
20878
+ this.#root = resolve27(workspace);
20630
20879
  this.#factory = options.factory ?? defaultFactory;
20631
20880
  this.#jobs = options.jobs;
20632
20881
  this.#maxOutputChars = options.maxOutputChars ?? 2e5;
@@ -20744,15 +20993,15 @@ function cleanEnvironment(environment) {
20744
20993
  return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
20745
20994
  }
20746
20995
  function workspacePath(root, input) {
20747
- const path = resolve26(root, input);
20996
+ const path = resolve27(root, input);
20748
20997
  const delta = relative19(root, path);
20749
- if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute11(delta)) throw new Error("Terminal cwd is outside the workspace");
20998
+ if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute12(delta)) throw new Error("Terminal cwd is outside the workspace");
20750
20999
  return path;
20751
21000
  }
20752
21001
 
20753
21002
  // src/tools/terminal.ts
20754
21003
  import { z as z33 } from "zod";
20755
- import { resolve as resolve27 } from "path";
21004
+ import { resolve as resolve28 } from "path";
20756
21005
  var Id = z33.string().regex(/^term-[a-f0-9-]+$/);
20757
21006
  var OpenInput = z33.object({ cwd: z33.string().optional(), shell: z33.string().optional(), args: z33.array(z33.string()).optional(), columns: z33.coerce.number().int().min(10).max(500).optional(), rows: z33.coerce.number().int().min(2).max(300).optional() });
20758
21007
  var OptionalBoolean = z33.union([z33.boolean(), z33.string().refine((value) => value === "true" || value === "false")]);
@@ -20766,8 +21015,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
20766
21015
  name: "TerminalOpen",
20767
21016
  description: "Open a persistent interactive pseudo-terminal in the workspace",
20768
21017
  inputSchema: OpenInput,
20769
- paths: (input) => [resolve27(workspace, input.cwd ?? ".")],
20770
- permissions: (input) => ({ paths: [resolve27(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve27(workspace, input.cwd ?? ".") }),
21018
+ paths: (input) => [resolve28(workspace, input.cwd ?? ".")],
21019
+ permissions: (input) => ({ paths: [resolve28(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve28(workspace, input.cwd ?? ".") }),
20771
21020
  execute: async (input, _signal, context) => terminals.open({
20772
21021
  owner: owner(context),
20773
21022
  ...input.cwd === void 0 ? {} : { cwd: input.cwd },
@@ -20993,8 +21242,8 @@ var PalClient = class {
20993
21242
  let disconnectTimer;
20994
21243
  await Promise.race([
20995
21244
  this.request({ version: 1, type: "disconnect", requestId: randomUUID19() }).catch(() => void 0),
20996
- new Promise((resolve29) => {
20997
- disconnectTimer = setTimeout(resolve29, Math.min(this.requestTimeoutMs, 250));
21245
+ new Promise((resolve30) => {
21246
+ disconnectTimer = setTimeout(resolve30, Math.min(this.requestTimeoutMs, 250));
20998
21247
  })
20999
21248
  ]);
21000
21249
  if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
@@ -21040,7 +21289,7 @@ var PalClient = class {
21040
21289
  return presence;
21041
21290
  }
21042
21291
  openSocket() {
21043
- return new Promise((resolve29, reject) => {
21292
+ return new Promise((resolve30, reject) => {
21044
21293
  let settled = false;
21045
21294
  const rejectConnection = (error) => {
21046
21295
  if (settled) return;
@@ -21063,7 +21312,7 @@ var PalClient = class {
21063
21312
  settled = true;
21064
21313
  if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
21065
21314
  socket.off("error", onInitialError);
21066
- resolve29();
21315
+ resolve30();
21067
21316
  };
21068
21317
  const onInitialError = (error) => {
21069
21318
  socket.off("connect", onConnect);
@@ -21094,12 +21343,12 @@ var PalClient = class {
21094
21343
  if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
21095
21344
  return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
21096
21345
  }
21097
- return new Promise((resolve29, reject) => {
21346
+ return new Promise((resolve30, reject) => {
21098
21347
  const timer = setTimeout(() => {
21099
21348
  this.pending.delete(request.requestId);
21100
21349
  reject(new Error(`Pal request '${request.type}' timed out`));
21101
21350
  }, this.requestTimeoutMs);
21102
- this.pending.set(request.requestId, { resolve: resolve29, reject, timer });
21351
+ this.pending.set(request.requestId, { resolve: resolve30, reject, timer });
21103
21352
  let frame;
21104
21353
  try {
21105
21354
  frame = encodeControlFrame(BrokerRequestSchema.parse(request));
@@ -21503,8 +21752,8 @@ var ApprovalBridge = class {
21503
21752
  }
21504
21753
  };
21505
21754
  async function createProductionRuntime(options) {
21506
- const workspace = resolve28(options.workspace ?? process.cwd());
21507
- const home = resolve28(options.home ?? homedir2());
21755
+ const workspace = resolve29(options.workspace ?? process.cwd());
21756
+ const home = resolve29(options.home ?? homedir2());
21508
21757
  const environment = options.environment ?? process.env;
21509
21758
  const ide = new FlavorIdeClient({ workspace, home, environment });
21510
21759
  const ideReady = ide.initialize();
@@ -21747,8 +21996,8 @@ async function createProductionRuntime(options) {
21747
21996
  const registeredProviders = registration.registered;
21748
21997
  let effectiveLlm = registration.effectiveLlm;
21749
21998
  const pluginHost = new PluginHost({
21750
- globalPluginDirs: [join26(home, ".flavor-code", "plugins")],
21751
- projectPluginDirs: [join26(workspace, ".flavor", "plugins")],
21999
+ globalPluginDirs: [join27(home, ".flavor-code", "plugins")],
22000
+ projectPluginDirs: [join27(workspace, ".flavor", "plugins")],
21752
22001
  config,
21753
22002
  // Existing project plugins (including astgraph and superharness) use Node.js
21754
22003
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -21823,6 +22072,7 @@ async function createProductionRuntime(options) {
21823
22072
  let collaborationEventPump;
21824
22073
  let collaborationEventsOpen = true;
21825
22074
  let islandControlServer;
22075
+ let sessionLease;
21826
22076
  const pumpCollaborationEvents = () => {
21827
22077
  if (collaborationEventPump !== void 0 || collaborationSession === void 0 || deliverCollaborationEvent === void 0) return;
21828
22078
  collaborationEventPump = (async () => {
@@ -21856,7 +22106,7 @@ async function createProductionRuntime(options) {
21856
22106
  const address = palSocketAddress({
21857
22107
  platform: process.platform,
21858
22108
  userScope: home,
21859
- runtimeDir: join26(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
22109
+ runtimeDir: join27(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
21860
22110
  });
21861
22111
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
21862
22112
  instanceId: collaboration.instanceId,
@@ -21967,8 +22217,8 @@ async function createProductionRuntime(options) {
21967
22217
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
21968
22218
  });
21969
22219
  const skills = new SkillRegistry({
21970
- globalRoots: [join26(home, ".flavor-code", "skills")],
21971
- projectRoots: [join26(workspace, ".flavor", "skills"), ...pluginSkillRoots],
22220
+ globalRoots: [join27(home, ".flavor-code", "skills")],
22221
+ projectRoots: [join27(workspace, ".flavor", "skills"), ...pluginSkillRoots],
21972
22222
  authorizeResource: async () => true,
21973
22223
  disabledNames: config.skills.disabled
21974
22224
  });
@@ -21976,7 +22226,7 @@ async function createProductionRuntime(options) {
21976
22226
  skillsReady.catch(() => void 0);
21977
22227
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
21978
22228
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
21979
- const flavor = await optionalText2(join26(workspace, "FLAVOR.md"));
22229
+ const flavor = await optionalText2(join27(workspace, "FLAVOR.md"));
21980
22230
  const instructionBaseline = await workspaceInstructions.baseline();
21981
22231
  let memoryContext;
21982
22232
  if (memoryStore !== void 0) {
@@ -22001,6 +22251,7 @@ async function createProductionRuntime(options) {
22001
22251
  const subagentStartedAt = {};
22002
22252
  const subagentElapsedMs = {};
22003
22253
  let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`;
22254
+ sessionLease = await sessionStore.acquireLease(sessionId);
22004
22255
  hookSessionId = sessionId;
22005
22256
  let harnessJournal = new HarnessJournal({ workspace, sessionId });
22006
22257
  const harnessRecovery = harnessJournal.recover();
@@ -22318,7 +22569,7 @@ async function createProductionRuntime(options) {
22318
22569
  maxEntryChars: config.memory.maxEntryChars,
22319
22570
  scoreThreshold: config.memory.scoreThreshold,
22320
22571
  autoStoreThreshold: config.memory.autoStoreThreshold,
22321
- maxCandidates: Math.min(config.memory.maxCandidatesPerTask, 1),
22572
+ maxCandidates: config.memory.maxCandidatesPerTask,
22322
22573
  ...config.language === void 0 ? {} : { language: config.language },
22323
22574
  generate: (prompt, signal) => generateMemoryExtraction(registry, childModel, prompt, signal)
22324
22575
  }) : void 0;
@@ -22557,7 +22808,7 @@ async function createProductionRuntime(options) {
22557
22808
  evolveService.toolDefinition(),
22558
22809
  ...mcpTools
22559
22810
  ];
22560
- const loopFlavor = await optionalText2(join26(input.workspace, "FLAVOR.md"));
22811
+ const loopFlavor = await optionalText2(join27(input.workspace, "FLAVOR.md"));
22561
22812
  const loopInstructionBaseline = await loopInstructions.baseline();
22562
22813
  const loopEnvironment = createPromptEnvironment({
22563
22814
  now: /* @__PURE__ */ new Date(),
@@ -22916,8 +23167,8 @@ async function createProductionRuntime(options) {
22916
23167
  explain: (query, focus, signal, onText) => runExplain({
22917
23168
  graph: new DesktopAstGraphService(workspace),
22918
23169
  readFile: async (path) => {
22919
- const root = resolve28(workspace);
22920
- const abs = resolve28(root, path);
23170
+ const root = resolve29(workspace);
23171
+ const abs = resolve29(root, path);
22921
23172
  if (abs !== root && !abs.startsWith(root + sep20)) throw new Error(`path outside workspace: ${path}`);
22922
23173
  return readFile22(abs, "utf8");
22923
23174
  },
@@ -22998,7 +23249,12 @@ ${summary2}`;
22998
23249
  for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
22999
23250
  for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
23000
23251
  const previousIdeSessionId = ideSessionId;
23001
- sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`;
23252
+ const nextSessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`;
23253
+ const nextLease = await sessionStore.acquireLease(nextSessionId);
23254
+ const previousLease = sessionLease;
23255
+ sessionLease = nextLease;
23256
+ sessionId = nextSessionId;
23257
+ await previousLease?.release();
23002
23258
  hookSessionId = sessionId;
23003
23259
  harnessJournal = new HarnessJournal({ workspace, sessionId });
23004
23260
  setUsageSession(sessionId);
@@ -23118,7 +23374,7 @@ ${renderMemoryDocument(entries)}`;
23118
23374
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
23119
23375
  }
23120
23376
  try {
23121
- const tokenStore = createFileTokenStore(join26(home, ".flavor-code", "auth.json"));
23377
+ const tokenStore = createFileTokenStore(join27(home, ".flavor-code", "auth.json"));
23122
23378
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
23123
23379
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
23124
23380
  const oauth = new OAuthCallbackAuthProvider({
@@ -23165,7 +23421,7 @@ ${renderMemoryDocument(entries)}`;
23165
23421
  }
23166
23422
  },
23167
23423
  async logout() {
23168
- const tokenStore = createFileTokenStore(join26(home, ".flavor-code", "auth.json"));
23424
+ const tokenStore = createFileTokenStore(join27(home, ".flavor-code", "auth.json"));
23169
23425
  const tokens = await tokenStore.load();
23170
23426
  const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
23171
23427
  if (!hadCredentials) {
@@ -23245,24 +23501,13 @@ ${renderMemoryDocument(entries)}`;
23245
23501
  setPermissionProfile: (profile) => harness.setPermissionProfile(profile)
23246
23502
  };
23247
23503
  let disposed = false;
23248
- return {
23249
- session,
23250
- services,
23251
- authorization,
23252
- approvals,
23253
- memoryReviews,
23254
- restoredTranscript,
23255
- jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
23256
- get sessionId() {
23257
- return sessionId;
23258
- },
23259
- get diagnostics() {
23260
- return diagnostics.map((item) => redactSecrets(item, secrets));
23261
- },
23262
- async dispose() {
23263
- if (disposed) return;
23264
- disposed = true;
23265
- const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
23504
+ let unregisterCrashCleanup = () => void 0;
23505
+ const disposeRuntime = async () => {
23506
+ if (disposed) return;
23507
+ disposed = true;
23508
+ unregisterCrashCleanup();
23509
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
23510
+ try {
23266
23511
  collaborationEventsOpen = false;
23267
23512
  unsubscribeCollaboration?.();
23268
23513
  if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
@@ -23281,8 +23526,29 @@ ${renderMemoryDocument(entries)}`;
23281
23526
  memoryReviews.dispose();
23282
23527
  if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
23283
23528
  await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, { stepTimeoutMs });
23529
+ } finally {
23530
+ const lease = sessionLease;
23531
+ sessionLease = void 0;
23532
+ await lease?.release();
23284
23533
  }
23285
23534
  };
23535
+ unregisterCrashCleanup = registerCrashCleanup(disposeRuntime);
23536
+ return {
23537
+ session,
23538
+ services,
23539
+ authorization,
23540
+ approvals,
23541
+ memoryReviews,
23542
+ restoredTranscript,
23543
+ jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
23544
+ get sessionId() {
23545
+ return sessionId;
23546
+ },
23547
+ get diagnostics() {
23548
+ return diagnostics.map((item) => redactSecrets(item, secrets));
23549
+ },
23550
+ dispose: disposeRuntime
23551
+ };
23286
23552
  } catch (primaryError) {
23287
23553
  collaborationEventsOpen = false;
23288
23554
  mcpDiscarded = true;
@@ -23303,6 +23569,12 @@ ${renderMemoryDocument(entries)}`;
23303
23569
  } catch (cleanupError) {
23304
23570
  attachCleanupError(primaryError, cleanupError);
23305
23571
  }
23572
+ try {
23573
+ await sessionLease?.release();
23574
+ } catch (leaseError) {
23575
+ attachCleanupError(primaryError, leaseError);
23576
+ }
23577
+ sessionLease = void 0;
23306
23578
  throw primaryError;
23307
23579
  }
23308
23580
  }
@@ -23441,8 +23713,8 @@ async function workspaceFingerprint(workspace) {
23441
23713
  let remaining = 5 * 1024 * 1024;
23442
23714
  for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
23443
23715
  if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
23444
- const path = resolve28(workspace, name);
23445
- const relativePath = relative20(resolve28(workspace), path);
23716
+ const path = resolve29(workspace, name);
23717
+ const relativePath = relative20(resolve29(workspace), path);
23446
23718
  if (relativePath === ".." || relativePath.startsWith(`..${sep20}`)) continue;
23447
23719
  hash.update(name);
23448
23720
  if (remaining <= 0) continue;
@@ -23575,8 +23847,8 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
23575
23847
  previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
23576
23848
  } catch {
23577
23849
  }
23578
- mkdirSync3(dirname19(markerPath), { recursive: true });
23579
- writeFileSync(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
23850
+ mkdirSync4(dirname19(markerPath), { recursive: true });
23851
+ writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
23580
23852
  `, "utf8");
23581
23853
  } catch {
23582
23854
  }
@@ -23677,7 +23949,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
23677
23949
  const configured = { ...providers };
23678
23950
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
23679
23951
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
23680
- const oauthTokenStore = createFileTokenStore(join26(home, ".flavor-code", "auth.json"));
23952
+ const oauthTokenStore = createFileTokenStore(join27(home, ".flavor-code", "auth.json"));
23681
23953
  const registered = [];
23682
23954
  let effectiveLlm;
23683
23955
  for (const [name, provider] of Object.entries(configured)) {
@@ -24113,6 +24385,7 @@ export {
24113
24385
  isDestructiveTool,
24114
24386
  redactErrorText,
24115
24387
  redactSecrets,
24388
+ prepareSpawnInvocation,
24116
24389
  PluginManifestSchema,
24117
24390
  initializeFlavor,
24118
24391
  DEFAULT_MAX_OUTPUT_BYTES2 as DEFAULT_MAX_OUTPUT_BYTES,
@@ -24132,6 +24405,7 @@ export {
24132
24405
  COMMAND_DESCRIPTIONS,
24133
24406
  AgentMessageQueue,
24134
24407
  FlavorSession,
24408
+ installCrashGuard,
24135
24409
  classifyMemoryHeat,
24136
24410
  MemoryStore,
24137
24411
  buildDockerInvocation,