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