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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/agent/loop.d.ts +3 -0
  2. package/dist/{app-C5ZQUOKJ.js → app-H7RYDMD3.js} +6 -6
  3. package/dist/{chunk-FOTMP4ZS.js → chunk-3BHKW5A7.js} +1 -1
  4. package/dist/{chunk-ODRWR4KK.js → chunk-IQAZ42TM.js} +1 -1
  5. package/dist/{chunk-URUVFECH.js → chunk-KBHWS7M6.js} +4 -4
  6. package/dist/{chunk-6MNOZ2TX.js → chunk-NIKCPCJG.js} +559 -377
  7. package/dist/{chunk-3WCDZ6ZL.js → chunk-ZJCAQXA7.js} +2 -1
  8. package/dist/{claude-ink-LP32NSX5.js → claude-ink-ECZ6QF3P.js} +2 -2
  9. package/dist/cli-main.js +7 -7
  10. package/dist/desktop/main.js +626 -443
  11. package/dist/desktop-renderer/assets/{index-CqRIr-hz.js → index-CYd5JkTR.js} +2 -2
  12. package/dist/desktop-renderer/assets/{interactive-terminal-C4o7MNPO.js → interactive-terminal-j52Ty3QO.js} +1 -1
  13. package/dist/desktop-renderer/index.html +1 -1
  14. package/dist/git/service.d.ts +3 -3
  15. package/dist/git/tools.d.ts +1 -1
  16. package/dist/harness/local.d.ts +2 -0
  17. package/dist/init/project.d.ts +0 -6
  18. package/dist/jobs/registry.d.ts +1 -1
  19. package/dist/{load-V2UIYYQI.js → load-SLSL3IWR.js} +1 -1
  20. package/dist/sdk/index.js +4 -4
  21. package/dist/tools/lsp.d.ts +13 -10
  22. package/dist/tools/runtime.d.ts +2 -1
  23. package/dist/tools/search.d.ts +1 -0
  24. package/dist/tools/types.d.ts +1 -1
  25. package/dist/utils/abort.d.ts +4 -0
  26. package/dist/utils/execFileNoThrow.d.ts +1 -0
  27. package/dist/utils/process-tree.d.ts +2 -0
  28. package/package.json +2 -2
  29. package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +7 -7
  30. package/dist/astgraph/db.mjs +0 -237
  31. package/dist/astgraph/extract.mjs +0 -239
  32. package/dist/astgraph/flavor-plugin.json +0 -25
  33. package/dist/astgraph/grammars.mjs +0 -68
  34. package/dist/astgraph/index.js +0 -301
  35. package/dist/astgraph/indexer.mjs +0 -175
  36. package/dist/astgraph/query.mjs +0 -165
  37. package/dist/astgraph/resolve.mjs +0 -93
  38. package/dist/astgraph/vendor/tree-sitter-javascript.wasm +0 -0
  39. package/dist/astgraph/vendor/tree-sitter-tsx.wasm +0 -0
  40. package/dist/astgraph/vendor/tree-sitter-typescript.wasm +0 -0
  41. package/dist/astgraph/vendor/tree-sitter.js +0 -3980
  42. package/dist/astgraph/vendor/tree-sitter.wasm +0 -0
  43. package/dist/astgraph/vendor/zod/LICENSE +0 -21
  44. package/dist/astgraph/vendor/zod/index.js +0 -4
  45. package/dist/astgraph/vendor/zod/locales/index.js +0 -1
  46. package/dist/astgraph/vendor/zod/locales/package.json +0 -7
  47. package/dist/astgraph/vendor/zod/package.json +0 -135
  48. package/dist/astgraph/vendor/zod/v4/classic/checks.js +0 -1
  49. package/dist/astgraph/vendor/zod/v4/classic/coerce.js +0 -17
  50. package/dist/astgraph/vendor/zod/v4/classic/compat.js +0 -31
  51. package/dist/astgraph/vendor/zod/v4/classic/errors.js +0 -48
  52. package/dist/astgraph/vendor/zod/v4/classic/external.js +0 -20
  53. package/dist/astgraph/vendor/zod/v4/classic/from-json-schema.js +0 -599
  54. package/dist/astgraph/vendor/zod/v4/classic/index.js +0 -4
  55. package/dist/astgraph/vendor/zod/v4/classic/iso.js +0 -30
  56. package/dist/astgraph/vendor/zod/v4/classic/package.json +0 -7
  57. package/dist/astgraph/vendor/zod/v4/classic/parse.js +0 -15
  58. package/dist/astgraph/vendor/zod/v4/classic/schemas.js +0 -1395
  59. package/dist/astgraph/vendor/zod/v4/core/api.js +0 -1087
  60. package/dist/astgraph/vendor/zod/v4/core/checks.js +0 -575
  61. package/dist/astgraph/vendor/zod/v4/core/core.js +0 -78
  62. package/dist/astgraph/vendor/zod/v4/core/doc.js +0 -35
  63. package/dist/astgraph/vendor/zod/v4/core/errors.js +0 -185
  64. package/dist/astgraph/vendor/zod/v4/core/index.js +0 -16
  65. package/dist/astgraph/vendor/zod/v4/core/json-schema-generator.js +0 -95
  66. package/dist/astgraph/vendor/zod/v4/core/json-schema-processors.js +0 -601
  67. package/dist/astgraph/vendor/zod/v4/core/json-schema.js +0 -1
  68. package/dist/astgraph/vendor/zod/v4/core/package.json +0 -7
  69. package/dist/astgraph/vendor/zod/v4/core/parse.js +0 -93
  70. package/dist/astgraph/vendor/zod/v4/core/regexes.js +0 -139
  71. package/dist/astgraph/vendor/zod/v4/core/registries.js +0 -51
  72. package/dist/astgraph/vendor/zod/v4/core/schemas.js +0 -2239
  73. package/dist/astgraph/vendor/zod/v4/core/standard-schema.js +0 -1
  74. package/dist/astgraph/vendor/zod/v4/core/to-json-schema.js +0 -448
  75. package/dist/astgraph/vendor/zod/v4/core/util.js +0 -674
  76. package/dist/astgraph/vendor/zod/v4/core/versions.js +0 -5
  77. package/dist/astgraph/vendor/zod/v4/index.js +0 -3
  78. package/dist/astgraph/vendor/zod/v4/locales/ar.js +0 -106
  79. package/dist/astgraph/vendor/zod/v4/locales/az.js +0 -105
  80. package/dist/astgraph/vendor/zod/v4/locales/be.js +0 -156
  81. package/dist/astgraph/vendor/zod/v4/locales/bg.js +0 -120
  82. package/dist/astgraph/vendor/zod/v4/locales/ca.js +0 -107
  83. package/dist/astgraph/vendor/zod/v4/locales/cs.js +0 -111
  84. package/dist/astgraph/vendor/zod/v4/locales/da.js +0 -115
  85. package/dist/astgraph/vendor/zod/v4/locales/de.js +0 -108
  86. package/dist/astgraph/vendor/zod/v4/locales/el.js +0 -109
  87. package/dist/astgraph/vendor/zod/v4/locales/en.js +0 -113
  88. package/dist/astgraph/vendor/zod/v4/locales/eo.js +0 -109
  89. package/dist/astgraph/vendor/zod/v4/locales/es.js +0 -132
  90. package/dist/astgraph/vendor/zod/v4/locales/fa.js +0 -114
  91. package/dist/astgraph/vendor/zod/v4/locales/fi.js +0 -112
  92. package/dist/astgraph/vendor/zod/v4/locales/fr-CA.js +0 -107
  93. package/dist/astgraph/vendor/zod/v4/locales/fr.js +0 -125
  94. package/dist/astgraph/vendor/zod/v4/locales/he.js +0 -214
  95. package/dist/astgraph/vendor/zod/v4/locales/hr.js +0 -122
  96. package/dist/astgraph/vendor/zod/v4/locales/hu.js +0 -108
  97. package/dist/astgraph/vendor/zod/v4/locales/hy.js +0 -147
  98. package/dist/astgraph/vendor/zod/v4/locales/id.js +0 -106
  99. package/dist/astgraph/vendor/zod/v4/locales/index.js +0 -52
  100. package/dist/astgraph/vendor/zod/v4/locales/is.js +0 -109
  101. package/dist/astgraph/vendor/zod/v4/locales/it.js +0 -108
  102. package/dist/astgraph/vendor/zod/v4/locales/ja.js +0 -107
  103. package/dist/astgraph/vendor/zod/v4/locales/ka.js +0 -112
  104. package/dist/astgraph/vendor/zod/v4/locales/kh.js +0 -5
  105. package/dist/astgraph/vendor/zod/v4/locales/km.js +0 -110
  106. package/dist/astgraph/vendor/zod/v4/locales/ko.js +0 -111
  107. package/dist/astgraph/vendor/zod/v4/locales/lt.js +0 -203
  108. package/dist/astgraph/vendor/zod/v4/locales/mk.js +0 -109
  109. package/dist/astgraph/vendor/zod/v4/locales/ms.js +0 -107
  110. package/dist/astgraph/vendor/zod/v4/locales/nl.js +0 -110
  111. package/dist/astgraph/vendor/zod/v4/locales/no.js +0 -108
  112. package/dist/astgraph/vendor/zod/v4/locales/ota.js +0 -109
  113. package/dist/astgraph/vendor/zod/v4/locales/package.json +0 -7
  114. package/dist/astgraph/vendor/zod/v4/locales/pl.js +0 -109
  115. package/dist/astgraph/vendor/zod/v4/locales/ps.js +0 -114
  116. package/dist/astgraph/vendor/zod/v4/locales/pt.js +0 -108
  117. package/dist/astgraph/vendor/zod/v4/locales/ro.js +0 -119
  118. package/dist/astgraph/vendor/zod/v4/locales/ru.js +0 -156
  119. package/dist/astgraph/vendor/zod/v4/locales/sl.js +0 -109
  120. package/dist/astgraph/vendor/zod/v4/locales/sv.js +0 -110
  121. package/dist/astgraph/vendor/zod/v4/locales/ta.js +0 -110
  122. package/dist/astgraph/vendor/zod/v4/locales/th.js +0 -110
  123. package/dist/astgraph/vendor/zod/v4/locales/tr.js +0 -105
  124. package/dist/astgraph/vendor/zod/v4/locales/ua.js +0 -5
  125. package/dist/astgraph/vendor/zod/v4/locales/uk.js +0 -108
  126. package/dist/astgraph/vendor/zod/v4/locales/ur.js +0 -110
  127. package/dist/astgraph/vendor/zod/v4/locales/uz.js +0 -110
  128. package/dist/astgraph/vendor/zod/v4/locales/vi.js +0 -108
  129. package/dist/astgraph/vendor/zod/v4/locales/yo.js +0 -107
  130. package/dist/astgraph/vendor/zod/v4/locales/zh-CN.js +0 -109
  131. package/dist/astgraph/vendor/zod/v4/locales/zh-TW.js +0 -107
  132. package/dist/astgraph/vendor/zod/v4/mini/checks.js +0 -1
  133. package/dist/astgraph/vendor/zod/v4/mini/coerce.js +0 -22
  134. package/dist/astgraph/vendor/zod/v4/mini/external.js +0 -14
  135. package/dist/astgraph/vendor/zod/v4/mini/index.js +0 -3
  136. package/dist/astgraph/vendor/zod/v4/mini/iso.js +0 -34
  137. package/dist/astgraph/vendor/zod/v4/mini/package.json +0 -7
  138. package/dist/astgraph/vendor/zod/v4/mini/parse.js +0 -1
  139. package/dist/astgraph/vendor/zod/v4/mini/schemas.js +0 -961
  140. package/dist/astgraph/vendor/zod/v4/package.json +0 -7
@@ -2,8 +2,8 @@
2
2
  import { appendFileSync } from "fs";
3
3
  import { createHash as createHash28, randomUUID as randomUUID29 } from "crypto";
4
4
  import { readFile as readFile40, stat as stat19, writeFile as writeFile19 } from "fs/promises";
5
- import { basename as basename12, dirname as dirname25, join as join50, resolve as resolve42 } from "path";
6
- import { fileURLToPath as fileURLToPath4 } from "url";
5
+ import { basename as basename12, dirname as dirname23, join as join50, resolve as resolve43 } from "path";
6
+ import { fileURLToPath as fileURLToPath3 } from "url";
7
7
  import { app, BrowserWindow as BrowserWindow2, clipboard, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
8
8
 
9
9
  // src/desktop/contracts.ts
@@ -293,13 +293,13 @@ var FlavorConfigSchema = z2.object({
293
293
  evaluationTimeoutMs: z2.number().int().min(100).max(3e4).default(2e3)
294
294
  }).prefault({}),
295
295
  maxIterations: z2.object({
296
- main: z2.number().int().min(10).max(500).default(80),
297
- subagent: z2.number().int().min(10).max(200).default(40),
296
+ main: z2.number().int().min(10).max(500).default(300),
297
+ subagent: z2.number().int().min(10).max(200).default(100),
298
298
  softLimitFactor: z2.number().min(0.5).max(1).default(0.8),
299
- extendBy: z2.number().int().min(5).max(100).default(20)
299
+ extendBy: z2.number().int().min(5).max(100).default(50)
300
300
  }).prefault({}),
301
301
  loop: z2.object({
302
- maxCycles: z2.number().int().positive().max(1e4).default(20),
302
+ maxCycles: z2.number().int().positive().max(1e4).default(100),
303
303
  maxTokens: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).default(5e5),
304
304
  isolation: z2.literal("auto").default("auto")
305
305
  }).prefault({}),
@@ -1990,7 +1990,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
1990
1990
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
1991
1991
  if (ready === "complete" || ready === "interactive") return candidate;
1992
1992
  }
1993
- await new Promise((resolve43) => setTimeout(resolve43, pollIntervalMs));
1993
+ await new Promise((resolve44) => setTimeout(resolve44, pollIntervalMs));
1994
1994
  }
1995
1995
  throw new Error(`D2C embedded preview did not navigate within ${navigationTimeoutMs} ms: ${url}${observedUrl === void 0 ? "" : `; last observed ${observedUrl}`}${observedFrames.length === 0 ? "; no live frames" : `; live frames ${observedFrames.join(", ")}`}`);
1996
1996
  };
@@ -2005,13 +2005,13 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2005
2005
  while (Date.now() <= deadline) {
2006
2006
  last = await frame(baseUrl).executeJavaScript(assertionScript(step), true);
2007
2007
  if (last.passed) return last;
2008
- await new Promise((resolve43) => setTimeout(resolve43, 50));
2008
+ await new Promise((resolve44) => setTimeout(resolve44, 50));
2009
2009
  }
2010
2010
  return last;
2011
2011
  };
