sparkecoder 0.1.121 → 0.1.123

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 (109) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +240 -26
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +588 -162
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-DczYH89U.d.ts → index-Bcz0aCAR.d.ts} +104 -104
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.js +549 -123
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-DxrKyetI.d.ts → schema-BWbWmfDQ.d.ts} +3 -3
  12. package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
  13. package/dist/server/index.js +549 -123
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/tools/index.d.ts +3 -3
  16. package/package.json +1 -1
  17. package/web/.next/BUILD_ID +1 -1
  18. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  20. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  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.html +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  37. package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  89. package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
  91. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  94. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  95. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  96. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  97. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  98. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  99. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  100. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  101. /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_buildManifest.js +0 -0
  102. /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_clientMiddlewareManifest.json +0 -0
  103. /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_ssgManifest.js +0 -0
  104. /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/MP4p8_EldjbZ69dONoEcM}/_buildManifest.js +0 -0
  105. /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/MP4p8_EldjbZ69dONoEcM}/_clientMiddlewareManifest.json +0 -0
  106. /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/MP4p8_EldjbZ69dONoEcM}/_ssgManifest.js +0 -0
  107. /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_buildManifest.js +0 -0
  108. /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_clientMiddlewareManifest.json +0 -0
  109. /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_ssgManifest.js +0 -0
@@ -2329,10 +2329,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2329
2329
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
2330
2330
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
2331
2331
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
2332
- const cachePath = join3(cacheDir, key2 + ext);
2333
- if (existsSync3(cachePath)) {
2332
+ const cachePath2 = join3(cacheDir, key2 + ext);
2333
+ if (existsSync3(cachePath2)) {
2334
2334
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
2335
- return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
2335
+ return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
2336
2336
  }
2337
2337
  let pipeline = sharp(buffer);
2338
2338
  if (needsResize) {
@@ -2357,7 +2357,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2357
2357
  }
2358
2358
  finalMediaType = "image/jpeg";
2359
2359
  }
2360
- writeFileSync2(cachePath, result);
2360
+ writeFileSync2(cachePath2, result);
2361
2361
  const resultMeta = await sharp(result).metadata();
2362
2362
  console.log(
2363
2363
  `[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
@@ -7430,6 +7430,84 @@ function stripOrphanedToolResults(msg, removedIds) {
7430
7430
  if (parts.length === 0) return null;
7431
7431
  return { ...msg, content: parts };
7432
7432
  }
7433
+ function wrapToolsNeverThrow(tools) {
7434
+ if (!tools || typeof tools !== "object") return tools;
7435
+ const wrapped = {};
7436
+ for (const [name, t] of Object.entries(tools)) {
7437
+ if (!t || typeof t.execute !== "function") {
7438
+ wrapped[name] = t;
7439
+ continue;
7440
+ }
7441
+ const original = t.execute;
7442
+ wrapped[name] = {
7443
+ ...t,
7444
+ execute: async (input, opts) => {
7445
+ try {
7446
+ return await original.call(t, input, opts);
7447
+ } catch (err) {
7448
+ const message = err?.message ?? String(err);
7449
+ console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
7450
+ return {
7451
+ __error: true,
7452
+ tool: name,
7453
+ message,
7454
+ note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
7455
+ };
7456
+ }
7457
+ }
7458
+ };
7459
+ }
7460
+ return wrapped;
7461
+ }
7462
+ function ensureToolResultsFollowCalls(messages) {
7463
+ if (!Array.isArray(messages) || messages.length < 3) return messages;
7464
+ let mutated = false;
7465
+ const result = messages.slice();
7466
+ let i = 0;
7467
+ while (i < result.length) {
7468
+ const msg = result[i];
7469
+ if (msg?.role !== "assistant" || !Array.isArray(msg.content)) {
7470
+ i++;
7471
+ continue;
7472
+ }
7473
+ const callIds = /* @__PURE__ */ new Set();
7474
+ for (const part of msg.content) {
7475
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
7476
+ callIds.add(part.toolCallId);
7477
+ }
7478
+ }
7479
+ if (callIds.size === 0) {
7480
+ i++;
7481
+ continue;
7482
+ }
7483
+ let toolIdx = -1;
7484
+ for (let j = i + 1; j < result.length; j++) {
7485
+ const m = result[j];
7486
+ if (m?.role === "assistant" && Array.isArray(m.content) && m.content.some((p) => p?.type === "tool-call")) {
7487
+ break;
7488
+ }
7489
+ if (m?.role === "tool" && Array.isArray(m.content)) {
7490
+ const answersOne = m.content.some(
7491
+ (p) => p?.type === "tool-result" && typeof p.toolCallId === "string" && callIds.has(p.toolCallId)
7492
+ );
7493
+ if (answersOne) {
7494
+ toolIdx = j;
7495
+ break;
7496
+ }
7497
+ }
7498
+ }
7499
+ if (toolIdx > i + 1) {
7500
+ const [toolMsg] = result.splice(toolIdx, 1);
7501
+ result.splice(i + 1, 0, toolMsg);
7502
+ mutated = true;
7503
+ console.warn(
7504
+ `[tool-repair] Reordered tool-result message from index ${toolIdx} to ${i + 1} to immediately follow assistant tool-call(s) (${[...callIds].join(", ")}). ${toolIdx - i - 1} message(s) were wedged between them.`
7505
+ );
7506
+ }
7507
+ i++;
7508
+ }
7509
+ return mutated ? result : messages;
7510
+ }
7433
7511
  function repairToolPairing(messages) {
7434
7512
  const toolCallIds = /* @__PURE__ */ new Set();
7435
7513
  const toolResultIds = /* @__PURE__ */ new Set();
@@ -7549,6 +7627,7 @@ ${summaryContent}`
7549
7627
  ];
7550
7628
  }
7551
7629
  messages = repairToolPairing(messages);
7630
+ messages = ensureToolResultsFollowCalls(messages);
7552
7631
  messages = ensureEndsWithUserOrTool(messages);
7553
7632
  return messages;
7554
7633
  }
