sparkecoder 0.1.120 → 0.1.122

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 (148) hide show
  1. package/dist/agent/index.js +182 -21
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +800 -220
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +748 -168
  6. package/dist/index.js.map +1 -1
  7. package/dist/server/index.js +748 -168
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/tools/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/web/.next/BUILD_ID +1 -1
  12. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  13. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  14. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  15. package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
  16. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  17. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  18. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  19. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.nft.json +1 -1
  20. package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  22. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  23. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  38. package/web/.next/standalone/web/.next/server/app/agents.rsc +2 -2
  39. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +2 -2
  43. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +2 -2
  45. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +2 -2
  46. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  83. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  86. package/web/.next/standalone/web/.next/server/app/index.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  92. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  93. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  94. package/web/.next/standalone/web/.next/server/app/settings.rsc +3 -3
  95. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
  96. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  97. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  98. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  99. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  100. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  101. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_6ab1f7b7._.js +3 -0
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f3e6443f._.js → [root-of-the-server]__4de426bd._.js} +2 -2
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/web_62ca4286._.js +3 -0
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +3 -1
  106. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  107. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  108. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  109. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  110. package/web/.next/standalone/web/.next/static/chunks/74ae1f17d607b2fc.js +7 -0
  111. package/web/.next/standalone/web/.next/static/chunks/883ea0d08f88e366.js +1 -0
  112. package/web/.next/standalone/web/.next/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
  113. package/web/.next/standalone/web/.next/static/chunks/9b88f148788e4504.js +3 -0
  114. package/web/.next/standalone/web/.next/static/chunks/acb0fc66f5414af6.css +1 -0
  115. package/web/.next/standalone/web/.next/static/static/chunks/74ae1f17d607b2fc.js +7 -0
  116. package/web/.next/standalone/web/.next/static/static/chunks/883ea0d08f88e366.js +1 -0
  117. package/web/.next/standalone/web/.next/static/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
  118. package/web/.next/standalone/web/.next/static/static/chunks/9b88f148788e4504.js +3 -0
  119. package/web/.next/standalone/web/.next/static/static/chunks/acb0fc66f5414af6.css +1 -0
  120. package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +464 -1
  121. package/web/.next/static/chunks/74ae1f17d607b2fc.js +7 -0
  122. package/web/.next/static/chunks/883ea0d08f88e366.js +1 -0
  123. package/web/.next/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
  124. package/web/.next/static/chunks/9b88f148788e4504.js +3 -0
  125. package/web/.next/static/chunks/acb0fc66f5414af6.css +1 -0
  126. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_7340c8b3._.js +0 -3
  127. package/web/.next/standalone/web/.next/server/chunks/ssr/web_41927ef5._.js +0 -3
  128. package/web/.next/standalone/web/.next/static/chunks/2c3c1d478808e4e4.js +0 -1
  129. package/web/.next/standalone/web/.next/static/chunks/3b0501ec3249235f.js +0 -7
  130. package/web/.next/standalone/web/.next/static/chunks/9a3afb48c245fb2a.js +0 -1
  131. package/web/.next/standalone/web/.next/static/chunks/b3bc7244f3477729.css +0 -1
  132. package/web/.next/standalone/web/.next/static/static/chunks/2c3c1d478808e4e4.js +0 -1
  133. package/web/.next/standalone/web/.next/static/static/chunks/3b0501ec3249235f.js +0 -7
  134. package/web/.next/standalone/web/.next/static/static/chunks/9a3afb48c245fb2a.js +0 -1
  135. package/web/.next/standalone/web/.next/static/static/chunks/b3bc7244f3477729.css +0 -1
  136. package/web/.next/static/chunks/2c3c1d478808e4e4.js +0 -1
  137. package/web/.next/static/chunks/3b0501ec3249235f.js +0 -7
  138. package/web/.next/static/chunks/9a3afb48c245fb2a.js +0 -1
  139. package/web/.next/static/chunks/b3bc7244f3477729.css +0 -1
  140. /package/web/.next/standalone/web/.next/static/{static/uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
  141. /package/web/.next/standalone/web/.next/static/{static/uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
  142. /package/web/.next/standalone/web/.next/static/{static/uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
  143. /package/web/.next/standalone/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → static/BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
  144. /package/web/.next/standalone/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → static/BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
  145. /package/web/.next/standalone/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → static/BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
  146. /package/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
  147. /package/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
  148. /package/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
@@ -963,6 +963,7 @@ __export(config_exports, {
963
963
  setApiKey: () => setApiKey,
964
964
  setMcpServers: () => setMcpServers,
965
965
  setPublicUrl: () => setPublicUrl,
966
+ setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
966
967
  setSlackConfig: () => setSlackConfig,
967
968
  setWebhookToken: () => setWebhookToken
968
969
  });
@@ -1252,6 +1253,40 @@ function setWebhookToken(token) {
1252
1253
  console.warn("[config] failed to persist webhook token:", err?.message || err);
1253
1254
  }
1254
1255
  }
1256
+ function setSkillsAdditionalDirectories(directories) {
1257
+ if (cachedConfig) {
1258
+ const cur = cachedConfig.skills || {};
1259
+ cachedConfig.skills = { ...cur, additionalDirectories: directories };
1260
+ try {
1261
+ const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
1262
+ const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
1263
+ cachedConfig.discoveredSkills = discovered;
1264
+ cachedConfig.resolvedSkillsDirectories = [
1265
+ ...discovered.allDirectories,
1266
+ ...additionalAbs
1267
+ ];
1268
+ } catch {
1269
+ }
1270
+ }
1271
+ try {
1272
+ const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
1273
+ const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
1274
+ let raw = {};
1275
+ if (existsSync(target)) {
1276
+ try {
1277
+ raw = JSON.parse(readFileSync(target, "utf-8"));
1278
+ } catch {
1279
+ raw = {};
1280
+ }
1281
+ } else {
1282
+ raw = createDefaultConfig();
1283
+ }
1284
+ raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
1285
+ writeFileSync(target, JSON.stringify(raw, null, 2));
1286
+ } catch (err) {
1287
+ console.warn("[config] failed to persist skill directories:", err?.message || err);
1288
+ }
1289
+ }
1255
1290
  function setPublicUrl(publicUrl) {
1256
1291
  if (cachedConfig) {
1257
1292
  cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
@@ -2294,10 +2329,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2294
2329
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
2295
2330
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
2296
2331
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
2297
- const cachePath = join3(cacheDir, key2 + ext);
2298
- if (existsSync3(cachePath)) {
2332
+ const cachePath2 = join3(cacheDir, key2 + ext);
2333
+ if (existsSync3(cachePath2)) {
2299
2334
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
2300
- return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
2335
+ return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
2301
2336
  }
2302
2337
  let pipeline = sharp(buffer);
2303
2338
  if (needsResize) {
@@ -2322,7 +2357,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2322
2357
  }
2323
2358
  finalMediaType = "image/jpeg";
2324
2359
  }
2325
- writeFileSync2(cachePath, result);
2360
+ writeFileSync2(cachePath2, result);
2326
2361
  const resultMeta = await sharp(result).metadata();
2327
2362
  console.log(
2328
2363
  `[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
@@ -3013,7 +3048,7 @@ async function createClient(serverId, handle, root) {
3013
3048
  },
3014
3049
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
3015
3050
  const normalized = normalizePath(filePath);
3016
- return new Promise((resolve12) => {
3051
+ return new Promise((resolve13) => {
3017
3052
  const startTime = Date.now();
3018
3053
  let debounceTimer;
3019
3054
  let resolved = false;
@@ -3032,7 +3067,7 @@ async function createClient(serverId, handle, root) {
3032
3067
  if (resolved) return;
3033
3068
  resolved = true;
3034
3069
  cleanup2();
3035
- resolve12(diagnostics.get(normalized) || []);
3070
+ resolve13(diagnostics.get(normalized) || []);
3036
3071
  };
3037
3072
  const onDiagnostic = () => {
3038
3073
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -3407,7 +3442,7 @@ Working directory: ${options.workingDirectory}`,
3407
3442
  isChunked: true
3408
3443
  });
3409
3444
  if (chunkCount > 1) {
3410
- await new Promise((resolve12) => setTimeout(resolve12, 0));
3445
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
3411
3446
  }
3412
3447
  }
3413
3448
  }
@@ -4026,7 +4061,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4026
4061
  if (!existsSync10(directory)) {
4027
4062
  return [];
4028
4063
  }
4029
- const skills = [];
4064
+ const skills2 = [];
4030
4065
  const entries = await readdir(directory, { withFileTypes: true });
4031
4066
  for (const entry2 of entries) {
4032
4067
  let filePath;
@@ -4050,7 +4085,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4050
4085
  if (parsed) {
4051
4086
  const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
4052
4087
  const loadType = alwaysApply ? "always" : defaultLoadType;
4053
- skills.push({
4088
+ skills2.push({
4054
4089
  name: parsed.metadata.name,
4055
4090
  description: parsed.metadata.description,
4056
4091
  filePath,
@@ -4064,7 +4099,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4064
4099
  } else {
4065
4100
  const name = getSkillNameFromPath(filePath);
4066
4101
  const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
4067
- skills.push({
4102
+ skills2.push({
4068
4103
  name,
4069
4104
  description: firstParagraph.replace(/^#\s*/, "").trim(),
4070
4105
  filePath,
@@ -4077,7 +4112,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4077
4112
  });
4078
4113
  }
4079
4114
  }
4080
- return skills.filter(
4115
+ return skills2.filter(
4081
4116
  (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
4082
4117
  );
4083
4118
  }
@@ -4085,8 +4120,8 @@ async function loadAllSkills(directories) {
4085
4120
  const allSkills = [];
4086
4121
  const seenNames = /* @__PURE__ */ new Set();
4087
4122
  for (const dir of directories) {
4088
- const skills = await loadSkillsFromDirectory(dir);
4089
- for (const skill of skills) {
4123
+ const skills2 = await loadSkillsFromDirectory(dir);
4124
+ for (const skill of skills2) {
4090
4125
  if (!seenNames.has(skill.name.toLowerCase())) {
4091
4126
  seenNames.add(skill.name.toLowerCase());
4092
4127
  allSkills.push(skill);
@@ -4099,12 +4134,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4099
4134
  const allSkills = [];
4100
4135
  const seenNames = /* @__PURE__ */ new Set();
4101
4136
  for (const { path, priority } of discovered.alwaysLoadedDirs) {
4102
- const skills = await loadSkillsFromDirectory(path, {
4137
+ const skills2 = await loadSkillsFromDirectory(path, {
4103
4138
  priority,
4104
4139
  defaultLoadType: "always",
4105
4140
  forceAlwaysApply: true
4106
4141
  });
4107
- for (const skill of skills) {
4142
+ for (const skill of skills2) {
4108
4143
  if (!seenNames.has(skill.name.toLowerCase())) {
4109
4144
  seenNames.add(skill.name.toLowerCase());
4110
4145
  allSkills.push(skill);
@@ -4112,12 +4147,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4112
4147
  }
4113
4148
  }
4114
4149
  for (const { path, priority } of discovered.onDemandDirs) {
4115
- const skills = await loadSkillsFromDirectory(path, {
4150
+ const skills2 = await loadSkillsFromDirectory(path, {
4116
4151
  priority,
4117
4152
  defaultLoadType: "on_demand",
4118
4153
  forceAlwaysApply: false
4119
4154
  });
4120
- for (const skill of skills) {
4155
+ for (const skill of skills2) {
4121
4156
  if (!seenNames.has(skill.name.toLowerCase())) {
4122
4157
  seenNames.add(skill.name.toLowerCase());
4123
4158
  allSkills.push(skill);
@@ -4142,7 +4177,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
4142
4177
  all: allSkills
4143
4178
  };
4144
4179
  }
4145
- async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4180
+ async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
4146
4181
  if (activeFiles.length === 0) {
4147
4182
  return [];
4148
4183
  }
@@ -4152,7 +4187,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4152
4187
  }
4153
4188
  return f;
4154
4189
  });
4155
- const matchedSkills = skills.filter((skill) => {
4190
+ const matchedSkills = skills2.filter((skill) => {
4156
4191
  if (skill.alwaysApply || skill.loadType === "always") {
4157
4192
  return false;
4158
4193
  }
@@ -4198,8 +4233,8 @@ async function loadSkillContent(skillName, directories) {
4198
4233
  content: parsed ? parsed.body : content
4199
4234
  };
4200
4235
  }
4201
- function formatSkillsForContext(skills) {
4202
- const onDemandSkills = skills.filter((s) => !s.alwaysApply && s.loadType !== "always");
4236
+ function formatSkillsForContext(skills2) {
4237
+ const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
4203
4238
  if (onDemandSkills.length === 0) {
4204
4239
  return "No on-demand skills available.";
4205
4240
  }
@@ -4210,12 +4245,12 @@ function formatSkillsForContext(skills) {
4210
4245
  }
4211
4246
  return lines.join("\n");
4212
4247
  }
4213
- function formatAlwaysLoadedSkills(skills) {
4214
- if (skills.length === 0) {
4248
+ function formatAlwaysLoadedSkills(skills2) {
4249
+ if (skills2.length === 0) {
4215
4250
  return "";
4216
4251
  }
4217
4252
  const sections = [];
4218
- for (const skill of skills) {
4253
+ for (const skill of skills2) {
4219
4254
  sections.push(`### ${skill.name}
4220
4255
 
4221
4256
  ${skill.content}`);
@@ -4224,12 +4259,12 @@ ${skill.content}`);
4224
4259
 
4225
4260
  ${sections.join("\n\n---\n\n")}`;
4226
4261
  }
4227
- function formatGlobMatchedSkills(skills) {
4228
- if (skills.length === 0) {
4262
+ function formatGlobMatchedSkills(skills2) {
4263
+ if (skills2.length === 0) {
4229
4264
  return "";
4230
4265
  }
4231
4266
  const sections = [];
4232
- for (const skill of skills) {
4267
+ for (const skill of skills2) {
4233
4268
  sections.push(`### ${skill.name}
4234
4269
 
4235
4270
  ${skill.content}`);
@@ -4271,16 +4306,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
4271
4306
  try {
4272
4307
  switch (action) {
4273
4308
  case "list": {
4274
- const skills = await loadAllSkills(options.skillsDirectories);
4309
+ const skills2 = await loadAllSkills(options.skillsDirectories);
4275
4310
  return {
4276
4311
  success: true,
4277
4312
  action: "list",
4278
- skillCount: skills.length,
4279
- skills: skills.map((s) => ({
4313
+ skillCount: skills2.length,
4314
+ skills: skills2.map((s) => ({
4280
4315
  name: s.name,
4281
4316
  description: s.description
4282
4317
  })),
4283
- formatted: formatSkillsForContext(skills)
4318
+ formatted: formatSkillsForContext(skills2)
4284
4319
  };
4285
4320
  }
4286
4321
  case "load": {
@@ -4718,8 +4753,8 @@ var init_subagent = __esm({
4718
4753
  if (eventQueue.length > 0) {
4719
4754
  yield eventQueue.shift();
4720
4755
  } else if (!done) {
4721
- const event = await new Promise((resolve12) => {
4722
- resolveNext = resolve12;
4756
+ const event = await new Promise((resolve13) => {
4757
+ resolveNext = resolve13;
4723
4758
  });
4724
4759
  if (event) {
4725
4760
  yield event;
@@ -6495,8 +6530,8 @@ async function buildSystemPrompt(options) {
6495
6530
  }
6496
6531
  } else {
6497
6532
  const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
6498
- const skills = await loadAllSkills2(skillsDirectories);
6499
- onDemandSkillsContext = formatSkillsForContext(skills);
6533
+ const skills2 = await loadAllSkills2(skillsDirectories);
6534
+ onDemandSkillsContext = formatSkillsForContext(skills2);
6500
6535
  }
6501
6536
  const todos = await todoQueries.getBySession(sessionId);
6502
6537
  const todosContext = formatTodosForContext(todos);
@@ -7395,6 +7430,35 @@ function stripOrphanedToolResults(msg, removedIds) {
7395
7430
  if (parts.length === 0) return null;
7396
7431
  return { ...msg, content: parts };
7397
7432
  }
7433
+ function wrapToolsNeverThrow(tools) {
7434
+ if (!tools || typeof tools !== "object") return tools;
7435
+ const wrapped = {};
7436
+ for (const [name, t] of Object.entries(tools)) {
7437
+ if (!t || typeof t.execute !== "function") {
7438
+ wrapped[name] = t;
7439
+ continue;
7440
+ }
7441
+ const original = t.execute;
7442
+ wrapped[name] = {
7443
+ ...t,
7444
+ execute: async (input, opts) => {
7445
+ try {
7446
+ return await original.call(t, input, opts);
7447
+ } catch (err) {
7448
+ const message = err?.message ?? String(err);
7449
+ console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
7450
+ return {
7451
+ __error: true,
7452
+ tool: name,
7453
+ message,
7454
+ note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
7455
+ };
7456
+ }
7457
+ }
7458
+ };
7459
+ }
7460
+ return wrapped;
7461
+ }
7398
7462
  function repairToolPairing(messages) {
7399
7463
  const toolCallIds = /* @__PURE__ */ new Set();
7400
7464
  const toolResultIds = /* @__PURE__ */ new Set();
@@ -7760,6 +7824,120 @@ var init_web = __esm({
7760
7824
  }
7761
7825
  });
7762
7826
 
7827
+ // src/integrations/slack/persistence.ts
7828
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
7829
+ import { join as join9, dirname as dirname6 } from "path";
7830
+ function cachePath() {
7831
+ return join9(ensureAppDataDirectory(), FILENAME);
7832
+ }
7833
+ function load() {
7834
+ if (loaded) return;
7835
+ loaded = true;
7836
+ const path = cachePath();
7837
+ if (!existsSync16(path)) return;
7838
+ try {
7839
+ const raw = readFileSync7(path, "utf-8");
7840
+ const parsed = JSON.parse(raw);
7841
+ if (parsed?.version !== FILE_VERSION) return;
7842
+ const now = Date.now();
7843
+ for (const [id, e] of Object.entries(parsed.users || {})) {
7844
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7845
+ userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
7846
+ }
7847
+ }
7848
+ for (const [key2, e] of Object.entries(parsed.threads || {})) {
7849
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7850
+ threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
7851
+ }
7852
+ }
7853
+ } catch (err) {
7854
+ console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
7855
+ }
7856
+ }
7857
+ function evictIfOversized(map, max) {
7858
+ if (map.size <= max) return;
7859
+ const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
7860
+ const toRemove = map.size - max;
7861
+ for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
7862
+ }
7863
+ function scheduleSave() {
7864
+ dirty = true;
7865
+ if (saveTimer) return;
7866
+ saveTimer = setTimeout(() => {
7867
+ saveTimer = null;
7868
+ if (dirty) saveSync();
7869
+ }, SAVE_DEBOUNCE_MS);
7870
+ saveTimer.unref?.();
7871
+ }
7872
+ function saveSync() {
7873
+ dirty = false;
7874
+ try {
7875
+ const path = cachePath();
7876
+ const dir = dirname6(path);
7877
+ if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
7878
+ const payload = {
7879
+ version: FILE_VERSION,
7880
+ users: Object.fromEntries(userMap),
7881
+ threads: Object.fromEntries(threadMap)
7882
+ };
7883
+ const tmp = `${path}.tmp`;
7884
+ writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
7885
+ renameSync(tmp, path);
7886
+ } catch (err) {
7887
+ console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
7888
+ }
7889
+ }
7890
+ function hookExit() {
7891
+ if (exitHooked) return;
7892
+ exitHooked = true;
7893
+ const flush2 = () => {
7894
+ if (dirty) saveSync();
7895
+ };
7896
+ process.once("beforeExit", flush2);
7897
+ process.once("SIGINT", flush2);
7898
+ process.once("SIGTERM", flush2);
7899
+ }
7900
+ function getCachedUserName(userId) {
7901
+ load();
7902
+ return userMap.get(userId);
7903
+ }
7904
+ function setCachedUserName(userId, entry2) {
7905
+ load();
7906
+ hookExit();
7907
+ userMap.set(userId, entry2);
7908
+ evictIfOversized(userMap, MAX_USERS);
7909
+ scheduleSave();
7910
+ }
7911
+ function getCachedThreadOwnership(key2) {
7912
+ load();
7913
+ return threadMap.get(key2);
7914
+ }
7915
+ function setCachedThreadOwnership(key2, entry2) {
7916
+ load();
7917
+ hookExit();
7918
+ threadMap.set(key2, entry2);
7919
+ evictIfOversized(threadMap, MAX_THREADS);
7920
+ scheduleSave();
7921
+ }
7922
+ var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
7923
+ var init_persistence = __esm({
7924
+ "src/integrations/slack/persistence.ts"() {
7925
+ "use strict";
7926
+ init_config();
7927
+ FILENAME = "slack-cache.json";
7928
+ FILE_VERSION = 1;
7929
+ SAVE_DEBOUNCE_MS = 500;
7930
+ MAX_USERS = 5e3;
7931
+ MAX_THREADS = 5e3;
7932
+ loaded = false;
7933
+ userMap = /* @__PURE__ */ new Map();
7934
+ threadMap = /* @__PURE__ */ new Map();
7935
+ dirty = false;
7936
+ saveTimer = null;
7937
+ exitHooked = false;
7938
+ }
7939
+ });
7940
+
7763
7941
  // src/integrations/slack/client.ts
7764
7942
  function readSlackConfig() {
7765
7943
  try {
@@ -7882,13 +8060,13 @@ async function fetchSlackUserName(userId) {
7882
8060
  async function resolveSlackUserName(userId) {
7883
8061
  if (!userId) return null;
7884
8062
  const now = Date.now();
7885
- const hit = userNameCache.get(userId);
8063
+ const hit = getCachedUserName(userId);
7886
8064
  if (hit && hit.expiresAt > now) return hit.name;
7887
8065
  const inflight = userInflight.get(userId);
7888
8066
  if (inflight) return inflight;
7889
8067
  const p = (async () => {
7890
8068
  const name = await fetchSlackUserName(userId);
7891
- userNameCache.set(userId, {
8069
+ setCachedUserName(userId, {
7892
8070
  name,
7893
8071
  expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
7894
8072
  });
@@ -7910,11 +8088,63 @@ async function normalizeSlackMentions(text) {
7910
8088
  }
7911
8089
  return text.replace(userMentionRe, (_full, id, label) => {
7912
8090
  if (label) return `${label} <@${id}>`;
7913
- const cached = userNameCache.get(id);
8091
+ const cached = getCachedUserName(id);
7914
8092
  const name = cached?.name;
7915
8093
  return name ? `${name} <@${id}>` : `<@${id}>`;
7916
8094
  });
7917
8095
  }
8096
+ function threadCacheKey(channel, threadTs) {
8097
+ return `${channel}\u241F${threadTs}`;
8098
+ }
8099
+ async function fetchBotParticipatedInThread(channel, threadTs) {
8100
+ const token = getSlackBotToken();
8101
+ if (!token) return false;
8102
+ const self = await ensureSlackSelfIdentity();
8103
+ if (!self) return false;
8104
+ try {
8105
+ const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
8106
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
8107
+ const data = await res.json().catch(() => ({}));
8108
+ if (!data?.ok) {
8109
+ console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
8110
+ return false;
8111
+ }
8112
+ const messages = Array.isArray(data.messages) ? data.messages : [];
8113
+ return messages.some(
8114
+ (m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
8115
+ );
8116
+ } catch (err) {
8117
+ console.warn(`[slack] conversations.replies error:`, err?.message || err);
8118
+ return false;
8119
+ }
8120
+ }
8121
+ async function botParticipatedInThread(channel, threadTs) {
8122
+ if (!channel || !threadTs) return false;
8123
+ const key2 = threadCacheKey(channel, threadTs);
8124
+ const now = Date.now();
8125
+ const hit = getCachedThreadOwnership(key2);
8126
+ if (hit && hit.expiresAt > now) return hit.owned;
8127
+ const inflight = threadOwnedInflight.get(key2);
8128
+ if (inflight) return inflight;
8129
+ const p = (async () => {
8130
+ const owned = await fetchBotParticipatedInThread(channel, threadTs);
8131
+ setCachedThreadOwnership(key2, {
8132
+ owned,
8133
+ expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
8134
+ });
8135
+ threadOwnedInflight.delete(key2);
8136
+ return owned;
8137
+ })();
8138
+ threadOwnedInflight.set(key2, p);
8139
+ return p;
8140
+ }
8141
+ function noteBotPostedInThread(channel, threadTs) {
8142
+ if (!channel || !threadTs) return;
8143
+ setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
8144
+ owned: true,
8145
+ expiresAt: Date.now() + THREAD_OWNED_TTL_MS
8146
+ });
8147
+ }
7918
8148
  function getSlackDeniedReplyPolicy() {
7919
8149
  try {
7920
8150
  const cfg = getConfig();
@@ -7927,17 +8157,20 @@ function getSlackDeniedReplyPolicy() {
7927
8157
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
7928
8158
  }
7929
8159
  }
7930
- var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
8160
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
7931
8161
  var init_client3 = __esm({
7932
8162
  "src/integrations/slack/client.ts"() {
7933
8163
  "use strict";
7934
8164
  init_config();
8165
+ init_persistence();
7935
8166
  cachedSelf = null;
7936
8167
  selfInflight = null;
7937
8168
  USER_TTL_MS = 60 * 60 * 1e3;
7938
8169
  USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7939
- userNameCache = /* @__PURE__ */ new Map();
7940
8170
  userInflight = /* @__PURE__ */ new Map();
8171
+ THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
8172
+ THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
8173
+ threadOwnedInflight = /* @__PURE__ */ new Map();
7941
8174
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
7942
8175
  }
7943
8176
  });
@@ -8037,6 +8270,7 @@ var init_slack = __esm({
8037
8270
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
8038
8271
  if (r.slackChannel && r.threadTs) {
8039
8272
  markThreadOwned(r.slackChannel, r.threadTs);
8273
+ noteBotPostedInThread(r.slackChannel, r.threadTs);
8040
8274
  }
8041
8275
  },
8042
8276
  displayLabel(ref) {
@@ -8694,8 +8928,8 @@ var init_orchestrator_actions = __esm({
8694
8928
 
8695
8929
  // src/integrations/mcp/store.ts
8696
8930
  import { nanoid as nanoid6 } from "nanoid";
8697
- import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
8698
- import { resolve as resolve10, join as join9 } from "path";
8931
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
8932
+ import { resolve as resolve10, join as join10 } from "path";
8699
8933
  function readServers() {
8700
8934
  try {
8701
8935
  const cfg = getConfig();
@@ -8707,12 +8941,12 @@ function readServers() {
8707
8941
  function refreshMcpServersFromDisk() {
8708
8942
  const candidates = [
8709
8943
  resolve10(process.cwd(), "sparkecoder.config.json"),
8710
- join9(ensureAppDataDirectory(), "sparkecoder.config.json")
8944
+ join10(ensureAppDataDirectory(), "sparkecoder.config.json")
8711
8945
  ];
8712
8946
  for (const path of candidates) {
8713
- if (!existsSync16(path)) continue;
8947
+ if (!existsSync17(path)) continue;
8714
8948
  try {
8715
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
8949
+ const raw = JSON.parse(readFileSync8(path, "utf-8"));
8716
8950
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
8717
8951
  setMcpServers(servers2);
8718
8952
  return servers2;
@@ -8964,11 +9198,11 @@ function waitForTaskQuestionAnswer(question) {
8964
9198
  if (pendingQuestions.has(k)) {
8965
9199
  return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
8966
9200
  }
8967
- return new Promise((resolve12, reject) => {
9201
+ return new Promise((resolve13, reject) => {
8968
9202
  pendingQuestions.set(k, {
8969
9203
  ...question,
8970
9204
  createdAt: /* @__PURE__ */ new Date(),
8971
- resolve: resolve12,
9205
+ resolve: resolve13,
8972
9206
  reject
8973
9207
  });
8974
9208
  });
@@ -9325,7 +9559,7 @@ __export(recorder_exports, {
9325
9559
  import { exec as exec5 } from "child_process";
9326
9560
  import { promisify as promisify5 } from "util";
9327
9561
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
9328
- import { join as join10 } from "path";
9562
+ import { join as join11 } from "path";
9329
9563
  import { tmpdir } from "os";
9330
9564
  import { nanoid as nanoid7 } from "nanoid";
9331
9565
  async function checkFfmpeg() {
@@ -9382,21 +9616,21 @@ var init_recorder = __esm({
9382
9616
  */
9383
9617
  async encode() {
9384
9618
  if (this.frames.length === 0) return null;
9385
- const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9619
+ const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9386
9620
  await mkdir4(workDir, { recursive: true });
9387
9621
  try {
9388
9622
  for (let i = 0; i < this.frames.length; i++) {
9389
- const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9623
+ const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9390
9624
  await writeFile5(framePath, this.frames[i].data);
9391
9625
  }
9392
9626
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
9393
9627
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
9394
9628
  const clampedFps = Math.max(1, Math.min(fps, 30));
9395
- const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
9629
+ const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
9396
9630
  const hasFfmpeg = await checkFfmpeg();
9397
9631
  if (hasFfmpeg) {
9398
9632
  await execAsync5(
9399
- `ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9633
+ `ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9400
9634
  { timeout: 12e4 }
9401
9635
  );
9402
9636
  } else {
@@ -9408,7 +9642,7 @@ var init_recorder = __esm({
9408
9642
  const files = await readdir5(workDir);
9409
9643
  for (const f of files) {
9410
9644
  if (f.startsWith("frame_")) {
9411
- await unlink2(join10(workDir, f)).catch(() => {
9645
+ await unlink2(join11(workDir, f)).catch(() => {
9412
9646
  });
9413
9647
  }
9414
9648
  }
@@ -9675,7 +9909,8 @@ ${personality.trim()}`;
9675
9909
  }
9676
9910
  const messages = await this.context.getMessages();
9677
9911
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9678
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
9912
+ const approvalWrapped = this.wrapToolsWithApproval(options, tools);
9913
+ const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
9679
9914
  const useAnthropic = isAnthropicModel(this.session.model);
9680
9915
  const stream = streamText2({
9681
9916
  model: resolveModel(this.session.model),
@@ -9689,6 +9924,17 @@ ${personality.trim()}`;
9689
9924
  providerOptions: useAnthropic ? {
9690
9925
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9691
9926
  } : void 0,
9927
+ // Run repairToolPairing before EVERY step's model call, not just the
9928
+ // first one. The AI SDK's multi-step loop can otherwise feed the model
9929
+ // a prompt containing an orphan tool-call (e.g. when a previous step's
9930
+ // tool result was lost, dropped during compaction, or the stream was
9931
+ // aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
9932
+ // ever reaches the model and we never hit AI_MissingToolResultsError.
9933
+ prepareStep: async ({ messages: stepMessages }) => {
9934
+ const repaired = repairToolPairing(stepMessages);
9935
+ if (repaired === stepMessages) return {};
9936
+ return { messages: repaired };
9937
+ },
9692
9938
  onStepFinish: async (step) => {
9693
9939
  options.onStepFinish?.(step);
9694
9940
  },
@@ -9724,7 +9970,7 @@ ${personality.trim()}`;
9724
9970
  });
9725
9971
  const messages = await this.context.getMessages();
9726
9972
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9727
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
9973
+ const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
9728
9974
  const useAnthropic = isAnthropicModel(this.session.model);
9729
9975
  const result = await generateText3({
9730
9976
  model: resolveModel(this.session.model),
@@ -9735,7 +9981,13 @@ ${personality.trim()}`;
9735
9981
  // Enable extended thinking/reasoning for models that support it
9736
9982
  providerOptions: useAnthropic ? {
9737
9983
  anthropic: getAnthropicProviderOptions(this.session.model)
9738
- } : void 0
9984
+ } : void 0,
9985
+ // Repair tool pairing before every step (see `stream()` for full rationale).
9986
+ prepareStep: async ({ messages: stepMessages }) => {
9987
+ const repaired = repairToolPairing(stepMessages);
9988
+ if (repaired === stepMessages) return {};
9989
+ return { messages: repaired };
9990
+ }
9739
9991
  });
9740
9992
  const responseMessages = result.response.messages;
9741
9993
  this.context.addResponseMessages(responseMessages);
@@ -9912,12 +10164,19 @@ ${p.text}` : p.text;
9912
10164
  model: resolveModel(this.session.model),
9913
10165
  system: systemPrompt,
9914
10166
  messages,
9915
- tools: taskTools,
10167
+ tools: wrapToolsNeverThrow(taskTools),
9916
10168
  stopWhen: stepCountIs2(500),
9917
10169
  abortSignal: combinedAbort,
9918
10170
  providerOptions: useAnthropic ? {
9919
10171
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9920
10172
  } : void 0,
10173
+ // See the matching note in `stream()` — repair tool pairing before
10174
+ // every step so we never feed the model an orphan tool-call.
10175
+ prepareStep: async ({ messages: stepMessages }) => {
10176
+ const repaired = repairToolPairing(stepMessages);
10177
+ if (repaired === stepMessages) return {};
10178
+ return { messages: repaired };
10179
+ },
9921
10180
  onStepFinish: async (step) => {
9922
10181
  options.onStepFinish?.(step);
9923
10182
  fireWebhook("task.step_finished", { iteration, text: step.text });
@@ -10117,14 +10376,14 @@ ${p.text}` : p.text;
10117
10376
  const result = await recorder.encode();
10118
10377
  recorder.clear();
10119
10378
  if (!result) return [];
10120
- const { readFile: readFile12, unlink: unlink3 } = await import("fs/promises");
10379
+ const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
10121
10380
  const uploadInfo = await storageQueries2.getUploadUrl(
10122
10381
  this.session.id,
10123
10382
  `browser-recording-${Date.now()}.mp4`,
10124
10383
  "video/mp4",
10125
10384
  "browser-recording"
10126
10385
  );
10127
- const fileData = await readFile12(result.path);
10386
+ const fileData = await readFile13(result.path);
10128
10387
  await fetch(uploadInfo.uploadUrl, {
10129
10388
  method: "PUT",
10130
10389
  headers: { "Content-Type": "video/mp4" },
@@ -10132,7 +10391,7 @@ ${p.text}` : p.text;
10132
10391
  });
10133
10392
  await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
10134
10393
  const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
10135
- await unlink3(result.path).catch(() => {
10394
+ await unlink4(result.path).catch(() => {
10136
10395
  });
10137
10396
  console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
10138
10397
  return [dlInfo.downloadUrl];
@@ -10150,13 +10409,13 @@ ${p.text}` : p.text;
10150
10409
  try {
10151
10410
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10152
10411
  if (!isRemoteConfigured2()) return [];
10153
- const { readFile: readFile12 } = await import("fs/promises");
10154
- const { join: join16, basename: basename6 } = await import("path");
10412
+ const { readFile: readFile13 } = await import("fs/promises");
10413
+ const { join: join18, basename: basename7 } = await import("path");
10155
10414
  const urls = [];
10156
10415
  for (const filePath of filePaths) {
10157
10416
  try {
10158
- const fullPath = filePath.startsWith("/") ? filePath : join16(this.session.workingDirectory, filePath);
10159
- const fileName = basename6(fullPath);
10417
+ const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
10418
+ const fileName = basename7(fullPath);
10160
10419
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10161
10420
  const mimeMap = {
10162
10421
  pdf: "application/pdf",
@@ -10180,7 +10439,7 @@ ${p.text}` : p.text;
10180
10439
  contentType,
10181
10440
  "task-output"
10182
10441
  );
10183
- const fileData = await readFile12(fullPath);
10442
+ const fileData = await readFile13(fullPath);
10184
10443
  await fetch(uploadInfo.uploadUrl, {
10185
10444
  method: "PUT",
10186
10445
  headers: { "Content-Type": contentType },
@@ -10229,8 +10488,8 @@ ${p.text}` : p.text;
10229
10488
  this.pendingApprovals.set(toolCallId, await execution);
10230
10489
  options.onApprovalRequired?.(await execution);
10231
10490
  await sessionQueries.updateStatus(this.session.id, "waiting");
10232
- const approved = await new Promise((resolve12) => {
10233
- approvalResolvers.set(toolCallId, { resolve: resolve12, sessionId: this.session.id });
10491
+ const approved = await new Promise((resolve13) => {
10492
+ approvalResolvers.set(toolCallId, { resolve: resolve13, sessionId: this.session.id });
10234
10493
  });
10235
10494
  const resolverData = approvalResolvers.get(toolCallId);
10236
10495
  approvalResolvers.delete(toolCallId);
@@ -10336,8 +10595,8 @@ async function withSessionLock(sessionId, fn) {
10336
10595
  state2.pending++;
10337
10596
  const prev = state2.tail;
10338
10597
  let release;
10339
- const next = new Promise((resolve12) => {
10340
- release = resolve12;
10598
+ const next = new Promise((resolve13) => {
10599
+ release = resolve13;
10341
10600
  });
10342
10601
  state2.tail = prev.then(() => next);
10343
10602
  await prev;
@@ -10360,19 +10619,19 @@ var init_session_lock = __esm({
10360
10619
  });
10361
10620
 
10362
10621
  // src/orchestrator/webhook-events.ts
10363
- import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
10364
- import { dirname as dirname6, join as join11 } from "path";
10622
+ import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
10623
+ import { dirname as dirname7, join as join12 } from "path";
10365
10624
  import { nanoid as nanoid9 } from "nanoid";
10366
10625
  function logFilePath() {
10367
- return join11(getAppDataDirectory(), "webhook-events.jsonl");
10626
+ return join12(getAppDataDirectory(), "webhook-events.jsonl");
10368
10627
  }
10369
10628
  function ensureLoaded() {
10370
10629
  if (cache !== null) return cache;
10371
10630
  cache = [];
10372
10631
  try {
10373
10632
  const p = logFilePath();
10374
- if (!existsSync17(p)) return cache;
10375
- const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
10633
+ if (!existsSync18(p)) return cache;
10634
+ const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
10376
10635
  for (const line of lines) {
10377
10636
  try {
10378
10637
  cache.push(JSON.parse(line));
@@ -10382,7 +10641,7 @@ function ensureLoaded() {
10382
10641
  if (cache.length > MAX_EVENTS) {
10383
10642
  cache = cache.slice(-MAX_EVENTS);
10384
10643
  try {
10385
- writeFileSync3(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10644
+ writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10386
10645
  } catch {
10387
10646
  }
10388
10647
  }
@@ -10396,7 +10655,7 @@ function appendEvent(ev) {
10396
10655
  if (list.length > MAX_EVENTS) list.shift();
10397
10656
  try {
10398
10657
  const p = logFilePath();
10399
- mkdirSync6(dirname6(p), { recursive: true });
10658
+ mkdirSync7(dirname7(p), { recursive: true });
10400
10659
  appendFileSync3(p, JSON.stringify(ev) + "\n");
10401
10660
  } catch {
10402
10661
  }
@@ -10427,8 +10686,8 @@ function updateEvent(id, patch) {
10427
10686
  list[i] = { ...list[i], ...patch };
10428
10687
  try {
10429
10688
  const p = logFilePath();
10430
- mkdirSync6(dirname6(p), { recursive: true });
10431
- writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10689
+ mkdirSync7(dirname7(p), { recursive: true });
10690
+ writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10432
10691
  } catch {
10433
10692
  }
10434
10693
  }
@@ -10460,7 +10719,7 @@ function listEvents(filter = {}) {
10460
10719
  function clearAllEvents() {
10461
10720
  cache = [];
10462
10721
  try {
10463
- writeFileSync3(logFilePath(), "");
10722
+ writeFileSync4(logFilePath(), "");
10464
10723
  } catch {
10465
10724
  }
10466
10725
  }
@@ -10727,12 +10986,12 @@ var init_scheduler = __esm({
10727
10986
 
10728
10987
  // src/server/index.ts
10729
10988
  import "dotenv/config";
10730
- import { Hono as Hono9 } from "hono";
10989
+ import { Hono as Hono10 } from "hono";
10731
10990
  import { serve } from "@hono/node-server";
10732
10991
  import { cors } from "hono/cors";
10733
10992
  import { logger } from "hono/logger";
10734
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10735
- import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
10993
+ import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
10994
+ import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
10736
10995
  import { spawn as spawn2 } from "child_process";
10737
10996
  import { createServer as createNetServer } from "net";
10738
10997
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -10746,9 +11005,9 @@ init_checkpoints();
10746
11005
  import { Hono } from "hono";
10747
11006
  import { zValidator } from "@hono/zod-validator";
10748
11007
  import { z as z16 } from "zod";
10749
- import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
11008
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
10750
11009
  import { readdir as readdir6 } from "fs/promises";
10751
- import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
11010
+ import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
10752
11011
  import { nanoid as nanoid10 } from "nanoid";
10753
11012
 
10754
11013
  // src/tasks/agent-status.ts
@@ -11386,12 +11645,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
11386
11645
  });
11387
11646
  function getAttachmentsDir(sessionId) {
11388
11647
  const appDataDir = getAppDataDirectory();
11389
- return join12(appDataDir, "attachments", sessionId);
11648
+ return join13(appDataDir, "attachments", sessionId);
11390
11649
  }
11391
11650
  function ensureAttachmentsDir(sessionId) {
11392
11651
  const dir = getAttachmentsDir(sessionId);
11393
- if (!existsSync18(dir)) {
11394
- mkdirSync7(dir, { recursive: true });
11652
+ if (!existsSync19(dir)) {
11653
+ mkdirSync8(dir, { recursive: true });
11395
11654
  }
11396
11655
  return dir;
11397
11656
  }
@@ -11402,12 +11661,12 @@ sessions2.get("/:id/attachments", async (c) => {
11402
11661
  return c.json({ error: "Session not found" }, 404);
11403
11662
  }
11404
11663
  const dir = getAttachmentsDir(sessionId);
11405
- if (!existsSync18(dir)) {
11664
+ if (!existsSync19(dir)) {
11406
11665
  return c.json({ sessionId, attachments: [], count: 0 });
11407
11666
  }
11408
11667
  const files = readdirSync3(dir);
11409
11668
  const attachments = files.map((filename) => {
11410
- const filePath = join12(dir, filename);
11669
+ const filePath = join13(dir, filename);
11411
11670
  const stats = statSync2(filePath);
11412
11671
  return {
11413
11672
  id: filename.split("_")[0],
@@ -11442,9 +11701,9 @@ sessions2.post("/:id/attachments", async (c) => {
11442
11701
  const id = nanoid10(10);
11443
11702
  const ext = extname8(file.name) || "";
11444
11703
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11445
- const filePath = join12(dir, safeFilename);
11704
+ const filePath = join13(dir, safeFilename);
11446
11705
  const arrayBuffer = await file.arrayBuffer();
11447
- writeFileSync4(filePath, Buffer.from(arrayBuffer));
11706
+ writeFileSync5(filePath, Buffer.from(arrayBuffer));
11448
11707
  return c.json({
11449
11708
  id,
11450
11709
  filename: file.name,
@@ -11468,13 +11727,13 @@ sessions2.post("/:id/attachments", async (c) => {
11468
11727
  const id = nanoid10(10);
11469
11728
  const ext = extname8(body.filename) || "";
11470
11729
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11471
- const filePath = join12(dir, safeFilename);
11730
+ const filePath = join13(dir, safeFilename);
11472
11731
  let base64Data = body.data;
11473
11732
  if (base64Data.includes(",")) {
11474
11733
  base64Data = base64Data.split(",")[1];
11475
11734
  }
11476
11735
  const buffer = Buffer.from(base64Data, "base64");
11477
- writeFileSync4(filePath, buffer);
11736
+ writeFileSync5(filePath, buffer);
11478
11737
  return c.json({
11479
11738
  id,
11480
11739
  filename: body.filename,
@@ -11497,7 +11756,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11497
11756
  return c.json({ error: "Session not found" }, 404);
11498
11757
  }
11499
11758
  const dir = getAttachmentsDir(sessionId);
11500
- if (!existsSync18(dir)) {
11759
+ if (!existsSync19(dir)) {
11501
11760
  return c.json({ error: "Attachment not found" }, 404);
11502
11761
  }
11503
11762
  const files = readdirSync3(dir);
@@ -11505,7 +11764,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11505
11764
  if (!file) {
11506
11765
  return c.json({ error: "Attachment not found" }, 404);
11507
11766
  }
11508
- const filePath = join12(dir, file);
11767
+ const filePath = join13(dir, file);
11509
11768
  unlinkSync2(filePath);
11510
11769
  return c.json({ success: true, id: attachmentId });
11511
11770
  });
@@ -11588,7 +11847,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
11588
11847
  const entries = await readdir6(currentDir, { withFileTypes: true });
11589
11848
  for (const entry2 of entries) {
11590
11849
  if (results.length >= limit * 2) break;
11591
- const fullPath = join12(currentDir, entry2.name);
11850
+ const fullPath = join13(currentDir, entry2.name);
11592
11851
  const relativePath = relative9(baseDir, fullPath);
11593
11852
  if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
11594
11853
  continue;
@@ -11636,7 +11895,7 @@ sessions2.get(
11636
11895
  return c.json({ error: "Session not found" }, 404);
11637
11896
  }
11638
11897
  const workingDirectory = session.workingDirectory;
11639
- if (!existsSync18(workingDirectory)) {
11898
+ if (!existsSync19(workingDirectory)) {
11640
11899
  return c.json({
11641
11900
  sessionId,
11642
11901
  workingDirectory,
@@ -11744,14 +12003,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
11744
12003
 
11745
12004
  // src/server/routes/agents.ts
11746
12005
  init_db();
11747
- init_agent();
11748
- init_session_lock();
11749
- init_config();
11750
12006
  import { Hono as Hono2 } from "hono";
11751
12007
  import { zValidator as zValidator2 } from "@hono/zod-validator";
11752
12008
  import { z as z17 } from "zod";
11753
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
11754
- import { join as join13 } from "path";
12009
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
12010
+ import { join as join14 } from "path";
12011
+
12012
+ // src/agent/missing-tool-recovery.ts
12013
+ init_db();
12014
+ function extractMissingToolCallIds(error) {
12015
+ if (!error) return [];
12016
+ const e = error;
12017
+ if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
12018
+ return e.toolCallIds;
12019
+ }
12020
+ const msg = typeof e.message === "string" ? e.message : "";
12021
+ const ids = /* @__PURE__ */ new Set();
12022
+ for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
12023
+ for (const id of m[1].split(/\s*,\s*/)) {
12024
+ const trimmed = id.trim();
12025
+ if (trimmed) ids.add(trimmed);
12026
+ }
12027
+ }
12028
+ return [...ids];
12029
+ }
12030
+ function isMissingToolResultsError(error) {
12031
+ if (!error) return false;
12032
+ const e = error;
12033
+ if (e.name === "AI_MissingToolResultsError") return true;
12034
+ if (Array.isArray(e.toolCallIds)) return true;
12035
+ return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
12036
+ }
12037
+ async function recoverFromMissingToolResults(sessionId, error) {
12038
+ const toolCallIds = extractMissingToolCallIds(error);
12039
+ if (toolCallIds.length === 0) return null;
12040
+ let history = [];
12041
+ try {
12042
+ history = await messageQueries.getModelMessages(sessionId);
12043
+ } catch (err) {
12044
+ console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
12045
+ }
12046
+ const toolNameByCallId = /* @__PURE__ */ new Map();
12047
+ const existingResultIds = /* @__PURE__ */ new Set();
12048
+ for (const msg of history) {
12049
+ if (!Array.isArray(msg.content)) continue;
12050
+ for (const part of msg.content) {
12051
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
12052
+ if (typeof part.toolName === "string") toolNameByCallId.set(part.toolCallId, part.toolName);
12053
+ }
12054
+ if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
12055
+ existingResultIds.add(part.toolCallId);
12056
+ }
12057
+ }
12058
+ }
12059
+ const resolved = toolCallIds.map((id) => ({
12060
+ toolCallId: id,
12061
+ toolName: toolNameByCallId.get(id) ?? "unknown",
12062
+ foundInAssistantMessage: toolNameByCallId.has(id)
12063
+ }));
12064
+ const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
12065
+ let syntheticToolMessageSaved = false;
12066
+ if (stillOrphaned.length > 0) {
12067
+ const syntheticParts = stillOrphaned.map((id) => ({
12068
+ type: "tool-result",
12069
+ toolCallId: id,
12070
+ toolName: toolNameByCallId.get(id) ?? "unknown",
12071
+ output: {
12072
+ type: "text",
12073
+ value: "[Auto-recovered: the original tool execution never produced a result (crash, timeout, or message stripped during context compaction). This synthetic placeholder lets the conversation continue.]"
12074
+ }
12075
+ }));
12076
+ try {
12077
+ await messageQueries.create(sessionId, {
12078
+ role: "tool",
12079
+ content: syntheticParts
12080
+ });
12081
+ syntheticToolMessageSaved = true;
12082
+ } catch (err) {
12083
+ console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
12084
+ }
12085
+ }
12086
+ const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
12087
+ return {
12088
+ kind: "missing_tool_results",
12089
+ toolCallIds,
12090
+ resolved,
12091
+ syntheticToolMessageSaved,
12092
+ lastFewMessageRoles,
12093
+ hint: syntheticToolMessageSaved ? "Synthetic tool-result(s) saved. Re-send your message to continue." : "Could not auto-recover; please reset the session or revert to the previous checkpoint."
12094
+ };
12095
+ }
12096
+
12097
+ // src/server/routes/agents.ts
12098
+ init_agent();
12099
+ init_session_lock();
12100
+ init_config();
11755
12101
 
11756
12102
  // src/server/resumable-stream.ts
11757
12103
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -11907,7 +12253,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
11907
12253
  toolCallId,
11908
12254
  argsTextDelta: chunk
11909
12255
  }));
11910
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12256
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
11911
12257
  }
11912
12258
  }
11913
12259
  function buildDevtoolsContextXml(sessionId) {
@@ -11958,12 +12304,12 @@ var rejectSchema = z17.object({
11958
12304
  var streamAbortControllers = /* @__PURE__ */ new Map();
11959
12305
  function getAttachmentsDirectory(sessionId) {
11960
12306
  const appDataDir = getAppDataDirectory();
11961
- return join13(appDataDir, "attachments", sessionId);
12307
+ return join14(appDataDir, "attachments", sessionId);
11962
12308
  }
11963
12309
  async function saveAttachmentToDisk(sessionId, attachment, index) {
11964
12310
  const attachmentsDir = getAttachmentsDirectory(sessionId);
11965
- if (!existsSync19(attachmentsDir)) {
11966
- mkdirSync8(attachmentsDir, { recursive: true });
12311
+ if (!existsSync20(attachmentsDir)) {
12312
+ mkdirSync9(attachmentsDir, { recursive: true });
11967
12313
  }
11968
12314
  let filename = attachment.filename;
11969
12315
  if (!filename) {
@@ -11981,8 +12327,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
11981
12327
  attachment.mediaType = resized.mediaType;
11982
12328
  attachment.data = buffer.toString("base64");
11983
12329
  }
11984
- const filePath = join13(attachmentsDir, filename);
11985
- writeFileSync5(filePath, buffer);
12330
+ const filePath = join14(attachmentsDir, filename);
12331
+ writeFileSync6(filePath, buffer);
11986
12332
  return filePath;
11987
12333
  }
11988
12334
  function stripDataUrlPrefix2(data) {
@@ -12162,7 +12508,7 @@ ${prompt}` });
12162
12508
  chunkIndex,
12163
12509
  chunkCount
12164
12510
  }));
12165
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12511
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12166
12512
  }
12167
12513
  const browserPort = progress.data?.browserStreamPort;
12168
12514
  const browserClosed = progress.data?.browserClosed;
@@ -12319,7 +12665,20 @@ ${prompt}` });
12319
12665
  await writeSSE(JSON.stringify({ type: "abort" }));
12320
12666
  } else {
12321
12667
  console.error("Agent error:", error);
12322
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
12668
+ let debugPayload = void 0;
12669
+ if (isMissingToolResultsError(error)) {
12670
+ try {
12671
+ const recovery = await recoverFromMissingToolResults(sessionId, error);
12672
+ if (recovery) debugPayload = recovery;
12673
+ } catch (recErr) {
12674
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
12675
+ }
12676
+ }
12677
+ await writeSSE(JSON.stringify({
12678
+ type: "error",
12679
+ errorText: error.message,
12680
+ ...debugPayload ? { debug: debugPayload } : {}
12681
+ }));
12323
12682
  try {
12324
12683
  await activeStreamQueries.markError(streamId);
12325
12684
  } catch {
@@ -12709,7 +13068,7 @@ agents.post(
12709
13068
  chunkIndex,
12710
13069
  chunkCount
12711
13070
  }));
12712
- await new Promise((resolve12) => setTimeout(resolve12, 0));
13071
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12713
13072
  }
12714
13073
  const browserPort = progress.data?.browserStreamPort;
12715
13074
  const browserClosed = progress.data?.browserClosed;
@@ -12859,7 +13218,20 @@ agents.post(
12859
13218
  await writeSSE(JSON.stringify({ type: "abort" }));
12860
13219
  } else {
12861
13220
  console.error("Agent error:", error);
12862
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
13221
+ let debugPayload = void 0;
13222
+ if (isMissingToolResultsError(error)) {
13223
+ try {
13224
+ const recovery = await recoverFromMissingToolResults(session.id, error);
13225
+ if (recovery) debugPayload = recovery;
13226
+ } catch (recErr) {
13227
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
13228
+ }
13229
+ }
13230
+ await writeSSE(JSON.stringify({
13231
+ type: "error",
13232
+ errorText: error.message,
13233
+ ...debugPayload ? { debug: debugPayload } : {}
13234
+ }));
12863
13235
  await activeStreamQueries.markError(streamId);
12864
13236
  }
12865
13237
  } finally {
@@ -12942,26 +13314,26 @@ init_config();
12942
13314
  import { Hono as Hono3 } from "hono";
12943
13315
  import { zValidator as zValidator3 } from "@hono/zod-validator";
12944
13316
  import { z as z18 } from "zod";
12945
- import { readFileSync as readFileSync9 } from "fs";
13317
+ import { readFileSync as readFileSync10 } from "fs";
12946
13318
  import { fileURLToPath as fileURLToPath3 } from "url";
12947
- import { dirname as dirname7, join as join14 } from "path";
13319
+ import { dirname as dirname8, join as join15 } from "path";
12948
13320
  var __filename = fileURLToPath3(import.meta.url);
12949
- var __dirname = dirname7(__filename);
13321
+ var __dirname = dirname8(__filename);
12950
13322
  var possiblePaths = [
12951
- join14(__dirname, "../package.json"),
13323
+ join15(__dirname, "../package.json"),
12952
13324
  // From dist/server -> dist/../package.json
12953
- join14(__dirname, "../../package.json"),
13325
+ join15(__dirname, "../../package.json"),
12954
13326
  // From dist/server (if nested differently)
12955
- join14(__dirname, "../../../package.json"),
13327
+ join15(__dirname, "../../../package.json"),
12956
13328
  // From src/server/routes (development)
12957
- join14(process.cwd(), "package.json")
13329
+ join15(process.cwd(), "package.json")
12958
13330
  // From current working directory
12959
13331
  ];
12960
13332
  var currentVersion = "0.0.0";
12961
13333
  var packageName = "sparkecoder";
12962
13334
  for (const packageJsonPath of possiblePaths) {
12963
13335
  try {
12964
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
13336
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
12965
13337
  if (packageJson.name === "sparkecoder") {
12966
13338
  currentVersion = packageJson.version || "0.0.0";
12967
13339
  packageName = packageJson.name || "sparkecoder";
@@ -13779,12 +14151,13 @@ slack.post("/events", async (c) => {
13779
14151
  if (inbound) {
13780
14152
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
13781
14153
  if (isThreadReply) {
13782
- const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
14154
+ const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
13783
14155
  if (!ours) {
13784
14156
  console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
13785
14157
  updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
13786
14158
  return c.json({ ok: true });
13787
14159
  }
14160
+ markThreadOwned(ev.channel, ev.thread_ts);
13788
14161
  }
13789
14162
  if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
13790
14163
  markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
@@ -13823,18 +14196,6 @@ slack.post("/events", async (c) => {
13823
14196
  }
13824
14197
  return c.json({ ok: true });
13825
14198
  });
13826
- async function threadBelongsToUs(channel, threadTs) {
13827
- try {
13828
- const sessions3 = await sessionQueries.list(500, 0);
13829
- return sessions3.some((s) => {
13830
- const slack2 = s.config?.slack;
13831
- return slack2?.channel === channel && slack2?.threadTs === threadTs;
13832
- });
13833
- } catch (err) {
13834
- console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
13835
- return false;
13836
- }
13837
- }
13838
14199
  async function findOrCreateOrchestratorId() {
13839
14200
  try {
13840
14201
  const all = await sessionQueries.list(500, 0);
@@ -14217,6 +14578,224 @@ mcpRouter.post("/:id/test", async (c) => {
14217
14578
  return c.json(result);
14218
14579
  });
14219
14580
 
14581
+ // src/server/routes/skills.ts
14582
+ init_config();
14583
+ init_skills();
14584
+ import { Hono as Hono9 } from "hono";
14585
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
14586
+ import { z as z22 } from "zod";
14587
+ import { existsSync as existsSync21, statSync as statSync3 } from "fs";
14588
+ import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
14589
+ import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
14590
+ var skills = new Hono9();
14591
+ function encodeId(filePath) {
14592
+ return Buffer.from(filePath, "utf-8").toString("base64url");
14593
+ }
14594
+ function decodeId(id) {
14595
+ try {
14596
+ const decoded = Buffer.from(id, "base64url").toString("utf-8");
14597
+ return decoded || null;
14598
+ } catch {
14599
+ return null;
14600
+ }
14601
+ }
14602
+ function listAllDirectories() {
14603
+ const cfg = getConfig();
14604
+ const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
14605
+ const out = [];
14606
+ for (const { path, priority } of discovered.alwaysLoadedDirs) {
14607
+ out.push({
14608
+ path,
14609
+ source: pathToSource(path),
14610
+ label: pathToLabel(path),
14611
+ priority,
14612
+ alwaysApply: true
14613
+ });
14614
+ }
14615
+ for (const { path, priority } of discovered.onDemandDirs) {
14616
+ out.push({
14617
+ path,
14618
+ source: pathToSource(path),
14619
+ label: pathToLabel(path),
14620
+ priority,
14621
+ alwaysApply: false
14622
+ });
14623
+ }
14624
+ const additional = cfg.skills?.additionalDirectories || [];
14625
+ for (const dir of additional) {
14626
+ const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
14627
+ if (out.some((d) => d.path === abs)) continue;
14628
+ out.push({
14629
+ path: abs,
14630
+ source: "additional",
14631
+ label: pathToLabel(abs),
14632
+ priority: 50,
14633
+ alwaysApply: false
14634
+ });
14635
+ }
14636
+ return out;
14637
+ }
14638
+ function pathToSource(path) {
14639
+ if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
14640
+ return "project";
14641
+ }
14642
+ function pathToLabel(path) {
14643
+ if (path.includes("/skills/default")) return "Built-in (default)";
14644
+ if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
14645
+ if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
14646
+ if (path.includes("/.cursor/rules")) return ".cursor/rules";
14647
+ if (path.includes("/.claude/skills")) return ".claude/skills";
14648
+ return basename6(dirname9(path)) + "/" + basename6(path);
14649
+ }
14650
+ skills.get("/", async (c) => {
14651
+ const dirs = listAllDirectories();
14652
+ const allSkills = [];
14653
+ for (const dir of dirs) {
14654
+ if (!existsSync21(dir.path)) continue;
14655
+ try {
14656
+ const list = await loadSkillsFromDirectory(dir.path, {
14657
+ priority: dir.priority,
14658
+ defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
14659
+ forceAlwaysApply: dir.alwaysApply
14660
+ });
14661
+ for (const s of list) {
14662
+ let size = 0;
14663
+ try {
14664
+ size = statSync3(s.filePath).size;
14665
+ } catch {
14666
+ }
14667
+ allSkills.push({
14668
+ id: encodeId(s.filePath),
14669
+ name: s.name,
14670
+ description: s.description,
14671
+ filePath: s.filePath,
14672
+ fileName: basename6(s.filePath),
14673
+ sourceDir: dir.path,
14674
+ sourceLabel: dir.label,
14675
+ sourceType: dir.source,
14676
+ alwaysApply: s.alwaysApply,
14677
+ globs: s.globs,
14678
+ sizeBytes: size
14679
+ });
14680
+ }
14681
+ } catch (err) {
14682
+ console.warn("[skills] failed to read", dir.path, err?.message || err);
14683
+ }
14684
+ }
14685
+ return c.json({
14686
+ directories: dirs.map((d) => ({
14687
+ path: d.path,
14688
+ label: d.label,
14689
+ source: d.source,
14690
+ alwaysApply: d.alwaysApply,
14691
+ exists: existsSync21(d.path),
14692
+ writable: isWritable(d.path)
14693
+ })),
14694
+ skills: allSkills
14695
+ });
14696
+ });
14697
+ function isWritable(dir) {
14698
+ try {
14699
+ if (!existsSync21(dir)) return false;
14700
+ if (dir.includes("/skills/default")) return false;
14701
+ return true;
14702
+ } catch {
14703
+ return false;
14704
+ }
14705
+ }
14706
+ skills.get("/:id", async (c) => {
14707
+ const filePath = decodeId(c.req.param("id"));
14708
+ if (!filePath || !existsSync21(filePath)) {
14709
+ return c.json({ error: "skill not found" }, 404);
14710
+ }
14711
+ const content = await readFile12(filePath, "utf-8");
14712
+ return c.json({
14713
+ id: c.req.param("id"),
14714
+ filePath,
14715
+ fileName: basename6(filePath),
14716
+ content,
14717
+ writable: !filePath.includes("/skills/default")
14718
+ });
14719
+ });
14720
+ skills.post(
14721
+ "/",
14722
+ zValidator7("json", z22.object({
14723
+ directory: z22.string().min(1),
14724
+ fileName: z22.string().min(1),
14725
+ content: z22.string()
14726
+ })),
14727
+ async (c) => {
14728
+ const { directory, fileName, content } = c.req.valid("json");
14729
+ const cfg = getConfig();
14730
+ const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
14731
+ if (targetDir.includes("/skills/default")) {
14732
+ return c.json({ error: "cannot write into built-in skills directory" }, 400);
14733
+ }
14734
+ const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
14735
+ const ext = extname9(safeName).toLowerCase();
14736
+ const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
14737
+ const filePath = join16(targetDir, finalName);
14738
+ if (existsSync21(filePath)) {
14739
+ return c.json({ error: `file already exists: ${finalName}` }, 409);
14740
+ }
14741
+ try {
14742
+ await mkdir5(targetDir, { recursive: true });
14743
+ await writeFile6(filePath, content, "utf-8");
14744
+ } catch (err) {
14745
+ return c.json({ error: err?.message || "write failed" }, 500);
14746
+ }
14747
+ return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
14748
+ }
14749
+ );
14750
+ skills.put(
14751
+ "/:id",
14752
+ zValidator7("json", z22.object({ content: z22.string() })),
14753
+ async (c) => {
14754
+ const filePath = decodeId(c.req.param("id"));
14755
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14756
+ if (filePath.includes("/skills/default")) {
14757
+ return c.json({ error: "built-in skills are read-only" }, 400);
14758
+ }
14759
+ await writeFile6(filePath, c.req.valid("json").content, "utf-8");
14760
+ return c.json({ ok: true });
14761
+ }
14762
+ );
14763
+ skills.delete("/:id", async (c) => {
14764
+ const filePath = decodeId(c.req.param("id"));
14765
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14766
+ if (filePath.includes("/skills/default")) {
14767
+ return c.json({ error: "built-in skills are read-only" }, 400);
14768
+ }
14769
+ await unlink3(filePath);
14770
+ return c.json({ ok: true });
14771
+ });
14772
+ skills.post(
14773
+ "/directories",
14774
+ zValidator7("json", z22.object({ path: z22.string().min(1) })),
14775
+ (c) => {
14776
+ const cfg = getConfig();
14777
+ const inputPath = c.req.valid("json").path.trim();
14778
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14779
+ const current = cfg.skills?.additionalDirectories || [];
14780
+ if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
14781
+ return c.json({ error: "directory already added" }, 409);
14782
+ }
14783
+ const next = [...current, abs];
14784
+ setSkillsAdditionalDirectories(next);
14785
+ return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
14786
+ }
14787
+ );
14788
+ skills.delete("/directories", (c) => {
14789
+ const inputPath = c.req.query("path");
14790
+ if (!inputPath) return c.json({ error: "path query param required" }, 400);
14791
+ const cfg = getConfig();
14792
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14793
+ const current = cfg.skills?.additionalDirectories || [];
14794
+ const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
14795
+ setSkillsAdditionalDirectories(next);
14796
+ return c.json({ ok: true });
14797
+ });
14798
+
14220
14799
  // src/server/auth/cf-access.ts
14221
14800
  init_config();
14222
14801
  import { createRemoteJWKSet, jwtVerify } from "jose";
@@ -14377,13 +14956,13 @@ var DEFAULT_WEB_PORT = 6969;
14377
14956
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
14378
14957
  function getWebDirectory() {
14379
14958
  try {
14380
- const currentDir = dirname8(fileURLToPath4(import.meta.url));
14381
- const webDir = resolve11(currentDir, "..", "web");
14382
- if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
14959
+ const currentDir = dirname10(fileURLToPath4(import.meta.url));
14960
+ const webDir = resolve12(currentDir, "..", "web");
14961
+ if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
14383
14962
  return webDir;
14384
14963
  }
14385
- const altWebDir = resolve11(currentDir, "..", "..", "web");
14386
- if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
14964
+ const altWebDir = resolve12(currentDir, "..", "..", "web");
14965
+ if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
14387
14966
  return altWebDir;
14388
14967
  }
14389
14968
  return null;
@@ -14406,18 +14985,18 @@ async function isSparkcoderWebRunning(port) {
14406
14985
  }
14407
14986
  }
14408
14987
  function isPortInUse(port) {
14409
- return new Promise((resolve12) => {
14988
+ return new Promise((resolve13) => {
14410
14989
  const server = createNetServer();
14411
14990
  server.once("error", (err) => {
14412
14991
  if (err.code === "EADDRINUSE") {
14413
- resolve12(true);
14992
+ resolve13(true);
14414
14993
  } else {
14415
- resolve12(false);
14994
+ resolve13(false);
14416
14995
  }
14417
14996
  });
14418
14997
  server.once("listening", () => {
14419
14998
  server.close();
14420
- resolve12(false);
14999
+ resolve13(false);
14421
15000
  });
14422
15001
  server.listen(port, "0.0.0.0");
14423
15002
  });
@@ -14441,30 +15020,30 @@ async function findWebPort(preferredPort) {
14441
15020
  return { port: preferredPort, alreadyRunning: false };
14442
15021
  }
14443
15022
  function hasProductionBuild(webDir) {
14444
- const buildIdPath = join15(webDir, ".next", "BUILD_ID");
14445
- return existsSync20(buildIdPath);
15023
+ const buildIdPath = join17(webDir, ".next", "BUILD_ID");
15024
+ return existsSync22(buildIdPath);
14446
15025
  }
14447
15026
  function hasSourceFiles(webDir) {
14448
- const appDir = join15(webDir, "src", "app");
14449
- const pagesDir = join15(webDir, "src", "pages");
14450
- const rootAppDir = join15(webDir, "app");
14451
- const rootPagesDir = join15(webDir, "pages");
14452
- return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
15027
+ const appDir = join17(webDir, "src", "app");
15028
+ const pagesDir = join17(webDir, "src", "pages");
15029
+ const rootAppDir = join17(webDir, "app");
15030
+ const rootPagesDir = join17(webDir, "pages");
15031
+ return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
14453
15032
  }
14454
15033
  function getStandaloneServerPath(webDir) {
14455
15034
  const possiblePaths2 = [
14456
- join15(webDir, ".next", "standalone", "server.js"),
14457
- join15(webDir, ".next", "standalone", "web", "server.js")
15035
+ join17(webDir, ".next", "standalone", "server.js"),
15036
+ join17(webDir, ".next", "standalone", "web", "server.js")
14458
15037
  ];
14459
15038
  for (const serverPath of possiblePaths2) {
14460
- if (existsSync20(serverPath)) {
15039
+ if (existsSync22(serverPath)) {
14461
15040
  return serverPath;
14462
15041
  }
14463
15042
  }
14464
15043
  return null;
14465
15044
  }
14466
15045
  function runCommand(command, args, cwd, env) {
14467
- return new Promise((resolve12) => {
15046
+ return new Promise((resolve13) => {
14468
15047
  const child = spawn2(command, args, {
14469
15048
  cwd,
14470
15049
  stdio: ["ignore", "pipe", "pipe"],
@@ -14479,10 +15058,10 @@ function runCommand(command, args, cwd, env) {
14479
15058
  output += data.toString();
14480
15059
  });
14481
15060
  child.on("close", (code) => {
14482
- resolve12({ success: code === 0, output });
15061
+ resolve13({ success: code === 0, output });
14483
15062
  });
14484
15063
  child.on("error", (err) => {
14485
- resolve12({ success: false, output: err.message });
15064
+ resolve13({ success: false, output: err.message });
14486
15065
  });
14487
15066
  });
14488
15067
  }
@@ -14497,15 +15076,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14497
15076
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
14498
15077
  return { process: null, port: actualPort };
14499
15078
  }
14500
- const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
14501
- const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
15079
+ const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
15080
+ const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
14502
15081
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
14503
15082
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
14504
15083
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
14505
15084
  const runtimeConfig = { apiBaseUrl: apiUrl };
14506
- const runtimeConfigPath = join15(webDir, "runtime-config.json");
15085
+ const runtimeConfigPath = join17(webDir, "runtime-config.json");
14507
15086
  try {
14508
- writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
15087
+ writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
14509
15088
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
14510
15089
  } catch (err) {
14511
15090
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -14525,7 +15104,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14525
15104
  if (standaloneServerPath) {
14526
15105
  command = "node";
14527
15106
  args = ["server.js"];
14528
- cwd = dirname8(standaloneServerPath);
15107
+ cwd = dirname10(standaloneServerPath);
14529
15108
  webEnv.PORT = String(actualPort);
14530
15109
  webEnv.HOSTNAME = "0.0.0.0";
14531
15110
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -14566,10 +15145,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14566
15145
  let started = false;
14567
15146
  let exited = false;
14568
15147
  let exitCode = null;
14569
- const startedPromise = new Promise((resolve12) => {
15148
+ const startedPromise = new Promise((resolve13) => {
14570
15149
  const timeout = setTimeout(() => {
14571
15150
  if (!started && !exited) {
14572
- resolve12(false);
15151
+ resolve13(false);
14573
15152
  }
14574
15153
  }, startupTimeout);
14575
15154
  child.stdout?.on("data", (data) => {
@@ -14583,7 +15162,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14583
15162
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
14584
15163
  started = true;
14585
15164
  clearTimeout(timeout);
14586
- resolve12(true);
15165
+ resolve13(true);
14587
15166
  }
14588
15167
  });
14589
15168
  child.stderr?.on("data", (data) => {
@@ -14595,14 +15174,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14595
15174
  child.on("error", (err) => {
14596
15175
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
14597
15176
  clearTimeout(timeout);
14598
- resolve12(false);
15177
+ resolve13(false);
14599
15178
  });
14600
15179
  child.on("exit", (code) => {
14601
15180
  exited = true;
14602
15181
  exitCode = code;
14603
15182
  if (!started) {
14604
15183
  clearTimeout(timeout);
14605
- resolve12(false);
15184
+ resolve13(false);
14606
15185
  }
14607
15186
  webUIProcess = null;
14608
15187
  });
@@ -14625,7 +15204,7 @@ function stopWebUI() {
14625
15204
  }
14626
15205
  }
14627
15206
  async function createApp(options = {}) {
14628
- const app = new Hono9();
15207
+ const app = new Hono10();
14629
15208
  app.use("*", cors({
14630
15209
  origin: "*",
14631
15210
  // Allow all origins
@@ -14653,6 +15232,7 @@ async function createApp(options = {}) {
14653
15232
  app.route("/api/schedules", schedulesRouter);
14654
15233
  app.route("/api/orchestrator", orchestratorRouter);
14655
15234
  app.route("/api/mcp", mcpRouter);
15235
+ app.route("/api/skills", skills);
14656
15236
  app.route("/api/webhooks", webhooksRouter);
14657
15237
  const config = getConfig();
14658
15238
  const webhookToken = config?.webhooks?.token;
@@ -14718,8 +15298,8 @@ async function startServer(options = {}) {
14718
15298
  if (options.workingDirectory) {
14719
15299
  config.resolvedWorkingDirectory = options.workingDirectory;
14720
15300
  }
14721
- if (!existsSync20(config.resolvedWorkingDirectory)) {
14722
- mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
15301
+ if (!existsSync22(config.resolvedWorkingDirectory)) {
15302
+ mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
14723
15303
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
14724
15304
  }
14725
15305
  if (!config.resolvedRemoteServer.url) {