sparkecoder 0.1.121 → 0.1.122
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.js +182 -21
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +483 -157
- package/dist/cli.js.map +1 -1
- package/dist/index.js +444 -118
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +444 -118
- package/dist/server/index.js.map +1 -1
- 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 → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_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,35 @@ 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
|
+
}
|
|
7433
7462
|
function repairToolPairing(messages) {
|
|
7434
7463
|
const toolCallIds = /* @__PURE__ */ new Set();
|
|
7435
7464
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -7795,6 +7824,120 @@ var init_web = __esm({
|
|
|
7795
7824
|
}
|
|
7796
7825
|
});
|
|
7797
7826
|
|
|
7827
|
+
// src/integrations/slack/persistence.ts
|
|
7828
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
7829
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
7830
|
+
function cachePath() {
|
|
7831
|
+
return join9(ensureAppDataDirectory(), FILENAME);
|
|
7832
|
+
}
|
|
7833
|
+
function load() {
|
|
7834
|
+
if (loaded) return;
|
|
7835
|
+
loaded = true;
|
|
7836
|
+
const path = cachePath();
|
|
7837
|
+
if (!existsSync16(path)) return;
|
|
7838
|
+
try {
|
|
7839
|
+
const raw = readFileSync7(path, "utf-8");
|
|
7840
|
+
const parsed = JSON.parse(raw);
|
|
7841
|
+
if (parsed?.version !== FILE_VERSION) return;
|
|
7842
|
+
const now = Date.now();
|
|
7843
|
+
for (const [id, e] of Object.entries(parsed.users || {})) {
|
|
7844
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7845
|
+
userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
for (const [key2, e] of Object.entries(parsed.threads || {})) {
|
|
7849
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7850
|
+
threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
|
|
7851
|
+
}
|
|
7852
|
+
}
|
|
7853
|
+
} catch (err) {
|
|
7854
|
+
console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
|
|
7855
|
+
}
|
|
7856
|
+
}
|
|
7857
|
+
function evictIfOversized(map, max) {
|
|
7858
|
+
if (map.size <= max) return;
|
|
7859
|
+
const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
7860
|
+
const toRemove = map.size - max;
|
|
7861
|
+
for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
|
|
7862
|
+
}
|
|
7863
|
+
function scheduleSave() {
|
|
7864
|
+
dirty = true;
|
|
7865
|
+
if (saveTimer) return;
|
|
7866
|
+
saveTimer = setTimeout(() => {
|
|
7867
|
+
saveTimer = null;
|
|
7868
|
+
if (dirty) saveSync();
|
|
7869
|
+
}, SAVE_DEBOUNCE_MS);
|
|
7870
|
+
saveTimer.unref?.();
|
|
7871
|
+
}
|
|
7872
|
+
function saveSync() {
|
|
7873
|
+
dirty = false;
|
|
7874
|
+
try {
|
|
7875
|
+
const path = cachePath();
|
|
7876
|
+
const dir = dirname6(path);
|
|
7877
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
7878
|
+
const payload = {
|
|
7879
|
+
version: FILE_VERSION,
|
|
7880
|
+
users: Object.fromEntries(userMap),
|
|
7881
|
+
threads: Object.fromEntries(threadMap)
|
|
7882
|
+
};
|
|
7883
|
+
const tmp = `${path}.tmp`;
|
|
7884
|
+
writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
|
|
7885
|
+
renameSync(tmp, path);
|
|
7886
|
+
} catch (err) {
|
|
7887
|
+
console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
|
|
7888
|
+
}
|
|
7889
|
+
}
|
|
7890
|
+
function hookExit() {
|
|
7891
|
+
if (exitHooked) return;
|
|
7892
|
+
exitHooked = true;
|
|
7893
|
+
const flush2 = () => {
|
|
7894
|
+
if (dirty) saveSync();
|
|
7895
|
+
};
|
|
7896
|
+
process.once("beforeExit", flush2);
|
|
7897
|
+
process.once("SIGINT", flush2);
|
|
7898
|
+
process.once("SIGTERM", flush2);
|
|
7899
|
+
}
|
|
7900
|
+
function getCachedUserName(userId) {
|
|
7901
|
+
load();
|
|
7902
|
+
return userMap.get(userId);
|
|
7903
|
+
}
|
|
7904
|
+
function setCachedUserName(userId, entry2) {
|
|
7905
|
+
load();
|
|
7906
|
+
hookExit();
|
|
7907
|
+
userMap.set(userId, entry2);
|
|
7908
|
+
evictIfOversized(userMap, MAX_USERS);
|
|
7909
|
+
scheduleSave();
|
|
7910
|
+
}
|
|
7911
|
+
function getCachedThreadOwnership(key2) {
|
|
7912
|
+
load();
|
|
7913
|
+
return threadMap.get(key2);
|
|
7914
|
+
}
|
|
7915
|
+
function setCachedThreadOwnership(key2, entry2) {
|
|
7916
|
+
load();
|
|
7917
|
+
hookExit();
|
|
7918
|
+
threadMap.set(key2, entry2);
|
|
7919
|
+
evictIfOversized(threadMap, MAX_THREADS);
|
|
7920
|
+
scheduleSave();
|
|
7921
|
+
}
|
|
7922
|
+
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
7923
|
+
var init_persistence = __esm({
|
|
7924
|
+
"src/integrations/slack/persistence.ts"() {
|
|
7925
|
+
"use strict";
|
|
7926
|
+
init_config();
|
|
7927
|
+
FILENAME = "slack-cache.json";
|
|
7928
|
+
FILE_VERSION = 1;
|
|
7929
|
+
SAVE_DEBOUNCE_MS = 500;
|
|
7930
|
+
MAX_USERS = 5e3;
|
|
7931
|
+
MAX_THREADS = 5e3;
|
|
7932
|
+
loaded = false;
|
|
7933
|
+
userMap = /* @__PURE__ */ new Map();
|
|
7934
|
+
threadMap = /* @__PURE__ */ new Map();
|
|
7935
|
+
dirty = false;
|
|
7936
|
+
saveTimer = null;
|
|
7937
|
+
exitHooked = false;
|
|
7938
|
+
}
|
|
7939
|
+
});
|
|
7940
|
+
|
|
7798
7941
|
// src/integrations/slack/client.ts
|
|
7799
7942
|
function readSlackConfig() {
|
|
7800
7943
|
try {
|
|
@@ -7917,13 +8060,13 @@ async function fetchSlackUserName(userId) {
|
|
|
7917
8060
|
async function resolveSlackUserName(userId) {
|
|
7918
8061
|
if (!userId) return null;
|
|
7919
8062
|
const now = Date.now();
|
|
7920
|
-
const hit =
|
|
8063
|
+
const hit = getCachedUserName(userId);
|
|
7921
8064
|
if (hit && hit.expiresAt > now) return hit.name;
|
|
7922
8065
|
const inflight = userInflight.get(userId);
|
|
7923
8066
|
if (inflight) return inflight;
|
|
7924
8067
|
const p = (async () => {
|
|
7925
8068
|
const name = await fetchSlackUserName(userId);
|
|
7926
|
-
|
|
8069
|
+
setCachedUserName(userId, {
|
|
7927
8070
|
name,
|
|
7928
8071
|
expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
7929
8072
|
});
|
|
@@ -7945,11 +8088,63 @@ async function normalizeSlackMentions(text) {
|
|
|
7945
8088
|
}
|
|
7946
8089
|
return text.replace(userMentionRe, (_full, id, label) => {
|
|
7947
8090
|
if (label) return `${label} <@${id}>`;
|
|
7948
|
-
const cached =
|
|
8091
|
+
const cached = getCachedUserName(id);
|
|
7949
8092
|
const name = cached?.name;
|
|
7950
8093
|
return name ? `${name} <@${id}>` : `<@${id}>`;
|
|
7951
8094
|
});
|
|
7952
8095
|
}
|
|
8096
|
+
function threadCacheKey(channel, threadTs) {
|
|
8097
|
+
return `${channel}\u241F${threadTs}`;
|
|
8098
|
+
}
|
|
8099
|
+
async function fetchBotParticipatedInThread(channel, threadTs) {
|
|
8100
|
+
const token = getSlackBotToken();
|
|
8101
|
+
if (!token) return false;
|
|
8102
|
+
const self = await ensureSlackSelfIdentity();
|
|
8103
|
+
if (!self) return false;
|
|
8104
|
+
try {
|
|
8105
|
+
const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
|
|
8106
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
8107
|
+
const data = await res.json().catch(() => ({}));
|
|
8108
|
+
if (!data?.ok) {
|
|
8109
|
+
console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
8110
|
+
return false;
|
|
8111
|
+
}
|
|
8112
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
8113
|
+
return messages.some(
|
|
8114
|
+
(m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
|
|
8115
|
+
);
|
|
8116
|
+
} catch (err) {
|
|
8117
|
+
console.warn(`[slack] conversations.replies error:`, err?.message || err);
|
|
8118
|
+
return false;
|
|
8119
|
+
}
|
|
8120
|
+
}
|
|
8121
|
+
async function botParticipatedInThread(channel, threadTs) {
|
|
8122
|
+
if (!channel || !threadTs) return false;
|
|
8123
|
+
const key2 = threadCacheKey(channel, threadTs);
|
|
8124
|
+
const now = Date.now();
|
|
8125
|
+
const hit = getCachedThreadOwnership(key2);
|
|
8126
|
+
if (hit && hit.expiresAt > now) return hit.owned;
|
|
8127
|
+
const inflight = threadOwnedInflight.get(key2);
|
|
8128
|
+
if (inflight) return inflight;
|
|
8129
|
+
const p = (async () => {
|
|
8130
|
+
const owned = await fetchBotParticipatedInThread(channel, threadTs);
|
|
8131
|
+
setCachedThreadOwnership(key2, {
|
|
8132
|
+
owned,
|
|
8133
|
+
expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
|
|
8134
|
+
});
|
|
8135
|
+
threadOwnedInflight.delete(key2);
|
|
8136
|
+
return owned;
|
|
8137
|
+
})();
|
|
8138
|
+
threadOwnedInflight.set(key2, p);
|
|
8139
|
+
return p;
|
|
8140
|
+
}
|
|
8141
|
+
function noteBotPostedInThread(channel, threadTs) {
|
|
8142
|
+
if (!channel || !threadTs) return;
|
|
8143
|
+
setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
|
|
8144
|
+
owned: true,
|
|
8145
|
+
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
8146
|
+
});
|
|
8147
|
+
}
|
|
7953
8148
|
function getSlackDeniedReplyPolicy() {
|
|
7954
8149
|
try {
|
|
7955
8150
|
const cfg = getConfig();
|
|
@@ -7962,17 +8157,20 @@ function getSlackDeniedReplyPolicy() {
|
|
|
7962
8157
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
7963
8158
|
}
|
|
7964
8159
|
}
|
|
7965
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS,
|
|
8160
|
+
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
7966
8161
|
var init_client3 = __esm({
|
|
7967
8162
|
"src/integrations/slack/client.ts"() {
|
|
7968
8163
|
"use strict";
|
|
7969
8164
|
init_config();
|
|
8165
|
+
init_persistence();
|
|
7970
8166
|
cachedSelf = null;
|
|
7971
8167
|
selfInflight = null;
|
|
7972
8168
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
7973
8169
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
7974
|
-
userNameCache = /* @__PURE__ */ new Map();
|
|
7975
8170
|
userInflight = /* @__PURE__ */ new Map();
|
|
8171
|
+
THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
8172
|
+
THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
8173
|
+
threadOwnedInflight = /* @__PURE__ */ new Map();
|
|
7976
8174
|
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
8175
|
}
|
|
7978
8176
|
});
|
|
@@ -8072,6 +8270,7 @@ var init_slack = __esm({
|
|
|
8072
8270
|
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
8073
8271
|
if (r.slackChannel && r.threadTs) {
|
|
8074
8272
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
8273
|
+
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8075
8274
|
}
|
|
8076
8275
|
},
|
|
8077
8276
|
displayLabel(ref) {
|
|
@@ -8729,8 +8928,8 @@ var init_orchestrator_actions = __esm({
|
|
|
8729
8928
|
|
|
8730
8929
|
// src/integrations/mcp/store.ts
|
|
8731
8930
|
import { nanoid as nanoid6 } from "nanoid";
|
|
8732
|
-
import { existsSync as
|
|
8733
|
-
import { resolve as resolve10, join as
|
|
8931
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
|
|
8932
|
+
import { resolve as resolve10, join as join10 } from "path";
|
|
8734
8933
|
function readServers() {
|
|
8735
8934
|
try {
|
|
8736
8935
|
const cfg = getConfig();
|
|
@@ -8742,12 +8941,12 @@ function readServers() {
|
|
|
8742
8941
|
function refreshMcpServersFromDisk() {
|
|
8743
8942
|
const candidates = [
|
|
8744
8943
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8745
|
-
|
|
8944
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8746
8945
|
];
|
|
8747
8946
|
for (const path of candidates) {
|
|
8748
|
-
if (!
|
|
8947
|
+
if (!existsSync17(path)) continue;
|
|
8749
8948
|
try {
|
|
8750
|
-
const raw = JSON.parse(
|
|
8949
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
8751
8950
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8752
8951
|
setMcpServers(servers2);
|
|
8753
8952
|
return servers2;
|
|
@@ -9360,7 +9559,7 @@ __export(recorder_exports, {
|
|
|
9360
9559
|
import { exec as exec5 } from "child_process";
|
|
9361
9560
|
import { promisify as promisify5 } from "util";
|
|
9362
9561
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
9363
|
-
import { join as
|
|
9562
|
+
import { join as join11 } from "path";
|
|
9364
9563
|
import { tmpdir } from "os";
|
|
9365
9564
|
import { nanoid as nanoid7 } from "nanoid";
|
|
9366
9565
|
async function checkFfmpeg() {
|
|
@@ -9417,21 +9616,21 @@ var init_recorder = __esm({
|
|
|
9417
9616
|
*/
|
|
9418
9617
|
async encode() {
|
|
9419
9618
|
if (this.frames.length === 0) return null;
|
|
9420
|
-
const workDir =
|
|
9619
|
+
const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
9421
9620
|
await mkdir4(workDir, { recursive: true });
|
|
9422
9621
|
try {
|
|
9423
9622
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9424
|
-
const framePath =
|
|
9623
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
9425
9624
|
await writeFile5(framePath, this.frames[i].data);
|
|
9426
9625
|
}
|
|
9427
9626
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9428
9627
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9429
9628
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9430
|
-
const outputPath =
|
|
9629
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
9431
9630
|
const hasFfmpeg = await checkFfmpeg();
|
|
9432
9631
|
if (hasFfmpeg) {
|
|
9433
9632
|
await execAsync5(
|
|
9434
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
9633
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
9435
9634
|
{ timeout: 12e4 }
|
|
9436
9635
|
);
|
|
9437
9636
|
} else {
|
|
@@ -9443,7 +9642,7 @@ var init_recorder = __esm({
|
|
|
9443
9642
|
const files = await readdir5(workDir);
|
|
9444
9643
|
for (const f of files) {
|
|
9445
9644
|
if (f.startsWith("frame_")) {
|
|
9446
|
-
await unlink2(
|
|
9645
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
9447
9646
|
});
|
|
9448
9647
|
}
|
|
9449
9648
|
}
|
|
@@ -9710,7 +9909,8 @@ ${personality.trim()}`;
|
|
|
9710
9909
|
}
|
|
9711
9910
|
const messages = await this.context.getMessages();
|
|
9712
9911
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9713
|
-
const
|
|
9912
|
+
const approvalWrapped = this.wrapToolsWithApproval(options, tools);
|
|
9913
|
+
const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
|
|
9714
9914
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9715
9915
|
const stream = streamText2({
|
|
9716
9916
|
model: resolveModel(this.session.model),
|
|
@@ -9724,6 +9924,17 @@ ${personality.trim()}`;
|
|
|
9724
9924
|
providerOptions: useAnthropic ? {
|
|
9725
9925
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9726
9926
|
} : void 0,
|
|
9927
|
+
// Run repairToolPairing before EVERY step's model call, not just the
|
|
9928
|
+
// first one. The AI SDK's multi-step loop can otherwise feed the model
|
|
9929
|
+
// a prompt containing an orphan tool-call (e.g. when a previous step's
|
|
9930
|
+
// tool result was lost, dropped during compaction, or the stream was
|
|
9931
|
+
// aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
|
|
9932
|
+
// ever reaches the model and we never hit AI_MissingToolResultsError.
|
|
9933
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
9934
|
+
const repaired = repairToolPairing(stepMessages);
|
|
9935
|
+
if (repaired === stepMessages) return {};
|
|
9936
|
+
return { messages: repaired };
|
|
9937
|
+
},
|
|
9727
9938
|
onStepFinish: async (step) => {
|
|
9728
9939
|
options.onStepFinish?.(step);
|
|
9729
9940
|
},
|
|
@@ -9759,7 +9970,7 @@ ${personality.trim()}`;
|
|
|
9759
9970
|
});
|
|
9760
9971
|
const messages = await this.context.getMessages();
|
|
9761
9972
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9762
|
-
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
9973
|
+
const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
|
|
9763
9974
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9764
9975
|
const result = await generateText3({
|
|
9765
9976
|
model: resolveModel(this.session.model),
|
|
@@ -9770,7 +9981,13 @@ ${personality.trim()}`;
|
|
|
9770
9981
|
// Enable extended thinking/reasoning for models that support it
|
|
9771
9982
|
providerOptions: useAnthropic ? {
|
|
9772
9983
|
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
9773
|
-
} : void 0
|
|
9984
|
+
} : void 0,
|
|
9985
|
+
// Repair tool pairing before every step (see `stream()` for full rationale).
|
|
9986
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
9987
|
+
const repaired = repairToolPairing(stepMessages);
|
|
9988
|
+
if (repaired === stepMessages) return {};
|
|
9989
|
+
return { messages: repaired };
|
|
9990
|
+
}
|
|
9774
9991
|
});
|
|
9775
9992
|
const responseMessages = result.response.messages;
|
|
9776
9993
|
this.context.addResponseMessages(responseMessages);
|
|
@@ -9947,12 +10164,19 @@ ${p.text}` : p.text;
|
|
|
9947
10164
|
model: resolveModel(this.session.model),
|
|
9948
10165
|
system: systemPrompt,
|
|
9949
10166
|
messages,
|
|
9950
|
-
tools: taskTools,
|
|
10167
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
9951
10168
|
stopWhen: stepCountIs2(500),
|
|
9952
10169
|
abortSignal: combinedAbort,
|
|
9953
10170
|
providerOptions: useAnthropic ? {
|
|
9954
10171
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9955
10172
|
} : void 0,
|
|
10173
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
10174
|
+
// every step so we never feed the model an orphan tool-call.
|
|
10175
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10176
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10177
|
+
if (repaired === stepMessages) return {};
|
|
10178
|
+
return { messages: repaired };
|
|
10179
|
+
},
|
|
9956
10180
|
onStepFinish: async (step) => {
|
|
9957
10181
|
options.onStepFinish?.(step);
|
|
9958
10182
|
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
@@ -10186,11 +10410,11 @@ ${p.text}` : p.text;
|
|
|
10186
10410
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10187
10411
|
if (!isRemoteConfigured2()) return [];
|
|
10188
10412
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
10189
|
-
const { join:
|
|
10413
|
+
const { join: join18, basename: basename7 } = await import("path");
|
|
10190
10414
|
const urls = [];
|
|
10191
10415
|
for (const filePath of filePaths) {
|
|
10192
10416
|
try {
|
|
10193
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10417
|
+
const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
|
|
10194
10418
|
const fileName = basename7(fullPath);
|
|
10195
10419
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10196
10420
|
const mimeMap = {
|
|
@@ -10395,19 +10619,19 @@ var init_session_lock = __esm({
|
|
|
10395
10619
|
});
|
|
10396
10620
|
|
|
10397
10621
|
// src/orchestrator/webhook-events.ts
|
|
10398
|
-
import { existsSync as
|
|
10399
|
-
import { dirname as
|
|
10622
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
10623
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
10400
10624
|
import { nanoid as nanoid9 } from "nanoid";
|
|
10401
10625
|
function logFilePath() {
|
|
10402
|
-
return
|
|
10626
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10403
10627
|
}
|
|
10404
10628
|
function ensureLoaded() {
|
|
10405
10629
|
if (cache !== null) return cache;
|
|
10406
10630
|
cache = [];
|
|
10407
10631
|
try {
|
|
10408
10632
|
const p = logFilePath();
|
|
10409
|
-
if (!
|
|
10410
|
-
const lines =
|
|
10633
|
+
if (!existsSync18(p)) return cache;
|
|
10634
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
10411
10635
|
for (const line of lines) {
|
|
10412
10636
|
try {
|
|
10413
10637
|
cache.push(JSON.parse(line));
|
|
@@ -10417,7 +10641,7 @@ function ensureLoaded() {
|
|
|
10417
10641
|
if (cache.length > MAX_EVENTS) {
|
|
10418
10642
|
cache = cache.slice(-MAX_EVENTS);
|
|
10419
10643
|
try {
|
|
10420
|
-
|
|
10644
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10421
10645
|
} catch {
|
|
10422
10646
|
}
|
|
10423
10647
|
}
|
|
@@ -10431,7 +10655,7 @@ function appendEvent(ev) {
|
|
|
10431
10655
|
if (list.length > MAX_EVENTS) list.shift();
|
|
10432
10656
|
try {
|
|
10433
10657
|
const p = logFilePath();
|
|
10434
|
-
|
|
10658
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10435
10659
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10436
10660
|
} catch {
|
|
10437
10661
|
}
|
|
@@ -10462,8 +10686,8 @@ function updateEvent(id, patch) {
|
|
|
10462
10686
|
list[i] = { ...list[i], ...patch };
|
|
10463
10687
|
try {
|
|
10464
10688
|
const p = logFilePath();
|
|
10465
|
-
|
|
10466
|
-
|
|
10689
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10690
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10467
10691
|
} catch {
|
|
10468
10692
|
}
|
|
10469
10693
|
}
|
|
@@ -10495,7 +10719,7 @@ function listEvents(filter = {}) {
|
|
|
10495
10719
|
function clearAllEvents() {
|
|
10496
10720
|
cache = [];
|
|
10497
10721
|
try {
|
|
10498
|
-
|
|
10722
|
+
writeFileSync4(logFilePath(), "");
|
|
10499
10723
|
} catch {
|
|
10500
10724
|
}
|
|
10501
10725
|
}
|
|
@@ -10766,8 +10990,8 @@ import { Hono as Hono10 } from "hono";
|
|
|
10766
10990
|
import { serve } from "@hono/node-server";
|
|
10767
10991
|
import { cors } from "hono/cors";
|
|
10768
10992
|
import { logger } from "hono/logger";
|
|
10769
|
-
import { existsSync as
|
|
10770
|
-
import { resolve as resolve12, dirname as
|
|
10993
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
10994
|
+
import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
|
|
10771
10995
|
import { spawn as spawn2 } from "child_process";
|
|
10772
10996
|
import { createServer as createNetServer } from "net";
|
|
10773
10997
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -10781,9 +11005,9 @@ init_checkpoints();
|
|
|
10781
11005
|
import { Hono } from "hono";
|
|
10782
11006
|
import { zValidator } from "@hono/zod-validator";
|
|
10783
11007
|
import { z as z16 } from "zod";
|
|
10784
|
-
import { existsSync as
|
|
11008
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
10785
11009
|
import { readdir as readdir6 } from "fs/promises";
|
|
10786
|
-
import { join as
|
|
11010
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
10787
11011
|
import { nanoid as nanoid10 } from "nanoid";
|
|
10788
11012
|
|
|
10789
11013
|
// src/tasks/agent-status.ts
|
|
@@ -11421,12 +11645,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11421
11645
|
});
|
|
11422
11646
|
function getAttachmentsDir(sessionId) {
|
|
11423
11647
|
const appDataDir = getAppDataDirectory();
|
|
11424
|
-
return
|
|
11648
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
11425
11649
|
}
|
|
11426
11650
|
function ensureAttachmentsDir(sessionId) {
|
|
11427
11651
|
const dir = getAttachmentsDir(sessionId);
|
|
11428
|
-
if (!
|
|
11429
|
-
|
|
11652
|
+
if (!existsSync19(dir)) {
|
|
11653
|
+
mkdirSync8(dir, { recursive: true });
|
|
11430
11654
|
}
|
|
11431
11655
|
return dir;
|
|
11432
11656
|
}
|
|
@@ -11437,12 +11661,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11437
11661
|
return c.json({ error: "Session not found" }, 404);
|
|
11438
11662
|
}
|
|
11439
11663
|
const dir = getAttachmentsDir(sessionId);
|
|
11440
|
-
if (!
|
|
11664
|
+
if (!existsSync19(dir)) {
|
|
11441
11665
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
11442
11666
|
}
|
|
11443
11667
|
const files = readdirSync3(dir);
|
|
11444
11668
|
const attachments = files.map((filename) => {
|
|
11445
|
-
const filePath =
|
|
11669
|
+
const filePath = join13(dir, filename);
|
|
11446
11670
|
const stats = statSync2(filePath);
|
|
11447
11671
|
return {
|
|
11448
11672
|
id: filename.split("_")[0],
|
|
@@ -11477,9 +11701,9 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11477
11701
|
const id = nanoid10(10);
|
|
11478
11702
|
const ext = extname8(file.name) || "";
|
|
11479
11703
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11480
|
-
const filePath =
|
|
11704
|
+
const filePath = join13(dir, safeFilename);
|
|
11481
11705
|
const arrayBuffer = await file.arrayBuffer();
|
|
11482
|
-
|
|
11706
|
+
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
11483
11707
|
return c.json({
|
|
11484
11708
|
id,
|
|
11485
11709
|
filename: file.name,
|
|
@@ -11503,13 +11727,13 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11503
11727
|
const id = nanoid10(10);
|
|
11504
11728
|
const ext = extname8(body.filename) || "";
|
|
11505
11729
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11506
|
-
const filePath =
|
|
11730
|
+
const filePath = join13(dir, safeFilename);
|
|
11507
11731
|
let base64Data = body.data;
|
|
11508
11732
|
if (base64Data.includes(",")) {
|
|
11509
11733
|
base64Data = base64Data.split(",")[1];
|
|
11510
11734
|
}
|
|
11511
11735
|
const buffer = Buffer.from(base64Data, "base64");
|
|
11512
|
-
|
|
11736
|
+
writeFileSync5(filePath, buffer);
|
|
11513
11737
|
return c.json({
|
|
11514
11738
|
id,
|
|
11515
11739
|
filename: body.filename,
|
|
@@ -11532,7 +11756,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11532
11756
|
return c.json({ error: "Session not found" }, 404);
|
|
11533
11757
|
}
|
|
11534
11758
|
const dir = getAttachmentsDir(sessionId);
|
|
11535
|
-
if (!
|
|
11759
|
+
if (!existsSync19(dir)) {
|
|
11536
11760
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11537
11761
|
}
|
|
11538
11762
|
const files = readdirSync3(dir);
|
|
@@ -11540,7 +11764,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11540
11764
|
if (!file) {
|
|
11541
11765
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11542
11766
|
}
|
|
11543
|
-
const filePath =
|
|
11767
|
+
const filePath = join13(dir, file);
|
|
11544
11768
|
unlinkSync2(filePath);
|
|
11545
11769
|
return c.json({ success: true, id: attachmentId });
|
|
11546
11770
|
});
|
|
@@ -11623,7 +11847,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
11623
11847
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
11624
11848
|
for (const entry2 of entries) {
|
|
11625
11849
|
if (results.length >= limit * 2) break;
|
|
11626
|
-
const fullPath =
|
|
11850
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
11627
11851
|
const relativePath = relative9(baseDir, fullPath);
|
|
11628
11852
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
11629
11853
|
continue;
|
|
@@ -11671,7 +11895,7 @@ sessions2.get(
|
|
|
11671
11895
|
return c.json({ error: "Session not found" }, 404);
|
|
11672
11896
|
}
|
|
11673
11897
|
const workingDirectory = session.workingDirectory;
|
|
11674
|
-
if (!
|
|
11898
|
+
if (!existsSync19(workingDirectory)) {
|
|
11675
11899
|
return c.json({
|
|
11676
11900
|
sessionId,
|
|
11677
11901
|
workingDirectory,
|
|
@@ -11779,14 +12003,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
|
|
|
11779
12003
|
|
|
11780
12004
|
// src/server/routes/agents.ts
|
|
11781
12005
|
init_db();
|
|
11782
|
-
init_agent();
|
|
11783
|
-
init_session_lock();
|
|
11784
|
-
init_config();
|
|
11785
12006
|
import { Hono as Hono2 } from "hono";
|
|
11786
12007
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
11787
12008
|
import { z as z17 } from "zod";
|
|
11788
|
-
import { existsSync as
|
|
11789
|
-
import { join as
|
|
12009
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
12010
|
+
import { join as join14 } from "path";
|
|
12011
|
+
|
|
12012
|
+
// src/agent/missing-tool-recovery.ts
|
|
12013
|
+
init_db();
|
|
12014
|
+
function extractMissingToolCallIds(error) {
|
|
12015
|
+
if (!error) return [];
|
|
12016
|
+
const e = error;
|
|
12017
|
+
if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
|
|
12018
|
+
return e.toolCallIds;
|
|
12019
|
+
}
|
|
12020
|
+
const msg = typeof e.message === "string" ? e.message : "";
|
|
12021
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12022
|
+
for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
|
|
12023
|
+
for (const id of m[1].split(/\s*,\s*/)) {
|
|
12024
|
+
const trimmed = id.trim();
|
|
12025
|
+
if (trimmed) ids.add(trimmed);
|
|
12026
|
+
}
|
|
12027
|
+
}
|
|
12028
|
+
return [...ids];
|
|
12029
|
+
}
|
|
12030
|
+
function isMissingToolResultsError(error) {
|
|
12031
|
+
if (!error) return false;
|
|
12032
|
+
const e = error;
|
|
12033
|
+
if (e.name === "AI_MissingToolResultsError") return true;
|
|
12034
|
+
if (Array.isArray(e.toolCallIds)) return true;
|
|
12035
|
+
return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
|
|
12036
|
+
}
|
|
12037
|
+
async function recoverFromMissingToolResults(sessionId, error) {
|
|
12038
|
+
const toolCallIds = extractMissingToolCallIds(error);
|
|
12039
|
+
if (toolCallIds.length === 0) return null;
|
|
12040
|
+
let history = [];
|
|
12041
|
+
try {
|
|
12042
|
+
history = await messageQueries.getModelMessages(sessionId);
|
|
12043
|
+
} catch (err) {
|
|
12044
|
+
console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
|
|
12045
|
+
}
|
|
12046
|
+
const toolNameByCallId = /* @__PURE__ */ new Map();
|
|
12047
|
+
const existingResultIds = /* @__PURE__ */ new Set();
|
|
12048
|
+
for (const msg of history) {
|
|
12049
|
+
if (!Array.isArray(msg.content)) continue;
|
|
12050
|
+
for (const part of msg.content) {
|
|
12051
|
+
if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
|
|
12052
|
+
if (typeof part.toolName === "string") toolNameByCallId.set(part.toolCallId, part.toolName);
|
|
12053
|
+
}
|
|
12054
|
+
if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
|
|
12055
|
+
existingResultIds.add(part.toolCallId);
|
|
12056
|
+
}
|
|
12057
|
+
}
|
|
12058
|
+
}
|
|
12059
|
+
const resolved = toolCallIds.map((id) => ({
|
|
12060
|
+
toolCallId: id,
|
|
12061
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
12062
|
+
foundInAssistantMessage: toolNameByCallId.has(id)
|
|
12063
|
+
}));
|
|
12064
|
+
const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
|
|
12065
|
+
let syntheticToolMessageSaved = false;
|
|
12066
|
+
if (stillOrphaned.length > 0) {
|
|
12067
|
+
const syntheticParts = stillOrphaned.map((id) => ({
|
|
12068
|
+
type: "tool-result",
|
|
12069
|
+
toolCallId: id,
|
|
12070
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
12071
|
+
output: {
|
|
12072
|
+
type: "text",
|
|
12073
|
+
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.]"
|
|
12074
|
+
}
|
|
12075
|
+
}));
|
|
12076
|
+
try {
|
|
12077
|
+
await messageQueries.create(sessionId, {
|
|
12078
|
+
role: "tool",
|
|
12079
|
+
content: syntheticParts
|
|
12080
|
+
});
|
|
12081
|
+
syntheticToolMessageSaved = true;
|
|
12082
|
+
} catch (err) {
|
|
12083
|
+
console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
|
|
12084
|
+
}
|
|
12085
|
+
}
|
|
12086
|
+
const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
|
|
12087
|
+
return {
|
|
12088
|
+
kind: "missing_tool_results",
|
|
12089
|
+
toolCallIds,
|
|
12090
|
+
resolved,
|
|
12091
|
+
syntheticToolMessageSaved,
|
|
12092
|
+
lastFewMessageRoles,
|
|
12093
|
+
hint: 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."
|
|
12094
|
+
};
|
|
12095
|
+
}
|
|
12096
|
+
|
|
12097
|
+
// src/server/routes/agents.ts
|
|
12098
|
+
init_agent();
|
|
12099
|
+
init_session_lock();
|
|
12100
|
+
init_config();
|
|
11790
12101
|
|
|
11791
12102
|
// src/server/resumable-stream.ts
|
|
11792
12103
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -11993,12 +12304,12 @@ var rejectSchema = z17.object({
|
|
|
11993
12304
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
11994
12305
|
function getAttachmentsDirectory(sessionId) {
|
|
11995
12306
|
const appDataDir = getAppDataDirectory();
|
|
11996
|
-
return
|
|
12307
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
11997
12308
|
}
|
|
11998
12309
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
11999
12310
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
12000
|
-
if (!
|
|
12001
|
-
|
|
12311
|
+
if (!existsSync20(attachmentsDir)) {
|
|
12312
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
12002
12313
|
}
|
|
12003
12314
|
let filename = attachment.filename;
|
|
12004
12315
|
if (!filename) {
|
|
@@ -12016,8 +12327,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12016
12327
|
attachment.mediaType = resized.mediaType;
|
|
12017
12328
|
attachment.data = buffer.toString("base64");
|
|
12018
12329
|
}
|
|
12019
|
-
const filePath =
|
|
12020
|
-
|
|
12330
|
+
const filePath = join14(attachmentsDir, filename);
|
|
12331
|
+
writeFileSync6(filePath, buffer);
|
|
12021
12332
|
return filePath;
|
|
12022
12333
|
}
|
|
12023
12334
|
function stripDataUrlPrefix2(data) {
|
|
@@ -12354,7 +12665,20 @@ ${prompt}` });
|
|
|
12354
12665
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12355
12666
|
} else {
|
|
12356
12667
|
console.error("Agent error:", error);
|
|
12357
|
-
|
|
12668
|
+
let debugPayload = void 0;
|
|
12669
|
+
if (isMissingToolResultsError(error)) {
|
|
12670
|
+
try {
|
|
12671
|
+
const recovery = await recoverFromMissingToolResults(sessionId, error);
|
|
12672
|
+
if (recovery) debugPayload = recovery;
|
|
12673
|
+
} catch (recErr) {
|
|
12674
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
12675
|
+
}
|
|
12676
|
+
}
|
|
12677
|
+
await writeSSE(JSON.stringify({
|
|
12678
|
+
type: "error",
|
|
12679
|
+
errorText: error.message,
|
|
12680
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
12681
|
+
}));
|
|
12358
12682
|
try {
|
|
12359
12683
|
await activeStreamQueries.markError(streamId);
|
|
12360
12684
|
} catch {
|
|
@@ -12894,7 +13218,20 @@ agents.post(
|
|
|
12894
13218
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12895
13219
|
} else {
|
|
12896
13220
|
console.error("Agent error:", error);
|
|
12897
|
-
|
|
13221
|
+
let debugPayload = void 0;
|
|
13222
|
+
if (isMissingToolResultsError(error)) {
|
|
13223
|
+
try {
|
|
13224
|
+
const recovery = await recoverFromMissingToolResults(session.id, error);
|
|
13225
|
+
if (recovery) debugPayload = recovery;
|
|
13226
|
+
} catch (recErr) {
|
|
13227
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
13228
|
+
}
|
|
13229
|
+
}
|
|
13230
|
+
await writeSSE(JSON.stringify({
|
|
13231
|
+
type: "error",
|
|
13232
|
+
errorText: error.message,
|
|
13233
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
13234
|
+
}));
|
|
12898
13235
|
await activeStreamQueries.markError(streamId);
|
|
12899
13236
|
}
|
|
12900
13237
|
} finally {
|
|
@@ -12977,26 +13314,26 @@ init_config();
|
|
|
12977
13314
|
import { Hono as Hono3 } from "hono";
|
|
12978
13315
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
12979
13316
|
import { z as z18 } from "zod";
|
|
12980
|
-
import { readFileSync as
|
|
13317
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
12981
13318
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12982
|
-
import { dirname as
|
|
13319
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
12983
13320
|
var __filename = fileURLToPath3(import.meta.url);
|
|
12984
|
-
var __dirname =
|
|
13321
|
+
var __dirname = dirname8(__filename);
|
|
12985
13322
|
var possiblePaths = [
|
|
12986
|
-
|
|
13323
|
+
join15(__dirname, "../package.json"),
|
|
12987
13324
|
// From dist/server -> dist/../package.json
|
|
12988
|
-
|
|
13325
|
+
join15(__dirname, "../../package.json"),
|
|
12989
13326
|
// From dist/server (if nested differently)
|
|
12990
|
-
|
|
13327
|
+
join15(__dirname, "../../../package.json"),
|
|
12991
13328
|
// From src/server/routes (development)
|
|
12992
|
-
|
|
13329
|
+
join15(process.cwd(), "package.json")
|
|
12993
13330
|
// From current working directory
|
|
12994
13331
|
];
|
|
12995
13332
|
var currentVersion = "0.0.0";
|
|
12996
13333
|
var packageName = "sparkecoder";
|
|
12997
13334
|
for (const packageJsonPath of possiblePaths) {
|
|
12998
13335
|
try {
|
|
12999
|
-
const packageJson = JSON.parse(
|
|
13336
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
13000
13337
|
if (packageJson.name === "sparkecoder") {
|
|
13001
13338
|
currentVersion = packageJson.version || "0.0.0";
|
|
13002
13339
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -13814,12 +14151,13 @@ slack.post("/events", async (c) => {
|
|
|
13814
14151
|
if (inbound) {
|
|
13815
14152
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
13816
14153
|
if (isThreadReply) {
|
|
13817
|
-
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await
|
|
14154
|
+
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
|
|
13818
14155
|
if (!ours) {
|
|
13819
14156
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
13820
14157
|
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
13821
14158
|
return c.json({ ok: true });
|
|
13822
14159
|
}
|
|
14160
|
+
markThreadOwned(ev.channel, ev.thread_ts);
|
|
13823
14161
|
}
|
|
13824
14162
|
if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
|
|
13825
14163
|
markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
|
|
@@ -13858,18 +14196,6 @@ slack.post("/events", async (c) => {
|
|
|
13858
14196
|
}
|
|
13859
14197
|
return c.json({ ok: true });
|
|
13860
14198
|
});
|
|
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
14199
|
async function findOrCreateOrchestratorId() {
|
|
13874
14200
|
try {
|
|
13875
14201
|
const all = await sessionQueries.list(500, 0);
|
|
@@ -14258,9 +14584,9 @@ init_skills();
|
|
|
14258
14584
|
import { Hono as Hono9 } from "hono";
|
|
14259
14585
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
14260
14586
|
import { z as z22 } from "zod";
|
|
14261
|
-
import { existsSync as
|
|
14587
|
+
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
14262
14588
|
import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
14263
|
-
import { resolve as resolve11, join as
|
|
14589
|
+
import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
14264
14590
|
var skills = new Hono9();
|
|
14265
14591
|
function encodeId(filePath) {
|
|
14266
14592
|
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
@@ -14319,13 +14645,13 @@ function pathToLabel(path) {
|
|
|
14319
14645
|
if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
|
|
14320
14646
|
if (path.includes("/.cursor/rules")) return ".cursor/rules";
|
|
14321
14647
|
if (path.includes("/.claude/skills")) return ".claude/skills";
|
|
14322
|
-
return basename6(
|
|
14648
|
+
return basename6(dirname9(path)) + "/" + basename6(path);
|
|
14323
14649
|
}
|
|
14324
14650
|
skills.get("/", async (c) => {
|
|
14325
14651
|
const dirs = listAllDirectories();
|
|
14326
14652
|
const allSkills = [];
|
|
14327
14653
|
for (const dir of dirs) {
|
|
14328
|
-
if (!
|
|
14654
|
+
if (!existsSync21(dir.path)) continue;
|
|
14329
14655
|
try {
|
|
14330
14656
|
const list = await loadSkillsFromDirectory(dir.path, {
|
|
14331
14657
|
priority: dir.priority,
|
|
@@ -14362,7 +14688,7 @@ skills.get("/", async (c) => {
|
|
|
14362
14688
|
label: d.label,
|
|
14363
14689
|
source: d.source,
|
|
14364
14690
|
alwaysApply: d.alwaysApply,
|
|
14365
|
-
exists:
|
|
14691
|
+
exists: existsSync21(d.path),
|
|
14366
14692
|
writable: isWritable(d.path)
|
|
14367
14693
|
})),
|
|
14368
14694
|
skills: allSkills
|
|
@@ -14370,7 +14696,7 @@ skills.get("/", async (c) => {
|
|
|
14370
14696
|
});
|
|
14371
14697
|
function isWritable(dir) {
|
|
14372
14698
|
try {
|
|
14373
|
-
if (!
|
|
14699
|
+
if (!existsSync21(dir)) return false;
|
|
14374
14700
|
if (dir.includes("/skills/default")) return false;
|
|
14375
14701
|
return true;
|
|
14376
14702
|
} catch {
|
|
@@ -14379,7 +14705,7 @@ function isWritable(dir) {
|
|
|
14379
14705
|
}
|
|
14380
14706
|
skills.get("/:id", async (c) => {
|
|
14381
14707
|
const filePath = decodeId(c.req.param("id"));
|
|
14382
|
-
if (!filePath || !
|
|
14708
|
+
if (!filePath || !existsSync21(filePath)) {
|
|
14383
14709
|
return c.json({ error: "skill not found" }, 404);
|
|
14384
14710
|
}
|
|
14385
14711
|
const content = await readFile12(filePath, "utf-8");
|
|
@@ -14408,8 +14734,8 @@ skills.post(
|
|
|
14408
14734
|
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
14409
14735
|
const ext = extname9(safeName).toLowerCase();
|
|
14410
14736
|
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
14411
|
-
const filePath =
|
|
14412
|
-
if (
|
|
14737
|
+
const filePath = join16(targetDir, finalName);
|
|
14738
|
+
if (existsSync21(filePath)) {
|
|
14413
14739
|
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
14414
14740
|
}
|
|
14415
14741
|
try {
|
|
@@ -14426,7 +14752,7 @@ skills.put(
|
|
|
14426
14752
|
zValidator7("json", z22.object({ content: z22.string() })),
|
|
14427
14753
|
async (c) => {
|
|
14428
14754
|
const filePath = decodeId(c.req.param("id"));
|
|
14429
|
-
if (!filePath || !
|
|
14755
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14430
14756
|
if (filePath.includes("/skills/default")) {
|
|
14431
14757
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14432
14758
|
}
|
|
@@ -14436,7 +14762,7 @@ skills.put(
|
|
|
14436
14762
|
);
|
|
14437
14763
|
skills.delete("/:id", async (c) => {
|
|
14438
14764
|
const filePath = decodeId(c.req.param("id"));
|
|
14439
|
-
if (!filePath || !
|
|
14765
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14440
14766
|
if (filePath.includes("/skills/default")) {
|
|
14441
14767
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14442
14768
|
}
|
|
@@ -14456,7 +14782,7 @@ skills.post(
|
|
|
14456
14782
|
}
|
|
14457
14783
|
const next = [...current, abs];
|
|
14458
14784
|
setSkillsAdditionalDirectories(next);
|
|
14459
|
-
return c.json({ ok: true, path: abs, exists:
|
|
14785
|
+
return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
|
|
14460
14786
|
}
|
|
14461
14787
|
);
|
|
14462
14788
|
skills.delete("/directories", (c) => {
|
|
@@ -14630,13 +14956,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
14630
14956
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
14631
14957
|
function getWebDirectory() {
|
|
14632
14958
|
try {
|
|
14633
|
-
const currentDir =
|
|
14959
|
+
const currentDir = dirname10(fileURLToPath4(import.meta.url));
|
|
14634
14960
|
const webDir = resolve12(currentDir, "..", "web");
|
|
14635
|
-
if (
|
|
14961
|
+
if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
|
|
14636
14962
|
return webDir;
|
|
14637
14963
|
}
|
|
14638
14964
|
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
14639
|
-
if (
|
|
14965
|
+
if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
|
|
14640
14966
|
return altWebDir;
|
|
14641
14967
|
}
|
|
14642
14968
|
return null;
|
|
@@ -14694,23 +15020,23 @@ async function findWebPort(preferredPort) {
|
|
|
14694
15020
|
return { port: preferredPort, alreadyRunning: false };
|
|
14695
15021
|
}
|
|
14696
15022
|
function hasProductionBuild(webDir) {
|
|
14697
|
-
const buildIdPath =
|
|
14698
|
-
return
|
|
15023
|
+
const buildIdPath = join17(webDir, ".next", "BUILD_ID");
|
|
15024
|
+
return existsSync22(buildIdPath);
|
|
14699
15025
|
}
|
|
14700
15026
|
function hasSourceFiles(webDir) {
|
|
14701
|
-
const appDir =
|
|
14702
|
-
const pagesDir =
|
|
14703
|
-
const rootAppDir =
|
|
14704
|
-
const rootPagesDir =
|
|
14705
|
-
return
|
|
15027
|
+
const appDir = join17(webDir, "src", "app");
|
|
15028
|
+
const pagesDir = join17(webDir, "src", "pages");
|
|
15029
|
+
const rootAppDir = join17(webDir, "app");
|
|
15030
|
+
const rootPagesDir = join17(webDir, "pages");
|
|
15031
|
+
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
14706
15032
|
}
|
|
14707
15033
|
function getStandaloneServerPath(webDir) {
|
|
14708
15034
|
const possiblePaths2 = [
|
|
14709
|
-
|
|
14710
|
-
|
|
15035
|
+
join17(webDir, ".next", "standalone", "server.js"),
|
|
15036
|
+
join17(webDir, ".next", "standalone", "web", "server.js")
|
|
14711
15037
|
];
|
|
14712
15038
|
for (const serverPath of possiblePaths2) {
|
|
14713
|
-
if (
|
|
15039
|
+
if (existsSync22(serverPath)) {
|
|
14714
15040
|
return serverPath;
|
|
14715
15041
|
}
|
|
14716
15042
|
}
|
|
@@ -14750,15 +15076,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14750
15076
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
14751
15077
|
return { process: null, port: actualPort };
|
|
14752
15078
|
}
|
|
14753
|
-
const usePnpm =
|
|
14754
|
-
const useNpm = !usePnpm &&
|
|
15079
|
+
const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
|
|
15080
|
+
const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
|
|
14755
15081
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
14756
15082
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
14757
15083
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
14758
15084
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
14759
|
-
const runtimeConfigPath =
|
|
15085
|
+
const runtimeConfigPath = join17(webDir, "runtime-config.json");
|
|
14760
15086
|
try {
|
|
14761
|
-
|
|
15087
|
+
writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
14762
15088
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
14763
15089
|
} catch (err) {
|
|
14764
15090
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -14778,7 +15104,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14778
15104
|
if (standaloneServerPath) {
|
|
14779
15105
|
command = "node";
|
|
14780
15106
|
args = ["server.js"];
|
|
14781
|
-
cwd =
|
|
15107
|
+
cwd = dirname10(standaloneServerPath);
|
|
14782
15108
|
webEnv.PORT = String(actualPort);
|
|
14783
15109
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
14784
15110
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -14972,8 +15298,8 @@ async function startServer(options = {}) {
|
|
|
14972
15298
|
if (options.workingDirectory) {
|
|
14973
15299
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
14974
15300
|
}
|
|
14975
|
-
if (!
|
|
14976
|
-
|
|
15301
|
+
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
15302
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
14977
15303
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
14978
15304
|
}
|
|
14979
15305
|
if (!config.resolvedRemoteServer.url) {
|