sparkecoder 0.1.120 → 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 +800 -220
- package/dist/cli.js.map +1 -1
- package/dist/index.js +748 -168
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +748 -168
- package/dist/server/index.js.map +1 -1
- package/dist/tools/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/(main)/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
- 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/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
- 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 +3 -3
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_6ab1f7b7._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f3e6443f._.js → [root-of-the-server]__4de426bd._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_62ca4286._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +3 -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/chunks/74ae1f17d607b2fc.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/883ea0d08f88e366.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
- package/web/.next/standalone/web/.next/static/chunks/9b88f148788e4504.js +3 -0
- package/web/.next/standalone/web/.next/static/chunks/acb0fc66f5414af6.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/74ae1f17d607b2fc.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/883ea0d08f88e366.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
- package/web/.next/standalone/web/.next/static/static/chunks/9b88f148788e4504.js +3 -0
- package/web/.next/standalone/web/.next/static/static/chunks/acb0fc66f5414af6.css +1 -0
- package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +464 -1
- package/web/.next/static/chunks/74ae1f17d607b2fc.js +7 -0
- package/web/.next/static/chunks/883ea0d08f88e366.js +1 -0
- package/web/.next/static/chunks/{44c575e006ddb992.js → 91988e253d5fa420.js} +4 -4
- package/web/.next/static/chunks/9b88f148788e4504.js +3 -0
- package/web/.next/static/chunks/acb0fc66f5414af6.css +1 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_7340c8b3._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_41927ef5._.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/2c3c1d478808e4e4.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/3b0501ec3249235f.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/9a3afb48c245fb2a.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/b3bc7244f3477729.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/2c3c1d478808e4e4.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/3b0501ec3249235f.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/9a3afb48c245fb2a.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/b3bc7244f3477729.css +0 -1
- package/web/.next/static/chunks/2c3c1d478808e4e4.js +0 -1
- package/web/.next/static/chunks/3b0501ec3249235f.js +0 -7
- package/web/.next/static/chunks/9a3afb48c245fb2a.js +0 -1
- package/web/.next/static/chunks/b3bc7244f3477729.css +0 -1
- /package/web/.next/standalone/web/.next/static/{static/uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → static/BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → static/BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → static/BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
- /package/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{uy1OnyxIm3QeGGgKEmxAj → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
package/dist/server/index.js
CHANGED
|
@@ -963,6 +963,7 @@ __export(config_exports, {
|
|
|
963
963
|
setApiKey: () => setApiKey,
|
|
964
964
|
setMcpServers: () => setMcpServers,
|
|
965
965
|
setPublicUrl: () => setPublicUrl,
|
|
966
|
+
setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
|
|
966
967
|
setSlackConfig: () => setSlackConfig,
|
|
967
968
|
setWebhookToken: () => setWebhookToken
|
|
968
969
|
});
|
|
@@ -1252,6 +1253,40 @@ function setWebhookToken(token) {
|
|
|
1252
1253
|
console.warn("[config] failed to persist webhook token:", err?.message || err);
|
|
1253
1254
|
}
|
|
1254
1255
|
}
|
|
1256
|
+
function setSkillsAdditionalDirectories(directories) {
|
|
1257
|
+
if (cachedConfig) {
|
|
1258
|
+
const cur = cachedConfig.skills || {};
|
|
1259
|
+
cachedConfig.skills = { ...cur, additionalDirectories: directories };
|
|
1260
|
+
try {
|
|
1261
|
+
const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
|
|
1262
|
+
const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
|
|
1263
|
+
cachedConfig.discoveredSkills = discovered;
|
|
1264
|
+
cachedConfig.resolvedSkillsDirectories = [
|
|
1265
|
+
...discovered.allDirectories,
|
|
1266
|
+
...additionalAbs
|
|
1267
|
+
];
|
|
1268
|
+
} catch {
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
|
|
1273
|
+
const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
|
|
1274
|
+
let raw = {};
|
|
1275
|
+
if (existsSync(target)) {
|
|
1276
|
+
try {
|
|
1277
|
+
raw = JSON.parse(readFileSync(target, "utf-8"));
|
|
1278
|
+
} catch {
|
|
1279
|
+
raw = {};
|
|
1280
|
+
}
|
|
1281
|
+
} else {
|
|
1282
|
+
raw = createDefaultConfig();
|
|
1283
|
+
}
|
|
1284
|
+
raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
|
|
1285
|
+
writeFileSync(target, JSON.stringify(raw, null, 2));
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
console.warn("[config] failed to persist skill directories:", err?.message || err);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1255
1290
|
function setPublicUrl(publicUrl) {
|
|
1256
1291
|
if (cachedConfig) {
|
|
1257
1292
|
cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
|
|
@@ -2294,10 +2329,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2294
2329
|
const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
|
|
2295
2330
|
const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
|
|
2296
2331
|
const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
|
|
2297
|
-
const
|
|
2298
|
-
if (existsSync3(
|
|
2332
|
+
const cachePath2 = join3(cacheDir, key2 + ext);
|
|
2333
|
+
if (existsSync3(cachePath2)) {
|
|
2299
2334
|
console.log(`[image-resize] Cache hit for ${width}x${height} image`);
|
|
2300
|
-
return { buffer: readFileSync2(
|
|
2335
|
+
return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
|
|
2301
2336
|
}
|
|
2302
2337
|
let pipeline = sharp(buffer);
|
|
2303
2338
|
if (needsResize) {
|
|
@@ -2322,7 +2357,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2322
2357
|
}
|
|
2323
2358
|
finalMediaType = "image/jpeg";
|
|
2324
2359
|
}
|
|
2325
|
-
writeFileSync2(
|
|
2360
|
+
writeFileSync2(cachePath2, result);
|
|
2326
2361
|
const resultMeta = await sharp(result).metadata();
|
|
2327
2362
|
console.log(
|
|
2328
2363
|
`[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
|
|
@@ -3013,7 +3048,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3013
3048
|
},
|
|
3014
3049
|
async waitForDiagnostics(filePath, timeoutMs = 5e3) {
|
|
3015
3050
|
const normalized = normalizePath(filePath);
|
|
3016
|
-
return new Promise((
|
|
3051
|
+
return new Promise((resolve13) => {
|
|
3017
3052
|
const startTime = Date.now();
|
|
3018
3053
|
let debounceTimer;
|
|
3019
3054
|
let resolved = false;
|
|
@@ -3032,7 +3067,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3032
3067
|
if (resolved) return;
|
|
3033
3068
|
resolved = true;
|
|
3034
3069
|
cleanup2();
|
|
3035
|
-
|
|
3070
|
+
resolve13(diagnostics.get(normalized) || []);
|
|
3036
3071
|
};
|
|
3037
3072
|
const onDiagnostic = () => {
|
|
3038
3073
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -3407,7 +3442,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3407
3442
|
isChunked: true
|
|
3408
3443
|
});
|
|
3409
3444
|
if (chunkCount > 1) {
|
|
3410
|
-
await new Promise((
|
|
3445
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
3411
3446
|
}
|
|
3412
3447
|
}
|
|
3413
3448
|
}
|
|
@@ -4026,7 +4061,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4026
4061
|
if (!existsSync10(directory)) {
|
|
4027
4062
|
return [];
|
|
4028
4063
|
}
|
|
4029
|
-
const
|
|
4064
|
+
const skills2 = [];
|
|
4030
4065
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
4031
4066
|
for (const entry2 of entries) {
|
|
4032
4067
|
let filePath;
|
|
@@ -4050,7 +4085,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4050
4085
|
if (parsed) {
|
|
4051
4086
|
const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
|
|
4052
4087
|
const loadType = alwaysApply ? "always" : defaultLoadType;
|
|
4053
|
-
|
|
4088
|
+
skills2.push({
|
|
4054
4089
|
name: parsed.metadata.name,
|
|
4055
4090
|
description: parsed.metadata.description,
|
|
4056
4091
|
filePath,
|
|
@@ -4064,7 +4099,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4064
4099
|
} else {
|
|
4065
4100
|
const name = getSkillNameFromPath(filePath);
|
|
4066
4101
|
const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
|
|
4067
|
-
|
|
4102
|
+
skills2.push({
|
|
4068
4103
|
name,
|
|
4069
4104
|
description: firstParagraph.replace(/^#\s*/, "").trim(),
|
|
4070
4105
|
filePath,
|
|
@@ -4077,7 +4112,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4077
4112
|
});
|
|
4078
4113
|
}
|
|
4079
4114
|
}
|
|
4080
|
-
return
|
|
4115
|
+
return skills2.filter(
|
|
4081
4116
|
(s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
|
|
4082
4117
|
);
|
|
4083
4118
|
}
|
|
@@ -4085,8 +4120,8 @@ async function loadAllSkills(directories) {
|
|
|
4085
4120
|
const allSkills = [];
|
|
4086
4121
|
const seenNames = /* @__PURE__ */ new Set();
|
|
4087
4122
|
for (const dir of directories) {
|
|
4088
|
-
const
|
|
4089
|
-
for (const skill of
|
|
4123
|
+
const skills2 = await loadSkillsFromDirectory(dir);
|
|
4124
|
+
for (const skill of skills2) {
|
|
4090
4125
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4091
4126
|
seenNames.add(skill.name.toLowerCase());
|
|
4092
4127
|
allSkills.push(skill);
|
|
@@ -4099,12 +4134,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4099
4134
|
const allSkills = [];
|
|
4100
4135
|
const seenNames = /* @__PURE__ */ new Set();
|
|
4101
4136
|
for (const { path, priority } of discovered.alwaysLoadedDirs) {
|
|
4102
|
-
const
|
|
4137
|
+
const skills2 = await loadSkillsFromDirectory(path, {
|
|
4103
4138
|
priority,
|
|
4104
4139
|
defaultLoadType: "always",
|
|
4105
4140
|
forceAlwaysApply: true
|
|
4106
4141
|
});
|
|
4107
|
-
for (const skill of
|
|
4142
|
+
for (const skill of skills2) {
|
|
4108
4143
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4109
4144
|
seenNames.add(skill.name.toLowerCase());
|
|
4110
4145
|
allSkills.push(skill);
|
|
@@ -4112,12 +4147,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4112
4147
|
}
|
|
4113
4148
|
}
|
|
4114
4149
|
for (const { path, priority } of discovered.onDemandDirs) {
|
|
4115
|
-
const
|
|
4150
|
+
const skills2 = await loadSkillsFromDirectory(path, {
|
|
4116
4151
|
priority,
|
|
4117
4152
|
defaultLoadType: "on_demand",
|
|
4118
4153
|
forceAlwaysApply: false
|
|
4119
4154
|
});
|
|
4120
|
-
for (const skill of
|
|
4155
|
+
for (const skill of skills2) {
|
|
4121
4156
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4122
4157
|
seenNames.add(skill.name.toLowerCase());
|
|
4123
4158
|
allSkills.push(skill);
|
|
@@ -4142,7 +4177,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4142
4177
|
all: allSkills
|
|
4143
4178
|
};
|
|
4144
4179
|
}
|
|
4145
|
-
async function getGlobMatchedSkills(
|
|
4180
|
+
async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
|
|
4146
4181
|
if (activeFiles.length === 0) {
|
|
4147
4182
|
return [];
|
|
4148
4183
|
}
|
|
@@ -4152,7 +4187,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
|
|
|
4152
4187
|
}
|
|
4153
4188
|
return f;
|
|
4154
4189
|
});
|
|
4155
|
-
const matchedSkills =
|
|
4190
|
+
const matchedSkills = skills2.filter((skill) => {
|
|
4156
4191
|
if (skill.alwaysApply || skill.loadType === "always") {
|
|
4157
4192
|
return false;
|
|
4158
4193
|
}
|
|
@@ -4198,8 +4233,8 @@ async function loadSkillContent(skillName, directories) {
|
|
|
4198
4233
|
content: parsed ? parsed.body : content
|
|
4199
4234
|
};
|
|
4200
4235
|
}
|
|
4201
|
-
function formatSkillsForContext(
|
|
4202
|
-
const onDemandSkills =
|
|
4236
|
+
function formatSkillsForContext(skills2) {
|
|
4237
|
+
const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
|
|
4203
4238
|
if (onDemandSkills.length === 0) {
|
|
4204
4239
|
return "No on-demand skills available.";
|
|
4205
4240
|
}
|
|
@@ -4210,12 +4245,12 @@ function formatSkillsForContext(skills) {
|
|
|
4210
4245
|
}
|
|
4211
4246
|
return lines.join("\n");
|
|
4212
4247
|
}
|
|
4213
|
-
function formatAlwaysLoadedSkills(
|
|
4214
|
-
if (
|
|
4248
|
+
function formatAlwaysLoadedSkills(skills2) {
|
|
4249
|
+
if (skills2.length === 0) {
|
|
4215
4250
|
return "";
|
|
4216
4251
|
}
|
|
4217
4252
|
const sections = [];
|
|
4218
|
-
for (const skill of
|
|
4253
|
+
for (const skill of skills2) {
|
|
4219
4254
|
sections.push(`### ${skill.name}
|
|
4220
4255
|
|
|
4221
4256
|
${skill.content}`);
|
|
@@ -4224,12 +4259,12 @@ ${skill.content}`);
|
|
|
4224
4259
|
|
|
4225
4260
|
${sections.join("\n\n---\n\n")}`;
|
|
4226
4261
|
}
|
|
4227
|
-
function formatGlobMatchedSkills(
|
|
4228
|
-
if (
|
|
4262
|
+
function formatGlobMatchedSkills(skills2) {
|
|
4263
|
+
if (skills2.length === 0) {
|
|
4229
4264
|
return "";
|
|
4230
4265
|
}
|
|
4231
4266
|
const sections = [];
|
|
4232
|
-
for (const skill of
|
|
4267
|
+
for (const skill of skills2) {
|
|
4233
4268
|
sections.push(`### ${skill.name}
|
|
4234
4269
|
|
|
4235
4270
|
${skill.content}`);
|
|
@@ -4271,16 +4306,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
4271
4306
|
try {
|
|
4272
4307
|
switch (action) {
|
|
4273
4308
|
case "list": {
|
|
4274
|
-
const
|
|
4309
|
+
const skills2 = await loadAllSkills(options.skillsDirectories);
|
|
4275
4310
|
return {
|
|
4276
4311
|
success: true,
|
|
4277
4312
|
action: "list",
|
|
4278
|
-
skillCount:
|
|
4279
|
-
skills:
|
|
4313
|
+
skillCount: skills2.length,
|
|
4314
|
+
skills: skills2.map((s) => ({
|
|
4280
4315
|
name: s.name,
|
|
4281
4316
|
description: s.description
|
|
4282
4317
|
})),
|
|
4283
|
-
formatted: formatSkillsForContext(
|
|
4318
|
+
formatted: formatSkillsForContext(skills2)
|
|
4284
4319
|
};
|
|
4285
4320
|
}
|
|
4286
4321
|
case "load": {
|
|
@@ -4718,8 +4753,8 @@ var init_subagent = __esm({
|
|
|
4718
4753
|
if (eventQueue.length > 0) {
|
|
4719
4754
|
yield eventQueue.shift();
|
|
4720
4755
|
} else if (!done) {
|
|
4721
|
-
const event = await new Promise((
|
|
4722
|
-
resolveNext =
|
|
4756
|
+
const event = await new Promise((resolve13) => {
|
|
4757
|
+
resolveNext = resolve13;
|
|
4723
4758
|
});
|
|
4724
4759
|
if (event) {
|
|
4725
4760
|
yield event;
|
|
@@ -6495,8 +6530,8 @@ async function buildSystemPrompt(options) {
|
|
|
6495
6530
|
}
|
|
6496
6531
|
} else {
|
|
6497
6532
|
const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
|
|
6498
|
-
const
|
|
6499
|
-
onDemandSkillsContext = formatSkillsForContext(
|
|
6533
|
+
const skills2 = await loadAllSkills2(skillsDirectories);
|
|
6534
|
+
onDemandSkillsContext = formatSkillsForContext(skills2);
|
|
6500
6535
|
}
|
|
6501
6536
|
const todos = await todoQueries.getBySession(sessionId);
|
|
6502
6537
|
const todosContext = formatTodosForContext(todos);
|
|
@@ -7395,6 +7430,35 @@ function stripOrphanedToolResults(msg, removedIds) {
|
|
|
7395
7430
|
if (parts.length === 0) return null;
|
|
7396
7431
|
return { ...msg, content: parts };
|
|
7397
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
|
+
}
|
|
7398
7462
|
function repairToolPairing(messages) {
|
|
7399
7463
|
const toolCallIds = /* @__PURE__ */ new Set();
|
|
7400
7464
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -7760,6 +7824,120 @@ var init_web = __esm({
|
|
|
7760
7824
|
}
|
|
7761
7825
|
});
|
|
7762
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
|
+
|
|
7763
7941
|
// src/integrations/slack/client.ts
|
|
7764
7942
|
function readSlackConfig() {
|
|
7765
7943
|
try {
|
|
@@ -7882,13 +8060,13 @@ async function fetchSlackUserName(userId) {
|
|
|
7882
8060
|
async function resolveSlackUserName(userId) {
|
|
7883
8061
|
if (!userId) return null;
|
|
7884
8062
|
const now = Date.now();
|
|
7885
|
-
const hit =
|
|
8063
|
+
const hit = getCachedUserName(userId);
|
|
7886
8064
|
if (hit && hit.expiresAt > now) return hit.name;
|
|
7887
8065
|
const inflight = userInflight.get(userId);
|
|
7888
8066
|
if (inflight) return inflight;
|
|
7889
8067
|
const p = (async () => {
|
|
7890
8068
|
const name = await fetchSlackUserName(userId);
|
|
7891
|
-
|
|
8069
|
+
setCachedUserName(userId, {
|
|
7892
8070
|
name,
|
|
7893
8071
|
expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
7894
8072
|
});
|
|
@@ -7910,11 +8088,63 @@ async function normalizeSlackMentions(text) {
|
|
|
7910
8088
|
}
|
|
7911
8089
|
return text.replace(userMentionRe, (_full, id, label) => {
|
|
7912
8090
|
if (label) return `${label} <@${id}>`;
|
|
7913
|
-
const cached =
|
|
8091
|
+
const cached = getCachedUserName(id);
|
|
7914
8092
|
const name = cached?.name;
|
|
7915
8093
|
return name ? `${name} <@${id}>` : `<@${id}>`;
|
|
7916
8094
|
});
|
|
7917
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
|
+
}
|
|
7918
8148
|
function getSlackDeniedReplyPolicy() {
|
|
7919
8149
|
try {
|
|
7920
8150
|
const cfg = getConfig();
|
|
@@ -7927,17 +8157,20 @@ function getSlackDeniedReplyPolicy() {
|
|
|
7927
8157
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
7928
8158
|
}
|
|
7929
8159
|
}
|
|
7930
|
-
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;
|
|
7931
8161
|
var init_client3 = __esm({
|
|
7932
8162
|
"src/integrations/slack/client.ts"() {
|
|
7933
8163
|
"use strict";
|
|
7934
8164
|
init_config();
|
|
8165
|
+
init_persistence();
|
|
7935
8166
|
cachedSelf = null;
|
|
7936
8167
|
selfInflight = null;
|
|
7937
8168
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
7938
8169
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
7939
|
-
userNameCache = /* @__PURE__ */ new Map();
|
|
7940
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();
|
|
7941
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.)";
|
|
7942
8175
|
}
|
|
7943
8176
|
});
|
|
@@ -8037,6 +8270,7 @@ var init_slack = __esm({
|
|
|
8037
8270
|
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
8038
8271
|
if (r.slackChannel && r.threadTs) {
|
|
8039
8272
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
8273
|
+
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8040
8274
|
}
|
|
8041
8275
|
},
|
|
8042
8276
|
displayLabel(ref) {
|
|
@@ -8694,8 +8928,8 @@ var init_orchestrator_actions = __esm({
|
|
|
8694
8928
|
|
|
8695
8929
|
// src/integrations/mcp/store.ts
|
|
8696
8930
|
import { nanoid as nanoid6 } from "nanoid";
|
|
8697
|
-
import { existsSync as
|
|
8698
|
-
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";
|
|
8699
8933
|
function readServers() {
|
|
8700
8934
|
try {
|
|
8701
8935
|
const cfg = getConfig();
|
|
@@ -8707,12 +8941,12 @@ function readServers() {
|
|
|
8707
8941
|
function refreshMcpServersFromDisk() {
|
|
8708
8942
|
const candidates = [
|
|
8709
8943
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8710
|
-
|
|
8944
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8711
8945
|
];
|
|
8712
8946
|
for (const path of candidates) {
|
|
8713
|
-
if (!
|
|
8947
|
+
if (!existsSync17(path)) continue;
|
|
8714
8948
|
try {
|
|
8715
|
-
const raw = JSON.parse(
|
|
8949
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
8716
8950
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8717
8951
|
setMcpServers(servers2);
|
|
8718
8952
|
return servers2;
|
|
@@ -8964,11 +9198,11 @@ function waitForTaskQuestionAnswer(question) {
|
|
|
8964
9198
|
if (pendingQuestions.has(k)) {
|
|
8965
9199
|
return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
|
|
8966
9200
|
}
|
|
8967
|
-
return new Promise((
|
|
9201
|
+
return new Promise((resolve13, reject) => {
|
|
8968
9202
|
pendingQuestions.set(k, {
|
|
8969
9203
|
...question,
|
|
8970
9204
|
createdAt: /* @__PURE__ */ new Date(),
|
|
8971
|
-
resolve:
|
|
9205
|
+
resolve: resolve13,
|
|
8972
9206
|
reject
|
|
8973
9207
|
});
|
|
8974
9208
|
});
|
|
@@ -9325,7 +9559,7 @@ __export(recorder_exports, {
|
|
|
9325
9559
|
import { exec as exec5 } from "child_process";
|
|
9326
9560
|
import { promisify as promisify5 } from "util";
|
|
9327
9561
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
9328
|
-
import { join as
|
|
9562
|
+
import { join as join11 } from "path";
|
|
9329
9563
|
import { tmpdir } from "os";
|
|
9330
9564
|
import { nanoid as nanoid7 } from "nanoid";
|
|
9331
9565
|
async function checkFfmpeg() {
|
|
@@ -9382,21 +9616,21 @@ var init_recorder = __esm({
|
|
|
9382
9616
|
*/
|
|
9383
9617
|
async encode() {
|
|
9384
9618
|
if (this.frames.length === 0) return null;
|
|
9385
|
-
const workDir =
|
|
9619
|
+
const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
9386
9620
|
await mkdir4(workDir, { recursive: true });
|
|
9387
9621
|
try {
|
|
9388
9622
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9389
|
-
const framePath =
|
|
9623
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
9390
9624
|
await writeFile5(framePath, this.frames[i].data);
|
|
9391
9625
|
}
|
|
9392
9626
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9393
9627
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9394
9628
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9395
|
-
const outputPath =
|
|
9629
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
9396
9630
|
const hasFfmpeg = await checkFfmpeg();
|
|
9397
9631
|
if (hasFfmpeg) {
|
|
9398
9632
|
await execAsync5(
|
|
9399
|
-
`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}"`,
|
|
9400
9634
|
{ timeout: 12e4 }
|
|
9401
9635
|
);
|
|
9402
9636
|
} else {
|
|
@@ -9408,7 +9642,7 @@ var init_recorder = __esm({
|
|
|
9408
9642
|
const files = await readdir5(workDir);
|
|
9409
9643
|
for (const f of files) {
|
|
9410
9644
|
if (f.startsWith("frame_")) {
|
|
9411
|
-
await unlink2(
|
|
9645
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
9412
9646
|
});
|
|
9413
9647
|
}
|
|
9414
9648
|
}
|
|
@@ -9675,7 +9909,8 @@ ${personality.trim()}`;
|
|
|
9675
9909
|
}
|
|
9676
9910
|
const messages = await this.context.getMessages();
|
|
9677
9911
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9678
|
-
const
|
|
9912
|
+
const approvalWrapped = this.wrapToolsWithApproval(options, tools);
|
|
9913
|
+
const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
|
|
9679
9914
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9680
9915
|
const stream = streamText2({
|
|
9681
9916
|
model: resolveModel(this.session.model),
|
|
@@ -9689,6 +9924,17 @@ ${personality.trim()}`;
|
|
|
9689
9924
|
providerOptions: useAnthropic ? {
|
|
9690
9925
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9691
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
|
+
},
|
|
9692
9938
|
onStepFinish: async (step) => {
|
|
9693
9939
|
options.onStepFinish?.(step);
|
|
9694
9940
|
},
|
|
@@ -9724,7 +9970,7 @@ ${personality.trim()}`;
|
|
|
9724
9970
|
});
|
|
9725
9971
|
const messages = await this.context.getMessages();
|
|
9726
9972
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9727
|
-
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
9973
|
+
const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
|
|
9728
9974
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9729
9975
|
const result = await generateText3({
|
|
9730
9976
|
model: resolveModel(this.session.model),
|
|
@@ -9735,7 +9981,13 @@ ${personality.trim()}`;
|
|
|
9735
9981
|
// Enable extended thinking/reasoning for models that support it
|
|
9736
9982
|
providerOptions: useAnthropic ? {
|
|
9737
9983
|
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
9738
|
-
} : 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
|
+
}
|
|
9739
9991
|
});
|
|
9740
9992
|
const responseMessages = result.response.messages;
|
|
9741
9993
|
this.context.addResponseMessages(responseMessages);
|
|
@@ -9912,12 +10164,19 @@ ${p.text}` : p.text;
|
|
|
9912
10164
|
model: resolveModel(this.session.model),
|
|
9913
10165
|
system: systemPrompt,
|
|
9914
10166
|
messages,
|
|
9915
|
-
tools: taskTools,
|
|
10167
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
9916
10168
|
stopWhen: stepCountIs2(500),
|
|
9917
10169
|
abortSignal: combinedAbort,
|
|
9918
10170
|
providerOptions: useAnthropic ? {
|
|
9919
10171
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9920
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
|
+
},
|
|
9921
10180
|
onStepFinish: async (step) => {
|
|
9922
10181
|
options.onStepFinish?.(step);
|
|
9923
10182
|
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
@@ -10117,14 +10376,14 @@ ${p.text}` : p.text;
|
|
|
10117
10376
|
const result = await recorder.encode();
|
|
10118
10377
|
recorder.clear();
|
|
10119
10378
|
if (!result) return [];
|
|
10120
|
-
const { readFile:
|
|
10379
|
+
const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
|
|
10121
10380
|
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
10122
10381
|
this.session.id,
|
|
10123
10382
|
`browser-recording-${Date.now()}.mp4`,
|
|
10124
10383
|
"video/mp4",
|
|
10125
10384
|
"browser-recording"
|
|
10126
10385
|
);
|
|
10127
|
-
const fileData = await
|
|
10386
|
+
const fileData = await readFile13(result.path);
|
|
10128
10387
|
await fetch(uploadInfo.uploadUrl, {
|
|
10129
10388
|
method: "PUT",
|
|
10130
10389
|
headers: { "Content-Type": "video/mp4" },
|
|
@@ -10132,7 +10391,7 @@ ${p.text}` : p.text;
|
|
|
10132
10391
|
});
|
|
10133
10392
|
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
10134
10393
|
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
10135
|
-
await
|
|
10394
|
+
await unlink4(result.path).catch(() => {
|
|
10136
10395
|
});
|
|
10137
10396
|
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
10138
10397
|
return [dlInfo.downloadUrl];
|
|
@@ -10150,13 +10409,13 @@ ${p.text}` : p.text;
|
|
|
10150
10409
|
try {
|
|
10151
10410
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10152
10411
|
if (!isRemoteConfigured2()) return [];
|
|
10153
|
-
const { readFile:
|
|
10154
|
-
const { join:
|
|
10412
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
10413
|
+
const { join: join18, basename: basename7 } = await import("path");
|
|
10155
10414
|
const urls = [];
|
|
10156
10415
|
for (const filePath of filePaths) {
|
|
10157
10416
|
try {
|
|
10158
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10159
|
-
const fileName =
|
|
10417
|
+
const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
|
|
10418
|
+
const fileName = basename7(fullPath);
|
|
10160
10419
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10161
10420
|
const mimeMap = {
|
|
10162
10421
|
pdf: "application/pdf",
|
|
@@ -10180,7 +10439,7 @@ ${p.text}` : p.text;
|
|
|
10180
10439
|
contentType,
|
|
10181
10440
|
"task-output"
|
|
10182
10441
|
);
|
|
10183
|
-
const fileData = await
|
|
10442
|
+
const fileData = await readFile13(fullPath);
|
|
10184
10443
|
await fetch(uploadInfo.uploadUrl, {
|
|
10185
10444
|
method: "PUT",
|
|
10186
10445
|
headers: { "Content-Type": contentType },
|
|
@@ -10229,8 +10488,8 @@ ${p.text}` : p.text;
|
|
|
10229
10488
|
this.pendingApprovals.set(toolCallId, await execution);
|
|
10230
10489
|
options.onApprovalRequired?.(await execution);
|
|
10231
10490
|
await sessionQueries.updateStatus(this.session.id, "waiting");
|
|
10232
|
-
const approved = await new Promise((
|
|
10233
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
10491
|
+
const approved = await new Promise((resolve13) => {
|
|
10492
|
+
approvalResolvers.set(toolCallId, { resolve: resolve13, sessionId: this.session.id });
|
|
10234
10493
|
});
|
|
10235
10494
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
10236
10495
|
approvalResolvers.delete(toolCallId);
|
|
@@ -10336,8 +10595,8 @@ async function withSessionLock(sessionId, fn) {
|
|
|
10336
10595
|
state2.pending++;
|
|
10337
10596
|
const prev = state2.tail;
|
|
10338
10597
|
let release;
|
|
10339
|
-
const next = new Promise((
|
|
10340
|
-
release =
|
|
10598
|
+
const next = new Promise((resolve13) => {
|
|
10599
|
+
release = resolve13;
|
|
10341
10600
|
});
|
|
10342
10601
|
state2.tail = prev.then(() => next);
|
|
10343
10602
|
await prev;
|
|
@@ -10360,19 +10619,19 @@ var init_session_lock = __esm({
|
|
|
10360
10619
|
});
|
|
10361
10620
|
|
|
10362
10621
|
// src/orchestrator/webhook-events.ts
|
|
10363
|
-
import { existsSync as
|
|
10364
|
-
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";
|
|
10365
10624
|
import { nanoid as nanoid9 } from "nanoid";
|
|
10366
10625
|
function logFilePath() {
|
|
10367
|
-
return
|
|
10626
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10368
10627
|
}
|
|
10369
10628
|
function ensureLoaded() {
|
|
10370
10629
|
if (cache !== null) return cache;
|
|
10371
10630
|
cache = [];
|
|
10372
10631
|
try {
|
|
10373
10632
|
const p = logFilePath();
|
|
10374
|
-
if (!
|
|
10375
|
-
const lines =
|
|
10633
|
+
if (!existsSync18(p)) return cache;
|
|
10634
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
10376
10635
|
for (const line of lines) {
|
|
10377
10636
|
try {
|
|
10378
10637
|
cache.push(JSON.parse(line));
|
|
@@ -10382,7 +10641,7 @@ function ensureLoaded() {
|
|
|
10382
10641
|
if (cache.length > MAX_EVENTS) {
|
|
10383
10642
|
cache = cache.slice(-MAX_EVENTS);
|
|
10384
10643
|
try {
|
|
10385
|
-
|
|
10644
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10386
10645
|
} catch {
|
|
10387
10646
|
}
|
|
10388
10647
|
}
|
|
@@ -10396,7 +10655,7 @@ function appendEvent(ev) {
|
|
|
10396
10655
|
if (list.length > MAX_EVENTS) list.shift();
|
|
10397
10656
|
try {
|
|
10398
10657
|
const p = logFilePath();
|
|
10399
|
-
|
|
10658
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10400
10659
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10401
10660
|
} catch {
|
|
10402
10661
|
}
|
|
@@ -10427,8 +10686,8 @@ function updateEvent(id, patch) {
|
|
|
10427
10686
|
list[i] = { ...list[i], ...patch };
|
|
10428
10687
|
try {
|
|
10429
10688
|
const p = logFilePath();
|
|
10430
|
-
|
|
10431
|
-
|
|
10689
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10690
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10432
10691
|
} catch {
|
|
10433
10692
|
}
|
|
10434
10693
|
}
|
|
@@ -10460,7 +10719,7 @@ function listEvents(filter = {}) {
|
|
|
10460
10719
|
function clearAllEvents() {
|
|
10461
10720
|
cache = [];
|
|
10462
10721
|
try {
|
|
10463
|
-
|
|
10722
|
+
writeFileSync4(logFilePath(), "");
|
|
10464
10723
|
} catch {
|
|
10465
10724
|
}
|
|
10466
10725
|
}
|
|
@@ -10727,12 +10986,12 @@ var init_scheduler = __esm({
|
|
|
10727
10986
|
|
|
10728
10987
|
// src/server/index.ts
|
|
10729
10988
|
import "dotenv/config";
|
|
10730
|
-
import { Hono as
|
|
10989
|
+
import { Hono as Hono10 } from "hono";
|
|
10731
10990
|
import { serve } from "@hono/node-server";
|
|
10732
10991
|
import { cors } from "hono/cors";
|
|
10733
10992
|
import { logger } from "hono/logger";
|
|
10734
|
-
import { existsSync as
|
|
10735
|
-
import { resolve 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";
|
|
10736
10995
|
import { spawn as spawn2 } from "child_process";
|
|
10737
10996
|
import { createServer as createNetServer } from "net";
|
|
10738
10997
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -10746,9 +11005,9 @@ init_checkpoints();
|
|
|
10746
11005
|
import { Hono } from "hono";
|
|
10747
11006
|
import { zValidator } from "@hono/zod-validator";
|
|
10748
11007
|
import { z as z16 } from "zod";
|
|
10749
|
-
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";
|
|
10750
11009
|
import { readdir as readdir6 } from "fs/promises";
|
|
10751
|
-
import { join as
|
|
11010
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
10752
11011
|
import { nanoid as nanoid10 } from "nanoid";
|
|
10753
11012
|
|
|
10754
11013
|
// src/tasks/agent-status.ts
|
|
@@ -11386,12 +11645,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11386
11645
|
});
|
|
11387
11646
|
function getAttachmentsDir(sessionId) {
|
|
11388
11647
|
const appDataDir = getAppDataDirectory();
|
|
11389
|
-
return
|
|
11648
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
11390
11649
|
}
|
|
11391
11650
|
function ensureAttachmentsDir(sessionId) {
|
|
11392
11651
|
const dir = getAttachmentsDir(sessionId);
|
|
11393
|
-
if (!
|
|
11394
|
-
|
|
11652
|
+
if (!existsSync19(dir)) {
|
|
11653
|
+
mkdirSync8(dir, { recursive: true });
|
|
11395
11654
|
}
|
|
11396
11655
|
return dir;
|
|
11397
11656
|
}
|
|
@@ -11402,12 +11661,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11402
11661
|
return c.json({ error: "Session not found" }, 404);
|
|
11403
11662
|
}
|
|
11404
11663
|
const dir = getAttachmentsDir(sessionId);
|
|
11405
|
-
if (!
|
|
11664
|
+
if (!existsSync19(dir)) {
|
|
11406
11665
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
11407
11666
|
}
|
|
11408
11667
|
const files = readdirSync3(dir);
|
|
11409
11668
|
const attachments = files.map((filename) => {
|
|
11410
|
-
const filePath =
|
|
11669
|
+
const filePath = join13(dir, filename);
|
|
11411
11670
|
const stats = statSync2(filePath);
|
|
11412
11671
|
return {
|
|
11413
11672
|
id: filename.split("_")[0],
|
|
@@ -11442,9 +11701,9 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11442
11701
|
const id = nanoid10(10);
|
|
11443
11702
|
const ext = extname8(file.name) || "";
|
|
11444
11703
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11445
|
-
const filePath =
|
|
11704
|
+
const filePath = join13(dir, safeFilename);
|
|
11446
11705
|
const arrayBuffer = await file.arrayBuffer();
|
|
11447
|
-
|
|
11706
|
+
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
11448
11707
|
return c.json({
|
|
11449
11708
|
id,
|
|
11450
11709
|
filename: file.name,
|
|
@@ -11468,13 +11727,13 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11468
11727
|
const id = nanoid10(10);
|
|
11469
11728
|
const ext = extname8(body.filename) || "";
|
|
11470
11729
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11471
|
-
const filePath =
|
|
11730
|
+
const filePath = join13(dir, safeFilename);
|
|
11472
11731
|
let base64Data = body.data;
|
|
11473
11732
|
if (base64Data.includes(",")) {
|
|
11474
11733
|
base64Data = base64Data.split(",")[1];
|
|
11475
11734
|
}
|
|
11476
11735
|
const buffer = Buffer.from(base64Data, "base64");
|
|
11477
|
-
|
|
11736
|
+
writeFileSync5(filePath, buffer);
|
|
11478
11737
|
return c.json({
|
|
11479
11738
|
id,
|
|
11480
11739
|
filename: body.filename,
|
|
@@ -11497,7 +11756,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11497
11756
|
return c.json({ error: "Session not found" }, 404);
|
|
11498
11757
|
}
|
|
11499
11758
|
const dir = getAttachmentsDir(sessionId);
|
|
11500
|
-
if (!
|
|
11759
|
+
if (!existsSync19(dir)) {
|
|
11501
11760
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11502
11761
|
}
|
|
11503
11762
|
const files = readdirSync3(dir);
|
|
@@ -11505,7 +11764,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11505
11764
|
if (!file) {
|
|
11506
11765
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11507
11766
|
}
|
|
11508
|
-
const filePath =
|
|
11767
|
+
const filePath = join13(dir, file);
|
|
11509
11768
|
unlinkSync2(filePath);
|
|
11510
11769
|
return c.json({ success: true, id: attachmentId });
|
|
11511
11770
|
});
|
|
@@ -11588,7 +11847,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
11588
11847
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
11589
11848
|
for (const entry2 of entries) {
|
|
11590
11849
|
if (results.length >= limit * 2) break;
|
|
11591
|
-
const fullPath =
|
|
11850
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
11592
11851
|
const relativePath = relative9(baseDir, fullPath);
|
|
11593
11852
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
11594
11853
|
continue;
|
|
@@ -11636,7 +11895,7 @@ sessions2.get(
|
|
|
11636
11895
|
return c.json({ error: "Session not found" }, 404);
|
|
11637
11896
|
}
|
|
11638
11897
|
const workingDirectory = session.workingDirectory;
|
|
11639
|
-
if (!
|
|
11898
|
+
if (!existsSync19(workingDirectory)) {
|
|
11640
11899
|
return c.json({
|
|
11641
11900
|
sessionId,
|
|
11642
11901
|
workingDirectory,
|
|
@@ -11744,14 +12003,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
|
|
|
11744
12003
|
|
|
11745
12004
|
// src/server/routes/agents.ts
|
|
11746
12005
|
init_db();
|
|
11747
|
-
init_agent();
|
|
11748
|
-
init_session_lock();
|
|
11749
|
-
init_config();
|
|
11750
12006
|
import { Hono as Hono2 } from "hono";
|
|
11751
12007
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
11752
12008
|
import { z as z17 } from "zod";
|
|
11753
|
-
import { existsSync as
|
|
11754
|
-
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();
|
|
11755
12101
|
|
|
11756
12102
|
// src/server/resumable-stream.ts
|
|
11757
12103
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -11907,7 +12253,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
|
|
|
11907
12253
|
toolCallId,
|
|
11908
12254
|
argsTextDelta: chunk
|
|
11909
12255
|
}));
|
|
11910
|
-
await new Promise((
|
|
12256
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
11911
12257
|
}
|
|
11912
12258
|
}
|
|
11913
12259
|
function buildDevtoolsContextXml(sessionId) {
|
|
@@ -11958,12 +12304,12 @@ var rejectSchema = z17.object({
|
|
|
11958
12304
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
11959
12305
|
function getAttachmentsDirectory(sessionId) {
|
|
11960
12306
|
const appDataDir = getAppDataDirectory();
|
|
11961
|
-
return
|
|
12307
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
11962
12308
|
}
|
|
11963
12309
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
11964
12310
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
11965
|
-
if (!
|
|
11966
|
-
|
|
12311
|
+
if (!existsSync20(attachmentsDir)) {
|
|
12312
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
11967
12313
|
}
|
|
11968
12314
|
let filename = attachment.filename;
|
|
11969
12315
|
if (!filename) {
|
|
@@ -11981,8 +12327,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
11981
12327
|
attachment.mediaType = resized.mediaType;
|
|
11982
12328
|
attachment.data = buffer.toString("base64");
|
|
11983
12329
|
}
|
|
11984
|
-
const filePath =
|
|
11985
|
-
|
|
12330
|
+
const filePath = join14(attachmentsDir, filename);
|
|
12331
|
+
writeFileSync6(filePath, buffer);
|
|
11986
12332
|
return filePath;
|
|
11987
12333
|
}
|
|
11988
12334
|
function stripDataUrlPrefix2(data) {
|
|
@@ -12162,7 +12508,7 @@ ${prompt}` });
|
|
|
12162
12508
|
chunkIndex,
|
|
12163
12509
|
chunkCount
|
|
12164
12510
|
}));
|
|
12165
|
-
await new Promise((
|
|
12511
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
12166
12512
|
}
|
|
12167
12513
|
const browserPort = progress.data?.browserStreamPort;
|
|
12168
12514
|
const browserClosed = progress.data?.browserClosed;
|
|
@@ -12319,7 +12665,20 @@ ${prompt}` });
|
|
|
12319
12665
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12320
12666
|
} else {
|
|
12321
12667
|
console.error("Agent error:", error);
|
|
12322
|
-
|
|
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
|
+
}));
|
|
12323
12682
|
try {
|
|
12324
12683
|
await activeStreamQueries.markError(streamId);
|
|
12325
12684
|
} catch {
|
|
@@ -12709,7 +13068,7 @@ agents.post(
|
|
|
12709
13068
|
chunkIndex,
|
|
12710
13069
|
chunkCount
|
|
12711
13070
|
}));
|
|
12712
|
-
await new Promise((
|
|
13071
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
12713
13072
|
}
|
|
12714
13073
|
const browserPort = progress.data?.browserStreamPort;
|
|
12715
13074
|
const browserClosed = progress.data?.browserClosed;
|
|
@@ -12859,7 +13218,20 @@ agents.post(
|
|
|
12859
13218
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12860
13219
|
} else {
|
|
12861
13220
|
console.error("Agent error:", error);
|
|
12862
|
-
|
|
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
|
+
}));
|
|
12863
13235
|
await activeStreamQueries.markError(streamId);
|
|
12864
13236
|
}
|
|
12865
13237
|
} finally {
|
|
@@ -12942,26 +13314,26 @@ init_config();
|
|
|
12942
13314
|
import { Hono as Hono3 } from "hono";
|
|
12943
13315
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
12944
13316
|
import { z as z18 } from "zod";
|
|
12945
|
-
import { readFileSync as
|
|
13317
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
12946
13318
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12947
|
-
import { dirname as
|
|
13319
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
12948
13320
|
var __filename = fileURLToPath3(import.meta.url);
|
|
12949
|
-
var __dirname =
|
|
13321
|
+
var __dirname = dirname8(__filename);
|
|
12950
13322
|
var possiblePaths = [
|
|
12951
|
-
|
|
13323
|
+
join15(__dirname, "../package.json"),
|
|
12952
13324
|
// From dist/server -> dist/../package.json
|
|
12953
|
-
|
|
13325
|
+
join15(__dirname, "../../package.json"),
|
|
12954
13326
|
// From dist/server (if nested differently)
|
|
12955
|
-
|
|
13327
|
+
join15(__dirname, "../../../package.json"),
|
|
12956
13328
|
// From src/server/routes (development)
|
|
12957
|
-
|
|
13329
|
+
join15(process.cwd(), "package.json")
|
|
12958
13330
|
// From current working directory
|
|
12959
13331
|
];
|
|
12960
13332
|
var currentVersion = "0.0.0";
|
|
12961
13333
|
var packageName = "sparkecoder";
|
|
12962
13334
|
for (const packageJsonPath of possiblePaths) {
|
|
12963
13335
|
try {
|
|
12964
|
-
const packageJson = JSON.parse(
|
|
13336
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
12965
13337
|
if (packageJson.name === "sparkecoder") {
|
|
12966
13338
|
currentVersion = packageJson.version || "0.0.0";
|
|
12967
13339
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -13779,12 +14151,13 @@ slack.post("/events", async (c) => {
|
|
|
13779
14151
|
if (inbound) {
|
|
13780
14152
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
13781
14153
|
if (isThreadReply) {
|
|
13782
|
-
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);
|
|
13783
14155
|
if (!ours) {
|
|
13784
14156
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
13785
14157
|
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
13786
14158
|
return c.json({ ok: true });
|
|
13787
14159
|
}
|
|
14160
|
+
markThreadOwned(ev.channel, ev.thread_ts);
|
|
13788
14161
|
}
|
|
13789
14162
|
if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
|
|
13790
14163
|
markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
|
|
@@ -13823,18 +14196,6 @@ slack.post("/events", async (c) => {
|
|
|
13823
14196
|
}
|
|
13824
14197
|
return c.json({ ok: true });
|
|
13825
14198
|
});
|
|
13826
|
-
async function threadBelongsToUs(channel, threadTs) {
|
|
13827
|
-
try {
|
|
13828
|
-
const sessions3 = await sessionQueries.list(500, 0);
|
|
13829
|
-
return sessions3.some((s) => {
|
|
13830
|
-
const slack2 = s.config?.slack;
|
|
13831
|
-
return slack2?.channel === channel && slack2?.threadTs === threadTs;
|
|
13832
|
-
});
|
|
13833
|
-
} catch (err) {
|
|
13834
|
-
console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
|
|
13835
|
-
return false;
|
|
13836
|
-
}
|
|
13837
|
-
}
|
|
13838
14199
|
async function findOrCreateOrchestratorId() {
|
|
13839
14200
|
try {
|
|
13840
14201
|
const all = await sessionQueries.list(500, 0);
|
|
@@ -14217,6 +14578,224 @@ mcpRouter.post("/:id/test", async (c) => {
|
|
|
14217
14578
|
return c.json(result);
|
|
14218
14579
|
});
|
|
14219
14580
|
|
|
14581
|
+
// src/server/routes/skills.ts
|
|
14582
|
+
init_config();
|
|
14583
|
+
init_skills();
|
|
14584
|
+
import { Hono as Hono9 } from "hono";
|
|
14585
|
+
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
14586
|
+
import { z as z22 } from "zod";
|
|
14587
|
+
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
14588
|
+
import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
14589
|
+
import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
14590
|
+
var skills = new Hono9();
|
|
14591
|
+
function encodeId(filePath) {
|
|
14592
|
+
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
14593
|
+
}
|
|
14594
|
+
function decodeId(id) {
|
|
14595
|
+
try {
|
|
14596
|
+
const decoded = Buffer.from(id, "base64url").toString("utf-8");
|
|
14597
|
+
return decoded || null;
|
|
14598
|
+
} catch {
|
|
14599
|
+
return null;
|
|
14600
|
+
}
|
|
14601
|
+
}
|
|
14602
|
+
function listAllDirectories() {
|
|
14603
|
+
const cfg = getConfig();
|
|
14604
|
+
const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
|
|
14605
|
+
const out = [];
|
|
14606
|
+
for (const { path, priority } of discovered.alwaysLoadedDirs) {
|
|
14607
|
+
out.push({
|
|
14608
|
+
path,
|
|
14609
|
+
source: pathToSource(path),
|
|
14610
|
+
label: pathToLabel(path),
|
|
14611
|
+
priority,
|
|
14612
|
+
alwaysApply: true
|
|
14613
|
+
});
|
|
14614
|
+
}
|
|
14615
|
+
for (const { path, priority } of discovered.onDemandDirs) {
|
|
14616
|
+
out.push({
|
|
14617
|
+
path,
|
|
14618
|
+
source: pathToSource(path),
|
|
14619
|
+
label: pathToLabel(path),
|
|
14620
|
+
priority,
|
|
14621
|
+
alwaysApply: false
|
|
14622
|
+
});
|
|
14623
|
+
}
|
|
14624
|
+
const additional = cfg.skills?.additionalDirectories || [];
|
|
14625
|
+
for (const dir of additional) {
|
|
14626
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
|
|
14627
|
+
if (out.some((d) => d.path === abs)) continue;
|
|
14628
|
+
out.push({
|
|
14629
|
+
path: abs,
|
|
14630
|
+
source: "additional",
|
|
14631
|
+
label: pathToLabel(abs),
|
|
14632
|
+
priority: 50,
|
|
14633
|
+
alwaysApply: false
|
|
14634
|
+
});
|
|
14635
|
+
}
|
|
14636
|
+
return out;
|
|
14637
|
+
}
|
|
14638
|
+
function pathToSource(path) {
|
|
14639
|
+
if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
|
|
14640
|
+
return "project";
|
|
14641
|
+
}
|
|
14642
|
+
function pathToLabel(path) {
|
|
14643
|
+
if (path.includes("/skills/default")) return "Built-in (default)";
|
|
14644
|
+
if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
|
|
14645
|
+
if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
|
|
14646
|
+
if (path.includes("/.cursor/rules")) return ".cursor/rules";
|
|
14647
|
+
if (path.includes("/.claude/skills")) return ".claude/skills";
|
|
14648
|
+
return basename6(dirname9(path)) + "/" + basename6(path);
|
|
14649
|
+
}
|
|
14650
|
+
skills.get("/", async (c) => {
|
|
14651
|
+
const dirs = listAllDirectories();
|
|
14652
|
+
const allSkills = [];
|
|
14653
|
+
for (const dir of dirs) {
|
|
14654
|
+
if (!existsSync21(dir.path)) continue;
|
|
14655
|
+
try {
|
|
14656
|
+
const list = await loadSkillsFromDirectory(dir.path, {
|
|
14657
|
+
priority: dir.priority,
|
|
14658
|
+
defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
|
|
14659
|
+
forceAlwaysApply: dir.alwaysApply
|
|
14660
|
+
});
|
|
14661
|
+
for (const s of list) {
|
|
14662
|
+
let size = 0;
|
|
14663
|
+
try {
|
|
14664
|
+
size = statSync3(s.filePath).size;
|
|
14665
|
+
} catch {
|
|
14666
|
+
}
|
|
14667
|
+
allSkills.push({
|
|
14668
|
+
id: encodeId(s.filePath),
|
|
14669
|
+
name: s.name,
|
|
14670
|
+
description: s.description,
|
|
14671
|
+
filePath: s.filePath,
|
|
14672
|
+
fileName: basename6(s.filePath),
|
|
14673
|
+
sourceDir: dir.path,
|
|
14674
|
+
sourceLabel: dir.label,
|
|
14675
|
+
sourceType: dir.source,
|
|
14676
|
+
alwaysApply: s.alwaysApply,
|
|
14677
|
+
globs: s.globs,
|
|
14678
|
+
sizeBytes: size
|
|
14679
|
+
});
|
|
14680
|
+
}
|
|
14681
|
+
} catch (err) {
|
|
14682
|
+
console.warn("[skills] failed to read", dir.path, err?.message || err);
|
|
14683
|
+
}
|
|
14684
|
+
}
|
|
14685
|
+
return c.json({
|
|
14686
|
+
directories: dirs.map((d) => ({
|
|
14687
|
+
path: d.path,
|
|
14688
|
+
label: d.label,
|
|
14689
|
+
source: d.source,
|
|
14690
|
+
alwaysApply: d.alwaysApply,
|
|
14691
|
+
exists: existsSync21(d.path),
|
|
14692
|
+
writable: isWritable(d.path)
|
|
14693
|
+
})),
|
|
14694
|
+
skills: allSkills
|
|
14695
|
+
});
|
|
14696
|
+
});
|
|
14697
|
+
function isWritable(dir) {
|
|
14698
|
+
try {
|
|
14699
|
+
if (!existsSync21(dir)) return false;
|
|
14700
|
+
if (dir.includes("/skills/default")) return false;
|
|
14701
|
+
return true;
|
|
14702
|
+
} catch {
|
|
14703
|
+
return false;
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14706
|
+
skills.get("/:id", async (c) => {
|
|
14707
|
+
const filePath = decodeId(c.req.param("id"));
|
|
14708
|
+
if (!filePath || !existsSync21(filePath)) {
|
|
14709
|
+
return c.json({ error: "skill not found" }, 404);
|
|
14710
|
+
}
|
|
14711
|
+
const content = await readFile12(filePath, "utf-8");
|
|
14712
|
+
return c.json({
|
|
14713
|
+
id: c.req.param("id"),
|
|
14714
|
+
filePath,
|
|
14715
|
+
fileName: basename6(filePath),
|
|
14716
|
+
content,
|
|
14717
|
+
writable: !filePath.includes("/skills/default")
|
|
14718
|
+
});
|
|
14719
|
+
});
|
|
14720
|
+
skills.post(
|
|
14721
|
+
"/",
|
|
14722
|
+
zValidator7("json", z22.object({
|
|
14723
|
+
directory: z22.string().min(1),
|
|
14724
|
+
fileName: z22.string().min(1),
|
|
14725
|
+
content: z22.string()
|
|
14726
|
+
})),
|
|
14727
|
+
async (c) => {
|
|
14728
|
+
const { directory, fileName, content } = c.req.valid("json");
|
|
14729
|
+
const cfg = getConfig();
|
|
14730
|
+
const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
|
|
14731
|
+
if (targetDir.includes("/skills/default")) {
|
|
14732
|
+
return c.json({ error: "cannot write into built-in skills directory" }, 400);
|
|
14733
|
+
}
|
|
14734
|
+
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
14735
|
+
const ext = extname9(safeName).toLowerCase();
|
|
14736
|
+
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
14737
|
+
const filePath = join16(targetDir, finalName);
|
|
14738
|
+
if (existsSync21(filePath)) {
|
|
14739
|
+
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
14740
|
+
}
|
|
14741
|
+
try {
|
|
14742
|
+
await mkdir5(targetDir, { recursive: true });
|
|
14743
|
+
await writeFile6(filePath, content, "utf-8");
|
|
14744
|
+
} catch (err) {
|
|
14745
|
+
return c.json({ error: err?.message || "write failed" }, 500);
|
|
14746
|
+
}
|
|
14747
|
+
return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
|
|
14748
|
+
}
|
|
14749
|
+
);
|
|
14750
|
+
skills.put(
|
|
14751
|
+
"/:id",
|
|
14752
|
+
zValidator7("json", z22.object({ content: z22.string() })),
|
|
14753
|
+
async (c) => {
|
|
14754
|
+
const filePath = decodeId(c.req.param("id"));
|
|
14755
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14756
|
+
if (filePath.includes("/skills/default")) {
|
|
14757
|
+
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14758
|
+
}
|
|
14759
|
+
await writeFile6(filePath, c.req.valid("json").content, "utf-8");
|
|
14760
|
+
return c.json({ ok: true });
|
|
14761
|
+
}
|
|
14762
|
+
);
|
|
14763
|
+
skills.delete("/:id", async (c) => {
|
|
14764
|
+
const filePath = decodeId(c.req.param("id"));
|
|
14765
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14766
|
+
if (filePath.includes("/skills/default")) {
|
|
14767
|
+
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14768
|
+
}
|
|
14769
|
+
await unlink3(filePath);
|
|
14770
|
+
return c.json({ ok: true });
|
|
14771
|
+
});
|
|
14772
|
+
skills.post(
|
|
14773
|
+
"/directories",
|
|
14774
|
+
zValidator7("json", z22.object({ path: z22.string().min(1) })),
|
|
14775
|
+
(c) => {
|
|
14776
|
+
const cfg = getConfig();
|
|
14777
|
+
const inputPath = c.req.valid("json").path.trim();
|
|
14778
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
|
|
14779
|
+
const current = cfg.skills?.additionalDirectories || [];
|
|
14780
|
+
if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
|
|
14781
|
+
return c.json({ error: "directory already added" }, 409);
|
|
14782
|
+
}
|
|
14783
|
+
const next = [...current, abs];
|
|
14784
|
+
setSkillsAdditionalDirectories(next);
|
|
14785
|
+
return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
|
|
14786
|
+
}
|
|
14787
|
+
);
|
|
14788
|
+
skills.delete("/directories", (c) => {
|
|
14789
|
+
const inputPath = c.req.query("path");
|
|
14790
|
+
if (!inputPath) return c.json({ error: "path query param required" }, 400);
|
|
14791
|
+
const cfg = getConfig();
|
|
14792
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
|
|
14793
|
+
const current = cfg.skills?.additionalDirectories || [];
|
|
14794
|
+
const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
|
|
14795
|
+
setSkillsAdditionalDirectories(next);
|
|
14796
|
+
return c.json({ ok: true });
|
|
14797
|
+
});
|
|
14798
|
+
|
|
14220
14799
|
// src/server/auth/cf-access.ts
|
|
14221
14800
|
init_config();
|
|
14222
14801
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
@@ -14377,13 +14956,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
14377
14956
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
14378
14957
|
function getWebDirectory() {
|
|
14379
14958
|
try {
|
|
14380
|
-
const currentDir =
|
|
14381
|
-
const webDir =
|
|
14382
|
-
if (
|
|
14959
|
+
const currentDir = dirname10(fileURLToPath4(import.meta.url));
|
|
14960
|
+
const webDir = resolve12(currentDir, "..", "web");
|
|
14961
|
+
if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
|
|
14383
14962
|
return webDir;
|
|
14384
14963
|
}
|
|
14385
|
-
const altWebDir =
|
|
14386
|
-
if (
|
|
14964
|
+
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
14965
|
+
if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
|
|
14387
14966
|
return altWebDir;
|
|
14388
14967
|
}
|
|
14389
14968
|
return null;
|
|
@@ -14406,18 +14985,18 @@ async function isSparkcoderWebRunning(port) {
|
|
|
14406
14985
|
}
|
|
14407
14986
|
}
|
|
14408
14987
|
function isPortInUse(port) {
|
|
14409
|
-
return new Promise((
|
|
14988
|
+
return new Promise((resolve13) => {
|
|
14410
14989
|
const server = createNetServer();
|
|
14411
14990
|
server.once("error", (err) => {
|
|
14412
14991
|
if (err.code === "EADDRINUSE") {
|
|
14413
|
-
|
|
14992
|
+
resolve13(true);
|
|
14414
14993
|
} else {
|
|
14415
|
-
|
|
14994
|
+
resolve13(false);
|
|
14416
14995
|
}
|
|
14417
14996
|
});
|
|
14418
14997
|
server.once("listening", () => {
|
|
14419
14998
|
server.close();
|
|
14420
|
-
|
|
14999
|
+
resolve13(false);
|
|
14421
15000
|
});
|
|
14422
15001
|
server.listen(port, "0.0.0.0");
|
|
14423
15002
|
});
|
|
@@ -14441,30 +15020,30 @@ async function findWebPort(preferredPort) {
|
|
|
14441
15020
|
return { port: preferredPort, alreadyRunning: false };
|
|
14442
15021
|
}
|
|
14443
15022
|
function hasProductionBuild(webDir) {
|
|
14444
|
-
const buildIdPath =
|
|
14445
|
-
return
|
|
15023
|
+
const buildIdPath = join17(webDir, ".next", "BUILD_ID");
|
|
15024
|
+
return existsSync22(buildIdPath);
|
|
14446
15025
|
}
|
|
14447
15026
|
function hasSourceFiles(webDir) {
|
|
14448
|
-
const appDir =
|
|
14449
|
-
const pagesDir =
|
|
14450
|
-
const rootAppDir =
|
|
14451
|
-
const rootPagesDir =
|
|
14452
|
-
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);
|
|
14453
15032
|
}
|
|
14454
15033
|
function getStandaloneServerPath(webDir) {
|
|
14455
15034
|
const possiblePaths2 = [
|
|
14456
|
-
|
|
14457
|
-
|
|
15035
|
+
join17(webDir, ".next", "standalone", "server.js"),
|
|
15036
|
+
join17(webDir, ".next", "standalone", "web", "server.js")
|
|
14458
15037
|
];
|
|
14459
15038
|
for (const serverPath of possiblePaths2) {
|
|
14460
|
-
if (
|
|
15039
|
+
if (existsSync22(serverPath)) {
|
|
14461
15040
|
return serverPath;
|
|
14462
15041
|
}
|
|
14463
15042
|
}
|
|
14464
15043
|
return null;
|
|
14465
15044
|
}
|
|
14466
15045
|
function runCommand(command, args, cwd, env) {
|
|
14467
|
-
return new Promise((
|
|
15046
|
+
return new Promise((resolve13) => {
|
|
14468
15047
|
const child = spawn2(command, args, {
|
|
14469
15048
|
cwd,
|
|
14470
15049
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -14479,10 +15058,10 @@ function runCommand(command, args, cwd, env) {
|
|
|
14479
15058
|
output += data.toString();
|
|
14480
15059
|
});
|
|
14481
15060
|
child.on("close", (code) => {
|
|
14482
|
-
|
|
15061
|
+
resolve13({ success: code === 0, output });
|
|
14483
15062
|
});
|
|
14484
15063
|
child.on("error", (err) => {
|
|
14485
|
-
|
|
15064
|
+
resolve13({ success: false, output: err.message });
|
|
14486
15065
|
});
|
|
14487
15066
|
});
|
|
14488
15067
|
}
|
|
@@ -14497,15 +15076,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14497
15076
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
14498
15077
|
return { process: null, port: actualPort };
|
|
14499
15078
|
}
|
|
14500
|
-
const usePnpm =
|
|
14501
|
-
const useNpm = !usePnpm &&
|
|
15079
|
+
const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
|
|
15080
|
+
const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
|
|
14502
15081
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
14503
15082
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
14504
15083
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
14505
15084
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
14506
|
-
const runtimeConfigPath =
|
|
15085
|
+
const runtimeConfigPath = join17(webDir, "runtime-config.json");
|
|
14507
15086
|
try {
|
|
14508
|
-
|
|
15087
|
+
writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
14509
15088
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
14510
15089
|
} catch (err) {
|
|
14511
15090
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -14525,7 +15104,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14525
15104
|
if (standaloneServerPath) {
|
|
14526
15105
|
command = "node";
|
|
14527
15106
|
args = ["server.js"];
|
|
14528
|
-
cwd =
|
|
15107
|
+
cwd = dirname10(standaloneServerPath);
|
|
14529
15108
|
webEnv.PORT = String(actualPort);
|
|
14530
15109
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
14531
15110
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -14566,10 +15145,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14566
15145
|
let started = false;
|
|
14567
15146
|
let exited = false;
|
|
14568
15147
|
let exitCode = null;
|
|
14569
|
-
const startedPromise = new Promise((
|
|
15148
|
+
const startedPromise = new Promise((resolve13) => {
|
|
14570
15149
|
const timeout = setTimeout(() => {
|
|
14571
15150
|
if (!started && !exited) {
|
|
14572
|
-
|
|
15151
|
+
resolve13(false);
|
|
14573
15152
|
}
|
|
14574
15153
|
}, startupTimeout);
|
|
14575
15154
|
child.stdout?.on("data", (data) => {
|
|
@@ -14583,7 +15162,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14583
15162
|
if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
14584
15163
|
started = true;
|
|
14585
15164
|
clearTimeout(timeout);
|
|
14586
|
-
|
|
15165
|
+
resolve13(true);
|
|
14587
15166
|
}
|
|
14588
15167
|
});
|
|
14589
15168
|
child.stderr?.on("data", (data) => {
|
|
@@ -14595,14 +15174,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14595
15174
|
child.on("error", (err) => {
|
|
14596
15175
|
if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
|
|
14597
15176
|
clearTimeout(timeout);
|
|
14598
|
-
|
|
15177
|
+
resolve13(false);
|
|
14599
15178
|
});
|
|
14600
15179
|
child.on("exit", (code) => {
|
|
14601
15180
|
exited = true;
|
|
14602
15181
|
exitCode = code;
|
|
14603
15182
|
if (!started) {
|
|
14604
15183
|
clearTimeout(timeout);
|
|
14605
|
-
|
|
15184
|
+
resolve13(false);
|
|
14606
15185
|
}
|
|
14607
15186
|
webUIProcess = null;
|
|
14608
15187
|
});
|
|
@@ -14625,7 +15204,7 @@ function stopWebUI() {
|
|
|
14625
15204
|
}
|
|
14626
15205
|
}
|
|
14627
15206
|
async function createApp(options = {}) {
|
|
14628
|
-
const app = new
|
|
15207
|
+
const app = new Hono10();
|
|
14629
15208
|
app.use("*", cors({
|
|
14630
15209
|
origin: "*",
|
|
14631
15210
|
// Allow all origins
|
|
@@ -14653,6 +15232,7 @@ async function createApp(options = {}) {
|
|
|
14653
15232
|
app.route("/api/schedules", schedulesRouter);
|
|
14654
15233
|
app.route("/api/orchestrator", orchestratorRouter);
|
|
14655
15234
|
app.route("/api/mcp", mcpRouter);
|
|
15235
|
+
app.route("/api/skills", skills);
|
|
14656
15236
|
app.route("/api/webhooks", webhooksRouter);
|
|
14657
15237
|
const config = getConfig();
|
|
14658
15238
|
const webhookToken = config?.webhooks?.token;
|
|
@@ -14718,8 +15298,8 @@ async function startServer(options = {}) {
|
|
|
14718
15298
|
if (options.workingDirectory) {
|
|
14719
15299
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
14720
15300
|
}
|
|
14721
|
-
if (!
|
|
14722
|
-
|
|
15301
|
+
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
15302
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
14723
15303
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
14724
15304
|
}
|
|
14725
15305
|
if (!config.resolvedRemoteServer.url) {
|