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