@@ -7743,7 +7822,7 @@ ${summaryContent}`
7743
7822
  }
7744
7823
  }
7745
7824
  async addResponseMessages(messages) {
7746
- const safe = repairToolPairing(messages);
7825
+ const safe = ensureToolResultsFollowCalls(repairToolPairing(messages));
7747
7826
  await messageQueries.addMany(this.sessionId, safe);
7748
7827
  try {
7749
7828
  const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
@@ -7795,6 +7874,120 @@ var init_web = __esm({
7795
7874
  }
7796
7875
  });
7797
7876
 
7877
+ // src/integrations/slack/persistence.ts
7878
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
7879
+ import { join as join9, dirname as dirname6 } from "path";
7880
+ function cachePath() {
7881
+ return join9(ensureAppDataDirectory(), FILENAME);
7882
+ }
7883
+ function load() {
7884
+ if (loaded) return;
7885
+ loaded = true;
7886
+ const path = cachePath();
7887
+ if (!existsSync16(path)) return;
7888
+ try {
7889
+ const raw = readFileSync7(path, "utf-8");
7890
+ const parsed = JSON.parse(raw);
7891
+ if (parsed?.version !== FILE_VERSION) return;
7892
+ const now = Date.now();
7893
+ for (const [id, e] of Object.entries(parsed.users || {})) {
7894
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7895
+ userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
7896
+ }
7897
+ }
7898
+ for (const [key2, e] of Object.entries(parsed.threads || {})) {
7899
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7900
+ threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
7901
+ }
7902
+ }
7903
+ } catch (err) {
7904
+ console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
7905
+ }
7906
+ }
7907
+ function evictIfOversized(map, max) {
7908
+ if (map.size <= max) return;
7909
+ const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
7910
+ const toRemove = map.size - max;
7911
+ for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
7912
+ }
7913
+ function scheduleSave() {
7914
+ dirty = true;
7915
+ if (saveTimer) return;
7916
+ saveTimer = setTimeout(() => {
7917
+ saveTimer = null;
7918
+ if (dirty) saveSync();
7919
+ }, SAVE_DEBOUNCE_MS);
7920
+ saveTimer.unref?.();
7921
+ }
7922
+ function saveSync() {
7923
+ dirty = false;
7924
+ try {
7925
+ const path = cachePath();
7926
+ const dir = dirname6(path);
7927
+ if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
7928
+ const payload = {
7929
+ version: FILE_VERSION,
7930
+ users: Object.fromEntries(userMap),
7931
+ threads: Object.fromEntries(threadMap)
7932
+ };
7933
+ const tmp = `${path}.tmp`;
7934
+ writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
7935
+ renameSync(tmp, path);
7936
+ } catch (err) {
7937
+ console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
7938
+ }
7939
+ }
7940
+ function hookExit() {
7941
+ if (exitHooked) return;
7942
+ exitHooked = true;
7943
+ const flush2 = () => {
7944
+ if (dirty) saveSync();
7945
+ };
7946
+ process.once("beforeExit", flush2);
7947
+ process.once("SIGINT", flush2);
7948
+ process.once("SIGTERM", flush2);
7949
+ }
7950
+ function getCachedUserName(userId) {
7951
+ load();
7952
+ return userMap.get(userId);
7953
+ }
7954
+ function setCachedUserName(userId, entry2) {
7955
+ load();
7956
+ hookExit();
7957
+ userMap.set(userId, entry2);
7958
+ evictIfOversized(userMap, MAX_USERS);
7959
+ scheduleSave();
7960
+ }
7961
+ function getCachedThreadOwnership(key2) {
7962
+ load();
7963
+ return threadMap.get(key2);
7964
+ }
7965
+ function setCachedThreadOwnership(key2, entry2) {
7966
+ load();
7967
+ hookExit();
7968
+ threadMap.set(key2, entry2);
7969
+ evictIfOversized(threadMap, MAX_THREADS);
7970
+ scheduleSave();
7971
+ }
7972
+ var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
7973
+ var init_persistence = __esm({
7974
+ "src/integrations/slack/persistence.ts"() {
7975
+ "use strict";
7976
+ init_config();
7977
+ FILENAME = "slack-cache.json";
7978
+ FILE_VERSION = 1;
7979
+ SAVE_DEBOUNCE_MS = 500;
7980
+ MAX_USERS = 5e3;
7981
+ MAX_THREADS = 5e3;
7982
+ loaded = false;
7983
+ userMap = /* @__PURE__ */ new Map();
7984
+ threadMap = /* @__PURE__ */ new Map();
7985
+ dirty = false;
7986
+ saveTimer = null;
7987
+ exitHooked = false;
7988
+ }
7989
+ });
7990
+
7798
7991
  // src/integrations/slack/client.ts
7799
7992
  function readSlackConfig() {
7800
7993
  try {
@@ -7917,13 +8110,13 @@ async function fetchSlackUserName(userId) {
7917
8110
  async function resolveSlackUserName(userId) {
7918
8111
  if (!userId) return null;
7919
8112
  const now = Date.now();
7920
- const hit = userNameCache.get(userId);
8113
+ const hit = getCachedUserName(userId);
7921
8114
  if (hit && hit.expiresAt > now) return hit.name;
7922
8115
  const inflight = userInflight.get(userId);
7923
8116
  if (inflight) return inflight;
7924
8117
  const p = (async () => {
7925
8118
  const name = await fetchSlackUserName(userId);
7926
- userNameCache.set(userId, {
8119
+ setCachedUserName(userId, {
7927
8120
  name,
7928
8121
  expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
7929
8122
  });
@@ -7945,11 +8138,63 @@ async function normalizeSlackMentions(text) {
7945
8138
  }
7946
8139
  return text.replace(userMentionRe, (_full, id, label) => {
7947
8140
  if (label) return `${label} <@${id}>`;
7948
- const cached = userNameCache.get(id);
8141
+ const cached = getCachedUserName(id);
7949
8142
  const name = cached?.name;
7950
8143
  return name ? `${name} <@${id}>` : `<@${id}>`;
7951
8144
  });
7952
8145
  }
8146
+ function threadCacheKey(channel, threadTs) {
8147
+ return `${channel}\u241F${threadTs}`;
8148
+ }
8149
+ async function fetchBotParticipatedInThread(channel, threadTs) {
8150
+ const token = getSlackBotToken();
8151
+ if (!token) return false;
8152
+ const self = await ensureSlackSelfIdentity();
8153
+ if (!self) return false;
8154
+ try {
8155
+ const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
8156
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
8157
+ const data = await res.json().catch(() => ({}));
8158
+ if (!data?.ok) {
8159
+ console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
8160
+ return false;
8161
+ }
8162
+ const messages = Array.isArray(data.messages) ? data.messages : [];
8163
+ return messages.some(
8164
+ (m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
8165
+ );
8166
+ } catch (err) {
8167
+ console.warn(`[slack] conversations.replies error:`, err?.message || err);
8168
+ return false;
8169
+ }
8170
+ }
8171
+ async function botParticipatedInThread(channel, threadTs) {
8172
+ if (!channel || !threadTs) return false;
8173
+ const key2 = threadCacheKey(channel, threadTs);
8174
+ const now = Date.now();
8175
+ const hit = getCachedThreadOwnership(key2);
8176
+ if (hit && hit.expiresAt > now) return hit.owned;
8177
+ const inflight = threadOwnedInflight.get(key2);
8178
+ if (inflight) return inflight;
8179
+ const p = (async () => {
8180
+ const owned = await fetchBotParticipatedInThread(channel, threadTs);
8181
+ setCachedThreadOwnership(key2, {
8182
+ owned,
8183
+ expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
8184
+ });
8185
+ threadOwnedInflight.delete(key2);
8186
+ return owned;
8187
+ })();
8188
+ threadOwnedInflight.set(key2, p);
8189
+ return p;
8190
+ }
8191
+ function noteBotPostedInThread(channel, threadTs) {
8192
+ if (!channel || !threadTs) return;
8193
+ setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
8194
+ owned: true,
8195
+ expiresAt: Date.now() + THREAD_OWNED_TTL_MS
8196
+ });
8197
+ }
7953
8198
  function getSlackDeniedReplyPolicy() {
7954
8199
  try {
7955
8200
  const cfg = getConfig();
@@ -7962,17 +8207,20 @@ function getSlackDeniedReplyPolicy() {
7962
8207
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
7963
8208
  }
7964
8209
  }
7965
- var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
8210
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
7966
8211
  var init_client3 = __esm({
7967
8212
  "src/integrations/slack/client.ts"() {
7968
8213
  "use strict";
7969
8214
  init_config();
8215
+ init_persistence();
7970
8216
  cachedSelf = null;
7971
8217
  selfInflight = null;
7972
8218
  USER_TTL_MS = 60 * 60 * 1e3;
7973
8219
  USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7974
- userNameCache = /* @__PURE__ */ new Map();
7975
8220
  userInflight = /* @__PURE__ */ new Map();
8221
+ THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
8222
+ THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
8223
+ threadOwnedInflight = /* @__PURE__ */ new Map();
7976
8224
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
7977
8225
  }
7978
8226
  });
@@ -8072,6 +8320,7 @@ var init_slack = __esm({
8072
8320
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
8073
8321
  if (r.slackChannel && r.threadTs) {
8074
8322
  markThreadOwned(r.slackChannel, r.threadTs);
8323
+ noteBotPostedInThread(r.slackChannel, r.threadTs);
8075
8324
  }
8076
8325
  },
8077
8326
  displayLabel(ref) {
@@ -8729,8 +8978,8 @@ var init_orchestrator_actions = __esm({
8729
8978
 
8730
8979
  // src/integrations/mcp/store.ts
8731
8980
  import { nanoid as nanoid6 } from "nanoid";
8732
- import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
8733
- import { resolve as resolve10, join as join9 } from "path";
8981
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
8982
+ import { resolve as resolve10, join as join10 } from "path";
8734
8983
  function readServers() {
8735
8984
  try {
8736
8985
  const cfg = getConfig();
@@ -8742,12 +8991,12 @@ function readServers() {
8742
8991
  function refreshMcpServersFromDisk() {
8743
8992
  const candidates = [
8744
8993
  resolve10(process.cwd(), "sparkecoder.config.json"),
8745
- join9(ensureAppDataDirectory(), "sparkecoder.config.json")
8994
+ join10(ensureAppDataDirectory(), "sparkecoder.config.json")
8746
8995
  ];
8747
8996
  for (const path of candidates) {
8748
- if (!existsSync16(path)) continue;
8997
+ if (!existsSync17(path)) continue;
8749
8998
  try {
8750
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
8999
+ const raw = JSON.parse(readFileSync8(path, "utf-8"));
8751
9000
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
8752
9001
  setMcpServers(servers2);
8753
9002
  return servers2;
@@ -9360,7 +9609,7 @@ __export(recorder_exports, {
9360
9609
  import { exec as exec5 } from "child_process";
9361
9610
  import { promisify as promisify5 } from "util";
9362
9611
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
9363
- import { join as join10 } from "path";
9612
+ import { join as join11 } from "path";
9364
9613
  import { tmpdir } from "os";
9365
9614
  import { nanoid as nanoid7 } from "nanoid";
9366
9615
  async function checkFfmpeg() {
@@ -9417,21 +9666,21 @@ var init_recorder = __esm({
9417
9666
  */
9418
9667
  async encode() {
9419
9668
  if (this.frames.length === 0) return null;
9420
- const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9669
+ const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9421
9670
  await mkdir4(workDir, { recursive: true });
9422
9671
  try {
9423
9672
  for (let i = 0; i < this.frames.length; i++) {
9424
- const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9673
+ const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9425
9674
  await writeFile5(framePath, this.frames[i].data);
9426
9675
  }
9427
9676
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
9428
9677
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
9429
9678
  const clampedFps = Math.max(1, Math.min(fps, 30));
9430
- const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
9679
+ const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
9431
9680
  const hasFfmpeg = await checkFfmpeg();
9432
9681
  if (hasFfmpeg) {
9433
9682
  await execAsync5(
9434
- `ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9683
+ `ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9435
9684
  { timeout: 12e4 }
9436
9685
  );
9437
9686
  } else {
@@ -9443,7 +9692,7 @@ var init_recorder = __esm({
9443
9692
  const files = await readdir5(workDir);
9444
9693
  for (const f of files) {
9445
9694
  if (f.startsWith("frame_")) {
9446
- await unlink2(join10(workDir, f)).catch(() => {
9695
+ await unlink2(join11(workDir, f)).catch(() => {
9447
9696
  });
9448
9697
  }
9449
9698
  }
@@ -9684,7 +9933,7 @@ ${prompt}` });
9684
9933
  const config = getConfig();
