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/cli.js
CHANGED
|
@@ -964,6 +964,7 @@ __export(config_exports, {
|
|
|
964
964
|
setApiKey: () => setApiKey,
|
|
965
965
|
setMcpServers: () => setMcpServers,
|
|
966
966
|
setPublicUrl: () => setPublicUrl,
|
|
967
|
+
setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
|
|
967
968
|
setSlackConfig: () => setSlackConfig,
|
|
968
969
|
setWebhookToken: () => setWebhookToken
|
|
969
970
|
});
|
|
@@ -1253,6 +1254,40 @@ function setWebhookToken(token) {
|
|
|
1253
1254
|
console.warn("[config] failed to persist webhook token:", err?.message || err);
|
|
1254
1255
|
}
|
|
1255
1256
|
}
|
|
1257
|
+
function setSkillsAdditionalDirectories(directories) {
|
|
1258
|
+
if (cachedConfig) {
|
|
1259
|
+
const cur = cachedConfig.skills || {};
|
|
1260
|
+
cachedConfig.skills = { ...cur, additionalDirectories: directories };
|
|
1261
|
+
try {
|
|
1262
|
+
const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
|
|
1263
|
+
const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
|
|
1264
|
+
cachedConfig.discoveredSkills = discovered;
|
|
1265
|
+
cachedConfig.resolvedSkillsDirectories = [
|
|
1266
|
+
...discovered.allDirectories,
|
|
1267
|
+
...additionalAbs
|
|
1268
|
+
];
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
|
|
1274
|
+
const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
|
|
1275
|
+
let raw = {};
|
|
1276
|
+
if (existsSync(target)) {
|
|
1277
|
+
try {
|
|
1278
|
+
raw = JSON.parse(readFileSync(target, "utf-8"));
|
|
1279
|
+
} catch {
|
|
1280
|
+
raw = {};
|
|
1281
|
+
}
|
|
1282
|
+
} else {
|
|
1283
|
+
raw = createDefaultConfig();
|
|
1284
|
+
}
|
|
1285
|
+
raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
|
|
1286
|
+
writeFileSync(target, JSON.stringify(raw, null, 2));
|
|
1287
|
+
} catch (err) {
|
|
1288
|
+
console.warn("[config] failed to persist skill directories:", err?.message || err);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1256
1291
|
function setPublicUrl(publicUrl) {
|
|
1257
1292
|
if (cachedConfig) {
|
|
1258
1293
|
cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
|
|
@@ -2295,10 +2330,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2295
2330
|
const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
|
|
2296
2331
|
const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
|
|
2297
2332
|
const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
|
|
2298
|
-
const
|
|
2299
|
-
if (existsSync3(
|
|
2333
|
+
const cachePath2 = join3(cacheDir, key2 + ext);
|
|
2334
|
+
if (existsSync3(cachePath2)) {
|
|
2300
2335
|
console.log(`[image-resize] Cache hit for ${width}x${height} image`);
|
|
2301
|
-
return { buffer: readFileSync2(
|
|
2336
|
+
return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
|
|
2302
2337
|
}
|
|
2303
2338
|
let pipeline = sharp(buffer);
|
|
2304
2339
|
if (needsResize) {
|
|
@@ -2323,7 +2358,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2323
2358
|
}
|
|
2324
2359
|
finalMediaType = "image/jpeg";
|
|
2325
2360
|
}
|
|
2326
|
-
writeFileSync2(
|
|
2361
|
+
writeFileSync2(cachePath2, result);
|
|
2327
2362
|
const resultMeta = await sharp(result).metadata();
|
|
2328
2363
|
console.log(
|
|
2329
2364
|
`[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
|
|
@@ -3014,7 +3049,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3014
3049
|
},
|
|
3015
3050
|
async waitForDiagnostics(filePath, timeoutMs = 5e3) {
|
|
3016
3051
|
const normalized = normalizePath(filePath);
|
|
3017
|
-
return new Promise((
|
|
3052
|
+
return new Promise((resolve14) => {
|
|
3018
3053
|
const startTime = Date.now();
|
|
3019
3054
|
let debounceTimer;
|
|
3020
3055
|
let resolved = false;
|
|
@@ -3033,7 +3068,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3033
3068
|
if (resolved) return;
|
|
3034
3069
|
resolved = true;
|
|
3035
3070
|
cleanup2();
|
|
3036
|
-
|
|
3071
|
+
resolve14(diagnostics.get(normalized) || []);
|
|
3037
3072
|
};
|
|
3038
3073
|
const onDiagnostic = () => {
|
|
3039
3074
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -3408,7 +3443,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3408
3443
|
isChunked: true
|
|
3409
3444
|
});
|
|
3410
3445
|
if (chunkCount > 1) {
|
|
3411
|
-
await new Promise((
|
|
3446
|
+
await new Promise((resolve14) => setTimeout(resolve14, 0));
|
|
3412
3447
|
}
|
|
3413
3448
|
}
|
|
3414
3449
|
}
|
|
@@ -4027,7 +4062,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4027
4062
|
if (!existsSync10(directory)) {
|
|
4028
4063
|
return [];
|
|
4029
4064
|
}
|
|
4030
|
-
const
|
|
4065
|
+
const skills2 = [];
|
|
4031
4066
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
4032
4067
|
for (const entry2 of entries) {
|
|
4033
4068
|
let filePath;
|
|
@@ -4051,7 +4086,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4051
4086
|
if (parsed) {
|
|
4052
4087
|
const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
|
|
4053
4088
|
const loadType = alwaysApply ? "always" : defaultLoadType;
|
|
4054
|
-
|
|
4089
|
+
skills2.push({
|
|
4055
4090
|
name: parsed.metadata.name,
|
|
4056
4091
|
description: parsed.metadata.description,
|
|
4057
4092
|
filePath,
|
|
@@ -4065,7 +4100,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4065
4100
|
} else {
|
|
4066
4101
|
const name = getSkillNameFromPath(filePath);
|
|
4067
4102
|
const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
|
|
4068
|
-
|
|
4103
|
+
skills2.push({
|
|
4069
4104
|
name,
|
|
4070
4105
|
description: firstParagraph.replace(/^#\s*/, "").trim(),
|
|
4071
4106
|
filePath,
|
|
@@ -4078,7 +4113,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4078
4113
|
});
|
|
4079
4114
|
}
|
|
4080
4115
|
}
|
|
4081
|
-
return
|
|
4116
|
+
return skills2.filter(
|
|
4082
4117
|
(s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
|
|
4083
4118
|
);
|
|
4084
4119
|
}
|
|
@@ -4086,8 +4121,8 @@ async function loadAllSkills(directories) {
|
|
|
4086
4121
|
const allSkills = [];
|
|
4087
4122
|
const seenNames = /* @__PURE__ */ new Set();
|
|
4088
4123
|
for (const dir of directories) {
|
|
4089
|
-
const
|
|
4090
|
-
for (const skill of
|
|
4124
|
+
const skills2 = await loadSkillsFromDirectory(dir);
|
|
4125
|
+
for (const skill of skills2) {
|
|
4091
4126
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4092
4127
|
seenNames.add(skill.name.toLowerCase());
|
|
4093
4128
|
allSkills.push(skill);
|
|
@@ -4100,12 +4135,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4100
4135
|
const allSkills = [];
|
|
4101
4136
|
const seenNames = /* @__PURE__ */ new Set();
|
|
4102
4137
|
for (const { path, priority } of discovered.alwaysLoadedDirs) {
|
|
4103
|
-
const
|
|
4138
|
+
const skills2 = await loadSkillsFromDirectory(path, {
|
|
4104
4139
|
priority,
|
|
4105
4140
|
defaultLoadType: "always",
|
|
4106
4141
|
forceAlwaysApply: true
|
|
4107
4142
|
});
|
|
4108
|
-
for (const skill of
|
|
4143
|
+
for (const skill of skills2) {
|
|
4109
4144
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4110
4145
|
seenNames.add(skill.name.toLowerCase());
|
|
4111
4146
|
allSkills.push(skill);
|
|
@@ -4113,12 +4148,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4113
4148
|
}
|
|
4114
4149
|
}
|
|
4115
4150
|
for (const { path, priority } of discovered.onDemandDirs) {
|
|
4116
|
-
const
|
|
4151
|
+
const skills2 = await loadSkillsFromDirectory(path, {
|
|
4117
4152
|
priority,
|
|
4118
4153
|
defaultLoadType: "on_demand",
|
|
4119
4154
|
forceAlwaysApply: false
|
|
4120
4155
|
});
|
|
4121
|
-
for (const skill of
|
|
4156
|
+
for (const skill of skills2) {
|
|
4122
4157
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4123
4158
|
seenNames.add(skill.name.toLowerCase());
|
|
4124
4159
|
allSkills.push(skill);
|
|
@@ -4143,7 +4178,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4143
4178
|
all: allSkills
|
|
4144
4179
|
};
|
|
4145
4180
|
}
|
|
4146
|
-
async function getGlobMatchedSkills(
|
|
4181
|
+
async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
|
|
4147
4182
|
if (activeFiles.length === 0) {
|
|
4148
4183
|
return [];
|
|
4149
4184
|
}
|
|
@@ -4153,7 +4188,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
|
|
|
4153
4188
|
}
|
|
4154
4189
|
return f;
|
|
4155
4190
|
});
|
|
4156
|
-
const matchedSkills =
|
|
4191
|
+
const matchedSkills = skills2.filter((skill) => {
|
|
4157
4192
|
if (skill.alwaysApply || skill.loadType === "always") {
|
|
4158
4193
|
return false;
|
|
4159
4194
|
}
|
|
@@ -4199,8 +4234,8 @@ async function loadSkillContent(skillName, directories) {
|
|
|
4199
4234
|
content: parsed ? parsed.body : content
|
|
4200
4235
|
};
|
|
4201
4236
|
}
|
|
4202
|
-
function formatSkillsForContext(
|
|
4203
|
-
const onDemandSkills =
|
|
4237
|
+
function formatSkillsForContext(skills2) {
|
|
4238
|
+
const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
|
|
4204
4239
|
if (onDemandSkills.length === 0) {
|
|
4205
4240
|
return "No on-demand skills available.";
|
|
4206
4241
|
}
|
|
@@ -4211,12 +4246,12 @@ function formatSkillsForContext(skills) {
|
|
|
4211
4246
|
}
|
|
4212
4247
|
return lines.join("\n");
|
|
4213
4248
|
}
|
|
4214
|
-
function formatAlwaysLoadedSkills(
|
|
4215
|
-
if (
|
|
4249
|
+
function formatAlwaysLoadedSkills(skills2) {
|
|
4250
|
+
if (skills2.length === 0) {
|
|
4216
4251
|
return "";
|
|
4217
4252
|
}
|
|
4218
4253
|
const sections = [];
|
|
4219
|
-
for (const skill of
|
|
4254
|
+
for (const skill of skills2) {
|
|
4220
4255
|
sections.push(`### ${skill.name}
|
|
4221
4256
|
|
|
4222
4257
|
${skill.content}`);
|
|
@@ -4225,12 +4260,12 @@ ${skill.content}`);
|
|
|
4225
4260
|
|
|
4226
4261
|
${sections.join("\n\n---\n\n")}`;
|
|
4227
4262
|
}
|
|
4228
|
-
function formatGlobMatchedSkills(
|
|
4229
|
-
if (
|
|
4263
|
+
function formatGlobMatchedSkills(skills2) {
|
|
4264
|
+
if (skills2.length === 0) {
|
|
4230
4265
|
return "";
|
|
4231
4266
|
}
|
|
4232
4267
|
const sections = [];
|
|
4233
|
-
for (const skill of
|
|
4268
|
+
for (const skill of skills2) {
|
|
4234
4269
|
sections.push(`### ${skill.name}
|
|
4235
4270
|
|
|
4236
4271
|
${skill.content}`);
|
|
@@ -4272,16 +4307,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
4272
4307
|
try {
|
|
4273
4308
|
switch (action) {
|
|
4274
4309
|
case "list": {
|
|
4275
|
-
const
|
|
4310
|
+
const skills2 = await loadAllSkills(options.skillsDirectories);
|
|
4276
4311
|
return {
|
|
4277
4312
|
success: true,
|
|
4278
4313
|
action: "list",
|
|
4279
|
-
skillCount:
|
|
4280
|
-
skills:
|
|
4314
|
+
skillCount: skills2.length,
|
|
4315
|
+
skills: skills2.map((s) => ({
|
|
4281
4316
|
name: s.name,
|
|
4282
4317
|
description: s.description
|
|
4283
4318
|
})),
|
|
4284
|
-
formatted: formatSkillsForContext(
|
|
4319
|
+
formatted: formatSkillsForContext(skills2)
|
|
4285
4320
|
};
|
|
4286
4321
|
}
|
|
4287
4322
|
case "load": {
|
|
@@ -4719,8 +4754,8 @@ var init_subagent = __esm({
|
|
|
4719
4754
|
if (eventQueue.length > 0) {
|
|
4720
4755
|
yield eventQueue.shift();
|
|
4721
4756
|
} else if (!done) {
|
|
4722
|
-
const event = await new Promise((
|
|
4723
|
-
resolveNext =
|
|
4757
|
+
const event = await new Promise((resolve14) => {
|
|
4758
|
+
resolveNext = resolve14;
|
|
4724
4759
|
});
|
|
4725
4760
|
if (event) {
|
|
4726
4761
|
yield event;
|
|
@@ -5362,7 +5397,7 @@ function formatError(error) {
|
|
|
5362
5397
|
}
|
|
5363
5398
|
}
|
|
5364
5399
|
function sleep(ms) {
|
|
5365
|
-
return new Promise((
|
|
5400
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
5366
5401
|
}
|
|
5367
5402
|
function isPathExcluded(relativePath, exclude) {
|
|
5368
5403
|
return exclude.some((pattern) => {
|
|
@@ -5380,7 +5415,7 @@ function isPathExcluded(relativePath, exclude) {
|
|
|
5380
5415
|
}
|
|
5381
5416
|
async function walkDirectory(dir, include, exclude, baseDir) {
|
|
5382
5417
|
const { readdirSync: readdirSync4 } = await import("fs");
|
|
5383
|
-
const { join:
|
|
5418
|
+
const { join: join19, relative: relative10 } = await import("path");
|
|
5384
5419
|
const files = [];
|
|
5385
5420
|
function walk(currentDir) {
|
|
5386
5421
|
let entries;
|
|
@@ -5390,7 +5425,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
|
|
|
5390
5425
|
return;
|
|
5391
5426
|
}
|
|
5392
5427
|
for (const entry2 of entries) {
|
|
5393
|
-
const fullPath =
|
|
5428
|
+
const fullPath = join19(currentDir, entry2.name);
|
|
5394
5429
|
const relativePath = relative10(baseDir, fullPath);
|
|
5395
5430
|
if (isPathExcluded(relativePath, exclude)) {
|
|
5396
5431
|
continue;
|
|
@@ -7238,8 +7273,8 @@ async function buildSystemPrompt(options) {
|
|
|
7238
7273
|
}
|
|
7239
7274
|
} else {
|
|
7240
7275
|
const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
|
|
7241
|
-
const
|
|
7242
|
-
onDemandSkillsContext = formatSkillsForContext(
|
|
7276
|
+
const skills2 = await loadAllSkills2(skillsDirectories);
|
|
7277
|
+
onDemandSkillsContext = formatSkillsForContext(skills2);
|
|
7243
7278
|
}
|
|
7244
7279
|
const todos = await todoQueries.getBySession(sessionId);
|
|
7245
7280
|
const todosContext = formatTodosForContext(todos);
|
|
@@ -8138,6 +8173,35 @@ function stripOrphanedToolResults(msg, removedIds) {
|
|
|
8138
8173
|
if (parts.length === 0) return null;
|
|
8139
8174
|
return { ...msg, content: parts };
|
|
8140
8175
|
}
|
|
8176
|
+
function wrapToolsNeverThrow(tools) {
|
|
8177
|
+
if (!tools || typeof tools !== "object") return tools;
|
|
8178
|
+
const wrapped = {};
|
|
8179
|
+
for (const [name, t] of Object.entries(tools)) {
|
|
8180
|
+
if (!t || typeof t.execute !== "function") {
|
|
8181
|
+
wrapped[name] = t;
|
|
8182
|
+
continue;
|
|
8183
|
+
}
|
|
8184
|
+
const original = t.execute;
|
|
8185
|
+
wrapped[name] = {
|
|
8186
|
+
...t,
|
|
8187
|
+
execute: async (input, opts) => {
|
|
8188
|
+
try {
|
|
8189
|
+
return await original.call(t, input, opts);
|
|
8190
|
+
} catch (err) {
|
|
8191
|
+
const message = err?.message ?? String(err);
|
|
8192
|
+
console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
|
|
8193
|
+
return {
|
|
8194
|
+
__error: true,
|
|
8195
|
+
tool: name,
|
|
8196
|
+
message,
|
|
8197
|
+
note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
|
|
8198
|
+
};
|
|
8199
|
+
}
|
|
8200
|
+
}
|
|
8201
|
+
};
|
|
8202
|
+
}
|
|
8203
|
+
return wrapped;
|
|
8204
|
+
}
|
|
8141
8205
|
function repairToolPairing(messages) {
|
|
8142
8206
|
const toolCallIds = /* @__PURE__ */ new Set();
|
|
8143
8207
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -8503,6 +8567,120 @@ var init_web = __esm({
|
|
|
8503
8567
|
}
|
|
8504
8568
|
});
|
|
8505
8569
|
|
|
8570
|
+
// src/integrations/slack/persistence.ts
|
|
8571
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
8572
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
8573
|
+
function cachePath() {
|
|
8574
|
+
return join9(ensureAppDataDirectory(), FILENAME);
|
|
8575
|
+
}
|
|
8576
|
+
function load() {
|
|
8577
|
+
if (loaded) return;
|
|
8578
|
+
loaded = true;
|
|
8579
|
+
const path = cachePath();
|
|
8580
|
+
if (!existsSync16(path)) return;
|
|
8581
|
+
try {
|
|
8582
|
+
const raw = readFileSync7(path, "utf-8");
|
|
8583
|
+
const parsed = JSON.parse(raw);
|
|
8584
|
+
if (parsed?.version !== FILE_VERSION) return;
|
|
8585
|
+
const now = Date.now();
|
|
8586
|
+
for (const [id, e] of Object.entries(parsed.users || {})) {
|
|
8587
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
8588
|
+
userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
|
|
8589
|
+
}
|
|
8590
|
+
}
|
|
8591
|
+
for (const [key2, e] of Object.entries(parsed.threads || {})) {
|
|
8592
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
8593
|
+
threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
} catch (err) {
|
|
8597
|
+
console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
|
|
8598
|
+
}
|
|
8599
|
+
}
|
|
8600
|
+
function evictIfOversized(map, max) {
|
|
8601
|
+
if (map.size <= max) return;
|
|
8602
|
+
const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
8603
|
+
const toRemove = map.size - max;
|
|
8604
|
+
for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
|
|
8605
|
+
}
|
|
8606
|
+
function scheduleSave() {
|
|
8607
|
+
dirty = true;
|
|
8608
|
+
if (saveTimer) return;
|
|
8609
|
+
saveTimer = setTimeout(() => {
|
|
8610
|
+
saveTimer = null;
|
|
8611
|
+
if (dirty) saveSync();
|
|
8612
|
+
}, SAVE_DEBOUNCE_MS);
|
|
8613
|
+
saveTimer.unref?.();
|
|
8614
|
+
}
|
|
8615
|
+
function saveSync() {
|
|
8616
|
+
dirty = false;
|
|
8617
|
+
try {
|
|
8618
|
+
const path = cachePath();
|
|
8619
|
+
const dir = dirname6(path);
|
|
8620
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
8621
|
+
const payload = {
|
|
8622
|
+
version: FILE_VERSION,
|
|
8623
|
+
users: Object.fromEntries(userMap),
|
|
8624
|
+
threads: Object.fromEntries(threadMap)
|
|
8625
|
+
};
|
|
8626
|
+
const tmp = `${path}.tmp`;
|
|
8627
|
+
writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
|
|
8628
|
+
renameSync(tmp, path);
|
|
8629
|
+
} catch (err) {
|
|
8630
|
+
console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
|
|
8631
|
+
}
|
|
8632
|
+
}
|
|
8633
|
+
function hookExit() {
|
|
8634
|
+
if (exitHooked) return;
|
|
8635
|
+
exitHooked = true;
|
|
8636
|
+
const flush2 = () => {
|
|
8637
|
+
if (dirty) saveSync();
|
|
8638
|
+
};
|
|
8639
|
+
process.once("beforeExit", flush2);
|
|
8640
|
+
process.once("SIGINT", flush2);
|
|
8641
|
+
process.once("SIGTERM", flush2);
|
|
8642
|
+
}
|
|
8643
|
+
function getCachedUserName(userId) {
|
|
8644
|
+
load();
|
|
8645
|
+
return userMap.get(userId);
|
|
8646
|
+
}
|
|
8647
|
+
function setCachedUserName(userId, entry2) {
|
|
8648
|
+
load();
|
|
8649
|
+
hookExit();
|
|
8650
|
+
userMap.set(userId, entry2);
|
|
8651
|
+
evictIfOversized(userMap, MAX_USERS);
|
|
8652
|
+
scheduleSave();
|
|
8653
|
+
}
|
|
8654
|
+
function getCachedThreadOwnership(key2) {
|
|
8655
|
+
load();
|
|
8656
|
+
return threadMap.get(key2);
|
|
8657
|
+
}
|
|
8658
|
+
function setCachedThreadOwnership(key2, entry2) {
|
|
8659
|
+
load();
|
|
8660
|
+
hookExit();
|
|
8661
|
+
threadMap.set(key2, entry2);
|
|
8662
|
+
evictIfOversized(threadMap, MAX_THREADS);
|
|
8663
|
+
scheduleSave();
|
|
8664
|
+
}
|
|
8665
|
+
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
8666
|
+
var init_persistence = __esm({
|
|
8667
|
+
"src/integrations/slack/persistence.ts"() {
|
|
8668
|
+
"use strict";
|
|
8669
|
+
init_config();
|
|
8670
|
+
FILENAME = "slack-cache.json";
|
|
8671
|
+
FILE_VERSION = 1;
|
|
8672
|
+
SAVE_DEBOUNCE_MS = 500;
|
|
8673
|
+
MAX_USERS = 5e3;
|
|
8674
|
+
MAX_THREADS = 5e3;
|
|
8675
|
+
loaded = false;
|
|
8676
|
+
userMap = /* @__PURE__ */ new Map();
|
|
8677
|
+
threadMap = /* @__PURE__ */ new Map();
|
|
8678
|
+
dirty = false;
|
|
8679
|
+
saveTimer = null;
|
|
8680
|
+
exitHooked = false;
|
|
8681
|
+
}
|
|
8682
|
+
});
|
|
8683
|
+
|
|
8506
8684
|
// src/integrations/slack/client.ts
|
|
8507
8685
|
function readSlackConfig() {
|
|
8508
8686
|
try {
|
|
@@ -8625,13 +8803,13 @@ async function fetchSlackUserName(userId) {
|
|
|
8625
8803
|
async function resolveSlackUserName(userId) {
|
|
8626
8804
|
if (!userId) return null;
|
|
8627
8805
|
const now = Date.now();
|
|
8628
|
-
const hit =
|
|
8806
|
+
const hit = getCachedUserName(userId);
|
|
8629
8807
|
if (hit && hit.expiresAt > now) return hit.name;
|
|
8630
8808
|
const inflight = userInflight.get(userId);
|
|
8631
8809
|
if (inflight) return inflight;
|
|
8632
8810
|
const p = (async () => {
|
|
8633
8811
|
const name = await fetchSlackUserName(userId);
|
|
8634
|
-
|
|
8812
|
+
setCachedUserName(userId, {
|
|
8635
8813
|
name,
|
|
8636
8814
|
expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
8637
8815
|
});
|
|
@@ -8653,11 +8831,63 @@ async function normalizeSlackMentions(text) {
|
|
|
8653
8831
|
}
|
|
8654
8832
|
return text.replace(userMentionRe, (_full, id, label) => {
|
|
8655
8833
|
if (label) return `${label} <@${id}>`;
|
|
8656
|
-
const cached =
|
|
8834
|
+
const cached = getCachedUserName(id);
|
|
8657
8835
|
const name = cached?.name;
|
|
8658
8836
|
return name ? `${name} <@${id}>` : `<@${id}>`;
|
|
8659
8837
|
});
|
|
8660
8838
|
}
|
|
8839
|
+
function threadCacheKey(channel, threadTs) {
|
|
8840
|
+
return `${channel}\u241F${threadTs}`;
|
|
8841
|
+
}
|
|
8842
|
+
async function fetchBotParticipatedInThread(channel, threadTs) {
|
|
8843
|
+
const token = getSlackBotToken();
|
|
8844
|
+
if (!token) return false;
|
|
8845
|
+
const self = await ensureSlackSelfIdentity();
|
|
8846
|
+
if (!self) return false;
|
|
8847
|
+
try {
|
|
8848
|
+
const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
|
|
8849
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
8850
|
+
const data = await res.json().catch(() => ({}));
|
|
8851
|
+
if (!data?.ok) {
|
|
8852
|
+
console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
8853
|
+
return false;
|
|
8854
|
+
}
|
|
8855
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
8856
|
+
return messages.some(
|
|
8857
|
+
(m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
|
|
8858
|
+
);
|
|
8859
|
+
} catch (err) {
|
|
8860
|
+
console.warn(`[slack] conversations.replies error:`, err?.message || err);
|
|
8861
|
+
return false;
|
|
8862
|
+
}
|
|
8863
|
+
}
|
|
8864
|
+
async function botParticipatedInThread(channel, threadTs) {
|
|
8865
|
+
if (!channel || !threadTs) return false;
|
|
8866
|
+
const key2 = threadCacheKey(channel, threadTs);
|
|
8867
|
+
const now = Date.now();
|
|
8868
|
+
const hit = getCachedThreadOwnership(key2);
|
|
8869
|
+
if (hit && hit.expiresAt > now) return hit.owned;
|
|
8870
|
+
const inflight = threadOwnedInflight.get(key2);
|
|
8871
|
+
if (inflight) return inflight;
|
|
8872
|
+
const p = (async () => {
|
|
8873
|
+
const owned = await fetchBotParticipatedInThread(channel, threadTs);
|
|
8874
|
+
setCachedThreadOwnership(key2, {
|
|
8875
|
+
owned,
|
|
8876
|
+
expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
|
|
8877
|
+
});
|
|
8878
|
+
threadOwnedInflight.delete(key2);
|
|
8879
|
+
return owned;
|
|
8880
|
+
})();
|
|
8881
|
+
threadOwnedInflight.set(key2, p);
|
|
8882
|
+
return p;
|
|
8883
|
+
}
|
|
8884
|
+
function noteBotPostedInThread(channel, threadTs) {
|
|
8885
|
+
if (!channel || !threadTs) return;
|
|
8886
|
+
setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
|
|
8887
|
+
owned: true,
|
|
8888
|
+
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
8889
|
+
});
|
|
8890
|
+
}
|
|
8661
8891
|
function getSlackDeniedReplyPolicy() {
|
|
8662
8892
|
try {
|
|
8663
8893
|
const cfg = getConfig();
|
|
@@ -8670,17 +8900,20 @@ function getSlackDeniedReplyPolicy() {
|
|
|
8670
8900
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
8671
8901
|
}
|
|
8672
8902
|
}
|
|
8673
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS,
|
|
8903
|
+
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
8674
8904
|
var init_client3 = __esm({
|
|
8675
8905
|
"src/integrations/slack/client.ts"() {
|
|
8676
8906
|
"use strict";
|
|
8677
8907
|
init_config();
|
|
8908
|
+
init_persistence();
|
|
8678
8909
|
cachedSelf = null;
|
|
8679
8910
|
selfInflight = null;
|
|
8680
8911
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
8681
8912
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
8682
|
-
userNameCache = /* @__PURE__ */ new Map();
|
|
8683
8913
|
userInflight = /* @__PURE__ */ new Map();
|
|
8914
|
+
THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
8915
|
+
THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
8916
|
+
threadOwnedInflight = /* @__PURE__ */ new Map();
|
|
8684
8917
|
DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
|
|
8685
8918
|
}
|
|
8686
8919
|
});
|
|
@@ -8780,6 +9013,7 @@ var init_slack = __esm({
|
|
|
8780
9013
|
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
8781
9014
|
if (r.slackChannel && r.threadTs) {
|
|
8782
9015
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
9016
|
+
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8783
9017
|
}
|
|
8784
9018
|
},
|
|
8785
9019
|
displayLabel(ref) {
|
|
@@ -9437,8 +9671,8 @@ var init_orchestrator_actions = __esm({
|
|
|
9437
9671
|
|
|
9438
9672
|
// src/integrations/mcp/store.ts
|
|
9439
9673
|
import { nanoid as nanoid6 } from "nanoid";
|
|
9440
|
-
import { existsSync as
|
|
9441
|
-
import { resolve as resolve10, join as
|
|
9674
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
|
|
9675
|
+
import { resolve as resolve10, join as join10 } from "path";
|
|
9442
9676
|
function readServers() {
|
|
9443
9677
|
try {
|
|
9444
9678
|
const cfg = getConfig();
|
|
@@ -9450,12 +9684,12 @@ function readServers() {
|
|
|
9450
9684
|
function refreshMcpServersFromDisk() {
|
|
9451
9685
|
const candidates = [
|
|
9452
9686
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9453
|
-
|
|
9687
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9454
9688
|
];
|
|
9455
9689
|
for (const path of candidates) {
|
|
9456
|
-
if (!
|
|
9690
|
+
if (!existsSync17(path)) continue;
|
|
9457
9691
|
try {
|
|
9458
|
-
const raw = JSON.parse(
|
|
9692
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
9459
9693
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9460
9694
|
setMcpServers(servers2);
|
|
9461
9695
|
return servers2;
|
|
@@ -9707,11 +9941,11 @@ function waitForTaskQuestionAnswer(question) {
|
|
|
9707
9941
|
if (pendingQuestions.has(k)) {
|
|
9708
9942
|
return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
|
|
9709
9943
|
}
|
|
9710
|
-
return new Promise((
|
|
9944
|
+
return new Promise((resolve14, reject) => {
|
|
9711
9945
|
pendingQuestions.set(k, {
|
|
9712
9946
|
...question,
|
|
9713
9947
|
createdAt: /* @__PURE__ */ new Date(),
|
|
9714
|
-
resolve:
|
|
9948
|
+
resolve: resolve14,
|
|
9715
9949
|
reject
|
|
9716
9950
|
});
|
|
9717
9951
|
});
|
|
@@ -10068,7 +10302,7 @@ __export(recorder_exports, {
|
|
|
10068
10302
|
import { exec as exec5 } from "child_process";
|
|
10069
10303
|
import { promisify as promisify5 } from "util";
|
|
10070
10304
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
10071
|
-
import { join as
|
|
10305
|
+
import { join as join11 } from "path";
|
|
10072
10306
|
import { tmpdir } from "os";
|
|
10073
10307
|
import { nanoid as nanoid7 } from "nanoid";
|
|
10074
10308
|
async function checkFfmpeg() {
|
|
@@ -10125,21 +10359,21 @@ var init_recorder = __esm({
|
|
|
10125
10359
|
*/
|
|
10126
10360
|
async encode() {
|
|
10127
10361
|
if (this.frames.length === 0) return null;
|
|
10128
|
-
const workDir =
|
|
10362
|
+
const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
10129
10363
|
await mkdir4(workDir, { recursive: true });
|
|
10130
10364
|
try {
|
|
10131
10365
|
for (let i = 0; i < this.frames.length; i++) {
|
|
10132
|
-
const framePath =
|
|
10366
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
10133
10367
|
await writeFile5(framePath, this.frames[i].data);
|
|
10134
10368
|
}
|
|
10135
10369
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
10136
10370
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
10137
10371
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
10138
|
-
const outputPath =
|
|
10372
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
10139
10373
|
const hasFfmpeg = await checkFfmpeg();
|
|
10140
10374
|
if (hasFfmpeg) {
|
|
10141
10375
|
await execAsync5(
|
|
10142
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
10376
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
10143
10377
|
{ timeout: 12e4 }
|
|
10144
10378
|
);
|
|
10145
10379
|
} else {
|
|
@@ -10151,7 +10385,7 @@ var init_recorder = __esm({
|
|
|
10151
10385
|
const files = await readdir5(workDir);
|
|
10152
10386
|
for (const f of files) {
|
|
10153
10387
|
if (f.startsWith("frame_")) {
|
|
10154
|
-
await unlink2(
|
|
10388
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
10155
10389
|
});
|
|
10156
10390
|
}
|
|
10157
10391
|
}
|
|
@@ -10418,7 +10652,8 @@ ${personality.trim()}`;
|
|
|
10418
10652
|
}
|
|
10419
10653
|
const messages = await this.context.getMessages();
|
|
10420
10654
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
10421
|
-
const
|
|
10655
|
+
const approvalWrapped = this.wrapToolsWithApproval(options, tools);
|
|
10656
|
+
const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
|
|
10422
10657
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10423
10658
|
const stream = streamText2({
|
|
10424
10659
|
model: resolveModel(this.session.model),
|
|
@@ -10432,6 +10667,17 @@ ${personality.trim()}`;
|
|
|
10432
10667
|
providerOptions: useAnthropic ? {
|
|
10433
10668
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10434
10669
|
} : void 0,
|
|
10670
|
+
// Run repairToolPairing before EVERY step's model call, not just the
|
|
10671
|
+
// first one. The AI SDK's multi-step loop can otherwise feed the model
|
|
10672
|
+
// a prompt containing an orphan tool-call (e.g. when a previous step's
|
|
10673
|
+
// tool result was lost, dropped during compaction, or the stream was
|
|
10674
|
+
// aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
|
|
10675
|
+
// ever reaches the model and we never hit AI_MissingToolResultsError.
|
|
10676
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10677
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10678
|
+
if (repaired === stepMessages) return {};
|
|
10679
|
+
return { messages: repaired };
|
|
10680
|
+
},
|
|
10435
10681
|
onStepFinish: async (step) => {
|
|
10436
10682
|
options.onStepFinish?.(step);
|
|
10437
10683
|
},
|
|
@@ -10467,7 +10713,7 @@ ${personality.trim()}`;
|
|
|
10467
10713
|
});
|
|
10468
10714
|
const messages = await this.context.getMessages();
|
|
10469
10715
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
10470
|
-
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
10716
|
+
const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
|
|
10471
10717
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10472
10718
|
const result = await generateText3({
|
|
10473
10719
|
model: resolveModel(this.session.model),
|
|
@@ -10478,7 +10724,13 @@ ${personality.trim()}`;
|
|
|
10478
10724
|
// Enable extended thinking/reasoning for models that support it
|
|
10479
10725
|
providerOptions: useAnthropic ? {
|
|
10480
10726
|
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
10481
|
-
} : void 0
|
|
10727
|
+
} : void 0,
|
|
10728
|
+
// Repair tool pairing before every step (see `stream()` for full rationale).
|
|
10729
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10730
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10731
|
+
if (repaired === stepMessages) return {};
|
|
10732
|
+
return { messages: repaired };
|
|
10733
|
+
}
|
|
10482
10734
|
});
|
|
10483
10735
|
const responseMessages = result.response.messages;
|
|
10484
10736
|
this.context.addResponseMessages(responseMessages);
|
|
@@ -10655,12 +10907,19 @@ ${p.text}` : p.text;
|
|
|
10655
10907
|
model: resolveModel(this.session.model),
|
|
10656
10908
|
system: systemPrompt,
|
|
10657
10909
|
messages,
|
|
10658
|
-
tools: taskTools,
|
|
10910
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
10659
10911
|
stopWhen: stepCountIs2(500),
|
|
10660
10912
|
abortSignal: combinedAbort,
|
|
10661
10913
|
providerOptions: useAnthropic ? {
|
|
10662
10914
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10663
10915
|
} : void 0,
|
|
10916
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
10917
|
+
// every step so we never feed the model an orphan tool-call.
|
|
10918
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10919
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10920
|
+
if (repaired === stepMessages) return {};
|
|
10921
|
+
return { messages: repaired };
|
|
10922
|
+
},
|
|
10664
10923
|
onStepFinish: async (step) => {
|
|
10665
10924
|
options.onStepFinish?.(step);
|
|
10666
10925
|
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
@@ -10860,14 +11119,14 @@ ${p.text}` : p.text;
|
|
|
10860
11119
|
const result = await recorder.encode();
|
|
10861
11120
|
recorder.clear();
|
|
10862
11121
|
if (!result) return [];
|
|
10863
|
-
const { readFile:
|
|
11122
|
+
const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
|
|
10864
11123
|
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
10865
11124
|
this.session.id,
|
|
10866
11125
|
`browser-recording-${Date.now()}.mp4`,
|
|
10867
11126
|
"video/mp4",
|
|
10868
11127
|
"browser-recording"
|
|
10869
11128
|
);
|
|
10870
|
-
const fileData = await
|
|
11129
|
+
const fileData = await readFile13(result.path);
|
|
10871
11130
|
await fetch(uploadInfo.uploadUrl, {
|
|
10872
11131
|
method: "PUT",
|
|
10873
11132
|
headers: { "Content-Type": "video/mp4" },
|
|
@@ -10875,7 +11134,7 @@ ${p.text}` : p.text;
|
|
|
10875
11134
|
});
|
|
10876
11135
|
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
10877
11136
|
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
10878
|
-
await
|
|
11137
|
+
await unlink4(result.path).catch(() => {
|
|
10879
11138
|
});
|
|
10880
11139
|
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
10881
11140
|
return [dlInfo.downloadUrl];
|
|
@@ -10893,13 +11152,13 @@ ${p.text}` : p.text;
|
|
|
10893
11152
|
try {
|
|
10894
11153
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10895
11154
|
if (!isRemoteConfigured2()) return [];
|
|
10896
|
-
const { readFile:
|
|
10897
|
-
const { join:
|
|
11155
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
11156
|
+
const { join: join19, basename: basename7 } = await import("path");
|
|
10898
11157
|
const urls = [];
|
|
10899
11158
|
for (const filePath of filePaths) {
|
|
10900
11159
|
try {
|
|
10901
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10902
|
-
const fileName =
|
|
11160
|
+
const fullPath = filePath.startsWith("/") ? filePath : join19(this.session.workingDirectory, filePath);
|
|
11161
|
+
const fileName = basename7(fullPath);
|
|
10903
11162
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10904
11163
|
const mimeMap = {
|
|
10905
11164
|
pdf: "application/pdf",
|
|
@@ -10923,7 +11182,7 @@ ${p.text}` : p.text;
|
|
|
10923
11182
|
contentType,
|
|
10924
11183
|
"task-output"
|
|
10925
11184
|
);
|
|
10926
|
-
const fileData = await
|
|
11185
|
+
const fileData = await readFile13(fullPath);
|
|
10927
11186
|
await fetch(uploadInfo.uploadUrl, {
|
|
10928
11187
|
method: "PUT",
|
|
10929
11188
|
headers: { "Content-Type": contentType },
|
|
@@ -10972,8 +11231,8 @@ ${p.text}` : p.text;
|
|
|
10972
11231
|
this.pendingApprovals.set(toolCallId, await execution);
|
|
10973
11232
|
options.onApprovalRequired?.(await execution);
|
|
10974
11233
|
await sessionQueries.updateStatus(this.session.id, "waiting");
|
|
10975
|
-
const approved = await new Promise((
|
|
10976
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
11234
|
+
const approved = await new Promise((resolve14) => {
|
|
11235
|
+
approvalResolvers.set(toolCallId, { resolve: resolve14, sessionId: this.session.id });
|
|
10977
11236
|
});
|
|
10978
11237
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
10979
11238
|
approvalResolvers.delete(toolCallId);
|
|
@@ -11079,8 +11338,8 @@ async function withSessionLock(sessionId, fn) {
|
|
|
11079
11338
|
state2.pending++;
|
|
11080
11339
|
const prev = state2.tail;
|
|
11081
11340
|
let release;
|
|
11082
|
-
const next = new Promise((
|
|
11083
|
-
release =
|
|
11341
|
+
const next = new Promise((resolve14) => {
|
|
11342
|
+
release = resolve14;
|
|
11084
11343
|
});
|
|
11085
11344
|
state2.tail = prev.then(() => next);
|
|
11086
11345
|
await prev;
|
|
@@ -11103,19 +11362,19 @@ var init_session_lock = __esm({
|
|
|
11103
11362
|
});
|
|
11104
11363
|
|
|
11105
11364
|
// src/orchestrator/webhook-events.ts
|
|
11106
|
-
import { existsSync as
|
|
11107
|
-
import { dirname as
|
|
11365
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
11366
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
11108
11367
|
import { nanoid as nanoid9 } from "nanoid";
|
|
11109
11368
|
function logFilePath() {
|
|
11110
|
-
return
|
|
11369
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
11111
11370
|
}
|
|
11112
11371
|
function ensureLoaded() {
|
|
11113
11372
|
if (cache !== null) return cache;
|
|
11114
11373
|
cache = [];
|
|
11115
11374
|
try {
|
|
11116
11375
|
const p = logFilePath();
|
|
11117
|
-
if (!
|
|
11118
|
-
const lines =
|
|
11376
|
+
if (!existsSync18(p)) return cache;
|
|
11377
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
11119
11378
|
for (const line of lines) {
|
|
11120
11379
|
try {
|
|
11121
11380
|
cache.push(JSON.parse(line));
|
|
@@ -11125,7 +11384,7 @@ function ensureLoaded() {
|
|
|
11125
11384
|
if (cache.length > MAX_EVENTS) {
|
|
11126
11385
|
cache = cache.slice(-MAX_EVENTS);
|
|
11127
11386
|
try {
|
|
11128
|
-
|
|
11387
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11129
11388
|
} catch {
|
|
11130
11389
|
}
|
|
11131
11390
|
}
|
|
@@ -11139,7 +11398,7 @@ function appendEvent(ev) {
|
|
|
11139
11398
|
if (list.length > MAX_EVENTS) list.shift();
|
|
11140
11399
|
try {
|
|
11141
11400
|
const p = logFilePath();
|
|
11142
|
-
|
|
11401
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
11143
11402
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
11144
11403
|
} catch {
|
|
11145
11404
|
}
|
|
@@ -11170,8 +11429,8 @@ function updateEvent(id, patch) {
|
|
|
11170
11429
|
list[i] = { ...list[i], ...patch };
|
|
11171
11430
|
try {
|
|
11172
11431
|
const p = logFilePath();
|
|
11173
|
-
|
|
11174
|
-
|
|
11432
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
11433
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11175
11434
|
} catch {
|
|
11176
11435
|
}
|
|
11177
11436
|
}
|
|
@@ -11203,7 +11462,7 @@ function listEvents(filter = {}) {
|
|
|
11203
11462
|
function clearAllEvents() {
|
|
11204
11463
|
cache = [];
|
|
11205
11464
|
try {
|
|
11206
|
-
|
|
11465
|
+
writeFileSync4(logFilePath(), "");
|
|
11207
11466
|
} catch {
|
|
11208
11467
|
}
|
|
11209
11468
|
}
|
|
@@ -11693,17 +11952,17 @@ import chalk from "chalk";
|
|
|
11693
11952
|
import ora from "ora";
|
|
11694
11953
|
import "dotenv/config";
|
|
11695
11954
|
import { createInterface } from "readline";
|
|
11696
|
-
import { dirname as
|
|
11955
|
+
import { dirname as dirname11 } from "path";
|
|
11697
11956
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11698
11957
|
|
|
11699
11958
|
// src/server/index.ts
|
|
11700
11959
|
import "dotenv/config";
|
|
11701
|
-
import { Hono as
|
|
11960
|
+
import { Hono as Hono10 } from "hono";
|
|
11702
11961
|
import { serve } from "@hono/node-server";
|
|
11703
11962
|
import { cors } from "hono/cors";
|
|
11704
11963
|
import { logger } from "hono/logger";
|
|
11705
|
-
import { existsSync as
|
|
11706
|
-
import { resolve as
|
|
11964
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
11965
|
+
import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
|
|
11707
11966
|
import { spawn as spawn2 } from "child_process";
|
|
11708
11967
|
import { createServer as createNetServer } from "net";
|
|
11709
11968
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -11717,9 +11976,9 @@ init_checkpoints();
|
|
|
11717
11976
|
import { Hono } from "hono";
|
|
11718
11977
|
import { zValidator } from "@hono/zod-validator";
|
|
11719
11978
|
import { z as z16 } from "zod";
|
|
11720
|
-
import { existsSync as
|
|
11979
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
11721
11980
|
import { readdir as readdir6 } from "fs/promises";
|
|
11722
|
-
import { join as
|
|
11981
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
11723
11982
|
import { nanoid as nanoid10 } from "nanoid";
|
|
11724
11983
|
|
|
11725
11984
|
// src/tasks/agent-status.ts
|
|
@@ -12357,12 +12616,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
12357
12616
|
});
|
|
12358
12617
|
function getAttachmentsDir(sessionId) {
|
|
12359
12618
|
const appDataDir = getAppDataDirectory();
|
|
12360
|
-
return
|
|
12619
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
12361
12620
|
}
|
|
12362
12621
|
function ensureAttachmentsDir(sessionId) {
|
|
12363
12622
|
const dir = getAttachmentsDir(sessionId);
|
|
12364
|
-
if (!
|
|
12365
|
-
|
|
12623
|
+
if (!existsSync19(dir)) {
|
|
12624
|
+
mkdirSync8(dir, { recursive: true });
|
|
12366
12625
|
}
|
|
12367
12626
|
return dir;
|
|
12368
12627
|
}
|
|
@@ -12373,12 +12632,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
12373
12632
|
return c.json({ error: "Session not found" }, 404);
|
|
12374
12633
|
}
|
|
12375
12634
|
const dir = getAttachmentsDir(sessionId);
|
|
12376
|
-
if (!
|
|
12635
|
+
if (!existsSync19(dir)) {
|
|
12377
12636
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
12378
12637
|
}
|
|
12379
12638
|
const files = readdirSync3(dir);
|
|
12380
12639
|
const attachments = files.map((filename) => {
|
|
12381
|
-
const filePath =
|
|
12640
|
+
const filePath = join13(dir, filename);
|
|
12382
12641
|
const stats = statSync2(filePath);
|
|
12383
12642
|
return {
|
|
12384
12643
|
id: filename.split("_")[0],
|
|
@@ -12413,9 +12672,9 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12413
12672
|
const id = nanoid10(10);
|
|
12414
12673
|
const ext = extname8(file.name) || "";
|
|
12415
12674
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12416
|
-
const filePath =
|
|
12675
|
+
const filePath = join13(dir, safeFilename);
|
|
12417
12676
|
const arrayBuffer = await file.arrayBuffer();
|
|
12418
|
-
|
|
12677
|
+
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
12419
12678
|
return c.json({
|
|
12420
12679
|
id,
|
|
12421
12680
|
filename: file.name,
|
|
@@ -12439,13 +12698,13 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12439
12698
|
const id = nanoid10(10);
|
|
12440
12699
|
const ext = extname8(body.filename) || "";
|
|
12441
12700
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12442
|
-
const filePath =
|
|
12701
|
+
const filePath = join13(dir, safeFilename);
|
|
12443
12702
|
let base64Data = body.data;
|
|
12444
12703
|
if (base64Data.includes(",")) {
|
|
12445
12704
|
base64Data = base64Data.split(",")[1];
|
|
12446
12705
|
}
|
|
12447
12706
|
const buffer = Buffer.from(base64Data, "base64");
|
|
12448
|
-
|
|
12707
|
+
writeFileSync5(filePath, buffer);
|
|
12449
12708
|
return c.json({
|
|
12450
12709
|
id,
|
|
12451
12710
|
filename: body.filename,
|
|
@@ -12468,7 +12727,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12468
12727
|
return c.json({ error: "Session not found" }, 404);
|
|
12469
12728
|
}
|
|
12470
12729
|
const dir = getAttachmentsDir(sessionId);
|
|
12471
|
-
if (!
|
|
12730
|
+
if (!existsSync19(dir)) {
|
|
12472
12731
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12473
12732
|
}
|
|
12474
12733
|
const files = readdirSync3(dir);
|
|
@@ -12476,7 +12735,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12476
12735
|
if (!file) {
|
|
12477
12736
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12478
12737
|
}
|
|
12479
|
-
const filePath =
|
|
12738
|
+
const filePath = join13(dir, file);
|
|
12480
12739
|
unlinkSync2(filePath);
|
|
12481
12740
|
return c.json({ success: true, id: attachmentId });
|
|
12482
12741
|
});
|
|
@@ -12559,7 +12818,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12559
12818
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12560
12819
|
for (const entry2 of entries) {
|
|
12561
12820
|
if (results.length >= limit * 2) break;
|
|
12562
|
-
const fullPath =
|
|
12821
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
12563
12822
|
const relativePath = relative9(baseDir, fullPath);
|
|
12564
12823
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12565
12824
|
continue;
|
|
@@ -12607,7 +12866,7 @@ sessions2.get(
|
|
|
12607
12866
|
return c.json({ error: "Session not found" }, 404);
|
|
12608
12867
|
}
|
|
12609
12868
|
const workingDirectory = session.workingDirectory;
|
|
12610
|
-
if (!
|
|
12869
|
+
if (!existsSync19(workingDirectory)) {
|
|
12611
12870
|
return c.json({
|
|
12612
12871
|
sessionId,
|
|
12613
12872
|
workingDirectory,
|
|
@@ -12715,14 +12974,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
|
|
|
12715
12974
|
|
|
12716
12975
|
// src/server/routes/agents.ts
|
|
12717
12976
|
init_db();
|
|
12718
|
-
init_agent();
|
|
12719
|
-
init_session_lock();
|
|
12720
|
-
init_config();
|
|
12721
12977
|
import { Hono as Hono2 } from "hono";
|
|
12722
12978
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12723
12979
|
import { z as z17 } from "zod";
|
|
12724
|
-
import { existsSync as
|
|
12725
|
-
import { join as
|
|
12980
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
12981
|
+
import { join as join14 } from "path";
|
|
12982
|
+
|
|
12983
|
+
// src/agent/missing-tool-recovery.ts
|
|
12984
|
+
init_db();
|
|
12985
|
+
function extractMissingToolCallIds(error) {
|
|
12986
|
+
if (!error) return [];
|
|
12987
|
+
const e = error;
|
|
12988
|
+
if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
|
|
12989
|
+
return e.toolCallIds;
|
|
12990
|
+
}
|
|
12991
|
+
const msg = typeof e.message === "string" ? e.message : "";
|
|
12992
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12993
|
+
for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
|
|
12994
|
+
for (const id of m[1].split(/\s*,\s*/)) {
|
|
12995
|
+
const trimmed = id.trim();
|
|
12996
|
+
if (trimmed) ids.add(trimmed);
|
|
12997
|
+
}
|
|
12998
|
+
}
|
|
12999
|
+
return [...ids];
|
|
13000
|
+
}
|
|
13001
|
+
function isMissingToolResultsError(error) {
|
|
13002
|
+
if (!error) return false;
|
|
13003
|
+
const e = error;
|
|
13004
|
+
if (e.name === "AI_MissingToolResultsError") return true;
|
|
13005
|
+
if (Array.isArray(e.toolCallIds)) return true;
|
|
13006
|
+
return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
|
|
13007
|
+
}
|
|
13008
|
+
async function recoverFromMissingToolResults(sessionId, error) {
|
|
13009
|
+
const toolCallIds = extractMissingToolCallIds(error);
|
|
13010
|
+
if (toolCallIds.length === 0) return null;
|
|
13011
|
+
let history = [];
|
|
13012
|
+
try {
|
|
13013
|
+
history = await messageQueries.getModelMessages(sessionId);
|
|
13014
|
+
} catch (err) {
|
|
13015
|
+
console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
|
|
13016
|
+
}
|
|
13017
|
+
const toolNameByCallId = /* @__PURE__ */ new Map();
|
|
13018
|
+
const existingResultIds = /* @__PURE__ */ new Set();
|
|
13019
|
+
for (const msg of history) {
|
|
13020
|
+
if (!Array.isArray(msg.content)) continue;
|
|
13021
|
+
for (const part of msg.content) {
|
|
13022
|
+
if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
|
|
13023
|
+
if (typeof part.toolName === "string") toolNameByCallId.set(part.toolCallId, part.toolName);
|
|
13024
|
+
}
|
|
13025
|
+
if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
|
|
13026
|
+
existingResultIds.add(part.toolCallId);
|
|
13027
|
+
}
|
|
13028
|
+
}
|
|
13029
|
+
}
|
|
13030
|
+
const resolved = toolCallIds.map((id) => ({
|
|
13031
|
+
toolCallId: id,
|
|
13032
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
13033
|
+
foundInAssistantMessage: toolNameByCallId.has(id)
|
|
13034
|
+
}));
|
|
13035
|
+
const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
|
|
13036
|
+
let syntheticToolMessageSaved = false;
|
|
13037
|
+
if (stillOrphaned.length > 0) {
|
|
13038
|
+
const syntheticParts = stillOrphaned.map((id) => ({
|
|
13039
|
+
type: "tool-result",
|
|
13040
|
+
toolCallId: id,
|
|
13041
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
13042
|
+
output: {
|
|
13043
|
+
type: "text",
|
|
13044
|
+
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.]"
|
|
13045
|
+
}
|
|
13046
|
+
}));
|
|
13047
|
+
try {
|
|
13048
|
+
await messageQueries.create(sessionId, {
|
|
13049
|
+
role: "tool",
|
|
13050
|
+
content: syntheticParts
|
|
13051
|
+
});
|
|
13052
|
+
syntheticToolMessageSaved = true;
|
|
13053
|
+
} catch (err) {
|
|
13054
|
+
console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
|
|
13055
|
+
}
|
|
13056
|
+
}
|
|
13057
|
+
const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
|
|
13058
|
+
return {
|
|
13059
|
+
kind: "missing_tool_results",
|
|
13060
|
+
toolCallIds,
|
|
13061
|
+
resolved,
|
|
13062
|
+
syntheticToolMessageSaved,
|
|
13063
|
+
lastFewMessageRoles,
|
|
13064
|
+
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."
|
|
13065
|
+
};
|
|
13066
|
+
}
|
|
13067
|
+
|
|
13068
|
+
// src/server/routes/agents.ts
|
|
13069
|
+
init_agent();
|
|
13070
|
+
init_session_lock();
|
|
13071
|
+
init_config();
|
|
12726
13072
|
|
|
12727
13073
|
// src/server/resumable-stream.ts
|
|
12728
13074
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -12878,7 +13224,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
|
|
|
12878
13224
|
toolCallId,
|
|
12879
13225
|
argsTextDelta: chunk
|
|
12880
13226
|
}));
|
|
12881
|
-
await new Promise((
|
|
13227
|
+
await new Promise((resolve14) => setTimeout(resolve14, 0));
|
|
12882
13228
|
}
|
|
12883
13229
|
}
|
|
12884
13230
|
function buildDevtoolsContextXml(sessionId) {
|
|
@@ -12929,12 +13275,12 @@ var rejectSchema = z17.object({
|
|
|
12929
13275
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12930
13276
|
function getAttachmentsDirectory(sessionId) {
|
|
12931
13277
|
const appDataDir = getAppDataDirectory();
|
|
12932
|
-
return
|
|
13278
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
12933
13279
|
}
|
|
12934
13280
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12935
13281
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
12936
|
-
if (!
|
|
12937
|
-
|
|
13282
|
+
if (!existsSync20(attachmentsDir)) {
|
|
13283
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
12938
13284
|
}
|
|
12939
13285
|
let filename = attachment.filename;
|
|
12940
13286
|
if (!filename) {
|
|
@@ -12952,8 +13298,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12952
13298
|
attachment.mediaType = resized.mediaType;
|
|
12953
13299
|
attachment.data = buffer.toString("base64");
|
|
12954
13300
|
}
|
|
12955
|
-
const filePath =
|
|
12956
|
-
|
|
13301
|
+
const filePath = join14(attachmentsDir, filename);
|
|
13302
|
+
writeFileSync6(filePath, buffer);
|
|
12957
13303
|
return filePath;
|
|
12958
13304
|
}
|
|
12959
13305
|
function stripDataUrlPrefix2(data) {
|
|
@@ -13133,7 +13479,7 @@ ${prompt}` });
|
|
|
13133
13479
|
chunkIndex,
|
|
13134
13480
|
chunkCount
|
|
13135
13481
|
}));
|
|
13136
|
-
await new Promise((
|
|
13482
|
+
await new Promise((resolve14) => setTimeout(resolve14, 0));
|
|
13137
13483
|
}
|
|
13138
13484
|
const browserPort = progress.data?.browserStreamPort;
|
|
13139
13485
|
const browserClosed = progress.data?.browserClosed;
|
|
@@ -13290,7 +13636,20 @@ ${prompt}` });
|
|
|
13290
13636
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
13291
13637
|
} else {
|
|
13292
13638
|
console.error("Agent error:", error);
|
|
13293
|
-
|
|
13639
|
+
let debugPayload = void 0;
|
|
13640
|
+
if (isMissingToolResultsError(error)) {
|
|
13641
|
+
try {
|
|
13642
|
+
const recovery = await recoverFromMissingToolResults(sessionId, error);
|
|
13643
|
+
if (recovery) debugPayload = recovery;
|
|
13644
|
+
} catch (recErr) {
|
|
13645
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
13646
|
+
}
|
|
13647
|
+
}
|
|
13648
|
+
await writeSSE(JSON.stringify({
|
|
13649
|
+
type: "error",
|
|
13650
|
+
errorText: error.message,
|
|
13651
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
13652
|
+
}));
|
|
13294
13653
|
try {
|
|
13295
13654
|
await activeStreamQueries.markError(streamId);
|
|
13296
13655
|
} catch {
|
|
@@ -13680,7 +14039,7 @@ agents.post(
|
|
|
13680
14039
|
chunkIndex,
|
|
13681
14040
|
chunkCount
|
|
13682
14041
|
}));
|
|
13683
|
-
await new Promise((
|
|
14042
|
+
await new Promise((resolve14) => setTimeout(resolve14, 0));
|
|
13684
14043
|
}
|
|
13685
14044
|
const browserPort = progress.data?.browserStreamPort;
|
|
13686
14045
|
const browserClosed = progress.data?.browserClosed;
|
|
@@ -13830,7 +14189,20 @@ agents.post(
|
|
|
13830
14189
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
13831
14190
|
} else {
|
|
13832
14191
|
console.error("Agent error:", error);
|
|
13833
|
-
|
|
14192
|
+
let debugPayload = void 0;
|
|
14193
|
+
if (isMissingToolResultsError(error)) {
|
|
14194
|
+
try {
|
|
14195
|
+
const recovery = await recoverFromMissingToolResults(session.id, error);
|
|
14196
|
+
if (recovery) debugPayload = recovery;
|
|
14197
|
+
} catch (recErr) {
|
|
14198
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
14199
|
+
}
|
|
14200
|
+
}
|
|
14201
|
+
await writeSSE(JSON.stringify({
|
|
14202
|
+
type: "error",
|
|
14203
|
+
errorText: error.message,
|
|
14204
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
14205
|
+
}));
|
|
13834
14206
|
await activeStreamQueries.markError(streamId);
|
|
13835
14207
|
}
|
|
13836
14208
|
} finally {
|
|
@@ -13913,26 +14285,26 @@ init_config();
|
|
|
13913
14285
|
import { Hono as Hono3 } from "hono";
|
|
13914
14286
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
13915
14287
|
import { z as z18 } from "zod";
|
|
13916
|
-
import { readFileSync as
|
|
14288
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
13917
14289
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13918
|
-
import { dirname as
|
|
14290
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
13919
14291
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13920
|
-
var __dirname =
|
|
14292
|
+
var __dirname = dirname8(__filename);
|
|
13921
14293
|
var possiblePaths = [
|
|
13922
|
-
|
|
14294
|
+
join15(__dirname, "../package.json"),
|
|
13923
14295
|
// From dist/server -> dist/../package.json
|
|
13924
|
-
|
|
14296
|
+
join15(__dirname, "../../package.json"),
|
|
13925
14297
|
// From dist/server (if nested differently)
|
|
13926
|
-
|
|
14298
|
+
join15(__dirname, "../../../package.json"),
|
|
13927
14299
|
// From src/server/routes (development)
|
|
13928
|
-
|
|
14300
|
+
join15(process.cwd(), "package.json")
|
|
13929
14301
|
// From current working directory
|
|
13930
14302
|
];
|
|
13931
14303
|
var currentVersion = "0.0.0";
|
|
13932
14304
|
var packageName = "sparkecoder";
|
|
13933
14305
|
for (const packageJsonPath of possiblePaths) {
|
|
13934
14306
|
try {
|
|
13935
|
-
const packageJson = JSON.parse(
|
|
14307
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
13936
14308
|
if (packageJson.name === "sparkecoder") {
|
|
13937
14309
|
currentVersion = packageJson.version || "0.0.0";
|
|
13938
14310
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -14750,12 +15122,13 @@ slack.post("/events", async (c) => {
|
|
|
14750
15122
|
if (inbound) {
|
|
14751
15123
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
14752
15124
|
if (isThreadReply) {
|
|
14753
|
-
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await
|
|
15125
|
+
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
|
|
14754
15126
|
if (!ours) {
|
|
14755
15127
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
14756
15128
|
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
14757
15129
|
return c.json({ ok: true });
|
|
14758
15130
|
}
|
|
15131
|
+
markThreadOwned(ev.channel, ev.thread_ts);
|
|
14759
15132
|
}
|
|
14760
15133
|
if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
|
|
14761
15134
|
markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
|
|
@@ -14794,18 +15167,6 @@ slack.post("/events", async (c) => {
|
|
|
14794
15167
|
}
|
|
14795
15168
|
return c.json({ ok: true });
|
|
14796
15169
|
});
|
|
14797
|
-
async function threadBelongsToUs(channel, threadTs) {
|
|
14798
|
-
try {
|
|
14799
|
-
const sessions3 = await sessionQueries.list(500, 0);
|
|
14800
|
-
return sessions3.some((s) => {
|
|
14801
|
-
const slack2 = s.config?.slack;
|
|
14802
|
-
return slack2?.channel === channel && slack2?.threadTs === threadTs;
|
|
14803
|
-
});
|
|
14804
|
-
} catch (err) {
|
|
14805
|
-
console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
|
|
14806
|
-
return false;
|
|
14807
|
-
}
|
|
14808
|
-
}
|
|
14809
15170
|
async function findOrCreateOrchestratorId() {
|
|
14810
15171
|
try {
|
|
14811
15172
|
const all = await sessionQueries.list(500, 0);
|
|
@@ -15188,6 +15549,224 @@ mcpRouter.post("/:id/test", async (c) => {
|
|
|
15188
15549
|
return c.json(result);
|
|
15189
15550
|
});
|
|
15190
15551
|
|
|
15552
|
+
// src/server/routes/skills.ts
|
|
15553
|
+
init_config();
|
|
15554
|
+
init_skills();
|
|
15555
|
+
import { Hono as Hono9 } from "hono";
|
|
15556
|
+
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
15557
|
+
import { z as z22 } from "zod";
|
|
15558
|
+
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
15559
|
+
import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
15560
|
+
import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
15561
|
+
var skills = new Hono9();
|
|
15562
|
+
function encodeId(filePath) {
|
|
15563
|
+
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
15564
|
+
}
|
|
15565
|
+
function decodeId(id) {
|
|
15566
|
+
try {
|
|
15567
|
+
const decoded = Buffer.from(id, "base64url").toString("utf-8");
|
|
15568
|
+
return decoded || null;
|
|
15569
|
+
} catch {
|
|
15570
|
+
return null;
|
|
15571
|
+
}
|
|
15572
|
+
}
|
|
15573
|
+
function listAllDirectories() {
|
|
15574
|
+
const cfg = getConfig();
|
|
15575
|
+
const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
|
|
15576
|
+
const out = [];
|
|
15577
|
+
for (const { path, priority } of discovered.alwaysLoadedDirs) {
|
|
15578
|
+
out.push({
|
|
15579
|
+
path,
|
|
15580
|
+
source: pathToSource(path),
|
|
15581
|
+
label: pathToLabel(path),
|
|
15582
|
+
priority,
|
|
15583
|
+
alwaysApply: true
|
|
15584
|
+
});
|
|
15585
|
+
}
|
|
15586
|
+
for (const { path, priority } of discovered.onDemandDirs) {
|
|
15587
|
+
out.push({
|
|
15588
|
+
path,
|
|
15589
|
+
source: pathToSource(path),
|
|
15590
|
+
label: pathToLabel(path),
|
|
15591
|
+
priority,
|
|
15592
|
+
alwaysApply: false
|
|
15593
|
+
});
|
|
15594
|
+
}
|
|
15595
|
+
const additional = cfg.skills?.additionalDirectories || [];
|
|
15596
|
+
for (const dir of additional) {
|
|
15597
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
|
|
15598
|
+
if (out.some((d) => d.path === abs)) continue;
|
|
15599
|
+
out.push({
|
|
15600
|
+
path: abs,
|
|
15601
|
+
source: "additional",
|
|
15602
|
+
label: pathToLabel(abs),
|
|
15603
|
+
priority: 50,
|
|
15604
|
+
alwaysApply: false
|
|
15605
|
+
});
|
|
15606
|
+
}
|
|
15607
|
+
return out;
|
|
15608
|
+
}
|
|
15609
|
+
function pathToSource(path) {
|
|
15610
|
+
if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
|
|
15611
|
+
return "project";
|
|
15612
|
+
}
|
|
15613
|
+
function pathToLabel(path) {
|
|
15614
|
+
if (path.includes("/skills/default")) return "Built-in (default)";
|
|
15615
|
+
if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
|
|
15616
|
+
if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
|
|
15617
|
+
if (path.includes("/.cursor/rules")) return ".cursor/rules";
|
|
15618
|
+
if (path.includes("/.claude/skills")) return ".claude/skills";
|
|
15619
|
+
return basename6(dirname9(path)) + "/" + basename6(path);
|
|
15620
|
+
}
|
|
15621
|
+
skills.get("/", async (c) => {
|
|
15622
|
+
const dirs = listAllDirectories();
|
|
15623
|
+
const allSkills = [];
|
|
15624
|
+
for (const dir of dirs) {
|
|
15625
|
+
if (!existsSync21(dir.path)) continue;
|
|
15626
|
+
try {
|
|
15627
|
+
const list = await loadSkillsFromDirectory(dir.path, {
|
|
15628
|
+
priority: dir.priority,
|
|
15629
|
+
defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
|
|
15630
|
+
forceAlwaysApply: dir.alwaysApply
|
|
15631
|
+
});
|
|
15632
|
+
for (const s of list) {
|
|
15633
|
+
let size = 0;
|
|
15634
|
+
try {
|
|
15635
|
+
size = statSync3(s.filePath).size;
|
|
15636
|
+
} catch {
|
|
15637
|
+
}
|
|
15638
|
+
allSkills.push({
|
|
15639
|
+
id: encodeId(s.filePath),
|
|
15640
|
+
name: s.name,
|
|
15641
|
+
description: s.description,
|
|
15642
|
+
filePath: s.filePath,
|
|
15643
|
+
fileName: basename6(s.filePath),
|
|
15644
|
+
sourceDir: dir.path,
|
|
15645
|
+
sourceLabel: dir.label,
|
|
15646
|
+
sourceType: dir.source,
|
|
15647
|
+
alwaysApply: s.alwaysApply,
|
|
15648
|
+
globs: s.globs,
|
|
15649
|
+
sizeBytes: size
|
|
15650
|
+
});
|
|
15651
|
+
}
|
|
15652
|
+
} catch (err) {
|
|
15653
|
+
console.warn("[skills] failed to read", dir.path, err?.message || err);
|
|
15654
|
+
}
|
|
15655
|
+
}
|
|
15656
|
+
return c.json({
|
|
15657
|
+
directories: dirs.map((d) => ({
|
|
15658
|
+
path: d.path,
|
|
15659
|
+
label: d.label,
|
|
15660
|
+
source: d.source,
|
|
15661
|
+
alwaysApply: d.alwaysApply,
|
|
15662
|
+
exists: existsSync21(d.path),
|
|
15663
|
+
writable: isWritable(d.path)
|
|
15664
|
+
})),
|
|
15665
|
+
skills: allSkills
|
|
15666
|
+
});
|
|
15667
|
+
});
|
|
15668
|
+
function isWritable(dir) {
|
|
15669
|
+
try {
|
|
15670
|
+
if (!existsSync21(dir)) return false;
|
|
15671
|
+
if (dir.includes("/skills/default")) return false;
|
|
15672
|
+
return true;
|
|
15673
|
+
} catch {
|
|
15674
|
+
return false;
|
|
15675
|
+
}
|
|
15676
|
+
}
|
|
15677
|
+
skills.get("/:id", async (c) => {
|
|
15678
|
+
const filePath = decodeId(c.req.param("id"));
|
|
15679
|
+
if (!filePath || !existsSync21(filePath)) {
|
|
15680
|
+
return c.json({ error: "skill not found" }, 404);
|
|
15681
|
+
}
|
|
15682
|
+
const content = await readFile12(filePath, "utf-8");
|
|
15683
|
+
return c.json({
|
|
15684
|
+
id: c.req.param("id"),
|
|
15685
|
+
filePath,
|
|
15686
|
+
fileName: basename6(filePath),
|
|
15687
|
+
content,
|
|
15688
|
+
writable: !filePath.includes("/skills/default")
|
|
15689
|
+
});
|
|
15690
|
+
});
|
|
15691
|
+
skills.post(
|
|
15692
|
+
"/",
|
|
15693
|
+
zValidator7("json", z22.object({
|
|
15694
|
+
directory: z22.string().min(1),
|
|
15695
|
+
fileName: z22.string().min(1),
|
|
15696
|
+
content: z22.string()
|
|
15697
|
+
})),
|
|
15698
|
+
async (c) => {
|
|
15699
|
+
const { directory, fileName, content } = c.req.valid("json");
|
|
15700
|
+
const cfg = getConfig();
|
|
15701
|
+
const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
|
|
15702
|
+
if (targetDir.includes("/skills/default")) {
|
|
15703
|
+
return c.json({ error: "cannot write into built-in skills directory" }, 400);
|
|
15704
|
+
}
|
|
15705
|
+
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
15706
|
+
const ext = extname9(safeName).toLowerCase();
|
|
15707
|
+
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
15708
|
+
const filePath = join16(targetDir, finalName);
|
|
15709
|
+
if (existsSync21(filePath)) {
|
|
15710
|
+
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
15711
|
+
}
|
|
15712
|
+
try {
|
|
15713
|
+
await mkdir5(targetDir, { recursive: true });
|
|
15714
|
+
await writeFile6(filePath, content, "utf-8");
|
|
15715
|
+
} catch (err) {
|
|
15716
|
+
return c.json({ error: err?.message || "write failed" }, 500);
|
|
15717
|
+
}
|
|
15718
|
+
return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
|
|
15719
|
+
}
|
|
15720
|
+
);
|
|
15721
|
+
skills.put(
|
|
15722
|
+
"/:id",
|
|
15723
|
+
zValidator7("json", z22.object({ content: z22.string() })),
|
|
15724
|
+
async (c) => {
|
|
15725
|
+
const filePath = decodeId(c.req.param("id"));
|
|
15726
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
15727
|
+
if (filePath.includes("/skills/default")) {
|
|
15728
|
+
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15729
|
+
}
|
|
15730
|
+
await writeFile6(filePath, c.req.valid("json").content, "utf-8");
|
|
15731
|
+
return c.json({ ok: true });
|
|
15732
|
+
}
|
|
15733
|
+
);
|
|
15734
|
+
skills.delete("/:id", async (c) => {
|
|
15735
|
+
const filePath = decodeId(c.req.param("id"));
|
|
15736
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
15737
|
+
if (filePath.includes("/skills/default")) {
|
|
15738
|
+
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15739
|
+
}
|
|
15740
|
+
await unlink3(filePath);
|
|
15741
|
+
return c.json({ ok: true });
|
|
15742
|
+
});
|
|
15743
|
+
skills.post(
|
|
15744
|
+
"/directories",
|
|
15745
|
+
zValidator7("json", z22.object({ path: z22.string().min(1) })),
|
|
15746
|
+
(c) => {
|
|
15747
|
+
const cfg = getConfig();
|
|
15748
|
+
const inputPath = c.req.valid("json").path.trim();
|
|
15749
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
|
|
15750
|
+
const current = cfg.skills?.additionalDirectories || [];
|
|
15751
|
+
if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
|
|
15752
|
+
return c.json({ error: "directory already added" }, 409);
|
|
15753
|
+
}
|
|
15754
|
+
const next = [...current, abs];
|
|
15755
|
+
setSkillsAdditionalDirectories(next);
|
|
15756
|
+
return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
|
|
15757
|
+
}
|
|
15758
|
+
);
|
|
15759
|
+
skills.delete("/directories", (c) => {
|
|
15760
|
+
const inputPath = c.req.query("path");
|
|
15761
|
+
if (!inputPath) return c.json({ error: "path query param required" }, 400);
|
|
15762
|
+
const cfg = getConfig();
|
|
15763
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
|
|
15764
|
+
const current = cfg.skills?.additionalDirectories || [];
|
|
15765
|
+
const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
|
|
15766
|
+
setSkillsAdditionalDirectories(next);
|
|
15767
|
+
return c.json({ ok: true });
|
|
15768
|
+
});
|
|
15769
|
+
|
|
15191
15770
|
// src/server/auth/cf-access.ts
|
|
15192
15771
|
init_config();
|
|
15193
15772
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
@@ -15444,13 +16023,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
15444
16023
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
15445
16024
|
function getWebDirectory() {
|
|
15446
16025
|
try {
|
|
15447
|
-
const currentDir =
|
|
15448
|
-
const webDir =
|
|
15449
|
-
if (
|
|
16026
|
+
const currentDir = dirname10(fileURLToPath4(import.meta.url));
|
|
16027
|
+
const webDir = resolve12(currentDir, "..", "web");
|
|
16028
|
+
if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
|
|
15450
16029
|
return webDir;
|
|
15451
16030
|
}
|
|
15452
|
-
const altWebDir =
|
|
15453
|
-
if (
|
|
16031
|
+
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
16032
|
+
if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
|
|
15454
16033
|
return altWebDir;
|
|
15455
16034
|
}
|
|
15456
16035
|
return null;
|
|
@@ -15473,18 +16052,18 @@ async function isSparkcoderWebRunning(port) {
|
|
|
15473
16052
|
}
|
|
15474
16053
|
}
|
|
15475
16054
|
function isPortInUse(port) {
|
|
15476
|
-
return new Promise((
|
|
16055
|
+
return new Promise((resolve14) => {
|
|
15477
16056
|
const server = createNetServer();
|
|
15478
16057
|
server.once("error", (err) => {
|
|
15479
16058
|
if (err.code === "EADDRINUSE") {
|
|
15480
|
-
|
|
16059
|
+
resolve14(true);
|
|
15481
16060
|
} else {
|
|
15482
|
-
|
|
16061
|
+
resolve14(false);
|
|
15483
16062
|
}
|
|
15484
16063
|
});
|
|
15485
16064
|
server.once("listening", () => {
|
|
15486
16065
|
server.close();
|
|
15487
|
-
|
|
16066
|
+
resolve14(false);
|
|
15488
16067
|
});
|
|
15489
16068
|
server.listen(port, "0.0.0.0");
|
|
15490
16069
|
});
|
|
@@ -15508,30 +16087,30 @@ async function findWebPort(preferredPort) {
|
|
|
15508
16087
|
return { port: preferredPort, alreadyRunning: false };
|
|
15509
16088
|
}
|
|
15510
16089
|
function hasProductionBuild(webDir) {
|
|
15511
|
-
const buildIdPath =
|
|
15512
|
-
return
|
|
16090
|
+
const buildIdPath = join17(webDir, ".next", "BUILD_ID");
|
|
16091
|
+
return existsSync22(buildIdPath);
|
|
15513
16092
|
}
|
|
15514
16093
|
function hasSourceFiles(webDir) {
|
|
15515
|
-
const appDir =
|
|
15516
|
-
const pagesDir =
|
|
15517
|
-
const rootAppDir =
|
|
15518
|
-
const rootPagesDir =
|
|
15519
|
-
return
|
|
16094
|
+
const appDir = join17(webDir, "src", "app");
|
|
16095
|
+
const pagesDir = join17(webDir, "src", "pages");
|
|
16096
|
+
const rootAppDir = join17(webDir, "app");
|
|
16097
|
+
const rootPagesDir = join17(webDir, "pages");
|
|
16098
|
+
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
15520
16099
|
}
|
|
15521
16100
|
function getStandaloneServerPath(webDir) {
|
|
15522
16101
|
const possiblePaths2 = [
|
|
15523
|
-
|
|
15524
|
-
|
|
16102
|
+
join17(webDir, ".next", "standalone", "server.js"),
|
|
16103
|
+
join17(webDir, ".next", "standalone", "web", "server.js")
|
|
15525
16104
|
];
|
|
15526
16105
|
for (const serverPath of possiblePaths2) {
|
|
15527
|
-
if (
|
|
16106
|
+
if (existsSync22(serverPath)) {
|
|
15528
16107
|
return serverPath;
|
|
15529
16108
|
}
|
|
15530
16109
|
}
|
|
15531
16110
|
return null;
|
|
15532
16111
|
}
|
|
15533
16112
|
function runCommand(command, args, cwd, env) {
|
|
15534
|
-
return new Promise((
|
|
16113
|
+
return new Promise((resolve14) => {
|
|
15535
16114
|
const child = spawn2(command, args, {
|
|
15536
16115
|
cwd,
|
|
15537
16116
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -15546,10 +16125,10 @@ function runCommand(command, args, cwd, env) {
|
|
|
15546
16125
|
output += data.toString();
|
|
15547
16126
|
});
|
|
15548
16127
|
child.on("close", (code) => {
|
|
15549
|
-
|
|
16128
|
+
resolve14({ success: code === 0, output });
|
|
15550
16129
|
});
|
|
15551
16130
|
child.on("error", (err) => {
|
|
15552
|
-
|
|
16131
|
+
resolve14({ success: false, output: err.message });
|
|
15553
16132
|
});
|
|
15554
16133
|
});
|
|
15555
16134
|
}
|
|
@@ -15564,15 +16143,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15564
16143
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15565
16144
|
return { process: null, port: actualPort };
|
|
15566
16145
|
}
|
|
15567
|
-
const usePnpm =
|
|
15568
|
-
const useNpm = !usePnpm &&
|
|
16146
|
+
const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
|
|
16147
|
+
const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
|
|
15569
16148
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15570
16149
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15571
16150
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15572
16151
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15573
|
-
const runtimeConfigPath =
|
|
16152
|
+
const runtimeConfigPath = join17(webDir, "runtime-config.json");
|
|
15574
16153
|
try {
|
|
15575
|
-
|
|
16154
|
+
writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15576
16155
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
15577
16156
|
} catch (err) {
|
|
15578
16157
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -15592,7 +16171,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15592
16171
|
if (standaloneServerPath) {
|
|
15593
16172
|
command = "node";
|
|
15594
16173
|
args = ["server.js"];
|
|
15595
|
-
cwd =
|
|
16174
|
+
cwd = dirname10(standaloneServerPath);
|
|
15596
16175
|
webEnv.PORT = String(actualPort);
|
|
15597
16176
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
15598
16177
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -15633,10 +16212,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15633
16212
|
let started = false;
|
|
15634
16213
|
let exited = false;
|
|
15635
16214
|
let exitCode = null;
|
|
15636
|
-
const startedPromise = new Promise((
|
|
16215
|
+
const startedPromise = new Promise((resolve14) => {
|
|
15637
16216
|
const timeout = setTimeout(() => {
|
|
15638
16217
|
if (!started && !exited) {
|
|
15639
|
-
|
|
16218
|
+
resolve14(false);
|
|
15640
16219
|
}
|
|
15641
16220
|
}, startupTimeout);
|
|
15642
16221
|
child.stdout?.on("data", (data) => {
|
|
@@ -15650,7 +16229,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15650
16229
|
if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
15651
16230
|
started = true;
|
|
15652
16231
|
clearTimeout(timeout);
|
|
15653
|
-
|
|
16232
|
+
resolve14(true);
|
|
15654
16233
|
}
|
|
15655
16234
|
});
|
|
15656
16235
|
child.stderr?.on("data", (data) => {
|
|
@@ -15662,14 +16241,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15662
16241
|
child.on("error", (err) => {
|
|
15663
16242
|
if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
|
|
15664
16243
|
clearTimeout(timeout);
|
|
15665
|
-
|
|
16244
|
+
resolve14(false);
|
|
15666
16245
|
});
|
|
15667
16246
|
child.on("exit", (code) => {
|
|
15668
16247
|
exited = true;
|
|
15669
16248
|
exitCode = code;
|
|
15670
16249
|
if (!started) {
|
|
15671
16250
|
clearTimeout(timeout);
|
|
15672
|
-
|
|
16251
|
+
resolve14(false);
|
|
15673
16252
|
}
|
|
15674
16253
|
webUIProcess = null;
|
|
15675
16254
|
});
|
|
@@ -15692,7 +16271,7 @@ function stopWebUI() {
|
|
|
15692
16271
|
}
|
|
15693
16272
|
}
|
|
15694
16273
|
async function createApp(options = {}) {
|
|
15695
|
-
const app = new
|
|
16274
|
+
const app = new Hono10();
|
|
15696
16275
|
app.use("*", cors({
|
|
15697
16276
|
origin: "*",
|
|
15698
16277
|
// Allow all origins
|
|
@@ -15720,6 +16299,7 @@ async function createApp(options = {}) {
|
|
|
15720
16299
|
app.route("/api/schedules", schedulesRouter);
|
|
15721
16300
|
app.route("/api/orchestrator", orchestratorRouter);
|
|
15722
16301
|
app.route("/api/mcp", mcpRouter);
|
|
16302
|
+
app.route("/api/skills", skills);
|
|
15723
16303
|
app.route("/api/webhooks", webhooksRouter);
|
|
15724
16304
|
const config = getConfig();
|
|
15725
16305
|
const webhookToken = config?.webhooks?.token;
|
|
@@ -15785,8 +16365,8 @@ async function startServer(options = {}) {
|
|
|
15785
16365
|
if (options.workingDirectory) {
|
|
15786
16366
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
15787
16367
|
}
|
|
15788
|
-
if (!
|
|
15789
|
-
|
|
16368
|
+
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
16369
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
15790
16370
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
15791
16371
|
}
|
|
15792
16372
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -16344,18 +16924,18 @@ function generateOpenAPISpec() {
|
|
|
16344
16924
|
init_config();
|
|
16345
16925
|
init_semantic();
|
|
16346
16926
|
init_db();
|
|
16347
|
-
import { mkdirSync as
|
|
16348
|
-
import { resolve as
|
|
16927
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, readFileSync as readFileSync11, existsSync as existsSync23, statSync as statSync4 } from "fs";
|
|
16928
|
+
import { resolve as resolve13, join as join18 } from "path";
|
|
16349
16929
|
function getCliVersion() {
|
|
16350
|
-
const here =
|
|
16930
|
+
const here = dirname11(fileURLToPath5(import.meta.url));
|
|
16351
16931
|
const candidates = [
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16932
|
+
join18(here, "..", "package.json"),
|
|
16933
|
+
join18(here, "..", "..", "package.json"),
|
|
16934
|
+
join18(process.cwd(), "package.json")
|
|
16355
16935
|
];
|
|
16356
16936
|
for (const p of candidates) {
|
|
16357
16937
|
try {
|
|
16358
|
-
const pkg = JSON.parse(
|
|
16938
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
16359
16939
|
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
16360
16940
|
} catch {
|
|
16361
16941
|
}
|
|
@@ -16396,18 +16976,18 @@ async function getActiveStream(baseUrl, sessionId) {
|
|
|
16396
16976
|
return { hasActiveStream: false };
|
|
16397
16977
|
}
|
|
16398
16978
|
function promptApproval(rl, toolName, input) {
|
|
16399
|
-
return new Promise((
|
|
16979
|
+
return new Promise((resolve14) => {
|
|
16400
16980
|
const inputStr = JSON.stringify(input);
|
|
16401
16981
|
const truncatedInput = inputStr.length > 100 ? inputStr.slice(0, 100) + "..." : inputStr;
|
|
16402
16982
|
console.log(chalk.dim(` Command: ${truncatedInput}`));
|
|
16403
16983
|
rl.question(chalk.yellow(` Approve? [y/n/a(lways)]: `), (answer) => {
|
|
16404
16984
|
const lower = answer.toLowerCase().trim();
|
|
16405
16985
|
if (lower === "a" || lower === "always") {
|
|
16406
|
-
|
|
16986
|
+
resolve14("always");
|
|
16407
16987
|
} else if (lower.startsWith("y")) {
|
|
16408
|
-
|
|
16988
|
+
resolve14("approve");
|
|
16409
16989
|
} else {
|
|
16410
|
-
|
|
16990
|
+
resolve14("reject");
|
|
16411
16991
|
}
|
|
16412
16992
|
});
|
|
16413
16993
|
});
|
|
@@ -16604,9 +17184,9 @@ async function runChat(options) {
|
|
|
16604
17184
|
input: process.stdin,
|
|
16605
17185
|
output: process.stdout
|
|
16606
17186
|
});
|
|
16607
|
-
const apiKey = await new Promise((
|
|
17187
|
+
const apiKey = await new Promise((resolve14) => {
|
|
16608
17188
|
keyRl.question(chalk.cyan("Enter your AI Gateway API key: "), (answer) => {
|
|
16609
|
-
|
|
17189
|
+
resolve14(answer.trim());
|
|
16610
17190
|
});
|
|
16611
17191
|
});
|
|
16612
17192
|
keyRl.close();
|
|
@@ -17002,8 +17582,8 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17002
17582
|
let outputSchema;
|
|
17003
17583
|
try {
|
|
17004
17584
|
const schemaStr = options.schema;
|
|
17005
|
-
if (
|
|
17006
|
-
outputSchema = JSON.parse(
|
|
17585
|
+
if (existsSync23(schemaStr)) {
|
|
17586
|
+
outputSchema = JSON.parse(readFileSync11(schemaStr, "utf-8"));
|
|
17007
17587
|
} else {
|
|
17008
17588
|
outputSchema = JSON.parse(schemaStr);
|
|
17009
17589
|
}
|
|
@@ -17070,28 +17650,28 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
17070
17650
|
let configLocation;
|
|
17071
17651
|
if (options.global) {
|
|
17072
17652
|
const appDataDir = ensureAppDataDirectory();
|
|
17073
|
-
configPath =
|
|
17653
|
+
configPath = join18(appDataDir, "sparkecoder.config.json");
|
|
17074
17654
|
configLocation = "global";
|
|
17075
17655
|
} else {
|
|
17076
|
-
configPath =
|
|
17656
|
+
configPath = resolve13(process.cwd(), "sparkecoder.config.json");
|
|
17077
17657
|
configLocation = "local";
|
|
17078
17658
|
}
|
|
17079
|
-
if (
|
|
17659
|
+
if (existsSync23(configPath) && !options.force) {
|
|
17080
17660
|
console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
|
|
17081
17661
|
console.log(chalk.dim(` ${configPath}`));
|
|
17082
17662
|
return;
|
|
17083
17663
|
}
|
|
17084
17664
|
const config = createDefaultConfig();
|
|
17085
|
-
|
|
17665
|
+
writeFileSync8(configPath, JSON.stringify(config, null, 2));
|
|
17086
17666
|
console.log(chalk.green(`\u2713 Created ${configLocation} config`));
|
|
17087
17667
|
console.log(chalk.dim(` ${configPath}`));
|
|
17088
17668
|
console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
|
|
17089
17669
|
});
|
|
17090
17670
|
program.command("slack-setup").description("Interactively configure Slack integration (bot token + signing secret)").option("--bot-token <token>", "Slack bot token (xoxb-...)").option("--signing-secret <secret>", "Slack app signing secret").option("-g, --global", "Write to global config (~/.sparkecoder/sparkecoder.config.json)").action(async (options) => {
|
|
17091
17671
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17092
|
-
const ask = (label, fallback = "") => new Promise((
|
|
17672
|
+
const ask = (label, fallback = "") => new Promise((resolve14) => {
|
|
17093
17673
|
const suffix = fallback ? ` (${fallback})` : "";
|
|
17094
|
-
rl.question(`${label}${suffix}: `, (answer) =>
|
|
17674
|
+
rl.question(`${label}${suffix}: `, (answer) => resolve14(answer.trim() || fallback));
|
|
17095
17675
|
});
|
|
17096
17676
|
try {
|
|
17097
17677
|
const botToken = options.botToken || await ask("Slack bot token (xoxb-...)");
|
|
@@ -17101,11 +17681,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17101
17681
|
console.error(chalk.red("Both bot token and signing secret are required."));
|
|
17102
17682
|
process.exit(1);
|
|
17103
17683
|
}
|
|
17104
|
-
const configPath = options.global ?
|
|
17684
|
+
const configPath = options.global ? join18(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve13(process.cwd(), "sparkecoder.config.json");
|
|
17105
17685
|
let existing = {};
|
|
17106
|
-
if (
|
|
17686
|
+
if (existsSync23(configPath)) {
|
|
17107
17687
|
try {
|
|
17108
|
-
existing = JSON.parse(
|
|
17688
|
+
existing = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
17109
17689
|
} catch {
|
|
17110
17690
|
}
|
|
17111
17691
|
} else {
|
|
@@ -17117,7 +17697,7 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17117
17697
|
signingSecret,
|
|
17118
17698
|
defaultOrchestratorName: existing.slack?.defaultOrchestratorName ?? "orchestrator"
|
|
17119
17699
|
};
|
|
17120
|
-
|
|
17700
|
+
writeFileSync8(configPath, JSON.stringify(existing, null, 2));
|
|
17121
17701
|
console.log(chalk.green(`
|
|
17122
17702
|
\u2713 Slack configured`));
|
|
17123
17703
|
console.log(chalk.dim(` ${configPath}`));
|
|
@@ -17369,9 +17949,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17369
17949
|
}
|
|
17370
17950
|
const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
|
|
17371
17951
|
console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
|
|
17372
|
-
const cfDir =
|
|
17373
|
-
const certPath =
|
|
17374
|
-
if (!
|
|
17952
|
+
const cfDir = join18(homedir2(), ".cloudflared");
|
|
17953
|
+
const certPath = join18(cfDir, "cert.pem");
|
|
17954
|
+
if (!existsSync23(certPath)) {
|
|
17375
17955
|
console.log(chalk.yellow("No Cloudflare login cert found."));
|
|
17376
17956
|
if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
|
|
17377
17957
|
run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
|
|
@@ -17415,8 +17995,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17415
17995
|
return;
|
|
17416
17996
|
}
|
|
17417
17997
|
}
|
|
17418
|
-
const credsFile = tunnel.credentials_file ||
|
|
17419
|
-
if (!
|
|
17998
|
+
const credsFile = tunnel.credentials_file || join18(cfDir, `${tunnel.id}.json`);
|
|
17999
|
+
if (!existsSync23(credsFile)) {
|
|
17420
18000
|
console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
|
|
17421
18001
|
}
|
|
17422
18002
|
let hostname = options.hostname;
|
|
@@ -17439,7 +18019,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17439
18019
|
console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
|
|
17440
18020
|
}
|
|
17441
18021
|
}
|
|
17442
|
-
const configPath =
|
|
18022
|
+
const configPath = join18(cfDir, "config.yml");
|
|
17443
18023
|
const configBody = `tunnel: ${tunnel.id}
|
|
17444
18024
|
credentials-file: ${credsFile}
|
|
17445
18025
|
ingress:
|
|
@@ -17450,14 +18030,14 @@ ingress:
|
|
|
17450
18030
|
- service: http_status:404
|
|
17451
18031
|
`;
|
|
17452
18032
|
let wroteConfig = false;
|
|
17453
|
-
if (
|
|
17454
|
-
const existing =
|
|
18033
|
+
if (existsSync23(configPath)) {
|
|
18034
|
+
const existing = readFileSync11(configPath, "utf8");
|
|
17455
18035
|
if (existing.trim() === configBody.trim()) {
|
|
17456
18036
|
console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
|
|
17457
18037
|
wroteConfig = true;
|
|
17458
18038
|
} else if (await confirm(`A different ${configPath} exists. Overwrite (a backup will be saved)?`, false)) {
|
|
17459
18039
|
copyFileSync(configPath, `${configPath}.bak.${Date.now()}`);
|
|
17460
|
-
|
|
18040
|
+
writeFileSync8(configPath, configBody);
|
|
17461
18041
|
console.log(chalk.green("\u2713"), `wrote ${configPath} (previous saved as .bak.*)`);
|
|
17462
18042
|
wroteConfig = true;
|
|
17463
18043
|
} else {
|
|
@@ -17465,7 +18045,7 @@ ingress:
|
|
|
17465
18045
|
console.log(chalk.cyan(configBody));
|
|
17466
18046
|
}
|
|
17467
18047
|
} else {
|
|
17468
|
-
|
|
18048
|
+
writeFileSync8(configPath, configBody);
|
|
17469
18049
|
console.log(chalk.green("\u2713"), `wrote ${configPath}`);
|
|
17470
18050
|
wroteConfig = true;
|
|
17471
18051
|
}
|
|
@@ -17566,7 +18146,7 @@ program.command("config").description("Show current configuration").option("-c,
|
|
|
17566
18146
|
});
|
|
17567
18147
|
program.command("index").description("Index repository for semantic search").option("--status", "Show index status instead of indexing").option("--full", "Force full re-index (ignore existing embeddings)").option("-w, --working-dir <path>", "Working directory (defaults to current directory)").option("-c, --config <path>", "Path to config file").option("-v, --verbose", "Show detailed progress").action(async (options) => {
|
|
17568
18148
|
try {
|
|
17569
|
-
const workingDir = options.workingDir ?
|
|
18149
|
+
const workingDir = options.workingDir ? resolve13(options.workingDir) : process.cwd();
|
|
17570
18150
|
let config = loadConfig(options.config, workingDir);
|
|
17571
18151
|
const remoteUrl = config.resolvedRemoteServer.url;
|
|
17572
18152
|
if (!remoteUrl) {
|
|
@@ -17700,7 +18280,7 @@ Indexing ${chalk.cyan(namespace)}...
|
|
|
17700
18280
|
});
|
|
17701
18281
|
program.command("search").description("Search indexed repository using semantic search").argument("<query>", "Search query").option("-n, --num <count>", "Number of results to show", "5").option("-w, --working-dir <path>", "Working directory (defaults to current directory)").option("-c, --config <path>", "Path to config file").action(async (query, options) => {
|
|
17702
18282
|
try {
|
|
17703
|
-
const workingDir = options.workingDir ?
|
|
18283
|
+
const workingDir = options.workingDir ? resolve13(options.workingDir) : process.cwd();
|
|
17704
18284
|
const config = loadConfig(options.config, workingDir);
|
|
17705
18285
|
const remoteUrl = config.resolvedRemoteServer.url;
|
|
17706
18286
|
if (!remoteUrl) {
|
|
@@ -17955,17 +18535,17 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
17955
18535
|
});
|
|
17956
18536
|
{
|
|
17957
18537
|
let stateFilePath = function() {
|
|
17958
|
-
return
|
|
18538
|
+
return join18(ensureAppDataDirectory(), "recordings.json");
|
|
17959
18539
|
}, readState = function() {
|
|
17960
18540
|
const p = stateFilePath();
|
|
17961
|
-
if (!
|
|
18541
|
+
if (!existsSync23(p)) return [];
|
|
17962
18542
|
try {
|
|
17963
|
-
return JSON.parse(
|
|
18543
|
+
return JSON.parse(readFileSync11(p, "utf-8"));
|
|
17964
18544
|
} catch {
|
|
17965
18545
|
return [];
|
|
17966
18546
|
}
|
|
17967
18547
|
}, writeState = function(rows) {
|
|
17968
|
-
|
|
18548
|
+
writeFileSync8(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
|
|
17969
18549
|
}, isAlive = function(pid) {
|
|
17970
18550
|
try {
|
|
17971
18551
|
process.kill(pid, 0);
|
|
@@ -17980,16 +18560,16 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
17980
18560
|
const record = program.command("record").description("Start/stop screen recordings");
|
|
17981
18561
|
record.command("start").description("Start a screen recording (returns id, path, pid as JSON)").option("--name <slug>", "Optional human-readable label for the recording").option("--dir <path>", "Output directory (default: ~/recordings)").action(async (opts) => {
|
|
17982
18562
|
const { homedir: homedir2, platform: osPlatform } = await import("os");
|
|
17983
|
-
const outDir = opts.dir ?
|
|
17984
|
-
|
|
18563
|
+
const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join18(homedir2(), "recordings");
|
|
18564
|
+
mkdirSync11(outDir, { recursive: true });
|
|
17985
18565
|
const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
17986
18566
|
const ext = osPlatform() === "darwin" ? "mov" : "mp4";
|
|
17987
18567
|
const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
|
|
17988
|
-
const path =
|
|
18568
|
+
const path = join18(outDir, filename);
|
|
17989
18569
|
let cmd;
|
|
17990
18570
|
let args;
|
|
17991
18571
|
if (osPlatform() === "darwin") {
|
|
17992
|
-
cmd =
|
|
18572
|
+
cmd = existsSync23("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
|
|
17993
18573
|
args = ["-v", "-C", "-k", path];
|
|
17994
18574
|
} else if (osPlatform() === "linux") {
|
|
17995
18575
|
const display = process.env.DISPLAY || ":0.0";
|
|
@@ -18092,8 +18672,8 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18092
18672
|
}
|
|
18093
18673
|
}
|
|
18094
18674
|
writeState(rows.filter((r) => r.id !== id));
|
|
18095
|
-
const fileExists =
|
|
18096
|
-
const sizeMb = fileExists ? Math.round(
|
|
18675
|
+
const fileExists = existsSync23(row.path);
|
|
18676
|
+
const sizeMb = fileExists ? Math.round(statSync4(row.path).size / (1024 * 1024) * 10) / 10 : 0;
|
|
18097
18677
|
console.log(JSON.stringify({
|
|
18098
18678
|
id,
|
|
18099
18679
|
path: row.path,
|
|
@@ -18127,7 +18707,7 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18127
18707
|
}
|
|
18128
18708
|
}
|
|
18129
18709
|
}
|
|
18130
|
-
stopped.push({ id: r.id, path: r.path, ok:
|
|
18710
|
+
stopped.push({ id: r.id, path: r.path, ok: existsSync23(r.path) });
|
|
18131
18711
|
}
|
|
18132
18712
|
writeState([]);
|
|
18133
18713
|
console.log(JSON.stringify({ stopped }));
|