sparkecoder 0.1.121 → 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 (102) hide show
  1. package/dist/agent/index.js +182 -21
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +483 -157
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +444 -118
  6. package/dist/index.js.map +1 -1
  7. package/dist/server/index.js +444 -118
  8. package/dist/server/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/web/.next/BUILD_ID +1 -1
  11. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  12. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  13. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  14. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  15. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  16. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  17. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  18. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  19. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  20. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  22. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  23. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  30. package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  39. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  66. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  74. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  82. package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  91. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  92. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  93. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  94. /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
  95. /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
  96. /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
  97. /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
  98. /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
  99. /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
  100. /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
  101. /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
  102. /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -2330,10 +2330,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2330
2330
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
2331
2331
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
2332
2332
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
2333
- const cachePath = join3(cacheDir, key2 + ext);
2334
- if (existsSync3(cachePath)) {
2333
+ const cachePath2 = join3(cacheDir, key2 + ext);
2334
+ if (existsSync3(cachePath2)) {
2335
2335
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
2336
- return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
2336
+ return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
2337
2337
  }
2338
2338
  let pipeline = sharp(buffer);
2339
2339
  if (needsResize) {
@@ -2358,7 +2358,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2358
2358
  }
2359
2359
  finalMediaType = "image/jpeg";
2360
2360
  }
2361
- writeFileSync2(cachePath, result);
2361
+ writeFileSync2(cachePath2, result);
2362
2362
  const resultMeta = await sharp(result).metadata();
2363
2363
  console.log(
2364
2364
  `[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
@@ -5415,7 +5415,7 @@ function isPathExcluded(relativePath, exclude) {
5415
5415
  }
5416
5416
  async function walkDirectory(dir, include, exclude, baseDir) {
5417
5417
  const { readdirSync: readdirSync4 } = await import("fs");
5418
- const { join: join18, relative: relative10 } = await import("path");
5418
+ const { join: join19, relative: relative10 } = await import("path");
5419
5419
  const files = [];
5420
5420
  function walk(currentDir) {
5421
5421
  let entries;
@@ -5425,7 +5425,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
5425
5425
  return;
5426
5426
  }
5427
5427
  for (const entry2 of entries) {
5428
- const fullPath = join18(currentDir, entry2.name);
5428
+ const fullPath = join19(currentDir, entry2.name);
5429
5429
  const relativePath = relative10(baseDir, fullPath);
5430
5430
  if (isPathExcluded(relativePath, exclude)) {
5431
5431
  continue;
@@ -8173,6 +8173,35 @@ function stripOrphanedToolResults(msg, removedIds) {
8173
8173
  if (parts.length === 0) return null;
8174
8174
  return { ...msg, content: parts };
8175
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
+ }
8176
8205
  function repairToolPairing(messages) {
8177
8206
  const toolCallIds = /* @__PURE__ */ new Set();
8178
8207
  const toolResultIds = /* @__PURE__ */ new Set();
@@ -8538,6 +8567,120 @@ var init_web = __esm({
8538
8567
  }
8539
8568
  });
8540
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
+
8541
8684
  // src/integrations/slack/client.ts
8542
8685
  function readSlackConfig() {
8543
8686
  try {
@@ -8660,13 +8803,13 @@ async function fetchSlackUserName(userId) {
8660
8803
  async function resolveSlackUserName(userId) {
8661
8804
  if (!userId) return null;
8662
8805
  const now = Date.now();
8663
- const hit = userNameCache.get(userId);
8806
+ const hit = getCachedUserName(userId);
8664
8807
  if (hit && hit.expiresAt > now) return hit.name;
8665
8808
  const inflight = userInflight.get(userId);
8666
8809
  if (inflight) return inflight;
8667
8810
  const p = (async () => {
8668
8811
  const name = await fetchSlackUserName(userId);
8669
- userNameCache.set(userId, {
8812
+ setCachedUserName(userId, {
8670
8813
  name,
8671
8814
  expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
8672
8815
  });
@@ -8688,11 +8831,63 @@ async function normalizeSlackMentions(text) {
8688
8831
  }
8689
8832
  return text.replace(userMentionRe, (_full, id, label) => {
8690
8833
  if (label) return `${label} <@${id}>`;
8691
- const cached = userNameCache.get(id);
8834
+ const cached = getCachedUserName(id);
8692
8835
  const name = cached?.name;
8693
8836
  return name ? `${name} <@${id}>` : `<@${id}>`;
8694
8837
  });
8695
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
+ }
8696
8891
  function getSlackDeniedReplyPolicy() {
8697
8892
  try {
8698
8893
  const cfg = getConfig();
@@ -8705,17 +8900,20 @@ function getSlackDeniedReplyPolicy() {
8705
8900
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
8706
8901
  }
8707
8902
  }
8708
- 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;
8709
8904
  var init_client3 = __esm({
8710
8905
  "src/integrations/slack/client.ts"() {
8711
8906
  "use strict";
8712
8907
  init_config();
8908
+ init_persistence();
8713
8909
  cachedSelf = null;
8714
8910
  selfInflight = null;
8715
8911
  USER_TTL_MS = 60 * 60 * 1e3;
8716
8912
  USER_FAIL_TTL_MS = 5 * 60 * 1e3;
8717
- userNameCache = /* @__PURE__ */ new Map();
8718
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();
8719
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.)";
8720
8918
  }
8721
8919
  });
@@ -8815,6 +9013,7 @@ var init_slack = __esm({
8815
9013
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
8816
9014
  if (r.slackChannel && r.threadTs) {
8817
9015
  markThreadOwned(r.slackChannel, r.threadTs);
9016
+ noteBotPostedInThread(r.slackChannel, r.threadTs);
8818
9017
  }
8819
9018
  },
8820
9019
  displayLabel(ref) {
@@ -9472,8 +9671,8 @@ var init_orchestrator_actions = __esm({
9472
9671
 
9473
9672
  // src/integrations/mcp/store.ts
9474
9673
  import { nanoid as nanoid6 } from "nanoid";
9475
- import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
9476
- 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";
9477
9676
  function readServers() {
9478
9677
  try {
9479
9678
  const cfg = getConfig();
@@ -9485,12 +9684,12 @@ function readServers() {
9485
9684
  function refreshMcpServersFromDisk() {
9486
9685
  const candidates = [
9487
9686
  resolve10(process.cwd(), "sparkecoder.config.json"),
9488
- join9(ensureAppDataDirectory(), "sparkecoder.config.json")
9687
+ join10(ensureAppDataDirectory(), "sparkecoder.config.json")
9489
9688
  ];
9490
9689
  for (const path of candidates) {
9491
- if (!existsSync16(path)) continue;
9690
+ if (!existsSync17(path)) continue;
9492
9691
  try {
9493
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
9692
+ const raw = JSON.parse(readFileSync8(path, "utf-8"));
9494
9693
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
9495
9694
  setMcpServers(servers2);
9496
9695
  return servers2;
@@ -10103,7 +10302,7 @@ __export(recorder_exports, {
10103
10302
  import { exec as exec5 } from "child_process";
10104
10303
  import { promisify as promisify5 } from "util";
10105
10304
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
10106
- import { join as join10 } from "path";
10305
+ import { join as join11 } from "path";
10107
10306
  import { tmpdir } from "os";
10108
10307
  import { nanoid as nanoid7 } from "nanoid";
10109
10308
  async function checkFfmpeg() {
@@ -10160,21 +10359,21 @@ var init_recorder = __esm({
10160
10359
  */
10161
10360
  async encode() {
10162
10361
  if (this.frames.length === 0) return null;
10163
- const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
10362
+ const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
10164
10363
  await mkdir4(workDir, { recursive: true });
10165
10364
  try {
10166
10365
  for (let i = 0; i < this.frames.length; i++) {
10167
- const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
10366
+ const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
10168
10367
  await writeFile5(framePath, this.frames[i].data);
10169
10368
  }
10170
10369
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
10171
10370
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
10172
10371
  const clampedFps = Math.max(1, Math.min(fps, 30));
10173
- const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
10372
+ const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
10174
10373
  const hasFfmpeg = await checkFfmpeg();
10175
10374
  if (hasFfmpeg) {
10176
10375
  await execAsync5(
10177
- `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}"`,
10178
10377
  { timeout: 12e4 }
