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/index.js CHANGED
@@ -450,6 +450,7 @@ __export(config_exports, {
450
450
  setApiKey: () => setApiKey,
451
451
  setMcpServers: () => setMcpServers,
452
452
  setPublicUrl: () => setPublicUrl,
453
+ setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
453
454
  setSlackConfig: () => setSlackConfig,
454
455
  setWebhookToken: () => setWebhookToken
455
456
  });
@@ -739,6 +740,40 @@ function setWebhookToken(token) {
739
740
  console.warn("[config] failed to persist webhook token:", err?.message || err);
740
741
  }
741
742
  }
743
+ function setSkillsAdditionalDirectories(directories) {
744
+ if (cachedConfig) {
745
+ const cur = cachedConfig.skills || {};
746
+ cachedConfig.skills = { ...cur, additionalDirectories: directories };
747
+ try {
748
+ const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
749
+ const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
750
+ cachedConfig.discoveredSkills = discovered;
751
+ cachedConfig.resolvedSkillsDirectories = [
752
+ ...discovered.allDirectories,
753
+ ...additionalAbs
754
+ ];
755
+ } catch {
756
+ }
757
+ }
758
+ try {
759
+ const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
760
+ const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
761
+ let raw = {};
762
+ if (existsSync(target)) {
763
+ try {
764
+ raw = JSON.parse(readFileSync(target, "utf-8"));
765
+ } catch {
766
+ raw = {};
767
+ }
768
+ } else {
769
+ raw = createDefaultConfig();
770
+ }
771
+ raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
772
+ writeFileSync(target, JSON.stringify(raw, null, 2));
773
+ } catch (err) {
774
+ console.warn("[config] failed to persist skill directories:", err?.message || err);
775
+ }
776
+ }
742
777
  function setPublicUrl(publicUrl) {
743
778
  if (cachedConfig) {
744
779
  cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
@@ -2311,10 +2346,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2311
2346
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
2312
2347
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
2313
2348
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
2314
- const cachePath = join3(cacheDir, key2 + ext);
2315
- if (existsSync3(cachePath)) {
2349
+ const cachePath2 = join3(cacheDir, key2 + ext);
2350
+ if (existsSync3(cachePath2)) {
2316
2351
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
2317
- return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
2352
+ return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
2318
2353
  }
2319
2354
  let pipeline = sharp(buffer);
2320
2355
  if (needsResize) {
@@ -2339,7 +2374,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2339
2374
  }
2340
2375
  finalMediaType = "image/jpeg";
2341
2376
  }
2342
- writeFileSync2(cachePath, result);
2377
+ writeFileSync2(cachePath2, result);
2343
2378
  const resultMeta = await sharp(result).metadata();
2344
2379
  console.log(
2345
2380
  `[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
@@ -3030,7 +3065,7 @@ async function createClient(serverId, handle, root) {
3030
3065
  },
3031
3066
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
3032
3067
  const normalized = normalizePath(filePath);
3033
- return new Promise((resolve12) => {
3068
+ return new Promise((resolve13) => {
3034
3069
  const startTime = Date.now();
3035
3070
  let debounceTimer;
3036
3071
  let resolved = false;
@@ -3049,7 +3084,7 @@ async function createClient(serverId, handle, root) {
3049
3084
  if (resolved) return;
3050
3085
  resolved = true;
3051
3086
  cleanup2();
3052
- resolve12(diagnostics.get(normalized) || []);
3087
+ resolve13(diagnostics.get(normalized) || []);
3053
3088
  };
3054
3089
  const onDiagnostic = () => {
3055
3090
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -3424,7 +3459,7 @@ Working directory: ${options.workingDirectory}`,
3424
3459
  isChunked: true
3425
3460
  });
3426
3461
  if (chunkCount > 1) {
3427
- await new Promise((resolve12) => setTimeout(resolve12, 0));
3462
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
3428
3463
  }
3429
3464
  }
3430
3465
  }
@@ -4043,7 +4078,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4043
4078
  if (!existsSync10(directory)) {
4044
4079
  return [];
4045
4080
  }
4046
- const skills = [];
4081
+ const skills2 = [];
4047
4082
  const entries = await readdir(directory, { withFileTypes: true });
