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
package/dist/cli.js CHANGED
@@ -964,6 +964,7 @@ __export(config_exports, {
964
964
  setApiKey: () => setApiKey,
965
965
  setMcpServers: () => setMcpServers,
966
966
  setPublicUrl: () => setPublicUrl,
967
+ setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
967
968
  setSlackConfig: () => setSlackConfig,
968
969
  setWebhookToken: () => setWebhookToken
969
970
  });
@@ -1253,6 +1254,40 @@ function setWebhookToken(token) {
1253
1254
  console.warn("[config] failed to persist webhook token:", err?.message || err);
1254
1255
  }
1255
1256
  }
1257
+ function setSkillsAdditionalDirectories(directories) {
1258
+ if (cachedConfig) {
1259
+ const cur = cachedConfig.skills || {};
1260
+ cachedConfig.skills = { ...cur, additionalDirectories: directories };
1261
+ try {
1262
+ const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
1263
+ const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
1264
+ cachedConfig.discoveredSkills = discovered;
1265
+ cachedConfig.resolvedSkillsDirectories = [
1266
+ ...discovered.allDirectories,
1267
+ ...additionalAbs
1268
+ ];
1269
+ } catch {
1270
+ }
1271
+ }
1272
+ try {
1273
+ const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
1274
+ const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
1275
+ let raw = {};
1276
+ if (existsSync(target)) {
1277
+ try {
1278
+ raw = JSON.parse(readFileSync(target, "utf-8"));
1279
+ } catch {
1280
+ raw = {};
1281
+ }
1282
+ } else {
1283
+ raw = createDefaultConfig();
1284
+ }
1285
+ raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
1286
+ writeFileSync(target, JSON.stringify(raw, null, 2));
1287
+ } catch (err) {
1288
+ console.warn("[config] failed to persist skill directories:", err?.message || err);
1289
+ }
1290
+ }
1256
1291
  function setPublicUrl(publicUrl) {
1257
1292
  if (cachedConfig) {
1258
1293
  cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
@@ -2295,10 +2330,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2295
2330
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
2296
2331
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
2297
2332
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
2298
- const cachePath = join3(cacheDir, key2 + ext);
2299
- if (existsSync3(cachePath)) {
2333
+ const cachePath2 = join3(cacheDir, key2 + ext);
2334
+ if (existsSync3(cachePath2)) {
2300
2335
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
2301
- return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
2336
+ return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
2302
2337
  }
2303
2338
  let pipeline = sharp(buffer);
2304
2339
  if (needsResize) {
@@ -2323,7 +2358,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2323
2358
  }
2324
2359
  finalMediaType = "image/jpeg";
2325
2360
  }
2326
- writeFileSync2(cachePath, result);
2361
+ writeFileSync2(cachePath2, result);
2327
2362
  const resultMeta = await sharp(result).metadata();
2328
2363
  console.log(
2329
2364
  `[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
@@ -3014,7 +3049,7 @@ async function createClient(serverId, handle, root) {
3014
3049
  },
3015
3050
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
3016
3051
  const normalized = normalizePath(filePath);
3017
- return new Promise((resolve13) => {
3052
+ return new Promise((resolve14) => {
3018
3053
  const startTime = Date.now();
3019
3054
  let debounceTimer;
3020
3055
  let resolved = false;
@@ -3033,7 +3068,7 @@ async function createClient(serverId, handle, root) {
3033
3068
  if (resolved) return;
3034
3069
  resolved = true;
3035
3070
  cleanup2();
3036
- resolve13(diagnostics.get(normalized) || []);
3071
+ resolve14(diagnostics.get(normalized) || []);
3037
3072
  };
3038
3073
  const onDiagnostic = () => {
3039
3074
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -3408,7 +3443,7 @@ Working directory: ${options.workingDirectory}`,
3408
3443
  isChunked: true
3409
3444
  });
3410
3445
  if (chunkCount > 1) {
3411
- await new Promise((resolve13) => setTimeout(resolve13, 0));
3446
+ await new Promise((resolve14) => setTimeout(resolve14, 0));
3412
3447
  }
3413
3448
  }
3414
3449
  }
@@ -4027,7 +4062,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4027
4062
  if (!existsSync10(directory)) {
4028
4063
  return [];
4029
4064
  }
4030
- const skills = [];
4065
+ const skills2 = [];
4031
4066
  const entries = await readdir(directory, { withFileTypes: true });
