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
@@ -1,6 +1,6 @@
1
1
  import 'ai';
2
- import '../schema-DxrKyetI.js';
3
- export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, M as MessageAttachment, d as buildSystemPrompt, e as buildTaskPromptAddendum } from '../index-DczYH89U.js';
4
- import '../search-CVVfuBPZ.js';
2
+ import '../schema-BWbWmfDQ.js';
3
+ export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, M as MessageAttachment, d as buildSystemPrompt, e as buildTaskPromptAddendum } from '../index-Bcz0aCAR.js';
4
+ import '../search-DOzC4ojH.js';
5
5
  import 'drizzle-orm/sqlite-core';
6
6
  import 'zod';
@@ -1894,7 +1894,7 @@ __export(recorder_exports, {
1894
1894
  import { exec as exec5 } from "child_process";
1895
1895
  import { promisify as promisify5 } from "util";
1896
1896
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
1897
- import { join as join10 } from "path";
1897
+ import { join as join11 } from "path";
1898
1898
  import { tmpdir } from "os";
1899
1899
  import { nanoid as nanoid7 } from "nanoid";
1900
1900
  async function checkFfmpeg() {
@@ -1951,21 +1951,21 @@ var init_recorder = __esm({
1951
1951
  */
1952
1952
  async encode() {
1953
1953
  if (this.frames.length === 0) return null;
1954
- const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
1954
+ const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
1955
1955
  await mkdir4(workDir, { recursive: true });
1956
1956
  try {
1957
1957
  for (let i = 0; i < this.frames.length; i++) {
1958
- const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
1958
+ const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
1959
1959
  await writeFile5(framePath, this.frames[i].data);
1960
1960
  }
1961
1961
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
1962
1962
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
1963
1963
  const clampedFps = Math.max(1, Math.min(fps, 30));
1964
- const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
1964
+ const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
1965
1965
  const hasFfmpeg = await checkFfmpeg();
1966
1966
  if (hasFfmpeg) {
1967
1967
  await execAsync5(
1968
- `ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
1968
+ `ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
1969
1969
  { timeout: 12e4 }
1970
1970
  );
1971
1971
  } else {
@@ -1977,7 +1977,7 @@ var init_recorder = __esm({
1977
1977
  const files = await readdir5(workDir);
1978
1978
  for (const f of files) {
1979
1979
  if (f.startsWith("frame_")) {
1980
- await unlink2(join10(workDir, f)).catch(() => {
1980
+ await unlink2(join11(workDir, f)).catch(() => {
1981
1981
  });
1982
1982
  }
1983
1983
  }
@@ -2835,10 +2835,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2835
2835
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
2836
2836
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
2837
2837
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
2838
- const cachePath = join3(cacheDir, key2 + ext);
2839
- if (existsSync3(cachePath)) {
2838
+ const cachePath2 = join3(cacheDir, key2 + ext);
2839
+ if (existsSync3(cachePath2)) {
2840
2840
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
2841
- return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
2841
+ return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
2842
2842
  }
2843
2843
  let pipeline = sharp(buffer);
2844
2844
  if (needsResize) {
@@ -2863,7 +2863,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
2863
2863
  }
2864
2864
  finalMediaType = "image/jpeg";
2865
2865
  }
2866
- writeFileSync2(cachePath, result);
2866
+ writeFileSync2(cachePath2, result);
2867
2867
  const resultMeta = await sharp(result).metadata();
2868
2868
  console.log(
2869
2869
  `[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
@@ -6865,6 +6865,7 @@ ${summaryContent}`
6865
6865
  ];
6866
6866
  }
6867
6867
  messages = repairToolPairing(messages);
6868
+ messages = ensureToolResultsFollowCalls(messages);
6868
6869
  messages = ensureEndsWithUserOrTool(messages);
6869
6870
  return messages;
6870
6871
  }
@@ -7059,7 +7060,7 @@ ${summaryContent}`
7059
7060
  }
7060
7061
  }
7061
7062
  async addResponseMessages(messages) {
7062
- const safe = repairToolPairing(messages);
7063
+ const safe = ensureToolResultsFollowCalls(repairToolPairing(messages));
7063
7064
  await messageQueries.addMany(this.sessionId, safe);
7064
7065
  try {
7065
7066
  const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
@@ -7104,6 +7105,84 @@ function stripOrphanedToolResults(msg, removedIds) {
7104
7105
  if (parts.length === 0) return null;
7105
7106
  return { ...msg, content: parts };
7106
7107
  }
7108
+ function wrapToolsNeverThrow(tools) {
7109
+ if (!tools || typeof tools !== "object") return tools;
7110
+ const wrapped = {};
7111
+ for (const [name, t] of Object.entries(tools)) {
7112
+ if (!t || typeof t.execute !== "function") {
7113
+ wrapped[name] = t;
7114
+ continue;
7115
+ }
7116
+ const original = t.execute;
7117
+ wrapped[name] = {
7118
+ ...t,
7119
+ execute: async (input, opts) => {
7120
+ try {
7121
+ return await original.call(t, input, opts);
7122
+ } catch (err) {
7123
+ const message = err?.message ?? String(err);
7124
+ console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
7125
+ return {
7126
+ __error: true,
7127
+ tool: name,
7128
+ message,
7129
+ note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
7130
+ };
7131
+ }
7132
+ }
7133
+ };
7134
+ }
7135
+ return wrapped;
7136
+ }
7137
+ function ensureToolResultsFollowCalls(messages) {
7138
+ if (!Array.isArray(messages) || messages.length < 3) return messages;
7139
+ let mutated = false;
7140
+ const result = messages.slice();
7141
+ let i = 0;
7142
+ while (i < result.length) {
7143
+ const msg = result[i];
7144
+ if (msg?.role !== "assistant" || !Array.isArray(msg.content)) {
7145
+ i++;
7146
+ continue;
7147
+ }
7148
+ const callIds = /* @__PURE__ */ new Set();
7149
+ for (const part of msg.content) {
7150
+ if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
7151
+ callIds.add(part.toolCallId);
7152
+ }
7153
+ }
7154
+ if (callIds.size === 0) {
7155
+ i++;
7156
+ continue;
7157
+ }
7158
+ let toolIdx = -1;
7159
+ for (let j = i + 1; j < result.length; j++) {
7160
+ const m = result[j];
7161
+ if (m?.role === "assistant" && Array.isArray(m.content) && m.content.some((p) => p?.type === "tool-call")) {
7162
+ break;
7163
+ }
7164
+ if (m?.role === "tool" && Array.isArray(m.content)) {
7165
+ const answersOne = m.content.some(
7166
+ (p) => p?.type === "tool-result" && typeof p.toolCallId === "string" && callIds.has(p.toolCallId)
7167
+ );
7168
+ if (answersOne) {
7169
+ toolIdx = j;
7170
+ break;
7171
+ }
7172
+ }
7173
+ }
7174
+ if (toolIdx > i + 1) {
7175
+ const [toolMsg] = result.splice(toolIdx, 1);
7176
+ result.splice(i + 1, 0, toolMsg);
7177
+ mutated = true;
7178
+ console.warn(
7179
+ `[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.`
7180
+ );
7181
+ }
7182
+ i++;
7183
+ }
7184
+ return mutated ? result : messages;
7185
+ }
7107
7186
  function repairToolPairing(messages) {
7108
7187
  const toolCallIds = /* @__PURE__ */ new Set();
7109
7188
  const toolResultIds = /* @__PURE__ */ new Set();
@@ -7181,6 +7260,100 @@ var webChannel = {
7181
7260
 
7182
7261
  // src/integrations/slack/client.ts
7183
7262
  init_config();
7263
+
7264
+ // src/integrations/slack/persistence.ts
7265
+ init_config();
7266
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
7267
+ import { join as join9, dirname as dirname6 } from "path";
7268
+ var FILENAME = "slack-cache.json";
7269
+ var FILE_VERSION = 1;
7270
+ var SAVE_DEBOUNCE_MS = 500;
7271
+ var MAX_THREADS = 5e3;
7272
+ var loaded = false;
7273
+ var userMap = /* @__PURE__ */ new Map();
7274
+ var threadMap = /* @__PURE__ */ new Map();
7275
+ var dirty = false;
7276
+ var saveTimer = null;
7277
+ function cachePath() {
7278
+ return join9(ensureAppDataDirectory(), FILENAME);
7279
+ }
7280
+ function load() {
7281
+ if (loaded) return;
7282
+ loaded = true;
7283
+ const path = cachePath();
7284
+ if (!existsSync16(path)) return;
7285
+ try {
7286
+ const raw = readFileSync7(path, "utf-8");
7287
+ const parsed = JSON.parse(raw);
7288
+ if (parsed?.version !== FILE_VERSION) return;
7289
+ const now = Date.now();
7290
+ for (const [id, e] of Object.entries(parsed.users || {})) {
7291
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7292
+ userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
7293
+ }
7294
+ }
7295
+ for (const [key2, e] of Object.entries(parsed.threads || {})) {
7296
+ if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
7297
+ threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
7298
+ }
7299
+ }
7300
+ } catch (err) {
7301
+ console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
7302
+ }
7303
+ }
7304
+ function evictIfOversized(map, max) {
7305
+ if (map.size <= max) return;
7306
+ const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
7307
+ const toRemove = map.size - max;
7308
+ for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
7309
+ }
7310
+ function scheduleSave() {
7311
+ dirty = true;
7312
+ if (saveTimer) return;
7313
+ saveTimer = setTimeout(() => {
7314
+ saveTimer = null;
7315
+ if (dirty) saveSync();
7316
+ }, SAVE_DEBOUNCE_MS);
7317
+ saveTimer.unref?.();
7318
+ }
7319
+ function saveSync() {
7320
+ dirty = false;
7321
+ try {
7322
+ const path = cachePath();
7323
+ const dir = dirname6(path);
7324
+ if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
7325
+ const payload = {
7326
+ version: FILE_VERSION,
7327
+ users: Object.fromEntries(userMap),
7328
+ threads: Object.fromEntries(threadMap)
7329
+ };
7330
+ const tmp = `${path}.tmp`;
7331
+ writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
7332
+ renameSync(tmp, path);
7333
+ } catch (err) {
7334
+ console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
7335
+ }
7336
+ }
7337
+ var exitHooked = false;
7338
+ function hookExit() {
7339
+ if (exitHooked) return;
7340
+ exitHooked = true;
7341
+ const flush2 = () => {
7342
+ if (dirty) saveSync();
7343
+ };
7344
+ process.once("beforeExit", flush2);
7345
+ process.once("SIGINT", flush2);
7346
+ process.once("SIGTERM", flush2);
7347
+ }
7348
+ function setCachedThreadOwnership(key2, entry2) {
7349
+ load();
7350
+ hookExit();
7351
+ threadMap.set(key2, entry2);
7352
+ evictIfOversized(threadMap, MAX_THREADS);
7353
+ scheduleSave();
7354
+ }
7355
+
7356
+ // src/integrations/slack/client.ts
7184
7357
  function readSlackConfig() {
7185
7358
  try {
7186
7359
  const cfg = getConfig();
@@ -7221,6 +7394,18 @@ function isSlackConfigured() {
7221
7394
  }
7222
7395
  var USER_TTL_MS = 60 * 60 * 1e3;
7223
7396
  var USER_FAIL_TTL_MS = 5 * 60 * 1e3;
7397
+ var THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
7398
+ var THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
7399
+ function threadCacheKey(channel, threadTs) {
7400
+ return `${channel}\u241F${threadTs}`;
7401
+ }
7402
+ function noteBotPostedInThread(channel, threadTs) {
7403
+ if (!channel || !threadTs) return;
7404
+ setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
7405
+ owned: true,
7406
+ expiresAt: Date.now() + THREAD_OWNED_TTL_MS
7407
+ });
7408
+ }
7224
7409
 
7225
7410
  // src/integrations/channels/slack.ts
7226
7411
  var ownedThreads = /* @__PURE__ */ new Set();
@@ -7245,6 +7430,7 @@ var slackChannel = {
7245
7430
  if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
7246
7431
  if (r.slackChannel && r.threadTs) {
7247
7432
  markThreadOwned(r.slackChannel, r.threadTs);
7433
+ noteBotPostedInThread(r.slackChannel, r.threadTs);
7248
7434
  }
7249
7435
  },
7250
7436
  displayLabel(ref) {
@@ -7761,8 +7947,8 @@ import { createMCPClient } from "@ai-sdk/mcp";
7761
7947
  // src/integrations/mcp/store.ts
7762
7948
  init_config();
7763
7949
  import { nanoid as nanoid6 } from "nanoid";
7764
- import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
7765
- import { resolve as resolve10, join as join9 } from "path";
7950
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
7951
+ import { resolve as resolve10, join as join10 } from "path";
7766
7952
  function readServers() {
7767
7953
  try {
7768
7954
  const cfg = getConfig();
@@ -7774,12 +7960,12 @@ function readServers() {
7774
7960
  function refreshMcpServersFromDisk() {
7775
7961
  const candidates = [
7776
7962
  resolve10(process.cwd(), "sparkecoder.config.json"),
7777
- join9(ensureAppDataDirectory(), "sparkecoder.config.json")
7963
+ join10(ensureAppDataDirectory(), "sparkecoder.config.json")
7778
7964
  ];
7779
7965
  for (const path of candidates) {
7780
- if (!existsSync16(path)) continue;
7966
+ if (!existsSync17(path)) continue;
7781
7967
  try {
7782
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
7968
+ const raw = JSON.parse(readFileSync8(path, "utf-8"));
7783
7969
  const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
7784
7970
  setMcpServers(servers2);
7785
7971
  return servers2;
@@ -8191,7 +8377,7 @@ ${prompt}` });
8191
8377
  const config = getConfig();
8192
8378
  const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
8193
8379
  if (!options.skipSaveUserMessage) {
8194
- this.context.addUserMessage(userContent);
8380
+ await this.context.addUserMessage(userContent);
8195
8381
  }
8196
8382
  await sessionQueries.updateStatus(this.session.id, "active");
8197
8383
  let systemPrompt = await buildSystemPrompt({
@@ -8217,7 +8403,8 @@ ${personality.trim()}`;
8217
8403
  }
8218
8404
  const messages = await this.context.getMessages();
8219
8405
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
8220
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
8406
+ const approvalWrapped = this.wrapToolsWithApproval(options, tools);
8407
+ const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
8221
8408
  const useAnthropic = isAnthropicModel(this.session.model);
8222
8409
  const stream = streamText2({
8223
8410
  model: resolveModel(this.session.model),
@@ -8231,6 +8418,18 @@ ${personality.trim()}`;
8231
8418
  providerOptions: useAnthropic ? {
8232
8419
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
8233
8420
  } : void 0,
8421
+ // Run repairToolPairing before EVERY step's model call, not just the
8422
+ // first one. The AI SDK's multi-step loop can otherwise feed the model
8423
+ // a prompt containing an orphan tool-call (e.g. when a previous step's
8424
+ // tool result was lost, dropped during compaction, or the stream was
8425
+ // aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
8426
+ // ever reaches the model and we never hit AI_MissingToolResultsError.
8427
+ prepareStep: async ({ messages: stepMessages }) => {
8428
+ const paired = repairToolPairing(stepMessages);
8429
+ const ordered = ensureToolResultsFollowCalls(paired);
8430
+ if (ordered === stepMessages) return {};
8431
+ return { messages: ordered };
8432
+ },
8234
8433
  onStepFinish: async (step) => {
8235
8434
  options.onStepFinish?.(step);
8236
8435
  },
@@ -8242,7 +8441,7 @@ ${personality.trim()}`;
8242
8441
  const result = await stream;
8243
8442
  const response = await result.response;
8244
8443
  const responseMessages = response.messages;
8245
- this.context.addResponseMessages(responseMessages);
8444
+ await this.context.addResponseMessages(responseMessages);
8246
8445
  };
8247
8446
  return {
8248
8447
  sessionId: this.session.id,
@@ -8256,7 +8455,7 @@ ${personality.trim()}`;
8256
8455
  */
8257
8456
  async run(options) {
8258
8457
  const config = getConfig();
8259
- this.context.addUserMessage(options.prompt);
8458
+ await this.context.addUserMessage(options.prompt);
8260
8459
  const systemPrompt = await buildSystemPrompt({
8261
8460
  workingDirectory: this.session.workingDirectory,
8262
8461
  skillsDirectories: config.resolvedSkillsDirectories,
@@ -8266,7 +8465,7 @@ ${personality.trim()}`;
8266
8465
  });
8267
8466
  const messages = await this.context.getMessages();
8268
8467
  const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
8269
- const wrappedTools = this.wrapToolsWithApproval(options, tools);
8468
+ const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
8270
8469
  const useAnthropic = isAnthropicModel(this.session.model);
8271
8470
  const result = await generateText3({
8272
8471
  model: resolveModel(this.session.model),
@@ -8277,10 +8476,17 @@ ${personality.trim()}`;
8277
8476
  // Enable extended thinking/reasoning for models that support it
8278
8477
  providerOptions: useAnthropic ? {
8279
8478
  anthropic: getAnthropicProviderOptions(this.session.model)
8280
- } : void 0
8479
+ } : void 0,
8480
+ // Repair tool pairing before every step (see `stream()` for full rationale).
8481
+ prepareStep: async ({ messages: stepMessages }) => {
8482
+ const paired = repairToolPairing(stepMessages);
8483
+ const ordered = ensureToolResultsFollowCalls(paired);
8484
+ if (ordered === stepMessages) return {};
8485
+ return { messages: ordered };
8486
+ }
8281
8487
  });
8282
8488
  const responseMessages = result.response.messages;
8283
- this.context.addResponseMessages(responseMessages);
8489
+ await this.context.addResponseMessages(responseMessages);
8284
8490
  return {
8285
8491
  text: result.text,
8286
8492
  steps: result.steps
@@ -8454,12 +8660,20 @@ ${p.text}` : p.text;
8454
8660
  model: resolveModel(this.session.model),
8455
8661
  system: systemPrompt,
8456
8662
  messages,
8457
- tools: taskTools,
8663
+ tools: wrapToolsNeverThrow(taskTools),
8458
8664
  stopWhen: stepCountIs2(500),
8459
8665
  abortSignal: combinedAbort,
8460
8666
  providerOptions: useAnthropic ? {
8461
8667
  anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
8462
8668
  } : void 0,
8669
+ // See the matching note in `stream()` — repair tool pairing before
8670
+ // every step so we never feed the model an orphan tool-call.
8671
+ prepareStep: async ({ messages: stepMessages }) => {
8672
+ const paired = repairToolPairing(stepMessages);
8673
+ const ordered = ensureToolResultsFollowCalls(paired);
8674
+ if (ordered === stepMessages) return {};
8675
+ return { messages: ordered };
8676
+ },
8463
8677
  onStepFinish: async (step) => {
8464
8678
  options.onStepFinish?.(step);
8465
8679
  fireWebhook("task.step_finished", { iteration, text: step.text });
@@ -8693,11 +8907,11 @@ ${p.text}` : p.text;
8693
8907
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
8694
8908
  if (!isRemoteConfigured2()) return [];
8695
8909
  const { readFile: readFile12 } = await import("fs/promises");
8696
- const { join: join11, basename: basename5 } = await import("path");
8910
+ const { join: join12, basename: basename5 } = await import("path");
8697
8911
  const urls = [];
8698
8912
  for (const filePath of filePaths) {
8699
8913
  try {
8700
- const fullPath = filePath.startsWith("/") ? filePath : join11(this.session.workingDirectory, filePath);
8914
+ const fullPath = filePath.startsWith("/") ? filePath : join12(this.session.workingDirectory, filePath);
8701
8915
  const fileName = basename5(fullPath);
8702
8916
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
8703
8917
  const mimeMap = {