4048
4083
  for (const entry2 of entries) {
4049
4084
  let filePath;
@@ -4067,7 +4102,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4067
4102
  if (parsed) {
4068
4103
  const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
4069
4104
  const loadType = alwaysApply ? "always" : defaultLoadType;
4070
- skills.push({
4105
+ skills2.push({
4071
4106
  name: parsed.metadata.name,
4072
4107
  description: parsed.metadata.description,
4073
4108
  filePath,
@@ -4081,7 +4116,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4081
4116
  } else {
4082
4117
  const name = getSkillNameFromPath(filePath);
4083
4118
  const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
4084
- skills.push({
4119
+ skills2.push({
4085
4120
  name,
4086
4121
  description: firstParagraph.replace(/^#\s*/, "").trim(),
4087
4122
  filePath,
@@ -4094,7 +4129,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4094
4129
  });
4095
4130
  }
4096
4131
  }
4097
- return skills.filter(
4132
+ return skills2.filter(
4098
4133
  (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
4099
4134
  );
4100
4135
  }
@@ -4102,8 +4137,8 @@ async function loadAllSkills(directories) {
4102
4137
  const allSkills = [];
4103
4138
  const seenNames = /* @__PURE__ */ new Set();
4104
4139
  for (const dir of directories) {
4105
- const skills = await loadSkillsFromDirectory(dir);
4106
- for (const skill of skills) {
4140
+ const skills2 = await loadSkillsFromDirectory(dir);
4141
+ for (const skill of skills2) {
4107
4142
  if (!seenNames.has(skill.name.toLowerCase())) {
4108
4143
  seenNames.add(skill.name.toLowerCase());
4109
4144
  allSkills.push(skill);
@@ -4116,12 +4151,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4116
4151
  const allSkills = [];
4117
4152
  const seenNames = /* @__PURE__ */ new Set();
4118
4153
  for (const { path, priority } of discovered.alwaysLoadedDirs) {
4119
- const skills = await loadSkillsFromDirectory(path, {
4154
+ const skills2 = await loadSkillsFromDirectory(path, {
4120
4155
  priority,
4121
4156
  defaultLoadType: "always",
4122
4157
  forceAlwaysApply: true
4123
4158
  });
4124
- for (const skill of skills) {
4159
+ for (const skill of skills2) {
4125
4160
  if (!seenNames.has(skill.name.toLowerCase())) {
4126
4161
  seenNames.add(skill.name.toLowerCase());
4127
4162
  allSkills.push(skill);
@@ -4129,12 +4164,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
4129
4164
  }
4130
4165
  }
4131
4166
  for (const { path, priority } of discovered.onDemandDirs) {
4132
- const skills = await loadSkillsFromDirectory(path, {
4167
+ const skills2 = await loadSkillsFromDirectory(path, {
4133
4168
  priority,
4134
4169
  defaultLoadType: "on_demand",
4135
4170
  forceAlwaysApply: false
4136
4171
  });
4137
- for (const skill of skills) {
4172
+ for (const skill of skills2) {
4138
4173
  if (!seenNames.has(skill.name.toLowerCase())) {
4139
4174
  seenNames.add(skill.name.toLowerCase());
4140
4175
  allSkills.push(skill);
@@ -4159,7 +4194,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
4159
4194
  all: allSkills
4160
4195
  };
4161
4196
  }
4162
- async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4197
+ async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
4163
4198
  if (activeFiles.length === 0) {
4164
4199
  return [];
4165
4200
  }
@@ -4169,7 +4204,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
4169
4204
  }
4170
4205
  return f;
4171
4206
  });
4172
- const matchedSkills = skills.filter((skill) => {
4207
+ const matchedSkills = skills2.filter((skill) => {
4173
4208
  if (skill.alwaysApply || skill.loadType === "always") {
4174
4209
  return false;
4175
4210
  }
@@ -4215,8 +4250,8 @@ async function loadSkillContent(skillName, directories) {
4215
4250
  content: parsed ? parsed.body : content
4216
4251
  };
4217
4252
  }
4218
- function formatSkillsForContext(skills) {
4219
- const onDemandSkills = skills.filter((s) => !s.alwaysApply && s.loadType !== "always");
4253
+ function formatSkillsForContext(skills2) {
4254
+ const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
4220
4255
  if (onDemandSkills.length === 0) {
4221
4256
  return "No on-demand skills available.";
4222
4257
  }
@@ -4227,12 +4262,12 @@ function formatSkillsForContext(skills) {
4227
4262
  }
4228
4263
  return lines.join("\n");
4229
4264
  }
4230
- function formatAlwaysLoadedSkills(skills) {
4231
- if (skills.length === 0) {
4265
+ function formatAlwaysLoadedSkills(skills2) {
4266
+ if (skills2.length === 0) {
4232
4267
  return "";
4233
4268
  }
4234
4269
  const sections = [];
4235
- for (const skill of skills) {
4270
+ for (const skill of skills2) {
4236
4271
  sections.push(`### ${skill.name}
4237
4272
 
4238
4273
  ${skill.content}`);
@@ -4241,12 +4276,12 @@ ${skill.content}`);
4241
4276
 
4242
4277
  ${sections.join("\n\n---\n\n")}`;
4243
4278
  }
4244
- function formatGlobMatchedSkills(skills) {
4245
- if (skills.length === 0) {
4279
+ function formatGlobMatchedSkills(skills2) {
4280
+ if (skills2.length === 0) {
4246
4281
  return "";
4247
4282
  }
4248
4283
  const sections = [];
4249
- for (const skill of skills) {
4284
+ for (const skill of skills2) {
4250
4285
  sections.push(`### ${skill.name}
4251
4286
 
4252
4287
  ${skill.content}`);
@@ -4288,16 +4323,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
4288
4323
  try {
4289
4324
  switch (action) {
4290
4325
  case "list": {
4291
- const skills = await loadAllSkills(options.skillsDirectories);
4326
+ const skills2 = await loadAllSkills(options.skillsDirectories);
4292
4327
  return {
4293
4328
  success: true,
4294
4329
  action: "list",
4295
- skillCount: skills.length,
4296
- skills: skills.map((s) => ({
4330
+ skillCount: skills2.length,
4331
+ skills: skills2.map((s) => ({
4297
4332
  name: s.name,
4298
4333
  description: s.description
4299
4334
  })),
4300
- formatted: formatSkillsForContext(skills)
4335
+ formatted: formatSkillsForContext(skills2)
4301
4336
  };
4302
4337
  }
4303
4338
  case "load": {
@@ -4735,8 +4770,8 @@ var init_subagent = __esm({
4735
4770
  if (eventQueue.length > 0) {
4736
4771
  yield eventQueue.shift();
4737
4772
  } else if (!done) {
4738
- const event = await new Promise((resolve12) => {
4739
- resolveNext = resolve12;
4773
+ const event = await new Promise((resolve13) => {
4774
+ resolveNext = resolve13;
4740
4775
  });
4741
4776
  if (event) {
4742
4777
  yield event;
@@ -6512,8 +6547,8 @@ async function buildSystemPrompt(options) {
6512
6547
  }
6513
6548
  } else {
6514
6549
  const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
6515
- const skills = await loadAllSkills2(skillsDirectories);
6516
- onDemandSkillsContext = formatSkillsForContext(skills);
6550
+ const skills2 = await loadAllSkills2(skillsDirectories);
6551
+ onDemandSkillsContext = formatSkillsForContext(skills2);
6517
6552
  }
6518
6553
  const todos = await todoQueries.getBySession(sessionId);
6519
6554
  const todosContext = formatTodosForContext(todos);
@@ -7412,6 +7447,35 @@ function stripOrphanedToolResults(msg, removedIds) {
7412
7447
  if (parts.length === 0) return null;
7413
7448
  return { ...msg, content: parts };
7414
7449
  }
7450
+ function wrapToolsNeverThrow(tools) {
7451
+ if (!tools || typeof tools !== "object") return tools;
7452
+ const wrapped = {};
7453
+ for (const [name, t] of Object.entries(tools)) {
7454
+ if (!t || typeof t.execute !== "function") {
7455
+ wrapped[name] = t;
7456
+ continue;
7457
+ }
7458
+ const original = t.execute;
7459
+ wrapped[name] = {
7460
+ ...t,
7461
+ execute: async (input, opts) => {
7462
+ try {
7463
+ return await original.call(t, input, opts);
7464
+ } catch (err) {
7465
+ const message = err?.message ?? String(err);
7466
+ console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
7467
+ return {
7468
+ __error: true,
7469
+ tool: name,
7470
+ message,
7471
+ note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
7472
+ };
7473
+ }
7474
+ }
7475
+ };
7476
+ }
7477
+ return wrapped;
7478
+ }
7415
7479
  function repairToolPairing(messages) {
7416
7480
  const toolCallIds = /* @__PURE__ */ new Set();
7417
7481
  const toolResultIds = /* @__PURE__ */ new Set();
@@ -7777,6 +7841,120 @@ var init_web = __esm({
7777
7841
  }
7778
7842
  });
7779
7843
 
7844
+ // src/integrations/slack/persistence.ts
7845
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
7846
+ import { join as join9, dirname as dirname6 } from "path";
7847
+ function cachePath() {
7848
+ return join9(ensureAppDataDirectory(), FILENAME);
7849
+ }
7850
+ function load() {
7851
+ if (loaded) return;
7852
+ loaded = true;
7853
+ const path = cachePath();
7854
+ if (!existsSync16(path)) return;
7855
+ try {
7856
+ const raw = readFileSync7(path, "utf-8");
7857
+ const parsed = JSON.parse(raw);
7858
+ if (parsed?.version !== FILE_VERSION) return;
7859
+ const now = Date.now();
7860
+ for (const [id, e] of Object.entries(parsed.users || {})) {
7861
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7862
+ userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
7863
+ }
7864
+ }
7865
+ for (const [key2, e] of Object.entries(parsed.threads || {})) {
7866
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7867
+ threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
7868
+ }
7869
+ }
7870
+ } catch (err) {
7871
+ console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
7872
+ }
7873
+ }
7874
+ function evictIfOversized(map, max) {
7875
+ if (map.size <= max) return;
7876
+ const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
7877
+ const toRemove = map.size - max;
7878
+ for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
7879
+ }
7880
+ function scheduleSave() {
7881
+ dirty = true;
7882
+ if (saveTimer) return;
7883
+ saveTimer = setTimeout(() => {
7884
+ saveTimer = null;
7885
+ if (dirty) saveSync();
7886
+ }, SAVE_DEBOUNCE_MS);
7887
+ saveTimer.unref?.();
7888
+ }
7889
+ function saveSync() {
7890
+ dirty = false;
7891
+ try {
7892
+ const path = cachePath();
7893
+ const dir = dirname6(path);
7894
+ if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
7895
+ const payload = {
7896
+ version: FILE_VERSION,
7897
+ users: Object.fromEntries(userMap),
7898
+ threads: Object.fromEntries(threadMap)
7899
+ };
7900
+ const tmp = `${path}.tmp`;
7901
+ writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
7902
+ renameSync(tmp, path);
7903
+ } catch (err) {
7904
+ console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
7905
+ }
7906
+ }
7907
+ function hookExit() {
7908
+ if (exitHooked) return;
7909
+ exitHooked = true;
7910
+ const flush2 = () => {
7911
+ if (dirty) saveSync();
7912
+ };
7913
+ process.once("beforeExit", flush2);
7914
+ process.once("SIGINT", flush2);
7915
+ process.once("SIGTERM", flush2);
7916
+ }
7917
+ function getCachedUserName(userId) {
7918
+ load();
7919
+ return userMap.get(userId);
7920
+ }
7921
+ function setCachedUserName(userId, entry2) {
7922
+ load();
7923
+ hookExit();
7924
+ userMap.set(userId, entry2);
7925
+ evictIfOversized(userMap, MAX_USERS);
7926
+ scheduleSave();
7927
+ }
7928
+ function getCachedThreadOwnership(key2) {
7929
+ load();
7930
+ return threadMap.get(key2);
7931
+ }
7932
+ function setCachedThreadOwnership(key2, entry2) {
7933
+ load();
7934
+ hookExit();
7935
+ threadMap.set(key2, entry2);
7936
+ evictIfOversized(threadMap, MAX_THREADS);
7937
+ scheduleSave();
7938
+ }
7939
+ var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
7940
+ var init_persistence = __esm({
7941
+ "src/integrations/slack/persistence.ts"() {
7942
+ "use strict";
7943
+ init_config();
7944
+ FILENAME = "slack-cache.json";
7945
+ FILE_VERSION = 1;
7946
+ SAVE_DEBOUNCE_MS = 500;
7947
+ MAX_USERS = 5e3;
7948
+ MAX_THREADS = 5e3;
7949
+ loaded = false;
7950
+ userMap = /* @__PURE__ */ new Map();
7951
+ threadMap = /* @__PURE__ */ new Map();
7952
+ dirty = false;
7953
+ saveTimer = null;
7954
+ exitHooked = false;
7955
+ }
7956
+ });
7957
+
7780
7958
  // src/integrations/slack/client.ts
7781
7959
  function readSlackConfig() {
7782
7960
  try {
@@ -7899,13 +8077,13 @@ async function fetchSlackUserName(userId) {
7899
8077
  async function resolveSlackUserName(userId) {
7900
8078
  if (!userId) return null;
7901
8079
  const now = Date.now();
7902
- const hit = userNameCache.get(userId);
8080
+ const hit = getCachedUserName(userId);
7903
8081
  if (hit && hit.expiresAt > now) return hit.name;
7904
8082
  const inflight = userInflight.get(userId);
7905
8083
  if (inflight) return inflight;
7906
8084
  const p = (async () => {
7907
8085
  const name = await fetchSlackUserName(userId);
7908
- userNameCache.set(userId, {
8086
+ setCachedUserName(userId, {
7909
8087
  name,
7910
8088
  expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
7911
8089
  });
@@ -7927,11 +8105,63 @@ async function normalizeSlackMentions(text) {
7927
8105
  }
7928
8106
  return text.replace(userMentionRe, (_full, id, label) => {
7929
8107
  if (label) return `${label} <@${id}>`;
7930
- const cached = userNameCache.get(id);
8108
+ const cached = getCachedUserName(id);
7931
8109
  const name = cached?.name;
7932
8110
  return name ? `${name} <@${id}>` : `<@${id}>`;
7933
8111
  });
7934
8112
  }
8113
+ function threadCacheKey(channel, threadTs) {
8114
+ return `${channel}\u241F${threadTs}`;
8115
+ }
8116
+ async function fetchBotParticipatedInThread(channel, threadTs) {
8117
+ const token = getSlackBotToken();
8118
+ if (!token) return false;
8119
+ const self = await ensureSlackSelfIdentity();
8120
+ if (!self) return false;
8121
+ try {
8122
+ const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
8123
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
8124
+ const data = await res.json().catch(() => ({}));
8125
+ if (!data?.ok) {
8126
+ console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
8127
+ return false;
8128
+ }
8129
+ const messages = Array.isArray(data.messages) ? data.messages : [];
8130
+ return messages.some(
8131
+ (m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
8132
+ );
8133
+ } catch (err) {
8134
+ console.warn(`[slack] conversations.replies error:`, err?.message || err);
8135
+ return false;
8136
+ }
8137
+ }
8138
+ async function botParticipatedInThread(channel, threadTs) {
8139
+ if (!channel || !threadTs) return false;
8140
+ const key2 = threadCacheKey(channel, threadTs);
8141
+ const now = Date.now();
8142
+ const hit = getCachedThreadOwnership(key2);
8143
+ if (hit && hit.expiresAt > now) return hit.owned;
8144
+ const inflight = threadOwnedInflight.get(key2);
8145
+ if (inflight) return inflight;
8146
+ const p = (async () => {
8147
+ const owned = await fetchBotParticipatedInThread(channel, threadTs);
8148
+ setCachedThreadOwnership(key2, {
8149
+ owned,
8150
+ expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
8151
+ });
8152
+ threadOwnedInflight.delete(key2);
8153
+ return owned;
8154
+ })();
8155
+ threadOwnedInflight.set(key2, p);
8156
+ return p;
8157
+ }
8158
+ function noteBotPostedInThread(channel, threadTs) {
8159
+ if (!channel || !threadTs) return;
8160
+ setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
8161
+ owned: true,
8162
+ expiresAt: Date.now() + THREAD_OWNED_TTL_MS
8163
+ });
8164
+ }
7935
8165
  function getSlackDeniedReplyPolicy() {
7936
8166
  try {
7937
8167
  const cfg = getConfig();
@@ -7944,17 +8174,20 @@ function getSlackDeniedReplyPolicy() {
7944
8174
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
7945
8175
  }
7946
8176
  }
7947
- var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
8177
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
7948
8178
  var init_client3 = __esm({
7949
8179
  "src/integrations/slack/client.ts"() {
7950
8180
  "use strict";
7951
8181
  init_config();
8182
+ init_persistence();
7952
8183
  cachedSelf = null;
7953
8184
  selfInflight = null;
7954
8185
  USER_TTL_MS = 60 * 60 * 1e3;
7955
8186
  USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7956
- userNameCache = /* @__PURE__ */ new Map();
7957
8187
  userInflight = /* @__PURE__ */ new Map();
8188
+ THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
8189
+ THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
8190
+ threadOwnedInflight = /* @__PURE__ */ new Map();
7958
8191
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
7959
8192
  }
7960
8193
  });
@@ -8054,6 +8287,7 @@ var init_slack = __esm({
8054
8287
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
8055
8288
  if (r.slackChannel && r.threadTs) {
8056
8289
  markThreadOwned(r.slackChannel, r.threadTs);
8290
+ noteBotPostedInThread(r.slackChannel, r.threadTs);
8057
8291
  }
8058
8292
  },
8059
8293
  displayLabel(ref) {
@@ -8711,8 +8945,8 @@ var init_orchestrator_actions = __esm({
8711
8945
 
8712
8946
  // src/integrations/mcp/store.ts
8713
8947
  import { nanoid as nanoid6 } from "nanoid";
8714
- import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
8715
- import { resolve as resolve10, join as join9 } from "path";
8948
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
8949
+ import { resolve as resolve10, join as join10 } from "path";
8716
8950
  function readServers() {
8717
8951
  try {
8718
8952
  const cfg = getConfig();
@@ -8724,12 +8958,12 @@ function readServers() {
8724
8958
  function refreshMcpServersFromDisk() {
8725
8959
  const candidates = [
8726
8960
  resolve10(process.cwd(), "sparkecoder.config.json"),
8727
- join9(ensureAppDataDirectory(), "sparkecoder.config.json")
8961
+ join10(ensureAppDataDirectory(), "sparkecoder.config.json")
8728
8962
  ];
8729
8963
  for (const path of candidates) {
8730
- if (!existsSync16(path)) continue;
8964
+ if (!existsSync17(path)) continue;
8731
8965
  try {
8732
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
8966
+ const raw = JSON.parse(readFileSync8(path, "utf-8"));
8733
8967
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
8734
8968
  setMcpServers(servers2);
8735
8969
  return servers2;
@@ -8981,11 +9215,11 @@ function waitForTaskQuestionAnswer(question) {
8981
9215
  if (pendingQuestions.has(k)) {
8982
9216
  return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
8983
9217
  }
8984
- return new Promise((resolve12, reject) => {
9218
+ return new Promise((resolve13, reject) => {
8985
9219
  pendingQuestions.set(k, {
8986
9220
  ...question,
8987
9221
  createdAt: /* @__PURE__ */ new Date(),
8988
- resolve: resolve12,
9222
+ resolve: resolve13,
8989
9223
  reject
8990
9224
  });
8991
9225
  });
@@ -9342,7 +9576,7 @@ __export(recorder_exports, {
9342
9576
  import { exec as exec5 } from "child_process";
9343
9577
  import { promisify as promisify5 } from "util";
9344
9578
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
9345
- import { join as join10 } from "path";
9579
+ import { join as join11 } from "path";
9346
9580
  import { tmpdir } from "os";
9347
9581
  import { nanoid as nanoid7 } from "nanoid";
9348
9582
  async function checkFfmpeg() {
@@ -9399,21 +9633,21 @@ var init_recorder = __esm({
9399
9633
  */
9400
9634
  async encode() {
9401
9635
  if (this.frames.length === 0) return null;
9402
- const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9636
+ const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9403
9637
  await mkdir4(workDir, { recursive: true });
9404
9638
  try {
9405
9639
  for (let i = 0; i < this.frames.length; i++) {
9406
- const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9640
+ const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9407
9641
  await writeFile5(framePath, this.frames[i].data);
9408
9642
  }
9409
9643
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
9410
9644
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
9411
9645
  const clampedFps = Math.max(1, Math.min(fps, 30));
9412
- const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
9646
+ const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
9413
9647
  const hasFfmpeg = await checkFfmpeg();
9414
9648
  if (hasFfmpeg) {
9415
9649
  await execAsync5(
9416
- `ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9650
+ `ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9417
9651
  { timeout: 12e4 }
9418
9652
  );
9419
9653
  } else {
@@ -9425,7 +9659,7 @@ var init_recorder = __esm({
9425
9659
  const files = await readdir5(workDir);
9426
9660
  for (const f of files) {
9427
9661
  if (f.startsWith("frame_")) {
9428
- await unlink2(join10(workDir, f)).catch(() => {
9662
+ await unlink2(join11(workDir, f)).catch(() => {
9429
9663
  });
9430
9664
  }
9431
9665
  }
@@ -9692,7 +9926,8 @@ ${personality.trim()}`;
9692
9926
  }
9693
9927
  const messages = await this.context.getMessages();
9694
9928
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9695
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
9929
+ const approvalWrapped = this.wrapToolsWithApproval(options, tools);
9930
+ const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
9696
9931
  const useAnthropic = isAnthropicModel(this.session.model);
9697
9932
  const stream = streamText2({
9698
9933
  model: resolveModel(this.session.model),
@@ -9706,6 +9941,17 @@ ${personality.trim()}`;
9706
9941
  providerOptions: useAnthropic ? {
9707
9942
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9708
9943
  } : void 0,
9944
+ // Run repairToolPairing before EVERY step's model call, not just the
9945
+ // first one. The AI SDK's multi-step loop can otherwise feed the model
9946
+ // a prompt containing an orphan tool-call (e.g. when a previous step's
9947
+ // tool result was lost, dropped during compaction, or the stream was
9948
+ // aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
9949
+ // ever reaches the model and we never hit AI_MissingToolResultsError.
9950
+ prepareStep: async ({ messages: stepMessages }) => {
9951
+ const repaired = repairToolPairing(stepMessages);
9952
+ if (repaired === stepMessages) return {};
9953
+ return { messages: repaired };
9954
+ },
9709
9955
  onStepFinish: async (step) => {
9710
9956
  options.onStepFinish?.(step);
9711
9957
  },
@@ -9741,7 +9987,7 @@ ${personality.trim()}`;
9741
9987
  });
9742
9988
  const messages = await this.context.getMessages();
9743
9989
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9744
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
9990
+ const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
9745
9991
  const useAnthropic = isAnthropicModel(this.session.model);
9746
9992
  const result = await generateText3({
9747
9993
  model: resolveModel(this.session.model),
@@ -9752,7 +9998,13 @@ ${personality.trim()}`;
9752
9998
  // Enable extended thinking/reasoning for models that support it
9753
9999
  providerOptions: useAnthropic ? {
9754
10000
  anthropic: getAnthropicProviderOptions(this.session.model)
9755
- } : void 0
10001
+ } : void 0,
10002
+ // Repair tool pairing before every step (see `stream()` for full rationale).
10003
+ prepareStep: async ({ messages: stepMessages }) => {
10004
+ const repaired = repairToolPairing(stepMessages);
10005
+ if (repaired === stepMessages) return {};
10006
+ return { messages: repaired };
10007
+ }
9756
10008
  });
9757
10009
  const responseMessages = result.response.messages;
9758
10010
  this.context.addResponseMessages(responseMessages);
@@ -9929,12 +10181,19 @@ ${p.text}` : p.text;
9929
10181
  model: resolveModel(this.session.model),
9930
10182
  system: systemPrompt,
9931
10183
  messages,
9932
- tools: taskTools,
10184
+ tools: wrapToolsNeverThrow(taskTools),
9933
10185
  stopWhen: stepCountIs2(500),
9934
10186
  abortSignal: combinedAbort,
9935
10187
  providerOptions: useAnthropic ? {
9936
10188
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9937
10189
  } : void 0,
10190
+ // See the matching note in `stream()` — repair tool pairing before
10191
+ // every step so we never feed the model an orphan tool-call.
10192
+ prepareStep: async ({ messages: stepMessages }) => {
10193
+ const repaired = repairToolPairing(stepMessages);
10194
+ if (repaired === stepMessages) return {};
10195
+ return { messages: repaired };
10196
+ },
9938
10197
  onStepFinish: async (step) => {
9939
10198
  options.onStepFinish?.(step);
9940
10199
  fireWebhook("task.step_finished", { iteration, text: step.text });
@@ -10134,14 +10393,14 @@ ${p.text}` : p.text;
10134
10393
  const result = await recorder.encode();
10135
10394
  recorder.clear();
10136
10395
  if (!result) return [];
10137
- const { readFile: readFile12, unlink: unlink3 } = await import("fs/promises");
10396
+ const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
10138
10397
  const uploadInfo = await storageQueries2.getUploadUrl(
10139
10398
  this.session.id,
10140
10399
  `browser-recording-${Date.now()}.mp4`,
10141
10400
  "video/mp4",
10142
10401
  "browser-recording"
10143
10402
  );
10144
- const fileData = await readFile12(result.path);
10403
+ const fileData = await readFile13(result.path);
10145
10404
  await fetch(uploadInfo.uploadUrl, {
10146
10405
  method: "PUT",
10147
10406
  headers: { "Content-Type": "video/mp4" },
@@ -10149,7 +10408,7 @@ ${p.text}` : p.text;
10149
10408
  });
10150
10409
  await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
10151
10410
  const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
10152
- await unlink3(result.path).catch(() => {
10411
+ await unlink4(result.path).catch(() => {
10153
10412
  });
10154
10413
  console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
10155
10414
  return [dlInfo.downloadUrl];
@@ -10167,13 +10426,13 @@ ${p.text}` : p.text;
10167
10426
  try {
10168
10427
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10169
10428
  if (!isRemoteConfigured2()) return [];
10170
- const { readFile: readFile12 } = await import("fs/promises");
10171
- const { join: join16, basename: basename6 } = await import("path");
10429
+ const { readFile: readFile13 } = await import("fs/promises");
10430
+ const { join: join18, basename: basename7 } = await import("path");
10172
10431
  const urls = [];
10173
10432
  for (const filePath of filePaths) {
10174
10433
  try {
10175
- const fullPath = filePath.startsWith("/") ? filePath : join16(this.session.workingDirectory, filePath);
10176
- const fileName = basename6(fullPath);
10434
+ const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
10435
+ const fileName = basename7(fullPath);
10177
10436
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10178
10437
  const mimeMap = {
10179
10438
  pdf: "application/pdf",
@@ -10197,7 +10456,7 @@ ${p.text}` : p.text;
10197
10456
  contentType,
10198
10457
  "task-output"
10199
10458
  );
10200
- const fileData = await readFile12(fullPath);
10459
+ const fileData = await readFile13(fullPath);
10201
10460
  await fetch(uploadInfo.uploadUrl, {
10202
10461
  method: "PUT",
10203
10462
  headers: { "Content-Type": contentType },
@@ -10246,8 +10505,8 @@ ${p.text}` : p.text;
10246
10505
  this.pendingApprovals.set(toolCallId, await execution);
10247
10506
  options.onApprovalRequired?.(await execution);
10248
10507
  await sessionQueries.updateStatus(this.session.id, "waiting");
10249
- const approved = await new Promise((resolve12) => {
10250
- approvalResolvers.set(toolCallId, { resolve: resolve12, sessionId: this.session.id });
10508
+ const approved = await new Promise((resolve13) => {
10509
+ approvalResolvers.set(toolCallId, { resolve: resolve13, sessionId: this.session.id });
10251
10510
  });
10252
10511
  const resolverData = approvalResolvers.get(toolCallId);
10253
10512
  approvalResolvers.delete(toolCallId);
@@ -10353,8 +10612,8 @@ async function withSessionLock(sessionId, fn) {
10353
10612
  state2.pending++;
10354
10613
  const prev = state2.tail;
10355
10614
  let release;
10356
- const next = new Promise((resolve12) => {
10357
- release = resolve12;
10615
+ const next = new Promise((resolve13) => {
10616
+ release = resolve13;
10358
10617
  });
10359
10618
  state2.tail = prev.then(() => next);
10360
10619
  await prev;
@@ -10377,19 +10636,19 @@ var init_session_lock = __esm({
10377
10636
  });
10378
10637
 
10379
10638
  // src/orchestrator/webhook-events.ts
10380
- import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
10381
- import { dirname as dirname6, join as join11 } from "path";
10639
+ import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
10640
+ import { dirname as dirname7, join as join12 } from "path";
10382
10641
  import { nanoid as nanoid9 } from "nanoid";
10383
10642
  function logFilePath() {
10384
- return join11(getAppDataDirectory(), "webhook-events.jsonl");
10643
+ return join12(getAppDataDirectory(), "webhook-events.jsonl");
10385
10644
  }
10386
10645
  function ensureLoaded() {
10387
10646
  if (cache !== null) return cache;
10388
10647
  cache = [];
10389
10648
  try {
10390
10649
  const p = logFilePath();
10391
- if (!existsSync17(p)) return cache;
10392
- const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
10650
+ if (!existsSync18(p)) return cache;
10651
+ const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
10393
10652
  for (const line of lines) {
10394
10653
  try {
10395
10654
  cache.push(JSON.parse(line));
@@ -10399,7 +10658,7 @@ function ensureLoaded() {
10399
10658
  if (cache.length > MAX_EVENTS) {
10400
10659
  cache = cache.slice(-MAX_EVENTS);
10401
10660
  try {
10402
- writeFileSync3(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10661
+ writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10403
10662
  } catch {
10404
10663
  }
10405
10664
  }
@@ -10413,7 +10672,7 @@ function appendEvent(ev) {
10413
10672
  if (list.length > MAX_EVENTS) list.shift();
10414
10673
  try {
10415
10674
  const p = logFilePath();
10416
- mkdirSync6(dirname6(p), { recursive: true });
10675
+ mkdirSync7(dirname7(p), { recursive: true });
10417
10676
  appendFileSync3(p, JSON.stringify(ev) + "\n");
10418
10677
  } catch {
10419
10678
  }
@@ -10444,8 +10703,8 @@ function updateEvent(id, patch) {
10444
10703
  list[i] = { ...list[i], ...patch };
10445
10704
  try {
10446
10705
  const p = logFilePath();
10447
- mkdirSync6(dirname6(p), { recursive: true });
10448
- writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10706
+ mkdirSync7(dirname7(p), { recursive: true });
10707
+ writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10449
10708
  } catch {
10450
10709
  }
10451
10710
  }
@@ -10477,7 +10736,7 @@ function listEvents(filter = {}) {
10477
10736
  function clearAllEvents() {
10478
10737
  cache = [];
10479
10738
  try {
10480
- writeFileSync3(logFilePath(), "");
10739
+ writeFileSync4(logFilePath(), "");
10481
10740
  } catch {
10482
10741
  }
10483
10742
  }
@@ -10747,12 +11006,12 @@ init_agent();
10747
11006
 
10748
11007
  // src/server/index.ts
10749
11008
  import "dotenv/config";
10750
- import { Hono as Hono9 } from "hono";
11009
+ import { Hono as Hono10 } from "hono";
10751
11010
  import { serve } from "@hono/node-server";
10752
11011
  import { cors } from "hono/cors";
10753
11012
  import { logger } from "hono/logger";
10754
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10755
- import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
11013
+ import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
11014
+ import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
10756
11015
  import { spawn as spawn2 } from "child_process";
10757
11016
  import { createServer as createNetServer } from "net";
10758
11017
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -10766,9 +11025,9 @@ init_checkpoints();
10766
11025
  import { Hono } from "hono";
10767
11026
  import { zValidator } from "@hono/zod-validator";
10768
11027
  import { z as z16 } from "zod";
10769
- import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
11028
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
10770
11029
  import { readdir as readdir6 } from "fs/promises";
10771
- import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
11030
+ import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
10772
11031
  import { nanoid as nanoid10 } from "nanoid";
10773
11032
 
10774
11033
  // src/tasks/agent-status.ts
@@ -11406,12 +11665,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
11406
11665
  });
11407
11666
  function getAttachmentsDir(sessionId) {
11408
11667
  const appDataDir = getAppDataDirectory();
11409
- return join12(appDataDir, "attachments", sessionId);
11668
+ return join13(appDataDir, "attachments", sessionId);
11410
11669
  }
11411
11670
  function ensureAttachmentsDir(sessionId) {
11412
11671
  const dir = getAttachmentsDir(sessionId);
11413
- if (!existsSync18(dir)) {
11414
- mkdirSync7(dir, { recursive: true });
11672
+ if (!existsSync19(dir)) {
11673
+ mkdirSync8(dir, { recursive: true });
11415
11674
  }
11416
11675
  return dir;
11417
11676
  }
@@ -11422,12 +11681,12 @@ sessions2.get("/:id/attachments", async (c) => {
11422
11681
  return c.json({ error: "Session not found" }, 404);
11423
11682
  }
11424
11683
  const dir = getAttachmentsDir(sessionId);
11425
- if (!existsSync18(dir)) {
11684
+ if (!existsSync19(dir)) {
11426
11685
  return c.json({ sessionId, attachments: [], count: 0 });
11427
11686
  }
11428
11687
  const files = readdirSync3(dir);
11429
11688
  const attachments = files.map((filename) => {
11430
- const filePath = join12(dir, filename);
11689
+ const filePath = join13(dir, filename);
11431
11690
  const stats = statSync2(filePath);
11432
11691
  return {
11433
11692
  id: filename.split("_")[0],
@@ -11462,9 +11721,9 @@ sessions2.post("/:id/attachments", async (c) => {
11462
11721
  const id = nanoid10(10);
11463
11722
  const ext = extname8(file.name) || "";
11464
11723
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11465
- const filePath = join12(dir, safeFilename);
11724
+ const filePath = join13(dir, safeFilename);
11466
11725
  const arrayBuffer = await file.arrayBuffer();
11467
- writeFileSync4(filePath, Buffer.from(arrayBuffer));
11726
+ writeFileSync5(filePath, Buffer.from(arrayBuffer));
11468
11727
  return c.json({
11469
11728
  id,
11470
11729
  filename: file.name,
@@ -11488,13 +11747,13 @@ sessions2.post("/:id/attachments", async (c) => {
11488
11747
  const id = nanoid10(10);
11489
11748
  const ext = extname8(body.filename) || "";
11490
11749
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11491
- const filePath = join12(dir, safeFilename);
11750
+ const filePath = join13(dir, safeFilename);
11492
11751
  let base64Data = body.data;
11493
11752
  if (base64Data.includes(",")) {
11494
11753
  base64Data = base64Data.split(",")[1];
11495
11754
  }
11496
11755
  const buffer = Buffer.from(base64Data, "base64");
11497
- writeFileSync4(filePath, buffer);
11756
+ writeFileSync5(filePath, buffer);
11498
11757
  return c.json({
11499
11758
  id,
11500
11759
  filename: body.filename,
@@ -11517,7 +11776,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11517
11776
  return c.json({ error: "Session not found" }, 404);
11518
11777
  }
11519
11778
  const dir = getAttachmentsDir(sessionId);
11520
- if (!existsSync18(dir)) {
11779
+ if (!existsSync19(dir)) {
11521
11780
  return c.json({ error: "Attachment not found" }, 404);
11522
11781
  }
11523
11782
  const files = readdirSync3(dir);
@@ -11525,7 +11784,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11525
11784
  if (!file) {
11526
11785
  return c.json({ error: "Attachment not found" }, 404);
11527
11786
  }
11528
- const filePath = join12(dir, file);
11787
+ const filePath = join13(dir, file);
11529
11788
  unlinkSync2(filePath);
11530
11789
  return c.json({ success: true, id: attachmentId });
11531
11790
  });
@@ -11608,7 +11867,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
11608
11867
  const entries = await readdir6(currentDir, { withFileTypes: true });
11609
11868
  for (const entry2 of entries) {
11610
11869
  if (results.length >= limit * 2) break;
11611
- const fullPath = join12(currentDir, entry2.name);
11870
+ const fullPath = join13(currentDir, entry2.name);
11612
11871
  const relativePath = relative9(baseDir, fullPath);
11613
11872
  if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
11614
11873
  continue;
@@ -11656,7 +11915,7 @@ sessions2.get(
11656
11915
  return c.json({ error: "Session not found" }, 404);
11657
11916
  }
11658
11917
  const workingDirectory = session.workingDirectory;
11659
- if (!existsSync18(workingDirectory)) {
11918
+ if (!existsSync19(workingDirectory)) {
11660
11919
  return c.json({
11661
11920
  sessionId,
11662
11921
  workingDirectory,
@@ -11764,14 +12023,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
11764
12023
 
11765
12024
  // src/server/routes/agents.ts
11766
12025
  init_db();
11767
- init_agent();
11768
- init_session_lock();
11769
- init_config();
11770
12026
  import { Hono as Hono2 } from "hono";
11771
12027
  import { zValidator as zValidator2 } from "@hono/zod-validator";
11772
12028
  import { z as z17 } from "zod";
11773
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
11774
- import { join as join13 } from "path";
12029
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
12030
+ import { join as join14 } from "path";
12031
+
12032
+ // src/agent/missing-tool-recovery.ts
12033
+ init_db();
12034
+ function extractMissingToolCallIds(error) {
12035
+ if (!error) return [];
12036
+ const e = error;
12037
+ if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
12038
+ return e.toolCallIds;
12039
+ }
12040
+ const msg = typeof e.message === "string" ? e.message : "";
12041
+ const ids = /* @__PURE__ */ new Set();
12042
+ for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
12043
+ for (const id of m[1].split(/\s*,\s*/)) {
12044
+ const trimmed = id.trim();
12045
+ if (trimmed) ids.add(trimmed);
12046
+ }
12047
+ }
12048
+ return [...ids];
12049
+ }
12050
+ function isMissingToolResultsError(error) {
12051
+ if (!error) return false;
12052
+ const e = error;
12053
+ if (e.name === "AI_MissingToolResultsError") return true;
12054
+ if (Array.isArray(e.toolCallIds)) return true;
12055
+ return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
12056
+ }
12057
+ async function recoverFromMissingToolResults(sessionId, error) {
12058
+ const toolCallIds = extractMissingToolCallIds(error);
12059
+ if (toolCallIds.length === 0) return null;
12060
+ let history = [];
12061
+ try {
12062
+ history = await messageQueries.getModelMessages(sessionId);
12063
+ } catch (err) {
12064
+ console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
12065
+ }
12066
+ const toolNameByCallId = /* @__PURE__ */ new Map();
12067
+ const existingResultIds = /* @__PURE__ */ new Set();
12068
+ for (const msg of history) {
12069
+ if (!Array.isArray(msg.content)) continue;
12070
+ for (const part of msg.content) {
12071
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
12072
+ if (typeof part.toolName === "string") toolNameByCallId.set(part.toolCallId, part.toolName);
12073
+ }
12074
+ if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
12075
+ existingResultIds.add(part.toolCallId);
12076
+ }
12077
+ }
12078
+ }
12079
+ const resolved = toolCallIds.map((id) => ({
12080
+ toolCallId: id,
12081
+ toolName: toolNameByCallId.get(id) ?? "unknown",
12082
+ foundInAssistantMessage: toolNameByCallId.has(id)
12083
+ }));
12084
+ const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
12085
+ let syntheticToolMessageSaved = false;
12086
+ if (stillOrphaned.length > 0) {
12087
+ const syntheticParts = stillOrphaned.map((id) => ({
12088
+ type: "tool-result",
12089
+ toolCallId: id,
12090
+ toolName: toolNameByCallId.get(id) ?? "unknown",
12091
+ output: {
12092
+ type: "text",
12093
+ 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.]"
12094
+ }
12095
+ }));
12096
+ try {
12097
+ await messageQueries.create(sessionId, {
12098
+ role: "tool",
12099
+ content: syntheticParts
12100
+ });
12101
+ syntheticToolMessageSaved = true;
12102
+ } catch (err) {
12103
+ console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
12104
+ }
12105
+ }
12106
+ const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
12107
+ return {
12108
+ kind: "missing_tool_results",
12109
+ toolCallIds,
12110
+ resolved,
12111
+ syntheticToolMessageSaved,
12112
+ lastFewMessageRoles,
12113
+ 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."
12114
+ };
12115
+ }
12116
+
12117
+ // src/server/routes/agents.ts
12118
+ init_agent();
12119
+ init_session_lock();
12120
+ init_config();
11775
12121
 
11776
12122
  // src/server/resumable-stream.ts
11777
12123
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -11927,7 +12273,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
11927
12273
  toolCallId,
11928
12274
  argsTextDelta: chunk
11929
12275
  }));
11930
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12276
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
11931
12277
  }
11932
12278
  }
11933
12279
  function buildDevtoolsContextXml(sessionId) {
@@ -11978,12 +12324,12 @@ var rejectSchema = z17.object({
11978
12324
  var streamAbortControllers = /* @__PURE__ */ new Map();
11979
12325
  function getAttachmentsDirectory(sessionId) {
11980
12326
  const appDataDir = getAppDataDirectory();
11981
- return join13(appDataDir, "attachments", sessionId);
12327
+ return join14(appDataDir, "attachments", sessionId);
11982
12328
  }
11983
12329
  async function saveAttachmentToDisk(sessionId, attachment, index) {
11984
12330
  const attachmentsDir = getAttachmentsDirectory(sessionId);
11985
- if (!existsSync19(attachmentsDir)) {
11986
- mkdirSync8(attachmentsDir, { recursive: true });
12331
+ if (!existsSync20(attachmentsDir)) {
12332
+ mkdirSync9(attachmentsDir, { recursive: true });
11987
12333
  }
11988
12334
  let filename = attachment.filename;
11989
12335
  if (!filename) {
@@ -12001,8 +12347,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
12001
12347
  attachment.mediaType = resized.mediaType;
12002
12348
  attachment.data = buffer.toString("base64");
12003
12349
  }
12004
- const filePath = join13(attachmentsDir, filename);
12005
- writeFileSync5(filePath, buffer);
12350
+ const filePath = join14(attachmentsDir, filename);
12351
+ writeFileSync6(filePath, buffer);
12006
12352
  return filePath;
12007
12353
  }
12008
12354
  function stripDataUrlPrefix2(data) {
@@ -12182,7 +12528,7 @@ ${prompt}` });
12182
12528
  chunkIndex,
12183
12529
  chunkCount
12184
12530
  }));
12185
- await new Promise((resolve12) => setTimeout(resolve12, 0));
12531
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12186
12532
  }
12187
12533
  const browserPort = progress.data?.browserStreamPort;
12188
12534
  const browserClosed = progress.data?.browserClosed;
@@ -12339,7 +12685,20 @@ ${prompt}` });
12339
12685
  await writeSSE(JSON.stringify({ type: "abort" }));
12340
12686
  } else {
12341
12687
  console.error("Agent error:", error);
12342
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
12688
+ let debugPayload = void 0;
12689
+ if (isMissingToolResultsError(error)) {
12690
+ try {
12691
+ const recovery = await recoverFromMissingToolResults(sessionId, error);
12692
+ if (recovery) debugPayload = recovery;
12693
+ } catch (recErr) {
12694
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
12695
+ }
12696
+ }
12697
+ await writeSSE(JSON.stringify({
12698
+ type: "error",
12699
+ errorText: error.message,
12700
+ ...debugPayload ? { debug: debugPayload } : {}
12701
+ }));
12343
12702
  try {
12344
12703
  await activeStreamQueries.markError(streamId);
12345
12704
  } catch {
@@ -12729,7 +13088,7 @@ agents.post(
12729
13088
  chunkIndex,
12730
13089
  chunkCount
12731
13090
  }));
12732
- await new Promise((resolve12) => setTimeout(resolve12, 0));
13091
+ await new Promise((resolve13) => setTimeout(resolve13, 0));
12733
13092
  }
12734
13093
  const browserPort = progress.data?.browserStreamPort;
12735
13094
  const browserClosed = progress.data?.browserClosed;
@@ -12879,7 +13238,20 @@ agents.post(
12879
13238
  await writeSSE(JSON.stringify({ type: "abort" }));
12880
13239
  } else {
12881
13240
  console.error("Agent error:", error);
12882
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
13241
+ let debugPayload = void 0;
13242
+ if (isMissingToolResultsError(error)) {
13243
+ try {
13244
+ const recovery = await recoverFromMissingToolResults(session.id, error);
13245
+ if (recovery) debugPayload = recovery;
13246
+ } catch (recErr) {
13247
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
13248
+ }
13249
+ }
13250
+ await writeSSE(JSON.stringify({
13251
+ type: "error",
13252
+ errorText: error.message,
13253
+ ...debugPayload ? { debug: debugPayload } : {}
13254
+ }));
12883
13255
  await activeStreamQueries.markError(streamId);
12884
13256
  }
12885
13257
  } finally {
@@ -12962,26 +13334,26 @@ init_config();
12962
13334
  import { Hono as Hono3 } from "hono";
12963
13335
  import { zValidator as zValidator3 } from "@hono/zod-validator";
12964
13336
  import { z as z18 } from "zod";
12965
- import { readFileSync as readFileSync9 } from "fs";
13337
+ import { readFileSync as readFileSync10 } from "fs";
12966
13338
  import { fileURLToPath as fileURLToPath3 } from "url";
12967
- import { dirname as dirname7, join as join14 } from "path";
13339
+ import { dirname as dirname8, join as join15 } from "path";
12968
13340
  var __filename = fileURLToPath3(import.meta.url);
12969
- var __dirname = dirname7(__filename);
13341
+ var __dirname = dirname8(__filename);
12970
13342
  var possiblePaths = [
12971
- join14(__dirname, "../package.json"),
13343
+ join15(__dirname, "../package.json"),
12972
13344
  // From dist/server -> dist/../package.json
12973
- join14(__dirname, "../../package.json"),
13345
+ join15(__dirname, "../../package.json"),
12974
13346
  // From dist/server (if nested differently)
12975
- join14(__dirname, "../../../package.json"),
13347
+ join15(__dirname, "../../../package.json"),
12976
13348
  // From src/server/routes (development)
12977
- join14(process.cwd(), "package.json")
13349
+ join15(process.cwd(), "package.json")
12978
13350
  // From current working directory
12979
13351
  ];
12980
13352
  var currentVersion = "0.0.0";
12981
13353
  var packageName = "sparkecoder";
12982
13354
  for (const packageJsonPath of possiblePaths) {
12983
13355
  try {
12984
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
13356
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
12985
13357
  if (packageJson.name === "sparkecoder") {
12986
13358
  currentVersion = packageJson.version || "0.0.0";
12987
13359
  packageName = packageJson.name || "sparkecoder";
@@ -13799,12 +14171,13 @@ slack.post("/events", async (c) => {
13799
14171
  if (inbound) {
13800
14172
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
13801
14173
  if (isThreadReply) {
13802
- const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
14174
+ const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
13803
14175
  if (!ours) {
13804
14176
  console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
13805
14177
  updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
13806
14178
  return c.json({ ok: true });
13807
14179
  }
14180
+ markThreadOwned(ev.channel, ev.thread_ts);
13808
14181
  }
13809
14182
  if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
13810
14183
  markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
@@ -13843,18 +14216,6 @@ slack.post("/events", async (c) => {
13843
14216
  }
13844
14217
  return c.json({ ok: true });
13845
14218
  });
13846
- async function threadBelongsToUs(channel, threadTs) {
13847
- try {
13848
- const sessions3 = await sessionQueries.list(500, 0);
13849
- return sessions3.some((s) => {
13850
- const slack2 = s.config?.slack;
13851
- return slack2?.channel === channel && slack2?.threadTs === threadTs;
13852
- });
13853
- } catch (err) {
13854
- console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
13855
- return false;
13856
- }
13857
- }
13858
14219
  async function findOrCreateOrchestratorId() {
13859
14220
  try {
13860
14221
  const all = await sessionQueries.list(500, 0);
@@ -14237,6 +14598,224 @@ mcpRouter.post("/:id/test", async (c) => {
14237
14598
  return c.json(result);
14238
14599
  });
14239
14600
 
14601
+ // src/server/routes/skills.ts
14602
+ init_config();
14603
+ init_skills();
14604
+ import { Hono as Hono9 } from "hono";
14605
+ import { zValidator as zValidator7 } from "@hono/zod-validator";
14606
+ import { z as z22 } from "zod";
14607
+ import { existsSync as existsSync21, statSync as statSync3 } from "fs";
14608
+ import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
14609
+ import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
14610
+ var skills = new Hono9();
14611
+ function encodeId(filePath) {
14612
+ return Buffer.from(filePath, "utf-8").toString("base64url");
14613
+ }
14614
+ function decodeId(id) {
14615
+ try {
14616
+ const decoded = Buffer.from(id, "base64url").toString("utf-8");
14617
+ return decoded || null;
14618
+ } catch {
14619
+ return null;
14620
+ }
14621
+ }
14622
+ function listAllDirectories() {
14623
+ const cfg = getConfig();
14624
+ const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
14625
+ const out = [];
14626
+ for (const { path, priority } of discovered.alwaysLoadedDirs) {
14627
+ out.push({
14628
+ path,
14629
+ source: pathToSource(path),
14630
+ label: pathToLabel(path),
14631
+ priority,
14632
+ alwaysApply: true
14633
+ });
14634
+ }
14635
+ for (const { path, priority } of discovered.onDemandDirs) {
14636
+ out.push({
14637
+ path,
14638
+ source: pathToSource(path),
14639
+ label: pathToLabel(path),
14640
+ priority,
14641
+ alwaysApply: false
14642
+ });
14643
+ }
14644
+ const additional = cfg.skills?.additionalDirectories || [];
14645
+ for (const dir of additional) {
14646
+ const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
14647
+ if (out.some((d) => d.path === abs)) continue;
14648
+ out.push({
14649
+ path: abs,
14650
+ source: "additional",
14651
+ label: pathToLabel(abs),
14652
+ priority: 50,
14653
+ alwaysApply: false
14654
+ });
14655
+ }
14656
+ return out;
14657
+ }
14658
+ function pathToSource(path) {
14659
+ if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
14660
+ return "project";
14661
+ }
14662
+ function pathToLabel(path) {
14663
+ if (path.includes("/skills/default")) return "Built-in (default)";
14664
+ if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
14665
+ if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
14666
+ if (path.includes("/.cursor/rules")) return ".cursor/rules";
14667
+ if (path.includes("/.claude/skills")) return ".claude/skills";
14668
+ return basename6(dirname9(path)) + "/" + basename6(path);
14669
+ }
14670
+ skills.get("/", async (c) => {
14671
+ const dirs = listAllDirectories();
14672
+ const allSkills = [];
14673
+ for (const dir of dirs) {
14674
+ if (!existsSync21(dir.path)) continue;
14675
+ try {
14676
+ const list = await loadSkillsFromDirectory(dir.path, {
14677
+ priority: dir.priority,
14678
+ defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
14679
+ forceAlwaysApply: dir.alwaysApply
14680
+ });
14681
+ for (const s of list) {
14682
+ let size = 0;
14683
+ try {
14684
+ size = statSync3(s.filePath).size;
14685
+ } catch {
14686
+ }
14687
+ allSkills.push({
14688
+ id: encodeId(s.filePath),
14689
+ name: s.name,
14690
+ description: s.description,
14691
+ filePath: s.filePath,
14692
+ fileName: basename6(s.filePath),
14693
+ sourceDir: dir.path,
14694
+ sourceLabel: dir.label,
14695
+ sourceType: dir.source,
14696
+ alwaysApply: s.alwaysApply,
14697
+ globs: s.globs,
14698
+ sizeBytes: size
14699
+ });
14700
+ }
14701
+ } catch (err) {
14702
+ console.warn("[skills] failed to read", dir.path, err?.message || err);
14703
+ }
14704
+ }
14705
+ return c.json({
14706
+ directories: dirs.map((d) => ({
14707
+ path: d.path,
14708
+ label: d.label,
14709
+ source: d.source,
14710
+ alwaysApply: d.alwaysApply,
14711
+ exists: existsSync21(d.path),
14712
+ writable: isWritable(d.path)
14713
+ })),
14714
+ skills: allSkills
14715
+ });
14716
+ });
14717
+ function isWritable(dir) {
14718
+ try {
14719
+ if (!existsSync21(dir)) return false;
14720
+ if (dir.includes("/skills/default")) return false;
14721
+ return true;
14722
+ } catch {
14723
+ return false;
14724
+ }
14725
+ }
14726
+ skills.get("/:id", async (c) => {
14727
+ const filePath = decodeId(c.req.param("id"));
14728
+ if (!filePath || !existsSync21(filePath)) {
14729
+ return c.json({ error: "skill not found" }, 404);
14730
+ }
14731
+ const content = await readFile12(filePath, "utf-8");
14732
+ return c.json({
14733
+ id: c.req.param("id"),
14734
+ filePath,
14735
+ fileName: basename6(filePath),
14736
+ content,
14737
+ writable: !filePath.includes("/skills/default")
14738
+ });
14739
+ });
14740
+ skills.post(
14741
+ "/",
14742
+ zValidator7("json", z22.object({
14743
+ directory: z22.string().min(1),
14744
+ fileName: z22.string().min(1),
14745
+ content: z22.string()
14746
+ })),
14747
+ async (c) => {
14748
+ const { directory, fileName, content } = c.req.valid("json");
14749
+ const cfg = getConfig();
14750
+ const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
14751
+ if (targetDir.includes("/skills/default")) {
14752
+ return c.json({ error: "cannot write into built-in skills directory" }, 400);
14753
+ }
14754
+ const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
14755
+ const ext = extname9(safeName).toLowerCase();
14756
+ const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
14757
+ const filePath = join16(targetDir, finalName);
14758
+ if (existsSync21(filePath)) {
14759
+ return c.json({ error: `file already exists: ${finalName}` }, 409);
14760
+ }
14761
+ try {
14762
+ await mkdir5(targetDir, { recursive: true });
14763
+ await writeFile6(filePath, content, "utf-8");
14764
+ } catch (err) {
14765
+ return c.json({ error: err?.message || "write failed" }, 500);
14766
+ }
14767
+ return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
14768
+ }
14769
+ );
14770
+ skills.put(
14771
+ "/:id",
14772
+ zValidator7("json", z22.object({ content: z22.string() })),
14773
+ async (c) => {
14774
+ const filePath = decodeId(c.req.param("id"));
14775
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14776
+ if (filePath.includes("/skills/default")) {
14777
+ return c.json({ error: "built-in skills are read-only" }, 400);
14778
+ }
14779
+ await writeFile6(filePath, c.req.valid("json").content, "utf-8");
14780
+ return c.json({ ok: true });
14781
+ }
14782
+ );
14783
+ skills.delete("/:id", async (c) => {
14784
+ const filePath = decodeId(c.req.param("id"));
14785
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14786
+ if (filePath.includes("/skills/default")) {
14787
+ return c.json({ error: "built-in skills are read-only" }, 400);
14788
+ }
14789
+ await unlink3(filePath);
14790
+ return c.json({ ok: true });
14791
+ });
14792
+ skills.post(
14793
+ "/directories",
14794
+ zValidator7("json", z22.object({ path: z22.string().min(1) })),
14795
+ (c) => {
14796
+ const cfg = getConfig();
14797
+ const inputPath = c.req.valid("json").path.trim();
14798
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14799
+ const current = cfg.skills?.additionalDirectories || [];
14800
+ if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
14801
+ return c.json({ error: "directory already added" }, 409);
14802
+ }
14803
+ const next = [...current, abs];
14804
+ setSkillsAdditionalDirectories(next);
14805
+ return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
14806
+ }
14807
+ );
14808
+ skills.delete("/directories", (c) => {
14809
+ const inputPath = c.req.query("path");
14810
+ if (!inputPath) return c.json({ error: "path query param required" }, 400);
14811
+ const cfg = getConfig();
14812
+ const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
14813
+ const current = cfg.skills?.additionalDirectories || [];
14814
+ const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
14815
+ setSkillsAdditionalDirectories(next);
14816
+ return c.json({ ok: true });
14817
+ });
14818
+
14240
14819
  // src/server/auth/cf-access.ts
14241
14820
  init_config();
14242
14821
  import { createRemoteJWKSet, jwtVerify } from "jose";
@@ -14397,13 +14976,13 @@ var DEFAULT_WEB_PORT = 6969;
14397
14976
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
14398
14977
  function getWebDirectory() {
14399
14978
  try {
14400
- const currentDir = dirname8(fileURLToPath4(import.meta.url));
14401
- const webDir = resolve11(currentDir, "..", "web");
14402
- if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
14979
+ const currentDir = dirname10(fileURLToPath4(import.meta.url));
14980
+ const webDir = resolve12(currentDir, "..", "web");
14981
+ if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
14403
14982
  return webDir;
14404
14983
  }
14405
- const altWebDir = resolve11(currentDir, "..", "..", "web");
14406
- if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
14984
+ const altWebDir = resolve12(currentDir, "..", "..", "web");
14985
+ if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
14407
14986
  return altWebDir;
14408
14987
  }
14409
14988
  return null;
@@ -14426,18 +15005,18 @@ async function isSparkcoderWebRunning(port) {
14426
15005
  }
14427
15006
  }
14428
15007
  function isPortInUse(port) {
14429
- return new Promise((resolve12) => {
15008
+ return new Promise((resolve13) => {
14430
15009
  const server = createNetServer();
14431
15010
  server.once("error", (err) => {
14432
15011
  if (err.code === "EADDRINUSE") {
14433
- resolve12(true);
15012
+ resolve13(true);
14434
15013
  } else {
14435
- resolve12(false);
15014
+ resolve13(false);
14436
15015
  }
14437
15016
  });
14438
15017
  server.once("listening", () => {
14439
15018
  server.close();
14440
- resolve12(false);
15019
+ resolve13(false);
14441
15020
  });
14442
15021
  server.listen(port, "0.0.0.0");
14443
15022
  });
@@ -14461,30 +15040,30 @@ async function findWebPort(preferredPort) {
14461
15040
  return { port: preferredPort, alreadyRunning: false };
14462
15041
  }
14463
15042
  function hasProductionBuild(webDir) {
14464
- const buildIdPath = join15(webDir, ".next", "BUILD_ID");
14465
- return existsSync20(buildIdPath);
15043
+ const buildIdPath = join17(webDir, ".next", "BUILD_ID");
15044
+ return existsSync22(buildIdPath);
14466
15045
  }
14467
15046
  function hasSourceFiles(webDir) {
14468
- const appDir = join15(webDir, "src", "app");
14469
- const pagesDir = join15(webDir, "src", "pages");
14470
- const rootAppDir = join15(webDir, "app");
14471
- const rootPagesDir = join15(webDir, "pages");
14472
- return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
15047
+ const appDir = join17(webDir, "src", "app");
15048
+ const pagesDir = join17(webDir, "src", "pages");
15049
+ const rootAppDir = join17(webDir, "app");
15050
+ const rootPagesDir = join17(webDir, "pages");
15051
+ return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
14473
15052
  }
14474
15053
  function getStandaloneServerPath(webDir) {
14475
15054
  const possiblePaths2 = [
14476
- join15(webDir, ".next", "standalone", "server.js"),
14477
- join15(webDir, ".next", "standalone", "web", "server.js")
15055
+ join17(webDir, ".next", "standalone", "server.js"),
15056
+ join17(webDir, ".next", "standalone", "web", "server.js")
14478
15057
  ];
14479
15058
  for (const serverPath of possiblePaths2) {
14480
- if (existsSync20(serverPath)) {
15059
+ if (existsSync22(serverPath)) {
14481
15060
  return serverPath;
14482
15061
  }
14483
15062
  }
14484
15063
  return null;
14485
15064
  }
14486
15065
  function runCommand(command, args, cwd, env) {
14487
- return new Promise((resolve12) => {
15066
+ return new Promise((resolve13) => {
14488
15067
  const child = spawn2(command, args, {
14489
15068
  cwd,
14490
15069
  stdio: ["ignore", "pipe", "pipe"],
@@ -14499,10 +15078,10 @@ function runCommand(command, args, cwd, env) {
14499
15078
  output += data.toString();
14500
15079
  });
14501
15080
  child.on("close", (code) => {
14502
- resolve12({ success: code === 0, output });
15081
+ resolve13({ success: code === 0, output });
14503
15082
  });
14504
15083
  child.on("error", (err) => {
14505
- resolve12({ success: false, output: err.message });
15084
+ resolve13({ success: false, output: err.message });
14506
15085
  });
14507
15086
  });
14508
15087
  }
@@ -14517,15 +15096,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14517
15096
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
14518
15097
  return { process: null, port: actualPort };
14519
15098
  }
14520
- const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
14521
- const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
15099
+ const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
15100
+ const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
14522
15101
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
14523
15102
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
14524
15103
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
14525
15104
  const runtimeConfig = { apiBaseUrl: apiUrl };
14526
- const runtimeConfigPath = join15(webDir, "runtime-config.json");
15105
+ const runtimeConfigPath = join17(webDir, "runtime-config.json");
14527
15106
  try {
14528
- writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
15107
+ writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
14529
15108
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
14530
15109
  } catch (err) {
14531
15110
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -14545,7 +15124,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14545
15124
  if (standaloneServerPath) {
14546
15125
  command = "node";
14547
15126
  args = ["server.js"];
14548
- cwd = dirname8(standaloneServerPath);
15127
+ cwd = dirname10(standaloneServerPath);
14549
15128
  webEnv.PORT = String(actualPort);
14550
15129
  webEnv.HOSTNAME = "0.0.0.0";
14551
15130
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -14586,10 +15165,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14586
15165
  let started = false;
14587
15166
  let exited = false;
14588
15167
  let exitCode = null;
14589
- const startedPromise = new Promise((resolve12) => {
15168
+ const startedPromise = new Promise((resolve13) => {
14590
15169
  const timeout = setTimeout(() => {
14591
15170
  if (!started && !exited) {
14592
- resolve12(false);
15171
+ resolve13(false);
14593
15172
  }
14594
15173
  }, startupTimeout);
14595
15174
  child.stdout?.on("data", (data) => {
@@ -14603,7 +15182,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14603
15182
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
14604
15183
  started = true;
14605
15184
  clearTimeout(timeout);
14606
- resolve12(true);
15185
+ resolve13(true);
14607
15186
  }
14608
15187
  });
14609
15188
  child.stderr?.on("data", (data) => {
@@ -14615,14 +15194,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14615
15194
  child.on("error", (err) => {
14616
15195
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
14617
15196
  clearTimeout(timeout);
14618
- resolve12(false);
15197
+ resolve13(false);
14619
15198
  });
14620
15199
  child.on("exit", (code) => {
14621
15200
  exited = true;
14622
15201
  exitCode = code;
14623
15202
  if (!started) {
14624
15203
  clearTimeout(timeout);
14625
- resolve12(false);
15204
+ resolve13(false);
14626
15205
  }
14627
15206
  webUIProcess = null;
14628
15207
  });
@@ -14645,7 +15224,7 @@ function stopWebUI() {
14645
15224
  }
14646
15225
  }
14647
15226
  async function createApp(options = {}) {
14648
- const app = new Hono9();
15227
+ const app = new Hono10();
14649
15228
  app.use("*", cors({
14650
15229
  origin: "*",
14651
15230
  // Allow all origins
@@ -14673,6 +15252,7 @@ async function createApp(options = {}) {
14673
15252
  app.route("/api/schedules", schedulesRouter);
14674
15253
  app.route("/api/orchestrator", orchestratorRouter);
14675
15254
  app.route("/api/mcp", mcpRouter);
15255
+ app.route("/api/skills", skills);
14676
15256
  app.route("/api/webhooks", webhooksRouter);
14677
15257
  const config = getConfig();
14678
15258
  const webhookToken = config?.webhooks?.token;
@@ -14738,8 +15318,8 @@ async function startServer(options = {}) {
14738
15318
  if (options.workingDirectory) {
14739
15319
  config.resolvedWorkingDirectory = options.workingDirectory;
14740
15320
  }
14741
- if (!existsSync20(config.resolvedWorkingDirectory)) {
14742
- mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
15321
+ if (!existsSync22(config.resolvedWorkingDirectory)) {
15322
+ mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
14743
15323
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
14744
15324
  }
14745
15325
  if (!config.resolvedRemoteServer.url) {