9685
9934
  const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
9686
9935
  if (!options.skipSaveUserMessage) {
9687
- this.context.addUserMessage(userContent);
9936
+ await this.context.addUserMessage(userContent);
9688
9937
  }
9689
9938
  await sessionQueries.updateStatus(this.session.id, "active");
9690
9939
  let systemPrompt = await buildSystemPrompt({
@@ -9710,7 +9959,8 @@ ${personality.trim()}`;
9710
9959
  }
9711
9960
  const messages = await this.context.getMessages();
9712
9961
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9713
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
9962
+ const approvalWrapped = this.wrapToolsWithApproval(options, tools);
9963
+ const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
9714
9964
  const useAnthropic = isAnthropicModel(this.session.model);
9715
9965
  const stream = streamText2({
9716
9966
  model: resolveModel(this.session.model),
@@ -9724,6 +9974,18 @@ ${personality.trim()}`;
9724
9974
  providerOptions: useAnthropic ? {
9725
9975
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9726
9976
  } : void 0,
9977
+ // Run repairToolPairing before EVERY step's model call, not just the
9978
+ // first one. The AI SDK's multi-step loop can otherwise feed the model
9979
+ // a prompt containing an orphan tool-call (e.g. when a previous step's
9980
+ // tool result was lost, dropped during compaction, or the stream was
9981
+ // aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
9982
+ // ever reaches the model and we never hit AI_MissingToolResultsError.
9983
+ prepareStep: async ({ messages: stepMessages }) => {
9984
+ const paired = repairToolPairing(stepMessages);
9985
+ const ordered = ensureToolResultsFollowCalls(paired);
9986
+ if (ordered === stepMessages) return {};
9987
+ return { messages: ordered };
9988
+ },
9727
9989
  onStepFinish: async (step) => {
9728
9990
  options.onStepFinish?.(step);
9729
9991
  },
@@ -9735,7 +9997,7 @@ ${personality.trim()}`;
9735
9997
  const result = await stream;
9736
9998
  const response = await result.response;
9737
9999
  const responseMessages = response.messages;
9738
- this.context.addResponseMessages(responseMessages);
10000
+ await this.context.addResponseMessages(responseMessages);
9739
10001
  };
9740
10002
  return {
9741
10003
  sessionId: this.session.id,
@@ -9749,7 +10011,7 @@ ${personality.trim()}`;
9749
10011
  */
9750
10012
  async run(options) {
9751
10013
  const config = getConfig();
9752
- this.context.addUserMessage(options.prompt);
10014
+ await this.context.addUserMessage(options.prompt);
9753
10015
  const systemPrompt = await buildSystemPrompt({
9754
10016
  workingDirectory: this.session.workingDirectory,
9755
10017
  skillsDirectories: config.resolvedSkillsDirectories,
@@ -9759,7 +10021,7 @@ ${personality.trim()}`;
9759
10021
  });
9760
10022
  const messages = await this.context.getMessages();
9761
10023
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9762
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
10024
+ const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
9763
10025
  const useAnthropic = isAnthropicModel(this.session.model);
9764
10026
  const result = await generateText3({
9765
10027
  model: resolveModel(this.session.model),
@@ -9770,10 +10032,17 @@ ${personality.trim()}`;
9770
10032
  // Enable extended thinking/reasoning for models that support it
9771
10033
  providerOptions: useAnthropic ? {
9772
10034
  anthropic: getAnthropicProviderOptions(this.session.model)
9773
- } : void 0
10035
+ } : void 0,
10036
+ // Repair tool pairing before every step (see `stream()` for full rationale).
10037
+ prepareStep: async ({ messages: stepMessages }) => {
10038
+ const paired = repairToolPairing(stepMessages);
10039
+ const ordered = ensureToolResultsFollowCalls(paired);
10040
+ if (ordered === stepMessages) return {};
10041
+ return { messages: ordered };
10042
+ }
9774
10043
  });
9775
10044
  const responseMessages = result.response.messages;
9776
- this.context.addResponseMessages(responseMessages);
10045
+ await this.context.addResponseMessages(responseMessages);
9777
10046
  return {
9778
10047
  text: result.text,
9779
10048
  steps: result.steps
@@ -9947,12 +10216,20 @@ ${p.text}` : p.text;
9947
10216
  model: resolveModel(this.session.model),
9948
10217
  system: systemPrompt,
9949
10218
  messages,
9950
- tools: taskTools,
10219
+ tools: wrapToolsNeverThrow(taskTools),
9951
10220
  stopWhen: stepCountIs2(500),
9952
10221
  abortSignal: combinedAbort,
9953
10222
  providerOptions: useAnthropic ? {
9954
10223
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9955
10224
  } : void 0,
10225
+ // See the matching note in `stream()` — repair tool pairing before
10226
+ // every step so we never feed the model an orphan tool-call.
10227
+ prepareStep: async ({ messages: stepMessages }) => {
10228
+ const paired = repairToolPairing(stepMessages);
10229
+ const ordered = ensureToolResultsFollowCalls(paired);
10230
+ if (ordered === stepMessages) return {};
10231
+ return { messages: ordered };
10232
+ },
9956
10233
  onStepFinish: async (step) => {
9957
10234
  options.onStepFinish?.(step);
9958
10235
  fireWebhook("task.step_finished", { iteration, text: step.text });
@@ -10186,11 +10463,11 @@ ${p.text}` : p.text;
10186
10463
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10187
10464
  if (!isRemoteConfigured2()) return [];
10188
10465
  const { readFile: readFile13 } = await import("fs/promises");
10189
- const { join: join17, basename: basename7 } = await import("path");
10466
+ const { join: join18, basename: basename7 } = await import("path");
10190
10467
  const urls = [];
10191
10468
  for (const filePath of filePaths) {
10192
10469
  try {
10193
- const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
10470
+ const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
10194
10471
  const fileName = basename7(fullPath);
10195
10472
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10196
10473
  const mimeMap = {
@@ -10395,19 +10672,19 @@ var init_session_lock = __esm({
10395
10672
  });
10396
10673
 
10397
10674
  // src/orchestrator/webhook-events.ts
10398
- import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
10399
- import { dirname as dirname6, join as join11 } from "path";
10675
+ import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
10676
+ import { dirname as dirname7, join as join12 } from "path";
10400
10677
  import { nanoid as nanoid9 } from "nanoid";
10401
10678
  function logFilePath() {
10402
- return join11(getAppDataDirectory(), "webhook-events.jsonl");
10679
+ return join12(getAppDataDirectory(), "webhook-events.jsonl");
10403
10680
  }
10404
10681
  function ensureLoaded() {
10405
10682
  if (cache !== null) return cache;
10406
10683
  cache = [];
10407
10684
  try {
10408
10685
  const p = logFilePath();
10409
- if (!existsSync17(p)) return cache;
10410
- const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
10686
+ if (!existsSync18(p)) return cache;
10687
+ const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
10411
10688
  for (const line of lines) {
10412
10689
  try {
10413
10690
  cache.push(JSON.parse(line));
@@ -10417,7 +10694,7 @@ function ensureLoaded() {
10417
10694
  if (cache.length > MAX_EVENTS) {
10418
10695
  cache = cache.slice(-MAX_EVENTS);
10419
10696
  try {
10420
- writeFileSync3(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10697
+ writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10421
10698
  } catch {
10422
10699
  }
10423
10700
  }
@@ -10431,7 +10708,7 @@ function appendEvent(ev) {
10431
10708
  if (list.length > MAX_EVENTS) list.shift();
10432
10709
  try {
10433
10710
  const p = logFilePath();
10434
- mkdirSync6(dirname6(p), { recursive: true });
10711
+ mkdirSync7(dirname7(p), { recursive: true });
10435
10712
  appendFileSync3(p, JSON.stringify(ev) + "\n");
10436
10713
  } catch {
10437
10714
  }
@@ -10462,8 +10739,8 @@ function updateEvent(id, patch) {
10462
10739
  list[i] = { ...list[i], ...patch };
10463
10740
  try {
10464
10741
  const p = logFilePath();
10465
- mkdirSync6(dirname6(p), { recursive: true });
10466
- writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10742
+ mkdirSync7(dirname7(p), { recursive: true });
10743
+ writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10467
10744
  } catch {
10468
10745
  }
10469
10746
  }
@@ -10495,7 +10772,7 @@ function listEvents(filter = {}) {
10495
10772
  function clearAllEvents() {
10496
10773
  cache = [];
10497
10774
  try {
10498
- writeFileSync3(logFilePath(), "");
10775
+ writeFileSync4(logFilePath(), "");
10499
10776
  } catch {
10500
10777
  }
10501
10778
  }
@@ -10766,8 +11043,8 @@ import { Hono as Hono10 } from "hono";
10766
11043
  import { serve } from "@hono/node-server";
10767
11044
  import { cors } from "hono/cors";
10768
11045
  import { logger } from "hono/logger";
10769
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10770
- import { resolve as resolve12, dirname as dirname9, join as join16 } from "path";
11046
+ import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
11047
+ import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
10771
11048
  import { spawn as spawn2 } from "child_process";
10772
11049
  import { createServer as createNetServer } from "net";
10773
11050
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -10781,9 +11058,9 @@ init_checkpoints();
10781
11058
  import { Hono } from "hono";
10782
11059
  import { zValidator } from "@hono/zod-validator";
10783
11060
  import { z as z16 } from "zod";
10784
- import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
11061
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
10785
11062
  import { readdir as readdir6 } from "fs/promises";
10786
- import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
11063
+ import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
10787
11064
  import { nanoid as nanoid10 } from "nanoid";
10788
11065
 
10789
11066
  // src/tasks/agent-status.ts
@@ -11421,12 +11698,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
11421
11698
  });
11422
11699
  function getAttachmentsDir(sessionId) {
11423
11700
  const appDataDir = getAppDataDirectory();
11424
- return join12(appDataDir, "attachments", sessionId);
11701
+ return join13(appDataDir, "attachments", sessionId);
11425
11702
  }
11426
11703
  function ensureAttachmentsDir(sessionId) {
11427
11704
  const dir = getAttachmentsDir(sessionId);
11428
- if (!existsSync18(dir)) {
11429
- mkdirSync7(dir, { recursive: true });
11705
+ if (!existsSync19(dir)) {
11706
+ mkdirSync8(dir, { recursive: true });
11430
11707
  }
11431
11708
  return dir;
11432
11709
  }
@@ -11437,12 +11714,12 @@ sessions2.get("/:id/attachments", async (c) => {
11437
11714
  return c.json({ error: "Session not found" }, 404);
11438
11715
  }
11439
11716
  const dir = getAttachmentsDir(sessionId);
11440
- if (!existsSync18(dir)) {
11717
+ if (!existsSync19(dir)) {
11441
11718
  return c.json({ sessionId, attachments: [], count: 0 });
11442
11719
  }
11443
11720
  const files = readdirSync3(dir);
11444
11721
  const attachments = files.map((filename) => {
11445
- const filePath = join12(dir, filename);
11722
+ const filePath = join13(dir, filename);
11446
11723
  const stats = statSync2(filePath);
11447
11724
  return {
11448
11725
  id: filename.split("_")[0],
@@ -11477,9 +11754,9 @@ sessions2.post("/:id/attachments", async (c) => {
11477
11754
  const id = nanoid10(10);
11478
11755
  const ext = extname8(file.name) || "";
11479
11756
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11480
- const filePath = join12(dir, safeFilename);
11757
+ const filePath = join13(dir, safeFilename);
11481
11758
  const arrayBuffer = await file.arrayBuffer();
11482
- writeFileSync4(filePath, Buffer.from(arrayBuffer));
11759
+ writeFileSync5(filePath, Buffer.from(arrayBuffer));
11483
11760
  return c.json({
11484
11761
  id,
11485
11762
  filename: file.name,
@@ -11503,13 +11780,13 @@ sessions2.post("/:id/attachments", async (c) => {
11503
11780
  const id = nanoid10(10);
11504
11781
  const ext = extname8(body.filename) || "";
11505
11782
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11506
- const filePath = join12(dir, safeFilename);
11783
+ const filePath = join13(dir, safeFilename);
11507
11784
  let base64Data = body.data;
11508
11785
  if (base64Data.includes(",")) {
11509
11786
  base64Data = base64Data.split(",")[1];
11510
11787
  }
11511
11788
  const buffer = Buffer.from(base64Data, "base64");
11512
- writeFileSync4(filePath, buffer);
11789
+ writeFileSync5(filePath, buffer);
11513
11790
  return c.json({
11514
11791
  id,
11515
11792
  filename: body.filename,
@@ -11532,7 +11809,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11532
11809
  return c.json({ error: "Session not found" }, 404);
11533
11810
  }
11534
11811
  const dir = getAttachmentsDir(sessionId);
11535
- if (!existsSync18(dir)) {
11812
+ if (!existsSync19(dir)) {
11536
11813
  return c.json({ error: "Attachment not found" }, 404);
11537
11814
  }
11538
11815
  const files = readdirSync3(dir);
@@ -11540,7 +11817,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11540
11817
  if (!file) {
11541
11818
  return c.json({ error: "Attachment not found" }, 404);
11542
11819
  }
11543
- const filePath = join12(dir, file);
11820
+ const filePath = join13(dir, file);
11544
11821
  unlinkSync2(filePath);
11545
11822
  return c.json({ success: true, id: attachmentId });
11546
11823
  });
@@ -11623,7 +11900,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
11623
11900
  const entries = await readdir6(currentDir, { withFileTypes: true });
11624
11901
  for (const entry2 of entries) {
11625
11902
  if (results.length >= limit * 2) break;
11626
- const fullPath = join12(currentDir, entry2.name);
11903
+ const fullPath = join13(currentDir, entry2.name);
11627
11904
  const relativePath = relative9(baseDir, fullPath);
11628
11905
  if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
11629
11906
  continue;
@@ -11671,7 +11948,7 @@ sessions2.get(
11671
11948
  return c.json({ error: "Session not found" }, 404);
11672
11949
  }
11673
11950
  const workingDirectory = session.workingDirectory;
11674
- if (!existsSync18(workingDirectory)) {
11951
+ if (!existsSync19(workingDirectory)) {
11675
11952
  return c.json({
11676
11953
  sessionId,
11677
11954
  workingDirectory,
@@ -11779,14 +12056,148 @@ sessions2.get("/:id/browser-recording", async (c) => {
11779
12056
 
11780
12057
  // src/server/routes/agents.ts
11781
12058
  init_db();
11782
- init_agent();
11783
- init_session_lock();
11784
- init_config();
11785
12059
  import { Hono as Hono2 } from "hono";
11786
12060
  import { zValidator as zValidator2 } from "@hono/zod-validator";
11787
12061
  import { z as z17 } from "zod";
11788
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
11789
- import { join as join13 } from "path";
12062
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
12063
+ import { join as join14 } from "path";
12064
+
12065
+ // src/agent/missing-tool-recovery.ts
12066
+ init_db();
12067
+ function extractMissingToolCallIds(error) {
12068
+ if (!error) return [];
12069
+ const e = error;
12070
+ if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
12071
+ return e.toolCallIds;
12072
+ }
12073
+ const msg = typeof e.message === "string" ? e.message : "";
12074
+ const ids = /* @__PURE__ */ new Set();
12075
+ for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
12076
+ for (const id of m[1].split(/\s*,\s*/)) {
12077
+ const trimmed = id.trim();
12078
+ if (trimmed) ids.add(trimmed);
12079
+ }
12080
+ }
12081
+ return [...ids];
12082
+ }
12083
+ function isMissingToolResultsError(error) {
12084
+ if (!error) return false;
12085
+ const e = error;
12086
+ if (e.name === "AI_MissingToolResultsError") return true;
12087
+ if (Array.isArray(e.toolCallIds)) return true;
12088
+ return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
12089
+ }
12090
+ async function recoverFromMissingToolResults(sessionId, error) {
12091
+ const toolCallIds = extractMissingToolCallIds(error);
12092
+ if (toolCallIds.length === 0) return null;
12093
+ let history = [];
12094
+ try {
12095
+ history = await messageQueries.getModelMessages(sessionId);
12096
+ } catch (err) {
12097
+ console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
12098
+ }
12099
+ const toolInfoByCallId = /* @__PURE__ */ new Map();
12100
+ const resultMessageIndexByCallId = /* @__PURE__ */ new Map();
12101
+ const existingResultIds = /* @__PURE__ */ new Set();
12102
+ for (let idx = 0; idx < history.length; idx++) {
12103
+ const msg = history[idx];
12104
+ if (!Array.isArray(msg.content)) continue;
12105
+ for (const part of msg.content) {
12106
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
12107
+ if (typeof part.toolName === "string") {
12108
+ toolInfoByCallId.set(part.toolCallId, { toolName: part.toolName, callMessageIndex: idx });
12109
+ }
12110
+ }
12111
+ if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
12112
+ existingResultIds.add(part.toolCallId);
12113
+ resultMessageIndexByCallId.set(part.toolCallId, idx);
12114
+ }
12115
+ }
12116
+ }
12117
+ const resolved = toolCallIds.map((id) => {
12118
+ const info = toolInfoByCallId.get(id);
12119
+ const resultIdx = resultMessageIndexByCallId.get(id);
12120
+ const wedgedRoles = info && typeof resultIdx === "number" && resultIdx > info.callMessageIndex + 1 ? history.slice(info.callMessageIndex + 1, resultIdx).map((m) => m.role) : void 0;
12121
+ return {
12122
+ toolCallId: id,
12123
+ toolName: info?.toolName ?? "unknown",
12124
+ foundInAssistantMessage: !!info,
12125
+ callMessageIndex: info?.callMessageIndex,
12126
+ resultMessageIndex: resultIdx,
12127
+ wedgedMessageRoles: wedgedRoles
12128
+ };
12129
+ });
12130
+ const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
12131
+ let syntheticToolMessageSaved = false;
12132
+ if (stillOrphaned.length > 0) {
12133
+ const syntheticParts = stillOrphaned.map((id) => ({
12134
+ type: "tool-result",
12135
+ toolCallId: id,
12136
+ toolName: toolInfoByCallId.get(id)?.toolName ?? "unknown",
12137
+ output: {
12138
+ type: "text",
12139
+ 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.]"
12140
+ }
12141
+ }));
12142
+ try {
12143
+ await messageQueries.create(sessionId, {
12144
+ role: "tool",
12145
+ content: syntheticParts
12146
+ });
12147
+ syntheticToolMessageSaved = true;
12148
+ } catch (err) {
12149
+ console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
12150
+ }
12151
+ }
12152
+ const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
12153
+ const first = resolved.find((r) => r.callMessageIndex !== void 0);
12154
+ let contextSnapshot;
12155
+ if (first?.callMessageIndex !== void 0) {
12156
+ const start = Math.max(0, first.callMessageIndex - 1);
12157
+ const end = Math.min(
12158
+ history.length,
12159
+ Math.max(first.callMessageIndex, first.resultMessageIndex ?? first.callMessageIndex) + 2
12160
+ );
12161
+ contextSnapshot = history.slice(start, end).map((m, offset) => ({
12162
+ index: start + offset,
12163
+ role: m.role,
12164
+ summary: summarizeContent(m.content)
12165
+ }));
12166
+ }
12167
+ const anyWedged = resolved.some((r) => r.wedgedMessageRoles && r.wedgedMessageRoles.length > 0);
12168
+ return {
12169
+ kind: "missing_tool_results",
12170
+ toolCallIds,
12171
+ resolved,
12172
+ syntheticToolMessageSaved,
12173
+ lastFewMessageRoles,
12174
+ contextSnapshot,
12175
+ hint: anyWedged ? "A non-tool message was wedged between the assistant tool-call and its tool-result (likely a Slack/system inbox event that arrived mid-turn). " + (syntheticToolMessageSaved ? "Synthetic tool-result was added as a fallback, but the proper fix (now in place) is the reordering pass that moves tool-results to immediately follow their tool-calls." : "The reordering pass should fix this on the next turn; if not, revert to the previous checkpoint.") : 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."
12176
+ };
12177
+ }
12178
+ function summarizeContent(content) {
12179
+ if (typeof content === "string") {
12180
+ return content.length > 160 ? content.slice(0, 160) + "\u2026" : content;
12181
+ }
12182
+ if (!Array.isArray(content)) return String(content);
12183
+ const parts = content.map((p) => {
12184
+ if (!p || typeof p !== "object") return String(p);
12185
+ if (p.type === "text") {
12186
+ const t = String(p.text ?? "");
12187
+ return `text(${t.length > 80 ? t.slice(0, 80) + "\u2026" : t})`;
12188
+ }
12189
+ if (p.type === "tool-call") return `tool-call(${p.toolName}:${p.toolCallId})`;
12190
+ if (p.type === "tool-result") return `tool-result(${p.toolName ?? "?"}:${p.toolCallId})`;
12191
+ if (p.type === "reasoning") return "reasoning";
12192
+ return p.type ?? "unknown";
12193
+ });
12194
+ return parts.join(", ");
12195
+ }
12196
+
12197
+ // src/server/routes/agents.ts
12198
+ init_agent();
12199
+ init_session_lock();
12200
+ init_config();
11790
12201
 
11791
12202
  // src/server/resumable-stream.ts
11792
12203
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -11993,12 +12404,12 @@ var rejectSchema = z17.object({
11993
12404
  var streamAbortControllers = /* @__PURE__ */ new Map();
11994
12405
  function getAttachmentsDirectory(sessionId) {
11995
12406
  const appDataDir = getAppDataDirectory();
11996
- return join13(appDataDir, "attachments", sessionId);
12407
+ return join14(appDataDir, "attachments", sessionId);
11997
12408
  }
11998
12409
  async function saveAttachmentToDisk(sessionId, attachment, index) {
11999
12410
  const attachmentsDir = getAttachmentsDirectory(sessionId);
12000
- if (!existsSync19(attachmentsDir)) {
12001
- mkdirSync8(attachmentsDir, { recursive: true });
12411
+ if (!existsSync20(attachmentsDir)) {
12412
+ mkdirSync9(attachmentsDir, { recursive: true });
12002
12413
  }
12003
12414
  let filename = attachment.filename;
12004
12415
  if (!filename) {
@@ -12016,8 +12427,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
12016
12427
  attachment.mediaType = resized.mediaType;
12017
12428
  attachment.data = buffer.toString("base64");
12018
12429
  }
12019
- const filePath = join13(attachmentsDir, filename);
12020
- writeFileSync5(filePath, buffer);
12430
+ const filePath = join14(attachmentsDir, filename);
12431
+ writeFileSync6(filePath, buffer);
12021
12432
  return filePath;
12022
12433
  }
12023
12434
  function stripDataUrlPrefix2(data) {
@@ -12354,7 +12765,20 @@ ${prompt}` });
12354
12765
  await writeSSE(JSON.stringify({ type: "abort" }));
12355
12766
  } else {
12356
12767
  console.error("Agent error:", error);
12357
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
12768
+ let debugPayload = void 0;
12769
+ if (isMissingToolResultsError(error)) {
12770
+ try {
12771
+ const recovery = await recoverFromMissingToolResults(sessionId, error);
12772
+ if (recovery) debugPayload = recovery;
12773
+ } catch (recErr) {
12774
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
12775
+ }
12776
+ }
12777
+ await writeSSE(JSON.stringify({
12778
+ type: "error",
12779
+ errorText: error.message,
12780
+ ...debugPayload ? { debug: debugPayload } : {}
12781
+ }));
12358
12782
  try {
12359
12783
  await activeStreamQueries.markError(streamId);
12360
12784
  } catch {
@@ -12894,7 +13318,20 @@ agents.post(
12894
13318
  await writeSSE(JSON.stringify({ type: "abort" }));
12895
13319
  } else {
12896
13320
  console.error("Agent error:", error);
12897
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
13321
+ let debugPayload = void 0;
13322
+ if (isMissingToolResultsError(error)) {
13323
+ try {
13324
+ const recovery = await recoverFromMissingToolResults(session.id, error);
13325
+ if (recovery) debugPayload = recovery;
13326
+ } catch (recErr) {
13327
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
13328
+ }
13329
+ }
13330
+ await writeSSE(JSON.stringify({
13331
+ type: "error",
13332
+ errorText: error.message,
13333
+ ...debugPayload ? { debug: debugPayload } : {}
13334
+ }));
12898
13335
  await activeStreamQueries.markError(streamId);
12899
13336
  }
12900
13337
  } finally {
@@ -12977,26 +13414,26 @@ init_config();
12977
13414
  import { Hono as Hono3 } from "hono";
12978
13415
  import { zValidator as zValidator3 } from "@hono/zod-validator";
12979
13416
  import { z as z18 } from "zod";
12980
- import { readFileSync as readFileSync9 } from "fs";
13417
+ import { readFileSync as readFileSync10 } from "fs";
12981
13418
  import { fileURLToPath as fileURLToPath3 } from "url";
12982
- import { dirname as dirname7, join as join14 } from "path";
13419
+ import { dirname as dirname8, join as join15 } from "path";
12983
13420
  var __filename = fileURLToPath3(import.meta.url);
12984
- var __dirname = dirname7(__filename);
13421
+ var __dirname = dirname8(__filename);
12985
13422
  var possiblePaths = [
12986
- join14(__dirname, "../package.json"),
13423
+ join15(__dirname, "../package.json"),
12987
13424
  // From dist/server -> dist/../package.json
12988
- join14(__dirname, "../../package.json"),
13425
+ join15(__dirname, "../../package.json"),
12989
13426
  // From dist/server (if nested differently)
12990
- join14(__dirname, "../../../package.json"),
13427
+ join15(__dirname, "../../../package.json"),
12991
13428
  // From src/server/routes (development)
12992
- join14(process.cwd(), "package.json")
13429
+ join15(process.cwd(), "package.json")
12993
13430
  // From current working directory
12994
13431
  ];
12995
13432
  var currentVersion = "0.0.0";
12996
13433
  var packageName = "sparkecoder";
12997
13434
  for (const packageJsonPath of possiblePaths) {
12998
13435
  try {
12999
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
13436
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
13000
13437
  if (packageJson.name === "sparkecoder") {
13001
13438
  currentVersion = packageJson.version || "0.0.0";
13002
13439
  packageName = packageJson.name || "sparkecoder";
@@ -13814,12 +14251,13 @@ slack.post("/events", async (c) => {
13814
14251
  if (inbound) {
13815
14252
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
13816
14253
  if (isThreadReply) {
13817
- const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
14254
+ const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
13818
14255
  if (!ours) {
13819
14256
  console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
13820
14257
  updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
13821
14258
  return c.json({ ok: true });
13822
14259
  }
14260
+ markThreadOwned(ev.channel, ev.thread_ts);
13823
14261
  }
13824
14262
  if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
13825
14263
  markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
@@ -13858,18 +14296,6 @@ slack.post("/events", async (c) => {
13858
14296
  }
13859
14297
  return c.json({ ok: true });
13860
14298
  });
13861
- async function threadBelongsToUs(channel, threadTs) {
13862
- try {
13863
- const sessions3 = await sessionQueries.list(500, 0);
13864
- return sessions3.some((s) => {
13865
- const slack2 = s.config?.slack;
13866
- return slack2?.channel === channel && slack2?.threadTs === threadTs;
13867
- });
13868
- } catch (err) {
13869
- console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
13870
- return false;
13871
- }
13872
- }
13873
14299
  async function findOrCreateOrchestratorId() {
13874
14300
  try {
13875
14301
  const all = await sessionQueries.list(500, 0);
@@ -14258,9 +14684,9 @@ init_skills();
14258
14684
  import { Hono as Hono9 } from "hono";
14259
14685
  import { zValidator as zValidator7 } from "@hono/zod-validator";
14260
14686
  import { z as z22 } from "zod";
14261
- import { existsSync as existsSync20, statSync as statSync3 } from "fs";
14687
+ import { existsSync as existsSync21, statSync as statSync3 } from "fs";
14262
14688
  import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
14263
- import { resolve as resolve11, join as join15, basename as basename6, dirname as dirname8, extname as extname9 } from "path";
14689
+ import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
14264
14690
  var skills = new Hono9();
14265
14691
  function encodeId(filePath) {
14266
14692
  return Buffer.from(filePath, "utf-8").toString("base64url");
@@ -14319,13 +14745,13 @@ function pathToLabel(path) {
14319
14745
  if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
14320
14746
  if (path.includes("/.cursor/rules")) return ".cursor/rules";
14321
14747
  if (path.includes("/.claude/skills")) return ".claude/skills";
14322
- return basename6(dirname8(path)) + "/" + basename6(path);
14748
+ return basename6(dirname9(path)) + "/" + basename6(path);
14323
14749
  }
14324
14750
  skills.get("/", async (c) => {
14325
14751
  const dirs = listAllDirectories();
14326
14752
  const allSkills = [];
14327
14753
  for (const dir of dirs) {
14328
- if (!existsSync20(dir.path)) continue;
14754
+ if (!existsSync21(dir.path)) continue;
14329
14755
  try {
14330
14756
  const list = await loadSkillsFromDirectory(dir.path, {
14331
14757
  priority: dir.priority,
@@ -14362,7 +14788,7 @@ skills.get("/", async (c) => {
14362
14788
  label: d.label,
14363
14789
  source: d.source,
14364
14790
  alwaysApply: d.alwaysApply,
14365
- exists: existsSync20(d.path),
14791
+ exists: existsSync21(d.path),
14366
14792
  writable: isWritable(d.path)
14367
14793
  })),
14368
14794
  skills: allSkills
@@ -14370,7 +14796,7 @@ skills.get("/", async (c) => {
14370
14796
  });
14371
14797
  function isWritable(dir) {
14372
14798
  try {
14373
- if (!existsSync20(dir)) return false;
14799
+ if (!existsSync21(dir)) return false;
14374
14800
  if (dir.includes("/skills/default")) return false;
14375
14801
  return true;
14376
14802
  } catch {
@@ -14379,7 +14805,7 @@ function isWritable(dir) {
14379
14805
  }
14380
14806
  skills.get("/:id", async (c) => {
14381
14807
  const filePath = decodeId(c.req.param("id"));
14382
- if (!filePath || !existsSync20(filePath)) {
14808
+ if (!filePath || !existsSync21(filePath)) {
14383
14809
  return c.json({ error: "skill not found" }, 404);
14384
14810
  }
14385
14811
  const content = await readFile12(filePath, "utf-8");
@@ -14408,8 +14834,8 @@ skills.post(
14408
14834
  const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
14409
14835
  const ext = extname9(safeName).toLowerCase();
14410
14836
  const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
14411
- const filePath = join15(targetDir, finalName);
14412
- if (existsSync20(filePath)) {
14837
+ const filePath = join16(targetDir, finalName);
14838
+ if (existsSync21(filePath)) {
14413
14839
  return c.json({ error: `file already exists: ${finalName}` }, 409);
14414
14840
  }
14415
14841
  try {
@@ -14426,7 +14852,7 @@ skills.put(
14426
14852
  zValidator7("json", z22.object({ content: z22.string() })),
14427
14853
  async (c) => {
14428
14854
  const filePath = decodeId(c.req.param("id"));
14429
- if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14855
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14430
14856
  if (filePath.includes("/skills/default")) {
14431
14857
  return c.json({ error: "built-in skills are read-only" }, 400);
14432
14858
  }
@@ -14436,7 +14862,7 @@ skills.put(
14436
14862
  );
14437
14863
  skills.delete("/:id", async (c) => {
14438
14864
  const filePath = decodeId(c.req.param("id"));
14439
- if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14865
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14440
14866
  if (filePath.includes("/skills/default")) {
14441
14867
  return c.json({ error: "built-in skills are read-only" }, 400);
14442
14868
  }
@@ -14456,7 +14882,7 @@ skills.post(
14456
14882
  }
14457
14883
  const next = [...current, abs];
14458
14884
  setSkillsAdditionalDirectories(next);
14459
- return c.json({ ok: true, path: abs, exists: existsSync20(abs) }, 201);
14885
+ return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
14460
14886
  }
14461
14887
  );
14462
14888
  skills.delete("/directories", (c) => {
@@ -14630,13 +15056,13 @@ var DEFAULT_WEB_PORT = 6969;
14630
15056
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
14631
15057
  function getWebDirectory() {
14632
15058
  try {
14633
- const currentDir = dirname9(fileURLToPath4(import.meta.url));
15059
+ const currentDir = dirname10(fileURLToPath4(import.meta.url));
14634
15060
  const webDir = resolve12(currentDir, "..", "web");
14635
- if (existsSync21(webDir) && existsSync21(join16(webDir, "package.json"))) {
15061
+ if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
14636
15062
  return webDir;
14637
15063
  }
14638
15064
  const altWebDir = resolve12(currentDir, "..", "..", "web");
14639
- if (existsSync21(altWebDir) && existsSync21(join16(altWebDir, "package.json"))) {
15065
+ if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
14640
15066
  return altWebDir;
14641
15067
  }
14642
15068
  return null;
@@ -14694,23 +15120,23 @@ async function findWebPort(preferredPort) {
14694
15120
  return { port: preferredPort, alreadyRunning: false };
14695
15121
  }
14696
15122
  function hasProductionBuild(webDir) {
14697
- const buildIdPath = join16(webDir, ".next", "BUILD_ID");
14698
- return existsSync21(buildIdPath);
15123
+ const buildIdPath = join17(webDir, ".next", "BUILD_ID");
15124
+ return existsSync22(buildIdPath);
14699
15125
  }
14700
15126
  function hasSourceFiles(webDir) {
14701
- const appDir = join16(webDir, "src", "app");
14702
- const pagesDir = join16(webDir, "src", "pages");
14703
- const rootAppDir = join16(webDir, "app");
14704
- const rootPagesDir = join16(webDir, "pages");
14705
- return existsSync21(appDir) || existsSync21(pagesDir) || existsSync21(rootAppDir) || existsSync21(rootPagesDir);
15127
+ const appDir = join17(webDir, "src", "app");
15128
+ const pagesDir = join17(webDir, "src", "pages");
15129
+ const rootAppDir = join17(webDir, "app");
15130
+ const rootPagesDir = join17(webDir, "pages");
15131
+ return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
14706
15132
  }
14707
15133
  function getStandaloneServerPath(webDir) {
14708
15134
  const possiblePaths2 = [
14709
- join16(webDir, ".next", "standalone", "server.js"),
14710
- join16(webDir, ".next", "standalone", "web", "server.js")
15135
+ join17(webDir, ".next", "standalone", "server.js"),
15136
+ join17(webDir, ".next", "standalone", "web", "server.js")
14711
15137
  ];
14712
15138
  for (const serverPath of possiblePaths2) {
14713
- if (existsSync21(serverPath)) {
15139
+ if (existsSync22(serverPath)) {
14714
15140
  return serverPath;
14715
15141
  }
14716
15142
  }
@@ -14750,15 +15176,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14750
15176
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
14751
15177
  return { process: null, port: actualPort };
14752
15178
  }
14753
- const usePnpm = existsSync21(join16(webDir, "pnpm-lock.yaml"));
14754
- const useNpm = !usePnpm && existsSync21(join16(webDir, "package-lock.json"));
15179
+ const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
15180
+ const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
14755
15181
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
14756
15182
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
14757
15183
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
14758
15184
  const runtimeConfig = { apiBaseUrl: apiUrl };
14759
- const runtimeConfigPath = join16(webDir, "runtime-config.json");
15185
+ const runtimeConfigPath = join17(webDir, "runtime-config.json");
14760
15186
  try {
14761
- writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
15187
+ writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
14762
15188
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
14763
15189
  } catch (err) {
14764
15190
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -14778,7 +15204,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14778
15204
  if (standaloneServerPath) {
14779
15205
  command = "node";
14780
15206
  args = ["server.js"];
14781
- cwd = dirname9(standaloneServerPath);
15207
+ cwd = dirname10(standaloneServerPath);
14782
15208
  webEnv.PORT = String(actualPort);
14783
15209
  webEnv.HOSTNAME = "0.0.0.0";
14784
15210
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -14972,8 +15398,8 @@ async function startServer(options = {}) {
14972
15398
  if (options.workingDirectory) {
14973
15399
  config.resolvedWorkingDirectory = options.workingDirectory;
14974
15400
  }
14975
- if (!existsSync21(config.resolvedWorkingDirectory)) {
14976
- mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
15401
+ if (!existsSync22(config.resolvedWorkingDirectory)) {
15402
+ mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
14977
15403
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
14978
15404
  }
14979
15405
  if (!config.resolvedRemoteServer.url) {