4032
4067
  for (const entry2 of entries) {
4033
4068
  let filePath;
@@ -4051,7 +4086,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4051
4086
  if (parsed) {
4052
4087
  const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
4053
4088
  const loadType = alwaysApply ? "always" : defaultLoadType;
4054
- skills.push({
4089
+ skills2.push({
4055
4090
  name: parsed.metadata.name,
4056
4091
  description: parsed.metadata.description,
4057
4092
  filePath,
@@ -4065,7 +4100,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4065
4100
  } else {
4066
4101
  const name = getSkillNameFromPath(filePath);
4067
4102
  const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
4068
- skills.push({
4103
+ skills2.push({
4069
4104
  name,
4070
4105
  description: firstParagraph.replace(/^#\s*/, "").trim(),
4071
4106
  filePath,
@@ -4078,7 +4113,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4078
4113
  });
4079
4114
  }
4080
4115
  }
4081
- return skills.filter(
4116
+ return skills2.filter(
4082
4117
  (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
4083
4118
  );
4084
4119
  }
@@ -4086,8 +4121,8 @@ async function loadAllSkills(directories) {
4086
4121
  const allSkills = [];
4087
4122
  const seenNames = /* @__PURE__ */ new Set();
4088
4123
  for (const dir of directories) {
4089
- const skills = await loadSkillsFromDirectory(dir);
4090
- for (const skill of skills) {
4124
+ const skills2 = await loadSkillsFromDirectory(dir);
4125
+ for (const skill of skills2) {
4091
4126
  if (!seenNames.has(skill.name.toLowerCase())) {
4092
4127
  seenNames.add(skill.name.toLowerCase());
4093
4128
  allSkills.push(skill);
@@ -4100,12 +4135,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4100
4135
  const allSkills = [];
4101
4136
  const seenNames = /* @__PURE__ */ new Set();
4102
4137
  for (const { path, priority } of discovered.alwaysLoadedDirs) {
4103
- const skills = await loadSkillsFromDirectory(path, {
4138
+ const skills2 = await loadSkillsFromDirectory(path, {
4104
4139
  priority,
4105
4140
  defaultLoadType: "always",
4106
4141
  forceAlwaysApply: true
4107
4142
  });
4108
- for (const skill of skills) {
4143
+ for (const skill of skills2) {
4109
4144
  if (!seenNames.has(skill.name.toLowerCase())) {
4110
4145
  seenNames.add(skill.name.toLowerCase());
4111
4146
  allSkills.push(skill);
@@ -4113,12 +4148,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4113
4148
  }
4114
4149
  }
4115
4150
  for (const { path, priority } of discovered.onDemandDirs) {
4116
- const skills = await loadSkillsFromDirectory(path, {
4151
+ const skills2 = await loadSkillsFromDirectory(path, {
4117
4152
  priority,
4118
4153
  defaultLoadType: "on_demand",
4119
4154
  forceAlwaysApply: false
4120
4155
  });
4121
- for (const skill of skills) {
4156
+ for (const skill of skills2) {
4122
4157
  if (!seenNames.has(skill.name.toLowerCase())) {
4123
4158
  seenNames.add(skill.name.toLowerCase());
4124
4159
  allSkills.push(skill);
@@ -4143,7 +4178,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
4143
4178
  all: allSkills
4144
4179
  };
4145
4180
  }
4146
- async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4181
+ async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
4147
4182
  if (activeFiles.length === 0) {
4148
4183
  return [];
4149
4184
  }
@@ -4153,7 +4188,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4153
4188
  }
4154
4189
  return f;
4155
4190
  });
4156
- const matchedSkills = skills.filter((skill) => {
4191
+ const matchedSkills = skills2.filter((skill) => {
4157
4192
  if (skill.alwaysApply || skill.loadType === "always") {
4158
4193
  return false;
4159
4194
  }
@@ -4199,8 +4234,8 @@ async function loadSkillContent(skillName, directories) {
4199
4234
  content: parsed ? parsed.body : content
4200
4235
  };
4201
4236
  }
4202
- function formatSkillsForContext(skills) {
4203
- const onDemandSkills = skills.filter((s) => !s.alwaysApply && s.loadType !== "always");
4237
+ function formatSkillsForContext(skills2) {
4238
+ const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
4204
4239
  if (onDemandSkills.length === 0) {
4205
4240
  return "No on-demand skills available.";
4206
4241
  }
@@ -4211,12 +4246,12 @@ function formatSkillsForContext(skills) {
4211
4246
  }
4212
4247
  return lines.join("\n");
4213
4248
  }
4214
- function formatAlwaysLoadedSkills(skills) {
4215
- if (skills.length === 0) {
4249
+ function formatAlwaysLoadedSkills(skills2) {
4250
+ if (skills2.length === 0) {
4216
4251
  return "";
4217
4252
  }
4218
4253
  const sections = [];
4219
- for (const skill of skills) {
4254
+ for (const skill of skills2) {
4220
4255
  sections.push(`### ${skill.name}
4221
4256
 
4222
4257
  ${skill.content}`);
@@ -4225,12 +4260,12 @@ ${skill.content}`);
4225
4260
 
4226
4261
  ${sections.join("\n\n---\n\n")}`;
4227
4262
  }
4228
- function formatGlobMatchedSkills(skills) {
4229
- if (skills.length === 0) {
4263
+ function formatGlobMatchedSkills(skills2) {
4264
+ if (skills2.length === 0) {
4230
4265
  return "";
4231
4266
  }
4232
4267
  const sections = [];
4233
- for (const skill of skills) {
4268
+ for (const skill of skills2) {
4234
4269
  sections.push(`### ${skill.name}
4235
4270
 
4236
4271
  ${skill.content}`);
@@ -4272,16 +4307,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
4272
4307
  try {
4273
4308
  switch (action) {
4274
4309
  case "list": {
4275
- const skills = await loadAllSkills(options.skillsDirectories);
4310
+ const skills2 = await loadAllSkills(options.skillsDirectories);
4276
4311
  return {
4277
4312
  success: true,
4278
4313
  action: "list",
4279
- skillCount: skills.length,
4280
- skills: skills.map((s) => ({
4314
+ skillCount: skills2.length,
4315
+ skills: skills2.map((s) => ({
4281
4316
  name: s.name,
4282
4317
  description: s.description
4283
4318
  })),
4284
- formatted: formatSkillsForContext(skills)
4319
+ formatted: formatSkillsForContext(skills2)
4285
4320
  };
4286
4321
  }
4287
4322
  case "load": {
@@ -4719,8 +4754,8 @@ var init_subagent = __esm({
4719
4754
  if (eventQueue.length > 0) {
4720
4755
  yield eventQueue.shift();
4721
4756
  } else if (!done) {
4722
- const event = await new Promise((resolve13) => {
4723
- resolveNext = resolve13;
4757
+ const event = await new Promise((resolve14) => {
4758
+ resolveNext = resolve14;
4724
4759
  });
4725
4760
  if (event) {
4726
4761
  yield event;
@@ -5362,7 +5397,7 @@ function formatError(error) {
5362
5397
  }
5363
5398
  }
5364
5399
  function sleep(ms) {
5365
- return new Promise((resolve13) => setTimeout(resolve13, ms));
5400
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
5366
5401
  }
5367
5402
  function isPathExcluded(relativePath, exclude) {
5368
5403
  return exclude.some((pattern) => {
@@ -5380,7 +5415,7 @@ function isPathExcluded(relativePath, exclude) {
5380
5415
  }
5381
5416
  async function walkDirectory(dir, include, exclude, baseDir) {
5382
5417
  const { readdirSync: readdirSync4 } = await import("fs");
5383
- const { join: join17, relative: relative10 } = await import("path");
5418
+ const { join: join19, relative: relative10 } = await import("path");
5384
5419
  const files = [];
5385
5420
  function walk(currentDir) {
5386
5421
  let entries;
@@ -5390,7 +5425,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
5390
5425
  return;
5391
5426
  }
5392
5427
  for (const entry2 of entries) {
5393
- const fullPath = join17(currentDir, entry2.name);
5428
+ const fullPath = join19(currentDir, entry2.name);
5394
5429
  const relativePath = relative10(baseDir, fullPath);
5395
5430
  if (isPathExcluded(relativePath, exclude)) {
5396
5431
  continue;
@@ -7238,8 +7273,8 @@ async function buildSystemPrompt(options) {
7238
7273
  }
7239
7274
  } else {
7240
7275
  const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
7241
- const skills = await loadAllSkills2(skillsDirectories);
7242
- onDemandSkillsContext = formatSkillsForContext(skills);
7276
+ const skills2 = await loadAllSkills2(skillsDirectories);
7277
+ onDemandSkillsContext = formatSkillsForContext(skills2);
7243
7278
  }
7244
7279
  const todos = await todoQueries.getBySession(sessionId);
7245
7280
  const todosContext = formatTodosForContext(todos);
@@ -8138,6 +8173,35 @@ function stripOrphanedToolResults(msg, removedIds) {
8138
8173
  if (parts.length === 0) return null;
8139
8174
  return { ...msg, content: parts };
8140
8175
  }
8176
+ function wrapToolsNeverThrow(tools) {
8177
+ if (!tools || typeof tools !== "object") return tools;
8178
+ const wrapped = {};
8179
+ for (const [name, t] of Object.entries(tools)) {
8180
+ if (!t || typeof t.execute !== "function") {
8181
+ wrapped[name] = t;
8182
+ continue;
8183
+ }
8184
+ const original = t.execute;
8185
+ wrapped[name] = {
8186
+ ...t,
8187
+ execute: async (input, opts) => {
8188
+ try {
8189
+ return await original.call(t, input, opts);
8190
+ } catch (err) {
8191
+ const message = err?.message ?? String(err);
8192
+ console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
8193
+ return {
8194
+ __error: true,
8195
+ tool: name,
8196
+ message,
8197
+ note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
8198
+ };
8199
+ }
8200
+ }
8201
+ };
8202
+ }
8203
+ return wrapped;
8204
+ }
8141
8205
  function repairToolPairing(messages) {
8142
8206
  const toolCallIds = /* @__PURE__ */ new Set();
8143
8207
  const toolResultIds = /* @__PURE__ */ new Set();
@@ -8503,6 +8567,120 @@ var init_web = __esm({
8503
8567
  }
8504
8568
  });
8505
8569
 
8570
+ // src/integrations/slack/persistence.ts
8571
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
8572
+ import { join as join9, dirname as dirname6 } from "path";
8573
+ function cachePath() {
8574
+ return join9(ensureAppDataDirectory(), FILENAME);
8575
+ }
8576
+ function load() {
8577
+ if (loaded) return;
8578
+ loaded = true;
8579
+ const path = cachePath();
8580
+ if (!existsSync16(path)) return;
8581
+ try {
8582
+ const raw = readFileSync7(path, "utf-8");
8583
+ const parsed = JSON.parse(raw);
8584
+ if (parsed?.version !== FILE_VERSION) return;
8585
+ const now = Date.now();
8586
+ for (const [id, e] of Object.entries(parsed.users || {})) {
8587
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
8588
+ userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
8589
+ }
8590
+ }
8591
+ for (const [key2, e] of Object.entries(parsed.threads || {})) {
8592
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
8593
+ threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
8594
+ }
8595
+ }
8596
+ } catch (err) {
8597
+ console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
8598
+ }
8599
+ }
8600
+ function evictIfOversized(map, max) {
8601
+ if (map.size <= max) return;
8602
+ const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
8603
+ const toRemove = map.size - max;
8604
+ for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
8605
+ }
8606
+ function scheduleSave() {
8607
+ dirty = true;
8608
+ if (saveTimer) return;
8609
+ saveTimer = setTimeout(() => {
8610
+ saveTimer = null;
8611
+ if (dirty) saveSync();
8612
+ }, SAVE_DEBOUNCE_MS);
8613
+ saveTimer.unref?.();
8614
+ }
8615
+ function saveSync() {
8616
+ dirty = false;
8617
+ try {
8618
+ const path = cachePath();
8619
+ const dir = dirname6(path);
8620
+ if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
8621
+ const payload = {
8622
+ version: FILE_VERSION,
8623
+ users: Object.fromEntries(userMap),
8624
+ threads: Object.fromEntries(threadMap)
8625
+ };
8626
+ const tmp = `${path}.tmp`;
8627
+ writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
8628
+ renameSync(tmp, path);
8629
+ } catch (err) {
8630
+ console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
8631
+ }
8632
+ }
8633
+ function hookExit() {
8634
+ if (exitHooked) return;
8635
+ exitHooked = true;
8636
+ const flush2 = () => {
8637
+ if (dirty) saveSync();
8638
+ };
8639
+ process.once("beforeExit", flush2);
8640
+ process.once("SIGINT", flush2);
8641
+ process.once("SIGTERM", flush2);
8642
+ }
8643
+ function getCachedUserName(userId) {
8644
+ load();
8645
+ return userMap.get(userId);
8646
+ }
8647
+ function setCachedUserName(userId, entry2) {
8648
+ load();
8649
+ hookExit();
8650
+ userMap.set(userId, entry2);
8651
+ evictIfOversized(userMap, MAX_USERS);
8652
+ scheduleSave();
8653
+ }
8654
+ function getCachedThreadOwnership(key2) {
8655
+ load();
8656
+ return threadMap.get(key2);
8657
+ }
8658
+ function setCachedThreadOwnership(key2, entry2) {
8659
+ load();
8660
+ hookExit();
8661
+ threadMap.set(key2, entry2);
8662
+ evictIfOversized(threadMap, MAX_THREADS);
8663
+ scheduleSave();
8664
+ }
8665
+ var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
8666
+ var init_persistence = __esm({
8667
+ "src/integrations/slack/persistence.ts"() {
8668
+ "use strict";
8669
+ init_config();
8670
+ FILENAME = "slack-cache.json";
8671
+ FILE_VERSION = 1;
8672
+ SAVE_DEBOUNCE_MS = 500;
8673
+ MAX_USERS = 5e3;
8674
+ MAX_THREADS = 5e3;
8675
+ loaded = false;
8676
+ userMap = /* @__PURE__ */ new Map();
8677
+ threadMap = /* @__PURE__ */ new Map();
8678
+ dirty = false;
8679
+ saveTimer = null;
8680
+ exitHooked = false;
8681
+ }
8682
+ });
8683
+
8506
8684
  // src/integrations/slack/client.ts
8507
8685
  function readSlackConfig() {
8508
8686
  try {
@@ -8625,13 +8803,13 @@ async function fetchSlackUserName(userId) {
8625
8803
  async function resolveSlackUserName(userId) {
8626
8804
  if (!userId) return null;
8627
8805
  const now = Date.now();
8628
- const hit = userNameCache.get(userId);
8806
+ const hit = getCachedUserName(userId);
8629
8807
  if (hit && hit.expiresAt > now) return hit.name;
8630
8808
  const inflight = userInflight.get(userId);
8631
8809
  if (inflight) return inflight;
8632
8810
  const p = (async () => {
8633
8811
  const name = await fetchSlackUserName(userId);
8634
- userNameCache.set(userId, {
8812
+ setCachedUserName(userId, {
8635
8813
  name,
8636
8814
  expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
8637
8815
  });
@@ -8653,11 +8831,63 @@ async function normalizeSlackMentions(text) {
8653
8831
  }
8654
8832
  return text.replace(userMentionRe, (_full, id, label) => {
8655
8833
  if (label) return `${label} <@${id}>`;
8656
- const cached = userNameCache.get(id);
8834
+ const cached = getCachedUserName(id);
8657
8835
  const name = cached?.name;
8658
8836
  return name ? `${name} <@${id}>` : `<@${id}>`;
8659
8837
  });
8660
8838
  }
8839
+ function threadCacheKey(channel, threadTs) {
8840
+ return `${channel}\u241F${threadTs}`;
8841
+ }
8842
+ async function fetchBotParticipatedInThread(channel, threadTs) {
8843
+ const token = getSlackBotToken();
8844
+ if (!token) return false;
8845
+ const self = await ensureSlackSelfIdentity();
8846
+ if (!self) return false;
8847
+ try {
8848
+ const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
8849
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
8850
+ const data = await res.json().catch(() => ({}));
8851
+ if (!data?.ok) {
8852
+ console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
8853
+ return false;
8854
+ }
8855
+ const messages = Array.isArray(data.messages) ? data.messages : [];
8856
+ return messages.some(
8857
+ (m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
8858
+ );
8859
+ } catch (err) {
8860
+ console.warn(`[slack] conversations.replies error:`, err?.message || err);
8861
+ return false;
8862
+ }
8863
+ }
8864
+ async function botParticipatedInThread(channel, threadTs) {
8865
+ if (!channel || !threadTs) return false;
8866
+ const key2 = threadCacheKey(channel, threadTs);
8867
+ const now = Date.now();
8868
+ const hit = getCachedThreadOwnership(key2);
8869
+ if (hit && hit.expiresAt > now) return hit.owned;
8870
+ const inflight = threadOwnedInflight.get(key2);
8871
+ if (inflight) return inflight;
8872
+ const p = (async () => {
8873
+ const owned = await fetchBotParticipatedInThread(channel, threadTs);
8874
+ setCachedThreadOwnership(key2, {
8875
+ owned,
8876
+ expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
8877
+ });
8878
+ threadOwnedInflight.delete(key2);
8879
+ return owned;
8880
+ })();
8881
+ threadOwnedInflight.set(key2, p);
8882
+ return p;
8883
+ }
8884
+ function noteBotPostedInThread(channel, threadTs) {
8885
+ if (!channel || !threadTs) return;
8886
+ setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
8887
+ owned: true,
8888
+ expiresAt: Date.now() + THREAD_OWNED_TTL_MS
8889
+ });
8890
+ }
8661
8891
  function getSlackDeniedReplyPolicy() {
8662
8892
  try {
8663
8893
  const cfg = getConfig();
@@ -8670,17 +8900,20 @@ function getSlackDeniedReplyPolicy() {
8670
8900
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
8671
8901
  }
8672
8902
  }
8673
- var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
8903
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
8674
8904
  var init_client3 = __esm({
8675
8905
  "src/integrations/slack/client.ts"() {
8676
8906
  "use strict";
8677
8907
  init_config();
8908
+ init_persistence();
8678
8909
  cachedSelf = null;
8679
8910
  selfInflight = null;
8680
8911
  USER_TTL_MS = 60 * 60 * 1e3;
8681
8912
  USER_FAIL_TTL_MS = 5 * 60 * 1e3;
8682
- userNameCache = /* @__PURE__ */ new Map();
8683
8913
  userInflight = /* @__PURE__ */ new Map();
8914
+ THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
8915
+ THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
8916
+ threadOwnedInflight = /* @__PURE__ */ new Map();
8684
8917
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
8685
8918
  }
8686
8919
  });
@@ -8780,6 +9013,7 @@ var init_slack = __esm({
8780
9013
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
8781
9014
  if (r.slackChannel && r.threadTs) {
8782
9015
  markThreadOwned(r.slackChannel, r.threadTs);
9016
+ noteBotPostedInThread(r.slackChannel, r.threadTs);
8783
9017
  }
8784
9018
  },
8785
9019
  displayLabel(ref) {
@@ -9437,8 +9671,8 @@ var init_orchestrator_actions = __esm({
9437
9671
 
9438
9672
  // src/integrations/mcp/store.ts
9439
9673
  import { nanoid as nanoid6 } from "nanoid";
9440
- import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
9441
- import { resolve as resolve10, join as join9 } from "path";
9674
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
9675
+ import { resolve as resolve10, join as join10 } from "path";
9442
9676
  function readServers() {
9443
9677
  try {
9444
9678
  const cfg = getConfig();
@@ -9450,12 +9684,12 @@ function readServers() {
9450
9684
  function refreshMcpServersFromDisk() {
9451
9685
  const candidates = [
9452
9686
  resolve10(process.cwd(), "sparkecoder.config.json"),
9453
- join9(ensureAppDataDirectory(), "sparkecoder.config.json")
9687
+ join10(ensureAppDataDirectory(), "sparkecoder.config.json")
9454
9688
  ];
9455
9689
  for (const path of candidates) {
9456
- if (!existsSync16(path)) continue;
9690
+ if (!existsSync17(path)) continue;
9457
9691
  try {
9458
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
9692
+ const raw = JSON.parse(readFileSync8(path, "utf-8"));
9459
9693
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
9460
9694
  setMcpServers(servers2);
9461
9695
  return servers2;
@@ -9707,11 +9941,11 @@ function waitForTaskQuestionAnswer(question) {
9707
9941
  if (pendingQuestions.has(k)) {
9708
9942
  return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
9709
9943
  }
9710
- return new Promise((resolve13, reject) => {
9944
+ return new Promise((resolve14, reject) => {
9711
9945
  pendingQuestions.set(k, {
9712
9946
  ...question,
9713
9947
  createdAt: /* @__PURE__ */ new Date(),
9714
- resolve: resolve13,
9948
+ resolve: resolve14,
9715
9949
  reject
9716
9950
  });
9717
9951
  });
@@ -10068,7 +10302,7 @@ __export(recorder_exports, {
10068
10302
  import { exec as exec5 } from "child_process";
10069
10303
  import { promisify as promisify5 } from "util";
10070
10304
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
10071
- import { join as join10 } from "path";
10305
+ import { join as join11 } from "path";
10072
10306
  import { tmpdir } from "os";
10073
10307
  import { nanoid as nanoid7 } from "nanoid";
10074
10308
  async function checkFfmpeg() {
@@ -10125,21 +10359,21 @@ var init_recorder = __esm({
10125
10359
  */
10126
10360
  async encode() {
10127
10361
  if (this.frames.length === 0) return null;
10128
- const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
10362
+ const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
10129
10363
  await mkdir4(workDir, { recursive: true });
10130
10364
  try {
10131
10365
  for (let i = 0; i < this.frames.length; i++) {
10132
- const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
10366
+ const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
10133
10367
  await writeFile5(framePath, this.frames[i].data);
10134
10368
  }
10135
10369
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
10136
10370
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
10137
10371
  const clampedFps = Math.max(1, Math.min(fps, 30));
10138
- const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
10372
+ const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
10139
10373
  const hasFfmpeg = await checkFfmpeg();
10140
10374
  if (hasFfmpeg) {
10141
10375
  await execAsync5(
10142
- `ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
10376
+ `ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
10143
10377
  { timeout: 12e4 }
10144
10378
  );
10145
10379
  } else {
@@ -10151,7 +10385,7 @@ var init_recorder = __esm({
10151
10385
  const files = await readdir5(workDir);
10152
10386
  for (const f of files) {
10153
10387
  if (f.startsWith("frame_")) {
10154
- await unlink2(join10(workDir, f)).catch(() => {
10388
+ await unlink2(join11(workDir, f)).catch(() => {
10155
10389
  });
10156
10390
  }
10157
10391
  }
@@ -10418,7 +10652,8 @@ ${personality.trim()}`;
10418
10652
  }
10419
10653
  const messages = await this.context.getMessages();
10420
10654
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
10421
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
10655
+ const approvalWrapped = this.wrapToolsWithApproval(options, tools);
10656
+ const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
10422
10657
  const useAnthropic = isAnthropicModel(this.session.model);
10423
10658
  const stream = streamText2({
10424
10659
  model: resolveModel(this.session.model),
@@ -10432,6 +10667,17 @@ ${personality.trim()}`;
10432
10667
  providerOptions: useAnthropic ? {
10433
10668
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
10434
10669
  } : void 0,
10670
+ // Run repairToolPairing before EVERY step's model call, not just the
10671
+ // first one. The AI SDK's multi-step loop can otherwise feed the model
10672
+ // a prompt containing an orphan tool-call (e.g. when a previous step's
10673
+ // tool result was lost, dropped during compaction, or the stream was
10674
+ // aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
10675
+ // ever reaches the model and we never hit AI_MissingToolResultsError.
10676
+ prepareStep: async ({ messages: stepMessages }) => {
10677
+ const repaired = repairToolPairing(stepMessages);
10678
+ if (repaired === stepMessages) return {};
10679
+ return { messages: repaired };
10680
+ },
10435
10681
  onStepFinish: async (step) => {
10436
10682
  options.onStepFinish?.(step);
10437
10683
  },
@@ -10467,7 +10713,7 @@ ${personality.trim()}`;
10467
10713
  });
10468
10714
  const messages = await this.context.getMessages();
10469
10715
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
10470
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
10716
+ const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
10471
10717
  const useAnthropic = isAnthropicModel(this.session.model);
10472
10718
  const result = await generateText3({
10473
10719
  model: resolveModel(this.session.model),
@@ -10478,7 +10724,13 @@ ${personality.trim()}`;
10478
10724
  // Enable extended thinking/reasoning for models that support it
10479
10725
  providerOptions: useAnthropic ? {
10480
10726
  anthropic: getAnthropicProviderOptions(this.session.model)
10481
- } : void 0
10727
+ } : void 0,
10728
+ // Repair tool pairing before every step (see `stream()` for full rationale).
10729
+ prepareStep: async ({ messages: stepMessages }) => {
10730
+ const repaired = repairToolPairing(stepMessages);
10731
+ if (repaired === stepMessages) return {};
10732
+ return { messages: repaired };
10733
+ }
10482
10734
  });
10483
10735
  const responseMessages = result.response.messages;
10484
10736
  this.context.addResponseMessages(responseMessages);
@@ -10655,12 +10907,19 @@ ${p.text}` : p.text;
10655
10907
  model: resolveModel(this.session.model),
10656
10908
  system: systemPrompt,
10657
10909
  messages,
10658
- tools: taskTools,
10910
+ tools: wrapToolsNeverThrow(taskTools),
10659
10911
  stopWhen: stepCountIs2(500),
10660
10912
  abortSignal: combinedAbort,
10661
10913
  providerOptions: useAnthropic ? {
10662
10914
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
10663
10915
  } : void 0,
10916
+ // See the matching note in `stream()` — repair tool pairing before
10917
+ // every step so we never feed the model an orphan tool-call.
10918
+ prepareStep: async ({ messages: stepMessages }) => {
10919
+ const repaired = repairToolPairing(stepMessages);
10920
+ if (repaired === stepMessages) return {};
10921
+ return { messages: repaired };
10922
+ },
10664
10923
  onStepFinish: async (step) => {
10665
10924
  options.onStepFinish?.(step);
10666
10925
  fireWebhook("task.step_finished", { iteration, text: step.text });
@@ -10860,14 +11119,14 @@ ${p.text}` : p.text;
10860
11119
  const result = await recorder.encode();
10861
11120
  recorder.clear();
10862
11121
  if (!result) return [];
10863
- const { readFile: readFile12, unlink: unlink3 } = await import("fs/promises");
11122
+ const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
10864
11123
  const uploadInfo = await storageQueries2.getUploadUrl(
10865
11124
  this.session.id,
10866
11125
  `browser-recording-${Date.now()}.mp4`,
10867
11126
  "video/mp4",
10868
11127
  "browser-recording"
10869
11128
  );
10870
- const fileData = await readFile12(result.path);
11129
+ const fileData = await readFile13(result.path);
10871
11130
  await fetch(uploadInfo.uploadUrl, {
10872
11131
  method: "PUT",
10873
11132
  headers: { "Content-Type": "video/mp4" },
@@ -10875,7 +11134,7 @@ ${p.text}` : p.text;
10875
11134
  });
10876
11135
  await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
10877
11136
  const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
10878
- await unlink3(result.path).catch(() => {
11137
+ await unlink4(result.path).catch(() => {
10879
11138
  });
10880
11139
  console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
10881
11140
  return [dlInfo.downloadUrl];
@@ -10893,13 +11152,13 @@ ${p.text}` : p.text;
10893
11152
  try {
10894
11153
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10895
11154
  if (!isRemoteConfigured2()) return [];
10896
- const { readFile: readFile12 } = await import("fs/promises");
10897
- const { join: join17, basename: basename6 } = await import("path");
11155
+ const { readFile: readFile13 } = await import("fs/promises");
11156
+ const { join: join19, basename: basename7 } = await import("path");
10898
11157
  const urls = [];
10899
11158
  for (const filePath of filePaths) {
10900
11159
  try {
10901
- const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
10902
- const fileName = basename6(fullPath);
11160
+ const fullPath = filePath.startsWith("/") ? filePath : join19(this.session.workingDirectory, filePath);
11161
+ const fileName = basename7(fullPath);
10903
11162
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10904
11163
  const mimeMap = {
10905
11164
  pdf: "application/pdf",
@@ -10923,7 +11182,7 @@ ${p.text}` : p.text;
10923
11182
  contentType,
10924
11183
  "task-output"
10925
11184
  );
10926
- const fileData = await readFile12(fullPath);
11185
+ const fileData = await readFile13(fullPath);
10927
11186
  await fetch(uploadInfo.uploadUrl, {
10928
11187
  method: "PUT",
10929
11188
  headers: { "Content-Type": contentType },
@@ -10972,8 +11231,8 @@ ${p.text}` : p.text;
10972
11231
  this.pendingApprovals.set(toolCallId, await execution);
10973
11232
  options.onApprovalRequired?.(await execution);
10974
11233
  await sessionQueries.updateStatus(this.session.id, "waiting");
10975
- const approved = await new Promise((resolve13) => {
10976
- approvalResolvers.set(toolCallId, { resolve: resolve13, sessionId: this.session.id });
11234
+ const approved = await new Promise((resolve14) => {
11235
+ approvalResolvers.set(toolCallId, { resolve: resolve14, sessionId: this.session.id });
10977
11236
  });
10978
11237
  const resolverData = approvalResolvers.get(toolCallId);
10979
11238
  approvalResolvers.delete(toolCallId);
@@ -11079,8 +11338,8 @@ async function withSessionLock(sessionId, fn) {
11079
11338
  state2.pending++;
11080
11339
  const prev = state2.tail;
11081
11340
  let release;
11082
- const next = new Promise((resolve13) => {
11083
- release = resolve13;
11341
+ const next = new Promise((resolve14) => {
11342
+ release = resolve14;
11084
11343
  });
11085
11344
  state2.tail = prev.then(() => next);
11086
11345
  await prev;
@@ -11103,19 +11362,19 @@ var init_session_lock = __esm({
11103
11362
  });
11104
11363
 
11105
11364
  // src/orchestrator/webhook-events.ts
11106
- import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
11107
- import { dirname as dirname6, join as join11 } from "path";
11365
+ import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
11366
+ import { dirname as dirname7, join as join12 } from "path";
11108
11367
  import { nanoid as nanoid9 } from "nanoid";
11109
11368
  function logFilePath() {
11110
- return join11(getAppDataDirectory(), "webhook-events.jsonl");
11369
+ return join12(getAppDataDirectory(), "webhook-events.jsonl");
11111
11370
  }
11112
11371
  function ensureLoaded() {
11113
11372
  if (cache !== null) return cache;
11114
11373
  cache = [];
11115
11374
  try {
11116
11375
  const p = logFilePath();
11117
- if (!existsSync17(p)) return cache;
11118
- const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
11376
+ if (!existsSync18(p)) return cache;
11377
+ const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
11119
11378
  for (const line of lines) {
11120
11379
  try {
11121
11380
  cache.push(JSON.parse(line));
@@ -11125,7 +11384,7 @@ function ensureLoaded() {
11125
11384
  if (cache.length > MAX_EVENTS) {
11126
11385
  cache = cache.slice(-MAX_EVENTS);
11127
11386
  try {
11128
- writeFileSync3(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
11387
+ writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
11129
11388
  } catch {
11130
11389
  }
11131
11390
  }
@@ -11139,7 +11398,7 @@ function appendEvent(ev) {
11139
11398
  if (list.length > MAX_EVENTS) list.shift();
11140
11399
  try {
11141
11400
  const p = logFilePath();
11142
- mkdirSync6(dirname6(p), { recursive: true });
11401
+ mkdirSync7(dirname7(p), { recursive: true });
11143
11402
  appendFileSync3(p, JSON.stringify(ev) + "\n");
11144
11403
  } catch {
11145
11404
  }
@@ -11170,8 +11429,8 @@ function updateEvent(id, patch) {
11170
11429
  list[i] = { ...list[i], ...patch };
11171
11430
  try {
11172
11431
  const p = logFilePath();
11173
- mkdirSync6(dirname6(p), { recursive: true });
11174
- writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
11432
+ mkdirSync7(dirname7(p), { recursive: true });
11433
+ writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
11175
11434
  } catch {
11176
11435
  }
11177
11436
  }
@@ -11203,7 +11462,7 @@ function listEvents(filter = {}) {
11203
11462
  function clearAllEvents() {
11204
11463
  cache = [];
11205
11464
  try {
11206
- writeFileSync3(logFilePath(), "");
11465
+ writeFileSync4(logFilePath(), "");
11207
11466
  } catch {
11208
11467
  }
11209
11468
  }
@@ -11693,17 +11952,17 @@ import chalk from "chalk";
11693
11952
  import ora from "ora";
11694
11953
  import "dotenv/config";
11695
11954
  import { createInterface } from "readline";
11696
- import { dirname as dirname9 } from "path";
11955
+ import { dirname as dirname11 } from "path";
11697
11956
  import { fileURLToPath as fileURLToPath5 } from "url";
11698
11957
 
11699
11958
  // src/server/index.ts
11700
11959
  import "dotenv/config";
11701
- import { Hono as Hono9 } from "hono";
11960
+ import { Hono as Hono10 } from "hono";
11702
11961
  import { serve } from "@hono/node-server";
11703
11962
  import { cors } from "hono/cors";
11704
11963
  import { logger } from "hono/logger";
11705
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
11706
- import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
11964
+ import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
11965
+ import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
11707
11966
  import { spawn as spawn2 } from "child_process";
11708
11967
  import { createServer as createNetServer } from "net";
11709
11968
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -11717,9 +11976,9 @@ init_checkpoints();
11717
11976
  import { Hono } from "hono";
11718
11977
  import { zValidator } from "@hono/zod-validator";
11719
11978
  import { z as z16 } from "zod";
11720
- import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
11979
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
11721
11980
  import { readdir as readdir6 } from "fs/promises";
11722
- import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
11981
+ import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
11723
11982
  import { nanoid as nanoid10 } from "nanoid";
11724
11983
 
11725
11984
  // src/tasks/agent-status.ts
@@ -12357,12 +12616,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
12357
12616
  });
12358
12617
  function getAttachmentsDir(sessionId) {
12359
12618
  const appDataDir = getAppDataDirectory();
12360
- return join12(appDataDir, "attachments", sessionId);
12619
+ return join13(appDataDir, "attachments", sessionId);
12361
12620
  }
12362
12621
  function ensureAttachmentsDir(sessionId) {
12363
12622
  const dir = getAttachmentsDir(sessionId);
12364
- if (!existsSync18(dir)) {
12365
- mkdirSync7(dir, { recursive: true });
12623
+ if (!existsSync19(dir)) {
12624
+ mkdirSync8(dir, { recursive: true });
12366
12625
  }
12367
12626
  return dir;
12368
12627
  }
@@ -12373,12 +12632,12 @@ sessions2.get("/:id/attachments", async (c) => {
12373
12632
  return c.json({ error: "Session not found" }, 404);
12374
12633
  }
12375
12634
  const dir = getAttachmentsDir(sessionId);
12376
- if (!existsSync18(dir)) {
12635
+ if (!existsSync19(dir)) {
12377
12636
  return c.json({ sessionId, attachments: [], count: 0 });
12378
12637
  }
12379
12638
  const files = readdirSync3(dir);
12380
12639
  const attachments = files.map((filename) => {
12381
- const filePath = join12(dir, filename);
12640
+ const filePath = join13(dir, filename);
12382
12641
  const stats = statSync2(filePath);
12383
12642
  return {
12384
12643
  id: filename.split("_")[0],
@@ -12413,9 +12672,9 @@ sessions2.post("/:id/attachments", async (c) => {
12413
12672
  const id = nanoid10(10);
12414
12673
  const ext = extname8(file.name) || "";
12415
12674
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
12416
- const filePath = join12(dir, safeFilename);
12675
+ const filePath = join13(dir, safeFilename);
12417
12676
  const arrayBuffer = await file.arrayBuffer();
12418
- writeFileSync4(filePath, Buffer.from(arrayBuffer));
12677
+ writeFileSync5(filePath, Buffer.from(arrayBuffer));
12419
12678
  return c.json({
12420
12679
  id,
12421
12680
  filename: file.name,
@@ -12439,13 +12698,13 @@ sessions2.post("/:id/attachments", async (c) => {
12439
12698
  const id = nanoid10(10);
12440
12699
  const ext = extname8(body.filename) || "";
12441
12700
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
12442
- const filePath = join12(dir, safeFilename);
12701
+ const filePath = join13(dir, safeFilename);
12443
12702
  let base64Data = body.data;
12444
12703
  if (base64Data.includes(",")) {
12445
12704
  base64Data = base64Data.split(",")[1];
12446
12705
  }
12447
12706
  const buffer = Buffer.from(base64Data, "base64");
12448
- writeFileSync4(filePath, buffer);
12707
+ writeFileSync5(filePath, buffer);
12449
12708
  return c.json({
12450
12709
  id,
12451
12710
  filename: body.filename,
@@ -12468,7 +12727,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
12468
12727
  return c.json({ error: "Session not found" }, 404);
12469
12728
  }
12470
12729
  const dir = getAttachmentsDir(sessionId);
12471
- if (!existsSync18(dir)) {
12730
+ if (!existsSync19(dir)) {
12472
12731
  return c.json({ error: "Attachment not found" }, 404);
12473
12732
  }
12474
12733
  const files = readdirSync3(dir);
@@ -12476,7 +12735,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
12476
12735
  if (!file) {
12477
12736
  return c.json({ error: "Attachment not found" }, 404);
12478
12737
  }
12479
- const filePath = join12(dir, file);
12738
+ const filePath = join13(dir, file);
12480
12739
  unlinkSync2(filePath);
12481
12740
  return c.json({ success: true, id: attachmentId });
12482
12741
  });
@@ -12559,7 +12818,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
12559
12818
  const entries = await readdir6(currentDir, { withFileTypes: true });
12560
12819
  for (const entry2 of entries) {
12561
12820
  if (results.length >= limit * 2) break;
12562
- const fullPath = join12(currentDir, entry2.name);
12821
+ const fullPath = join13(currentDir, entry2.name);
12563
12822
  const relativePath = relative9(baseDir, fullPath);
12564
12823
  if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
12565
12824
  continue;
@@ -12607,7 +12866,7 @@ sessions2.get(
12607
12866
  return c.json({ error: "Session not found" }, 404);
12608
12867
  }
12609
12868
  const workingDirectory = session.workingDirectory;
12610
- if (!existsSync18(workingDirectory)) {
12869
+ if (!existsSync19(workingDirectory)) {
12611
12870
  return c.json({
12612
12871
  sessionId,
12613
12872
  workingDirectory,
@@ -12715,14 +12974,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
12715
12974
 
12716
12975
  // src/server/routes/agents.ts
12717
12976
  init_db();
12718
- init_agent();
12719
- init_session_lock();
12720
- init_config();
12721
12977
  import { Hono as Hono2 } from "hono";
12722
12978
  import { zValidator as zValidator2 } from "@hono/zod-validator";
12723
12979
  import { z as z17 } from "zod";
12724
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
12725
- import { join as join13 } from "path";
12980
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
12981
+ import { join as join14 } from "path";
12982
+
12983
+ // src/agent/missing-tool-recovery.ts
12984
+ init_db();
12985
+ function extractMissingToolCallIds(error) {
12986
+ if (!error) return [];
12987
+ const e = error;
12988
+ if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
12989
+ return e.toolCallIds;
12990
+ }
12991
+ const msg = typeof e.message === "string" ? e.message : "";
12992
+ const ids = /* @__PURE__ */ new Set();
12993
+ for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
12994
+ for (const id of m[1].split(/\s*,\s*/)) {
12995
+ const trimmed = id.trim();
12996
+ if (trimmed) ids.add(trimmed);
12997
+ }
12998
+ }
12999
+ return [...ids];
13000
+ }
13001
+ function isMissingToolResultsError(error) {
13002
+ if (!error) return false;
13003
+ const e = error;
13004
+ if (e.name === "AI_MissingToolResultsError") return true;
13005
+ if (Array.isArray(e.toolCallIds)) return true;
13006
+ return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
13007
+ }
13008
+ async function recoverFromMissingToolResults(sessionId, error) {
13009
+ const toolCallIds = extractMissingToolCallIds(error);
13010
+ if (toolCallIds.length === 0) return null;
13011
+ let history = [];
13012
+ try {
13013
+ history = await messageQueries.getModelMessages(sessionId);
13014
+ } catch (err) {
13015
+ console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
13016
+ }
13017
+ const toolNameByCallId = /* @__PURE__ */ new Map();
13018
+ const existingResultIds = /* @__PURE__ */ new Set();
13019
+ for (const msg of history) {
13020
+ if (!Array.isArray(msg.content)) continue;
13021
+ for (const part of msg.content) {
13022
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
13023
+ if (typeof part.toolName === "string") toolNameByCallId.set(part.toolCallId, part.toolName);
13024
+ }
13025
+ if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
13026
+ existingResultIds.add(part.toolCallId);
13027
+ }
13028
+ }
13029
+ }
13030
+ const resolved = toolCallIds.map((id) => ({
13031
+ toolCallId: id,
13032
+ toolName: toolNameByCallId.get(id) ?? "unknown",
13033
+ foundInAssistantMessage: toolNameByCallId.has(id)
13034
+ }));
13035
+ const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
13036
+ let syntheticToolMessageSaved = false;
13037
+ if (stillOrphaned.length > 0) {
13038
+ const syntheticParts = stillOrphaned.map((id) => ({
13039
+ type: "tool-result",
13040
+ toolCallId: id,
13041
+ toolName: toolNameByCallId.get(id) ?? "unknown",
13042
+ output: {
13043
+ type: "text",
13044
+ 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.]"
13045
+ }
13046
+ }));
13047
+ try {
13048
+ await messageQueries.create(sessionId, {
13049
+ role: "tool",
13050
+ content: syntheticParts
13051
+ });
13052
+ syntheticToolMessageSaved = true;
13053
+ } catch (err) {
13054
+ console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
13055
+ }
13056
+ }
13057
+ const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
13058
+ return {
13059
+ kind: "missing_tool_results",
13060
+ toolCallIds,
13061
+ resolved,
13062
+ syntheticToolMessageSaved,
13063
+ lastFewMessageRoles,
13064
+ 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."
13065
+ };
13066
+ }
13067
+
13068
+ // src/server/routes/agents.ts
13069
+ init_agent();
13070
+ init_session_lock();
13071
+ init_config();
12726
13072
 
12727
13073
  // src/server/resumable-stream.ts
12728
13074
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -12878,7 +13224,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
12878
13224
  toolCallId,
12879
13225
  argsTextDelta: chunk
12880
13226
  }));
12881
- await new Promise((resolve13) => setTimeout(resolve13, 0));
13227
+ await new Promise((resolve14) => setTimeout(resolve14, 0));
12882
13228
  }
12883
13229
  }
12884
13230
  function buildDevtoolsContextXml(sessionId) {
@@ -12929,12 +13275,12 @@ var rejectSchema = z17.object({
12929
13275
  var streamAbortControllers = /* @__PURE__ */ new Map();
12930
13276
  function getAttachmentsDirectory(sessionId) {
12931
13277
  const appDataDir = getAppDataDirectory();
12932
- return join13(appDataDir, "attachments", sessionId);
13278
+ return join14(appDataDir, "attachments", sessionId);
12933
13279
  }
12934
13280
  async function saveAttachmentToDisk(sessionId, attachment, index) {
12935
13281
  const attachmentsDir = getAttachmentsDirectory(sessionId);
12936
- if (!existsSync19(attachmentsDir)) {
12937
- mkdirSync8(attachmentsDir, { recursive: true });
13282
+ if (!existsSync20(attachmentsDir)) {
13283
+ mkdirSync9(attachmentsDir, { recursive: true });
12938
13284
  }
12939
13285
  let filename = attachment.filename;
12940
13286
  if (!filename) {
@@ -12952,8 +13298,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
12952
13298
  attachment.mediaType = resized.mediaType;
12953
13299
  attachment.data = buffer.toString("base64");
12954
13300
  }
12955
- const filePath = join13(attachmentsDir, filename);
12956
- writeFileSync5(filePath, buffer);
13301
+ const filePath = join14(attachmentsDir, filename);
13302
+ writeFileSync6(filePath, buffer);
12957
13303
  return filePath;
12958
13304
  }
12959
13305
  function stripDataUrlPrefix2(data) {
@@ -13133,7 +13479,7 @@ ${prompt}` });
13133
13479
  chunkIndex,
13134
13480
  chunkCount
13135
13481
  }));
13136
- await new Promise((resolve13) => setTimeout(resolve13, 0));
13482
+ await new Promise((resolve14) => setTimeout(resolve14, 0));
13137
13483
  }
13138
13484
  const browserPort = progress.data?.browserStreamPort;
13139
13485
  const browserClosed = progress.data?.browserClosed;
@@ -13290,7 +13636,20 @@ ${prompt}` });
13290
13636
  await writeSSE(JSON.stringify({ type: "abort" }));
13291
13637
  } else {
13292
13638
  console.error("Agent error:", error);
13293
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
13639
+ let debugPayload = void 0;
13640
+ if (isMissingToolResultsError(error)) {
13641
+ try {
13642
+ const recovery = await recoverFromMissingToolResults(sessionId, error);
13643
+ if (recovery) debugPayload = recovery;
13644
+ } catch (recErr) {
13645
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
13646
+ }
13647
+ }
13648
+ await writeSSE(JSON.stringify({
13649
+ type: "error",
13650
+ errorText: error.message,
13651
+ ...debugPayload ? { debug: debugPayload } : {}
13652
+ }));
13294
13653
  try {
13295
13654
  await activeStreamQueries.markError(streamId);
13296
13655
  } catch {
@@ -13680,7 +14039,7 @@ agents.post(
13680
14039
  chunkIndex,
13681
14040
  chunkCount
13682
14041
  }));
13683
- await new Promise((resolve13) => setTimeout(resolve13, 0));
14042
+ await new Promise((resolve14) => setTimeout(resolve14, 0));
13684
14043
  }
13685
14044
  const browserPort = progress.data?.browserStreamPort;
13686
14045
  const browserClosed = progress.data?.browserClosed;
@@ -13830,7 +14189,20 @@ agents.post(
13830
14189
  await writeSSE(JSON.stringify({ type: "abort" }));
13831
14190
  } else {
13832
14191
  console.error("Agent error:", error);
13833
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
14192
+ let debugPayload = void 0;
14193
+ if (isMissingToolResultsError(error)) {
14194
+ try {
14195
+ const recovery = await recoverFromMissingToolResults(session.id, error);
14196
+ if (recovery) debugPayload = recovery;
14197
+ } catch (recErr) {
14198
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
14199
+ }
14200
+ }
14201
+ await writeSSE(JSON.stringify({
14202
+ type: "error",
14203
+ errorText: error.message,
14204
+ ...debugPayload ? { debug: debugPayload } : {}
14205
+ }));
13834
14206
  await activeStreamQueries.markError(streamId);
13835
14207
  }
13836
14208
  } finally {
@@ -13913,26 +14285,26 @@ init_config();
13913
14285
  import { Hono as Hono3 } from "hono";
13914
14286
  import { zValidator as zValidator3 } from "@hono/zod-validator";
13915
14287
  import { z as z18 } from "zod";
13916
- import { readFileSync as readFileSync9 } from "fs";
14288
+ import { readFileSync as readFileSync10 } from "fs";
13917
14289
  import { fileURLToPath as fileURLToPath3 } from "url";
13918
- import { dirname as dirname7, join as join14 } from "path";
14290
+ import { dirname as dirname8, join as join15 } from "path";
13919
14291
  var __filename = fileURLToPath3(import.meta.url);
13920
- var __dirname = dirname7(__filename);
14292
+ var __dirname = dirname8(__filename);
13921
14293
  var possiblePaths = [
13922
- join14(__dirname, "../package.json"),
14294
+ join15(__dirname, "../package.json"),
13923
14295
  // From dist/server -> dist/../package.json
13924
- join14(__dirname, "../../package.json"),
14296
+ join15(__dirname, "../../package.json"),
13925
14297
  // From dist/server (if nested differently)
13926
- join14(__dirname, "../../../package.json"),
14298
+ join15(__dirname, "../../../package.json"),
13927
14299
  // From src/server/routes (development)
13928
- join14(process.cwd(), "package.json")
14300
+ join15(process.cwd(), "package.json")
13929
14301
  // From current working directory
13930
14302
  ];
13931
14303
  var currentVersion = "0.0.0";
13932
14304
  var packageName = "sparkecoder";
13933
14305
  for (const packageJsonPath of possiblePaths) {
13934
14306
  try {
13935
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
14307
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
13936
14308
  if (packageJson.name === "sparkecoder") {
13937
14309
  currentVersion = packageJson.version || "0.0.0";
13938
14310
  packageName = packageJson.name || "sparkecoder";
@@ -14750,12 +15122,13 @@ slack.post("/events", async (c) => {
14750
15122
  if (inbound) {
14751
15123
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
14752
15124
  if (isThreadReply) {
14753
- const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
15125
+ const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
14754
15126
  if (!ours) {
14755
15127
  console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
14756
15128
  updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
14757
15129
  return c.json({ ok: true });
14758
15130
  }
15131
+ markThreadOwned(ev.channel, ev.thread_ts);
14759
15132
  }
14760
15133
  if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
14761
15134
  markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
@@ -14794,18 +15167,6 @@ slack.post("/events", async (c) => {
14794
15167
  }
14795
15168
  return c.json({ ok: true });
14796
15169
  });
14797
- async function threadBelongsToUs(channel, threadTs) {
14798
- try {
14799
- const sessions3 = await sessionQueries.list(500, 0);
14800
- return sessions3.some((s) => {
14801
- const slack2 = s.config?.slack;
14802
- return slack2?.channel === channel && slack2?.threadTs === threadTs;
14803
- });
14804
- } catch (err) {
14805
- console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
14806
- return false;
14807
- }
14808
- }
14809
15170
  async function findOrCreateOrchestratorId() {
14810
15171
  try {
14811
15172
  const all = await sessionQueries.list(500, 0);
@@ -15188,6 +15549,224 @@ mcpRouter.post("/:id/test", async (c) => {
15188
15549
  return c.json(result);
15189
15550
  });
15190
15551
 
15552
+ // src/server/routes/skills.ts
15553
+ init_config();
15554
+ init_skills();
15555
+ import { Hono as Hono9 } from "hono";
15556
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
15557
+ import { z as z22 } from "zod";
15558
+ import { existsSync as existsSync21, statSync as statSync3 } from "fs";
15559
+ import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
15560
+ import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
15561
+ var skills = new Hono9();
15562
+ function encodeId(filePath) {
15563
+ return Buffer.from(filePath, "utf-8").toString("base64url");
15564
+ }
15565
+ function decodeId(id) {
15566
+ try {
15567
+ const decoded = Buffer.from(id, "base64url").toString("utf-8");
15568
+ return decoded || null;
15569
+ } catch {
15570
+ return null;
15571
+ }
15572
+ }
15573
+ function listAllDirectories() {
15574
+ const cfg = getConfig();
15575
+ const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
15576
+ const out = [];
15577
+ for (const { path, priority } of discovered.alwaysLoadedDirs) {
15578
+ out.push({
15579
+ path,
15580
+ source: pathToSource(path),
15581
+ label: pathToLabel(path),
15582
+ priority,
15583
+ alwaysApply: true
15584
+ });
15585
+ }
15586
+ for (const { path, priority } of discovered.onDemandDirs) {
15587
+ out.push({
15588
+ path,
15589
+ source: pathToSource(path),
15590
+ label: pathToLabel(path),
15591
+ priority,
15592
+ alwaysApply: false
15593
+ });
15594
+ }
15595
+ const additional = cfg.skills?.additionalDirectories || [];
15596
+ for (const dir of additional) {
15597
+ const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
15598
+ if (out.some((d) => d.path === abs)) continue;
15599
+ out.push({
15600
+ path: abs,
15601
+ source: "additional",
15602
+ label: pathToLabel(abs),
15603
+ priority: 50,
15604
+ alwaysApply: false
15605
+ });
15606
+ }
15607
+ return out;
15608
+ }
15609
+ function pathToSource(path) {
15610
+ if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
15611
+ return "project";
15612
+ }
15613
+ function pathToLabel(path) {
15614
+ if (path.includes("/skills/default")) return "Built-in (default)";
15615
+ if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
15616
+ if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
15617
+ if (path.includes("/.cursor/rules")) return ".cursor/rules";
15618
+ if (path.includes("/.claude/skills")) return ".claude/skills";
15619
+ return basename6(dirname9(path)) + "/" + basename6(path);
15620
+ }
15621
+ skills.get("/", async (c) => {
15622
+ const dirs = listAllDirectories();
15623
+ const allSkills = [];
15624
+ for (const dir of dirs) {
15625
+ if (!existsSync21(dir.path)) continue;
15626
+ try {
15627
+ const list = await loadSkillsFromDirectory(dir.path, {
15628
+ priority: dir.priority,
15629
+ defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
15630
+ forceAlwaysApply: dir.alwaysApply
15631
+ });
15632
+ for (const s of list) {
15633
+ let size = 0;
15634
+ try {
15635
+ size = statSync3(s.filePath).size;
15636
+ } catch {
15637
+ }
15638
+ allSkills.push({
15639
+ id: encodeId(s.filePath),
15640
+ name: s.name,
15641
+ description: s.description,
15642
+ filePath: s.filePath,
15643
+ fileName: basename6(s.filePath),
15644
+ sourceDir: dir.path,
15645
+ sourceLabel: dir.label,
15646
+ sourceType: dir.source,
15647
+ alwaysApply: s.alwaysApply,
15648
+ globs: s.globs,
15649
+ sizeBytes: size
15650
+ });
15651
+ }
15652
+ } catch (err) {
15653
+ console.warn("[skills] failed to read", dir.path, err?.message || err);
15654
+ }
15655
+ }
15656
+ return c.json({
15657
+ directories: dirs.map((d) => ({
15658
+ path: d.path,
15659
+ label: d.label,
15660
+ source: d.source,
15661
+ alwaysApply: d.alwaysApply,
15662
+ exists: existsSync21(d.path),
15663
+ writable: isWritable(d.path)
15664
+ })),
15665
+ skills: allSkills
15666
+ });
15667
+ });
15668
+ function isWritable(dir) {
15669
+ try {
15670
+ if (!existsSync21(dir)) return false;
15671
+ if (dir.includes("/skills/default")) return false;
15672
+ return true;
15673
+ } catch {
15674
+ return false;
15675
+ }
15676
+ }
15677
+ skills.get("/:id", async (c) => {
15678
+ const filePath = decodeId(c.req.param("id"));
15679
+ if (!filePath || !existsSync21(filePath)) {
15680
+ return c.json({ error: "skill not found" }, 404);
15681
+ }
15682
+ const content = await readFile12(filePath, "utf-8");
15683
+ return c.json({
15684
+ id: c.req.param("id"),
15685
+ filePath,
15686
+ fileName: basename6(filePath),
15687
+ content,
15688
+ writable: !filePath.includes("/skills/default")
15689
+ });
15690
+ });
15691
+ skills.post(
15692
+ "/",
15693
+ zValidator7("json", z22.object({
15694
+ directory: z22.string().min(1),
15695
+ fileName: z22.string().min(1),
15696
+ content: z22.string()
15697
+ })),
15698
+ async (c) => {
15699
+ const { directory, fileName, content } = c.req.valid("json");
15700
+ const cfg = getConfig();
15701
+ const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
15702
+ if (targetDir.includes("/skills/default")) {
15703
+ return c.json({ error: "cannot write into built-in skills directory" }, 400);
15704
+ }
15705
+ const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
15706
+ const ext = extname9(safeName).toLowerCase();
15707
+ const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
15708
+ const filePath = join16(targetDir, finalName);
15709
+ if (existsSync21(filePath)) {
15710
+ return c.json({ error: `file already exists: ${finalName}` }, 409);
15711
+ }
15712
+ try {
15713
+ await mkdir5(targetDir, { recursive: true });
15714
+ await writeFile6(filePath, content, "utf-8");
15715
+ } catch (err) {
15716
+ return c.json({ error: err?.message || "write failed" }, 500);
15717
+ }
15718
+ return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
15719
+ }
15720
+ );
15721
+ skills.put(
15722
+ "/:id",
15723
+ zValidator7("json", z22.object({ content: z22.string() })),
15724
+ async (c) => {
15725
+ const filePath = decodeId(c.req.param("id"));
15726
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
15727
+ if (filePath.includes("/skills/default")) {
15728
+ return c.json({ error: "built-in skills are read-only" }, 400);
15729
+ }
15730
+ await writeFile6(filePath, c.req.valid("json").content, "utf-8");
15731
+ return c.json({ ok: true });
15732
+ }
15733
+ );
15734
+ skills.delete("/:id", async (c) => {
15735
+ const filePath = decodeId(c.req.param("id"));
15736
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
15737
+ if (filePath.includes("/skills/default")) {
15738
+ return c.json({ error: "built-in skills are read-only" }, 400);
15739
+ }
15740
+ await unlink3(filePath);
15741
+ return c.json({ ok: true });
15742
+ });
15743
+ skills.post(
15744
+ "/directories",
15745
+ zValidator7("json", z22.object({ path: z22.string().min(1) })),
15746
+ (c) => {
15747
+ const cfg = getConfig();
15748
+ const inputPath = c.req.valid("json").path.trim();
15749
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
15750
+ const current = cfg.skills?.additionalDirectories || [];
15751
+ if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
15752
+ return c.json({ error: "directory already added" }, 409);
15753
+ }
15754
+ const next = [...current, abs];
15755
+ setSkillsAdditionalDirectories(next);
15756
+ return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
15757
+ }
15758
+ );
15759
+ skills.delete("/directories", (c) => {
15760
+ const inputPath = c.req.query("path");
15761
+ if (!inputPath) return c.json({ error: "path query param required" }, 400);
15762
+ const cfg = getConfig();
15763
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
15764
+ const current = cfg.skills?.additionalDirectories || [];
15765
+ const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
15766
+ setSkillsAdditionalDirectories(next);
15767
+ return c.json({ ok: true });
15768
+ });
15769
+
15191
15770
  // src/server/auth/cf-access.ts
15192
15771
  init_config();
15193
15772
  import { createRemoteJWKSet, jwtVerify } from "jose";
@@ -15444,13 +16023,13 @@ var DEFAULT_WEB_PORT = 6969;
15444
16023
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
15445
16024
  function getWebDirectory() {
15446
16025
  try {
15447
- const currentDir = dirname8(fileURLToPath4(import.meta.url));
15448
- const webDir = resolve11(currentDir, "..", "web");
15449
- if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
16026
+ const currentDir = dirname10(fileURLToPath4(import.meta.url));
16027
+ const webDir = resolve12(currentDir, "..", "web");
16028
+ if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
15450
16029
  return webDir;
15451
16030
  }
15452
- const altWebDir = resolve11(currentDir, "..", "..", "web");
15453
- if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
16031
+ const altWebDir = resolve12(currentDir, "..", "..", "web");
16032
+ if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
15454
16033
  return altWebDir;
15455
16034
  }
15456
16035
  return null;
@@ -15473,18 +16052,18 @@ async function isSparkcoderWebRunning(port) {
15473
16052
  }
15474
16053
  }
15475
16054
  function isPortInUse(port) {
15476
- return new Promise((resolve13) => {
16055
+ return new Promise((resolve14) => {
15477
16056
  const server = createNetServer();
15478
16057
  server.once("error", (err) => {
15479
16058
  if (err.code === "EADDRINUSE") {
15480
- resolve13(true);
16059
+ resolve14(true);
15481
16060
  } else {
15482
- resolve13(false);
16061
+ resolve14(false);
15483
16062
  }
15484
16063
  });
15485
16064
  server.once("listening", () => {
15486
16065
  server.close();
15487
- resolve13(false);
16066
+ resolve14(false);
15488
16067
  });
15489
16068
  server.listen(port, "0.0.0.0");
15490
16069
  });
@@ -15508,30 +16087,30 @@ async function findWebPort(preferredPort) {
15508
16087
  return { port: preferredPort, alreadyRunning: false };
15509
16088
  }
15510
16089
  function hasProductionBuild(webDir) {
15511
- const buildIdPath = join15(webDir, ".next", "BUILD_ID");
15512
- return existsSync20(buildIdPath);
16090
+ const buildIdPath = join17(webDir, ".next", "BUILD_ID");
16091
+ return existsSync22(buildIdPath);
15513
16092
  }
15514
16093
  function hasSourceFiles(webDir) {
15515
- const appDir = join15(webDir, "src", "app");
15516
- const pagesDir = join15(webDir, "src", "pages");
15517
- const rootAppDir = join15(webDir, "app");
15518
- const rootPagesDir = join15(webDir, "pages");
15519
- return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
16094
+ const appDir = join17(webDir, "src", "app");
16095
+ const pagesDir = join17(webDir, "src", "pages");
16096
+ const rootAppDir = join17(webDir, "app");
16097
+ const rootPagesDir = join17(webDir, "pages");
16098
+ return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
15520
16099
  }
15521
16100
  function getStandaloneServerPath(webDir) {
15522
16101
  const possiblePaths2 = [
15523
- join15(webDir, ".next", "standalone", "server.js"),
15524
- join15(webDir, ".next", "standalone", "web", "server.js")
16102
+ join17(webDir, ".next", "standalone", "server.js"),
16103
+ join17(webDir, ".next", "standalone", "web", "server.js")
15525
16104
  ];
15526
16105
  for (const serverPath of possiblePaths2) {
15527
- if (existsSync20(serverPath)) {
16106
+ if (existsSync22(serverPath)) {
15528
16107
  return serverPath;
15529
16108
  }
15530
16109
  }
15531
16110
  return null;
15532
16111
  }
15533
16112
  function runCommand(command, args, cwd, env) {
15534
- return new Promise((resolve13) => {
16113
+ return new Promise((resolve14) => {
15535
16114
  const child = spawn2(command, args, {
15536
16115
  cwd,
15537
16116
  stdio: ["ignore", "pipe", "pipe"],
@@ -15546,10 +16125,10 @@ function runCommand(command, args, cwd, env) {
15546
16125
  output += data.toString();
15547
16126
  });
15548
16127
  child.on("close", (code) => {
15549
- resolve13({ success: code === 0, output });
16128
+ resolve14({ success: code === 0, output });
15550
16129
  });
15551
16130
  child.on("error", (err) => {
15552
- resolve13({ success: false, output: err.message });
16131
+ resolve14({ success: false, output: err.message });
15553
16132
  });
15554
16133
  });
15555
16134
  }
@@ -15564,15 +16143,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15564
16143
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
15565
16144
  return { process: null, port: actualPort };
15566
16145
  }
15567
- const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
15568
- const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
16146
+ const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
16147
+ const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
15569
16148
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
15570
16149
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
15571
16150
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
15572
16151
  const runtimeConfig = { apiBaseUrl: apiUrl };
15573
- const runtimeConfigPath = join15(webDir, "runtime-config.json");
16152
+ const runtimeConfigPath = join17(webDir, "runtime-config.json");
15574
16153
  try {
15575
- writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
16154
+ writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
15576
16155
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
15577
16156
  } catch (err) {
15578
16157
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -15592,7 +16171,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15592
16171
  if (standaloneServerPath) {
15593
16172
  command = "node";
15594
16173
  args = ["server.js"];
15595
- cwd = dirname8(standaloneServerPath);
16174
+ cwd = dirname10(standaloneServerPath);
15596
16175
  webEnv.PORT = String(actualPort);
15597
16176
  webEnv.HOSTNAME = "0.0.0.0";
15598
16177
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -15633,10 +16212,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15633
16212
  let started = false;
15634
16213
  let exited = false;
15635
16214
  let exitCode = null;
15636
- const startedPromise = new Promise((resolve13) => {
16215
+ const startedPromise = new Promise((resolve14) => {
15637
16216
  const timeout = setTimeout(() => {
15638
16217
  if (!started && !exited) {
15639
- resolve13(false);
16218
+ resolve14(false);
15640
16219
  }
15641
16220
  }, startupTimeout);
15642
16221
  child.stdout?.on("data", (data) => {
@@ -15650,7 +16229,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15650
16229
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
15651
16230
  started = true;
15652
16231
  clearTimeout(timeout);
15653
- resolve13(true);
16232
+ resolve14(true);
15654
16233
  }
15655
16234
  });
15656
16235
  child.stderr?.on("data", (data) => {
@@ -15662,14 +16241,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15662
16241
  child.on("error", (err) => {
15663
16242
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
15664
16243
  clearTimeout(timeout);
15665
- resolve13(false);
16244
+ resolve14(false);
15666
16245
  });
15667
16246
  child.on("exit", (code) => {
15668
16247
  exited = true;
15669
16248
  exitCode = code;
15670
16249
  if (!started) {
15671
16250
  clearTimeout(timeout);
15672
- resolve13(false);
16251
+ resolve14(false);
15673
16252
  }
15674
16253
  webUIProcess = null;
15675
16254
  });
@@ -15692,7 +16271,7 @@ function stopWebUI() {
15692
16271
  }
15693
16272
  }
15694
16273
  async function createApp(options = {}) {
15695
- const app = new Hono9();
16274
+ const app = new Hono10();
15696
16275
  app.use("*", cors({
15697
16276
  origin: "*",
15698
16277
  // Allow all origins
@@ -15720,6 +16299,7 @@ async function createApp(options = {}) {
15720
16299
  app.route("/api/schedules", schedulesRouter);
15721
16300
  app.route("/api/orchestrator", orchestratorRouter);
15722
16301
  app.route("/api/mcp", mcpRouter);
16302
+ app.route("/api/skills", skills);
15723
16303
  app.route("/api/webhooks", webhooksRouter);
15724
16304
  const config = getConfig();
15725
16305
  const webhookToken = config?.webhooks?.token;
@@ -15785,8 +16365,8 @@ async function startServer(options = {}) {
15785
16365
  if (options.workingDirectory) {
15786
16366
  config.resolvedWorkingDirectory = options.workingDirectory;
15787
16367
  }
15788
- if (!existsSync20(config.resolvedWorkingDirectory)) {
15789
- mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
16368
+ if (!existsSync22(config.resolvedWorkingDirectory)) {
16369
+ mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
15790
16370
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
15791
16371
  }
15792
16372
  if (!config.resolvedRemoteServer.url) {
@@ -16344,18 +16924,18 @@ function generateOpenAPISpec() {
16344
16924
  init_config();
16345
16925
  init_semantic();
16346
16926
  init_db();
16347
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7, readFileSync as readFileSync10, existsSync as existsSync21, statSync as statSync3 } from "fs";
16348
- import { resolve as resolve12, join as join16 } from "path";
16927
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, readFileSync as readFileSync11, existsSync as existsSync23, statSync as statSync4 } from "fs";
16928
+ import { resolve as resolve13, join as join18 } from "path";
16349
16929
  function getCliVersion() {
16350
- const here = dirname9(fileURLToPath5(import.meta.url));
16930
+ const here = dirname11(fileURLToPath5(import.meta.url));
16351
16931
  const candidates = [
16352
- join16(here, "..", "package.json"),
16353
- join16(here, "..", "..", "package.json"),
16354
- join16(process.cwd(), "package.json")
16932
+ join18(here, "..", "package.json"),
16933
+ join18(here, "..", "..", "package.json"),
16934
+ join18(process.cwd(), "package.json")
16355
16935
  ];
16356
16936
  for (const p of candidates) {
16357
16937
  try {
16358
- const pkg = JSON.parse(readFileSync10(p, "utf8"));
16938
+ const pkg = JSON.parse(readFileSync11(p, "utf8"));
16359
16939
  if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
16360
16940
  } catch {
16361
16941
  }
@@ -16396,18 +16976,18 @@ async function getActiveStream(baseUrl, sessionId) {
16396
16976
  return { hasActiveStream: false };
16397
16977
  }
16398
16978
  function promptApproval(rl, toolName, input) {
16399
- return new Promise((resolve13) => {
16979
+ return new Promise((resolve14) => {
16400
16980
  const inputStr = JSON.stringify(input);
16401
16981
  const truncatedInput = inputStr.length > 100 ? inputStr.slice(0, 100) + "..." : inputStr;
16402
16982
  console.log(chalk.dim(` Command: ${truncatedInput}`));
16403
16983
  rl.question(chalk.yellow(` Approve? [y/n/a(lways)]: `), (answer) => {
16404
16984
  const lower = answer.toLowerCase().trim();
16405
16985
  if (lower === "a" || lower === "always") {
16406
- resolve13("always");
16986
+ resolve14("always");
16407
16987
  } else if (lower.startsWith("y")) {
16408
- resolve13("approve");
16988
+ resolve14("approve");
16409
16989
  } else {
16410
- resolve13("reject");
16990
+ resolve14("reject");
16411
16991
  }
16412
16992
  });
16413
16993
  });
@@ -16604,9 +17184,9 @@ async function runChat(options) {
16604
17184
  input: process.stdin,
16605
17185
  output: process.stdout
16606
17186
  });
16607
- const apiKey = await new Promise((resolve13) => {
17187
+ const apiKey = await new Promise((resolve14) => {
16608
17188
  keyRl.question(chalk.cyan("Enter your AI Gateway API key: "), (answer) => {
16609
- resolve13(answer.trim());
17189
+ resolve14(answer.trim());
16610
17190
  });
16611
17191
  });
16612
17192
  keyRl.close();
@@ -17002,8 +17582,8 @@ program.command("task").description("Run an autonomous task that completes witho
17002
17582
  let outputSchema;
17003
17583
  try {
17004
17584
  const schemaStr = options.schema;
17005
- if (existsSync21(schemaStr)) {
17006
- outputSchema = JSON.parse(readFileSync10(schemaStr, "utf-8"));
17585
+ if (existsSync23(schemaStr)) {
17586
+ outputSchema = JSON.parse(readFileSync11(schemaStr, "utf-8"));
17007
17587
  } else {
17008
17588
  outputSchema = JSON.parse(schemaStr);
17009
17589
  }
@@ -17070,28 +17650,28 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
17070
17650
  let configLocation;
17071
17651
  if (options.global) {
17072
17652
  const appDataDir = ensureAppDataDirectory();
17073
- configPath = join16(appDataDir, "sparkecoder.config.json");
17653
+ configPath = join18(appDataDir, "sparkecoder.config.json");
17074
17654
  configLocation = "global";
17075
17655
  } else {
17076
- configPath = resolve12(process.cwd(), "sparkecoder.config.json");
17656
+ configPath = resolve13(process.cwd(), "sparkecoder.config.json");
17077
17657
  configLocation = "local";
17078
17658
  }
17079
- if (existsSync21(configPath) && !options.force) {
17659
+ if (existsSync23(configPath) && !options.force) {
17080
17660
  console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
17081
17661
  console.log(chalk.dim(` ${configPath}`));
17082
17662
  return;
17083
17663
  }
17084
17664
  const config = createDefaultConfig();
17085
- writeFileSync7(configPath, JSON.stringify(config, null, 2));
17665
+ writeFileSync8(configPath, JSON.stringify(config, null, 2));
17086
17666
  console.log(chalk.green(`\u2713 Created ${configLocation} config`));
17087
17667
  console.log(chalk.dim(` ${configPath}`));
17088
17668
  console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
17089
17669
  });
17090
17670
  program.command("slack-setup").description("Interactively configure Slack integration (bot token + signing secret)").option("--bot-token <token>", "Slack bot token (xoxb-...)").option("--signing-secret <secret>", "Slack app signing secret").option("-g, --global", "Write to global config (~/.sparkecoder/sparkecoder.config.json)").action(async (options) => {
17091
17671
  const rl = createInterface({ input: process.stdin, output: process.stdout });
17092
- const ask = (label, fallback = "") => new Promise((resolve13) => {
17672
+ const ask = (label, fallback = "") => new Promise((resolve14) => {
17093
17673
  const suffix = fallback ? ` (${fallback})` : "";
17094
- rl.question(`${label}${suffix}: `, (answer) => resolve13(answer.trim() || fallback));
17674
+ rl.question(`${label}${suffix}: `, (answer) => resolve14(answer.trim() || fallback));
17095
17675
  });
17096
17676
  try {
17097
17677
  const botToken = options.botToken || await ask("Slack bot token (xoxb-...)");
@@ -17101,11 +17681,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
17101
17681
  console.error(chalk.red("Both bot token and signing secret are required."));
17102
17682
  process.exit(1);
17103
17683
  }
17104
- const configPath = options.global ? join16(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve12(process.cwd(), "sparkecoder.config.json");
17684
+ const configPath = options.global ? join18(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve13(process.cwd(), "sparkecoder.config.json");
17105
17685
  let existing = {};
17106
- if (existsSync21(configPath)) {
17686
+ if (existsSync23(configPath)) {
17107
17687
  try {
17108
- existing = JSON.parse(readFileSync10(configPath, "utf-8"));
17688
+ existing = JSON.parse(readFileSync11(configPath, "utf-8"));
17109
17689
  } catch {
17110
17690
  }
17111
17691
  } else {
@@ -17117,7 +17697,7 @@ program.command("slack-setup").description("Interactively configure Slack integr
17117
17697
  signingSecret,
17118
17698
  defaultOrchestratorName: existing.slack?.defaultOrchestratorName ?? "orchestrator"
17119
17699
  };
17120
- writeFileSync7(configPath, JSON.stringify(existing, null, 2));
17700
+ writeFileSync8(configPath, JSON.stringify(existing, null, 2));
17121
17701
  console.log(chalk.green(`
17122
17702
  \u2713 Slack configured`));
17123
17703
  console.log(chalk.dim(` ${configPath}`));
@@ -17369,9 +17949,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17369
17949
  }
17370
17950
  const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
17371
17951
  console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
17372
- const cfDir = join16(homedir2(), ".cloudflared");
17373
- const certPath = join16(cfDir, "cert.pem");
17374
- if (!existsSync21(certPath)) {
17952
+ const cfDir = join18(homedir2(), ".cloudflared");
17953
+ const certPath = join18(cfDir, "cert.pem");
17954
+ if (!existsSync23(certPath)) {
17375
17955
  console.log(chalk.yellow("No Cloudflare login cert found."));
17376
17956
  if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
17377
17957
  run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
@@ -17415,8 +17995,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17415
17995
  return;
17416
17996
  }
17417
17997
  }
17418
- const credsFile = tunnel.credentials_file || join16(cfDir, `${tunnel.id}.json`);
17419
- if (!existsSync21(credsFile)) {
17998
+ const credsFile = tunnel.credentials_file || join18(cfDir, `${tunnel.id}.json`);
17999
+ if (!existsSync23(credsFile)) {
17420
18000
  console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
17421
18001
  }
17422
18002
  let hostname = options.hostname;
@@ -17439,7 +18019,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17439
18019
  console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
17440
18020
  }
17441
18021
  }
17442
- const configPath = join16(cfDir, "config.yml");
18022
+ const configPath = join18(cfDir, "config.yml");
17443
18023
  const configBody = `tunnel: ${tunnel.id}
17444
18024
  credentials-file: ${credsFile}
17445
18025
  ingress:
@@ -17450,14 +18030,14 @@ ingress:
17450
18030
  - service: http_status:404
17451
18031
  `;
17452
18032
  let wroteConfig = false;
17453
- if (existsSync21(configPath)) {
17454
- const existing = readFileSync10(configPath, "utf8");
18033
+ if (existsSync23(configPath)) {
18034
+ const existing = readFileSync11(configPath, "utf8");
17455
18035
  if (existing.trim() === configBody.trim()) {
17456
18036
  console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
17457
18037
  wroteConfig = true;
17458
18038
  } else if (await confirm(`A different ${configPath} exists. Overwrite (a backup will be saved)?`, false)) {
17459
18039
  copyFileSync(configPath, `${configPath}.bak.${Date.now()}`);
17460
- writeFileSync7(configPath, configBody);
18040
+ writeFileSync8(configPath, configBody);
17461
18041
  console.log(chalk.green("\u2713"), `wrote ${configPath} (previous saved as .bak.*)`);
17462
18042
  wroteConfig = true;
17463
18043
  } else {
@@ -17465,7 +18045,7 @@ ingress:
17465
18045
  console.log(chalk.cyan(configBody));
17466
18046
  }
17467
18047
  } else {
17468
- writeFileSync7(configPath, configBody);
18048
+ writeFileSync8(configPath, configBody);
17469
18049
  console.log(chalk.green("\u2713"), `wrote ${configPath}`);
17470
18050
  wroteConfig = true;
17471
18051
  }
@@ -17566,7 +18146,7 @@ program.command("config").description("Show current configuration").option("-c,
17566
18146
  });
17567
18147
  program.command("index").description("Index repository for semantic search").option("--status", "Show index status instead of indexing").option("--full", "Force full re-index (ignore existing embeddings)").option("-w, --working-dir <path>", "Working directory (defaults to current directory)").option("-c, --config <path>", "Path to config file").option("-v, --verbose", "Show detailed progress").action(async (options) => {
17568
18148
  try {
17569
- const workingDir = options.workingDir ? resolve12(options.workingDir) : process.cwd();
18149
+ const workingDir = options.workingDir ? resolve13(options.workingDir) : process.cwd();
17570
18150
  let config = loadConfig(options.config, workingDir);
17571
18151
  const remoteUrl = config.resolvedRemoteServer.url;
17572
18152
  if (!remoteUrl) {
@@ -17700,7 +18280,7 @@ Indexing ${chalk.cyan(namespace)}...
17700
18280
  });
17701
18281
  program.command("search").description("Search indexed repository using semantic search").argument("<query>", "Search query").option("-n, --num <count>", "Number of results to show", "5").option("-w, --working-dir <path>", "Working directory (defaults to current directory)").option("-c, --config <path>", "Path to config file").action(async (query, options) => {
17702
18282
  try {
17703
- const workingDir = options.workingDir ? resolve12(options.workingDir) : process.cwd();
18283
+ const workingDir = options.workingDir ? resolve13(options.workingDir) : process.cwd();
17704
18284
  const config = loadConfig(options.config, workingDir);
17705
18285
  const remoteUrl = config.resolvedRemoteServer.url;
17706
18286
  if (!remoteUrl) {
@@ -17955,17 +18535,17 @@ program.command("request-permissions").description("Open System Settings to the
17955
18535
  });
17956
18536
  {
17957
18537
  let stateFilePath = function() {
17958
- return join16(ensureAppDataDirectory(), "recordings.json");
18538
+ return join18(ensureAppDataDirectory(), "recordings.json");
17959
18539
  }, readState = function() {
17960
18540
  const p = stateFilePath();
17961
- if (!existsSync21(p)) return [];
18541
+ if (!existsSync23(p)) return [];
17962
18542
  try {
17963
- return JSON.parse(readFileSync10(p, "utf-8"));
18543
+ return JSON.parse(readFileSync11(p, "utf-8"));
17964
18544
  } catch {
17965
18545
  return [];
17966
18546
  }
17967
18547
  }, writeState = function(rows) {
17968
- writeFileSync7(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
18548
+ writeFileSync8(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
17969
18549
  }, isAlive = function(pid) {
17970
18550
  try {
17971
18551
  process.kill(pid, 0);
@@ -17980,16 +18560,16 @@ program.command("request-permissions").description("Open System Settings to the
17980
18560
  const record = program.command("record").description("Start/stop screen recordings");
17981
18561
  record.command("start").description("Start a screen recording (returns id, path, pid as JSON)").option("--name <slug>", "Optional human-readable label for the recording").option("--dir <path>", "Output directory (default: ~/recordings)").action(async (opts) => {
17982
18562
  const { homedir: homedir2, platform: osPlatform } = await import("os");
17983
- const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) : join16(homedir2(), "recordings");
17984
- mkdirSync10(outDir, { recursive: true });
18563
+ const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join18(homedir2(), "recordings");
18564
+ mkdirSync11(outDir, { recursive: true });
17985
18565
  const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
17986
18566
  const ext = osPlatform() === "darwin" ? "mov" : "mp4";
17987
18567
  const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
17988
- const path = join16(outDir, filename);
18568
+ const path = join18(outDir, filename);
17989
18569
  let cmd;
17990
18570
  let args;
17991
18571
  if (osPlatform() === "darwin") {
17992
- cmd = existsSync21("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
18572
+ cmd = existsSync23("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
17993
18573
  args = ["-v", "-C", "-k", path];
17994
18574
  } else if (osPlatform() === "linux") {
17995
18575
  const display = process.env.DISPLAY || ":0.0";
@@ -18092,8 +18672,8 @@ program.command("request-permissions").description("Open System Settings to the
18092
18672
  }
18093
18673
  }
18094
18674
  writeState(rows.filter((r) => r.id !== id));
18095
- const fileExists = existsSync21(row.path);
18096
- const sizeMb = fileExists ? Math.round(statSync3(row.path).size / (1024 * 1024) * 10) / 10 : 0;
18675
+ const fileExists = existsSync23(row.path);
18676
+ const sizeMb = fileExists ? Math.round(statSync4(row.path).size / (1024 * 1024) * 10) / 10 : 0;
18097
18677
  console.log(JSON.stringify({
18098
18678
  id,
18099
18679
  path: row.path,
@@ -18127,7 +18707,7 @@ program.command("request-permissions").description("Open System Settings to the
18127
18707
  }
18128
18708
  }
18129
18709
  }
18130
- stopped.push({ id: r.id, path: r.path, ok: existsSync21(r.path) });
18710
+ stopped.push({ id: r.id, path: r.path, ok: existsSync23(r.path) });
18131
18711
  }
18132
18712
  writeState([]);
18133
18713
  console.log(JSON.stringify({ stopped }));