10179
10378
  );
10180
10379
  } else {
@@ -10186,7 +10385,7 @@ var init_recorder = __esm({
10186
10385
  const files = await readdir5(workDir);
10187
10386
  for (const f of files) {
10188
10387
  if (f.startsWith("frame_")) {
10189
- await unlink2(join10(workDir, f)).catch(() => {
10388
+ await unlink2(join11(workDir, f)).catch(() => {
10190
10389
  });
10191
10390
  }
10192
10391
  }
@@ -10453,7 +10652,8 @@ ${personality.trim()}`;
10453
10652
  }
10454
10653
  const messages = await this.context.getMessages();
10455
10654
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
10456
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
10655
+ const approvalWrapped = this.wrapToolsWithApproval(options, tools);
10656
+ const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
10457
10657
  const useAnthropic = isAnthropicModel(this.session.model);
10458
10658
  const stream = streamText2({
10459
10659
  model: resolveModel(this.session.model),
@@ -10467,6 +10667,17 @@ ${personality.trim()}`;
10467
10667
  providerOptions: useAnthropic ? {
10468
10668
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
10469
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
+ },
10470
10681
  onStepFinish: async (step) => {
10471
10682
  options.onStepFinish?.(step);
10472
10683
  },
@@ -10502,7 +10713,7 @@ ${personality.trim()}`;
10502
10713
  });
10503
10714
  const messages = await this.context.getMessages();
10504
10715
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
10505
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
10716
+ const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
10506
10717
  const useAnthropic = isAnthropicModel(this.session.model);
10507
10718
  const result = await generateText3({
10508
10719
  model: resolveModel(this.session.model),
@@ -10513,7 +10724,13 @@ ${personality.trim()}`;
10513
10724
  // Enable extended thinking/reasoning for models that support it
10514
10725
  providerOptions: useAnthropic ? {
10515
10726
  anthropic: getAnthropicProviderOptions(this.session.model)
10516
- } : 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
+ }
10517
10734
  });
10518
10735
  const responseMessages = result.response.messages;
10519
10736
  this.context.addResponseMessages(responseMessages);
@@ -10690,12 +10907,19 @@ ${p.text}` : p.text;
10690
10907
  model: resolveModel(this.session.model),
10691
10908
  system: systemPrompt,
10692
10909
  messages,
10693
- tools: taskTools,
10910
+ tools: wrapToolsNeverThrow(taskTools),
10694
10911
  stopWhen: stepCountIs2(500),
10695
10912
  abortSignal: combinedAbort,
10696
10913
  providerOptions: useAnthropic ? {
10697
10914
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
10698
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
+ },
10699
10923
  onStepFinish: async (step) => {
10700
10924
  options.onStepFinish?.(step);
10701
10925
  fireWebhook("task.step_finished", { iteration, text: step.text });
@@ -10929,11 +11153,11 @@ ${p.text}` : p.text;
10929
11153
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10930
11154
  if (!isRemoteConfigured2()) return [];
10931
11155
  const { readFile: readFile13 } = await import("fs/promises");
10932
- const { join: join18, basename: basename7 } = await import("path");
11156
+ const { join: join19, basename: basename7 } = await import("path");
10933
11157
  const urls = [];
10934
11158
  for (const filePath of filePaths) {
10935
11159
  try {
10936
- const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
11160
+ const fullPath = filePath.startsWith("/") ? filePath : join19(this.session.workingDirectory, filePath);
10937
11161
  const fileName = basename7(fullPath);
10938
11162
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10939
11163
  const mimeMap = {
@@ -11138,19 +11362,19 @@ var init_session_lock = __esm({
11138
11362
  });
11139
11363
 
11140
11364
  // src/orchestrator/webhook-events.ts
11141
- import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
11142
- 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";
11143
11367
  import { nanoid as nanoid9 } from "nanoid";
11144
11368
  function logFilePath() {
11145
- return join11(getAppDataDirectory(), "webhook-events.jsonl");
11369
+ return join12(getAppDataDirectory(), "webhook-events.jsonl");
11146
11370
  }
11147
11371
  function ensureLoaded() {
11148
11372
  if (cache !== null) return cache;
11149
11373
  cache = [];
11150
11374
  try {
11151
11375
  const p = logFilePath();
11152
- if (!existsSync17(p)) return cache;
11153
- 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);
11154
11378
  for (const line of lines) {
11155
11379
  try {
11156
11380
  cache.push(JSON.parse(line));
@@ -11160,7 +11384,7 @@ function ensureLoaded() {
11160
11384
  if (cache.length > MAX_EVENTS) {
11161
11385
  cache = cache.slice(-MAX_EVENTS);
11162
11386
  try {
11163
- writeFileSync3(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
11387
+ writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
11164
11388
  } catch {
11165
11389
  }
11166
11390
  }
@@ -11174,7 +11398,7 @@ function appendEvent(ev) {
11174
11398
  if (list.length > MAX_EVENTS) list.shift();
11175
11399
  try {
11176
11400
  const p = logFilePath();
11177
- mkdirSync6(dirname6(p), { recursive: true });
11401
+ mkdirSync7(dirname7(p), { recursive: true });
11178
11402
  appendFileSync3(p, JSON.stringify(ev) + "\n");
11179
11403
  } catch {
11180
11404
  }
@@ -11205,8 +11429,8 @@ function updateEvent(id, patch) {
11205
11429
  list[i] = { ...list[i], ...patch };
11206
11430
  try {
11207
11431
  const p = logFilePath();
11208
- mkdirSync6(dirname6(p), { recursive: true });
11209
- 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");
11210
11434
  } catch {
11211
11435
  }
11212
11436
  }
@@ -11238,7 +11462,7 @@ function listEvents(filter = {}) {
11238
11462
  function clearAllEvents() {
11239
11463
  cache = [];
11240
11464
  try {
11241
- writeFileSync3(logFilePath(), "");
11465
+ writeFileSync4(logFilePath(), "");
11242
11466
  } catch {
11243
11467
  }
11244
11468
  }
@@ -11728,7 +11952,7 @@ import chalk from "chalk";
11728
11952
  import ora from "ora";
11729
11953
  import "dotenv/config";
11730
11954
  import { createInterface } from "readline";
11731
- import { dirname as dirname10 } from "path";
11955
+ import { dirname as dirname11 } from "path";
11732
11956
  import { fileURLToPath as fileURLToPath5 } from "url";
11733
11957
 
11734
11958
  // src/server/index.ts
@@ -11737,8 +11961,8 @@ import { Hono as Hono10 } from "hono";
11737
11961
  import { serve } from "@hono/node-server";
11738
11962
  import { cors } from "hono/cors";
11739
11963
  import { logger } from "hono/logger";
11740
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
11741
- import { resolve as resolve12, dirname as dirname9, join as join16 } 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";
11742
11966
  import { spawn as spawn2 } from "child_process";
11743
11967
  import { createServer as createNetServer } from "net";
11744
11968
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -11752,9 +11976,9 @@ init_checkpoints();
11752
11976
  import { Hono } from "hono";
11753
11977
  import { zValidator } from "@hono/zod-validator";
11754
11978
  import { z as z16 } from "zod";
11755
- 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";
11756
11980
  import { readdir as readdir6 } from "fs/promises";
11757
- 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";
11758
11982
  import { nanoid as nanoid10 } from "nanoid";
11759
11983
 
11760
11984
  // src/tasks/agent-status.ts
@@ -12392,12 +12616,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
12392
12616
  });
12393
12617
  function getAttachmentsDir(sessionId) {
12394
12618
  const appDataDir = getAppDataDirectory();
12395
- return join12(appDataDir, "attachments", sessionId);
12619
+ return join13(appDataDir, "attachments", sessionId);
12396
12620
  }
12397
12621
  function ensureAttachmentsDir(sessionId) {
12398
12622
  const dir = getAttachmentsDir(sessionId);
12399
- if (!existsSync18(dir)) {
12400
- mkdirSync7(dir, { recursive: true });
12623
+ if (!existsSync19(dir)) {
12624
+ mkdirSync8(dir, { recursive: true });
12401
12625
  }
12402
12626
  return dir;
12403
12627
  }
@@ -12408,12 +12632,12 @@ sessions2.get("/:id/attachments", async (c) => {
12408
12632
  return c.json({ error: "Session not found" }, 404);
12409
12633
  }
12410
12634
  const dir = getAttachmentsDir(sessionId);
12411
- if (!existsSync18(dir)) {
12635
+ if (!existsSync19(dir)) {
12412
12636
  return c.json({ sessionId, attachments: [], count: 0 });
12413
12637
  }
12414
12638
  const files = readdirSync3(dir);
12415
12639
  const attachments = files.map((filename) => {
12416
- const filePath = join12(dir, filename);
12640
+ const filePath = join13(dir, filename);
12417
12641
  const stats = statSync2(filePath);
12418
12642
  return {
12419
12643
  id: filename.split("_")[0],
@@ -12448,9 +12672,9 @@ sessions2.post("/:id/attachments", async (c) => {
12448
12672
  const id = nanoid10(10);
12449
12673
  const ext = extname8(file.name) || "";
12450
12674
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
12451
- const filePath = join12(dir, safeFilename);
12675
+ const filePath = join13(dir, safeFilename);
12452
12676
  const arrayBuffer = await file.arrayBuffer();
12453
- writeFileSync4(filePath, Buffer.from(arrayBuffer));
12677
+ writeFileSync5(filePath, Buffer.from(arrayBuffer));
12454
12678
  return c.json({
12455
12679
  id,
12456
12680
  filename: file.name,
@@ -12474,13 +12698,13 @@ sessions2.post("/:id/attachments", async (c) => {
12474
12698
  const id = nanoid10(10);
12475
12699
  const ext = extname8(body.filename) || "";
12476
12700
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
12477
- const filePath = join12(dir, safeFilename);
12701
+ const filePath = join13(dir, safeFilename);
12478
12702
  let base64Data = body.data;
12479
12703
  if (base64Data.includes(",")) {
12480
12704
  base64Data = base64Data.split(",")[1];
12481
12705
  }
12482
12706
  const buffer = Buffer.from(base64Data, "base64");
12483
- writeFileSync4(filePath, buffer);
12707
+ writeFileSync5(filePath, buffer);
12484
12708
  return c.json({
12485
12709
  id,
12486
12710
  filename: body.filename,
@@ -12503,7 +12727,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
12503
12727
  return c.json({ error: "Session not found" }, 404);
12504
12728
  }
12505
12729
  const dir = getAttachmentsDir(sessionId);
12506
- if (!existsSync18(dir)) {
12730
+ if (!existsSync19(dir)) {
12507
12731
  return c.json({ error: "Attachment not found" }, 404);
12508
12732
  }
12509
12733
  const files = readdirSync3(dir);
@@ -12511,7 +12735,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
12511
12735
  if (!file) {
12512
12736
  return c.json({ error: "Attachment not found" }, 404);
12513
12737
  }
12514
- const filePath = join12(dir, file);
12738
+ const filePath = join13(dir, file);
12515
12739
  unlinkSync2(filePath);
12516
12740
  return c.json({ success: true, id: attachmentId });
12517
12741
  });
@@ -12594,7 +12818,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
12594
12818
  const entries = await readdir6(currentDir, { withFileTypes: true });
12595
12819
  for (const entry2 of entries) {
12596
12820
  if (results.length >= limit * 2) break;
12597
- const fullPath = join12(currentDir, entry2.name);
12821
+ const fullPath = join13(currentDir, entry2.name);
12598
12822
  const relativePath = relative9(baseDir, fullPath);
12599
12823
  if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
12600
12824
  continue;
@@ -12642,7 +12866,7 @@ sessions2.get(
12642
12866
  return c.json({ error: "Session not found" }, 404);
12643
12867
  }
12644
12868
  const workingDirectory = session.workingDirectory;
12645
- if (!existsSync18(workingDirectory)) {
12869
+ if (!existsSync19(workingDirectory)) {
12646
12870
  return c.json({
12647
12871
  sessionId,
12648
12872
  workingDirectory,
@@ -12750,14 +12974,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
12750
12974
 
12751
12975
  // src/server/routes/agents.ts
12752
12976
  init_db();
12753
- init_agent();
12754
- init_session_lock();
12755
- init_config();
12756
12977
  import { Hono as Hono2 } from "hono";
12757
12978
  import { zValidator as zValidator2 } from "@hono/zod-validator";
12758
12979
  import { z as z17 } from "zod";
12759
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
12760
- 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();
12761
13072
 
12762
13073
  // src/server/resumable-stream.ts
12763
13074
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -12964,12 +13275,12 @@ var rejectSchema = z17.object({
12964
13275
  var streamAbortControllers = /* @__PURE__ */ new Map();
12965
13276
  function getAttachmentsDirectory(sessionId) {
12966
13277
  const appDataDir = getAppDataDirectory();
12967
- return join13(appDataDir, "attachments", sessionId);
13278
+ return join14(appDataDir, "attachments", sessionId);
12968
13279
  }
12969
13280
  async function saveAttachmentToDisk(sessionId, attachment, index) {
12970
13281
  const attachmentsDir = getAttachmentsDirectory(sessionId);
12971
- if (!existsSync19(attachmentsDir)) {
12972
- mkdirSync8(attachmentsDir, { recursive: true });
13282
+ if (!existsSync20(attachmentsDir)) {
13283
+ mkdirSync9(attachmentsDir, { recursive: true });
12973
13284
  }
12974
13285
  let filename = attachment.filename;
12975
13286
  if (!filename) {
@@ -12987,8 +13298,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
12987
13298
  attachment.mediaType = resized.mediaType;
12988
13299
  attachment.data = buffer.toString("base64");
12989
13300
  }
12990
- const filePath = join13(attachmentsDir, filename);
12991
- writeFileSync5(filePath, buffer);
13301
+ const filePath = join14(attachmentsDir, filename);
13302
+ writeFileSync6(filePath, buffer);
12992
13303
  return filePath;
12993
13304
  }
12994
13305
  function stripDataUrlPrefix2(data) {
@@ -13325,7 +13636,20 @@ ${prompt}` });
13325
13636
  await writeSSE(JSON.stringify({ type: "abort" }));
13326
13637
  } else {
13327
13638
  console.error("Agent error:", error);
13328
- 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
+ }));
13329
13653
  try {
13330
13654
  await activeStreamQueries.markError(streamId);
13331
13655
  } catch {
@@ -13865,7 +14189,20 @@ agents.post(
13865
14189
  await writeSSE(JSON.stringify({ type: "abort" }));
13866
14190
  } else {
13867
14191
  console.error("Agent error:", error);
13868
- 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
+ }));
13869
14206
  await activeStreamQueries.markError(streamId);
13870
14207
  }
13871
14208
  } finally {
@@ -13948,26 +14285,26 @@ init_config();
13948
14285
  import { Hono as Hono3 } from "hono";
13949
14286
  import { zValidator as zValidator3 } from "@hono/zod-validator";
13950
14287
  import { z as z18 } from "zod";
13951
- import { readFileSync as readFileSync9 } from "fs";
14288
+ import { readFileSync as readFileSync10 } from "fs";
13952
14289
  import { fileURLToPath as fileURLToPath3 } from "url";
13953
- import { dirname as dirname7, join as join14 } from "path";
14290
+ import { dirname as dirname8, join as join15 } from "path";
13954
14291
  var __filename = fileURLToPath3(import.meta.url);
13955
- var __dirname = dirname7(__filename);
14292
+ var __dirname = dirname8(__filename);
13956
14293
  var possiblePaths = [
13957
- join14(__dirname, "../package.json"),
14294
+ join15(__dirname, "../package.json"),
13958
14295
  // From dist/server -> dist/../package.json
13959
- join14(__dirname, "../../package.json"),
14296
+ join15(__dirname, "../../package.json"),
13960
14297
  // From dist/server (if nested differently)
13961
- join14(__dirname, "../../../package.json"),
14298
+ join15(__dirname, "../../../package.json"),
13962
14299
  // From src/server/routes (development)
13963
- join14(process.cwd(), "package.json")
14300
+ join15(process.cwd(), "package.json")
13964
14301
  // From current working directory
13965
14302
  ];
13966
14303
  var currentVersion = "0.0.0";
13967
14304
  var packageName = "sparkecoder";
13968
14305
  for (const packageJsonPath of possiblePaths) {
13969
14306
  try {
13970
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
14307
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
13971
14308
  if (packageJson.name === "sparkecoder") {
13972
14309
  currentVersion = packageJson.version || "0.0.0";
13973
14310
  packageName = packageJson.name || "sparkecoder";
@@ -14785,12 +15122,13 @@ slack.post("/events", async (c) => {
14785
15122
  if (inbound) {
14786
15123
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
14787
15124
  if (isThreadReply) {
14788
- 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);
14789
15126
  if (!ours) {
14790
15127
  console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
14791
15128
  updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
14792
15129
  return c.json({ ok: true });
14793
15130
  }
15131
+ markThreadOwned(ev.channel, ev.thread_ts);
14794
15132
  }
14795
15133
  if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
14796
15134
  markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
@@ -14829,18 +15167,6 @@ slack.post("/events", async (c) => {
14829
15167
  }
14830
15168
  return c.json({ ok: true });
14831
15169
  });
14832
- async function threadBelongsToUs(channel, threadTs) {
14833
- try {
14834
- const sessions3 = await sessionQueries.list(500, 0);
14835
- return sessions3.some((s) => {
14836
- const slack2 = s.config?.slack;
14837
- return slack2?.channel === channel && slack2?.threadTs === threadTs;
14838
- });
14839
- } catch (err) {
14840
- console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
14841
- return false;
14842
- }
14843
- }
14844
15170
  async function findOrCreateOrchestratorId() {
14845
15171
  try {
14846
15172
  const all = await sessionQueries.list(500, 0);
@@ -15229,9 +15555,9 @@ init_skills();
15229
15555
  import { Hono as Hono9 } from "hono";
15230
15556
  import { zValidator as zValidator7 } from "@hono/zod-validator";
15231
15557
  import { z as z22 } from "zod";
15232
- import { existsSync as existsSync20, statSync as statSync3 } from "fs";
15558
+ import { existsSync as existsSync21, statSync as statSync3 } from "fs";
15233
15559
  import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
15234
- import { resolve as resolve11, join as join15, basename as basename6, dirname as dirname8, extname as extname9 } from "path";
15560
+ import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
15235
15561
  var skills = new Hono9();
15236
15562
  function encodeId(filePath) {
15237
15563
  return Buffer.from(filePath, "utf-8").toString("base64url");
@@ -15290,13 +15616,13 @@ function pathToLabel(path) {
15290
15616
  if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
15291
15617
  if (path.includes("/.cursor/rules")) return ".cursor/rules";
15292
15618
  if (path.includes("/.claude/skills")) return ".claude/skills";
15293
- return basename6(dirname8(path)) + "/" + basename6(path);
15619
+ return basename6(dirname9(path)) + "/" + basename6(path);
15294
15620
  }
15295
15621
  skills.get("/", async (c) => {
15296
15622
  const dirs = listAllDirectories();
15297
15623
  const allSkills = [];
15298
15624
  for (const dir of dirs) {
15299
- if (!existsSync20(dir.path)) continue;
15625
+ if (!existsSync21(dir.path)) continue;
15300
15626
  try {
15301
15627
  const list = await loadSkillsFromDirectory(dir.path, {
15302
15628
  priority: dir.priority,
@@ -15333,7 +15659,7 @@ skills.get("/", async (c) => {
15333
15659
  label: d.label,
15334
15660
  source: d.source,
15335
15661
  alwaysApply: d.alwaysApply,
15336
- exists: existsSync20(d.path),
15662
+ exists: existsSync21(d.path),
15337
15663
  writable: isWritable(d.path)
15338
15664
  })),
15339
15665
  skills: allSkills
@@ -15341,7 +15667,7 @@ skills.get("/", async (c) => {
15341
15667
  });
15342
15668
  function isWritable(dir) {
15343
15669
  try {
15344
- if (!existsSync20(dir)) return false;
15670
+ if (!existsSync21(dir)) return false;
15345
15671
  if (dir.includes("/skills/default")) return false;
15346
15672
  return true;
15347
15673
  } catch {
@@ -15350,7 +15676,7 @@ function isWritable(dir) {
15350
15676
  }
15351
15677
  skills.get("/:id", async (c) => {
15352
15678
  const filePath = decodeId(c.req.param("id"));
15353
- if (!filePath || !existsSync20(filePath)) {
15679
+ if (!filePath || !existsSync21(filePath)) {
15354
15680
  return c.json({ error: "skill not found" }, 404);
15355
15681
  }
15356
15682
  const content = await readFile12(filePath, "utf-8");
@@ -15379,8 +15705,8 @@ skills.post(
15379
15705
  const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
15380
15706
  const ext = extname9(safeName).toLowerCase();
15381
15707
  const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
15382
- const filePath = join15(targetDir, finalName);
15383
- if (existsSync20(filePath)) {
15708
+ const filePath = join16(targetDir, finalName);
15709
+ if (existsSync21(filePath)) {
15384
15710
  return c.json({ error: `file already exists: ${finalName}` }, 409);
15385
15711
  }
15386
15712
  try {
@@ -15397,7 +15723,7 @@ skills.put(
15397
15723
  zValidator7("json", z22.object({ content: z22.string() })),
15398
15724
  async (c) => {
15399
15725
  const filePath = decodeId(c.req.param("id"));
15400
- if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
15726
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
15401
15727
  if (filePath.includes("/skills/default")) {
15402
15728
  return c.json({ error: "built-in skills are read-only" }, 400);
15403
15729
  }
@@ -15407,7 +15733,7 @@ skills.put(
15407
15733
  );
15408
15734
  skills.delete("/:id", async (c) => {
15409
15735
  const filePath = decodeId(c.req.param("id"));
15410
- if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
15736
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
15411
15737
  if (filePath.includes("/skills/default")) {
15412
15738
  return c.json({ error: "built-in skills are read-only" }, 400);
15413
15739
  }
@@ -15427,7 +15753,7 @@ skills.post(
15427
15753
  }
15428
15754
  const next = [...current, abs];
15429
15755
  setSkillsAdditionalDirectories(next);
15430
- return c.json({ ok: true, path: abs, exists: existsSync20(abs) }, 201);
15756
+ return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
15431
15757
  }
15432
15758
  );
15433
15759
  skills.delete("/directories", (c) => {
@@ -15697,13 +16023,13 @@ var DEFAULT_WEB_PORT = 6969;
15697
16023
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
15698
16024
  function getWebDirectory() {
15699
16025
  try {
15700
- const currentDir = dirname9(fileURLToPath4(import.meta.url));
16026
+ const currentDir = dirname10(fileURLToPath4(import.meta.url));
15701
16027
  const webDir = resolve12(currentDir, "..", "web");
15702
- if (existsSync21(webDir) && existsSync21(join16(webDir, "package.json"))) {
16028
+ if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
15703
16029
  return webDir;
15704
16030
  }
15705
16031
  const altWebDir = resolve12(currentDir, "..", "..", "web");
15706
- if (existsSync21(altWebDir) && existsSync21(join16(altWebDir, "package.json"))) {
16032
+ if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
15707
16033
  return altWebDir;
15708
16034
  }
15709
16035
  return null;
@@ -15761,23 +16087,23 @@ async function findWebPort(preferredPort) {
15761
16087
  return { port: preferredPort, alreadyRunning: false };
15762
16088
  }
15763
16089
  function hasProductionBuild(webDir) {
15764
- const buildIdPath = join16(webDir, ".next", "BUILD_ID");
15765
- return existsSync21(buildIdPath);
16090
+ const buildIdPath = join17(webDir, ".next", "BUILD_ID");
16091
+ return existsSync22(buildIdPath);
15766
16092
  }
15767
16093
  function hasSourceFiles(webDir) {
15768
- const appDir = join16(webDir, "src", "app");
15769
- const pagesDir = join16(webDir, "src", "pages");
15770
- const rootAppDir = join16(webDir, "app");
15771
- const rootPagesDir = join16(webDir, "pages");
15772
- return existsSync21(appDir) || existsSync21(pagesDir) || existsSync21(rootAppDir) || existsSync21(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);
15773
16099
  }
15774
16100
  function getStandaloneServerPath(webDir) {
15775
16101
  const possiblePaths2 = [
15776
- join16(webDir, ".next", "standalone", "server.js"),
15777
- join16(webDir, ".next", "standalone", "web", "server.js")
16102
+ join17(webDir, ".next", "standalone", "server.js"),
16103
+ join17(webDir, ".next", "standalone", "web", "server.js")
15778
16104
  ];
15779
16105
  for (const serverPath of possiblePaths2) {
15780
- if (existsSync21(serverPath)) {
16106
+ if (existsSync22(serverPath)) {
15781
16107
  return serverPath;
15782
16108
  }
15783
16109
  }
@@ -15817,15 +16143,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15817
16143
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
15818
16144
  return { process: null, port: actualPort };
15819
16145
  }
15820
- const usePnpm = existsSync21(join16(webDir, "pnpm-lock.yaml"));
15821
- const useNpm = !usePnpm && existsSync21(join16(webDir, "package-lock.json"));
16146
+ const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
16147
+ const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
15822
16148
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
15823
16149
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
15824
16150
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
15825
16151
  const runtimeConfig = { apiBaseUrl: apiUrl };
15826
- const runtimeConfigPath = join16(webDir, "runtime-config.json");
16152
+ const runtimeConfigPath = join17(webDir, "runtime-config.json");
15827
16153
  try {
15828
- writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
16154
+ writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
15829
16155
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
15830
16156
  } catch (err) {
15831
16157
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -15845,7 +16171,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
15845
16171
  if (standaloneServerPath) {
15846
16172
  command = "node";
15847
16173
  args = ["server.js"];
15848
- cwd = dirname9(standaloneServerPath);
16174
+ cwd = dirname10(standaloneServerPath);
15849
16175
  webEnv.PORT = String(actualPort);
15850
16176
  webEnv.HOSTNAME = "0.0.0.0";
15851
16177
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -16039,8 +16365,8 @@ async function startServer(options = {}) {
16039
16365
  if (options.workingDirectory) {
16040
16366
  config.resolvedWorkingDirectory = options.workingDirectory;
16041
16367
  }
16042
- if (!existsSync21(config.resolvedWorkingDirectory)) {
16043
- mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
16368
+ if (!existsSync22(config.resolvedWorkingDirectory)) {
16369
+ mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
16044
16370
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
16045
16371
  }
16046
16372
  if (!config.resolvedRemoteServer.url) {
@@ -16598,18 +16924,18 @@ function generateOpenAPISpec() {
16598
16924
  init_config();
16599
16925
  init_semantic();
16600
16926
  init_db();
16601
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7, readFileSync as readFileSync10, existsSync as existsSync22, statSync as statSync4 } from "fs";
16602
- import { resolve as resolve13, join as join17 } 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";
16603
16929
  function getCliVersion() {
16604
- const here = dirname10(fileURLToPath5(import.meta.url));
16930
+ const here = dirname11(fileURLToPath5(import.meta.url));
16605
16931
  const candidates = [
16606
- join17(here, "..", "package.json"),
16607
- join17(here, "..", "..", "package.json"),
16608
- join17(process.cwd(), "package.json")
16932
+ join18(here, "..", "package.json"),
16933
+ join18(here, "..", "..", "package.json"),
16934
+ join18(process.cwd(), "package.json")
16609
16935
  ];
16610
16936
  for (const p of candidates) {
16611
16937
  try {
16612
- const pkg = JSON.parse(readFileSync10(p, "utf8"));
16938
+ const pkg = JSON.parse(readFileSync11(p, "utf8"));
16613
16939
  if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
16614
16940
  } catch {
16615
16941
  }
@@ -17256,8 +17582,8 @@ program.command("task").description("Run an autonomous task that completes witho
17256
17582
  let outputSchema;
17257
17583
  try {
17258
17584
  const schemaStr = options.schema;
17259
- if (existsSync22(schemaStr)) {
17260
- outputSchema = JSON.parse(readFileSync10(schemaStr, "utf-8"));
17585
+ if (existsSync23(schemaStr)) {
17586
+ outputSchema = JSON.parse(readFileSync11(schemaStr, "utf-8"));
17261
17587
  } else {
17262
17588
  outputSchema = JSON.parse(schemaStr);
17263
17589
  }
@@ -17324,19 +17650,19 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
17324
17650
  let configLocation;
17325
17651
  if (options.global) {
17326
17652
  const appDataDir = ensureAppDataDirectory();
17327
- configPath = join17(appDataDir, "sparkecoder.config.json");
17653
+ configPath = join18(appDataDir, "sparkecoder.config.json");
17328
17654
  configLocation = "global";
17329
17655
  } else {
17330
17656
  configPath = resolve13(process.cwd(), "sparkecoder.config.json");
17331
17657
  configLocation = "local";
17332
17658
  }
17333
- if (existsSync22(configPath) && !options.force) {
17659
+ if (existsSync23(configPath) && !options.force) {
17334
17660
  console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
17335
17661
  console.log(chalk.dim(` ${configPath}`));
17336
17662
  return;
17337
17663
  }
17338
17664
  const config = createDefaultConfig();
17339
- writeFileSync7(configPath, JSON.stringify(config, null, 2));
17665
+ writeFileSync8(configPath, JSON.stringify(config, null, 2));
17340
17666
  console.log(chalk.green(`\u2713 Created ${configLocation} config`));
17341
17667
  console.log(chalk.dim(` ${configPath}`));
17342
17668
  console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
@@ -17355,11 +17681,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
17355
17681
  console.error(chalk.red("Both bot token and signing secret are required."));
17356
17682
  process.exit(1);
17357
17683
  }
17358
- const configPath = options.global ? join17(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve13(process.cwd(), "sparkecoder.config.json");
17684
+ const configPath = options.global ? join18(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve13(process.cwd(), "sparkecoder.config.json");
17359
17685
  let existing = {};
17360
- if (existsSync22(configPath)) {
17686
+ if (existsSync23(configPath)) {
17361
17687
  try {
17362
- existing = JSON.parse(readFileSync10(configPath, "utf-8"));
17688
+ existing = JSON.parse(readFileSync11(configPath, "utf-8"));
17363
17689
  } catch {
17364
17690
  }
17365
17691
  } else {
@@ -17371,7 +17697,7 @@ program.command("slack-setup").description("Interactively configure Slack integr
17371
17697
  signingSecret,
17372
17698
  defaultOrchestratorName: existing.slack?.defaultOrchestratorName ?? "orchestrator"
17373
17699
  };
17374
- writeFileSync7(configPath, JSON.stringify(existing, null, 2));
17700
+ writeFileSync8(configPath, JSON.stringify(existing, null, 2));
17375
17701
  console.log(chalk.green(`
17376
17702
  \u2713 Slack configured`));
17377
17703
  console.log(chalk.dim(` ${configPath}`));
@@ -17623,9 +17949,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17623
17949
  }
17624
17950
  const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
17625
17951
  console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
17626
- const cfDir = join17(homedir2(), ".cloudflared");
17627
- const certPath = join17(cfDir, "cert.pem");
17628
- if (!existsSync22(certPath)) {
17952
+ const cfDir = join18(homedir2(), ".cloudflared");
17953
+ const certPath = join18(cfDir, "cert.pem");
17954
+ if (!existsSync23(certPath)) {
17629
17955
  console.log(chalk.yellow("No Cloudflare login cert found."));
17630
17956
  if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
17631
17957
  run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
@@ -17669,8 +17995,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17669
17995
  return;
17670
17996
  }
17671
17997
  }
17672
- const credsFile = tunnel.credentials_file || join17(cfDir, `${tunnel.id}.json`);
17673
- if (!existsSync22(credsFile)) {
17998
+ const credsFile = tunnel.credentials_file || join18(cfDir, `${tunnel.id}.json`);
17999
+ if (!existsSync23(credsFile)) {
17674
18000
  console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
17675
18001
  }
17676
18002
  let hostname = options.hostname;
@@ -17693,7 +18019,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
17693
18019
  console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
17694
18020
  }
17695
18021
  }
17696
- const configPath = join17(cfDir, "config.yml");
18022
+ const configPath = join18(cfDir, "config.yml");
17697
18023
  const configBody = `tunnel: ${tunnel.id}
17698
18024
  credentials-file: ${credsFile}
17699
18025
  ingress:
@@ -17704,14 +18030,14 @@ ingress:
17704
18030
  - service: http_status:404
17705
18031
  `;
17706
18032
  let wroteConfig = false;
17707
- if (existsSync22(configPath)) {
17708
- const existing = readFileSync10(configPath, "utf8");
18033
+ if (existsSync23(configPath)) {
18034
+ const existing = readFileSync11(configPath, "utf8");
17709
18035
  if (existing.trim() === configBody.trim()) {
17710
18036
  console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
17711
18037
  wroteConfig = true;
17712
18038
  } else if (await confirm(`A different ${configPath} exists. Overwrite (a backup will be saved)?`, false)) {
17713
18039
  copyFileSync(configPath, `${configPath}.bak.${Date.now()}`);
17714
- writeFileSync7(configPath, configBody);
18040
+ writeFileSync8(configPath, configBody);
17715
18041
  console.log(chalk.green("\u2713"), `wrote ${configPath} (previous saved as .bak.*)`);
17716
18042
  wroteConfig = true;
17717
18043
  } else {
@@ -17719,7 +18045,7 @@ ingress:
17719
18045
  console.log(chalk.cyan(configBody));
17720
18046
  }
17721
18047
  } else {
17722
- writeFileSync7(configPath, configBody);
18048
+ writeFileSync8(configPath, configBody);
17723
18049
  console.log(chalk.green("\u2713"), `wrote ${configPath}`);
17724
18050
  wroteConfig = true;
17725
18051
  }
@@ -18209,17 +18535,17 @@ program.command("request-permissions").description("Open System Settings to the
18209
18535
  });
18210
18536
  {
18211
18537
  let stateFilePath = function() {
18212
- return join17(ensureAppDataDirectory(), "recordings.json");
18538
+ return join18(ensureAppDataDirectory(), "recordings.json");
18213
18539
  }, readState = function() {
18214
18540
  const p = stateFilePath();
18215
- if (!existsSync22(p)) return [];
18541
+ if (!existsSync23(p)) return [];
18216
18542
  try {
18217
- return JSON.parse(readFileSync10(p, "utf-8"));
18543
+ return JSON.parse(readFileSync11(p, "utf-8"));
18218
18544
  } catch {
18219
18545
  return [];
18220
18546
  }
18221
18547
  }, writeState = function(rows) {
18222
- writeFileSync7(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
18548
+ writeFileSync8(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
18223
18549
  }, isAlive = function(pid) {
18224
18550
  try {
18225
18551
  process.kill(pid, 0);
@@ -18234,16 +18560,16 @@ program.command("request-permissions").description("Open System Settings to the
18234
18560
  const record = program.command("record").description("Start/stop screen recordings");
18235
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) => {
18236
18562
  const { homedir: homedir2, platform: osPlatform } = await import("os");
18237
- const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join17(homedir2(), "recordings");
18238
- mkdirSync10(outDir, { recursive: true });
18563
+ const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join18(homedir2(), "recordings");
18564
+ mkdirSync11(outDir, { recursive: true });
18239
18565
  const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
18240
18566
  const ext = osPlatform() === "darwin" ? "mov" : "mp4";
18241
18567
  const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
18242
- const path = join17(outDir, filename);
18568
+ const path = join18(outDir, filename);
18243
18569
  let cmd;
18244
18570
  let args;
18245
18571
  if (osPlatform() === "darwin") {
18246
- cmd = existsSync22("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
18572
+ cmd = existsSync23("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
18247
18573
  args = ["-v", "-C", "-k", path];
18248
18574
  } else if (osPlatform() === "linux") {
18249
18575
  const display = process.env.DISPLAY || ":0.0";
@@ -18346,7 +18672,7 @@ program.command("request-permissions").description("Open System Settings to the
18346
18672
  }
18347
18673
  }
18348
18674
  writeState(rows.filter((r) => r.id !== id));
18349
- const fileExists = existsSync22(row.path);
18675
+ const fileExists = existsSync23(row.path);
18350
18676
  const sizeMb = fileExists ? Math.round(statSync4(row.path).size / (1024 * 1024) * 10) / 10 : 0;
18351
18677
  console.log(JSON.stringify({
18352
18678
  id,
@@ -18381,7 +18707,7 @@ program.command("request-permissions").description("Open System Settings to the
18381
18707
  }
18382
18708
  }
18383
18709
  }
18384
- stopped.push({ id: r.id, path: r.path, ok: existsSync22(r.path) });
18710
+ stopped.push({ id: r.id, path: r.path, ok: existsSync23(r.path) });
18385
18711
  }
18386
18712
  writeState([]);
18387
18713
  console.log(JSON.stringify({ stopped }));