2012
2012
  const settleAfterAction = async (previousUrl) => {
2013
2013
  const origin = new URL(previousUrl).origin;
2014
- await new Promise((resolve43) => setTimeout(resolve43, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
2014
+ await new Promise((resolve44) => setTimeout(resolve44, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
2015
2015
  const deadline = Date.now() + Math.min(2e3, navigationTimeoutMs);
2016
2016
  while (Date.now() <= deadline) {
2017
2017
  const candidate = host().mainFrame.framesInSubtree.find((item) => !item.isDestroyed() && sameOrigin(item.url, origin));
@@ -2019,7 +2019,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2019
2019
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
2020
2020
  if (ready === "complete" || ready === "interactive") return candidate;
2021
2021
  }
2022
- await new Promise((resolve43) => setTimeout(resolve43, pollIntervalMs));
2022
+ await new Promise((resolve44) => setTimeout(resolve44, pollIntervalMs));
2023
2023
  }
2024
2024
  throw new Error("D2C preview frame was unavailable after an interaction action");
2025
2025
  };
@@ -2106,7 +2106,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2106
2106
  return result;
2107
2107
  },
2108
2108
  settle: async () => {
2109
- await new Promise((resolve43) => setTimeout(resolve43, 150));
2109
+ await new Promise((resolve44) => setTimeout(resolve44, 150));
2110
2110
  requests = await frame(activeUrl).executeJavaScript(`performance.getEntriesByType("resource").filter((entry) => {
2111
2111
  try { return ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && new URL(entry.name).origin === ${JSON.stringify(mockOrigin)}; } catch { return false; }
2112
2112
  }).length`, false).catch(() => 0);
@@ -2192,7 +2192,7 @@ function responseText(protocol, value) {
2192
2192
  }
2193
2193
  throw new Error("D2C Judge response did not contain text output");
2194
2194
  }
2195
- function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve43) => setTimeout(resolve43, ms))) {
2195
+ function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve44) => setTimeout(resolve44, ms))) {
2196
2196
  const request = async (config, prompt, images, maxTokens) => {
2197
2197
  const openAi = config.protocol === "openai-compatible";
2198
2198
  const imageContent = openAi ? images.map((image) => ({ type: "image_url", image_url: { url: dataUrl(image), detail: "high" } })) : images.map((image) => ({ type: "image", source: { type: "base64", media_type: "image/png", data: image.toString("base64") } }));
@@ -2551,7 +2551,7 @@ import { z as z7 } from "zod";
2551
2551
  import { Worker } from "worker_threads";
2552
2552
  function comparePngsInWorker(left, right, signal) {
2553
2553
  signal?.throwIfAborted();
2554
- return new Promise((resolve43, reject) => {
2554
+ return new Promise((resolve44, reject) => {
2555
2555
  const worker = new Worker(new URL("./pixel-worker.js", import.meta.url));
2556
2556
  let settled = false;
2557
2557
  const finish = (callback) => {
@@ -2569,7 +2569,7 @@ function comparePngsInWorker(left, right, signal) {
2569
2569
  finish(() => reject(new Error(value.error ?? "D2C pixel worker failed")));
2570
2570
  return;
2571
2571
  }
2572
- finish(() => resolve43({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2572
+ finish(() => resolve44({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2573
2573
  });
2574
2574
  worker.once("error", (error) => finish(() => reject(error)));
2575
2575
  worker.once("exit", (code) => {
@@ -3961,11 +3961,11 @@ function createD2cTools(workspace, options = {}) {
3961
3961
  name: "D2cImport",
3962
3962
  description: "Import a Pixso HTML design export for D2C (design-to-code) comparison. Copies the export directory into .flavor/d2c/<task>/design/. The task name must be lowercase letters, digits and dashes.",
3963
3963
  inputSchema: D2cImportInput,
3964
- paths: (input) => [input.exportDir],
3964
+ paths: (input) => [resolve4(workspace, input.exportDir), resolve4(workspace, ".flavor", "d2c", input.task)],
3965
3965
  summarize: (input) => `${input.task} \u2190 ${input.exportDir}`,
3966
3966
  execute: async (input, signal) => {
3967
3967
  signal.throwIfAborted();
3968
- const manifest = await importDesign(workspace, input.task, input.exportDir);
3968
+ const manifest = await importDesign(workspace, input.task, resolve4(workspace, input.exportDir));
3969
3969
  return {
3970
3970
  task: input.task,
3971
3971
  entryHtml: manifest.entryHtml,
@@ -3979,7 +3979,7 @@ function createD2cTools(workspace, options = {}) {
3979
3979
  name: "D2cCompare",
3980
3980
  description: "Compare an imported Pixso design against the rendered implementation and score visual fidelity. The implementation is a frontend project directory (Vue or React, Vite-based; dependencies are installed and the dev server is started and stopped automatically), a localhost URL of an already running server, or a workspace-relative .html file. Returns a similarity score plus structured offsets, color deviations, font mismatches and missing or extra elements, and stores the report with screenshots under .flavor/d2c/<task>/reports/. Requires the desktop app for rendering.",
3981
3981
  inputSchema: D2cCompareInput,
3982
- paths: (input) => [input.implementation],
3982
+ paths: (input) => [resolve4(workspace, ".flavor", "d2c", input.task), .../^https?:\/\//iu.test(input.implementation) ? [] : [resolve4(workspace, input.implementation)]],
3983
3983
  summarize: (input) => input.task,
3984
3984
  execute: async (input, signal) => {
3985
3985
  signal.throwIfAborted();
@@ -4226,10 +4226,10 @@ ${summarizeReport(report2)}`).join("\n\n")
4226
4226
 
4227
4227
  // src/production.ts
4228
4228
  import { readFile as readFile28 } from "fs/promises";
4229
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
4229
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
4230
4230
  import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
4231
4231
  import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
4232
- import { basename as basename9, dirname as dirname24, join as join37, relative as relative24, resolve as resolve35, sep as sep24 } from "path";
4232
+ import { basename as basename9, dirname as dirname22, join as join37, relative as relative25, resolve as resolve36, sep as sep25 } from "path";
4233
4233
 
4234
4234
  // src/agent/subagents.ts
4235
4235
  import { z as z10 } from "zod";
@@ -4442,12 +4442,12 @@ function hasCycle(nodes) {
4442
4442
 
4443
4443
  // src/utils/async.ts
4444
4444
  function withTimeout(promise, ms) {
4445
- return new Promise((resolve43, reject) => {
4445
+ return new Promise((resolve44, reject) => {
4446
4446
  let settled = false;
4447
4447
  const timer = setTimeout(() => {
4448
4448
  if (settled) return;
4449
4449
  settled = true;
4450
- resolve43({ timedOut: true, value: void 0 });
4450
+ resolve44({ timedOut: true, value: void 0 });
4451
4451
  }, ms);
4452
4452
  timer.unref?.();
4453
4453
  promise.then(
@@ -4455,7 +4455,7 @@ function withTimeout(promise, ms) {
4455
4455
  if (!settled) {
4456
4456
  settled = true;
4457
4457
  clearTimeout(timer);
4458
- resolve43({ timedOut: false, value });
4458
+ resolve44({ timedOut: false, value });
4459
4459
  }
4460
4460
  },
4461
4461
  (error) => {
@@ -4470,13 +4470,13 @@ function withTimeout(promise, ms) {
4470
4470
  }
4471
4471
  function awaitWithSignal3(promise, signal) {
4472
4472
  signal.throwIfAborted();
4473
- return new Promise((resolve43, reject) => {
4473
+ return new Promise((resolve44, reject) => {
4474
4474
  const onAbort = () => reject(signal.reason);
4475
4475
  signal.addEventListener("abort", onAbort, { once: true });
4476
4476
  promise.then(
4477
4477
  (value) => {
4478
4478
  signal.removeEventListener("abort", onAbort);
4479
- resolve43(value);
4479
+ resolve44(value);
4480
4480
  },
4481
4481
  (error) => {
4482
4482
  signal.removeEventListener("abort", onAbort);
@@ -4739,7 +4739,7 @@ var TaskPlanSchema = z11.object({
4739
4739
  }
4740
4740
  dependencies2.add(dependency);
4741
4741
  }
4742
- if (task.status === "completed") {
4742
+ if (task.status === "completed" || task.status === "in_progress") {
4743
4743
  const incomplete = task.dependencies.find((dependency) => plan.tasks.find((candidate) => candidate.id === dependency)?.status !== "completed");
4744
4744
  if (incomplete !== void 0) {
4745
4745
  context.addIssue({
@@ -4768,7 +4768,7 @@ function updatePlanTask(plan, input) {
4768
4768
  if (!isValidTransition(previousStatus, update.status)) {
4769
4769
  throw new Error(`Invalid task transition for ${update.taskId}: ${previousStatus} -> ${update.status}`);
4770
4770
  }
4771
- if (update.status === "completed") {
4771
+ if (update.status === "completed" || update.status === "in_progress") {
4772
4772
  const incomplete = current.tasks[index].dependencies.find((dependency) => current.tasks.find((task) => task.id === dependency)?.status !== "completed");
4773
4773
  if (incomplete !== void 0) throw new Error(`Task ${update.taskId} has an incomplete dependency: ${incomplete}`);
4774
4774
  }
@@ -4793,6 +4793,7 @@ function isValidTransition(from, to) {
4793
4793
  if (from === "in_progress") {
4794
4794
  return to === "completed" || to === "failed" || to === "blocked" || to === "cancelled";
4795
4795
  }
4796
+ if (from === "blocked" || from === "failed") return to === "pending" || to === "in_progress" || to === "cancelled";
4796
4797
  return false;
4797
4798
  }
4798
4799
  function normalizeAbandonedPlan(plan) {
@@ -6163,8 +6164,8 @@ function withStructuredOutput(options) {
6163
6164
  if (retry.backoffMs.length < retry.maxRetries) {
6164
6165
  throw new Error("Structured output backoffMs must provide one delay per retry");
6165
6166
  }
6166
- for (const delay4 of retry.backoffMs.slice(0, retry.maxRetries)) {
6167
- if (!Number.isSafeInteger(delay4) || delay4 < 0) {
6167
+ for (const delay3 of retry.backoffMs.slice(0, retry.maxRetries)) {
6168
+ if (!Number.isSafeInteger(delay3) || delay3 < 0) {
6168
6169
  throw new Error("Structured output retry delays must be non-negative integers");
6169
6170
  }
6170
6171
  }
@@ -6525,10 +6526,10 @@ function sanitize(message2, secrets) {
6525
6526
  }
6526
6527
  function waitForRetry(delayMs, signal) {
6527
6528
  if (signal?.aborted) return Promise.reject(signal.reason);
6528
- return new Promise((resolve43, reject) => {
6529
+ return new Promise((resolve44, reject) => {
6529
6530
  const timer = setTimeout(() => {
6530
6531
  signal?.removeEventListener("abort", onAbort);
6531
- resolve43();
6532
+ resolve44();
6532
6533
  }, delayMs);
6533
6534
  const onAbort = () => {
6534
6535
  clearTimeout(timer);
@@ -6599,6 +6600,15 @@ var AgentLoop = class {
6599
6600
  get iterationLimitMode() {
6600
6601
  return this.#iterationLimitMode;
6601
6602
  }
6603
+ get maxIterations() {
6604
+ return this.#options.maxIterations;
6605
+ }
6606
+ get softLimitFactor() {
6607
+ return this.#options.softLimitFactor;
6608
+ }
6609
+ get extendIterations() {
6610
+ return this.#options.extendIterations;
6611
+ }
6602
6612
  setModel(modelId) {
6603
6613
  this.#options.registry.get(modelId);
6604
6614
  this.#options.modelId = modelId;
@@ -7166,10 +7176,10 @@ function serializeToolInput(input) {
7166
7176
  }
7167
7177
  function waitForRetry2(delayMs, signal) {
7168
7178
  if (signal?.aborted) return Promise.reject(signal.reason);
7169
- return new Promise((resolve43, reject) => {
7179
+ return new Promise((resolve44, reject) => {
7170
7180
  const timer = setTimeout(() => {
7171
7181
  signal?.removeEventListener("abort", onAbort);
7172
- resolve43();
7182
+ resolve44();
7173
7183
  }, delayMs);
7174
7184
  const onAbort = () => {
7175
7185
  clearTimeout(timer);
@@ -7824,8 +7834,9 @@ var ToolRuntime = class {
7824
7834
  }
7825
7835
  }
7826
7836
  async execute(call, context) {
7837
+ if (this.#disposed) return { ok: false, error: { code: "runtime_disposed", message: "ToolRuntime is disposed" } };
7827
7838
  const definition = this.#tools.get(call.name);
7828
- const journalId = definition === void 0 || this.#journal === void 0 ? void 0 : this.#journal.start(call.name, call.input, definition.retrySafe === true || definition.readOnly === true);
7839
+ const journalId = definition === void 0 || this.#journal === void 0 ? void 0 : this.#journal.start(call.name, call.input, definition.retrySafe === true || definition.readOnly === true || getToolCategory(call.name) === "read");
7829
7840
  try {
7830
7841
  const result = await this.#execute(call, context);
7831
7842
  if (journalId !== void 0) this.#journal?.complete(journalId, result);
@@ -7852,6 +7863,10 @@ var ToolRuntime = class {
7852
7863
  if (tool === void 0) return { ok: false, error: { code: "unknown_tool", message: `Unknown tool: ${call.name}` } };
7853
7864
  const signal = context.signal ?? new AbortController().signal;
7854
7865
  const toolCallId = call.id ?? randomUUID7();
7866
+ if (tool.agents !== void 0 && !tool.agents.includes(context.agent)) {
7867
+ return this.#fail(toolCallId, tool.name, call.input, context.agent, "permission_denied", `Tool ${tool.name} is not available to ${context.agent} agents`);
7868
+ }
7869
+ if (signal.aborted) return this.#fail(toolCallId, tool.name, call.input, context.agent, "cancelled", message(signal.reason));
7855
7870
  const validation = this.normalize(call);
7856
7871
  if (!validation.ok) {
7857
7872
  return this.#fail(toolCallId, call.name, call.input, context.agent, validation.error.code, validation.error.message);
@@ -7959,17 +7974,37 @@ var ToolRuntime = class {
7959
7974
  } catch (error) {
7960
7975
  return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_output", message(error));
7961
7976
  }
7962
- const declaredPresentation = tool.presentResult?.(rawOutput, input);
7963
- const presentation = declaredPresentation ?? getToolPresentation(rawOutput);
7964
- const rendered = tool.renderForModel?.(rawOutput, input);
7977
+ const warnings = [];
7978
+ let presentation = getToolPresentation(rawOutput);
7979
+ try {
7980
+ presentation = tool.presentResult?.(rawOutput, input) ?? presentation;
7981
+ } catch (error) {
7982
+ warnings.push(`Tool succeeded; result presentation failed: ${message(error)}`);
7983
+ }
7984
+ let rendered;
7985
+ try {
7986
+ rendered = tool.renderForModel?.(rawOutput, input);
7987
+ } catch (error) {
7988
+ warnings.push(`Tool succeeded; result rendering failed: ${message(error)}`);
7989
+ }
7965
7990
  const output = rendered === void 0 ? await this.#limitOutput(tool.name, rawOutput) : rawOutput;
7966
7991
  const content = rendered === void 0 ? void 0 : await this.#limitText(tool.name, rendered);
7967
- const additionalContext = await this.#afterSuccess?.(tool.name, tool.paths(input), input, rawOutput, context);
7968
- await this.#hooks.emit({
7969
- version: 1,
7970
- type: "PostToolUse",
7971
- payload: { toolCallId, tool: tool.name, input, agent: context.agent, output }
7972
- });
7992
+ let additionalContext = [];
7993
+ try {
7994
+ additionalContext = await this.#afterSuccess?.(tool.name, tool.paths(input), input, rawOutput, context) ?? [];
7995
+ } catch (error) {
7996
+ warnings.push(`Tool succeeded; context update failed: ${message(error)}`);
7997
+ }
7998
+ try {
7999
+ await this.#hooks.emit({
8000
+ version: 1,
8001
+ type: "PostToolUse",
8002
+ payload: { toolCallId, tool: tool.name, input, agent: context.agent, output }
8003
+ });
8004
+ } catch (error) {
8005
+ warnings.push(`Tool succeeded; PostToolUse hook failed: ${message(error)}`);
8006
+ }
8007
+ additionalContext = [...additionalContext, ...warnings];
7973
8008
  return {
7974
8009
  ok: true,
7975
8010
  output,
@@ -8000,17 +8035,23 @@ var ToolRuntime = class {
8000
8035
  const preview = headTailPreview(serialized, previewChars);
8001
8036
  this.#turnOutputChars += preview.length;
8002
8037
  const directory = join7(this.#workspace, ".flavor", "tool-results");
8003
- await mkdir6(directory, { recursive: true });
8004
8038
  const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7()}-${safeFilename(tool)}.txt`;
8005
8039
  const savedTo = join7(directory, filename2);
8006
- await writeFile3(savedTo, serialized, "utf8");
8040
+ let storage;
8041
+ try {
8042
+ await mkdir6(directory, { recursive: true });
8043
+ await writeFile3(savedTo, serialized, "utf8");
8044
+ storage = { savedTo };
8045
+ } catch (error) {
8046
+ storage = { storageError: `Tool succeeded, but the full output could not be saved: ${message(error)}` };
8047
+ }
8007
8048
  return {
8008
8049
  truncated: true,
8009
8050
  reason,
8010
8051
  preview,
8011
8052
  originalChars: serialized.length,
8012
8053
  previewChars: preview.length,
8013
- savedTo
8054
+ ...storage
8014
8055
  };
8015
8056
  }
8016
8057
  callPresentation(call) {
@@ -8211,6 +8252,8 @@ var LocalHarness = class {
8211
8252
  agent,
8212
8253
  ownerId,
8213
8254
  ...maxIterations === void 0 ? {} : { maxIterations },
8255
+ ...this.#options.softLimitFactor === void 0 ? {} : { softLimitFactor: this.#options.softLimitFactor },
8256
+ ...this.#options.extendIterations === void 0 ? {} : { extendIterations: this.#options.extendIterations },
8214
8257
  ...isMain && this.#options.hasActiveProgress !== void 0 ? { hasActiveProgress: this.#options.hasActiveProgress } : {},
8215
8258
  ...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {},
8216
8259
  ...this.#options.modelJournal === void 0 ? {} : { modelJournal: this.#options.modelJournal }
@@ -8752,7 +8795,7 @@ function resolveWindowsCommand(command, cwd, env) {
8752
8795
  ...pathValue.split(";").filter(Boolean).map((entry2) => resolve9(unquote(entry2), command))
8753
8796
  ];
8754
8797
  for (const base of bases) {
8755
- const candidates = extname2(base).length > 0 ? [base] : [base, ...pathExt.map((extension2) => `${base}${extension2}`)];
8798
+ const candidates = extname2(base).length > 0 ? [base] : [...pathExt.map((extension2) => `${base}${extension2}`), base];
8756
8799
  for (const candidate of candidates) {
8757
8800
  if (isRegularFile(candidate)) return resolve9(candidate);
8758
8801
  }
@@ -8898,7 +8941,7 @@ function decoratePayload(payload, envelope) {
8898
8941
  return envelope === void 0 ? payload : { ...payload, ...envelope };
8899
8942
  }
8900
8943
  function runShellHandler(descriptor, event, signal) {
8901
- return new Promise((resolve43, reject) => {
8944
+ return new Promise((resolve44, reject) => {
8902
8945
  const env = { ...process.env, ...descriptor.env };
8903
8946
  const invocation = prepareSpawnInvocation(descriptor.command, descriptor.args ?? [], { env });
8904
8947
  const child = spawn(invocation.command, invocation.args, {
@@ -8944,7 +8987,7 @@ function runShellHandler(descriptor, event, signal) {
8944
8987
  return;
8945
8988
  }
8946
8989
  try {
8947
- resolve43(HookDecisionSchema.parse(JSON.parse(stdout)));
8990
+ resolve44(HookDecisionSchema.parse(JSON.parse(stdout)));
8948
8991
  } catch (error) {
8949
8992
  reject(error);
8950
8993
  }
@@ -8983,15 +9026,16 @@ function zodRecord(payload) {
8983
9026
  // src/utils/execFileNoThrow.ts
8984
9027
  import { execFile } from "child_process";
8985
9028
  function execFileNoThrow(file, args, options = {}) {
8986
- return new Promise((resolve43) => {
9029
+ return new Promise((resolve44) => {
8987
9030
  const child = execFile(file, args, {
8988
9031
  timeout: options.timeout,
8989
9032
  cwd: options.useCwd === false ? void 0 : process.cwd(),
8990
9033
  env: options.env,
8991
9034
  encoding: "utf8",
8992
- windowsHide: true
9035
+ windowsHide: true,
9036
+ ...options.signal === void 0 ? {} : { signal: options.signal }
8993
9037
  }, (error, stdout, stderr) => {
8994
- resolve43({
9038
+ resolve44({
8995
9039
  stdout,
8996
9040
  stderr,
8997
9041
  code: typeof error?.code === "number" ? error.code : error ? 1 : 0,
@@ -9163,14 +9207,14 @@ async function createIslandControlServer(options) {
9163
9207
  void dispatch(line, socket, options, token);
9164
9208
  });
9165
9209
  });
9166
- await new Promise((resolve43, reject) => {
9210
+ await new Promise((resolve44, reject) => {
9167
9211
  const onError = (error) => {
9168
9212
  server.removeListener("listening", onListening);
9169
9213
  reject(error);
9170
9214
  };
9171
9215
  const onListening = () => {
9172
9216
  server.removeListener("error", onError);
9173
- resolve43();
9217
+ resolve44();
9174
9218
  };
9175
9219
  server.once("error", onError);
9176
9220
  server.once("listening", onListening);
@@ -9241,11 +9285,11 @@ function write(socket, payload) {
9241
9285
  }
9242
9286
  }
9243
9287
  function closeServer(server) {
9244
- return new Promise((resolve43) => {
9288
+ return new Promise((resolve44) => {
9245
9289
  try {
9246
- server.close(() => resolve43());
9290
+ server.close(() => resolve44());
9247
9291
  } catch {
9248
- resolve43();
9292
+ resolve44();
9249
9293
  }
9250
9294
  });
9251
9295
  }
@@ -10430,7 +10474,7 @@ var USAGE = [
10430
10474
  " clear reset signals, done markers, and verified markers"
10431
10475
  ].join("\n");
10432
10476
  function runCommand(command, cwd, timeoutMs) {
10433
- return new Promise((resolve43) => {
10477
+ return new Promise((resolve44) => {
10434
10478
  const isWindows = process.platform === "win32";
10435
10479
  const child = spawn2(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
10436
10480
  cwd,
@@ -10440,7 +10484,7 @@ function runCommand(command, cwd, timeoutMs) {
10440
10484
  let stderr = "";
10441
10485
  const timer = setTimeout(() => {
10442
10486
  child.kill();
10443
- resolve43({ ok: false, stdout, stderr: `${stderr}
10487
+ resolve44({ ok: false, stdout, stderr: `${stderr}
10444
10488
  [killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
10445
10489
  }, timeoutMs);
10446
10490
  child.stdout.on("data", (chunk) => {
@@ -10451,12 +10495,12 @@ function runCommand(command, cwd, timeoutMs) {
10451
10495
  });
10452
10496
  child.on("error", (error) => {
10453
10497
  clearTimeout(timer);
10454
- resolve43({ ok: false, stdout, stderr: `${stderr}
10498
+ resolve44({ ok: false, stdout, stderr: `${stderr}
10455
10499
  ${error.message}`, code: "spawn" });
10456
10500
  });
10457
10501
  child.on("close", (code) => {
10458
10502
  clearTimeout(timer);
10459
- resolve43({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10503
+ resolve44({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10460
10504
  });
10461
10505
  });
10462
10506
  }
@@ -10827,10 +10871,12 @@ ${result.stderr.slice(-4e3)}` : ""}`;
10827
10871
  name: "evolve_improve",
10828
10872
  description: "Implement a fix for one repeated tool failure. Default kind=plugin scaffolds the fix-<tool>/ plugin dir and writes PLAN.md with instructions for implementing, verifying, reloading, and testing it. kind=prompt_rule instead stores a concise guardrail rule (from `implementation`) that is injected into future system prompts and closes the suggestion. Use when the model proposes a concrete fix for a suggestion in the system prompt.",
10829
10873
  inputSchema,
10830
- paths: () => [],
10831
- execute: async (input) => {
10874
+ paths: (input) => [join12(workspace, ".flavor", "evolve"), ...input.kind === "prompt_rule" ? [] : [join12(workspace, ".flavor", "plugins")]],
10875
+ execute: async (input, signal) => {
10876
+ signal.throwIfAborted();
10832
10877
  const { suggestionId, implementation, kind } = input;
10833
10878
  const suggestions = await openWithTrends(100);
10879
+ signal.throwIfAborted();
10834
10880
  const suggestion = suggestions.find((item) => item.id === suggestionId);
10835
10881
  if (suggestion === void 0) throw new Error(`No open suggestion with id "${suggestionId}".`);
10836
10882
  if (kind === "prompt_rule") {
@@ -10869,9 +10915,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
10869
10915
 
10870
10916
  // src/init/project.ts
10871
10917
  import { lstat as lstat4, mkdir as mkdir9, readdir as readdir4, readFile as readFile9, realpath as realpath7, writeFile as writeFile8 } from "fs/promises";
10872
- import { copyFileSync, mkdirSync as mkdirSync2, readdirSync } from "fs";
10873
- import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute8, join as join13, relative as relative8, sep as sep8 } from "path";
10874
- import { fileURLToPath } from "url";
10918
+ import { basename as basename4, isAbsolute as isAbsolute8, join as join13, relative as relative8, sep as sep8 } from "path";
10875
10919
  var GENERATED_START = "<!-- flavor-code:start -->";
10876
10920
  var GENERATED_END = "<!-- flavor-code:end -->";
10877
10921
  var MAX_MANIFEST_BYTES2 = 1e6;
@@ -11322,29 +11366,8 @@ async function createExampleFlavorConfig(cwd) {
11322
11366
  };
11323
11367
  await writeFile8(configPath, JSON.stringify(example, null, 2) + "\n");
11324
11368
  }
11325
- var ASTGRAPH_SRC_DIR = join13(dirname9(fileURLToPath(import.meta.url)), "astgraph");
11326
- async function copyAstgraphPlugin(cwd) {
11327
- const pluginDir = join13(cwd, ".flavor", "plugins", "astgraph");
11328
- try {
11329
- await lstat4(pluginDir);
11330
- return;
11331
- } catch (error) {
11332
- if (!isMissingPathError(error)) throw error;
11333
- }
11334
- copyDirectory(ASTGRAPH_SRC_DIR, pluginDir);
11335
- }
11336
- function copyDirectory(from, to) {
11337
- mkdirSync2(to, { recursive: true, mode: 448 });
11338
- for (const entry2 of readdirSync(from, { withFileTypes: true })) {
11339
- const source = join13(from, entry2.name);
11340
- const target = join13(to, entry2.name);
11341
- if (entry2.isDirectory()) copyDirectory(source, target);
11342
- else if (entry2.isFile()) copyFileSync(source, target);
11343
- }
11344
- }
11345
11369
  async function initializeFlavor(cwd) {
11346
11370
  await ensureFlavorDirectories(cwd);
11347
- await copyAstgraphPlugin(cwd);
11348
11371
  await createExampleFlavorConfig(cwd);
11349
11372
  const facts = await inspectProject(cwd);
11350
11373
  const path = join13(cwd, "FLAVOR.md");
@@ -12439,7 +12462,7 @@ function formatPriorGaps(gaps) {
12439
12462
  import { constants as constants4 } from "fs";
12440
12463
  import { randomUUID as randomUUID11 } from "crypto";
12441
12464
  import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
12442
- import { dirname as dirname10, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
12465
+ import { dirname as dirname9, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
12443
12466
  var DEFAULT_MAX_BYTES = 1024 * 1024;
12444
12467
  var GoalStore = class {
12445
12468
  #workspace;
@@ -12452,7 +12475,7 @@ var GoalStore = class {
12452
12475
  }
12453
12476
  async save(input) {
12454
12477
  const state = GoalStateSchema.parse(input);
12455
- await assertNoSymlink(this.#workspace, dirname10(this.#root));
12478
+ await assertNoSymlink(this.#workspace, dirname9(this.#root));
12456
12479
  await mkdir11(this.#root, { recursive: true, mode: 448 });
12457
12480
  await assertNoSymlink(this.#workspace, this.#root);
12458
12481
  const target = join16(this.#root, `${state.id}.json`);
@@ -12486,7 +12509,7 @@ async function assertNoSymlink(root, target) {
12486
12509
  const chain = [];
12487
12510
  while (cursor !== stop) {
12488
12511
  chain.push(cursor);
12489
- cursor = dirname10(cursor);
12512
+ cursor = dirname9(cursor);
12490
12513
  }
12491
12514
  for (const path of chain.reverse()) {
12492
12515
  try {
@@ -12573,7 +12596,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
12573
12596
  // src/loop/store.ts
12574
12597
  import { constants as constants5 } from "fs";
12575
12598
  import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile12, realpath as realpath8, rename as rename5, rm as rm7, stat as stat6 } from "fs/promises";
12576
- import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
12599
+ import { dirname as dirname10, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
12577
12600
  import { randomUUID as randomUUID12 } from "crypto";
12578
12601
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
12579
12602
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
@@ -12655,7 +12678,7 @@ var LoopStore = class {
12655
12678
  }
12656
12679
  async #prepare(loopId) {
12657
12680
  validateId(loopId);
12658
- await assertNoSymlink2(this.#workspace, dirname11(this.#root));
12681
+ await assertNoSymlink2(this.#workspace, dirname10(this.#root));
12659
12682
  await mkdir13(this.#root, { recursive: true, mode: 448 });
12660
12683
  await assertNoSymlink2(this.#workspace, this.#root);
12661
12684
  const directory = join18(this.#root, loopId);
@@ -12688,7 +12711,7 @@ async function assertNoSymlink2(root, target) {
12688
12711
  const chain = [];
12689
12712
  while (cursor !== stop) {
12690
12713
  chain.push(cursor);
12691
- cursor = dirname11(cursor);
12714
+ cursor = dirname10(cursor);
12692
12715
  }
12693
12716
  for (const path of chain.reverse()) {
12694
12717
  try {
@@ -12718,7 +12741,7 @@ import { readFile as readFile13 } from "fs/promises";
12718
12741
  import { join as join20 } from "path";
12719
12742
 
12720
12743
  // src/tools/shell.ts
12721
- import { spawn as spawn3 } from "child_process";
12744
+ import { spawn as spawn4 } from "child_process";
12722
12745
  import { existsSync as existsSync4 } from "fs";
12723
12746
  import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as join19, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
12724
12747
  import { z as z23 } from "zod";
@@ -12761,7 +12784,7 @@ function createJobTools(registry) {
12761
12784
  inputSchema: IdInput,
12762
12785
  paths: () => [],
12763
12786
  execute: async (input, _signal, context) => {
12764
- registry.kill(input.id, owner(context));
12787
+ await registry.kill(input.id, owner(context));
12765
12788
  return registry.read(input.id, owner(context));
12766
12789
  },
12767
12790
  presentResult: (output) => jobPresentation("kill", output)
@@ -12801,11 +12824,51 @@ function owner(context) {
12801
12824
  return context?.ownerId ?? context?.agent ?? "main";
12802
12825
  }
12803
12826
 
12827
+ // src/utils/process-tree.ts
12828
+ import { spawn as spawn3 } from "child_process";
12829
+ async function terminateProcessTree3(pid) {
12830
+ if (pid === void 0) return;
12831
+ if (process.platform === "win32") {
12832
+ await new Promise((resolve44) => {
12833
+ const child = spawn3("taskkill", ["/pid", String(pid), "/T", "/F"], { shell: false, windowsHide: true });
12834
+ const finish = () => {
12835
+ clearTimeout(timer);
12836
+ resolve44();
12837
+ };
12838
+ const timer = setTimeout(finish, 5e3);
12839
+ timer.unref();
12840
+ child.once("error", finish);
12841
+ child.once("close", finish);
12842
+ });
12843
+ try {
12844
+ process.kill(pid);
12845
+ } catch {
12846
+ }
12847
+ } else {
12848
+ try {
12849
+ process.kill(-pid, "SIGTERM");
12850
+ } catch {
12851
+ try {
12852
+ process.kill(pid, "SIGTERM");
12853
+ } catch {
12854
+ }
12855
+ }
12856
+ await new Promise((resolve44) => setTimeout(resolve44, 250));
12857
+ try {
12858
+ process.kill(-pid, "SIGKILL");
12859
+ } catch {
12860
+ try {
12861
+ process.kill(pid, "SIGKILL");
12862
+ } catch {
12863
+ }
12864
+ }
12865
+ }
12866
+ }
12867
+
12804
12868
  // src/tools/shell.ts
12805
12869
  var DEFAULT_MAX_OUTPUT_BYTES2 = 1048576;
12806
12870
  var DEFAULT_SHELL_TIMEOUT_MS = 10 * 6e4;
12807
12871
  var ELLIPSIS = "\u2026";
12808
- var TERMINATION_GRACE_MS = 250;
12809
12872
  var TERMINATION_FAILURE_MS = 5e3;
12810
12873
  var STREAM_CLOSE_GRACE_MS = 500;
12811
12874
  var ShellInput = z23.object({
@@ -12824,12 +12887,6 @@ function normalizeShellCommand(input) {
12824
12887
  if ((executable === "cmd" || executable === "cmd.exe") && /^\/[ck]$/iu.test(args[0] ?? "")) {
12825
12888
  return { command, args: [args[0], ...args.slice(1).map(unquoteToken)] };
12826
12889
  }
12827
- if ((executable === "pwsh" || executable === "pwsh.exe" || executable === "powershell" || executable === "powershell.exe") && /^(?:-c|-command)$/iu.test(args[0] ?? "") && args.length > 1) {
12828
- return { command, args: [args[0], ...args.slice(1).map(unquoteToken)] };
12829
- }
12830
- if (/^(?:sh|bash|zsh|fish)$/iu.test(executable) && args[0] === "-c" && args[1] !== void 0) {
12831
- return { command, args: [args[0], unquoteToken(args[1]), ...args.slice(2)] };
12832
- }
12833
12890
  if (!/\s/u.test(command) || containsShellSyntax(command)) return { command, args };
12834
12891
  const tokens2 = tokenizeCommandLine(command);
12835
12892
  if (tokens2.length <= 1) return { command, args };
@@ -12947,7 +13004,8 @@ function shellInvocation(input, cwd, environment) {
12947
13004
  const payload = serializeCmdPayload(input.args.slice(commandFlag + 1));
12948
13005
  return {
12949
13006
  command: resolved,
12950
- args: [...input.args.slice(0, commandFlag + 1), `chcp 65001>nul & ${payload}`]
13007
+ args: [...input.args.slice(0, commandFlag + 1), `chcp 65001>nul & ${payload}`],
13008
+ windowsVerbatimArguments: true
12951
13009
  };
12952
13010
  }
12953
13011
  }
@@ -12963,7 +13021,8 @@ function shellInvocation(input, cwd, environment) {
12963
13021
  if (shell2.kind === "posix") return { command: shell2.command, args: ["-c", script] };
12964
13022
  if (shell2.kind === "cmd") return {
12965
13023
  command: shell2.command,
12966
- args: ["/d", "/s", "/c", `chcp 65001>nul & ${script}`]
13024
+ args: ["/d", "/s", "/c", `chcp 65001>nul & ${script}`],
13025
+ windowsVerbatimArguments: true
12967
13026
  };
12968
13027
  const encoded = Buffer.from(script, "utf16le").toString("base64");
12969
13028
  return {
@@ -12986,8 +13045,8 @@ function executionEnvironmentCommand(environment, command, args) {
12986
13045
  function structuredShellScript(shell2, command, args) {
12987
13046
  if (shell2.kind === "posix") return [command, ...args].map(posixQuote).join(" ");
12988
13047
  if (shell2.kind === "cmd") return serializeCmdCommand([command, ...args]);
12989
- const invocation = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map(psQuote).join(" ")}` : ""}`;
12990
- return `$ErrorActionPreference='Stop'; try { ${invocation}; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE } else { exit 0 } } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 127 }`;
13048
+ const invocation = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map((arg) => /^-[A-Za-z][A-Za-z0-9-]*$/u.test(arg) ? arg : psQuote(arg)).join(" ")}` : ""}`;
13049
+ return `$ErrorActionPreference='Stop'; try { ${invocation}; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE } else { exit 0 } } catch { [Console]::Error.WriteLine($_.Exception.Message); if ($_.Exception -is [System.Management.Automation.CommandNotFoundException]) { exit 127 } else { exit 1 } }`;
12991
13050
  }
12992
13051
  function posixQuote(value) {
12993
13052
  return `'${value.replace(/'/gu, `'"'"'`)}'`;
@@ -13020,7 +13079,7 @@ ${result.stderr}`;
13020
13079
  return {
13021
13080
  kind: "command-not-found",
13022
13081
  message: `Executable "${command}" was not found by the selected runtime shell.`,
13023
- hint: "Use a command available on PATH, or pass an existing executable path without surrounding quotes."
13082
+ hint: "Use a command available on PATH, or pass an existing executable path without surrounding quotes. For an unavailable PowerShell cmdlet, check Get-Command and its module in the selected shell; do not keep retrying the same command."
13024
13083
  };
13025
13084
  }
13026
13085
  if (code === "EACCES" || /(?:permission denied|access is denied|拒绝访问)/iu.test(detail)) {
@@ -13029,11 +13088,11 @@ ${result.stderr}`;
13029
13088
  if (/(?:no such file or directory|cannot find (?:the )?path|system cannot find|系统找不到指定的路径)/iu.test(detail)) {
13030
13089
  return { kind: "path-not-found", message: "The command referenced a path that does not exist." };
13031
13090
  }
13032
- if (/(?:syntax error|unexpected token|parsererror|语法错误)/iu.test(detail)) {
13091
+ if (/(?:syntax\s*error|unexpected token|parsererror|unterminated (?:regexp|string)|语法错误)/iu.test(detail)) {
13033
13092
  return {
13034
13093
  kind: "shell-syntax",
13035
- message: "The selected runtime shell rejected the command syntax.",
13036
- hint: "Pass normal arguments through args; reserve shell syntax for pipes, redirects, and operators."
13094
+ message: "The command or interpreter rejected the syntax.",
13095
+ hint: 'Pass normal arguments through args. For JavaScript use command="node", args=["-e", "<complete script>"] without an extra shell or surrounding quotes. Legacy Windows PowerShell can strip quotes when forwarding native arguments.'
13037
13096
  };
13038
13097
  }
13039
13098
  return {
@@ -13055,7 +13114,7 @@ function createShellTool(workspace, options = {}) {
13055
13114
  }
13056
13115
  return {
13057
13116
  name: "Shell",
13058
- description: "Run a bounded command reliably inside the workspace. Pass a bare executable in command and one unquoted logical value per args entry; shell syntax is supported only when command contains pipes, redirects, or operators. stdout/stderr are returned verbatim and failures include a structured diagnostic. background=true returns a managed job id.",
13117
+ description: 'Run a bounded command reliably inside the workspace. Pass a bare executable in command and one logical value per args entry, without adding surrounding shell quotes. For Node scripts use command="node", args=["-e", "<complete JavaScript>"]; do not wrap native commands in cmd /c or PowerShell -Command unless shell syntax is needed, since legacy PowerShell can strip embedded quotes. Use Grep for text searches and Read/Edit for file changes. stdout/stderr are returned verbatim and failures include a structured diagnostic. background=true returns a managed job id.',
13059
13118
  inputSchema: ShellInput,
13060
13119
  paths: (input) => [workingDirectory(root, input.cwd ?? void 0)],
13061
13120
  summarize: (input) => [input.command, ...input.args].join(" "),
@@ -13155,7 +13214,7 @@ async function executeShell(root, input, cancellation, maxBytes, environment = p
13155
13214
  return;
13156
13215
  }
13157
13216
  const invocation = shellInvocation(input, cwd, environment);
13158
- const child = spawn3(invocation.command, invocation.args, {
13217
+ const child = spawn4(invocation.command, invocation.args, {
13159
13218
  cwd,
13160
13219
  shell: false,
13161
13220
  windowsHide: true,
@@ -13182,7 +13241,7 @@ async function executeShell(root, input, cancellation, maxBytes, environment = p
13182
13241
  void finishResolve(observedExitCode ?? child.exitCode, observedSignal ?? child.signalCode);
13183
13242
  return;
13184
13243
  }
13185
- termination = terminateTree(child.pid);
13244
+ termination = terminateProcessTree3(child.pid);
13186
13245
  terminalTimer = setTimeout(() => finishReject(new Error(`Process did not close after ${reason} termination`)), TERMINATION_FAILURE_MS);
13187
13246
  terminalTimer.unref();
13188
13247
  };
@@ -13342,52 +13401,6 @@ function workingDirectory(root, cwd = ".") {
13342
13401
  if (delta === ".." || delta.startsWith(`..${sep11}`) || isAbsolute9(delta)) throw new Error("Working directory is outside the workspace");
13343
13402
  return candidate;
13344
13403
  }
13345
- async function terminateTree(pid) {
13346
- if (pid === void 0) return;
13347
- if (process.platform === "win32") {
13348
- await waitForProcess(spawn3("taskkill", ["/pid", String(pid), "/T", "/F"], { shell: false, windowsHide: true }));
13349
- try {
13350
- process.kill(pid);
13351
- } catch {
13352
- }
13353
- } else {
13354
- try {
13355
- process.kill(-pid, "SIGTERM");
13356
- } catch {
13357
- try {
13358
- process.kill(pid, "SIGTERM");
13359
- } catch {
13360
- }
13361
- }
13362
- await delay2(TERMINATION_GRACE_MS);
13363
- try {
13364
- process.kill(-pid, "SIGKILL");
13365
- } catch {
13366
- try {
13367
- process.kill(pid, "SIGKILL");
13368
- } catch {
13369
- }
13370
- }
13371
- }
13372
- }
13373
- function waitForProcess(child) {
13374
- return new Promise((resolvePromise) => {
13375
- let settled = false;
13376
- const finish = () => {
13377
- if (settled) return;
13378
- settled = true;
13379
- clearTimeout(timer);
13380
- resolvePromise();
13381
- };
13382
- const timer = setTimeout(finish, TERMINATION_FAILURE_MS);
13383
- timer.unref();
13384
- child.once("error", finish);
13385
- child.once("close", finish);
13386
- });
13387
- }
13388
- function delay2(milliseconds) {
13389
- return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
13390
- }
13391
13404
  function decodeOutput(buffer) {
13392
13405
  const encoding = outputEncoding(buffer);
13393
13406
  return new TextDecoder(encoding).decode(buffer);
@@ -13639,7 +13652,7 @@ function isEnvTruthy(value) {
13639
13652
 
13640
13653
  // src/utils/log.ts
13641
13654
  import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
13642
- import { dirname as dirname12, join as join21, resolve as resolve16 } from "path";
13655
+ import { dirname as dirname11, join as join21, resolve as resolve16 } from "path";
13643
13656
  var pending = Promise.resolve();
13644
13657
  var usagePending = Promise.resolve();
13645
13658
  function usageLogPath() {
@@ -13658,7 +13671,7 @@ async function appendUsageLog(line) {
13658
13671
  usagePending = usagePending.catch(() => void 0).then(async () => {
13659
13672
  try {
13660
13673
  const path = usageLogPath();
13661
- await mkdir14(dirname12(path), { recursive: true, mode: 448 });
13674
+ await mkdir14(dirname11(path), { recursive: true, mode: 448 });
13662
13675
  const flag = truncateOnNextAppend ? "w" : "a";
13663
13676
  truncateOnNextAppend = false;
13664
13677
  await appendFile3(path, `${line}
@@ -13686,7 +13699,7 @@ var AuditLogger = class {
13686
13699
  }
13687
13700
  async #write(line) {
13688
13701
  try {
13689
- await mkdir14(dirname12(this.#path), { recursive: true, mode: 448 });
13702
+ await mkdir14(dirname11(this.#path), { recursive: true, mode: 448 });
13690
13703
  await appendFile3(this.#path, line, { encoding: "utf8", mode: 384, flag: "a" });
13691
13704
  } catch {
13692
13705
  }
@@ -14546,12 +14559,12 @@ import {
14546
14559
 
14547
14560
  // src/utils/version.ts
14548
14561
  import { readFileSync as readFileSync2 } from "fs";
14549
- import { dirname as dirname13, join as join22 } from "path";
14550
- import { fileURLToPath as fileURLToPath2 } from "url";
14562
+ import { dirname as dirname12, join as join22 } from "path";
14563
+ import { fileURLToPath } from "url";
14551
14564
  var cached;
14552
14565
  function packageVersion() {
14553
14566
  if (cached !== void 0) return cached;
14554
- let directory = dirname13(fileURLToPath2(import.meta.url));
14567
+ let directory = dirname12(fileURLToPath(import.meta.url));
14555
14568
  for (; ; ) {
14556
14569
  try {
14557
14570
  const manifest = JSON.parse(readFileSync2(join22(directory, "package.json"), "utf8"));
@@ -14561,7 +14574,7 @@ function packageVersion() {
14561
14574
  }
14562
14575
  } catch {
14563
14576
  }
14564
- const parent = dirname13(directory);
14577
+ const parent = dirname12(directory);
14565
14578
  if (parent === directory) break;
14566
14579
  directory = parent;
14567
14580
  }
@@ -14787,8 +14800,8 @@ var OAuthCallbackAuthProvider = class {
14787
14800
  async #startCallbackServer(expectedState, signal) {
14788
14801
  let serverResolve;
14789
14802
  let serverReject;
14790
- const codePromise = new Promise((resolve43, reject) => {
14791
- serverResolve = resolve43;
14803
+ const codePromise = new Promise((resolve44, reject) => {
14804
+ serverResolve = resolve44;
14792
14805
  serverReject = reject;
14793
14806
  });
14794
14807
  const server = createServer3((req, res) => {
@@ -14822,7 +14835,7 @@ var OAuthCallbackAuthProvider = class {
14822
14835
  );
14823
14836
  serverResolve(code);
14824
14837
  });
14825
- return new Promise((resolve43, reject) => {
14838
+ return new Promise((resolve44, reject) => {
14826
14839
  server.once("error", (error) => {
14827
14840
  reject(new Error(`OAuth callback server failed: ${error.message}`));
14828
14841
  });
@@ -14839,7 +14852,7 @@ var OAuthCallbackAuthProvider = class {
14839
14852
  signal.addEventListener("abort", () => {
14840
14853
  serverReject(new Error("OAuth flow aborted"));
14841
14854
  }, { once: true });
14842
- resolve43({ redirectUri, promise: codePromise });
14855
+ resolve44({ redirectUri, promise: codePromise });
14843
14856
  });
14844
14857
  });
14845
14858
  }
@@ -14921,7 +14934,7 @@ function escapeHtml(text) {
14921
14934
  }
14922
14935
 
14923
14936
  // src/auth/store.ts
14924
- import { dirname as dirname14 } from "path";
14937
+ import { dirname as dirname13 } from "path";
14925
14938
  async function retainOnlyCredentials(store, keepCredentialId) {
14926
14939
  const tokens2 = await store.load();
14927
14940
  const retained = Object.fromEntries(Object.entries(tokens2).filter(([id]) => id === keepCredentialId));
@@ -14932,7 +14945,7 @@ function createFileTokenStore(filePath) {
14932
14945
  const store = {
14933
14946
  tokens: {},
14934
14947
  async load() {
14935
- const key = await loadOrCreateConfigKey(dirname14(filePath));
14948
+ const key = await loadOrCreateConfigKey(dirname13(filePath));
14936
14949
  const result = await readRecoverableFile(filePath, (raw) => parseTokens(decryptDocument(raw, key)));
14937
14950
  const tokens2 = result?.value ?? {};
14938
14951
  baseline = { ...tokens2 };
@@ -14940,7 +14953,7 @@ function createFileTokenStore(filePath) {
14940
14953
  return tokens2;
14941
14954
  },
14942
14955
  async save(tokens2) {
14943
- const key = await loadOrCreateConfigKey(dirname14(filePath));
14956
+ const key = await loadOrCreateConfigKey(dirname13(filePath));
14944
14957
  const removals = Object.keys(baseline).filter((providerId) => !(providerId in tokens2));
14945
14958
  const upserts = Object.fromEntries(Object.entries(tokens2).filter(
14946
14959
  ([providerId, token]) => !sameToken(baseline[providerId], token)
@@ -15769,10 +15782,14 @@ function createSkillResourceTool(registry) {
15769
15782
  name: "SkillResource",
15770
15783
  description: "Read a bounded resource explicitly referenced by a discovered skill; scripts are returned as data and never executed",
15771
15784
  inputSchema: SkillResourceInput,
15785
+ readOnly: true,
15772
15786
  paths: () => [],
15773
15787
  execute: async (input, signal) => {
15774
15788
  signal.throwIfAborted();
15775
- const skill = (await registry.discover()).find((candidate) => candidate.name === input.skill);
15789
+ const requested = input.skill.trim();
15790
+ const alias = requested.includes(":") ? requested.slice(requested.lastIndexOf(":") + 1) : requested;
15791
+ const discovered = await registry.discover();
15792
+ const skill = discovered.find((candidate) => candidate.name === requested) ?? discovered.find((candidate) => candidate.name === alias);
15776
15793
  if (skill === void 0) throw new Error(`Unknown skill: ${input.skill}`);
15777
15794
  const capability = await registry.resolveResource(skill, input.reference);
15778
15795
  const content = await registry.readResource(capability);
@@ -15790,7 +15807,7 @@ function createSkillResourceTool(registry) {
15790
15807
  // src/session/store.ts
15791
15808
  import { constants as constants7 } from "fs";
15792
15809
  import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile17, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat7 } from "fs/promises";
15793
- import { basename as basename6, dirname as dirname15, join as join24, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
15810
+ import { basename as basename6, dirname as dirname14, join as join24, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
15794
15811
  import { randomUUID as randomUUID13 } from "crypto";
15795
15812
  import { z as z27 } from "zod";
15796
15813
 
@@ -16712,7 +16729,7 @@ var SessionStore = class {
16712
16729
  }
16713
16730
  }
16714
16731
  async #prepareDirectory() {
16715
- await assertNoSymlink3(this.#workspace, dirname15(this.#sessions));
16732
+ await assertNoSymlink3(this.#workspace, dirname14(this.#sessions));
16716
16733
  await mkdir15(this.#sessions, { recursive: true, mode: 448 });
16717
16734
  await this.#assertSafeExistingDirectory();
16718
16735
  }
@@ -16940,7 +16957,7 @@ async function assertNoSymlink3(root, target) {
16940
16957
  const chain = [];
16941
16958
  while (cursor !== stop) {
16942
16959
  chain.push(cursor);
16943
- cursor = dirname15(cursor);
16960
+ cursor = dirname14(cursor);
16944
16961
  }
16945
16962
  for (const path of chain.reverse()) {
16946
16963
  try {
@@ -16985,13 +17002,13 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
16985
17002
  // src/session/tree.ts
16986
17003
  import { randomUUID as randomUUID15 } from "crypto";
16987
17004
  import { mkdir as mkdir17, readFile as readFile19, rename as rename8, writeFile as writeFile11 } from "fs/promises";
16988
- import { dirname as dirname17, join as join26, resolve as resolve22 } from "path";
17005
+ import { dirname as dirname16, join as join26, resolve as resolve22 } from "path";
16989
17006
 
16990
17007
  // src/session/checkpoint.ts
16991
17008
  import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
16992
17009
  import { execFile as execFile2 } from "child_process";
16993
17010
  import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile18, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
16994
- import { dirname as dirname16, join as join25, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
17011
+ import { dirname as dirname15, join as join25, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
16995
17012
  var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
16996
17013
  var MAX_FILE_BYTES = 16 * 1024 * 1024;
16997
17014
  var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
@@ -17077,7 +17094,7 @@ var WorkspaceCheckpointStore = class {
17077
17094
  for (const file of checkpoint.files) {
17078
17095
  const target = contained(this.#workspace, file.path);
17079
17096
  const object = objects.get(file.digest);
17080
- await mkdir16(dirname16(target), { recursive: true });
17097
+ await mkdir16(dirname15(target), { recursive: true });
17081
17098
  const temporary = `${target}.${process.pid}.${randomUUID14()}.tmp`;
17082
17099
  await writeFile10(temporary, object, { mode: file.mode });
17083
17100
  await rename7(temporary, target);
@@ -17149,7 +17166,7 @@ function contained(workspace, path) {
17149
17166
  return absolute;
17150
17167
  }
17151
17168
  async function atomicJson(path, value) {
17152
- await mkdir16(dirname16(path), { recursive: true });
17169
+ await mkdir16(dirname15(path), { recursive: true });
17153
17170
  const temporary = `${path}.${process.pid}.${randomUUID14()}.tmp`;
17154
17171
  await writeFile10(temporary, `${JSON.stringify(value)}
17155
17172
  `, { encoding: "utf8", mode: 384 });
@@ -17264,7 +17281,7 @@ var SessionHistory = class _SessionHistory {
17264
17281
  leafId: this.#leafId,
17265
17282
  nodes: this.#nodes
17266
17283
  };
17267
- await mkdir17(dirname17(this.#path), { recursive: true });
17284
+ await mkdir17(dirname16(this.#path), { recursive: true });
17268
17285
  const temporary = `${this.#path}.${process.pid}.${randomUUID15()}.tmp`;
17269
17286
  await writeFile11(temporary, `${JSON.stringify(document)}
17270
17287
  `, { encoding: "utf8", mode: 384 });
@@ -17563,14 +17580,14 @@ var ProjectSleepScheduler = class {
17563
17580
  const next = new Date(now);
17564
17581
  next.setDate(next.getDate() + 1);
17565
17582
  next.setHours(0, 0, 0, 0);
17566
- const delay4 = Math.max(1, next.getTime() - now.getTime());
17583
+ const delay3 = Math.max(1, next.getTime() - now.getTime());
17567
17584
  const targetDate = localDateKey(now);
17568
17585
  this.#dailyTimer = setTimeout(() => {
17569
17586
  this.#dailyTimer = void 0;
17570
17587
  if (this.#disposed) return;
17571
17588
  this.#enqueue(targetDate);
17572
17589
  this.#scheduleNextMidnight();
17573
- }, delay4);
17590
+ }, delay3);
17574
17591
  this.#dailyTimer.unref?.();
17575
17592
  }
17576
17593
  #enqueue(date) {
@@ -17865,7 +17882,7 @@ function computeSessionStats(documents) {
17865
17882
  // src/tools/files.ts
17866
17883
  import { constants as constants9 } from "fs";
17867
17884
  import { mkdir as mkdir19, open as open10, readFile as readFile20, realpath as realpath12, rename as rename10, rm as rm11, stat as stat8 } from "fs/promises";
17868
- import { dirname as dirname18, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
17885
+ import { dirname as dirname17, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
17869
17886
  import { randomUUID as randomUUID17 } from "crypto";
17870
17887
  import { z as z29 } from "zod";
17871
17888
 
@@ -17983,8 +18000,12 @@ var ReadInput2 = z29.object({
17983
18000
  force: z29.union([z29.boolean(), z29.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
17984
18001
  });
17985
18002
  var WriteInput = z29.object({ path: z29.string().min(1), content: z29.string() });
17986
- var EditInput = z29.object({ path: z29.string().min(1), oldText: z29.string().min(1), newText: z29.string() });
17987
- var ApplyPatchInput = z29.object({ patch: z29.string().min(1) });
18003
+ var EditInput = z29.object({
18004
+ path: z29.string().min(1),
18005
+ oldText: z29.string().min(1).describe("Copy exact current file text from Read, without displayed line numbers or diff prefixes. Include enough context for one unique match."),
18006
+ newText: z29.string()
18007
+ });
18008
+ var ApplyPatchInput = z29.object({ patch: z29.string().min(1).describe("One-file unified diff with --- a/path, +++ b/path and @@ hunk headers. Use /dev/null as the old path to create a file.") });
17988
18009
  var FileObservationStore = class {
17989
18010
  #versions = /* @__PURE__ */ new Map();
17990
18011
  get(path) {
@@ -18027,9 +18048,9 @@ function createReadTool(workspace, options = {}) {
18027
18048
  const windowBytes = rangeRequested ? Math.max(maxBytes, Math.min(info.size, MAX_READ_BYTES)) : maxBytes;
18028
18049
  const contents = await readBounded2(path, windowBytes, signal, openFile2);
18029
18050
  if (isBinary(contents)) throw new Error("Cannot read binary file as text");
18030
- const textEnd = utf8SafeEnd(contents, windowBytes);
18051
+ const textEnd = contents.length > windowBytes ? utf8SafeEnd(contents, windowBytes) : contents.length;
18031
18052
  const truncated = contents.length > windowBytes;
18032
- const text = contents.subarray(0, textEnd).toString("utf8").replaceAll("\r\n", "\n");
18053
+ const text = decodeFileText(contents.subarray(0, textEnd)).replaceAll("\r\n", "\n");
18033
18054
  options.observations?.set(path, versionOfStat(info));
18034
18055
  const lines = text.split("\n");
18035
18056
  if (lines.at(-1) === "") lines.pop();
@@ -18105,7 +18126,7 @@ function createEditTool(workspace, options = {}) {
18105
18126
  const guard = createPathGuard(workspace);
18106
18127
  return {
18107
18128
  name: "Edit",
18108
- description: "Replace one unique exact text match",
18129
+ description: "Replace one unique exact text match, accepting LF or CRLF input. Read the file before editing; after a mismatch, re-read with force=true and copy the current text instead of retrying the same oldText.",
18109
18130
  inputSchema: EditInput,
18110
18131
  paths: (input) => [guard.lexical(input.path)],
18111
18132
  execute: async (input, signal) => {
@@ -18115,14 +18136,14 @@ function createEditTool(workspace, options = {}) {
18115
18136
  const expected = await expectedMutationVersion(path, options.observations);
18116
18137
  const contents = await readText(path);
18117
18138
  const hasCRLF = contents.includes("\r\n");
18118
- const norm = (s) => hasCRLF ? s.replace(/\r\n/g, "\n") : s;
18139
+ const norm = (s) => s.replace(/\r\n/g, "\n");
18119
18140
  const contentsLF = norm(contents);
18120
18141
  const oldTextLF = norm(input.oldText);
18121
18142
  const newTextLF = norm(input.newText);
18122
18143
  const first = contentsLF.indexOf(oldTextLF);
18123
- const second = first < 0 ? -1 : contentsLF.indexOf(oldTextLF, first + oldTextLF.length);
18144
+ const second = first < 0 ? -1 : contentsLF.indexOf(oldTextLF, first + 1);
18124
18145
  if (first < 0 || second >= 0) {
18125
- const diagnosis = buildEditDiagnosis(contentsLF, oldTextLF, first, second);
18146
+ const diagnosis = buildEditDiagnosis(contentsLF, oldTextLF, second);
18126
18147
  throw new Error(diagnosis);
18127
18148
  }
18128
18149
  const updatedLF = contentsLF.slice(0, first) + newTextLF + contentsLF.slice(first + oldTextLF.length);
@@ -18142,7 +18163,7 @@ function createApplyPatchTool(workspace, options = {}) {
18142
18163
  const guard = createPathGuard(workspace);
18143
18164
  return {
18144
18165
  name: "ApplyPatch",
18145
- description: "Apply a workspace-limited unified diff, relocating hunks only by unique exact context",
18166
+ description: "Apply a single-file workspace-limited unified diff, relocating hunks only by unique exact context. Submit separate calls for multiple files.",
18146
18167
  inputSchema: ApplyPatchInput,
18147
18168
  paths: (input) => parsePatch(input.patch).map((file) => guard.lexical(file.path)),
18148
18169
  execute: async (input, signal) => {
@@ -18207,7 +18228,7 @@ function createPathGuard(workspace) {
18207
18228
  break;
18208
18229
  } catch (error) {
18209
18230
  if (!isMissing2(error)) throw error;
18210
- const parent = dirname18(ancestor);
18231
+ const parent = dirname17(ancestor);
18211
18232
  if (parent === ancestor) throw error;
18212
18233
  ancestor = parent;
18213
18234
  }
@@ -18218,12 +18239,19 @@ function createPathGuard(workspace) {
18218
18239
  }
18219
18240
  async function atomicWrite(path, content, signal, expectedVersion) {
18220
18241
  abortIfNeeded(signal);
18221
- await mkdir19(dirname18(path), { recursive: true });
18242
+ await mkdir19(dirname17(path), { recursive: true });
18222
18243
  const temporary = `${path}.${randomUUID17()}.tmp`;
18244
+ let mode;
18245
+ try {
18246
+ mode = (await stat8(path)).mode & 511;
18247
+ } catch (error) {
18248
+ if (!isMissing2(error)) throw error;
18249
+ }
18223
18250
  try {
18224
18251
  const handle = await open10(temporary, constants9.O_CREAT | constants9.O_EXCL | constants9.O_WRONLY, 384);
18225
18252
  try {
18226
18253
  await handle.writeFile(content, "utf8");
18254
+ if (mode !== void 0) await handle.chmod(mode);
18227
18255
  await handle.sync();
18228
18256
  } finally {
18229
18257
  await handle.close();
@@ -18240,7 +18268,14 @@ async function atomicWrite(path, content, signal, expectedVersion) {
18240
18268
  async function readText(path) {
18241
18269
  const contents = await readFile20(path);
18242
18270
  if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
18243
- return contents.toString("utf8");
18271
+ return decodeFileText(contents);
18272
+ }
18273
+ function decodeFileText(contents) {
18274
+ try {
18275
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(contents);
18276
+ } catch {
18277
+ throw new Error("File is not valid UTF-8 text. Convert its encoding before using Read/Edit/ApplyPatch to avoid corrupting its contents.");
18278
+ }
18244
18279
  }
18245
18280
  var ABSENT_VERSION = "absent";
18246
18281
  async function expectedMutationVersion(path, observations) {
@@ -18450,11 +18485,12 @@ function patchPath(header) {
18450
18485
  const raw = header.split(" ", 1)[0].trim();
18451
18486
  return raw.startsWith("a/") || raw.startsWith("b/") ? raw.slice(2) : raw;
18452
18487
  }
18453
- function buildEditDiagnosis(contentsLF, oldTextLF, first, second) {
18488
+ function buildEditDiagnosis(contentsLF, oldTextLF, second) {
18454
18489
  let diagnosis = "oldText must match exactly once";
18455
18490
  if (second >= 0) {
18456
- const count = contentsLF.split(oldTextLF).length - 1;
18457
- diagnosis += ` \u2014 matched ${count} times in the file`;
18491
+ let count = 0;
18492
+ for (let index = contentsLF.indexOf(oldTextLF); index >= 0; index = contentsLF.indexOf(oldTextLF, index + 1)) count += 1;
18493
+ diagnosis += ` \u2014 matched ${count} times in the file. No changes made. Include more surrounding text to select one occurrence.`;
18458
18494
  return diagnosis;
18459
18495
  }
18460
18496
  const oldLines = oldTextLF.split("\n");
@@ -18490,7 +18526,8 @@ ${snippet}
18490
18526
  First lines of file:
18491
18527
  ${fileSnippet}`;
18492
18528
  }
18493
- return diagnosis;
18529
+ return `${diagnosis}
18530
+ No changes made. Use Read with force=true to retrieve current text, or Grep with fixedStrings=true to locate it. Copy oldText exactly without line-number prefixes; do not repeat the same failed edit.`;
18494
18531
  }
18495
18532
  function applyHunks(original, hunks) {
18496
18533
  const hasCRLF = original.includes("\r\n");
@@ -18576,9 +18613,9 @@ function matchesLines(source, start, expected) {
18576
18613
  }
18577
18614
 
18578
18615
  // src/tools/search.ts
18579
- import { spawn as spawn4 } from "child_process";
18616
+ import { spawn as spawn5 } from "child_process";
18580
18617
  import { open as open11, opendir, stat as stat9 } from "fs/promises";
18581
- import { dirname as dirname19, isAbsolute as isAbsolute13, relative as relative17, resolve as resolve25, sep as sep17 } from "path";
18618
+ import { isAbsolute as isAbsolute13, relative as relative17, resolve as resolve25, sep as sep17 } from "path";
18582
18619
  import { rgPath as bundledRgPath } from "@vscode/ripgrep";
18583
18620
  import createIgnore from "ignore";
18584
18621
  import { z as z30 } from "zod";
@@ -18669,8 +18706,9 @@ var GlobInput = z30.object({
18669
18706
  limit: z30.coerce.number().int().positive().max(1e5).nullable().optional()
18670
18707
  });
18671
18708
  var GrepInput = z30.object({
18672
- pattern: z30.string().min(1),
18673
- path: z30.string().min(1).describe('Workspace-relative search path. Use JSON null, not the string "null", for the workspace root.').nullable().optional(),
18709
+ pattern: z30.string().min(1).describe("Regular expression without / delimiters, or literal text when fixedStrings=true."),
18710
+ fixedStrings: BooleanInput.describe("Match literal text, useful for Markdown links and paths containing regex punctuation.").nullable().optional(),
18711
+ path: z30.string().min(1).describe("Workspace-relative file or directory. A file restricts the search to that file. Use glob for multiple filenames and JSON null for the workspace root.").nullable().optional(),
18674
18712
  glob: z30.string().min(1).nullable().optional(),
18675
18713
  fileType: z30.enum(FILE_TYPES).nullable().optional(),
18676
18714
  includeIgnored: BooleanInput.describe("Include files excluded by .gitignore or .ignore. Git metadata remains excluded.").nullable().optional(),
@@ -18719,7 +18757,7 @@ function createGrepTool(workspace, options = {}) {
18719
18757
  const resources = searchResources(options);
18720
18758
  return {
18721
18759
  name: "Grep",
18722
- description: `Search workspace text with a regular expression. fileType must be one of: ${FILE_TYPES.join(", ")}.`,
18760
+ description: `Search workspace text with a regular expression or fixedStrings=true for literal text. Zero matches is a successful search, not a path error. fileType must be one of: ${FILE_TYPES.join(", ")}.`,
18723
18761
  inputSchema: GrepInput,
18724
18762
  paths: (input) => [scope(root, searchPath(input.path))],
18725
18763
  summarize: (input) => {
@@ -18727,10 +18765,10 @@ function createGrepTool(workspace, options = {}) {
18727
18765
  const where = path ? ` in ${path}` : "";
18728
18766
  const kind = input.fileType ? ` [${input.fileType}]` : "";
18729
18767
  const ignored2 = input.includeIgnored === true ? " (including ignored files)" : "";
18730
- return `pattern: /${input.pattern}/${kind}${where}${ignored2}`;
18768
+ return `pattern: ${input.fixedStrings === true ? JSON.stringify(input.pattern) : `/${input.pattern}/`}${kind}${where}${ignored2}`;
18731
18769
  },
18732
18770
  execute: async (input, signal) => {
18733
- const expression = new RegExp(input.pattern);
18771
+ const expression = new RegExp(input.fixedStrings === true ? input.pattern.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&") : input.pattern);
18734
18772
  const limit = input.limit ?? options.defaultLimit ?? DEFAULT_RESULT_LIMIT;
18735
18773
  const context = input.context ?? 0;
18736
18774
  const start = await resolveGrepPath(root, searchPath(input.path));
@@ -18781,10 +18819,12 @@ async function rgGrep(root, start, input, context, signal, executable, argsPrefi
18781
18819
  }
18782
18820
  const args = ["--json", "--line-number", "--column", "--sort", "path", "--hidden", "--glob", "!.git", "--glob", "!.git/**", "--max-filesize", String(maxFileBytes), "--context", String(context)];
18783
18821
  if (input.includeIgnored === true) args.push("--no-ignore");
18822
+ if (input.fixedStrings === true) args.push("--fixed-strings");
18784
18823
  if (input.glob !== void 0 && input.glob !== null) args.push("--glob", input.glob);
18785
18824
  else for (const extension2 of typeExtensions ?? []) args.push("--glob", `*${extension2}`);
18786
18825
  args.push("--regexp", input.pattern, "--", relative17(root, start) || ".");
18787
18826
  const matches = [];
18827
+ const matcher = input.glob == null ? void 0 : globRegex(input.glob);
18788
18828
  const ignoreLayers = input.includeIgnored === true ? [] : await collectIgnoreLayers(root, start, signal, resources, ignoreBudget);
18789
18829
  const parser = delimitedParser(10, (record3) => {
18790
18830
  if (record3.length === 0) return true;
@@ -18797,6 +18837,7 @@ async function rgGrep(root, start, input, context, signal, executable, argsPrefi
18797
18837
  const lineWithEnding = tryDecodeUtf8(lineValue);
18798
18838
  if (decodedPath === void 0 || lineWithEnding === void 0) return true;
18799
18839
  const path = normalizePath(decodedPath).replace(/^\.\//, "");
18840
+ if (matcher !== void 0 && !matcher.test(path)) return true;
18800
18841
  if (typeExtensions !== void 0 && !typeExtensions.some((extension2) => path.endsWith(extension2))) return true;
18801
18842
  if (ignored(path, false, ignoreLayers)) return true;
18802
18843
  const text = lineWithEnding.replace(/\r?\n$/, "");
@@ -18921,7 +18962,12 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
18921
18962
  }
18922
18963
  ignores.length = ruleCount;
18923
18964
  }
18924
- await walk(start);
18965
+ if ((await stat9(start)).isFile()) {
18966
+ const path = normalizedRelative(root, start);
18967
+ if (!isGitMetadataPath(root, start) && (includeIgnored || !ignored(path, false, ignores)) && accept(path)) output.push(path);
18968
+ } else {
18969
+ await walk(start);
18970
+ }
18925
18971
  return output;
18926
18972
  }
18927
18973
  async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget) {
@@ -18951,7 +18997,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
18951
18997
  if (!ignored(path, true, layers)) await walk(absolute);
18952
18998
  }
18953
18999
  }
18954
- await walk(start);
19000
+ if (!(await stat9(start)).isFile()) await walk(start);
18955
19001
  return layers;
18956
19002
  }
18957
19003
  async function loadIgnoreFiles(directory, base, rules, budget, signal) {
@@ -19071,8 +19117,12 @@ async function resolveGrepPath(root, path = ".") {
19071
19117
  const candidate = scope(root, path);
19072
19118
  try {
19073
19119
  const info = await stat9(candidate);
19074
- if (info.isFile()) return dirname19(candidate);
19075
- } catch {
19120
+ if (!info.isFile() && !info.isDirectory()) throw new Error("Search path must be a file or directory");
19121
+ } catch (error) {
19122
+ if (error.code === "ENOENT") {
19123
+ throw new Error(`Search path does not exist: ${path}. Use Glob to find the file; put filename patterns in glob, not path.`);
19124
+ }
19125
+ throw error;
19076
19126
  }
19077
19127
  return candidate;
19078
19128
  }
@@ -19142,7 +19192,7 @@ function delimitedParser(delimiter4, onRecord) {
19142
19192
  function runStreaming(executable, args, cwd, signal, maxBytes, parser) {
19143
19193
  return new Promise((resolvePromise, reject) => {
19144
19194
  abort(signal);
19145
- const child = spawn4(executable, args, { cwd, shell: false, windowsHide: true });
19195
+ const child = spawn5(executable, args, { cwd, shell: false, windowsHide: true });
19146
19196
  let stdoutBytes = 0;
19147
19197
  let stderrBytes = 0;
19148
19198
  let stderr = Buffer.alloc(0);
@@ -19270,6 +19320,19 @@ import { isIP } from "net";
19270
19320
  import { request as httpRequest } from "http";
19271
19321
  import { request as httpsRequest } from "https";
19272
19322
  import { z as z31 } from "zod";
19323
+
19324
+ // src/utils/abort.ts
19325
+ function waitWithSignal(operation, signal) {
19326
+ if (signal === void 0) return operation;
19327
+ return new Promise((resolve44, reject) => {
19328
+ const abort2 = () => reject(signal.reason);
19329
+ if (signal.aborted) abort2();
19330
+ else signal.addEventListener("abort", abort2, { once: true });
19331
+ operation.then(resolve44, reject).finally(() => signal.removeEventListener("abort", abort2));
19332
+ });
19333
+ }
19334
+
19335
+ // src/tools/web.ts
19273
19336
  var WebFetchInput = z31.object({
19274
19337
  url: z31.string().url().max(8192),
19275
19338
  timeoutMs: z31.coerce.number().int().positive().max(12e4).optional(),
@@ -19315,7 +19378,12 @@ function createWebSearchTool(provider = createDefaultWebSearchProvider()) {
19315
19378
  summarize: (input) => input.query,
19316
19379
  presentCall: (input) => ({ kind: "web", title: `Searching: ${input.query}` }),
19317
19380
  permissions: () => ({ paths: [] }),
19318
- execute: async (input, signal) => ({ query: input.query, results: [...await provider.search(input.query, input.maxResults ?? 8, signal)] }),
19381
+ execute: async (input, signal) => {
19382
+ signal.throwIfAborted();
19383
+ const maxResults = input.maxResults ?? 8;
19384
+ const results = await waitWithSignal(provider.search(input.query, maxResults, signal), signal);
19385
+ return { query: input.query, results: [...results].slice(0, maxResults) };
19386
+ },
19319
19387
  renderForModel: (output) => output.results.length === 0 ? `No web results found for: ${output.query}` : output.results.map((item, index) => `${index + 1}. ${item.title}
19320
19388
  ${item.url}
19321
19389
  ${item.snippet}`).join("\n\n"),
@@ -19360,10 +19428,14 @@ var MultiSourceWebSearchProvider = class {
19360
19428
  }
19361
19429
  };
19362
19430
  async function fetchPublic(initialUrl, options) {
19431
+ options.signal.throwIfAborted();
19432
+ const deadline = AbortSignal.timeout(options.timeoutMs);
19433
+ options = { ...options, signal: AbortSignal.any([options.signal, deadline]) };
19363
19434
  let current = new URL(initialUrl);
19364
19435
  for (let redirects = 0; redirects <= 5; redirects += 1) {
19436
+ options.signal.throwIfAborted();
19365
19437
  validateUrl(current);
19366
- const addresses = await lookup(normalizedHostname(current.hostname), { all: true, verbatim: true });
19438
+ const addresses = await waitWithSignal(lookup(normalizedHostname(current.hostname), { all: true, verbatim: true }), options.signal);
19367
19439
  if (addresses.length === 0) throw new Error("Host did not resolve");
19368
19440
  if (addresses.some((entry2) => isBlockedResolvedAddress(entry2.address, current.hostname))) {
19369
19441
  throw new Error("Blocked private or special network destination");
@@ -19375,7 +19447,13 @@ async function fetchPublic(initialUrl, options) {
19375
19447
  continue;
19376
19448
  }
19377
19449
  const contentType = response2.contentType;
19378
- const decoded = new TextDecoder(charset(contentType), { fatal: false }).decode(response2.body);
19450
+ let decoder;
19451
+ try {
19452
+ decoder = new TextDecoder(charset(contentType), { fatal: false });
19453
+ } catch {
19454
+ decoder = new TextDecoder("utf-8");
19455
+ }
19456
+ const decoded = decoder.decode(response2.body);
19379
19457
  const content = !options.rawHtml && /text\/html|application\/xhtml\+xml/i.test(contentType) ? htmlToText(decoded) : decoded;
19380
19458
  return { url: current.href, status: response2.status, contentType, content, truncated: response2.truncated };
19381
19459
  }
@@ -19410,8 +19488,8 @@ async function requestAddress(url, address, options) {
19410
19488
  const status = response2.statusCode ?? 0;
19411
19489
  const location = response2.headers.location;
19412
19490
  if (location !== void 0 && [301, 302, 303, 307, 308].includes(status)) {
19413
- response2.resume();
19414
19491
  finish({ status, contentType: "", body: new Uint8Array(), truncated: false, redirect: location });
19492
+ response2.destroy();
19415
19493
  return;
19416
19494
  }
19417
19495
  const chunks = [];
@@ -19427,7 +19505,11 @@ async function requestAddress(url, address, options) {
19427
19505
  const accepted = chunk.subarray(0, options.maxBytes - bytes);
19428
19506
  chunks.push(accepted);
19429
19507
  bytes += accepted.length;
19430
- if (accepted.length < chunk.length) truncated = true;
19508
+ if (accepted.length < chunk.length) {
19509
+ truncated = true;
19510
+ finish({ status, contentType: String(response2.headers["content-type"] ?? "application/octet-stream"), body: Buffer.concat(chunks), truncated });
19511
+ response2.destroy();
19512
+ }
19431
19513
  });
19432
19514
  response2.once("end", () => finish({
19433
19515
  status,
@@ -19573,11 +19655,11 @@ function charset(contentType) {
19573
19655
  }
19574
19656
 
19575
19657
  // src/tools/lsp.ts
19576
- import { spawn as spawn5 } from "child_process";
19658
+ import { spawn as spawn6 } from "child_process";
19577
19659
  import { existsSync as existsSync5 } from "fs";
19578
19660
  import { access as access2, constants as constants10, readFile as readFile21 } from "fs/promises";
19579
- import { basename as basename7, dirname as dirname20, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
19580
- import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "url";
19661
+ import { basename as basename7, dirname as dirname18, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
19662
+ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL3 } from "url";
19581
19663
  import { z as z32 } from "zod";
19582
19664
  var LSP_REQUEST_TIMEOUT_MS = 15e3;
19583
19665
  var LspConnection = class {
@@ -19589,12 +19671,14 @@ var LspConnection = class {
19589
19671
  #nextId = 1;
19590
19672
  #disposed = false;
19591
19673
  #initError;
19674
+ #shutdownPromise;
19592
19675
  constructor(command, args, cwd) {
19593
19676
  const invocation = prepareSpawnInvocation(command, args, { cwd });
19594
- this.#process = spawn5(invocation.command, invocation.args, {
19677
+ this.#process = spawn6(invocation.command, invocation.args, {
19595
19678
  cwd,
19596
19679
  stdio: ["pipe", "pipe", "pipe"],
19597
19680
  windowsHide: true,
19681
+ detached: process.platform !== "win32",
19598
19682
  ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
19599
19683
  });
19600
19684
  this.#process.on("error", (err) => {
@@ -19603,9 +19687,15 @@ var LspConnection = class {
19603
19687
  });
19604
19688
  this.#process.once("exit", (code, signal) => {
19605
19689
  const reason = signal !== null ? `LSP server "${command}" exited with signal ${signal}` : `LSP server "${command}" exited with code ${code}`;
19606
- this.#rejectAll(new Error(reason));
19690
+ this.#initError = new Error(reason);
19691
+ this.#rejectAll(this.#initError);
19607
19692
  });
19608
19693
  this.#process.stdout.on("data", (chunk) => this.#buffer.feed(chunk, this.#onMessage));
19694
+ this.#process.stderr.resume();
19695
+ this.#process.stdin.on("error", (error) => {
19696
+ this.#initError = error;
19697
+ this.#rejectAll(error);
19698
+ });
19609
19699
  }
19610
19700
  async initialize(rootUri) {
19611
19701
  if (this.#initError !== void 0) throw this.#initError;
@@ -19627,67 +19717,84 @@ var LspConnection = class {
19627
19717
  if (initResult === void 0) throw new Error("LSP initialize returned no capabilities");
19628
19718
  this.#notify("initialized", {});
19629
19719
  }
19630
- async findReferences(uri, line, character) {
19720
+ async findReferences(uri, line, character, signal) {
19721
+ signal?.throwIfAborted();
19631
19722
  await this.#syncDocument(uri);
19632
19723
  const result = await this.#request("textDocument/references", {
19633
19724
  textDocument: { uri },
19634
19725
  position: { line, character },
19635
19726
  context: { includeDeclaration: false }
19636
- });
19727
+ }, signal);
19637
19728
  if (result === null || result === void 0) return [];
19638
19729
  if (!Array.isArray(result)) return [];
19639
19730
  return result.filter(isLocation);
19640
19731
  }
19641
- async hover(uri, line, character) {
19732
+ async hover(uri, line, character, signal) {
19733
+ signal?.throwIfAborted();
19642
19734
  await this.#syncDocument(uri);
19643
19735
  const result = await this.#request("textDocument/hover", {
19644
19736
  textDocument: { uri },
19645
19737
  position: { line, character }
19646
- });
19738
+ }, signal);
19647
19739
  if (result === null || result === void 0) return null;
19648
19740
  if (typeof result === "object" && "contents" in result) {
19649
19741
  return result;
19650
19742
  }
19651
19743
  return null;
19652
19744
  }
19653
- async diagnostics(uri) {
19745
+ async diagnostics(uri, signal) {
19746
+ signal?.throwIfAborted();
19654
19747
  await this.#syncDocument(uri);
19655
19748
  const result = await this.#request("textDocument/diagnostic", {
19656
19749
  textDocument: { uri }
19657
- });
19750
+ }, signal);
19658
19751
  if (result === null || result === void 0) return [];
19659
19752
  const items = result?.items ?? result;
19660
19753
  if (!Array.isArray(items)) return [];
19661
19754
  return items.filter(isDiagnostic);
19662
19755
  }
19663
- async shutdown() {
19664
- if (this.#disposed) return;
19756
+ shutdown() {
19757
+ if (this.#shutdownPromise !== void 0) return this.#shutdownPromise;
19665
19758
  this.#disposed = true;
19666
- try {
19667
- await this.#request("shutdown", null);
19668
- } catch {
19669
- }
19670
- this.#notify("exit", null);
19671
- this.#process.kill();
19672
19759
  this.#rejectAll(new Error("LSP connection disposed"));
19760
+ this.#shutdownPromise = (async () => {
19761
+ await terminateProcessTree3(this.#process.pid);
19762
+ this.#process.stdin.destroy();
19763
+ this.#process.stdout.destroy();
19764
+ this.#process.stderr.destroy();
19765
+ })();
19766
+ return this.#shutdownPromise;
19673
19767
  }
19674
19768
  // -- JSON-RPC core -------------------------------------------------------
19675
- async #request(method, params) {
19769
+ async #request(method, params, signal) {
19676
19770
  if (this.#disposed) throw new Error("LSP connection disposed");
19771
+ if (this.#initError !== void 0) throw this.#initError;
19772
+ signal?.throwIfAborted();
19677
19773
  const id = this.#nextId++;
19678
19774
  const request = { jsonrpc: "2.0", id, method, params };
19679
- const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
19680
- const promise = new Promise((resolve43, reject) => {
19681
- this.#pending.set(id, { resolve: resolve43, reject });
19682
- timeout.addEventListener("abort", () => {
19683
- if (this.#pending.has(id)) {
19684
- this.#pending.delete(id);
19685
- reject(new Error(`LSP request "${method}" timed out after ${LSP_REQUEST_TIMEOUT_MS}ms`));
19775
+ let timer;
19776
+ let onAbort;
19777
+ const promise = new Promise((resolve44, reject) => {
19778
+ this.#pending.set(id, { resolve: resolve44, reject });
19779
+ timer = setTimeout(() => reject(new Error(`LSP request "${method}" timed out after ${LSP_REQUEST_TIMEOUT_MS}ms`)), LSP_REQUEST_TIMEOUT_MS);
19780
+ timer.unref();
19781
+ onAbort = () => {
19782
+ try {
19783
+ this.#notify("$/cancelRequest", { id });
19784
+ } catch {
19686
19785
  }
19687
- }, { once: true });
19786
+ reject(signal?.reason);
19787
+ };
19788
+ signal?.addEventListener("abort", onAbort, { once: true });
19688
19789
  });
19689
- this.#send(request);
19690
- return promise;
19790
+ try {
19791
+ this.#send(request);
19792
+ return await promise;
19793
+ } finally {
19794
+ if (timer !== void 0) clearTimeout(timer);
19795
+ if (onAbort !== void 0) signal?.removeEventListener("abort", onAbort);
19796
+ this.#pending.delete(id);
19797
+ }
19691
19798
  }
19692
19799
  #notify(method, params) {
19693
19800
  const notification = { jsonrpc: "2.0", method, params };
@@ -19707,6 +19814,7 @@ var LspConnection = class {
19707
19814
  } catch {
19708
19815
  return;
19709
19816
  }
19817
+ if (typeof parsed !== "object" || parsed === null) return;
19710
19818
  if ("method" in parsed) {
19711
19819
  if ("id" in parsed) this.#handleServerRequest(parsed);
19712
19820
  return;
@@ -19742,7 +19850,7 @@ var LspConnection = class {
19742
19850
  async #syncDocumentNow(uri) {
19743
19851
  const url = new URL(uri);
19744
19852
  if (url.protocol !== "file:") return;
19745
- const filePath = fileURLToPath3(url);
19853
+ const filePath = fileURLToPath2(url);
19746
19854
  const text = await readFile21(filePath, "utf8");
19747
19855
  const current = this.#documents.get(uri);
19748
19856
  if (current === void 0) {
@@ -19795,7 +19903,7 @@ var LspBuffer = class {
19795
19903
  }
19796
19904
  };
19797
19905
  var TYPESCRIPT_CLI = resolve26(
19798
- dirname20(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
19906
+ dirname18(fileURLToPath2(import.meta.resolve("typescript/package.json"))),
19799
19907
  "lib",
19800
19908
  "tsc.js"
19801
19909
  );
@@ -19830,6 +19938,8 @@ var KNOWN_SERVERS = [
19830
19938
  }
19831
19939
  ];
19832
19940
  var RealLspManager = class {
19941
+ #disposed = false;
19942
+ #disposal;
19833
19943
  #workspace;
19834
19944
  #connections = /* @__PURE__ */ new Map();
19835
19945
  #pendingStarts = /* @__PURE__ */ new Map();
@@ -19841,27 +19951,32 @@ var RealLspManager = class {
19841
19951
  this.#onStatus = options.onStatus;
19842
19952
  }
19843
19953
  dispose() {
19844
- for (const [, connection] of this.#connections) {
19845
- connection.shutdown().catch(() => {
19846
- });
19847
- }
19954
+ if (this.#disposal !== void 0) return this.#disposal;
19955
+ this.#disposed = true;
19956
+ const shutdowns = [...this.#connections.values()].map((connection) => connection.shutdown());
19957
+ shutdowns.push(...[...this.#pendingStarts.values()].map(async (start) => {
19958
+ const connection = await start;
19959
+ await connection.shutdown();
19960
+ }));
19848
19961
  this.#connections.clear();
19849
19962
  this.#pendingStarts.clear();
19963
+ this.#disposal = Promise.allSettled(shutdowns).then(() => void 0);
19964
+ return this.#disposal;
19850
19965
  }
19851
- async findReferences(uri, line, character) {
19852
- const connection = await this.#getConnection(uri);
19853
- if (connection === void 0) return [];
19854
- return connection.findReferences(uri, line, character);
19966
+ async findReferences(uri, line, character, signal) {
19967
+ signal?.throwIfAborted();
19968
+ const connection = await waitWithSignal(this.#getConnection(uri), signal);
19969
+ return connection.findReferences(uri, line, character, signal);
19855
19970
  }
19856
- async hover(uri, line, character) {
19857
- const connection = await this.#getConnection(uri);
19858
- if (connection === void 0) return null;
19859
- return connection.hover(uri, line, character);
19971
+ async hover(uri, line, character, signal) {
19972
+ signal?.throwIfAborted();
19973
+ const connection = await waitWithSignal(this.#getConnection(uri), signal);
19974
+ return connection.hover(uri, line, character, signal);
19860
19975
  }
19861
- async diagnostics(uri) {
19862
- const connection = await this.#getConnection(uri);
19863
- if (connection === void 0) return [];
19864
- return connection.diagnostics(uri);
19976
+ async diagnostics(uri, signal) {
19977
+ signal?.throwIfAborted();
19978
+ const connection = await waitWithSignal(this.#getConnection(uri), signal);
19979
+ return connection.diagnostics(uri, signal);
19865
19980
  }
19866
19981
  #languageForUri(uri) {
19867
19982
  try {
@@ -19877,20 +19992,25 @@ var RealLspManager = class {
19877
19992
  return void 0;
19878
19993
  }
19879
19994
  async #getConnection(uri) {
19995
+ if (this.#disposed) throw new Error("LSP manager is disposed");
19880
19996
  const language = this.#languageForUri(uri);
19881
- if (language === void 0) return void 0;
19997
+ if (language === void 0) throw new Error(`No language server configured for ${uri}; use Read/Grep or the project compiler`);
19882
19998
  const existing = this.#connections.get(language);
19883
19999
  if (existing !== void 0) return existing;
19884
20000
  const pending2 = this.#pendingStarts.get(language);
19885
20001
  if (pending2 !== void 0) return pending2;
19886
20002
  const config = this.#serverConfigs.find((c) => c.language === language);
19887
- if (config === void 0) return void 0;
20003
+ if (config === void 0) throw new Error(`No language server configured for ${language}`);
19888
20004
  const hasRoot = config.rootFiles.some((file) => existsSync5(resolve26(this.#workspace, file)));
19889
- if (!hasRoot) return void 0;
20005
+ if (!hasRoot) throw new Error(`Cannot start ${language} language server: workspace needs ${config.rootFiles.join(" or ")}`);
19890
20006
  const startPromise = this.#startServer(config);
19891
20007
  this.#pendingStarts.set(language, startPromise);
19892
20008
  try {
19893
20009
  const connection = await startPromise;
20010
+ if (this.#disposed) {
20011
+ await connection.shutdown();
20012
+ throw new Error("LSP manager is disposed");
20013
+ }
19894
20014
  this.#connections.set(language, connection);
19895
20015
  return connection;
19896
20016
  } finally {
@@ -19908,6 +20028,7 @@ var RealLspManager = class {
19908
20028
  await connection.initialize(rootUri);
19909
20029
  this.#onStatus?.(`${config.language} Language Server ready (${config.command})`);
19910
20030
  } catch (error) {
20031
+ await connection.shutdown();
19911
20032
  this.#onStatus?.(`${config.language} Language Server failed: ${error instanceof Error ? error.message : String(error)}`);
19912
20033
  throw error;
19913
20034
  }
@@ -19916,13 +20037,13 @@ var RealLspManager = class {
19916
20037
  };
19917
20038
  var LspFindRefsInput = z32.object({
19918
20039
  file: z32.string().min(1),
19919
- line: z32.coerce.number().int().nonnegative(),
19920
- character: z32.coerce.number().int().nonnegative()
20040
+ line: z32.coerce.number().int().nonnegative().describe("Zero-based line number (first line is 0)."),
20041
+ character: z32.coerce.number().int().nonnegative().describe("Zero-based UTF-16 character offset.")
19921
20042
  });
19922
20043
  var LspHoverInput = z32.object({
19923
20044
  file: z32.string().min(1),
19924
- line: z32.coerce.number().int().nonnegative(),
19925
- character: z32.coerce.number().int().nonnegative()
20045
+ line: z32.coerce.number().int().nonnegative().describe("Zero-based line number (first line is 0)."),
20046
+ character: z32.coerce.number().int().nonnegative().describe("Zero-based UTF-16 character offset.")
19926
20047
  });
19927
20048
  var LspDiagnosticsInput = z32.object({
19928
20049
  file: z32.string().min(1)
@@ -19948,10 +20069,11 @@ function createLspTools(workspace, options = {}) {
19948
20069
  inputSchema: LspFindRefsInput,
19949
20070
  paths: (input) => [guard(input.file)],
19950
20071
  summarize: (input) => `${basename7(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
19951
- execute: async (input, _signal) => {
20072
+ execute: async (input, signal) => {
20073
+ signal.throwIfAborted();
19952
20074
  const path = guard(input.file);
19953
20075
  const uri = toUri(path);
19954
- const refs = await manager.findReferences(uri, input.line, input.character);
20076
+ const refs = await waitWithSignal(manager.findReferences(uri, input.line, input.character, signal), signal);
19955
20077
  if (refs.length === 0) return `No references found at ${input.file}:${input.line}:${input.character}`;
19956
20078
  return refs.map((loc) => {
19957
20079
  const filePath = uriToPath(loc.uri);
@@ -19965,12 +20087,14 @@ function createLspTools(workspace, options = {}) {
19965
20087
  inputSchema: LspHoverInput,
19966
20088
  paths: (input) => [guard(input.file)],
19967
20089
  summarize: (input) => `${basename7(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
19968
- execute: async (input, _signal) => {
20090
+ execute: async (input, signal) => {
20091
+ signal.throwIfAborted();
19969
20092
  const path = guard(input.file);
19970
20093
  const uri = toUri(path);
19971
- const info = await manager.hover(uri, input.line, input.character);
20094
+ const info = await waitWithSignal(manager.hover(uri, input.line, input.character, signal), signal);
19972
20095
  if (info === null) return `No hover information at ${input.file}:${input.line}:${input.character}`;
19973
- const contents = typeof info.contents === "string" ? info.contents : info.contents.value;
20096
+ const parts = Array.isArray(info.contents) ? info.contents : [info.contents];
20097
+ const contents = parts.map((part) => typeof part === "string" ? part : part.value).join("\n\n");
19974
20098
  return contents;
19975
20099
  }
19976
20100
  };
@@ -19980,10 +20104,11 @@ function createLspTools(workspace, options = {}) {
19980
20104
  inputSchema: LspDiagnosticsInput,
19981
20105
  paths: (input) => [guard(input.file)],
19982
20106
  summarize: (input) => `${basename7(input.file)}${langTag(input.file)}`,
19983
- execute: async (input, _signal) => {
20107
+ execute: async (input, signal) => {
20108
+ signal.throwIfAborted();
19984
20109
  const path = guard(input.file);
19985
20110
  const uri = toUri(path);
19986
- const diags = await manager.diagnostics(uri);
20111
+ const diags = await waitWithSignal(manager.diagnostics(uri, signal), signal);
19987
20112
  if (diags.length === 0) return `No diagnostics for ${input.file}`;
19988
20113
  return diags.map((d) => {
19989
20114
  const severity = severityLabel(d.severity);
@@ -20002,6 +20127,14 @@ function languageIdForPath(filePath) {
20002
20127
  return "typescript";
20003
20128
  case ".cts":
20004
20129
  return "typescript";
20130
+ case ".py":
20131
+ case ".pyi":
20132
+ case ".pyx":
20133
+ return "python";
20134
+ case ".rs":
20135
+ return "rust";
20136
+ case ".go":
20137
+ return "go";
20005
20138
  default:
20006
20139
  return "typescript";
20007
20140
  }
@@ -20013,7 +20146,7 @@ function isWithin7(root, candidate) {
20013
20146
  function uriToPath(uri) {
20014
20147
  try {
20015
20148
  const url = new URL(uri);
20016
- if (url.protocol === "file:") return decodeURIComponent(url.pathname);
20149
+ if (url.protocol === "file:") return fileURLToPath2(url);
20017
20150
  } catch {
20018
20151
  }
20019
20152
  return uri;
@@ -20040,12 +20173,14 @@ function severityLabel(severity) {
20040
20173
  }
20041
20174
 
20042
20175
  // src/git/service.ts
20043
- async function git2(workspace, args, timeoutMs = 3e4) {
20044
- const result = await execFileNoThrow("git", ["-C", workspace, ...args], { timeout: timeoutMs, useCwd: false });
20176
+ async function git2(workspace, args, timeoutMs = 3e4, signal) {
20177
+ signal?.throwIfAborted();
20178
+ const result = await execFileNoThrow("git", ["-C", workspace, ...args], { timeout: timeoutMs, useCwd: false, ...signal === void 0 ? {} : { signal } });
20179
+ signal?.throwIfAborted();
20045
20180
  return { ok: result.code === 0, stdout: result.stdout, stderr: result.stderr, code: result.code };
20046
20181
  }
20047
- async function isGitRepository(workspace) {
20048
- const result = await git2(workspace, ["rev-parse", "--is-inside-work-tree"], 2e3);
20182
+ async function isGitRepository(workspace, signal) {
20183
+ const result = await git2(workspace, ["rev-parse", "--is-inside-work-tree"], 2e3, signal);
20049
20184
  return result.ok && result.stdout.trim() === "true";
20050
20185
  }
20051
20186
  function gitFailure(result, action) {
@@ -20123,16 +20258,21 @@ async function commit(workspace, commitMessage) {
20123
20258
  const head = await git2(workspace, ["log", "-1", "--pretty=%h %s"]);
20124
20259
  return head.stdout.trim() || "committed";
20125
20260
  }
20126
- async function fileHistory(workspace, path, limit) {
20261
+ async function fileHistory(workspace, path, limit, signal) {
20127
20262
  const args = [
20263
+ "--literal-pathspecs",
20128
20264
  "log",
20129
20265
  `--max-count=${limit}`,
20130
20266
  "--date=short",
20131
20267
  "--pretty=format:%h%x1f%ad%x1f%an%x1f%s",
20132
20268
  ...path === void 0 ? [] : ["--follow", "--", path]
20133
20269
  ];
20134
- const result = await git2(workspace, args, 15e3);
20135
- if (!result.ok) throw gitFailure(result, "log");
20270
+ const result = await git2(workspace, args, 15e3, signal);
20271
+ if (!result.ok) {
20272
+ const head = await git2(workspace, ["rev-parse", "--verify", "HEAD"], 2e3, signal);
20273
+ if (!head.ok && await isGitRepository(workspace, signal)) return [];
20274
+ throw gitFailure(result, "log");
20275
+ }
20136
20276
  return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => {
20137
20277
  const [hash = "", date = "", author = "", ...subject] = line.split("");
20138
20278
  return { hash, date, author, subject: subject.join("") };
@@ -20471,24 +20611,33 @@ function mapNode(value) {
20471
20611
 
20472
20612
  // src/git/tools.ts
20473
20613
  import { z as z34 } from "zod";
20614
+ import { isAbsolute as isAbsolute15, relative as relative19, resolve as resolve28, sep as sep19 } from "path";
20474
20615
  var GitHistoryInput = z34.object({
20475
- path: z34.string().describe("File to inspect; omit for the whole repository history").optional(),
20476
- limit: z34.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
20616
+ path: z34.string().min(1).describe("Workspace-relative or absolute file path; omit for the whole repository history").optional(),
20617
+ limit: z34.coerce.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
20477
20618
  });
20478
20619
  function createGitHistoryTool(workspace) {
20620
+ const root = resolve28(workspace);
20621
+ const file = (path) => {
20622
+ const absolute = resolve28(root, path);
20623
+ const delta = relative19(root, absolute);
20624
+ if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute15(delta)) throw new Error("GitHistory path is outside the workspace");
20625
+ return absolute;
20626
+ };
20479
20627
  return {
20480
20628
  name: "GitHistory",
20481
20629
  description: "Show git commit history for the repository or for one file (follows renames). Use to understand when and why code changed.",
20482
20630
  inputSchema: GitHistoryInput,
20483
20631
  readOnly: true,
20484
- paths: (input) => input.path === void 0 ? [] : [input.path],
20632
+ paths: (input) => input.path === void 0 ? [root] : [file(input.path)],
20485
20633
  summarize: (input) => input.path ?? "repository",
20486
20634
  renderForModel: (output) => output,
20487
20635
  async execute(input, signal) {
20488
20636
  signal.throwIfAborted();
20489
- if (!await isGitRepository(workspace)) throw new Error("Not a git repository");
20637
+ const path = input.path === void 0 ? void 0 : relative19(root, file(input.path));
20638
+ if (!await isGitRepository(root, signal)) throw new Error("Not a git repository");
20490
20639
  const limit = input.limit ?? 20;
20491
- const entries = await fileHistory(workspace, input.path, limit);
20640
+ const entries = await fileHistory(root, path, limit, signal);
20492
20641
  if (entries.length === 0) {
20493
20642
  return input.path === void 0 ? "No commits found." : `No commits found for ${input.path} (the file may be untracked).`;
20494
20643
  }
@@ -20551,7 +20700,8 @@ var QuestionBridge = class {
20551
20700
  }
20552
20701
  async #askRelayed(questions, signal) {
20553
20702
  try {
20554
- const answers = await this.#relay(questions, signal);
20703
+ const answers = await waitWithSignal(this.#relay(questions, signal), signal);
20704
+ signal.throwIfAborted();
20555
20705
  if (answers !== void 0) {
20556
20706
  this.#active = false;
20557
20707
  return answers;
@@ -20563,10 +20713,15 @@ var QuestionBridge = class {
20563
20713
  }
20564
20714
  }
20565
20715
  #askLocally(questions, signal) {
20566
- return new Promise((resolve43, reject) => {
20716
+ if (signal.aborted) {
20717
+ this.#active = false;
20718
+ return Promise.reject(signal.reason);
20719
+ }
20720
+ return new Promise((resolve44, reject) => {
20567
20721
  const onAbort = () => {
20568
20722
  this.#pending = void 0;
20569
20723
  this.#active = false;
20724
+ this.#onChange?.();
20570
20725
  reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
20571
20726
  };
20572
20727
  signal.addEventListener("abort", onAbort, { once: true });
@@ -20577,7 +20732,7 @@ var QuestionBridge = class {
20577
20732
  this.#pending = void 0;
20578
20733
  this.#active = false;
20579
20734
  this.#onChange?.();
20580
- resolve43(answers);
20735
+ resolve44(answers);
20581
20736
  },
20582
20737
  reject: (error) => {
20583
20738
  signal.removeEventListener("abort", onAbort);
@@ -20670,7 +20825,7 @@ function createTodoWriteTool() {
20670
20825
  // src/tools/managed.ts
20671
20826
  import { randomUUID as randomUUID18 } from "crypto";
20672
20827
  import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
20673
- import { dirname as dirname21, join as join30, resolve as resolve28 } from "path";
20828
+ import { dirname as dirname19, join as join30, resolve as resolve29 } from "path";
20674
20829
  import { z as z37 } from "zod";
20675
20830
  var MANAGED_TOOL_VERSION = 1;
20676
20831
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -20712,9 +20867,9 @@ var ManagedToolStore = class {
20712
20867
  #entries = /* @__PURE__ */ new Map();
20713
20868
  #diagnostics = [];
20714
20869
  constructor(options) {
20715
- this.#workspace = resolve28(options.workspace);
20870
+ this.#workspace = resolve29(options.workspace);
20716
20871
  this.#projectRoot = join30(this.#workspace, ".flavor", "tools");
20717
- this.#globalRoot = join30(resolve28(options.home), ".flavor-code", "tools");
20872
+ this.#globalRoot = join30(resolve29(options.home), ".flavor-code", "tools");
20718
20873
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
20719
20874
  }
20720
20875
  get diagnostics() {
@@ -20954,7 +21109,7 @@ ${sourceUrl}`
20954
21109
  }
20955
21110
  }
20956
21111
  async function writeExclusive(path, content) {
20957
- await mkdir20(dirname21(path), { recursive: true });
21112
+ await mkdir20(dirname19(path), { recursive: true });
20958
21113
  const temporary = `${path}.${randomUUID18()}.tmp`;
20959
21114
  try {
20960
21115
  await writeFile12(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
@@ -22268,9 +22423,9 @@ Address this now (for example run the required verification or fix), then finish
22268
22423
  }
22269
22424
  #waitForPlanningIdle(key) {
22270
22425
  if (!this.#activePlanning.has(key)) return Promise.resolve();
22271
- return new Promise((resolve43) => {
22426
+ return new Promise((resolve44) => {
22272
22427
  const check2 = () => {
22273
- if (!this.#activePlanning.has(key)) resolve43();
22428
+ if (!this.#activePlanning.has(key)) resolve44();
22274
22429
  else this.#planningIdleWaiters.add(check2);
22275
22430
  };
22276
22431
  this.#planningIdleWaiters.add(check2);
@@ -22381,7 +22536,7 @@ function requiredPals(service) {
22381
22536
  import { constants as constants11 } from "fs";
22382
22537
  import { access as access3, lstat as lstat12, readFile as readFile23, readdir as readdir10, stat as stat11 } from "fs/promises";
22383
22538
  import { homedir } from "os";
22384
- import { isAbsolute as isAbsolute15, join as join31, relative as relative19, resolve as resolve29, sep as sep19 } from "path";
22539
+ import { isAbsolute as isAbsolute16, join as join31, relative as relative20, resolve as resolve30, sep as sep20 } from "path";
22385
22540
  import { rgPath as bundledRgPath2 } from "@vscode/ripgrep";
22386
22541
 
22387
22542
  // src/utils/semver.ts
@@ -22398,8 +22553,8 @@ async function fetchLatestVersion(options = {}) {
22398
22553
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
22399
22554
  const fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
22400
22555
  let timer;
22401
- const timeout = new Promise((resolve43) => {
22402
- timer = setTimeout(() => resolve43(void 0), timeoutMs);
22556
+ const timeout = new Promise((resolve44) => {
22557
+ timer = setTimeout(() => resolve44(void 0), timeoutMs);
22403
22558
  timer.unref?.();
22404
22559
  });
22405
22560
  try {
@@ -22419,8 +22574,8 @@ async function fetchLatestVersion(options = {}) {
22419
22574
 
22420
22575
  // src/doctor.ts
22421
22576
  async function runDoctor(options = {}, dependencies2 = {}) {
22422
- const workspace = resolve29(options.workspace ?? process.cwd());
22423
- const home = resolve29(options.home ?? homedir());
22577
+ const workspace = resolve30(options.workspace ?? process.cwd());
22578
+ const home = resolve30(options.home ?? homedir());
22424
22579
  const environment = options.environment ?? process.env;
22425
22580
  const platform = options.platform ?? process.platform;
22426
22581
  const arch = options.arch ?? process.arch;
@@ -22568,14 +22723,14 @@ async function pluginCheck(directories) {
22568
22723
  }
22569
22724
  for (const entry2 of entries) {
22570
22725
  if (!entry2.isDirectory() || entry2.name.startsWith(".")) continue;
22571
- const root = resolve29(directory, entry2.name);
22726
+ const root = resolve30(directory, entry2.name);
22572
22727
  try {
22573
22728
  const rootInfo = await lstat12(root);
22574
22729
  if (rootInfo.isSymbolicLink()) throw new Error("plugin directory must not be a symbolic link");
22575
22730
  const manifest = PluginManifestSchema.parse(JSON.parse(await readFile23(join31(root, "flavor-plugin.json"), "utf8")));
22576
- const main = resolve29(root, manifest.main);
22577
- const delta = relative19(root, main);
22578
- if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute15(delta)) throw new Error("plugin entry escapes its root");
22731
+ const main = resolve30(root, manifest.main);
22732
+ const delta = relative20(root, main);
22733
+ if (delta === ".." || delta.startsWith(`..${sep20}`) || isAbsolute16(delta)) throw new Error("plugin entry escapes its root");
22579
22734
  const mainInfo = await lstat12(main);
22580
22735
  if (!mainInfo.isFile() || mainInfo.isSymbolicLink()) throw new Error("plugin entry must be a regular file");
22581
22736
  valid += 1;
@@ -22639,7 +22794,7 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
22639
22794
  ]);
22640
22795
 
22641
22796
  // src/utils/crash-guard.ts
22642
- import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
22797
+ import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
22643
22798
  import { join as join32 } from "path";
22644
22799
  var installed = false;
22645
22800
  var crashing = false;
@@ -22679,7 +22834,7 @@ function installCrashGuard(options = {}) {
22679
22834
  const writeCrashLog = (kind, detail) => {
22680
22835
  try {
22681
22836
  const dir = crashLogDir();
22682
- mkdirSync3(dir, { recursive: true });
22837
+ mkdirSync2(dir, { recursive: true });
22683
22838
  const path = join32(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
22684
22839
  writeFileSync(path, [
22685
22840
  `kind: ${kind}`,
@@ -23452,7 +23607,7 @@ ${total}`;
23452
23607
  // src/memory/store.ts
23453
23608
  import { createHash as createHash17 } from "crypto";
23454
23609
  import { readFile as readFile24, rm as rm12 } from "fs/promises";
23455
- import { resolve as resolve30, join as join33, relative as relative20, sep as sep20 } from "path";
23610
+ import { resolve as resolve31, join as join33, relative as relative21, sep as sep21 } from "path";
23456
23611
 
23457
23612
  // src/memory/similarity.ts
23458
23613
  function normalizeForSimilarity(value) {
@@ -23554,7 +23709,7 @@ var MemoryStore = class {
23554
23709
  constructor(options) {
23555
23710
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
23556
23711
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
23557
- this.workspace = resolve30(options.workspace);
23712
+ this.workspace = resolve31(options.workspace);
23558
23713
  this.#memoryRoot = join33(this.workspace, ".flavor", "memory");
23559
23714
  this.path = join33(this.#memoryRoot, "MEMORY.md");
23560
23715
  this.#behaviorPath = join33(this.#memoryRoot, "behavior.json");
@@ -23780,9 +23935,9 @@ var MemoryStore = class {
23780
23935
  }
23781
23936
  #resolveContentPath(contentPath) {
23782
23937
  if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
23783
- const path = resolve30(this.#memoryRoot, ...contentPath.split("/"));
23784
- const rel = relative20(this.#memoryRoot, path);
23785
- if (rel === ".." || rel.startsWith(`..${sep20}`)) throw new Error("Memory content path escapes the memory directory");
23938
+ const path = resolve31(this.#memoryRoot, ...contentPath.split("/"));
23939
+ const rel = relative21(this.#memoryRoot, path);
23940
+ if (rel === ".." || rel.startsWith(`..${sep21}`)) throw new Error("Memory content path escapes the memory directory");
23786
23941
  return path;
23787
23942
  }
23788
23943
  async #updateIndex(update) {
@@ -24239,14 +24394,14 @@ var MemoryReviewBridge = class {
24239
24394
  };
24240
24395
 
24241
24396
  // src/execution/docker.ts
24242
- import { spawn as spawn6 } from "child_process";
24243
- import { relative as relative21, resolve as resolve31, sep as sep21, win32 } from "path";
24397
+ import { spawn as spawn7 } from "child_process";
24398
+ import { relative as relative22, resolve as resolve32, sep as sep22, win32 } from "path";
24244
24399
  function buildDockerInvocation(options) {
24245
24400
  const workspace = options.workspace;
24246
24401
  const cwd = options.request.cwd;
24247
24402
  const windows = /^[A-Za-z]:[\\/]/.test(workspace);
24248
- const delta = windows ? win32.relative(workspace, cwd) : relative21(resolve31(workspace), resolve31(cwd));
24249
- if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep21}`)) throw new Error("Docker cwd escapes workspace");
24403
+ const delta = windows ? win32.relative(workspace, cwd) : relative22(resolve32(workspace), resolve32(cwd));
24404
+ if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep22}`)) throw new Error("Docker cwd escapes workspace");
24250
24405
  const containerCwd = delta.length === 0 ? "/workspace" : `/workspace/${delta.replaceAll("\\", "/")}`;
24251
24406
  const args = [
24252
24407
  "run",
@@ -24298,7 +24453,7 @@ function runDockerInvocation(invocation, signal, timeoutMs, maxOutputBytes = DEF
24298
24453
  resolvePromise({ exitCode: null, signal: null, stdout: "", stderr: "", terminationReason: "cancelled" });
24299
24454
  return;
24300
24455
  }
24301
- const child = spawn6(invocation.command, invocation.args, { windowsHide: true, shell: false });
24456
+ const child = spawn7(invocation.command, invocation.args, { windowsHide: true, shell: false });
24302
24457
  const stdout = new BoundedUtf8Output(maxOutputBytes);
24303
24458
  const stderr = new BoundedUtf8Output(maxOutputBytes);
24304
24459
  let reason = null;
@@ -24416,7 +24571,7 @@ function createExecutionEnvironment(workspace, config) {
24416
24571
 
24417
24572
  // src/ide/client.ts
24418
24573
  import { readFile as readFile25, readdir as readdir11, stat as stat12 } from "fs/promises";
24419
- import { basename as basename8, join as join34, relative as relative22, resolve as resolve32, sep as sep22 } from "path";
24574
+ import { basename as basename8, join as join34, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
24420
24575
  var FlavorIdeClient = class {
24421
24576
  #workspace;
24422
24577
  #home;
@@ -24427,8 +24582,8 @@ var FlavorIdeClient = class {
24427
24582
  #sessionId;
24428
24583
  #eventTail = Promise.resolve();
24429
24584
  constructor(options) {
24430
- this.#workspace = resolve32(options.workspace);
24431
- this.#home = resolve32(options.home);
24585
+ this.#workspace = resolve33(options.workspace);
24586
+ this.#home = resolve33(options.home);
24432
24587
  this.#environment = options.environment ?? process.env;
24433
24588
  this.#fetch = options.fetch ?? fetch;
24434
24589
  }
@@ -24626,16 +24781,16 @@ function parsePort(value) {
24626
24781
  return Number.isInteger(port2) && port2 > 0 && port2 <= 65535 ? port2 : void 0;
24627
24782
  }
24628
24783
  function isWithin8(parent, child) {
24629
- const base = normalize(resolve32(parent));
24630
- const target = normalize(resolve32(child));
24631
- return target === base || target.startsWith(`${base}${sep22}`);
24784
+ const base = normalize(resolve33(parent));
24785
+ const target = normalize(resolve33(child));
24786
+ return target === base || target.startsWith(`${base}${sep23}`);
24632
24787
  }
24633
24788
  function normalize(path) {
24634
24789
  const normalized = path.normalize("NFC");
24635
24790
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
24636
24791
  }
24637
24792
  function relativeDisplay(workspace, filePath) {
24638
- const path = relative22(workspace, filePath).replaceAll("\\", "/");
24793
+ const path = relative23(workspace, filePath).replaceAll("\\", "/");
24639
24794
  return path && !path.startsWith("../") ? path : basename8(filePath);
24640
24795
  }
24641
24796
 
@@ -24654,11 +24809,12 @@ var JobRegistry = class {
24654
24809
  }
24655
24810
  create(input) {
24656
24811
  this.#pruneCompleted();
24812
+ if (this.#jobs.size >= this.#maxJobs) throw new Error(`Background job limit (${this.#maxJobs}) reached; wait for or cancel an existing job first`);
24657
24813
  const id = `job-${randomUUID19().slice(0, 12)}`;
24658
24814
  const now = (/* @__PURE__ */ new Date()).toISOString();
24659
24815
  let settle;
24660
- const done = new Promise((resolve43) => {
24661
- settle = resolve43;
24816
+ const done = new Promise((resolve44) => {
24817
+ settle = resolve44;
24662
24818
  });
24663
24819
  const record3 = {
24664
24820
  id,
@@ -24692,7 +24848,7 @@ var JobRegistry = class {
24692
24848
  },
24693
24849
  complete: (result = {}) => this.#finish(
24694
24850
  record3,
24695
- result.exitCode !== void 0 && result.exitCode !== null && result.exitCode !== 0 ? "failed" : "completed",
24851
+ result.error !== void 0 || result.exitCode !== void 0 && result.exitCode !== null && result.exitCode !== 0 ? "failed" : "completed",
24696
24852
  result
24697
24853
  ),
24698
24854
  fail: (error) => this.#finish(record3, "failed", { error: error instanceof Error ? error.message : String(error) }),
@@ -24708,21 +24864,24 @@ var JobRegistry = class {
24708
24864
  return { ...snapshot2(job), output: job.output.slice(effective - job.outputStart), cursor: job.outputStart + job.output.length };
24709
24865
  }
24710
24866
  async wait(id, owner2, signal) {
24867
+ signal?.throwIfAborted();
24711
24868
  const job = this.#owned(id, owner2);
24712
24869
  if (job.state !== "running") return snapshot2(job);
24713
- if (signal === void 0) return job.done;
24714
- return Promise.race([job.done, new Promise((_resolve, reject) => {
24715
- if (signal.aborted) {
24716
- reject(signal.reason);
24717
- return;
24718
- }
24719
- signal.addEventListener("abort", () => reject(signal.reason), { once: true });
24720
- })]);
24870
+ return waitWithSignal(job.done, signal);
24721
24871
  }
24722
24872
  kill(id, owner2) {
24723
24873
  const job = this.#owned(id, owner2);
24724
24874
  if (job.state !== "running") return;
24725
- void job.cancel?.();
24875
+ try {
24876
+ const cancellation = job.cancel?.();
24877
+ if (cancellation !== void 0) return cancellation.then(
24878
+ () => this.#finish(job, "cancelled", {}),
24879
+ (error) => this.#finish(job, "failed", { error: `Cancellation failed: ${error instanceof Error ? error.message : String(error)}` })
24880
+ );
24881
+ } catch (error) {
24882
+ this.#finish(job, "failed", { error: `Cancellation failed: ${error instanceof Error ? error.message : String(error)}` });
24883
+ return;
24884
+ }
24726
24885
  this.#finish(job, "cancelled", {});
24727
24886
  }
24728
24887
  subscribe(listener) {
@@ -24782,7 +24941,7 @@ function snapshot2(job) {
24782
24941
  // src/terminal/service.ts
24783
24942
  import { randomUUID as randomUUID20 } from "crypto";
24784
24943
  import { createRequire as nodeCreateRequire2 } from "module";
24785
- import { isAbsolute as isAbsolute16, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
24944
+ import { isAbsolute as isAbsolute17, relative as relative24, resolve as resolve34, sep as sep24 } from "path";
24786
24945
  var TerminalService = class {
24787
24946
  #root;
24788
24947
  #factory;
@@ -24790,10 +24949,11 @@ var TerminalService = class {
24790
24949
  #maxOutputChars;
24791
24950
  #sessions = /* @__PURE__ */ new Map();
24792
24951
  constructor(workspace, options = {}) {
24793
- this.#root = resolve33(workspace);
24952
+ this.#root = resolve34(workspace);
24794
24953
  this.#factory = options.factory ?? defaultFactory;
24795
24954
  this.#jobs = options.jobs;
24796
24955
  this.#maxOutputChars = options.maxOutputChars ?? 2e5;
24956
+ if (!Number.isSafeInteger(this.#maxOutputChars) || this.#maxOutputChars <= 0) throw new Error("maxOutputChars must be a positive integer");
24797
24957
  }
24798
24958
  open(input) {
24799
24959
  const cwd = workspacePath(this.#root, input.cwd ?? ".");
@@ -24820,7 +24980,13 @@ var TerminalService = class {
24820
24980
  truncated: false,
24821
24981
  disposables: []
24822
24982
  };
24823
- const job = this.#jobs?.create({ kind: "terminal", owner: input.owner, label: shell2, cancel: () => this.close(id, input.owner) });
24983
+ let job;
24984
+ try {
24985
+ job = this.#jobs?.create({ kind: "terminal", owner: input.owner, label: shell2, cancel: () => this.close(id, input.owner) });
24986
+ } catch (error) {
24987
+ pty.kill();
24988
+ throw error;
24989
+ }
24824
24990
  if (job !== void 0) {
24825
24991
  jobId = job.id;
24826
24992
  record3.jobId = job.id;
@@ -24846,10 +25012,10 @@ var TerminalService = class {
24846
25012
  return { ...snapshot3(record3), ...jobId === void 0 ? {} : { jobId } };
24847
25013
  }
24848
25014
  write(id, owner2, data2) {
24849
- this.#owned(id, owner2).pty.write(data2);
25015
+ this.#running(id, owner2).pty.write(data2);
24850
25016
  }
24851
25017
  resize(id, owner2, columns, rows) {
24852
- this.#owned(id, owner2).pty.resize(columns, rows);
25018
+ this.#running(id, owner2).pty.resize(columns, rows);
24853
25019
  }
24854
25020
  read(id, owner2, cursor = 0) {
24855
25021
  const record3 = this.#owned(id, owner2);
@@ -24880,6 +25046,11 @@ var TerminalService = class {
24880
25046
  if (record3.owner !== owner2) throw new Error("Terminal owner mismatch");
24881
25047
  return record3;
24882
25048
  }
25049
+ #running(id, owner2) {
25050
+ const record3 = this.#owned(id, owner2);
25051
+ if (record3.state !== "running") throw new Error(`Terminal ${id} is ${record3.state}; use TerminalOpen to start a new session`);
25052
+ return record3;
25053
+ }
24883
25054
  };
24884
25055
  function snapshot3(record3) {
24885
25056
  return {
@@ -24908,15 +25079,15 @@ function cleanEnvironment(environment) {
24908
25079
  return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
24909
25080
  }
24910
25081
  function workspacePath(root, input) {
24911
- const path = resolve33(root, input);
24912
- const delta = relative23(root, path);
24913
- if (delta === ".." || delta.startsWith(`..${sep23}`) || isAbsolute16(delta)) throw new Error("Terminal cwd is outside the workspace");
25082
+ const path = resolve34(root, input);
25083
+ const delta = relative24(root, path);
25084
+ if (delta === ".." || delta.startsWith(`..${sep24}`) || isAbsolute17(delta)) throw new Error("Terminal cwd is outside the workspace");
24914
25085
  return path;
24915
25086
  }
24916
25087
 
24917
25088
  // src/tools/terminal.ts
24918
25089
  import { z as z40 } from "zod";
24919
- import { resolve as resolve34 } from "path";
25090
+ import { resolve as resolve35 } from "path";
24920
25091
  var Id = z40.string().regex(/^term-[a-f0-9-]+$/);
24921
25092
  var OpenInput = z40.object({ cwd: z40.string().optional(), shell: z40.string().optional(), args: z40.array(z40.string()).optional(), columns: z40.coerce.number().int().min(10).max(500).optional(), rows: z40.coerce.number().int().min(2).max(300).optional() });
24922
25093
  var OptionalBoolean = z40.union([z40.boolean(), z40.string().refine((value) => value === "true" || value === "false")]);
@@ -24930,8 +25101,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
24930
25101
  name: "TerminalOpen",
24931
25102
  description: "Open a persistent interactive pseudo-terminal in the workspace",
24932
25103
  inputSchema: OpenInput,
24933
- paths: (input) => [resolve34(workspace, input.cwd ?? ".")],
24934
- permissions: (input) => ({ paths: [resolve34(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve34(workspace, input.cwd ?? ".") }),
25104
+ paths: (input) => [resolve35(workspace, input.cwd ?? ".")],
25105
+ permissions: (input) => ({ paths: [resolve35(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve35(workspace, input.cwd ?? ".") }),
24935
25106
  execute: async (input, _signal, context) => terminals.open({
24936
25107
  owner: owner(context),
24937
25108
  ...input.cwd === void 0 ? {} : { cwd: input.cwd },
@@ -24960,7 +25131,7 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
24960
25131
  inputSchema: ReadInput3,
24961
25132
  paths: () => [],
24962
25133
  execute: async (input, _signal, context) => terminals.read(input.id, owner(context), input.cursor ?? 0),
24963
- renderForModel: (output) => output.output,
25134
+ renderForModel: (output) => JSON.stringify(output),
24964
25135
  presentResult: (output) => {
24965
25136
  const result = output;
24966
25137
  return {
@@ -25028,13 +25199,13 @@ import { createHash as createHash20 } from "crypto";
25028
25199
  import { Socket as Socket2 } from "net";
25029
25200
  import { tmpdir as tmpdir3 } from "os";
25030
25201
  import { join as join36 } from "path";
25031
- import { spawn as spawn7 } from "child_process";
25202
+ import { spawn as spawn8 } from "child_process";
25032
25203
 
25033
25204
  // src/pals/broker.ts
25034
25205
  import { createHash as createHash19, randomUUID as randomUUID23, timingSafeEqual } from "crypto";
25035
25206
  import { chmod as chmod3, lstat as lstat13, mkdir as mkdir23, unlink as unlink6 } from "fs/promises";
25036
25207
  import { createServer as createServer4, Socket } from "net";
25037
- import { dirname as dirname23 } from "path";
25208
+ import { dirname as dirname21 } from "path";
25038
25209
 
25039
25210
  // src/pals/auth.ts
25040
25211
  import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
@@ -25085,7 +25256,7 @@ function validatePalAuthToken(token) {
25085
25256
  // src/pals/lifecycle.ts
25086
25257
  import { randomUUID as randomUUID22 } from "crypto";
25087
25258
  import { mkdir as mkdir22, open as open13, readFile as readFile27, rename as rename11, stat as stat13, unlink as unlink5 } from "fs/promises";
25088
- import { dirname as dirname22 } from "path";
25259
+ import { dirname as dirname20 } from "path";
25089
25260
  function defaultProcessAlive(pid) {
25090
25261
  if (!Number.isSafeInteger(pid) || pid <= 0) return true;
25091
25262
  try {
@@ -25106,7 +25277,7 @@ function parseMetadata(value) {
25106
25277
  }
25107
25278
  async function acquirePalFileLock(options) {
25108
25279
  if (await options.endpointLive()) return void 0;
25109
- await mkdir22(dirname22(options.path), { recursive: true, mode: 448 });
25280
+ await mkdir22(dirname20(options.path), { recursive: true, mode: 448 });
25110
25281
  const pid = options.pid ?? process.pid;
25111
25282
  const now = options.now ?? Date.now;
25112
25283
  const nonce = randomUUID22();
@@ -25161,16 +25332,16 @@ async function acquirePalFileLock(options) {
25161
25332
 
25162
25333
  // src/pals/broker-cli.ts
25163
25334
  var startupAttempts = /* @__PURE__ */ new Map();
25164
- function delay3(milliseconds) {
25165
- return new Promise((resolve43) => setTimeout(resolve43, milliseconds));
25335
+ function delay2(milliseconds) {
25336
+ return new Promise((resolve44) => setTimeout(resolve44, milliseconds));
25166
25337
  }
25167
25338
  function canConnect(address) {
25168
- return new Promise((resolve43) => {
25339
+ return new Promise((resolve44) => {
25169
25340
  const socket = new Socket2();
25170
25341
  const finish = (connected) => {
25171
25342
  socket.removeAllListeners();
25172
25343
  socket.destroy();
25173
- resolve43(connected);
25344
+ resolve44(connected);
25174
25345
  };
25175
25346
  socket.once("connect", () => finish(true));
25176
25347
  socket.once("error", () => finish(false));
@@ -25193,7 +25364,7 @@ async function spawnPalBrokerProcess(options) {
25193
25364
  const lock = await acquirePalFileLock({ path: lockPath, endpointLive: () => canConnect(address) });
25194
25365
  if (lock === void 0) return;
25195
25366
  try {
25196
- const child = spawn7(options.executable, [...options.args], {
25367
+ const child = spawn8(options.executable, [...options.args], {
25197
25368
  detached: true,
25198
25369
  stdio: "ignore",
25199
25370
  windowsHide: true,
@@ -25201,7 +25372,7 @@ async function spawnPalBrokerProcess(options) {
25201
25372
  });
25202
25373
  child.unref();
25203
25374
  const deadline = Date.now() + 5e3;
25204
- while (Date.now() < deadline && !await canConnect(address)) await delay3(25);
25375
+ while (Date.now() < deadline && !await canConnect(address)) await delay2(25);
25205
25376
  if (!await canConnect(address)) throw new Error(`Pals broker process did not become ready at '${address}'`);
25206
25377
  } finally {
25207
25378
  await lock.release();
@@ -25222,7 +25393,7 @@ async function ensureOnce(options) {
25222
25393
  const deadline = Date.now() + timeoutMs;
25223
25394
  do {
25224
25395
  if (await canConnect(options.address)) return;
25225
- await delay3(retryDelayMs);
25396
+ await delay2(retryDelayMs);
25226
25397
  } while (Date.now() < deadline);
25227
25398
  throw new Error(`Timed out waiting for pals broker at '${options.address}'`);
25228
25399
  }
@@ -25361,8 +25532,8 @@ var PalClient = class {
25361
25532
  let disconnectTimer;
25362
25533
  await Promise.race([
25363
25534
  this.request({ version: 1, type: "disconnect", requestId: randomUUID24() }).catch(() => void 0),
25364
- new Promise((resolve43) => {
25365
- disconnectTimer = setTimeout(resolve43, Math.min(this.requestTimeoutMs, 250));
25535
+ new Promise((resolve44) => {
25536
+ disconnectTimer = setTimeout(resolve44, Math.min(this.requestTimeoutMs, 250));
25366
25537
  })
25367
25538
  ]);
25368
25539
  if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
@@ -25408,7 +25579,7 @@ var PalClient = class {
25408
25579
  return presence;
25409
25580
  }
25410
25581
  openSocket() {
25411
- return new Promise((resolve43, reject) => {
25582
+ return new Promise((resolve44, reject) => {
25412
25583
  let settled = false;
25413
25584
  const rejectConnection = (error) => {
25414
25585
  if (settled) return;
@@ -25431,7 +25602,7 @@ var PalClient = class {
25431
25602
  settled = true;
25432
25603
  if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
25433
25604
  socket.off("error", onInitialError);
25434
- resolve43();
25605
+ resolve44();
25435
25606
  };
25436
25607
  const onInitialError = (error) => {
25437
25608
  socket.off("connect", onConnect);
@@ -25462,12 +25633,12 @@ var PalClient = class {
25462
25633
  if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
25463
25634
  return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
25464
25635
  }
25465
- return new Promise((resolve43, reject) => {
25636
+ return new Promise((resolve44, reject) => {
25466
25637
  const timer = setTimeout(() => {
25467
25638
  this.pending.delete(request.requestId);
25468
25639
  reject(new Error(`Pal request '${request.type}' timed out`));
25469
25640
  }, this.requestTimeoutMs);
25470
- this.pending.set(request.requestId, { resolve: resolve43, reject, timer });
25641
+ this.pending.set(request.requestId, { resolve: resolve44, reject, timer });
25471
25642
  let frame;
25472
25643
  try {
25473
25644
  frame = encodeControlFrame(BrokerRequestSchema.parse(request));
@@ -25571,12 +25742,12 @@ var PalClient = class {
25571
25742
  }
25572
25743
  scheduleReconnect() {
25573
25744
  if (this.reconnectTimer !== void 0 || this.closed) return;
25574
- const delay4 = this.reconnectDelayMs;
25745
+ const delay3 = this.reconnectDelayMs;
25575
25746
  this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, this.reconnectMaxDelayMs);
25576
25747
  this.reconnectTimer = setTimeout(() => {
25577
25748
  this.reconnectTimer = void 0;
25578
25749
  void this.start().catch(() => this.scheduleReconnect());
25579
- }, delay4);
25750
+ }, delay3);
25580
25751
  this.reconnectTimer.unref?.();
25581
25752
  }
25582
25753
  };
@@ -25678,6 +25849,7 @@ function createPalsTools(client, options) {
25678
25849
  execute: async (input, signal) => {
25679
25850
  signal.throwIfAborted();
25680
25851
  const recipientId = await assertSendTarget(client, input.target, input.coWorkId);
25852
+ signal.throwIfAborted();
25681
25853
  return client.sendChat(recipientId, shareGuard.protect(input.message));
25682
25854
  }
25683
25855
  },
@@ -25701,6 +25873,7 @@ function createPalsTools(client, options) {
25701
25873
  execute: async (input, signal) => {
25702
25874
  signal.throwIfAborted();
25703
25875
  assertRequiredParticipant(await client.coWorkStatus(input.plan.coWorkId), options.selfId);
25876
+ signal.throwIfAborted();
25704
25877
  const plan = CoWorkPlanSchema.parse({
25705
25878
  ...input.plan,
25706
25879
  goal: shareGuard.protect(input.plan.goal),
@@ -25717,11 +25890,13 @@ function createPalsTools(client, options) {
25717
25890
  execute: async (input, signal) => {
25718
25891
  signal.throwIfAborted();
25719
25892
  let state = await assertCurrentToken(client, input);
25893
+ signal.throwIfAborted();
25720
25894
  assertRequiredParticipant(state, options.selfId);
25721
25895
  if (!state.planAcceptedParticipantIds.includes(options.selfId)) {
25722
25896
  state = await client.coWorkAction({ type: "cowork-plan-accept", ...input });
25723
25897
  }
25724
25898
  if (state.readyParticipantIds.includes(options.selfId) || state.phase === "running") return state;
25899
+ signal.throwIfAborted();
25725
25900
  return client.coWorkAction({ type: "cowork-ready", ...input });
25726
25901
  }
25727
25902
  },
@@ -25735,6 +25910,7 @@ function createPalsTools(client, options) {
25735
25910
  execute: async (input, signal) => {
25736
25911
  signal.throwIfAborted();
25737
25912
  const recipientId = await assertSendTarget(client, input.target, input.coWorkId);
25913
+ signal.throwIfAborted();
25738
25914
  return client.sendChat(recipientId, `[co-work ${input.coWorkId} progress] ${shareGuard.protect(input.detail)}`);
25739
25915
  }
25740
25916
  },
@@ -25748,6 +25924,7 @@ function createPalsTools(client, options) {
25748
25924
  execute: async (input, signal) => {
25749
25925
  signal.throwIfAborted();
25750
25926
  const state = await assertCurrentToken(client, input);
25927
+ signal.throwIfAborted();
25751
25928
  assertRequiredParticipant(state, options.selfId);
25752
25929
  if (state.phase !== "running" || !state.acceptedParticipantIds.includes(options.selfId) || !state.readyParticipantIds.includes(options.selfId)) {
25753
25930
  throw new Error("A required participant must be accepted, ready, and running before reporting completion");
@@ -25765,6 +25942,7 @@ function createPalsTools(client, options) {
25765
25942
  execute: async (input, signal) => {
25766
25943
  signal.throwIfAborted();
25767
25944
  const state = await assertCurrentToken(client, input);
25945
+ signal.throwIfAborted();
25768
25946
  assertRequiredParticipant(state, options.selfId);
25769
25947
  if (state.integrationOwnerId !== options.selfId) throw new Error("Only the broker-designated integration owner can finalize integration");
25770
25948
  if (state.phase !== "verifying") throw new Error("Co-work is not ready for integration verification");
@@ -25871,8 +26049,8 @@ var ApprovalBridge = class {
25871
26049
  }
25872
26050
  };
25873
26051
  async function createProductionRuntime(options) {
25874
- const workspace = resolve35(options.workspace ?? process.cwd());
25875
- const home = resolve35(options.home ?? homedir3());
26052
+ const workspace = resolve36(options.workspace ?? process.cwd());
26053
+ const home = resolve36(options.home ?? homedir3());
25876
26054
  const environment = options.environment ?? process.env;
25877
26055
  const ide = new FlavorIdeClient({ workspace, home, environment });
25878
26056
  const ideReady = ide.initialize();
@@ -26046,6 +26224,7 @@ async function createProductionRuntime(options) {
26046
26224
  const executionEnvironment = createExecutionEnvironment(workspace, config.execution);
26047
26225
  const jobs = new JobRegistry();
26048
26226
  const terminals = new TerminalService(workspace, { jobs });
26227
+ const lspManager = new RealLspManager({ workspace, onStatus: (message2) => emitOutput({ type: "notice", message: message2 }) });
26049
26228
  const observations = new FileObservationStore();
26050
26229
  const workspaceInstructions = new WorkspaceInstructions(workspace);
26051
26230
  const fileMutationOptions = {
@@ -26069,9 +26248,7 @@ async function createProductionRuntime(options) {
26069
26248
  createWebSearchTool(),
26070
26249
  ...createJobTools(jobs),
26071
26250
  ...createTerminalTools(terminals, workspace),
26072
- ...createLspTools(workspace, {
26073
- onStatus: (message2) => emitOutput({ type: "notice", message: message2 })
26074
- }),
26251
+ ...createLspTools(workspace, { manager: lspManager }),
26075
26252
  ...options.approvalPolicy === "deny" ? [] : [createAskUserQuestionTool(askUserQuestionHandler)],
26076
26253
  createTaskOutputTool(),
26077
26254
  createTodoWriteTool()
@@ -26608,6 +26785,8 @@ async function createProductionRuntime(options) {
26608
26785
  permissionPolicy,
26609
26786
  maxIterationsMain: config.maxIterations.main,
26610
26787
  maxIterationsSubagent: config.maxIterations.subagent,
26788
+ softLimitFactor: config.maxIterations.softLimitFactor,
26789
+ extendIterations: config.maxIterations.extendBy,
26611
26790
  hasActiveProgress,
26612
26791
  afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
26613
26792
  toolJournal: {
@@ -26999,6 +27178,8 @@ async function createProductionRuntime(options) {
26999
27178
  permissionPolicy,
27000
27179
  maxIterationsMain: config.maxIterations.main,
27001
27180
  maxIterationsSubagent: config.maxIterations.subagent,
27181
+ softLimitFactor: config.maxIterations.softLimitFactor,
27182
+ extendIterations: config.maxIterations.extendBy,
27002
27183
  loopMode: true,
27003
27184
  afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
27004
27185
  toolJournal: {
@@ -27288,9 +27469,9 @@ async function createProductionRuntime(options) {
27288
27469
  explain: (query, focus, signal, onText) => runExplain({
27289
27470
  graph: new DesktopAstGraphService(workspace),
27290
27471
  readFile: async (path) => {
27291
- const root = resolve35(workspace);
27292
- const abs = resolve35(root, path);
27293
- if (abs !== root && !abs.startsWith(root + sep24)) throw new Error(`path outside workspace: ${path}`);
27472
+ const root = resolve36(workspace);
27473
+ const abs = resolve36(root, path);
27474
+ if (abs !== root && !abs.startsWith(root + sep25)) throw new Error(`path outside workspace: ${path}`);
27294
27475
  return readFile28(abs, "utf8");
27295
27476
  },
27296
27477
  history: async (path, limit) => fileHistory(workspace, path, limit),
@@ -27642,6 +27823,7 @@ ${renderMemoryDocument(entries)}`;
27642
27823
  if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
27643
27824
  if (islandControlServer !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "island-control", islandControlServer.close());
27644
27825
  terminals.dispose();
27826
+ await boundedStep(stepTimeoutMs, diagnostics, "language-servers", lspManager.dispose());
27645
27827
  await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
27646
27828
  auditLogger.close();
27647
27829
  evolveService.dispose();
@@ -27682,6 +27864,7 @@ ${renderMemoryDocument(entries)}`;
27682
27864
  if (sleepScheduler !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "sleep-scheduler", sleepScheduler.dispose());
27683
27865
  if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
27684
27866
  terminals.dispose();
27867
+ await boundedStep(stepTimeoutMs, diagnostics, "language-servers", lspManager.dispose());
27685
27868
  await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
27686
27869
  unsubscribeCollaboration?.();
27687
27870
  pendingCollaborationEvents.length = 0;
@@ -27835,9 +28018,9 @@ async function workspaceFingerprint(workspace) {
27835
28018
  let remaining = 5 * 1024 * 1024;
27836
28019
  for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
27837
28020
  if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
27838
- const path = resolve35(workspace, name);
27839
- const relativePath = relative24(resolve35(workspace), path);
27840
- if (relativePath === ".." || relativePath.startsWith(`..${sep24}`)) continue;
28021
+ const path = resolve36(workspace, name);
28022
+ const relativePath = relative25(resolve36(workspace), path);
28023
+ if (relativePath === ".." || relativePath.startsWith(`..${sep25}`)) continue;
27841
28024
  hash.update(name);
27842
28025
  if (remaining <= 0) continue;
27843
28026
  try {
@@ -27969,7 +28152,7 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
27969
28152
  previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
27970
28153
  } catch {
27971
28154
  }
27972
- mkdirSync4(dirname24(markerPath), { recursive: true });
28155
+ mkdirSync3(dirname22(markerPath), { recursive: true });
27973
28156
  writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
27974
28157
  `, "utf8");
27975
28158
  } catch {
@@ -28504,7 +28687,7 @@ function createMemoryTaskId() {
28504
28687
  import { createHash as createHash27 } from "crypto";
28505
28688
  import { mkdir as mkdir27, readFile as readFile36, readdir as readdir13, rm as rm15, stat as stat16, writeFile as writeFile17 } from "fs/promises";
28506
28689
  import { homedir as homedir5 } from "os";
28507
- import { basename as basename10, join as join47, resolve as resolve38 } from "path";
28690
+ import { basename as basename10, join as join47, resolve as resolve39 } from "path";
28508
28691
 
28509
28692
  // src/d2c/workflow.ts
28510
28693
  import { createHash as createHash22 } from "crypto";
@@ -29032,7 +29215,7 @@ function confirmApiMapping(mapping, operationKey, operations) {
29032
29215
 
29033
29216
  // src/d2c/modules.ts
29034
29217
  import { readFile as readFile29 } from "fs/promises";
29035
- import { isAbsolute as isAbsolute17, join as join39, normalize as normalize2, posix, sep as sep25, win32 as win322 } from "path";
29218
+ import { isAbsolute as isAbsolute18, join as join39, normalize as normalize2, posix, sep as sep26, win32 as win322 } from "path";
29036
29219
  import { z as z43 } from "zod";
29037
29220
  var ModuleSchema = z43.object({
29038
29221
  id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
@@ -29044,10 +29227,10 @@ var ModuleSchema = z43.object({
29044
29227
  }).strict();
29045
29228
  var ManifestSchema3 = z43.object({ schema: z43.literal(1), modules: z43.array(ModuleSchema).min(1).max(500) }).passthrough();
29046
29229
  function safeSource(path) {
29047
- if (isAbsolute17(path) || posix.isAbsolute(path) || win322.isAbsolute(path) || path.includes("\0")) return false;
29230
+ if (isAbsolute18(path) || posix.isAbsolute(path) || win322.isAbsolute(path) || path.includes("\0")) return false;
29048
29231
  const value = normalize2(path);
29049
29232
  const portableSegments = path.replaceAll("\\", "/").split("/");
29050
- return value !== ".." && !value.startsWith(`..${sep25}`) && !portableSegments.includes("..");
29233
+ return value !== ".." && !value.startsWith(`..${sep26}`) && !portableSegments.includes("..");
29051
29234
  }
29052
29235
  function d2cOutputDirectory(workspace, task) {
29053
29236
  if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
@@ -29270,7 +29453,7 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
29270
29453
  }
29271
29454
 
29272
29455
  // src/d2c/mock-runner.ts
29273
- import { spawn as spawn8 } from "child_process";
29456
+ import { spawn as spawn9 } from "child_process";
29274
29457
  import { access as access4, readFile as readFile31 } from "fs/promises";
29275
29458
  import { createServer as createServer5 } from "net";
29276
29459
  import { join as join41 } from "path";
@@ -29293,7 +29476,7 @@ async function port() {
29293
29476
  });
29294
29477
  });
29295
29478
  }
29296
- function waitForProcess2(child, timeoutMs, label) {
29479
+ function waitForProcess(child, timeoutMs, label) {
29297
29480
  return new Promise((resolvePromise, rejectPromise) => {
29298
29481
  const timer = setTimeout(() => {
29299
29482
  void terminateProcessTree(child, true);
@@ -29323,7 +29506,7 @@ async function install(projectDir, signal) {
29323
29506
  if (key.toLowerCase() === "npm_config_allow_scripts") delete env[key];
29324
29507
  }
29325
29508
  }
29326
- const child = spawn8(command, args, { cwd: projectDir, env, shell: false, windowsHide: true });
29509
+ const child = spawn9(command, args, { cwd: projectDir, env, shell: false, windowsHide: true });
29327
29510
  let output = "";
29328
29511
  const append = (chunk) => {
29329
29512
  output = (output + chunk.toString("utf8")).slice(-8e3);
@@ -29335,7 +29518,7 @@ async function install(projectDir, signal) {
29335
29518
  };
29336
29519
  signal?.addEventListener("abort", abort2, { once: true });
29337
29520
  try {
29338
- await waitForProcess2(child, 18e4, "D2C mock dependency install");
29521
+ await waitForProcess(child, 18e4, "D2C mock dependency install");
29339
29522
  return output;
29340
29523
  } catch (error) {
29341
29524
  throw new Error(`${error instanceof Error ? error.message : String(error)}${output.trim() ? `
@@ -29355,7 +29538,7 @@ ${output.trim()}` : ""}`);
29355
29538
  async function installPython(serverDir, signal) {
29356
29539
  signal?.throwIfAborted();
29357
29540
  const executable = process.env.FLAVOR_PYTHON?.trim() || (process.platform === "win32" ? "python" : "python3");
29358
- const child = spawn8(executable, ["-m", "pip", "install", "-r", "requirements.txt"], {
29541
+ const child = spawn9(executable, ["-m", "pip", "install", "-r", "requirements.txt"], {
29359
29542
  cwd: serverDir,
29360
29543
  env: process.env,
29361
29544
  shell: false,
@@ -29372,7 +29555,7 @@ async function installPython(serverDir, signal) {
29372
29555
  };
29373
29556
  signal?.addEventListener("abort", abort2, { once: true });
29374
29557
  try {
29375
- await waitForProcess2(child, 18e4, "E2E backend dependency install");
29558
+ await waitForProcess(child, 18e4, "E2E backend dependency install");
29376
29559
  } catch (error) {
29377
29560
  throw new Error(`${error instanceof Error ? error.message : String(error)}${output.trim() ? `
29378
29561
  ${output.trim()}` : ""}`);
@@ -29386,7 +29569,7 @@ async function runPythonBackend(projectDir, options) {
29386
29569
  const selectedPort = await port();
29387
29570
  const url = `http://127.0.0.1:${selectedPort}`;
29388
29571
  const executable = process.env.FLAVOR_PYTHON?.trim() || (process.platform === "win32" ? "python" : "python3");
29389
- const child = spawn8(executable, ["-m", "uvicorn", "main:app", "--host", "127.0.0.1", "--port", String(selectedPort)], {
29572
+ const child = spawn9(executable, ["-m", "uvicorn", "main:app", "--host", "127.0.0.1", "--port", String(selectedPort)], {
29390
29573
  cwd: serverDir,
29391
29574
  windowsHide: true,
29392
29575
  shell: false,
@@ -29446,7 +29629,7 @@ async function runD2cMockServer(projectDir, options = {}) {
29446
29629
  if (options.installDependencies !== false) await install(projectDir, options.signal);
29447
29630
  const selectedPort = await port();
29448
29631
  const url = `http://127.0.0.1:${selectedPort}`;
29449
- const child = spawn8(process.execPath, [serverPath], {
29632
+ const child = spawn9(process.execPath, [serverPath], {
29450
29633
  cwd: projectDir,
29451
29634
  windowsHide: true,
29452
29635
  env: { ...process.env, D2C_MOCK_PORT: String(selectedPort), ELECTRON_RUN_AS_NODE: "1" }
@@ -30189,14 +30372,14 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
30189
30372
  // src/skills/manager.ts
30190
30373
  import { lstat as lstat14, mkdir as mkdir26, readFile as readFile35, readdir as readdir12, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
30191
30374
  import { homedir as homedir4 } from "os";
30192
- import { isAbsolute as isAbsolute18, join as join44, relative as relative25, resolve as resolve36, sep as sep26 } from "path";
30375
+ import { isAbsolute as isAbsolute19, join as join44, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
30193
30376
  import { stringify } from "yaml";
30194
30377
  var SkillManager = class {
30195
30378
  #workspace;
30196
30379
  #home;
30197
30380
  constructor(options) {
30198
- this.#workspace = resolve36(options.workspace);
30199
- this.#home = resolve36(options.home ?? homedir4());
30381
+ this.#workspace = resolve37(options.workspace);
30382
+ this.#home = resolve37(options.home ?? homedir4());
30200
30383
  }
30201
30384
  async list() {
30202
30385
  const { registry, disabled } = await this.#registry();
@@ -30340,12 +30523,12 @@ async function discoverPluginSkillRoots(directory) {
30340
30523
  try {
30341
30524
  const manifest = PluginManifestSchema.parse(JSON.parse(await readFile35(join44(pluginRoot, "flavor-plugin.json"), "utf8")));
30342
30525
  for (const contribution of manifest.contributes.skillRoots) {
30343
- const candidate = resolve36(pluginRoot, contribution.path);
30526
+ const candidate = resolve37(pluginRoot, contribution.path);
30344
30527
  const info = await lstat14(candidate);
30345
30528
  if (!info.isDirectory() || info.isSymbolicLink()) continue;
30346
30529
  const physical = await realpath13(candidate);
30347
- const relation = relative25(pluginRoot, physical);
30348
- if (relation === ".." || relation.startsWith(`..${sep26}`) || isAbsolute18(relation)) continue;
30530
+ const relation = relative26(pluginRoot, physical);
30531
+ if (relation === ".." || relation.startsWith(`..${sep27}`) || isAbsolute19(relation)) continue;
30349
30532
  roots.push(physical);
30350
30533
  }
30351
30534
  } catch {
@@ -30605,7 +30788,7 @@ function protocolLabel(type) {
30605
30788
  import { createReadStream as createReadStream2 } from "fs";
30606
30789
  import { realpath as realpath14, stat as stat15 } from "fs/promises";
30607
30790
  import { createServer as createServer6 } from "http";
30608
- import { extname as extname4, isAbsolute as isAbsolute19, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
30791
+ import { extname as extname4, isAbsolute as isAbsolute20, relative as relative27, resolve as resolve38, sep as sep28 } from "path";
30609
30792
  var MIME_TYPES = {
30610
30793
  ".css": "text/css; charset=utf-8",
30611
30794
  ".gif": "image/gif",
@@ -30623,11 +30806,11 @@ function sendNotFound(response2) {
30623
30806
  response2.end("Not found");
30624
30807
  }
30625
30808
  function isInside(root, candidate) {
30626
- const delta = relative26(root, candidate);
30627
- return delta === "" || !delta.startsWith(`..${sep27}`) && delta !== ".." && !isAbsolute19(delta);
30809
+ const delta = relative27(root, candidate);
30810
+ return delta === "" || !delta.startsWith(`..${sep28}`) && delta !== ".." && !isAbsolute20(delta);
30628
30811
  }
30629
30812
  async function startD2cProductPreview(rootDirectory) {
30630
- const root = await realpath14(resolve37(rootDirectory));
30813
+ const root = await realpath14(resolve38(rootDirectory));
30631
30814
  const rootInfo = await stat15(root);
30632
30815
  if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
30633
30816
  const server = createServer6(async (request, response2) => {
@@ -30639,7 +30822,7 @@ async function startD2cProductPreview(rootDirectory) {
30639
30822
  let pathname = decodeURIComponent(url.pathname);
30640
30823
  if (pathname === "/") pathname = "/index.html";
30641
30824
  if (pathname.includes("\0")) return sendNotFound(response2);
30642
- const unresolved = resolve37(root, `.${pathname}`);
30825
+ const unresolved = resolve38(root, `.${pathname}`);
30643
30826
  if (!isInside(root, unresolved)) return sendNotFound(response2);
30644
30827
  const target = await realpath14(unresolved).catch(() => void 0);
30645
30828
  if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
@@ -30758,7 +30941,7 @@ var DesktopRuntimeController = class {
30758
30941
  /** Backend source digest observed when the managed process was started. */
30759
30942
  #d2cMockSourceFingerprints = /* @__PURE__ */ new Map();
30760
30943
  constructor(options) {
30761
- this.#home = resolve38(options.home ?? homedir5());
30944
+ this.#home = resolve39(options.home ?? homedir5());
30762
30945
  this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
30763
30946
  this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
30764
30947
  this.#listSessions = options.listSessions ?? (async (workspace) => {
@@ -30833,7 +31016,7 @@ var DesktopRuntimeController = class {
30833
31016
  };
30834
31017
  }
30835
31018
  async openWorkspace(path) {
30836
- const workspace = resolve38(path);
31019
+ const workspace = resolve39(path);
30837
31020
  if (workspace !== this.#workspace) {
30838
31021
  await this.#disposeRuntime();
30839
31022
  await this.#stopAllD2cPreviews();
@@ -32055,7 +32238,7 @@ async function loginShellEnvironment(environment, platform = process.platform, d
32055
32238
 
32056
32239
  // src/desktop/git-manager.ts
32057
32240
  import { readFile as readFile37, rm as rm16 } from "fs/promises";
32058
- import { isAbsolute as isAbsolute20, relative as relative27, resolve as resolve39 } from "path";
32241
+ import { isAbsolute as isAbsolute21, relative as relative28, resolve as resolve40 } from "path";
32059
32242
  function failure(action, stderr, stdout) {
32060
32243
  return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
32061
32244
  }
@@ -32148,13 +32331,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
32148
32331
  return path.replaceAll("\\", "/");
32149
32332
  }));
32150
32333
  try {
32151
- const tree = JSON.parse(await readFile37(resolve39(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
32334
+ const tree = JSON.parse(await readFile37(resolve40(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
32152
32335
  const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
32153
32336
  const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
32154
32337
  if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
32155
32338
  const load = async (id) => {
32156
32339
  if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
32157
- return JSON.parse(await readFile37(resolve39(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
32340
+ return JSON.parse(await readFile37(resolve40(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
32158
32341
  };
32159
32342
  const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
32160
32343
  const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
@@ -32163,7 +32346,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
32163
32346
  const object = async (digest) => {
32164
32347
  if (digest === void 0) return "";
32165
32348
  if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
32166
- const value = await readFile37(resolve39(workspace, ".flavor", "checkpoints", "objects", digest));
32349
+ const value = await readFile37(resolve40(workspace, ".flavor", "checkpoints", "objects", digest));
32167
32350
  return value.includes(0) ? "[binary content]" : value.toString("utf8");
32168
32351
  };
32169
32352
  const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
@@ -32187,11 +32370,11 @@ async function assertListed(workspace, path) {
32187
32370
  return file;
32188
32371
  }
32189
32372
  function safeWorkspacePath(workspace, path) {
32190
- if (isAbsolute20(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
32191
- const root = resolve39(workspace);
32192
- const target = resolve39(root, path);
32193
- const child = relative27(root, target);
32194
- if (child === "" || child.startsWith("..") || isAbsolute20(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
32373
+ if (isAbsolute21(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
32374
+ const root = resolve40(workspace);
32375
+ const target = resolve40(root, path);
32376
+ const child = relative28(root, target);
32377
+ if (child === "" || child.startsWith("..") || isAbsolute21(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
32195
32378
  return target;
32196
32379
  }
32197
32380
  function safeGitTarget(value) {
@@ -32203,13 +32386,13 @@ function safeGitTarget(value) {
32203
32386
  // src/desktop/worktree-manager.ts
32204
32387
  import { randomUUID as randomUUID28 } from "crypto";
32205
32388
  import { mkdir as mkdir28, readFile as readFile38, readdir as readdir14, rm as rm17, stat as stat17, writeFile as writeFile18 } from "fs/promises";
32206
- import { basename as basename11, isAbsolute as isAbsolute21, join as join48, relative as relative28, resolve as resolve40, sep as sep28 } from "path";
32389
+ import { basename as basename11, isAbsolute as isAbsolute22, join as join48, relative as relative29, resolve as resolve41, sep as sep29 } from "path";
32207
32390
  var DesktopWorktreeManager = class {
32208
32391
  #repository;
32209
32392
  #storage;
32210
32393
  constructor(options) {
32211
- this.#repository = resolve40(options.repository);
32212
- this.#storage = resolve40(options.storage);
32394
+ this.#repository = resolve41(options.repository);
32395
+ this.#storage = resolve41(options.storage);
32213
32396
  }
32214
32397
  async create(label) {
32215
32398
  if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
@@ -32290,21 +32473,21 @@ var DesktopWorktreeManager = class {
32290
32473
  return this.#safeStoragePath(`${id}.json`);
32291
32474
  }
32292
32475
  #safeStoragePath(name) {
32293
- const target = resolve40(this.#storage, name);
32294
- const delta = relative28(this.#storage, target);
32295
- if (delta === "" || delta === ".." || delta.startsWith(`..${sep28}`) || isAbsolute21(delta)) throw new Error("Worktree path escapes storage");
32476
+ const target = resolve41(this.#storage, name);
32477
+ const delta = relative29(this.#storage, target);
32478
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep29}`) || isAbsolute22(delta)) throw new Error("Worktree path escapes storage");
32296
32479
  return target;
32297
32480
  }
32298
32481
  };
32299
32482
  function validateStored(value, repository, storage) {
32300
32483
  if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
32301
32484
  const item = value;
32302
- if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve40(item.repository ?? "") !== repository) {
32485
+ if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve41(item.repository ?? "") !== repository) {
32303
32486
  throw new Error("Invalid worktree metadata");
32304
32487
  }
32305
- const path = resolve40(item.path);
32306
- const delta = relative28(storage, path);
32307
- if (delta === "" || delta === ".." || delta.startsWith(`..${sep28}`) || isAbsolute21(delta) || basename11(path) !== item.id) {
32488
+ const path = resolve41(item.path);
32489
+ const delta = relative29(storage, path);
32490
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep29}`) || isAbsolute22(delta) || basename11(path) !== item.id) {
32308
32491
  throw new Error("Worktree metadata escapes storage");
32309
32492
  }
32310
32493
  return { version: 1, id: item.id, mode: "worktree", repository, path, branch: item.branch, createdAt: item.createdAt };
@@ -32313,15 +32496,15 @@ function validateStored(value, repository, storage) {
32313
32496
  // src/desktop/workbench-service.ts
32314
32497
  import { readFile as readFile39, readdir as readdir15, stat as stat18 } from "fs/promises";
32315
32498
  import { homedir as homedir6 } from "os";
32316
- import { join as join49, resolve as resolve41 } from "path";
32499
+ import { join as join49, resolve as resolve42 } from "path";
32317
32500
  var MAX_TEXT = 128e3;
32318
32501
  var MAX_ITEMS = 200;
32319
32502
  var DesktopWorkbenchService = class {
32320
32503
  #workspace;
32321
32504
  #home;
32322
32505
  constructor(workspace, home = homedir6()) {
32323
- this.#workspace = resolve41(workspace);
32324
- this.#home = resolve41(home);
32506
+ this.#workspace = resolve42(workspace);
32507
+ this.#home = resolve42(home);
32325
32508
  }
32326
32509
  async inspect(sessionId) {
32327
32510
  const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
@@ -32372,7 +32555,7 @@ var DesktopWorkbenchService = class {
32372
32555
  }
32373
32556
  async #permissionFiles() {
32374
32557
  const candidates = [
32375
- ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve41(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
32558
+ ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve42(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
32376
32559
  { tier: "user", path: join49(this.#home, ".flavor-code", "permissions.json"), content: "" },
32377
32560
  { tier: "project", path: join49(this.#workspace, ".flavor", "permissions.json"), content: "" },
32378
32561
  { tier: "local", path: join49(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
@@ -32445,14 +32628,14 @@ function redact2(value, key = "") {
32445
32628
 
32446
32629
  // src/desktop/main.ts
32447
32630
  installCrashGuard();
32448
- var moduleDirectory = dirname25(fileURLToPath4(import.meta.url));
32631
+ var moduleDirectory = dirname23(fileURLToPath3(import.meta.url));
32449
32632
  var developmentUrl = process.env.FLAVOR_DESKTOP_DEV_URL;
32450
32633
  var mainWindow;
32451
32634
  var appMenu;
32452
32635
  var quitting = false;
32453
32636
  function logStartup(step, detail) {
32454
32637
  try {
32455
- const logPath = join50(dirname25(process.execPath), "flavor-code-startup.log");
32638
+ const logPath = join50(dirname23(process.execPath), "flavor-code-startup.log");
32456
32639
  const ts = (/* @__PURE__ */ new Date()).toISOString();
32457
32640
  appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
32458
32641
  `);
@@ -32502,7 +32685,7 @@ function sessionMetaKey(workspace, sessionId) {
32502
32685
  return `${workspace}\0${sessionId}`;
32503
32686
  }
32504
32687
  function worktreeManager(workspace) {
32505
- const key = createHash28("sha256").update(resolve42(workspace)).digest("hex").slice(0, 20);
32688
+ const key = createHash28("sha256").update(resolve43(workspace)).digest("hex").slice(0, 20);
32506
32689
  return new DesktopWorktreeManager({ repository: workspace, storage: join50(app.getPath("userData"), "task-worktrees", key) });
32507
32690
  }
32508
32691
  function activeWorkingDirectory() {
@@ -32718,7 +32901,7 @@ async function assertDirectory(path) {
32718
32901
  if (!(await stat19(path)).isDirectory()) throw new Error("The selected project is not a directory");
32719
32902
  }
32720
32903
  async function openWorkspace(path) {
32721
- const workspace = resolve42(path);
32904
+ const workspace = resolve43(path);
32722
32905
  await assertDirectory(workspace);
32723
32906
  let project = managedProjects.get(workspace);
32724
32907
  if (project === void 0) {
@@ -32729,10 +32912,10 @@ async function openWorkspace(path) {
32729
32912
  activeWorkspace = workspace;
32730
32913
  controller = nextController;
32731
32914
  project.snapshot = await nextController.openWorkspace(workspace);
32732
- for (const saved of workbench.taskCheckouts.filter((item) => resolve42(item.workspace) === workspace)) {
32915
+ for (const saved of workbench.taskCheckouts.filter((item) => resolve43(item.workspace) === workspace)) {
32733
32916
  try {
32734
32917
  await assertDirectory(saved.workingDirectory);
32735
- const task = { environment: "worktree", workingDirectory: resolve42(saved.workingDirectory), worktreeId: saved.worktreeId };
32918
+ const task = { environment: "worktree", workingDirectory: resolve43(saved.workingDirectory), worktreeId: saved.worktreeId };
32736
32919
  task.controller = createDesktopController(workspace, task);
32737
32920
  task.snapshot = await task.controller.openWorkspace(task.workingDirectory);
32738
32921
  task.sessionId = saved.sessionId;
@@ -32862,7 +33045,7 @@ function installIpcHandlers() {
32862
33045
  } catch {
32863
33046
  }
32864
33047
  }
32865
- if (persisted.workspace !== void 0 && managedProjects.has(resolve42(persisted.workspace))) {
33048
+ if (persisted.workspace !== void 0 && managedProjects.has(resolve43(persisted.workspace))) {
32866
33049
  try {
32867
33050
  return await openWorkspace(persisted.workspace);
32868
33051
  } catch {
@@ -32923,7 +33106,7 @@ function installIpcHandlers() {
32923
33106
  });
32924
33107
  ipcMain.handle(DESKTOP_CHANNELS.updateProject, async (_event, value) => {
32925
33108
  const input = UpdateProjectInputSchema.parse(value);
32926
- const workspace = resolve42(input.workspace);
33109
+ const workspace = resolve43(input.workspace);
32927
33110
  if (!managedProjects.has(workspace)) throw new Error("\u9879\u76EE\u672A\u6253\u5F00");
32928
33111
  workbench.projectMeta[workspace] = { ...workbench.projectMeta[workspace], ...input.label === void 0 ? {} : { label: input.label || void 0 }, ...input.pinned === void 0 ? {} : { pinned: input.pinned } };
32929
33112
  await savePersistedProjects();
@@ -32931,7 +33114,7 @@ function installIpcHandlers() {
32931
33114
  });
32932
33115
  ipcMain.handle(DESKTOP_CHANNELS.closeProject, async (_event, value) => {
32933
33116
  const input = CloseProjectInputSchema.parse(value);
32934
- const workspace = resolve42(input.workspace);
33117
+ const workspace = resolve43(input.workspace);
32935
33118
  const project = managedProjects.get(workspace);
32936
33119
  if (project === void 0) return decorateSnapshot(controller.snapshot());
32937
33120
  if ([...project.tasks].some((task) => task.snapshot.activeSession?.busy) && !input.force) throw new Error("\u9879\u76EE\u4E2D\u4ECD\u6709\u4EFB\u52A1\u8FD0\u884C\uFF1B\u786E\u8BA4\u540E\u518D\u5173\u95ED\u9879\u76EE");
@@ -32947,12 +33130,12 @@ function installIpcHandlers() {
32947
33130
  return decorateSnapshot(controller.snapshot());
32948
33131
  });
32949
33132
  ipcMain.handle(DESKTOP_CHANNELS.revealProject, async (_event, value) => {
32950
- const workspace = resolve42(ProjectPathInputSchema.parse(value).workspace);
33133
+ const workspace = resolve43(ProjectPathInputSchema.parse(value).workspace);
32951
33134
  await assertDirectory(workspace);
32952
33135
  await shell.openPath(workspace);
32953
33136
  });
32954
33137
  ipcMain.handle(DESKTOP_CHANNELS.copyProjectPath, async (_event, value) => {
32955
- clipboard.writeText(resolve42(ProjectPathInputSchema.parse(value).workspace));
33138
+ clipboard.writeText(resolve43(ProjectPathInputSchema.parse(value).workspace));
32956
33139
  });
32957
33140
  ipcMain.handle(DESKTOP_CHANNELS.dismissRecovery, async (_event, value) => {
32958
33141
  const { sessionId } = DeleteSessionInputSchema.parse(value);