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
package/dist/index.js CHANGED
@@ -2346,10 +2346,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2346
2346
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
2347
2347
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
2348
2348
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
2349
- const cachePath = join3(cacheDir, key2 + ext);
2350
- if (existsSync3(cachePath)) {
2349
+ const cachePath2 = join3(cacheDir, key2 + ext);
2350
+ if (existsSync3(cachePath2)) {
2351
2351
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
2352
- return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
2352
+ return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
2353
2353
  }
2354
2354
  let pipeline = sharp(buffer);
2355
2355
  if (needsResize) {
@@ -2374,7 +2374,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2374
2374
  }
2375
2375
  finalMediaType = "image/jpeg";
2376
2376
  }
2377
- writeFileSync2(cachePath, result);
2377
+ writeFileSync2(cachePath2, result);
2378
2378
  const resultMeta = await sharp(result).metadata();
2379
2379
  console.log(
2380
2380
  `[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
@@ -7447,6 +7447,84 @@ function stripOrphanedToolResults(msg, removedIds) {
7447
7447
  if (parts.length === 0) return null;
7448
7448
  return { ...msg, content: parts };
7449
7449
  }
7450
+ function wrapToolsNeverThrow(tools) {
7451
+ if (!tools || typeof tools !== "object") return tools;
7452
+ const wrapped = {};
7453
+ for (const [name, t] of Object.entries(tools)) {
7454
+ if (!t || typeof t.execute !== "function") {
7455
+ wrapped[name] = t;
7456
+ continue;
7457
+ }
7458
+ const original = t.execute;
7459
+ wrapped[name] = {
7460
+ ...t,
7461
+ execute: async (input, opts) => {
7462
+ try {
7463
+ return await original.call(t, input, opts);
7464
+ } catch (err) {
7465
+ const message = err?.message ?? String(err);
7466
+ console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
7467
+ return {
7468
+ __error: true,
7469
+ tool: name,
7470
+ message,
7471
+ note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
7472
+ };
7473
+ }
7474
+ }
7475
+ };
7476
+ }
7477
+ return wrapped;
7478
+ }
7479
+ function ensureToolResultsFollowCalls(messages) {
7480
+ if (!Array.isArray(messages) || messages.length < 3) return messages;
7481
+ let mutated = false;
7482
+ const result = messages.slice();
7483
+ let i = 0;
7484
+ while (i < result.length) {
7485
+ const msg = result[i];
7486
+ if (msg?.role !== "assistant" || !Array.isArray(msg.content)) {
7487
+ i++;
7488
+ continue;
7489
+ }
7490
+ const callIds = /* @__PURE__ */ new Set();
7491
+ for (const part of msg.content) {
7492
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
7493
+ callIds.add(part.toolCallId);
7494
+ }
7495
+ }
7496
+ if (callIds.size === 0) {
7497
+ i++;
7498
+ continue;
7499
+ }
7500
+ let toolIdx = -1;
7501
+ for (let j = i + 1; j < result.length; j++) {
7502
+ const m = result[j];
7503
+ if (m?.role === "assistant" && Array.isArray(m.content) && m.content.some((p) => p?.type === "tool-call")) {
7504
+ break;
7505
+ }
7506
+ if (m?.role === "tool" && Array.isArray(m.content)) {
7507
+ const answersOne = m.content.some(
7508
+ (p) => p?.type === "tool-result" && typeof p.toolCallId === "string" && callIds.has(p.toolCallId)
7509
+ );
7510
+ if (answersOne) {
7511
+ toolIdx = j;
7512
+ break;
7513
+ }
7514
+ }
7515
+ }
7516
+ if (toolIdx > i + 1) {
7517
+ const [toolMsg] = result.splice(toolIdx, 1);
7518
+ result.splice(i + 1, 0, toolMsg);
7519
+ mutated = true;
7520
+ console.warn(
7521
+ `[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.`
7522
+ );
7523
+ }
7524
+ i++;
7525
+ }
7526
+ return mutated ? result : messages;
7527
+ }
7450
7528
  function repairToolPairing(messages) {
7451
7529
  const toolCallIds = /* @__PURE__ */ new Set();
7452
7530
  const toolResultIds = /* @__PURE__ */ new Set();
@@ -7566,6 +7644,7 @@ ${summaryContent}`
7566
7644
  ];
7567
7645
  }
7568
7646
  messages = repairToolPairing(messages);
7647
+ messages = ensureToolResultsFollowCalls(messages);
7569
7648
  messages = ensureEndsWithUserOrTool(messages);
7570
7649
  return messages;
7571
7650
  }
