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/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,35 @@ 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
|
+
}
|
|
7450
7479
|
function repairToolPairing(messages) {
|
|
7451
7480
|
const toolCallIds = /* @__PURE__ */ new Set();
|
|
7452
7481
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -7812,6 +7841,120 @@ var init_web = __esm({
|
|
|
7812
7841
|
}
|
|
7813
7842
|
});
|
|
7814
7843
|
|
|
7844
|
+
// src/integrations/slack/persistence.ts
|
|
7845
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
7846
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
7847
|
+
function cachePath() {
|
|
7848
|
+
return join9(ensureAppDataDirectory(), FILENAME);
|
|
7849
|
+
}
|
|
7850
|
+
function load() {
|
|
7851
|
+
if (loaded) return;
|
|
7852
|
+
loaded = true;
|
|
7853
|
+
const path = cachePath();
|
|
7854
|
+
if (!existsSync16(path)) return;
|
|
7855
|
+
try {
|
|
7856
|
+
const raw = readFileSync7(path, "utf-8");
|
|
7857
|
+
const parsed = JSON.parse(raw);
|
|
7858
|
+
if (parsed?.version !== FILE_VERSION) return;
|
|
7859
|
+
const now = Date.now();
|
|
7860
|
+
for (const [id, e] of Object.entries(parsed.users || {})) {
|
|
7861
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7862
|
+
userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
|
|
7863
|
+
}
|
|
7864
|
+
}
|
|
7865
|
+
for (const [key2, e] of Object.entries(parsed.threads || {})) {
|
|
7866
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7867
|
+
threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
} catch (err) {
|
|
7871
|
+
console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
function evictIfOversized(map, max) {
|
|
7875
|
+
if (map.size <= max) return;
|
|
7876
|
+
const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
7877
|
+
const toRemove = map.size - max;
|
|
7878
|
+
for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
|
|
7879
|
+
}
|
|
7880
|
+
function scheduleSave() {
|
|
7881
|
+
dirty = true;
|
|
7882
|
+
if (saveTimer) return;
|
|
7883
|
+
saveTimer = setTimeout(() => {
|
|
7884
|
+
saveTimer = null;
|
|
7885
|
+
if (dirty) saveSync();
|
|
7886
|
+
}, SAVE_DEBOUNCE_MS);
|
|
7887
|
+
saveTimer.unref?.();
|
|
7888
|
+
}
|
|
7889
|
+
function saveSync() {
|
|
7890
|
+
dirty = false;
|
|
7891
|
+
try {
|
|
7892
|
+
const path = cachePath();
|
|
7893
|
+
const dir = dirname6(path);
|
|
7894
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
7895
|
+
const payload = {
|
|
7896
|
+
version: FILE_VERSION,
|
|
7897
|
+
users: Object.fromEntries(userMap),
|
|
7898
|
+
threads: Object.fromEntries(threadMap)
|
|
7899
|
+
};
|
|
7900
|
+
const tmp = `${path}.tmp`;
|
|
7901
|
+
writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
|
|
7902
|
+
renameSync(tmp, path);
|
|
7903
|
+
} catch (err) {
|
|
7904
|
+
console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7907
|
+
function hookExit() {
|
|
7908
|
+
if (exitHooked) return;
|
|
7909
|
+
exitHooked = true;
|
|
7910
|
+
const flush2 = () => {
|
|
7911
|
+
if (dirty) saveSync();
|
|
7912
|
+
};
|
|
7913
|
+
process.once("beforeExit", flush2);
|
|
7914
|
+
process.once("SIGINT", flush2);
|
|
7915
|
+
process.once("SIGTERM", flush2);
|
|
7916
|
+
}
|
|
7917
|
+
function getCachedUserName(userId) {
|
|
7918
|
+
load();
|
|
7919
|
+
return userMap.get(userId);
|
|
7920
|
+
}
|
|
7921
|
+
function setCachedUserName(userId, entry2) {
|
|
7922
|
+
load();
|
|
7923
|
+
hookExit();
|
|
7924
|
+
userMap.set(userId, entry2);
|
|
7925
|
+
evictIfOversized(userMap, MAX_USERS);
|
|
7926
|
+
scheduleSave();
|
|
7927
|
+
}
|
|
7928
|
+
function getCachedThreadOwnership(key2) {
|
|
7929
|
+
load();
|
|
7930
|
+
return threadMap.get(key2);
|
|
7931
|
+
}
|
|
7932
|
+
function setCachedThreadOwnership(key2, entry2) {
|
|
7933
|
+
load();
|
|
7934
|
+
hookExit();
|
|
7935
|
+
threadMap.set(key2, entry2);
|
|
7936
|
+
evictIfOversized(threadMap, MAX_THREADS);
|
|
7937
|
+
scheduleSave();
|
|
7938
|
+
}
|
|
7939
|
+
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
7940
|
+
var init_persistence = __esm({
|
|
7941
|
+
"src/integrations/slack/persistence.ts"() {
|
|
7942
|
+
"use strict";
|
|
7943
|
+
init_config();
|
|
7944
|
+
FILENAME = "slack-cache.json";
|
|
7945
|
+
FILE_VERSION = 1;
|
|
7946
|
+
SAVE_DEBOUNCE_MS = 500;
|
|
7947
|
+
MAX_USERS = 5e3;
|
|
7948
|
+
MAX_THREADS = 5e3;
|
|
7949
|
+
loaded = false;
|
|
7950
|
+
userMap = /* @__PURE__ */ new Map();
|
|
7951
|
+
threadMap = /* @__PURE__ */ new Map();
|
|
7952
|
+
dirty = false;
|
|
7953
|
+
saveTimer = null;
|
|
7954
|
+
exitHooked = false;
|
|
7955
|
+
}
|
|
7956
|
+
});
|
|
7957
|
+
|
|
7815
7958
|
// src/integrations/slack/client.ts
|
|
7816
7959
|
function readSlackConfig() {
|
|
7817
7960
|
try {
|
|
@@ -7934,13 +8077,13 @@ async function fetchSlackUserName(userId) {
|
|
|
7934
8077
|
async function resolveSlackUserName(userId) {
|
|
7935
8078
|
if (!userId) return null;
|
|
7936
8079
|
const now = Date.now();
|
|
7937
|
-
const hit =
|
|
8080
|
+
const hit = getCachedUserName(userId);
|
|
7938
8081
|
if (hit && hit.expiresAt > now) return hit.name;
|
|
7939
8082
|
const inflight = userInflight.get(userId);
|
|
7940
8083
|
if (inflight) return inflight;
|
|
7941
8084
|
const p = (async () => {
|
|
7942
8085
|
const name = await fetchSlackUserName(userId);
|
|
7943
|
-
|
|
8086
|
+
setCachedUserName(userId, {
|
|
7944
8087
|
name,
|
|
7945
8088
|
expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
7946
8089
|
});
|
|
@@ -7962,11 +8105,63 @@ async function normalizeSlackMentions(text) {
|
|
|
7962
8105
|
}
|
|
7963
8106
|
return text.replace(userMentionRe, (_full, id, label) => {
|
|
7964
8107
|
if (label) return `${label} <@${id}>`;
|
|
7965
|
-
const cached =
|
|
8108
|
+
const cached = getCachedUserName(id);
|
|
7966
8109
|
const name = cached?.name;
|
|
7967
8110
|
return name ? `${name} <@${id}>` : `<@${id}>`;
|
|
7968
8111
|
});
|
|
7969
8112
|
}
|
|
8113
|
+
function threadCacheKey(channel, threadTs) {
|
|
8114
|
+
return `${channel}\u241F${threadTs}`;
|
|
8115
|
+
}
|
|
8116
|
+
async function fetchBotParticipatedInThread(channel, threadTs) {
|
|
8117
|
+
const token = getSlackBotToken();
|
|
8118
|
+
if (!token) return false;
|
|
8119
|
+
const self = await ensureSlackSelfIdentity();
|
|
8120
|
+
if (!self) return false;
|
|
8121
|
+
try {
|
|
8122
|
+
const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
|
|
8123
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
8124
|
+
const data = await res.json().catch(() => ({}));
|
|
8125
|
+
if (!data?.ok) {
|
|
8126
|
+
console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
8127
|
+
return false;
|
|
8128
|
+
}
|
|
8129
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
8130
|
+
return messages.some(
|
|
8131
|
+
(m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
|
|
8132
|
+
);
|
|
8133
|
+
} catch (err) {
|
|
8134
|
+
console.warn(`[slack] conversations.replies error:`, err?.message || err);
|
|
8135
|
+
return false;
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
async function botParticipatedInThread(channel, threadTs) {
|
|
8139
|
+
if (!channel || !threadTs) return false;
|
|
8140
|
+
const key2 = threadCacheKey(channel, threadTs);
|
|
8141
|
+
const now = Date.now();
|
|
8142
|
+
const hit = getCachedThreadOwnership(key2);
|
|
8143
|
+
if (hit && hit.expiresAt > now) return hit.owned;
|
|
8144
|
+
const inflight = threadOwnedInflight.get(key2);
|
|
8145
|
+
if (inflight) return inflight;
|
|
8146
|
+
const p = (async () => {
|
|
8147
|
+
const owned = await fetchBotParticipatedInThread(channel, threadTs);
|
|
8148
|
+
setCachedThreadOwnership(key2, {
|
|
8149
|
+
owned,
|
|
8150
|
+
expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
|
|
8151
|
+
});
|
|
8152
|
+
threadOwnedInflight.delete(key2);
|
|
8153
|
+
return owned;
|
|
8154
|
+
})();
|
|
8155
|
+
threadOwnedInflight.set(key2, p);
|
|
8156
|
+
return p;
|
|
8157
|
+
}
|
|
8158
|
+
function noteBotPostedInThread(channel, threadTs) {
|
|
8159
|
+
if (!channel || !threadTs) return;
|
|
8160
|
+
setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
|
|
8161
|
+
owned: true,
|
|
8162
|
+
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
8163
|
+
});
|
|
8164
|
+
}
|
|
7970
8165
|
function getSlackDeniedReplyPolicy() {
|
|
7971
8166
|
try {
|
|
7972
8167
|
const cfg = getConfig();
|
|
@@ -7979,17 +8174,20 @@ function getSlackDeniedReplyPolicy() {
|
|
|
7979
8174
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
7980
8175
|
}
|
|
7981
8176
|
}
|
|
7982
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS,
|
|
8177
|
+
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
7983
8178
|
var init_client3 = __esm({
|
|
7984
8179
|
"src/integrations/slack/client.ts"() {
|
|
7985
8180
|
"use strict";
|
|
7986
8181
|
init_config();
|
|
8182
|
+
init_persistence();
|
|
7987
8183
|
cachedSelf = null;
|
|
7988
8184
|
selfInflight = null;
|
|
7989
8185
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
7990
8186
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
7991
|
-
userNameCache = /* @__PURE__ */ new Map();
|
|
7992
8187
|
userInflight = /* @__PURE__ */ new Map();
|
|
8188
|
+
THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
8189
|
+
THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
8190
|
+
threadOwnedInflight = /* @__PURE__ */ new Map();
|
|
7993
8191
|
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
8192
|
}
|
|
7995
8193
|
});
|
|
@@ -8089,6 +8287,7 @@ var init_slack = __esm({
|
|
|
8089
8287
|
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
8090
8288
|
if (r.slackChannel && r.threadTs) {
|
|
8091
8289
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
8290
|
+
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8092
8291
|
}
|
|
8093
8292
|
},
|
|
8094
8293
|
displayLabel(ref) {
|
|
@@ -8746,8 +8945,8 @@ var init_orchestrator_actions = __esm({
|
|
|
8746
8945
|
|
|
8747
8946
|
// src/integrations/mcp/store.ts
|
|
8748
8947
|
import { nanoid as nanoid6 } from "nanoid";
|
|
8749
|
-
import { existsSync as
|
|
8750
|
-
import { resolve as resolve10, join as
|
|
8948
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
|
|
8949
|
+
import { resolve as resolve10, join as join10 } from "path";
|
|
8751
8950
|
function readServers() {
|
|
8752
8951
|
try {
|
|
8753
8952
|
const cfg = getConfig();
|
|
@@ -8759,12 +8958,12 @@ function readServers() {
|
|
|
8759
8958
|
function refreshMcpServersFromDisk() {
|
|
8760
8959
|
const candidates = [
|
|
8761
8960
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8762
|
-
|
|
8961
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8763
8962
|
];
|
|
8764
8963
|
for (const path of candidates) {
|
|
8765
|
-
if (!
|
|
8964
|
+
if (!existsSync17(path)) continue;
|
|
8766
8965
|
try {
|
|
8767
|
-
const raw = JSON.parse(
|
|
8966
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
8768
8967
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8769
8968
|
setMcpServers(servers2);
|
|
8770
8969
|
return servers2;
|
|
@@ -9377,7 +9576,7 @@ __export(recorder_exports, {
|
|
|
9377
9576
|
import { exec as exec5 } from "child_process";
|
|
9378
9577
|
import { promisify as promisify5 } from "util";
|
|
9379
9578
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
9380
|
-
import { join as
|
|
9579
|
+
import { join as join11 } from "path";
|
|
9381
9580
|
import { tmpdir } from "os";
|
|
9382
9581
|
import { nanoid as nanoid7 } from "nanoid";
|
|
9383
9582
|
async function checkFfmpeg() {
|
|
@@ -9434,21 +9633,21 @@ var init_recorder = __esm({
|
|
|
9434
9633
|
*/
|
|
9435
9634
|
async encode() {
|
|
9436
9635
|
if (this.frames.length === 0) return null;
|
|
9437
|
-
const workDir =
|
|
9636
|
+
const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
9438
9637
|
await mkdir4(workDir, { recursive: true });
|
|
9439
9638
|
try {
|
|
9440
9639
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9441
|
-
const framePath =
|
|
9640
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
9442
9641
|
await writeFile5(framePath, this.frames[i].data);
|
|
9443
9642
|
}
|
|
9444
9643
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9445
9644
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9446
9645
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9447
|
-
const outputPath =
|
|
9646
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
9448
9647
|
const hasFfmpeg = await checkFfmpeg();
|
|
9449
9648
|
if (hasFfmpeg) {
|
|
9450
9649
|
await execAsync5(
|
|
9451
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
9650
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
9452
9651
|
{ timeout: 12e4 }
|
|
9453
9652
|
);
|
|
9454
9653
|
} else {
|
|
@@ -9460,7 +9659,7 @@ var init_recorder = __esm({
|
|
|
9460
9659
|
const files = await readdir5(workDir);
|
|
9461
9660
|
for (const f of files) {
|
|
9462
9661
|
if (f.startsWith("frame_")) {
|
|
9463
|
-
await unlink2(
|
|
9662
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
9464
9663
|
});
|
|
9465
9664
|
}
|
|
9466
9665
|
}
|
|
@@ -9727,7 +9926,8 @@ ${personality.trim()}`;
|
|
|
9727
9926
|
}
|
|
9728
9927
|
const messages = await this.context.getMessages();
|
|
9729
9928
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9730
|
-
const
|
|
9929
|
+
const approvalWrapped = this.wrapToolsWithApproval(options, tools);
|
|
9930
|
+
const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
|
|
9731
9931
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9732
9932
|
const stream = streamText2({
|
|
9733
9933
|
model: resolveModel(this.session.model),
|
|
@@ -9741,6 +9941,17 @@ ${personality.trim()}`;
|
|
|
9741
9941
|
providerOptions: useAnthropic ? {
|
|
9742
9942
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9743
9943
|
} : void 0,
|
|
9944
|
+
// Run repairToolPairing before EVERY step's model call, not just the
|
|
9945
|
+
// first one. The AI SDK's multi-step loop can otherwise feed the model
|
|
9946
|
+
// a prompt containing an orphan tool-call (e.g. when a previous step's
|
|
9947
|
+
// tool result was lost, dropped during compaction, or the stream was
|
|
9948
|
+
// aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
|
|
9949
|
+
// ever reaches the model and we never hit AI_MissingToolResultsError.
|
|
9950
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
9951
|
+
const repaired = repairToolPairing(stepMessages);
|
|
9952
|
+
if (repaired === stepMessages) return {};
|
|
9953
|
+
return { messages: repaired };
|
|
9954
|
+
},
|
|
9744
9955
|
onStepFinish: async (step) => {
|
|
9745
9956
|
options.onStepFinish?.(step);
|
|
9746
9957
|
},
|
|
@@ -9776,7 +9987,7 @@ ${personality.trim()}`;
|
|
|
9776
9987
|
});
|
|
9777
9988
|
const messages = await this.context.getMessages();
|
|
9778
9989
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9779
|
-
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
9990
|
+
const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
|
|
9780
9991
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9781
9992
|
const result = await generateText3({
|
|
9782
9993
|
model: resolveModel(this.session.model),
|
|
@@ -9787,7 +9998,13 @@ ${personality.trim()}`;
|
|
|
9787
9998
|
// Enable extended thinking/reasoning for models that support it
|
|
9788
9999
|
providerOptions: useAnthropic ? {
|
|
9789
10000
|
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
9790
|
-
} : void 0
|
|
10001
|
+
} : void 0,
|
|
10002
|
+
// Repair tool pairing before every step (see `stream()` for full rationale).
|
|
10003
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10004
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10005
|
+
if (repaired === stepMessages) return {};
|
|
10006
|
+
return { messages: repaired };
|
|
10007
|
+
}
|
|
9791
10008
|
});
|
|
9792
10009
|
const responseMessages = result.response.messages;
|
|
9793
10010
|
this.context.addResponseMessages(responseMessages);
|
|
@@ -9964,12 +10181,19 @@ ${p.text}` : p.text;
|
|
|
9964
10181
|
model: resolveModel(this.session.model),
|
|
9965
10182
|
system: systemPrompt,
|
|
9966
10183
|
messages,
|
|
9967
|
-
tools: taskTools,
|
|
10184
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
9968
10185
|
stopWhen: stepCountIs2(500),
|
|
9969
10186
|
abortSignal: combinedAbort,
|
|
9970
10187
|
providerOptions: useAnthropic ? {
|
|
9971
10188
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9972
10189
|
} : void 0,
|
|
10190
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
10191
|
+
// every step so we never feed the model an orphan tool-call.
|
|
10192
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10193
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10194
|
+
if (repaired === stepMessages) return {};
|
|
10195
|
+
return { messages: repaired };
|
|
10196
|
+
},
|
|
9973
10197
|
onStepFinish: async (step) => {
|
|
9974
10198
|
options.onStepFinish?.(step);
|
|
9975
10199
|
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
@@ -10203,11 +10427,11 @@ ${p.text}` : p.text;
|
|
|
10203
10427
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10204
10428
|
if (!isRemoteConfigured2()) return [];
|
|
10205
10429
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
10206
|
-
const { join:
|
|
10430
|
+
const { join: join18, basename: basename7 } = await import("path");
|
|
10207
10431
|
const urls = [];
|
|
10208
10432
|
for (const filePath of filePaths) {
|
|
10209
10433
|
try {
|
|
10210
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10434
|
+
const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
|
|
10211
10435
|
const fileName = basename7(fullPath);
|
|
10212
10436
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10213
10437
|
const mimeMap = {
|
|
@@ -10412,19 +10636,19 @@ var init_session_lock = __esm({
|
|
|
10412
10636
|
});
|
|
10413
10637
|
|
|
10414
10638
|
// src/orchestrator/webhook-events.ts
|
|
10415
|
-
import { existsSync as
|
|
10416
|
-
import { dirname as
|
|
10639
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
10640
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
10417
10641
|
import { nanoid as nanoid9 } from "nanoid";
|
|
10418
10642
|
function logFilePath() {
|
|
10419
|
-
return
|
|
10643
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10420
10644
|
}
|
|
10421
10645
|
function ensureLoaded() {
|
|
10422
10646
|
if (cache !== null) return cache;
|
|
10423
10647
|
cache = [];
|
|
10424
10648
|
try {
|
|
10425
10649
|
const p = logFilePath();
|
|
10426
|
-
if (!
|
|
10427
|
-
const lines =
|
|
10650
|
+
if (!existsSync18(p)) return cache;
|
|
10651
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
10428
10652
|
for (const line of lines) {
|
|
10429
10653
|
try {
|
|
10430
10654
|
cache.push(JSON.parse(line));
|
|
@@ -10434,7 +10658,7 @@ function ensureLoaded() {
|
|
|
10434
10658
|
if (cache.length > MAX_EVENTS) {
|
|
10435
10659
|
cache = cache.slice(-MAX_EVENTS);
|
|
10436
10660
|
try {
|
|
10437
|
-
|
|
10661
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10438
10662
|
} catch {
|
|
10439
10663
|
}
|
|
10440
10664
|
}
|
|
@@ -10448,7 +10672,7 @@ function appendEvent(ev) {
|
|
|
10448
10672
|
if (list.length > MAX_EVENTS) list.shift();
|
|
10449
10673
|
try {
|
|
10450
10674
|
const p = logFilePath();
|
|
10451
|
-
|
|
10675
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10452
10676
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10453
10677
|
} catch {
|
|
10454
10678
|
}
|
|
@@ -10479,8 +10703,8 @@ function updateEvent(id, patch) {
|
|
|
10479
10703
|
list[i] = { ...list[i], ...patch };
|
|
10480
10704
|
try {
|
|
10481
10705
|
const p = logFilePath();
|
|
10482
|
-
|
|
10483
|
-
|
|
10706
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10707
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10484
10708
|
} catch {
|
|
10485
10709
|
}
|
|
10486
10710
|
}
|
|
@@ -10512,7 +10736,7 @@ function listEvents(filter = {}) {
|
|
|
10512
10736
|
function clearAllEvents() {
|
|
10513
10737
|
cache = [];
|
|
10514
10738
|
try {
|
|
10515
|
-
|
|
10739
|
+
writeFileSync4(logFilePath(), "");
|
|
10516
10740
|
} catch {
|
|
10517
10741
|
}
|
|
10518
10742
|
}
|
|
@@ -10786,8 +11010,8 @@ import { Hono as Hono10 } from "hono";
|
|
|
10786
11010
|
import { serve } from "@hono/node-server";
|
|
10787
11011
|
import { cors } from "hono/cors";
|
|
10788
11012
|
import { logger } from "hono/logger";
|
|
10789
|
-
import { existsSync as
|
|
10790
|
-
import { resolve as resolve12, dirname as
|
|
11013
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
11014
|
+
import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
|
|
10791
11015
|
import { spawn as spawn2 } from "child_process";
|
|
10792
11016
|
import { createServer as createNetServer } from "net";
|
|
10793
11017
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -10801,9 +11025,9 @@ init_checkpoints();
|
|
|
10801
11025
|
import { Hono } from "hono";
|
|
10802
11026
|
import { zValidator } from "@hono/zod-validator";
|
|
10803
11027
|
import { z as z16 } from "zod";
|
|
10804
|
-
import { existsSync as
|
|
11028
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
10805
11029
|
import { readdir as readdir6 } from "fs/promises";
|
|
10806
|
-
import { join as
|
|
11030
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
10807
11031
|
import { nanoid as nanoid10 } from "nanoid";
|
|
10808
11032
|
|
|
10809
11033
|
// src/tasks/agent-status.ts
|
|
@@ -11441,12 +11665,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11441
11665
|
});
|
|
11442
11666
|
function getAttachmentsDir(sessionId) {
|
|
11443
11667
|
const appDataDir = getAppDataDirectory();
|
|
11444
|
-
return
|
|
11668
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
11445
11669
|
}
|
|
11446
11670
|
function ensureAttachmentsDir(sessionId) {
|
|
11447
11671
|
const dir = getAttachmentsDir(sessionId);
|
|
11448
|
-
if (!
|
|
11449
|
-
|
|
11672
|
+
if (!existsSync19(dir)) {
|
|
11673
|
+
mkdirSync8(dir, { recursive: true });
|
|
11450
11674
|
}
|
|
11451
11675
|
return dir;
|
|
11452
11676
|
}
|
|
@@ -11457,12 +11681,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11457
11681
|
return c.json({ error: "Session not found" }, 404);
|
|
11458
11682
|
}
|
|
11459
11683
|
const dir = getAttachmentsDir(sessionId);
|
|
11460
|
-
if (!
|
|
11684
|
+
if (!existsSync19(dir)) {
|
|
11461
11685
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
11462
11686
|
}
|
|
11463
11687
|
const files = readdirSync3(dir);
|
|
11464
11688
|
const attachments = files.map((filename) => {
|
|
11465
|
-
const filePath =
|
|
11689
|
+
const filePath = join13(dir, filename);
|
|
11466
11690
|
const stats = statSync2(filePath);
|
|
11467
11691
|
return {
|
|
11468
11692
|
id: filename.split("_")[0],
|
|
@@ -11497,9 +11721,9 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11497
11721
|
const id = nanoid10(10);
|
|
11498
11722
|
const ext = extname8(file.name) || "";
|
|
11499
11723
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11500
|
-
const filePath =
|
|
11724
|
+
const filePath = join13(dir, safeFilename);
|
|
11501
11725
|
const arrayBuffer = await file.arrayBuffer();
|
|
11502
|
-
|
|
11726
|
+
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
11503
11727
|
return c.json({
|
|
11504
11728
|
id,
|
|
11505
11729
|
filename: file.name,
|
|
@@ -11523,13 +11747,13 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11523
11747
|
const id = nanoid10(10);
|
|
11524
11748
|
const ext = extname8(body.filename) || "";
|
|
11525
11749
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11526
|
-
const filePath =
|
|
11750
|
+
const filePath = join13(dir, safeFilename);
|
|
11527
11751
|
let base64Data = body.data;
|
|
11528
11752
|
if (base64Data.includes(",")) {
|
|
11529
11753
|
base64Data = base64Data.split(",")[1];
|
|
11530
11754
|
}
|
|
11531
11755
|
const buffer = Buffer.from(base64Data, "base64");
|
|
11532
|
-
|
|
11756
|
+
writeFileSync5(filePath, buffer);
|
|
11533
11757
|
return c.json({
|
|
11534
11758
|
id,
|
|
11535
11759
|
filename: body.filename,
|
|
@@ -11552,7 +11776,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11552
11776
|
return c.json({ error: "Session not found" }, 404);
|
|
11553
11777
|
}
|
|
11554
11778
|
const dir = getAttachmentsDir(sessionId);
|
|
11555
|
-
if (!
|
|
11779
|
+
if (!existsSync19(dir)) {
|
|
11556
11780
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11557
11781
|
}
|
|
11558
11782
|
const files = readdirSync3(dir);
|
|
@@ -11560,7 +11784,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11560
11784
|
if (!file) {
|
|
11561
11785
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11562
11786
|
}
|
|
11563
|
-
const filePath =
|
|
11787
|
+
const filePath = join13(dir, file);
|
|
11564
11788
|
unlinkSync2(filePath);
|
|
11565
11789
|
return c.json({ success: true, id: attachmentId });
|
|
11566
11790
|
});
|
|
@@ -11643,7 +11867,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
11643
11867
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
11644
11868
|
for (const entry2 of entries) {
|
|
11645
11869
|
if (results.length >= limit * 2) break;
|
|
11646
|
-
const fullPath =
|
|
11870
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
11647
11871
|
const relativePath = relative9(baseDir, fullPath);
|
|
11648
11872
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
11649
11873
|
continue;
|
|
@@ -11691,7 +11915,7 @@ sessions2.get(
|
|
|
11691
11915
|
return c.json({ error: "Session not found" }, 404);
|
|
11692
11916
|
}
|
|
11693
11917
|
const workingDirectory = session.workingDirectory;
|
|
11694
|
-
if (!
|
|
11918
|
+
if (!existsSync19(workingDirectory)) {
|
|
11695
11919
|
return c.json({
|
|
11696
11920
|
sessionId,
|
|
11697
11921
|
workingDirectory,
|
|
@@ -11799,14 +12023,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
|
|
|
11799
12023
|
|
|
11800
12024
|
// src/server/routes/agents.ts
|
|
11801
12025
|
init_db();
|
|
11802
|
-
init_agent();
|
|
11803
|
-
init_session_lock();
|
|
11804
|
-
init_config();
|
|
11805
12026
|
import { Hono as Hono2 } from "hono";
|
|
11806
12027
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
11807
12028
|
import { z as z17 } from "zod";
|
|
11808
|
-
import { existsSync as
|
|
11809
|
-
import { join as
|
|
12029
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
12030
|
+
import { join as join14 } from "path";
|
|
12031
|
+
|
|
12032
|
+
// src/agent/missing-tool-recovery.ts
|
|
12033
|
+
init_db();
|
|
12034
|
+
function extractMissingToolCallIds(error) {
|
|
12035
|
+
if (!error) return [];
|
|
12036
|
+
const e = error;
|
|
12037
|
+
if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
|
|
12038
|
+
return e.toolCallIds;
|
|
12039
|
+
}
|
|
12040
|
+
const msg = typeof e.message === "string" ? e.message : "";
|
|
12041
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12042
|
+
for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
|
|
12043
|
+
for (const id of m[1].split(/\s*,\s*/)) {
|
|
12044
|
+
const trimmed = id.trim();
|
|
12045
|
+
if (trimmed) ids.add(trimmed);
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
return [...ids];
|
|
12049
|
+
}
|
|
12050
|
+
function isMissingToolResultsError(error) {
|
|
12051
|
+
if (!error) return false;
|
|
12052
|
+
const e = error;
|
|
12053
|
+
if (e.name === "AI_MissingToolResultsError") return true;
|
|
12054
|
+
if (Array.isArray(e.toolCallIds)) return true;
|
|
12055
|
+
return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
|
|
12056
|
+
}
|
|
12057
|
+
async function recoverFromMissingToolResults(sessionId, error) {
|
|
12058
|
+
const toolCallIds = extractMissingToolCallIds(error);
|
|
12059
|
+
if (toolCallIds.length === 0) return null;
|
|
12060
|
+
let history = [];
|
|
12061
|
+
try {
|
|
12062
|
+
history = await messageQueries.getModelMessages(sessionId);
|
|
12063
|
+
} catch (err) {
|
|
12064
|
+
console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
|
|
12065
|
+
}
|
|
12066
|
+
const toolNameByCallId = /* @__PURE__ */ new Map();
|
|
12067
|
+
const existingResultIds = /* @__PURE__ */ new Set();
|
|
12068
|
+
for (const msg of history) {
|
|
12069
|
+
if (!Array.isArray(msg.content)) continue;
|
|
12070
|
+
for (const part of msg.content) {
|
|
12071
|
+
if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
|
|
12072
|
+
if (typeof part.toolName === "string") toolNameByCallId.set(part.toolCallId, part.toolName);
|
|
12073
|
+
}
|
|
12074
|
+
if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
|
|
12075
|
+
existingResultIds.add(part.toolCallId);
|
|
12076
|
+
}
|
|
12077
|
+
}
|
|
12078
|
+
}
|
|
12079
|
+
const resolved = toolCallIds.map((id) => ({
|
|
12080
|
+
toolCallId: id,
|
|
12081
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
12082
|
+
foundInAssistantMessage: toolNameByCallId.has(id)
|
|
12083
|
+
}));
|
|
12084
|
+
const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
|
|
12085
|
+
let syntheticToolMessageSaved = false;
|
|
12086
|
+
if (stillOrphaned.length > 0) {
|
|
12087
|
+
const syntheticParts = stillOrphaned.map((id) => ({
|
|
12088
|
+
type: "tool-result",
|
|
12089
|
+
toolCallId: id,
|
|
12090
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
12091
|
+
output: {
|
|
12092
|
+
type: "text",
|
|
12093
|
+
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.]"
|
|
12094
|
+
}
|
|
12095
|
+
}));
|
|
12096
|
+
try {
|
|
12097
|
+
await messageQueries.create(sessionId, {
|
|
12098
|
+
role: "tool",
|
|
12099
|
+
content: syntheticParts
|
|
12100
|
+
});
|
|
12101
|
+
syntheticToolMessageSaved = true;
|
|
12102
|
+
} catch (err) {
|
|
12103
|
+
console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
|
|
12107
|
+
return {
|
|
12108
|
+
kind: "missing_tool_results",
|
|
12109
|
+
toolCallIds,
|
|
12110
|
+
resolved,
|
|
12111
|
+
syntheticToolMessageSaved,
|
|
12112
|
+
lastFewMessageRoles,
|
|
12113
|
+
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."
|
|
12114
|
+
};
|
|
12115
|
+
}
|
|
12116
|
+
|
|
12117
|
+
// src/server/routes/agents.ts
|
|
12118
|
+
init_agent();
|
|
12119
|
+
init_session_lock();
|
|
12120
|
+
init_config();
|
|
11810
12121
|
|
|
11811
12122
|
// src/server/resumable-stream.ts
|
|
11812
12123
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -12013,12 +12324,12 @@ var rejectSchema = z17.object({
|
|
|
12013
12324
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12014
12325
|
function getAttachmentsDirectory(sessionId) {
|
|
12015
12326
|
const appDataDir = getAppDataDirectory();
|
|
12016
|
-
return
|
|
12327
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
12017
12328
|
}
|
|
12018
12329
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12019
12330
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
12020
|
-
if (!
|
|
12021
|
-
|
|
12331
|
+
if (!existsSync20(attachmentsDir)) {
|
|
12332
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
12022
12333
|
}
|
|
12023
12334
|
let filename = attachment.filename;
|
|
12024
12335
|
if (!filename) {
|
|
@@ -12036,8 +12347,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12036
12347
|
attachment.mediaType = resized.mediaType;
|
|
12037
12348
|
attachment.data = buffer.toString("base64");
|
|
12038
12349
|
}
|
|
12039
|
-
const filePath =
|
|
12040
|
-
|
|
12350
|
+
const filePath = join14(attachmentsDir, filename);
|
|
12351
|
+
writeFileSync6(filePath, buffer);
|
|
12041
12352
|
return filePath;
|
|
12042
12353
|
}
|
|
12043
12354
|
function stripDataUrlPrefix2(data) {
|
|
@@ -12374,7 +12685,20 @@ ${prompt}` });
|
|
|
12374
12685
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12375
12686
|
} else {
|
|
12376
12687
|
console.error("Agent error:", error);
|
|
12377
|
-
|
|
12688
|
+
let debugPayload = void 0;
|
|
12689
|
+
if (isMissingToolResultsError(error)) {
|
|
12690
|
+
try {
|
|
12691
|
+
const recovery = await recoverFromMissingToolResults(sessionId, error);
|
|
12692
|
+
if (recovery) debugPayload = recovery;
|
|
12693
|
+
} catch (recErr) {
|
|
12694
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12697
|
+
await writeSSE(JSON.stringify({
|
|
12698
|
+
type: "error",
|
|
12699
|
+
errorText: error.message,
|
|
12700
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
12701
|
+
}));
|
|
12378
12702
|
try {
|
|
12379
12703
|
await activeStreamQueries.markError(streamId);
|
|
12380
12704
|
} catch {
|
|
@@ -12914,7 +13238,20 @@ agents.post(
|
|
|
12914
13238
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12915
13239
|
} else {
|
|
12916
13240
|
console.error("Agent error:", error);
|
|
12917
|
-
|
|
13241
|
+
let debugPayload = void 0;
|
|
13242
|
+
if (isMissingToolResultsError(error)) {
|
|
13243
|
+
try {
|
|
13244
|
+
const recovery = await recoverFromMissingToolResults(session.id, error);
|
|
13245
|
+
if (recovery) debugPayload = recovery;
|
|
13246
|
+
} catch (recErr) {
|
|
13247
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
13248
|
+
}
|
|
13249
|
+
}
|
|
13250
|
+
await writeSSE(JSON.stringify({
|
|
13251
|
+
type: "error",
|
|
13252
|
+
errorText: error.message,
|
|
13253
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
13254
|
+
}));
|
|
12918
13255
|
await activeStreamQueries.markError(streamId);
|
|
12919
13256
|
}
|
|
12920
13257
|
} finally {
|
|
@@ -12997,26 +13334,26 @@ init_config();
|
|
|
12997
13334
|
import { Hono as Hono3 } from "hono";
|
|
12998
13335
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
12999
13336
|
import { z as z18 } from "zod";
|
|
13000
|
-
import { readFileSync as
|
|
13337
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
13001
13338
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13002
|
-
import { dirname as
|
|
13339
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
13003
13340
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13004
|
-
var __dirname =
|
|
13341
|
+
var __dirname = dirname8(__filename);
|
|
13005
13342
|
var possiblePaths = [
|
|
13006
|
-
|
|
13343
|
+
join15(__dirname, "../package.json"),
|
|
13007
13344
|
// From dist/server -> dist/../package.json
|
|
13008
|
-
|
|
13345
|
+
join15(__dirname, "../../package.json"),
|
|
13009
13346
|
// From dist/server (if nested differently)
|
|
13010
|
-
|
|
13347
|
+
join15(__dirname, "../../../package.json"),
|
|
13011
13348
|
// From src/server/routes (development)
|
|
13012
|
-
|
|
13349
|
+
join15(process.cwd(), "package.json")
|
|
13013
13350
|
// From current working directory
|
|
13014
13351
|
];
|
|
13015
13352
|
var currentVersion = "0.0.0";
|
|
13016
13353
|
var packageName = "sparkecoder";
|
|
13017
13354
|
for (const packageJsonPath of possiblePaths) {
|
|
13018
13355
|
try {
|
|
13019
|
-
const packageJson = JSON.parse(
|
|
13356
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
13020
13357
|
if (packageJson.name === "sparkecoder") {
|
|
13021
13358
|
currentVersion = packageJson.version || "0.0.0";
|
|
13022
13359
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -13834,12 +14171,13 @@ slack.post("/events", async (c) => {
|
|
|
13834
14171
|
if (inbound) {
|
|
13835
14172
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
13836
14173
|
if (isThreadReply) {
|
|
13837
|
-
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await
|
|
14174
|
+
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
|
|
13838
14175
|
if (!ours) {
|
|
13839
14176
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
13840
14177
|
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
13841
14178
|
return c.json({ ok: true });
|
|
13842
14179
|
}
|
|
14180
|
+
markThreadOwned(ev.channel, ev.thread_ts);
|
|
13843
14181
|
}
|
|
13844
14182
|
if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
|
|
13845
14183
|
markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
|
|
@@ -13878,18 +14216,6 @@ slack.post("/events", async (c) => {
|
|
|
13878
14216
|
}
|
|
13879
14217
|
return c.json({ ok: true });
|
|
13880
14218
|
});
|
|
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
14219
|
async function findOrCreateOrchestratorId() {
|
|
13894
14220
|
try {
|
|
13895
14221
|
const all = await sessionQueries.list(500, 0);
|
|
@@ -14278,9 +14604,9 @@ init_skills();
|
|
|
14278
14604
|
import { Hono as Hono9 } from "hono";
|
|
14279
14605
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
14280
14606
|
import { z as z22 } from "zod";
|
|
14281
|
-
import { existsSync as
|
|
14607
|
+
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
14282
14608
|
import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
14283
|
-
import { resolve as resolve11, join as
|
|
14609
|
+
import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
14284
14610
|
var skills = new Hono9();
|
|
14285
14611
|
function encodeId(filePath) {
|
|
14286
14612
|
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
@@ -14339,13 +14665,13 @@ function pathToLabel(path) {
|
|
|
14339
14665
|
if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
|
|
14340
14666
|
if (path.includes("/.cursor/rules")) return ".cursor/rules";
|
|
14341
14667
|
if (path.includes("/.claude/skills")) return ".claude/skills";
|
|
14342
|
-
return basename6(
|
|
14668
|
+
return basename6(dirname9(path)) + "/" + basename6(path);
|
|
14343
14669
|
}
|
|
14344
14670
|
skills.get("/", async (c) => {
|
|
14345
14671
|
const dirs = listAllDirectories();
|
|
14346
14672
|
const allSkills = [];
|
|
14347
14673
|
for (const dir of dirs) {
|
|
14348
|
-
if (!
|
|
14674
|
+
if (!existsSync21(dir.path)) continue;
|
|
14349
14675
|
try {
|
|
14350
14676
|
const list = await loadSkillsFromDirectory(dir.path, {
|
|
14351
14677
|
priority: dir.priority,
|
|
@@ -14382,7 +14708,7 @@ skills.get("/", async (c) => {
|
|
|
14382
14708
|
label: d.label,
|
|
14383
14709
|
source: d.source,
|
|
14384
14710
|
alwaysApply: d.alwaysApply,
|
|
14385
|
-
exists:
|
|
14711
|
+
exists: existsSync21(d.path),
|
|
14386
14712
|
writable: isWritable(d.path)
|
|
14387
14713
|
})),
|
|
14388
14714
|
skills: allSkills
|
|
@@ -14390,7 +14716,7 @@ skills.get("/", async (c) => {
|
|
|
14390
14716
|
});
|
|
14391
14717
|
function isWritable(dir) {
|
|
14392
14718
|
try {
|
|
14393
|
-
if (!
|
|
14719
|
+
if (!existsSync21(dir)) return false;
|
|
14394
14720
|
if (dir.includes("/skills/default")) return false;
|
|
14395
14721
|
return true;
|
|
14396
14722
|
} catch {
|
|
@@ -14399,7 +14725,7 @@ function isWritable(dir) {
|
|
|
14399
14725
|
}
|
|
14400
14726
|
skills.get("/:id", async (c) => {
|
|
14401
14727
|
const filePath = decodeId(c.req.param("id"));
|
|
14402
|
-
if (!filePath || !
|
|
14728
|
+
if (!filePath || !existsSync21(filePath)) {
|
|
14403
14729
|
return c.json({ error: "skill not found" }, 404);
|
|
14404
14730
|
}
|
|
14405
14731
|
const content = await readFile12(filePath, "utf-8");
|
|
@@ -14428,8 +14754,8 @@ skills.post(
|
|
|
14428
14754
|
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
14429
14755
|
const ext = extname9(safeName).toLowerCase();
|
|
14430
14756
|
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
14431
|
-
const filePath =
|
|
14432
|
-
if (
|
|
14757
|
+
const filePath = join16(targetDir, finalName);
|
|
14758
|
+
if (existsSync21(filePath)) {
|
|
14433
14759
|
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
14434
14760
|
}
|
|
14435
14761
|
try {
|
|
@@ -14446,7 +14772,7 @@ skills.put(
|
|
|
14446
14772
|
zValidator7("json", z22.object({ content: z22.string() })),
|
|
14447
14773
|
async (c) => {
|
|
14448
14774
|
const filePath = decodeId(c.req.param("id"));
|
|
14449
|
-
if (!filePath || !
|
|
14775
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14450
14776
|
if (filePath.includes("/skills/default")) {
|
|
14451
14777
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14452
14778
|
}
|
|
@@ -14456,7 +14782,7 @@ skills.put(
|
|
|
14456
14782
|
);
|
|
14457
14783
|
skills.delete("/:id", async (c) => {
|
|
14458
14784
|
const filePath = decodeId(c.req.param("id"));
|
|
14459
|
-
if (!filePath || !
|
|
14785
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14460
14786
|
if (filePath.includes("/skills/default")) {
|
|
14461
14787
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14462
14788
|
}
|
|
@@ -14476,7 +14802,7 @@ skills.post(
|
|
|
14476
14802
|
}
|
|
14477
14803
|
const next = [...current, abs];
|
|
14478
14804
|
setSkillsAdditionalDirectories(next);
|
|
14479
|
-
return c.json({ ok: true, path: abs, exists:
|
|
14805
|
+
return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
|
|
14480
14806
|
}
|
|
14481
14807
|
);
|
|
14482
14808
|
skills.delete("/directories", (c) => {
|
|
@@ -14650,13 +14976,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
14650
14976
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
14651
14977
|
function getWebDirectory() {
|
|
14652
14978
|
try {
|
|
14653
|
-
const currentDir =
|
|
14979
|
+
const currentDir = dirname10(fileURLToPath4(import.meta.url));
|
|
14654
14980
|
const webDir = resolve12(currentDir, "..", "web");
|
|
14655
|
-
if (
|
|
14981
|
+
if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
|
|
14656
14982
|
return webDir;
|
|
14657
14983
|
}
|
|
14658
14984
|
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
14659
|
-
if (
|
|
14985
|
+
if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
|
|
14660
14986
|
return altWebDir;
|
|
14661
14987
|
}
|
|
14662
14988
|
return null;
|
|
@@ -14714,23 +15040,23 @@ async function findWebPort(preferredPort) {
|
|
|
14714
15040
|
return { port: preferredPort, alreadyRunning: false };
|
|
14715
15041
|
}
|
|
14716
15042
|
function hasProductionBuild(webDir) {
|
|
14717
|
-
const buildIdPath =
|
|
14718
|
-
return
|
|
15043
|
+
const buildIdPath = join17(webDir, ".next", "BUILD_ID");
|
|
15044
|
+
return existsSync22(buildIdPath);
|
|
14719
15045
|
}
|
|
14720
15046
|
function hasSourceFiles(webDir) {
|
|
14721
|
-
const appDir =
|
|
14722
|
-
const pagesDir =
|
|
14723
|
-
const rootAppDir =
|
|
14724
|
-
const rootPagesDir =
|
|
14725
|
-
return
|
|
15047
|
+
const appDir = join17(webDir, "src", "app");
|
|
15048
|
+
const pagesDir = join17(webDir, "src", "pages");
|
|
15049
|
+
const rootAppDir = join17(webDir, "app");
|
|
15050
|
+
const rootPagesDir = join17(webDir, "pages");
|
|
15051
|
+
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
14726
15052
|
}
|
|
14727
15053
|
function getStandaloneServerPath(webDir) {
|
|
14728
15054
|
const possiblePaths2 = [
|
|
14729
|
-
|
|
14730
|
-
|
|
15055
|
+
join17(webDir, ".next", "standalone", "server.js"),
|
|
15056
|
+
join17(webDir, ".next", "standalone", "web", "server.js")
|
|
14731
15057
|
];
|
|
14732
15058
|
for (const serverPath of possiblePaths2) {
|
|
14733
|
-
if (
|
|
15059
|
+
if (existsSync22(serverPath)) {
|
|
14734
15060
|
return serverPath;
|
|
14735
15061
|
}
|
|
14736
15062
|
}
|
|
@@ -14770,15 +15096,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14770
15096
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
14771
15097
|
return { process: null, port: actualPort };
|
|
14772
15098
|
}
|
|
14773
|
-
const usePnpm =
|
|
14774
|
-
const useNpm = !usePnpm &&
|
|
15099
|
+
const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
|
|
15100
|
+
const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
|
|
14775
15101
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
14776
15102
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
14777
15103
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
14778
15104
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
14779
|
-
const runtimeConfigPath =
|
|
15105
|
+
const runtimeConfigPath = join17(webDir, "runtime-config.json");
|
|
14780
15106
|
try {
|
|
14781
|
-
|
|
15107
|
+
writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
14782
15108
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
14783
15109
|
} catch (err) {
|
|
14784
15110
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -14798,7 +15124,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14798
15124
|
if (standaloneServerPath) {
|
|
14799
15125
|
command = "node";
|
|
14800
15126
|
args = ["server.js"];
|
|
14801
|
-
cwd =
|
|
15127
|
+
cwd = dirname10(standaloneServerPath);
|
|
14802
15128
|
webEnv.PORT = String(actualPort);
|
|
14803
15129
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
14804
15130
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -14992,8 +15318,8 @@ async function startServer(options = {}) {
|
|
|
14992
15318
|
if (options.workingDirectory) {
|
|
14993
15319
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
14994
15320
|
}
|
|
14995
|
-
if (!
|
|
14996
|
-
|
|
15321
|
+
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
15322
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
14997
15323
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
14998
15324
|
}
|
|
14999
15325
|
if (!config.resolvedRemoteServer.url) {
|