@@ -7760,7 +7839,7 @@ ${summaryContent}`
7760
7839
  }
7761
7840
  }
7762
7841
  async addResponseMessages(messages) {
7763
- const safe = repairToolPairing(messages);
7842
+ const safe = ensureToolResultsFollowCalls(repairToolPairing(messages));
7764
7843
  await messageQueries.addMany(this.sessionId, safe);
7765
7844
  try {
7766
7845
  const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
@@ -7812,6 +7891,120 @@ var init_web = __esm({
7812
7891
  }
7813
7892
  });
7814
7893
 
7894
+ // src/integrations/slack/persistence.ts
7895
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
7896
+ import { join as join9, dirname as dirname6 } from "path";
7897
+ function cachePath() {
7898
+ return join9(ensureAppDataDirectory(), FILENAME);
7899
+ }
7900
+ function load() {
7901
+ if (loaded) return;
7902
+ loaded = true;
7903
+ const path = cachePath();
7904
+ if (!existsSync16(path)) return;
7905
+ try {
7906
+ const raw = readFileSync7(path, "utf-8");
7907
+ const parsed = JSON.parse(raw);
7908
+ if (parsed?.version !== FILE_VERSION) return;
7909
+ const now = Date.now();
7910
+ for (const [id, e] of Object.entries(parsed.users || {})) {
7911
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7912
+ userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
7913
+ }
7914
+ }
7915
+ for (const [key2, e] of Object.entries(parsed.threads || {})) {
7916
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7917
+ threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
7918
+ }
7919
+ }
7920
+ } catch (err) {
7921
+ console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
7922
+ }
7923
+ }
7924
+ function evictIfOversized(map, max) {
7925
+ if (map.size <= max) return;
7926
+ const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
7927
+ const toRemove = map.size - max;
7928
+ for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
7929
+ }
7930
+ function scheduleSave() {
7931
+ dirty = true;
7932
+ if (saveTimer) return;
7933
+ saveTimer = setTimeout(() => {
7934
+ saveTimer = null;
7935
+ if (dirty) saveSync();
7936
+ }, SAVE_DEBOUNCE_MS);
7937
+ saveTimer.unref?.();
7938
+ }
7939
+ function saveSync() {
7940
+ dirty = false;
7941
+ try {
7942
+ const path = cachePath();
7943
+ const dir = dirname6(path);
7944
+ if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
7945
+ const payload = {
7946
+ version: FILE_VERSION,
7947
+ users: Object.fromEntries(userMap),
7948
+ threads: Object.fromEntries(threadMap)
7949
+ };
7950
+ const tmp = `${path}.tmp`;
7951
+ writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
7952
+ renameSync(tmp, path);
7953
+ } catch (err) {
7954
+ console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
7955
+ }
7956
+ }
7957
+ function hookExit() {
7958
+ if (exitHooked) return;
7959
+ exitHooked = true;
7960
+ const flush2 = () => {
7961
+ if (dirty) saveSync();
7962
+ };
7963
+ process.once("beforeExit", flush2);
7964
+ process.once("SIGINT", flush2);
7965
+ process.once("SIGTERM", flush2);
7966
+ }
7967
+ function getCachedUserName(userId) {
7968
+ load();
7969
+ return userMap.get(userId);
7970
+ }
7971
+ function setCachedUserName(userId, entry2) {
7972
+ load();
7973
+ hookExit();
7974
+ userMap.set(userId, entry2);
7975
+ evictIfOversized(userMap, MAX_USERS);
7976
+ scheduleSave();
7977
+ }
7978
+ function getCachedThreadOwnership(key2) {
7979
+ load();
7980
+ return threadMap.get(key2);
7981
+ }
7982
+ function setCachedThreadOwnership(key2, entry2) {
7983
+ load();
7984
+ hookExit();
7985
+ threadMap.set(key2, entry2);
7986
+ evictIfOversized(threadMap, MAX_THREADS);
7987
+ scheduleSave();
7988
+ }
7989
+ var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
7990
+ var init_persistence = __esm({
7991
+ "src/integrations/slack/persistence.ts"() {
7992
+ "use strict";
7993
+ init_config();
7994
+ FILENAME = "slack-cache.json";
7995
+ FILE_VERSION = 1;
7996
+ SAVE_DEBOUNCE_MS = 500;
7997
+ MAX_USERS = 5e3;
7998
+ MAX_THREADS = 5e3;
7999
+ loaded = false;
8000
+ userMap = /* @__PURE__ */ new Map();
8001
+ threadMap = /* @__PURE__ */ new Map();
8002
+ dirty = false;
8003
+ saveTimer = null;
8004
+ exitHooked = false;
8005
+ }
8006
+ });
8007
+
7815
8008
  // src/integrations/slack/client.ts
7816
8009
  function readSlackConfig() {
7817
8010
  try {
@@ -7934,13 +8127,13 @@ async function fetchSlackUserName(userId) {
7934
8127
  async function resolveSlackUserName(userId) {
7935
8128
  if (!userId) return null;
7936
8129
  const now = Date.now();
7937
- const hit = userNameCache.get(userId);
8130
+ const hit = getCachedUserName(userId);
7938
8131
  if (hit && hit.expiresAt > now) return hit.name;
7939
8132
  const inflight = userInflight.get(userId);
7940
8133
  if (inflight) return inflight;
7941
8134
  const p = (async () => {
7942
8135
  const name = await fetchSlackUserName(userId);
7943
- userNameCache.set(userId, {
8136
+ setCachedUserName(userId, {
7944
8137
  name,
7945
8138
  expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
7946
8139
  });
@@ -7962,11 +8155,63 @@ async function normalizeSlackMentions(text) {
7962
8155
  }
7963
8156
  return text.replace(userMentionRe, (_full, id, label) => {
7964
8157
  if (label) return `${label} <@${id}>`;
7965
- const cached = userNameCache.get(id);
8158
+ const cached = getCachedUserName(id);
7966
8159
  const name = cached?.name;
7967
8160
  return name ? `${name} <@${id}>` : `<@${id}>`;
7968
8161
  });
7969
8162
  }
8163
+ function threadCacheKey(channel, threadTs) {
8164
+ return `${channel}\u241F${threadTs}`;
8165
+ }
8166
+ async function fetchBotParticipatedInThread(channel, threadTs) {
8167
+ const token = getSlackBotToken();
8168
+ if (!token) return false;
8169
+ const self = await ensureSlackSelfIdentity();
8170
+ if (!self) return false;
8171
+ try {
8172
+ const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
8173
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
8174
+ const data = await res.json().catch(() => ({}));
8175
+ if (!data?.ok) {
8176
+ console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
8177
+ return false;
8178
+ }
8179
+ const messages = Array.isArray(data.messages) ? data.messages : [];
8180
+ return messages.some(
8181
+ (m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
8182
+ );
8183
+ } catch (err) {
8184
+ console.warn(`[slack] conversations.replies error:`, err?.message || err);
8185
+ return false;
8186
+ }
8187
+ }
8188
+ async function botParticipatedInThread(channel, threadTs) {
8189
+ if (!channel || !threadTs) return false;
8190
+ const key2 = threadCacheKey(channel, threadTs);
8191
+ const now = Date.now();
8192
+ const hit = getCachedThreadOwnership(key2);
8193
+ if (hit && hit.expiresAt > now) return hit.owned;
8194
+ const inflight = threadOwnedInflight.get(key2);
8195
+ if (inflight) return inflight;
8196
+ const p = (async () => {
8197
+ const owned = await fetchBotParticipatedInThread(channel, threadTs);
8198
+ setCachedThreadOwnership(key2, {
8199
+ owned,
8200
+ expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
8201
+ });
8202
+ threadOwnedInflight.delete(key2);
8203
+ return owned;
8204
+ })();
8205
+ threadOwnedInflight.set(key2, p);
8206
+ return p;
8207
+ }
8208
+ function noteBotPostedInThread(channel, threadTs) {
8209
+ if (!channel || !threadTs) return;
8210
+ setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
8211
+ owned: true,
8212
+ expiresAt: Date.now() + THREAD_OWNED_TTL_MS
8213
+ });
8214
+ }
7970
8215
  function getSlackDeniedReplyPolicy() {
7971
8216
  try {
7972
8217
  const cfg = getConfig();
@@ -7979,17 +8224,20 @@ function getSlackDeniedReplyPolicy() {
7979
8224
  return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
7980
8225
  }
7981
8226
  }
7982
- var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userNameCache, userInflight, DEFAULT_DENIED_TEMPLATE;
8227
+ var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
7983
8228
  var init_client3 = __esm({
7984
8229
  "src/integrations/slack/client.ts"() {
7985
8230
  "use strict";
7986
8231
  init_config();
8232
+ init_persistence();
7987
8233
  cachedSelf = null;
7988
8234
  selfInflight = null;
7989
8235
  USER_TTL_MS = 60 * 60 * 1e3;
7990
8236
  USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7991
- userNameCache = /* @__PURE__ */ new Map();
7992
8237
  userInflight = /* @__PURE__ */ new Map();
8238
+ THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
8239
+ THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
8240
+ threadOwnedInflight = /* @__PURE__ */ new Map();
7993
8241
  DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
7994
8242
  }
7995
8243
  });
@@ -8089,6 +8337,7 @@ var init_slack = __esm({
8089
8337
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
8090
8338
  if (r.slackChannel && r.threadTs) {
8091
8339
  markThreadOwned(r.slackChannel, r.threadTs);
8340
+ noteBotPostedInThread(r.slackChannel, r.threadTs);
8092
8341
  }
8093
8342
  },
8094
8343
  displayLabel(ref) {
@@ -8746,8 +8995,8 @@ var init_orchestrator_actions = __esm({
8746
8995
 
8747
8996
  // src/integrations/mcp/store.ts
8748
8997
  import { nanoid as nanoid6 } from "nanoid";
8749
- import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
8750
- import { resolve as resolve10, join as join9 } from "path";
8998
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
8999
+ import { resolve as resolve10, join as join10 } from "path";
8751
9000
  function readServers() {
8752
9001
  try {
8753
9002
  const cfg = getConfig();
@@ -8759,12 +9008,12 @@ function readServers() {
8759
9008
  function refreshMcpServersFromDisk() {
8760
9009
  const candidates = [
8761
9010
  resolve10(process.cwd(), "sparkecoder.config.json"),
8762
- join9(ensureAppDataDirectory(), "sparkecoder.config.json")
9011
+ join10(ensureAppDataDirectory(), "sparkecoder.config.json")
8763
9012
  ];
8764
9013
  for (const path of candidates) {
8765
- if (!existsSync16(path)) continue;
9014
+ if (!existsSync17(path)) continue;
8766
9015
  try {
8767
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
9016
+ const raw = JSON.parse(readFileSync8(path, "utf-8"));
8768
9017
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
8769
9018
  setMcpServers(servers2);
8770
9019
  return servers2;
@@ -9377,7 +9626,7 @@ __export(recorder_exports, {
9377
9626
  import { exec as exec5 } from "child_process";
9378
9627
  import { promisify as promisify5 } from "util";
9379
9628
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
9380
- import { join as join10 } from "path";
9629
+ import { join as join11 } from "path";
9381
9630
  import { tmpdir } from "os";
9382
9631
  import { nanoid as nanoid7 } from "nanoid";
9383
9632
  async function checkFfmpeg() {
@@ -9434,21 +9683,21 @@ var init_recorder = __esm({
9434
9683
  */
9435
9684
  async encode() {
9436
9685
  if (this.frames.length === 0) return null;
9437
- const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9686
+ const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
9438
9687
  await mkdir4(workDir, { recursive: true });
9439
9688
  try {
9440
9689
  for (let i = 0; i < this.frames.length; i++) {
9441
- const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9690
+ const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
9442
9691
  await writeFile5(framePath, this.frames[i].data);
9443
9692
  }
9444
9693
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
9445
9694
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
9446
9695
  const clampedFps = Math.max(1, Math.min(fps, 30));
9447
- const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
9696
+ const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
9448
9697
  const hasFfmpeg = await checkFfmpeg();
9449
9698
  if (hasFfmpeg) {
9450
9699
  await execAsync5(
9451
- `ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9700
+ `ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
9452
9701
  { timeout: 12e4 }
9453
9702
  );
9454
9703
  } else {
@@ -9460,7 +9709,7 @@ var init_recorder = __esm({
9460
9709
  const files = await readdir5(workDir);
9461
9710
  for (const f of files) {
9462
9711
  if (f.startsWith("frame_")) {
9463
- await unlink2(join10(workDir, f)).catch(() => {
9712
+ await unlink2(join11(workDir, f)).catch(() => {
9464
9713
  });
9465
9714
  }
9466
9715
  }
@@ -9701,7 +9950,7 @@ ${prompt}` });
9701
9950
  const config = getConfig();
9702
9951
  const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
9703
9952
  if (!options.skipSaveUserMessage) {
9704
- this.context.addUserMessage(userContent);
9953
+ await this.context.addUserMessage(userContent);
9705
9954
  }
9706
9955
  await sessionQueries.updateStatus(this.session.id, "active");
9707
9956
  let systemPrompt = await buildSystemPrompt({
@@ -9727,7 +9976,8 @@ ${personality.trim()}`;
9727
9976
  }
9728
9977
  const messages = await this.context.getMessages();
9729
9978
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9730
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
9979
+ const approvalWrapped = this.wrapToolsWithApproval(options, tools);
9980
+ const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
9731
9981
  const useAnthropic = isAnthropicModel(this.session.model);
9732
9982
  const stream = streamText2({
9733
9983
  model: resolveModel(this.session.model),
@@ -9741,6 +9991,18 @@ ${personality.trim()}`;
9741
9991
  providerOptions: useAnthropic ? {
9742
9992
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9743
9993
  } : void 0,
9994
+ // Run repairToolPairing before EVERY step's model call, not just the
9995
+ // first one. The AI SDK's multi-step loop can otherwise feed the model
9996
+ // a prompt containing an orphan tool-call (e.g. when a previous step's
9997
+ // tool result was lost, dropped during compaction, or the stream was
9998
+ // aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
9999
+ // ever reaches the model and we never hit AI_MissingToolResultsError.
10000
+ prepareStep: async ({ messages: stepMessages }) => {
10001
+ const paired = repairToolPairing(stepMessages);
10002
+ const ordered = ensureToolResultsFollowCalls(paired);
10003
+ if (ordered === stepMessages) return {};
10004
+ return { messages: ordered };
10005
+ },
9744
10006
  onStepFinish: async (step) => {
9745
10007
  options.onStepFinish?.(step);
9746
10008
  },
@@ -9752,7 +10014,7 @@ ${personality.trim()}`;
9752
10014
  const result = await stream;
9753
10015
  const response = await result.response;
9754
10016
  const responseMessages = response.messages;
9755
- this.context.addResponseMessages(responseMessages);
10017
+ await this.context.addResponseMessages(responseMessages);
9756
10018
  };
9757
10019
  return {
9758
10020
  sessionId: this.session.id,
@@ -9766,7 +10028,7 @@ ${personality.trim()}`;
9766
10028
  */
9767
10029
  async run(options) {
9768
10030
  const config = getConfig();
9769
- this.context.addUserMessage(options.prompt);
10031
+ await this.context.addUserMessage(options.prompt);
9770
10032
  const systemPrompt = await buildSystemPrompt({
9771
10033
  workingDirectory: this.session.workingDirectory,
9772
10034
  skillsDirectories: config.resolvedSkillsDirectories,
@@ -9776,7 +10038,7 @@ ${personality.trim()}`;
9776
10038
  });
9777
10039
  const messages = await this.context.getMessages();
9778
10040
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
9779
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
10041
+ const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
9780
10042
  const useAnthropic = isAnthropicModel(this.session.model);
9781
10043
  const result = await generateText3({
9782
10044
  model: resolveModel(this.session.model),
@@ -9787,10 +10049,17 @@ ${personality.trim()}`;
9787
10049
  // Enable extended thinking/reasoning for models that support it
9788
10050
  providerOptions: useAnthropic ? {
9789
10051
  anthropic: getAnthropicProviderOptions(this.session.model)
9790
- } : void 0
10052
+ } : void 0,
10053
+ // Repair tool pairing before every step (see `stream()` for full rationale).
10054
+ prepareStep: async ({ messages: stepMessages }) => {
10055
+ const paired = repairToolPairing(stepMessages);
10056
+ const ordered = ensureToolResultsFollowCalls(paired);
10057
+ if (ordered === stepMessages) return {};
10058
+ return { messages: ordered };
10059
+ }
9791
10060
  });
9792
10061
  const responseMessages = result.response.messages;
9793
- this.context.addResponseMessages(responseMessages);
10062
+ await this.context.addResponseMessages(responseMessages);
9794
10063
  return {
9795
10064
  text: result.text,
9796
10065
  steps: result.steps
@@ -9964,12 +10233,20 @@ ${p.text}` : p.text;
9964
10233
  model: resolveModel(this.session.model),
9965
10234
  system: systemPrompt,
9966
10235
  messages,
9967
- tools: taskTools,
10236
+ tools: wrapToolsNeverThrow(taskTools),
9968
10237
  stopWhen: stepCountIs2(500),
9969
10238
  abortSignal: combinedAbort,
9970
10239
  providerOptions: useAnthropic ? {
9971
10240
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9972
10241
  } : void 0,
10242
+ // See the matching note in `stream()` — repair tool pairing before
10243
+ // every step so we never feed the model an orphan tool-call.
10244
+ prepareStep: async ({ messages: stepMessages }) => {
10245
+ const paired = repairToolPairing(stepMessages);
10246
+ const ordered = ensureToolResultsFollowCalls(paired);
10247
+ if (ordered === stepMessages) return {};
10248
+ return { messages: ordered };
10249
+ },
9973
10250
  onStepFinish: async (step) => {
9974
10251
  options.onStepFinish?.(step);
9975
10252
  fireWebhook("task.step_finished", { iteration, text: step.text });
@@ -10203,11 +10480,11 @@ ${p.text}` : p.text;
10203
10480
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10204
10481
  if (!isRemoteConfigured2()) return [];
10205
10482
  const { readFile: readFile13 } = await import("fs/promises");
10206
- const { join: join17, basename: basename7 } = await import("path");
10483
+ const { join: join18, basename: basename7 } = await import("path");
10207
10484
  const urls = [];
10208
10485
  for (const filePath of filePaths) {
10209
10486
  try {
10210
- const fullPath = filePath.startsWith("/") ? filePath : join17(this.session.workingDirectory, filePath);
10487
+ const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
10211
10488
  const fileName = basename7(fullPath);
10212
10489
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
10213
10490
  const mimeMap = {
@@ -10412,19 +10689,19 @@ var init_session_lock = __esm({
10412
10689
  });
10413
10690
 
10414
10691
  // src/orchestrator/webhook-events.ts
10415
- import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
10416
- import { dirname as dirname6, join as join11 } from "path";
10692
+ import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
10693
+ import { dirname as dirname7, join as join12 } from "path";
10417
10694
  import { nanoid as nanoid9 } from "nanoid";
10418
10695
  function logFilePath() {
10419
- return join11(getAppDataDirectory(), "webhook-events.jsonl");
10696
+ return join12(getAppDataDirectory(), "webhook-events.jsonl");
10420
10697
  }
10421
10698
  function ensureLoaded() {
10422
10699
  if (cache !== null) return cache;
10423
10700
  cache = [];
10424
10701
  try {
10425
10702
  const p = logFilePath();
10426
- if (!existsSync17(p)) return cache;
10427
- const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
10703
+ if (!existsSync18(p)) return cache;
10704
+ const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
10428
10705
  for (const line of lines) {
10429
10706
  try {
10430
10707
  cache.push(JSON.parse(line));
@@ -10434,7 +10711,7 @@ function ensureLoaded() {
10434
10711
  if (cache.length > MAX_EVENTS) {
10435
10712
  cache = cache.slice(-MAX_EVENTS);
10436
10713
  try {
10437
- writeFileSync3(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10714
+ writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
10438
10715
  } catch {
10439
10716
  }
10440
10717
  }
@@ -10448,7 +10725,7 @@ function appendEvent(ev) {
10448
10725
  if (list.length > MAX_EVENTS) list.shift();
10449
10726
  try {
10450
10727
  const p = logFilePath();
10451
- mkdirSync6(dirname6(p), { recursive: true });
10728
+ mkdirSync7(dirname7(p), { recursive: true });
10452
10729
  appendFileSync3(p, JSON.stringify(ev) + "\n");
10453
10730
  } catch {
10454
10731
  }
@@ -10479,8 +10756,8 @@ function updateEvent(id, patch) {
10479
10756
  list[i] = { ...list[i], ...patch };
10480
10757
  try {
10481
10758
  const p = logFilePath();
10482
- mkdirSync6(dirname6(p), { recursive: true });
10483
- writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10759
+ mkdirSync7(dirname7(p), { recursive: true });
10760
+ writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
10484
10761
  } catch {
10485
10762
  }
10486
10763
  }
@@ -10512,7 +10789,7 @@ function listEvents(filter = {}) {
10512
10789
  function clearAllEvents() {
10513
10790
  cache = [];
10514
10791
  try {
10515
- writeFileSync3(logFilePath(), "");
10792
+ writeFileSync4(logFilePath(), "");
10516
10793
  } catch {
10517
10794
  }
10518
10795
  }
@@ -10786,8 +11063,8 @@ import { Hono as Hono10 } from "hono";
10786
11063
  import { serve } from "@hono/node-server";
10787
11064
  import { cors } from "hono/cors";
10788
11065
  import { logger } from "hono/logger";
10789
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
10790
- import { resolve as resolve12, dirname as dirname9, join as join16 } from "path";
11066
+ import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
11067
+ import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
10791
11068
  import { spawn as spawn2 } from "child_process";
10792
11069
  import { createServer as createNetServer } from "net";
10793
11070
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -10801,9 +11078,9 @@ init_checkpoints();
10801
11078
  import { Hono } from "hono";
10802
11079
  import { zValidator } from "@hono/zod-validator";
10803
11080
  import { z as z16 } from "zod";
10804
- import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
11081
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
10805
11082
  import { readdir as readdir6 } from "fs/promises";
10806
- import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
11083
+ import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
10807
11084
  import { nanoid as nanoid10 } from "nanoid";
10808
11085
 
10809
11086
  // src/tasks/agent-status.ts
@@ -11441,12 +11718,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
11441
11718
  });
11442
11719
  function getAttachmentsDir(sessionId) {
11443
11720
  const appDataDir = getAppDataDirectory();
11444
- return join12(appDataDir, "attachments", sessionId);
11721
+ return join13(appDataDir, "attachments", sessionId);
11445
11722
  }
11446
11723
  function ensureAttachmentsDir(sessionId) {
11447
11724
  const dir = getAttachmentsDir(sessionId);
11448
- if (!existsSync18(dir)) {
11449
- mkdirSync7(dir, { recursive: true });
11725
+ if (!existsSync19(dir)) {
11726
+ mkdirSync8(dir, { recursive: true });
11450
11727
  }
11451
11728
  return dir;
11452
11729
  }
@@ -11457,12 +11734,12 @@ sessions2.get("/:id/attachments", async (c) => {
11457
11734
  return c.json({ error: "Session not found" }, 404);
11458
11735
  }
11459
11736
  const dir = getAttachmentsDir(sessionId);
11460
- if (!existsSync18(dir)) {
11737
+ if (!existsSync19(dir)) {
11461
11738
  return c.json({ sessionId, attachments: [], count: 0 });
11462
11739
  }
11463
11740
  const files = readdirSync3(dir);
11464
11741
  const attachments = files.map((filename) => {
11465
- const filePath = join12(dir, filename);
11742
+ const filePath = join13(dir, filename);
11466
11743
  const stats = statSync2(filePath);
11467
11744
  return {
11468
11745
  id: filename.split("_")[0],
@@ -11497,9 +11774,9 @@ sessions2.post("/:id/attachments", async (c) => {
11497
11774
  const id = nanoid10(10);
11498
11775
  const ext = extname8(file.name) || "";
11499
11776
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11500
- const filePath = join12(dir, safeFilename);
11777
+ const filePath = join13(dir, safeFilename);
11501
11778
  const arrayBuffer = await file.arrayBuffer();
11502
- writeFileSync4(filePath, Buffer.from(arrayBuffer));
11779
+ writeFileSync5(filePath, Buffer.from(arrayBuffer));
11503
11780
  return c.json({
11504
11781
  id,
11505
11782
  filename: file.name,
@@ -11523,13 +11800,13 @@ sessions2.post("/:id/attachments", async (c) => {
11523
11800
  const id = nanoid10(10);
11524
11801
  const ext = extname8(body.filename) || "";
11525
11802
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
11526
- const filePath = join12(dir, safeFilename);
11803
+ const filePath = join13(dir, safeFilename);
11527
11804
  let base64Data = body.data;
11528
11805
  if (base64Data.includes(",")) {
11529
11806
  base64Data = base64Data.split(",")[1];
11530
11807
  }
11531
11808
  const buffer = Buffer.from(base64Data, "base64");
11532
- writeFileSync4(filePath, buffer);
11809
+ writeFileSync5(filePath, buffer);
11533
11810
  return c.json({
11534
11811
  id,
11535
11812
  filename: body.filename,
@@ -11552,7 +11829,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11552
11829
  return c.json({ error: "Session not found" }, 404);
11553
11830
  }
11554
11831
  const dir = getAttachmentsDir(sessionId);
11555
- if (!existsSync18(dir)) {
11832
+ if (!existsSync19(dir)) {
11556
11833
  return c.json({ error: "Attachment not found" }, 404);
11557
11834
  }
11558
11835
  const files = readdirSync3(dir);
@@ -11560,7 +11837,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
11560
11837
  if (!file) {
11561
11838
  return c.json({ error: "Attachment not found" }, 404);
11562
11839
  }
11563
- const filePath = join12(dir, file);
11840
+ const filePath = join13(dir, file);
11564
11841
  unlinkSync2(filePath);
11565
11842
  return c.json({ success: true, id: attachmentId });
11566
11843
  });
@@ -11643,7 +11920,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
11643
11920
  const entries = await readdir6(currentDir, { withFileTypes: true });
11644
11921
  for (const entry2 of entries) {
11645
11922
  if (results.length >= limit * 2) break;
11646
- const fullPath = join12(currentDir, entry2.name);
11923
+ const fullPath = join13(currentDir, entry2.name);
11647
11924
  const relativePath = relative9(baseDir, fullPath);
11648
11925
  if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
11649
11926
  continue;
@@ -11691,7 +11968,7 @@ sessions2.get(
11691
11968
  return c.json({ error: "Session not found" }, 404);
11692
11969
  }
11693
11970
  const workingDirectory = session.workingDirectory;
11694
- if (!existsSync18(workingDirectory)) {
11971
+ if (!existsSync19(workingDirectory)) {
11695
11972
  return c.json({
11696
11973
  sessionId,
11697
11974
  workingDirectory,
@@ -11799,14 +12076,148 @@ sessions2.get("/:id/browser-recording", async (c) => {
11799
12076
 
11800
12077
  // src/server/routes/agents.ts
11801
12078
  init_db();
11802
- init_agent();
11803
- init_session_lock();
11804
- init_config();
11805
12079
  import { Hono as Hono2 } from "hono";
11806
12080
  import { zValidator as zValidator2 } from "@hono/zod-validator";
11807
12081
  import { z as z17 } from "zod";
11808
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
11809
- import { join as join13 } from "path";
12082
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
12083
+ import { join as join14 } from "path";
12084
+
12085
+ // src/agent/missing-tool-recovery.ts
12086
+ init_db();
12087
+ function extractMissingToolCallIds(error) {
12088
+ if (!error) return [];
12089
+ const e = error;
12090
+ if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
12091
+ return e.toolCallIds;
12092
+ }
12093
+ const msg = typeof e.message === "string" ? e.message : "";
12094
+ const ids = /* @__PURE__ */ new Set();
12095
+ for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
12096
+ for (const id of m[1].split(/\s*,\s*/)) {
12097
+ const trimmed = id.trim();
12098
+ if (trimmed) ids.add(trimmed);
12099
+ }
12100
+ }
12101
+ return [...ids];
12102
+ }
12103
+ function isMissingToolResultsError(error) {
12104
+ if (!error) return false;
12105
+ const e = error;
12106
+ if (e.name === "AI_MissingToolResultsError") return true;
12107
+ if (Array.isArray(e.toolCallIds)) return true;
12108
+ return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
12109
+ }
12110
+ async function recoverFromMissingToolResults(sessionId, error) {
12111
+ const toolCallIds = extractMissingToolCallIds(error);
12112
+ if (toolCallIds.length === 0) return null;
12113
+ let history = [];
12114
+ try {
12115
+ history = await messageQueries.getModelMessages(sessionId);
12116
+ } catch (err) {
12117
+ console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
12118
+ }
12119
+ const toolInfoByCallId = /* @__PURE__ */ new Map();
12120
+ const resultMessageIndexByCallId = /* @__PURE__ */ new Map();
12121
+ const existingResultIds = /* @__PURE__ */ new Set();
12122
+ for (let idx = 0; idx < history.length; idx++) {
12123
+ const msg = history[idx];
12124
+ if (!Array.isArray(msg.content)) continue;
12125
+ for (const part of msg.content) {
12126
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
12127
+ if (typeof part.toolName === "string") {
12128
+ toolInfoByCallId.set(part.toolCallId, { toolName: part.toolName, callMessageIndex: idx });
12129
+ }
12130
+ }
12131
+ if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
12132
+ existingResultIds.add(part.toolCallId);
12133
+ resultMessageIndexByCallId.set(part.toolCallId, idx);
12134
+ }
12135
+ }
12136
+ }
12137
+ const resolved = toolCallIds.map((id) => {
12138
+ const info = toolInfoByCallId.get(id);
12139
+ const resultIdx = resultMessageIndexByCallId.get(id);
12140
+ const wedgedRoles = info && typeof resultIdx === "number" && resultIdx > info.callMessageIndex + 1 ? history.slice(info.callMessageIndex + 1, resultIdx).map((m) => m.role) : void 0;
12141
+ return {
12142
+ toolCallId: id,
12143
+ toolName: info?.toolName ?? "unknown",
12144
+ foundInAssistantMessage: !!info,
12145
+ callMessageIndex: info?.callMessageIndex,
12146
+ resultMessageIndex: resultIdx,
12147
+ wedgedMessageRoles: wedgedRoles
12148
+ };
12149
+ });
12150
+ const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
12151
+ let syntheticToolMessageSaved = false;
12152
+ if (stillOrphaned.length > 0) {
12153
+ const syntheticParts = stillOrphaned.map((id) => ({
12154
+ type: "tool-result",
12155
+ toolCallId: id,
12156
+ toolName: toolInfoByCallId.get(id)?.toolName ?? "unknown",
12157
+ output: {
12158
+ type: "text",
12159
+ 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.]"
12160
+ }
12161
+ }));
12162
+ try {
12163
+ await messageQueries.create(sessionId, {
12164
+ role: "tool",
12165
+ content: syntheticParts
12166
+ });
12167
+ syntheticToolMessageSaved = true;
12168
+ } catch (err) {
12169
+ console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
12170
+ }
12171
+ }
12172
+ const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
12173
+ const first = resolved.find((r) => r.callMessageIndex !== void 0);
12174
+ let contextSnapshot;
12175
+ if (first?.callMessageIndex !== void 0) {
12176
+ const start = Math.max(0, first.callMessageIndex - 1);
12177
+ const end = Math.min(
12178
+ history.length,
12179
+ Math.max(first.callMessageIndex, first.resultMessageIndex ?? first.callMessageIndex) + 2
12180
+ );
12181
+ contextSnapshot = history.slice(start, end).map((m, offset) => ({
12182
+ index: start + offset,
12183
+ role: m.role,
12184
+ summary: summarizeContent(m.content)
12185
+ }));
12186
+ }
12187
+ const anyWedged = resolved.some((r) => r.wedgedMessageRoles && r.wedgedMessageRoles.length > 0);
12188
+ return {
12189
+ kind: "missing_tool_results",
12190
+ toolCallIds,
12191
+ resolved,
12192
+ syntheticToolMessageSaved,
12193
+ lastFewMessageRoles,
12194
+ contextSnapshot,
12195
+ 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."
12196
+ };
12197
+ }
12198
+ function summarizeContent(content) {
12199
+ if (typeof content === "string") {
12200
+ return content.length > 160 ? content.slice(0, 160) + "\u2026" : content;
12201
+ }
12202
+ if (!Array.isArray(content)) return String(content);
12203
+ const parts = content.map((p) => {
12204
+ if (!p || typeof p !== "object") return String(p);
12205
+ if (p.type === "text") {
12206
+ const t = String(p.text ?? "");
12207
+ return `text(${t.length > 80 ? t.slice(0, 80) + "\u2026" : t})`;
12208
+ }
12209
+ if (p.type === "tool-call") return `tool-call(${p.toolName}:${p.toolCallId})`;
12210
+ if (p.type === "tool-result") return `tool-result(${p.toolName ?? "?"}:${p.toolCallId})`;
12211
+ if (p.type === "reasoning") return "reasoning";
12212
+ return p.type ?? "unknown";
12213
+ });
12214
+ return parts.join(", ");
12215
+ }
12216
+
12217
+ // src/server/routes/agents.ts
12218
+ init_agent();
12219
+ init_session_lock();
12220
+ init_config();
11810
12221
 
11811
12222
  // src/server/resumable-stream.ts
11812
12223
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -12013,12 +12424,12 @@ var rejectSchema = z17.object({
12013
12424
  var streamAbortControllers = /* @__PURE__ */ new Map();
12014
12425
  function getAttachmentsDirectory(sessionId) {
12015
12426
  const appDataDir = getAppDataDirectory();
12016
- return join13(appDataDir, "attachments", sessionId);
12427
+ return join14(appDataDir, "attachments", sessionId);
12017
12428
  }
12018
12429
  async function saveAttachmentToDisk(sessionId, attachment, index) {
12019
12430
  const attachmentsDir = getAttachmentsDirectory(sessionId);
12020
- if (!existsSync19(attachmentsDir)) {
12021
- mkdirSync8(attachmentsDir, { recursive: true });
12431
+ if (!existsSync20(attachmentsDir)) {
12432
+ mkdirSync9(attachmentsDir, { recursive: true });
12022
12433
  }
12023
12434
  let filename = attachment.filename;
12024
12435
  if (!filename) {
@@ -12036,8 +12447,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
12036
12447
  attachment.mediaType = resized.mediaType;
12037
12448
  attachment.data = buffer.toString("base64");
12038
12449
  }
12039
- const filePath = join13(attachmentsDir, filename);
12040
- writeFileSync5(filePath, buffer);
12450
+ const filePath = join14(attachmentsDir, filename);
12451
+ writeFileSync6(filePath, buffer);
12041
12452
  return filePath;
12042
12453
  }
12043
12454
  function stripDataUrlPrefix2(data) {
@@ -12374,7 +12785,20 @@ ${prompt}` });
12374
12785
  await writeSSE(JSON.stringify({ type: "abort" }));
12375
12786
  } else {
12376
12787
  console.error("Agent error:", error);
12377
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
12788
+ let debugPayload = void 0;
12789
+ if (isMissingToolResultsError(error)) {
12790
+ try {
12791
+ const recovery = await recoverFromMissingToolResults(sessionId, error);
12792
+ if (recovery) debugPayload = recovery;
12793
+ } catch (recErr) {
12794
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
12795
+ }
12796
+ }
12797
+ await writeSSE(JSON.stringify({
12798
+ type: "error",
12799
+ errorText: error.message,
12800
+ ...debugPayload ? { debug: debugPayload } : {}
12801
+ }));
12378
12802
  try {
12379
12803
  await activeStreamQueries.markError(streamId);
12380
12804
  } catch {
@@ -12914,7 +13338,20 @@ agents.post(
12914
13338
  await writeSSE(JSON.stringify({ type: "abort" }));
12915
13339
  } else {
12916
13340
  console.error("Agent error:", error);
12917
- await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
13341
+ let debugPayload = void 0;
13342
+ if (isMissingToolResultsError(error)) {
13343
+ try {
13344
+ const recovery = await recoverFromMissingToolResults(session.id, error);
13345
+ if (recovery) debugPayload = recovery;
13346
+ } catch (recErr) {
13347
+ console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
13348
+ }
13349
+ }
13350
+ await writeSSE(JSON.stringify({
13351
+ type: "error",
13352
+ errorText: error.message,
13353
+ ...debugPayload ? { debug: debugPayload } : {}
13354
+ }));
12918
13355
  await activeStreamQueries.markError(streamId);
12919
13356
  }
12920
13357
  } finally {
@@ -12997,26 +13434,26 @@ init_config();
12997
13434
  import { Hono as Hono3 } from "hono";
12998
13435
  import { zValidator as zValidator3 } from "@hono/zod-validator";
12999
13436
  import { z as z18 } from "zod";
13000
- import { readFileSync as readFileSync9 } from "fs";
13437
+ import { readFileSync as readFileSync10 } from "fs";
13001
13438
  import { fileURLToPath as fileURLToPath3 } from "url";
13002
- import { dirname as dirname7, join as join14 } from "path";
13439
+ import { dirname as dirname8, join as join15 } from "path";
13003
13440
  var __filename = fileURLToPath3(import.meta.url);
13004
- var __dirname = dirname7(__filename);
13441
+ var __dirname = dirname8(__filename);
13005
13442
  var possiblePaths = [
13006
- join14(__dirname, "../package.json"),
13443
+ join15(__dirname, "../package.json"),
13007
13444
  // From dist/server -> dist/../package.json
13008
- join14(__dirname, "../../package.json"),
13445
+ join15(__dirname, "../../package.json"),
13009
13446
  // From dist/server (if nested differently)
13010
- join14(__dirname, "../../../package.json"),
13447
+ join15(__dirname, "../../../package.json"),
13011
13448
  // From src/server/routes (development)
13012
- join14(process.cwd(), "package.json")
13449
+ join15(process.cwd(), "package.json")
13013
13450
  // From current working directory
13014
13451
  ];
13015
13452
  var currentVersion = "0.0.0";
13016
13453
  var packageName = "sparkecoder";
13017
13454
  for (const packageJsonPath of possiblePaths) {
13018
13455
  try {
13019
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
13456
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
13020
13457
  if (packageJson.name === "sparkecoder") {
13021
13458
  currentVersion = packageJson.version || "0.0.0";
13022
13459
  packageName = packageJson.name || "sparkecoder";
@@ -13834,12 +14271,13 @@ slack.post("/events", async (c) => {
13834
14271
  if (inbound) {
13835
14272
  const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
13836
14273
  if (isThreadReply) {
13837
- const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
14274
+ const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
13838
14275
  if (!ours) {
13839
14276
  console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
13840
14277
  updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
13841
14278
  return c.json({ ok: true });
13842
14279
  }
14280
+ markThreadOwned(ev.channel, ev.thread_ts);
13843
14281
  }
13844
14282
  if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
13845
14283
  markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
@@ -13878,18 +14316,6 @@ slack.post("/events", async (c) => {
13878
14316
  }
13879
14317
  return c.json({ ok: true });
13880
14318
  });
13881
- async function threadBelongsToUs(channel, threadTs) {
13882
- try {
13883
- const sessions3 = await sessionQueries.list(500, 0);
13884
- return sessions3.some((s) => {
13885
- const slack2 = s.config?.slack;
13886
- return slack2?.channel === channel && slack2?.threadTs === threadTs;
13887
- });
13888
- } catch (err) {
13889
- console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
13890
- return false;
13891
- }
13892
- }
13893
14319
  async function findOrCreateOrchestratorId() {
13894
14320
  try {
13895
14321
  const all = await sessionQueries.list(500, 0);
@@ -14278,9 +14704,9 @@ init_skills();
14278
14704
  import { Hono as Hono9 } from "hono";
14279
14705
  import { zValidator as zValidator7 } from "@hono/zod-validator";
14280
14706
  import { z as z22 } from "zod";
14281
- import { existsSync as existsSync20, statSync as statSync3 } from "fs";
14707
+ import { existsSync as existsSync21, statSync as statSync3 } from "fs";
14282
14708
  import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
14283
- import { resolve as resolve11, join as join15, basename as basename6, dirname as dirname8, extname as extname9 } from "path";
14709
+ import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
14284
14710
  var skills = new Hono9();
14285
14711
  function encodeId(filePath) {
14286
14712
  return Buffer.from(filePath, "utf-8").toString("base64url");
@@ -14339,13 +14765,13 @@ function pathToLabel(path) {
14339
14765
  if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
14340
14766
  if (path.includes("/.cursor/rules")) return ".cursor/rules";
14341
14767
  if (path.includes("/.claude/skills")) return ".claude/skills";
14342
- return basename6(dirname8(path)) + "/" + basename6(path);
14768
+ return basename6(dirname9(path)) + "/" + basename6(path);
14343
14769
  }
14344
14770
  skills.get("/", async (c) => {
14345
14771
  const dirs = listAllDirectories();
14346
14772
  const allSkills = [];
14347
14773
  for (const dir of dirs) {
14348
- if (!existsSync20(dir.path)) continue;
14774
+ if (!existsSync21(dir.path)) continue;
14349
14775
  try {
14350
14776
  const list = await loadSkillsFromDirectory(dir.path, {
14351
14777
  priority: dir.priority,
@@ -14382,7 +14808,7 @@ skills.get("/", async (c) => {
14382
14808
  label: d.label,
14383
14809
  source: d.source,
14384
14810
  alwaysApply: d.alwaysApply,
14385
- exists: existsSync20(d.path),
14811
+ exists: existsSync21(d.path),
14386
14812
  writable: isWritable(d.path)
14387
14813
  })),
14388
14814
  skills: allSkills
@@ -14390,7 +14816,7 @@ skills.get("/", async (c) => {
14390
14816
  });
14391
14817
  function isWritable(dir) {
14392
14818
  try {
14393
- if (!existsSync20(dir)) return false;
14819
+ if (!existsSync21(dir)) return false;
14394
14820
  if (dir.includes("/skills/default")) return false;
14395
14821
  return true;
14396
14822
  } catch {
@@ -14399,7 +14825,7 @@ function isWritable(dir) {
14399
14825
  }
14400
14826
  skills.get("/:id", async (c) => {
14401
14827
  const filePath = decodeId(c.req.param("id"));
14402
- if (!filePath || !existsSync20(filePath)) {
14828
+ if (!filePath || !existsSync21(filePath)) {
14403
14829
  return c.json({ error: "skill not found" }, 404);
14404
14830
  }
14405
14831
  const content = await readFile12(filePath, "utf-8");
@@ -14428,8 +14854,8 @@ skills.post(
14428
14854
  const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
14429
14855
  const ext = extname9(safeName).toLowerCase();
14430
14856
  const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
14431
- const filePath = join15(targetDir, finalName);
14432
- if (existsSync20(filePath)) {
14857
+ const filePath = join16(targetDir, finalName);
14858
+ if (existsSync21(filePath)) {
14433
14859
  return c.json({ error: `file already exists: ${finalName}` }, 409);
14434
14860
  }
14435
14861
  try {
@@ -14446,7 +14872,7 @@ skills.put(
14446
14872
  zValidator7("json", z22.object({ content: z22.string() })),
14447
14873
  async (c) => {
14448
14874
  const filePath = decodeId(c.req.param("id"));
14449
- if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14875
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14450
14876
  if (filePath.includes("/skills/default")) {
14451
14877
  return c.json({ error: "built-in skills are read-only" }, 400);
14452
14878
  }
@@ -14456,7 +14882,7 @@ skills.put(
14456
14882
  );
14457
14883
  skills.delete("/:id", async (c) => {
14458
14884
  const filePath = decodeId(c.req.param("id"));
14459
- if (!filePath || !existsSync20(filePath)) return c.json({ error: "skill not found" }, 404);
14885
+ if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
14460
14886
  if (filePath.includes("/skills/default")) {
14461
14887
  return c.json({ error: "built-in skills are read-only" }, 400);
14462
14888
  }
@@ -14476,7 +14902,7 @@ skills.post(
14476
14902
  }
14477
14903
  const next = [...current, abs];
14478
14904
  setSkillsAdditionalDirectories(next);
14479
- return c.json({ ok: true, path: abs, exists: existsSync20(abs) }, 201);
14905
+ return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
14480
14906
  }
14481
14907
  );
14482
14908
  skills.delete("/directories", (c) => {
@@ -14650,13 +15076,13 @@ var DEFAULT_WEB_PORT = 6969;
14650
15076
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
14651
15077
  function getWebDirectory() {
14652
15078
  try {
14653
- const currentDir = dirname9(fileURLToPath4(import.meta.url));
15079
+ const currentDir = dirname10(fileURLToPath4(import.meta.url));
14654
15080
  const webDir = resolve12(currentDir, "..", "web");
14655
- if (existsSync21(webDir) && existsSync21(join16(webDir, "package.json"))) {
15081
+ if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
14656
15082
  return webDir;
14657
15083
  }
14658
15084
  const altWebDir = resolve12(currentDir, "..", "..", "web");
14659
- if (existsSync21(altWebDir) && existsSync21(join16(altWebDir, "package.json"))) {
15085
+ if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
14660
15086
  return altWebDir;
14661
15087
  }
14662
15088
  return null;
@@ -14714,23 +15140,23 @@ async function findWebPort(preferredPort) {
14714
15140
  return { port: preferredPort, alreadyRunning: false };
14715
15141
  }
14716
15142
  function hasProductionBuild(webDir) {
14717
- const buildIdPath = join16(webDir, ".next", "BUILD_ID");
14718
- return existsSync21(buildIdPath);
15143
+ const buildIdPath = join17(webDir, ".next", "BUILD_ID");
15144
+ return existsSync22(buildIdPath);
14719
15145
  }
14720
15146
  function hasSourceFiles(webDir) {
14721
- const appDir = join16(webDir, "src", "app");
14722
- const pagesDir = join16(webDir, "src", "pages");
14723
- const rootAppDir = join16(webDir, "app");
14724
- const rootPagesDir = join16(webDir, "pages");
14725
- return existsSync21(appDir) || existsSync21(pagesDir) || existsSync21(rootAppDir) || existsSync21(rootPagesDir);
15147
+ const appDir = join17(webDir, "src", "app");
15148
+ const pagesDir = join17(webDir, "src", "pages");
15149
+ const rootAppDir = join17(webDir, "app");
15150
+ const rootPagesDir = join17(webDir, "pages");
15151
+ return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
14726
15152
  }
14727
15153
  function getStandaloneServerPath(webDir) {
14728
15154
  const possiblePaths2 = [
14729
- join16(webDir, ".next", "standalone", "server.js"),
14730
- join16(webDir, ".next", "standalone", "web", "server.js")
15155
+ join17(webDir, ".next", "standalone", "server.js"),
15156
+ join17(webDir, ".next", "standalone", "web", "server.js")
14731
15157
  ];
14732
15158
  for (const serverPath of possiblePaths2) {
14733
- if (existsSync21(serverPath)) {
15159
+ if (existsSync22(serverPath)) {
14734
15160
  return serverPath;
14735
15161
  }
14736
15162
  }
@@ -14770,15 +15196,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14770
15196
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
14771
15197
  return { process: null, port: actualPort };
14772
15198
  }
14773
- const usePnpm = existsSync21(join16(webDir, "pnpm-lock.yaml"));
14774
- const useNpm = !usePnpm && existsSync21(join16(webDir, "package-lock.json"));
15199
+ const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
15200
+ const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
14775
15201
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
14776
15202
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
14777
15203
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
14778
15204
  const runtimeConfig = { apiBaseUrl: apiUrl };
14779
- const runtimeConfigPath = join16(webDir, "runtime-config.json");
15205
+ const runtimeConfigPath = join17(webDir, "runtime-config.json");
14780
15206
  try {
14781
- writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
15207
+ writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
14782
15208
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
14783
15209
  } catch (err) {
14784
15210
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -14798,7 +15224,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
14798
15224
  if (standaloneServerPath) {
14799
15225
  command = "node";
14800
15226
  args = ["server.js"];
14801
- cwd = dirname9(standaloneServerPath);
15227
+ cwd = dirname10(standaloneServerPath);
14802
15228
  webEnv.PORT = String(actualPort);
14803
15229
  webEnv.HOSTNAME = "0.0.0.0";
14804
15230
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -14992,8 +15418,8 @@ async function startServer(options = {}) {
14992
15418
  if (options.workingDirectory) {
14993
15419
  config.resolvedWorkingDirectory = options.workingDirectory;
14994
15420
  }
14995
- if (!existsSync21(config.resolvedWorkingDirectory)) {
14996
- mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
15421
+ if (!existsSync22(config.resolvedWorkingDirectory)) {
15422
+ mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
14997
15423
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
14998
15424
  }
14999
15425
  if (!config.resolvedRemoteServer.url) {