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/index.js
CHANGED
|
@@ -450,6 +450,7 @@ __export(config_exports, {
|
|
|
450
450
|
setApiKey: () => setApiKey,
|
|
451
451
|
setMcpServers: () => setMcpServers,
|
|
452
452
|
setPublicUrl: () => setPublicUrl,
|
|
453
|
+
setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
|
|
453
454
|
setSlackConfig: () => setSlackConfig,
|
|
454
455
|
setWebhookToken: () => setWebhookToken
|
|
455
456
|
});
|
|
@@ -739,6 +740,40 @@ function setWebhookToken(token) {
|
|
|
739
740
|
console.warn("[config] failed to persist webhook token:", err?.message || err);
|
|
740
741
|
}
|
|
741
742
|
}
|
|
743
|
+
function setSkillsAdditionalDirectories(directories) {
|
|
744
|
+
if (cachedConfig) {
|
|
745
|
+
const cur = cachedConfig.skills || {};
|
|
746
|
+
cachedConfig.skills = { ...cur, additionalDirectories: directories };
|
|
747
|
+
try {
|
|
748
|
+
const discovered = discoverSkillDirectories(cachedConfig.resolvedWorkingDirectory);
|
|
749
|
+
const additionalAbs = directories.map((d) => resolve(cachedConfig.resolvedWorkingDirectory, d)).filter((d) => existsSync(d));
|
|
750
|
+
cachedConfig.discoveredSkills = discovered;
|
|
751
|
+
cachedConfig.resolvedSkillsDirectories = [
|
|
752
|
+
...discovered.allDirectories,
|
|
753
|
+
...additionalAbs
|
|
754
|
+
];
|
|
755
|
+
} catch {
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
|
|
760
|
+
const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
|
|
761
|
+
let raw = {};
|
|
762
|
+
if (existsSync(target)) {
|
|
763
|
+
try {
|
|
764
|
+
raw = JSON.parse(readFileSync(target, "utf-8"));
|
|
765
|
+
} catch {
|
|
766
|
+
raw = {};
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
raw = createDefaultConfig();
|
|
770
|
+
}
|
|
771
|
+
raw.skills = { ...raw.skills || {}, additionalDirectories: directories };
|
|
772
|
+
writeFileSync(target, JSON.stringify(raw, null, 2));
|
|
773
|
+
} catch (err) {
|
|
774
|
+
console.warn("[config] failed to persist skill directories:", err?.message || err);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
742
777
|
function setPublicUrl(publicUrl) {
|
|
743
778
|
if (cachedConfig) {
|
|
744
779
|
cachedConfig.server = { ...cachedConfig.server ?? {}, publicUrl };
|
|
@@ -2311,10 +2346,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2311
2346
|
const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
|
|
2312
2347
|
const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
|
|
2313
2348
|
const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
|
|
2314
|
-
const
|
|
2315
|
-
if (existsSync3(
|
|
2349
|
+
const cachePath2 = join3(cacheDir, key2 + ext);
|
|
2350
|
+
if (existsSync3(cachePath2)) {
|
|
2316
2351
|
console.log(`[image-resize] Cache hit for ${width}x${height} image`);
|
|
2317
|
-
return { buffer: readFileSync2(
|
|
2352
|
+
return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
|
|
2318
2353
|
}
|
|
2319
2354
|
let pipeline = sharp(buffer);
|
|
2320
2355
|
if (needsResize) {
|
|
@@ -2339,7 +2374,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2339
2374
|
}
|
|
2340
2375
|
finalMediaType = "image/jpeg";
|
|
2341
2376
|
}
|
|
2342
|
-
writeFileSync2(
|
|
2377
|
+
writeFileSync2(cachePath2, result);
|
|
2343
2378
|
const resultMeta = await sharp(result).metadata();
|
|
2344
2379
|
console.log(
|
|
2345
2380
|
`[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
|
|
@@ -3030,7 +3065,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3030
3065
|
},
|
|
3031
3066
|
async waitForDiagnostics(filePath, timeoutMs = 5e3) {
|
|
3032
3067
|
const normalized = normalizePath(filePath);
|
|
3033
|
-
return new Promise((
|
|
3068
|
+
return new Promise((resolve13) => {
|
|
3034
3069
|
const startTime = Date.now();
|
|
3035
3070
|
let debounceTimer;
|
|
3036
3071
|
let resolved = false;
|
|
@@ -3049,7 +3084,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3049
3084
|
if (resolved) return;
|
|
3050
3085
|
resolved = true;
|
|
3051
3086
|
cleanup2();
|
|
3052
|
-
|
|
3087
|
+
resolve13(diagnostics.get(normalized) || []);
|
|
3053
3088
|
};
|
|
3054
3089
|
const onDiagnostic = () => {
|
|
3055
3090
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -3424,7 +3459,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3424
3459
|
isChunked: true
|
|
3425
3460
|
});
|
|
3426
3461
|
if (chunkCount > 1) {
|
|
3427
|
-
await new Promise((
|
|
3462
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
3428
3463
|
}
|
|
3429
3464
|
}
|
|
3430
3465
|
}
|
|
@@ -4043,7 +4078,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4043
4078
|
if (!existsSync10(directory)) {
|
|
4044
4079
|
return [];
|
|
4045
4080
|
}
|
|
4046
|
-
const
|
|
4081
|
+
const skills2 = [];
|
|
4047
4082
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
4048
4083
|
for (const entry2 of entries) {
|
|
4049
4084
|
let filePath;
|
|
@@ -4067,7 +4102,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4067
4102
|
if (parsed) {
|
|
4068
4103
|
const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
|
|
4069
4104
|
const loadType = alwaysApply ? "always" : defaultLoadType;
|
|
4070
|
-
|
|
4105
|
+
skills2.push({
|
|
4071
4106
|
name: parsed.metadata.name,
|
|
4072
4107
|
description: parsed.metadata.description,
|
|
4073
4108
|
filePath,
|
|
@@ -4081,7 +4116,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4081
4116
|
} else {
|
|
4082
4117
|
const name = getSkillNameFromPath(filePath);
|
|
4083
4118
|
const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
|
|
4084
|
-
|
|
4119
|
+
skills2.push({
|
|
4085
4120
|
name,
|
|
4086
4121
|
description: firstParagraph.replace(/^#\s*/, "").trim(),
|
|
4087
4122
|
filePath,
|
|
@@ -4094,7 +4129,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
4094
4129
|
});
|
|
4095
4130
|
}
|
|
4096
4131
|
}
|
|
4097
|
-
return
|
|
4132
|
+
return skills2.filter(
|
|
4098
4133
|
(s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
|
|
4099
4134
|
);
|
|
4100
4135
|
}
|
|
@@ -4102,8 +4137,8 @@ async function loadAllSkills(directories) {
|
|
|
4102
4137
|
const allSkills = [];
|
|
4103
4138
|
const seenNames = /* @__PURE__ */ new Set();
|
|
4104
4139
|
for (const dir of directories) {
|
|
4105
|
-
const
|
|
4106
|
-
for (const skill of
|
|
4140
|
+
const skills2 = await loadSkillsFromDirectory(dir);
|
|
4141
|
+
for (const skill of skills2) {
|
|
4107
4142
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4108
4143
|
seenNames.add(skill.name.toLowerCase());
|
|
4109
4144
|
allSkills.push(skill);
|
|
@@ -4116,12 +4151,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4116
4151
|
const allSkills = [];
|
|
4117
4152
|
const seenNames = /* @__PURE__ */ new Set();
|
|
4118
4153
|
for (const { path, priority } of discovered.alwaysLoadedDirs) {
|
|
4119
|
-
const
|
|
4154
|
+
const skills2 = await loadSkillsFromDirectory(path, {
|
|
4120
4155
|
priority,
|
|
4121
4156
|
defaultLoadType: "always",
|
|
4122
4157
|
forceAlwaysApply: true
|
|
4123
4158
|
});
|
|
4124
|
-
for (const skill of
|
|
4159
|
+
for (const skill of skills2) {
|
|
4125
4160
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4126
4161
|
seenNames.add(skill.name.toLowerCase());
|
|
4127
4162
|
allSkills.push(skill);
|
|
@@ -4129,12 +4164,12 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4129
4164
|
}
|
|
4130
4165
|
}
|
|
4131
4166
|
for (const { path, priority } of discovered.onDemandDirs) {
|
|
4132
|
-
const
|
|
4167
|
+
const skills2 = await loadSkillsFromDirectory(path, {
|
|
4133
4168
|
priority,
|
|
4134
4169
|
defaultLoadType: "on_demand",
|
|
4135
4170
|
forceAlwaysApply: false
|
|
4136
4171
|
});
|
|
4137
|
-
for (const skill of
|
|
4172
|
+
for (const skill of skills2) {
|
|
4138
4173
|
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
4139
4174
|
seenNames.add(skill.name.toLowerCase());
|
|
4140
4175
|
allSkills.push(skill);
|
|
@@ -4159,7 +4194,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
4159
4194
|
all: allSkills
|
|
4160
4195
|
};
|
|
4161
4196
|
}
|
|
4162
|
-
async function getGlobMatchedSkills(
|
|
4197
|
+
async function getGlobMatchedSkills(skills2, activeFiles, workingDirectory) {
|
|
4163
4198
|
if (activeFiles.length === 0) {
|
|
4164
4199
|
return [];
|
|
4165
4200
|
}
|
|
@@ -4169,7 +4204,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
|
|
|
4169
4204
|
}
|
|
4170
4205
|
return f;
|
|
4171
4206
|
});
|
|
4172
|
-
const matchedSkills =
|
|
4207
|
+
const matchedSkills = skills2.filter((skill) => {
|
|
4173
4208
|
if (skill.alwaysApply || skill.loadType === "always") {
|
|
4174
4209
|
return false;
|
|
4175
4210
|
}
|
|
@@ -4215,8 +4250,8 @@ async function loadSkillContent(skillName, directories) {
|
|
|
4215
4250
|
content: parsed ? parsed.body : content
|
|
4216
4251
|
};
|
|
4217
4252
|
}
|
|
4218
|
-
function formatSkillsForContext(
|
|
4219
|
-
const onDemandSkills =
|
|
4253
|
+
function formatSkillsForContext(skills2) {
|
|
4254
|
+
const onDemandSkills = skills2.filter((s) => !s.alwaysApply && s.loadType !== "always");
|
|
4220
4255
|
if (onDemandSkills.length === 0) {
|
|
4221
4256
|
return "No on-demand skills available.";
|
|
4222
4257
|
}
|
|
@@ -4227,12 +4262,12 @@ function formatSkillsForContext(skills) {
|
|
|
4227
4262
|
}
|
|
4228
4263
|
return lines.join("\n");
|
|
4229
4264
|
}
|
|
4230
|
-
function formatAlwaysLoadedSkills(
|
|
4231
|
-
if (
|
|
4265
|
+
function formatAlwaysLoadedSkills(skills2) {
|
|
4266
|
+
if (skills2.length === 0) {
|
|
4232
4267
|
return "";
|
|
4233
4268
|
}
|
|
4234
4269
|
const sections = [];
|
|
4235
|
-
for (const skill of
|
|
4270
|
+
for (const skill of skills2) {
|
|
4236
4271
|
sections.push(`### ${skill.name}
|
|
4237
4272
|
|
|
4238
4273
|
${skill.content}`);
|
|
@@ -4241,12 +4276,12 @@ ${skill.content}`);
|
|
|
4241
4276
|
|
|
4242
4277
|
${sections.join("\n\n---\n\n")}`;
|
|
4243
4278
|
}
|
|
4244
|
-
function formatGlobMatchedSkills(
|
|
4245
|
-
if (
|
|
4279
|
+
function formatGlobMatchedSkills(skills2) {
|
|
4280
|
+
if (skills2.length === 0) {
|
|
4246
4281
|
return "";
|
|
4247
4282
|
}
|
|
4248
4283
|
const sections = [];
|
|
4249
|
-
for (const skill of
|
|
4284
|
+
for (const skill of skills2) {
|
|
4250
4285
|
sections.push(`### ${skill.name}
|
|
4251
4286
|
|
|
4252
4287
|
${skill.content}`);
|
|
@@ -4288,16 +4323,16 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
4288
4323
|
try {
|
|
4289
4324
|
switch (action) {
|
|
4290
4325
|
case "list": {
|
|
4291
|
-
const
|
|
4326
|
+
const skills2 = await loadAllSkills(options.skillsDirectories);
|
|
4292
4327
|
return {
|
|
4293
4328
|
success: true,
|
|
4294
4329
|
action: "list",
|
|
4295
|
-
skillCount:
|
|
4296
|
-
skills:
|
|
4330
|
+
skillCount: skills2.length,
|
|
4331
|
+
skills: skills2.map((s) => ({
|
|
4297
4332
|
name: s.name,
|
|
4298
4333
|
description: s.description
|
|
4299
4334
|
})),
|
|
4300
|
-
formatted: formatSkillsForContext(
|
|
4335
|
+
formatted: formatSkillsForContext(skills2)
|
|
4301
4336
|
};
|
|
4302
4337
|
}
|
|
4303
4338
|
case "load": {
|
|
@@ -4735,8 +4770,8 @@ var init_subagent = __esm({
|
|
|
4735
4770
|
if (eventQueue.length > 0) {
|
|
4736
4771
|
yield eventQueue.shift();
|
|
4737
4772
|
} else if (!done) {
|
|
4738
|
-
const event = await new Promise((
|
|
4739
|
-
resolveNext =
|
|
4773
|
+
const event = await new Promise((resolve13) => {
|
|
4774
|
+
resolveNext = resolve13;
|
|
4740
4775
|
});
|
|
4741
4776
|
if (event) {
|
|
4742
4777
|
yield event;
|
|
@@ -6512,8 +6547,8 @@ async function buildSystemPrompt(options) {
|
|
|
6512
6547
|
}
|
|
6513
6548
|
} else {
|
|
6514
6549
|
const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
|
|
6515
|
-
const
|
|
6516
|
-
onDemandSkillsContext = formatSkillsForContext(
|
|
6550
|
+
const skills2 = await loadAllSkills2(skillsDirectories);
|
|
6551
|
+
onDemandSkillsContext = formatSkillsForContext(skills2);
|
|
6517
6552
|
}
|
|
6518
6553
|
const todos = await todoQueries.getBySession(sessionId);
|
|
6519
6554
|
const todosContext = formatTodosForContext(todos);
|
|
@@ -7412,6 +7447,35 @@ function stripOrphanedToolResults(msg, removedIds) {
|
|
|
7412
7447
|
if (parts.length === 0) return null;
|
|
7413
7448
|
return { ...msg, content: parts };
|
|
7414
7449
|
}
|
|
7450
|
+
function wrapToolsNeverThrow(tools) {
|
|
7451
|
+
if (!tools || typeof tools !== "object") return tools;
|
|
7452
|
+
const wrapped = {};
|
|
7453
|
+
for (const [name, t] of Object.entries(tools)) {
|
|
7454
|
+
if (!t || typeof t.execute !== "function") {
|
|
7455
|
+
wrapped[name] = t;
|
|
7456
|
+
continue;
|
|
7457
|
+
}
|
|
7458
|
+
const original = t.execute;
|
|
7459
|
+
wrapped[name] = {
|
|
7460
|
+
...t,
|
|
7461
|
+
execute: async (input, opts) => {
|
|
7462
|
+
try {
|
|
7463
|
+
return await original.call(t, input, opts);
|
|
7464
|
+
} catch (err) {
|
|
7465
|
+
const message = err?.message ?? String(err);
|
|
7466
|
+
console.warn(`[tool:${name}] threw \u2014 converted to error result so the tool-call isn't orphaned:`, message);
|
|
7467
|
+
return {
|
|
7468
|
+
__error: true,
|
|
7469
|
+
tool: name,
|
|
7470
|
+
message,
|
|
7471
|
+
note: "Tool execution threw an exception. The agent should treat this as a failed tool call and decide how to recover."
|
|
7472
|
+
};
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
};
|
|
7476
|
+
}
|
|
7477
|
+
return wrapped;
|
|
7478
|
+
}
|
|
7415
7479
|
function repairToolPairing(messages) {
|
|
7416
7480
|
const toolCallIds = /* @__PURE__ */ new Set();
|
|
7417
7481
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -7777,6 +7841,120 @@ var init_web = __esm({
|
|
|
7777
7841
|
}
|
|
7778
7842
|
});
|
|
7779
7843
|
|
|
7844
|
+
// src/integrations/slack/persistence.ts
|
|
7845
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
7846
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
7847
|
+
function cachePath() {
|
|
7848
|
+
return join9(ensureAppDataDirectory(), FILENAME);
|
|
7849
|
+
}
|
|
7850
|
+
function load() {
|
|
7851
|
+
if (loaded) return;
|
|
7852
|
+
loaded = true;
|
|
7853
|
+
const path = cachePath();
|
|
7854
|
+
if (!existsSync16(path)) return;
|
|
7855
|
+
try {
|
|
7856
|
+
const raw = readFileSync7(path, "utf-8");
|
|
7857
|
+
const parsed = JSON.parse(raw);
|
|
7858
|
+
if (parsed?.version !== FILE_VERSION) return;
|
|
7859
|
+
const now = Date.now();
|
|
7860
|
+
for (const [id, e] of Object.entries(parsed.users || {})) {
|
|
7861
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7862
|
+
userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
|
|
7863
|
+
}
|
|
7864
|
+
}
|
|
7865
|
+
for (const [key2, e] of Object.entries(parsed.threads || {})) {
|
|
7866
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7867
|
+
threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
} catch (err) {
|
|
7871
|
+
console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
function evictIfOversized(map, max) {
|
|
7875
|
+
if (map.size <= max) return;
|
|
7876
|
+
const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
7877
|
+
const toRemove = map.size - max;
|
|
7878
|
+
for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
|
|
7879
|
+
}
|
|
7880
|
+
function scheduleSave() {
|
|
7881
|
+
dirty = true;
|
|
7882
|
+
if (saveTimer) return;
|
|
7883
|
+
saveTimer = setTimeout(() => {
|
|
7884
|
+
saveTimer = null;
|
|
7885
|
+
if (dirty) saveSync();
|
|
7886
|
+
}, SAVE_DEBOUNCE_MS);
|
|
7887
|
+
saveTimer.unref?.();
|
|
7888
|
+
}
|
|
7889
|
+
function saveSync() {
|
|
7890
|
+
dirty = false;
|
|
7891
|
+
try {
|
|
7892
|
+
const path = cachePath();
|
|
7893
|
+
const dir = dirname6(path);
|
|
7894
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
7895
|
+
const payload = {
|
|
7896
|
+
version: FILE_VERSION,
|
|
7897
|
+
users: Object.fromEntries(userMap),
|
|
7898
|
+
threads: Object.fromEntries(threadMap)
|
|
7899
|
+
};
|
|
7900
|
+
const tmp = `${path}.tmp`;
|
|
7901
|
+
writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
|
|
7902
|
+
renameSync(tmp, path);
|
|
7903
|
+
} catch (err) {
|
|
7904
|
+
console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7907
|
+
function hookExit() {
|
|
7908
|
+
if (exitHooked) return;
|
|
7909
|
+
exitHooked = true;
|
|
7910
|
+
const flush2 = () => {
|
|
7911
|
+
if (dirty) saveSync();
|
|
7912
|
+
};
|
|
7913
|
+
process.once("beforeExit", flush2);
|
|
7914
|
+
process.once("SIGINT", flush2);
|
|
7915
|
+
process.once("SIGTERM", flush2);
|
|
7916
|
+
}
|
|
7917
|
+
function getCachedUserName(userId) {
|
|
7918
|
+
load();
|
|
7919
|
+
return userMap.get(userId);
|
|
7920
|
+
}
|
|
7921
|
+
function setCachedUserName(userId, entry2) {
|
|
7922
|
+
load();
|
|
7923
|
+
hookExit();
|
|
7924
|
+
userMap.set(userId, entry2);
|
|
7925
|
+
evictIfOversized(userMap, MAX_USERS);
|
|
7926
|
+
scheduleSave();
|
|
7927
|
+
}
|
|
7928
|
+
function getCachedThreadOwnership(key2) {
|
|
7929
|
+
load();
|
|
7930
|
+
return threadMap.get(key2);
|
|
7931
|
+
}
|
|
7932
|
+
function setCachedThreadOwnership(key2, entry2) {
|
|
7933
|
+
load();
|
|
7934
|
+
hookExit();
|
|
7935
|
+
threadMap.set(key2, entry2);
|
|
7936
|
+
evictIfOversized(threadMap, MAX_THREADS);
|
|
7937
|
+
scheduleSave();
|
|
7938
|
+
}
|
|
7939
|
+
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
7940
|
+
var init_persistence = __esm({
|
|
7941
|
+
"src/integrations/slack/persistence.ts"() {
|
|
7942
|
+
"use strict";
|
|
7943
|
+
init_config();
|
|
7944
|
+
FILENAME = "slack-cache.json";
|
|
7945
|
+
FILE_VERSION = 1;
|
|
7946
|
+
SAVE_DEBOUNCE_MS = 500;
|
|
7947
|
+
MAX_USERS = 5e3;
|
|
7948
|
+
MAX_THREADS = 5e3;
|
|
7949
|
+
loaded = false;
|
|
7950
|
+
userMap = /* @__PURE__ */ new Map();
|
|
7951
|
+
threadMap = /* @__PURE__ */ new Map();
|
|
7952
|
+
dirty = false;
|
|
7953
|
+
saveTimer = null;
|
|
7954
|
+
exitHooked = false;
|
|
7955
|
+
}
|
|
7956
|
+
});
|
|
7957
|
+
|
|
7780
7958
|
// src/integrations/slack/client.ts
|
|
7781
7959
|
function readSlackConfig() {
|
|
7782
7960
|
try {
|
|
@@ -7899,13 +8077,13 @@ async function fetchSlackUserName(userId) {
|
|
|
7899
8077
|
async function resolveSlackUserName(userId) {
|
|
7900
8078
|
if (!userId) return null;
|
|
7901
8079
|
const now = Date.now();
|
|
7902
|
-
const hit =
|
|
8080
|
+
const hit = getCachedUserName(userId);
|
|
7903
8081
|
if (hit && hit.expiresAt > now) return hit.name;
|
|
7904
8082
|
const inflight = userInflight.get(userId);
|
|
7905
8083
|
if (inflight) return inflight;
|
|
7906
8084
|
const p = (async () => {
|
|
7907
8085
|
const name = await fetchSlackUserName(userId);
|
|
7908
|
-
|
|
8086
|
+
setCachedUserName(userId, {
|
|
7909
8087
|
name,
|
|
7910
8088
|
expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
7911
8089
|
});
|
|
@@ -7927,11 +8105,63 @@ async function normalizeSlackMentions(text) {
|
|
|
7927
8105
|
}
|
|
7928
8106
|
return text.replace(userMentionRe, (_full, id, label) => {
|
|
7929
8107
|
if (label) return `${label} <@${id}>`;
|
|
7930
|
-
const cached =
|
|
8108
|
+
const cached = getCachedUserName(id);
|
|
7931
8109
|
const name = cached?.name;
|
|
7932
8110
|
return name ? `${name} <@${id}>` : `<@${id}>`;
|
|
7933
8111
|
});
|
|
7934
8112
|
}
|
|
8113
|
+
function threadCacheKey(channel, threadTs) {
|
|
8114
|
+
return `${channel}\u241F${threadTs}`;
|
|
8115
|
+
}
|
|
8116
|
+
async function fetchBotParticipatedInThread(channel, threadTs) {
|
|
8117
|
+
const token = getSlackBotToken();
|
|
8118
|
+
if (!token) return false;
|
|
8119
|
+
const self = await ensureSlackSelfIdentity();
|
|
8120
|
+
if (!self) return false;
|
|
8121
|
+
try {
|
|
8122
|
+
const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
|
|
8123
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
8124
|
+
const data = await res.json().catch(() => ({}));
|
|
8125
|
+
if (!data?.ok) {
|
|
8126
|
+
console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
8127
|
+
return false;
|
|
8128
|
+
}
|
|
8129
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
8130
|
+
return messages.some(
|
|
8131
|
+
(m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
|
|
8132
|
+
);
|
|
8133
|
+
} catch (err) {
|
|
8134
|
+
console.warn(`[slack] conversations.replies error:`, err?.message || err);
|
|
8135
|
+
return false;
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
async function botParticipatedInThread(channel, threadTs) {
|
|
8139
|
+
if (!channel || !threadTs) return false;
|
|
8140
|
+
const key2 = threadCacheKey(channel, threadTs);
|
|
8141
|
+
const now = Date.now();
|
|
8142
|
+
const hit = getCachedThreadOwnership(key2);
|
|
8143
|
+
if (hit && hit.expiresAt > now) return hit.owned;
|
|
8144
|
+
const inflight = threadOwnedInflight.get(key2);
|
|
8145
|
+
if (inflight) return inflight;
|
|
8146
|
+
const p = (async () => {
|
|
8147
|
+
const owned = await fetchBotParticipatedInThread(channel, threadTs);
|
|
8148
|
+
setCachedThreadOwnership(key2, {
|
|
8149
|
+
owned,
|
|
8150
|
+
expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
|
|
8151
|
+
});
|
|
8152
|
+
threadOwnedInflight.delete(key2);
|
|
8153
|
+
return owned;
|
|
8154
|
+
})();
|
|
8155
|
+
threadOwnedInflight.set(key2, p);
|
|
8156
|
+
return p;
|
|
8157
|
+
}
|
|
8158
|
+
function noteBotPostedInThread(channel, threadTs) {
|
|
8159
|
+
if (!channel || !threadTs) return;
|
|
8160
|
+
setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
|
|
8161
|
+
owned: true,
|
|
8162
|
+
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
8163
|
+
});
|
|
8164
|
+
}
|
|
7935
8165
|
function getSlackDeniedReplyPolicy() {
|
|
7936
8166
|
try {
|
|
7937
8167
|
const cfg = getConfig();
|
|
@@ -7944,17 +8174,20 @@ function getSlackDeniedReplyPolicy() {
|
|
|
7944
8174
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
7945
8175
|
}
|
|
7946
8176
|
}
|
|
7947
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS,
|
|
8177
|
+
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
7948
8178
|
var init_client3 = __esm({
|
|
7949
8179
|
"src/integrations/slack/client.ts"() {
|
|
7950
8180
|
"use strict";
|
|
7951
8181
|
init_config();
|
|
8182
|
+
init_persistence();
|
|
7952
8183
|
cachedSelf = null;
|
|
7953
8184
|
selfInflight = null;
|
|
7954
8185
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
7955
8186
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
7956
|
-
userNameCache = /* @__PURE__ */ new Map();
|
|
7957
8187
|
userInflight = /* @__PURE__ */ new Map();
|
|
8188
|
+
THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
8189
|
+
THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
8190
|
+
threadOwnedInflight = /* @__PURE__ */ new Map();
|
|
7958
8191
|
DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
|
|
7959
8192
|
}
|
|
7960
8193
|
});
|
|
@@ -8054,6 +8287,7 @@ var init_slack = __esm({
|
|
|
8054
8287
|
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
8055
8288
|
if (r.slackChannel && r.threadTs) {
|
|
8056
8289
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
8290
|
+
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8057
8291
|
}
|
|
8058
8292
|
},
|
|
8059
8293
|
displayLabel(ref) {
|
|
@@ -8711,8 +8945,8 @@ var init_orchestrator_actions = __esm({
|
|
|
8711
8945
|
|
|
8712
8946
|
// src/integrations/mcp/store.ts
|
|
8713
8947
|
import { nanoid as nanoid6 } from "nanoid";
|
|
8714
|
-
import { existsSync as
|
|
8715
|
-
import { resolve as resolve10, join as
|
|
8948
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
|
|
8949
|
+
import { resolve as resolve10, join as join10 } from "path";
|
|
8716
8950
|
function readServers() {
|
|
8717
8951
|
try {
|
|
8718
8952
|
const cfg = getConfig();
|
|
@@ -8724,12 +8958,12 @@ function readServers() {
|
|
|
8724
8958
|
function refreshMcpServersFromDisk() {
|
|
8725
8959
|
const candidates = [
|
|
8726
8960
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8727
|
-
|
|
8961
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8728
8962
|
];
|
|
8729
8963
|
for (const path of candidates) {
|
|
8730
|
-
if (!
|
|
8964
|
+
if (!existsSync17(path)) continue;
|
|
8731
8965
|
try {
|
|
8732
|
-
const raw = JSON.parse(
|
|
8966
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
8733
8967
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8734
8968
|
setMcpServers(servers2);
|
|
8735
8969
|
return servers2;
|
|
@@ -8981,11 +9215,11 @@ function waitForTaskQuestionAnswer(question) {
|
|
|
8981
9215
|
if (pendingQuestions.has(k)) {
|
|
8982
9216
|
return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
|
|
8983
9217
|
}
|
|
8984
|
-
return new Promise((
|
|
9218
|
+
return new Promise((resolve13, reject) => {
|
|
8985
9219
|
pendingQuestions.set(k, {
|
|
8986
9220
|
...question,
|
|
8987
9221
|
createdAt: /* @__PURE__ */ new Date(),
|
|
8988
|
-
resolve:
|
|
9222
|
+
resolve: resolve13,
|
|
8989
9223
|
reject
|
|
8990
9224
|
});
|
|
8991
9225
|
});
|
|
@@ -9342,7 +9576,7 @@ __export(recorder_exports, {
|
|
|
9342
9576
|
import { exec as exec5 } from "child_process";
|
|
9343
9577
|
import { promisify as promisify5 } from "util";
|
|
9344
9578
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
9345
|
-
import { join as
|
|
9579
|
+
import { join as join11 } from "path";
|
|
9346
9580
|
import { tmpdir } from "os";
|
|
9347
9581
|
import { nanoid as nanoid7 } from "nanoid";
|
|
9348
9582
|
async function checkFfmpeg() {
|
|
@@ -9399,21 +9633,21 @@ var init_recorder = __esm({
|
|
|
9399
9633
|
*/
|
|
9400
9634
|
async encode() {
|
|
9401
9635
|
if (this.frames.length === 0) return null;
|
|
9402
|
-
const workDir =
|
|
9636
|
+
const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
9403
9637
|
await mkdir4(workDir, { recursive: true });
|
|
9404
9638
|
try {
|
|
9405
9639
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9406
|
-
const framePath =
|
|
9640
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
9407
9641
|
await writeFile5(framePath, this.frames[i].data);
|
|
9408
9642
|
}
|
|
9409
9643
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9410
9644
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9411
9645
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9412
|
-
const outputPath =
|
|
9646
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
9413
9647
|
const hasFfmpeg = await checkFfmpeg();
|
|
9414
9648
|
if (hasFfmpeg) {
|
|
9415
9649
|
await execAsync5(
|
|
9416
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
9650
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
9417
9651
|
{ timeout: 12e4 }
|
|
9418
9652
|
);
|
|
9419
9653
|
} else {
|
|
@@ -9425,7 +9659,7 @@ var init_recorder = __esm({
|
|
|
9425
9659
|
const files = await readdir5(workDir);
|
|
9426
9660
|
for (const f of files) {
|
|
9427
9661
|
if (f.startsWith("frame_")) {
|
|
9428
|
-
await unlink2(
|
|
9662
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
9429
9663
|
});
|
|
9430
9664
|
}
|
|
9431
9665
|
}
|
|
@@ -9692,7 +9926,8 @@ ${personality.trim()}`;
|
|
|
9692
9926
|
}
|
|
9693
9927
|
const messages = await this.context.getMessages();
|
|
9694
9928
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9695
|
-
const
|
|
9929
|
+
const approvalWrapped = this.wrapToolsWithApproval(options, tools);
|
|
9930
|
+
const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
|
|
9696
9931
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9697
9932
|
const stream = streamText2({
|
|
9698
9933
|
model: resolveModel(this.session.model),
|
|
@@ -9706,6 +9941,17 @@ ${personality.trim()}`;
|
|
|
9706
9941
|
providerOptions: useAnthropic ? {
|
|
9707
9942
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9708
9943
|
} : void 0,
|
|
9944
|
+
// Run repairToolPairing before EVERY step's model call, not just the
|
|
9945
|
+
// first one. The AI SDK's multi-step loop can otherwise feed the model
|
|
9946
|
+
// a prompt containing an orphan tool-call (e.g. when a previous step's
|
|
9947
|
+
// tool result was lost, dropped during compaction, or the stream was
|
|
9948
|
+
// aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
|
|
9949
|
+
// ever reaches the model and we never hit AI_MissingToolResultsError.
|
|
9950
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
9951
|
+
const repaired = repairToolPairing(stepMessages);
|
|
9952
|
+
if (repaired === stepMessages) return {};
|
|
9953
|
+
return { messages: repaired };
|
|
9954
|
+
},
|
|
9709
9955
|
onStepFinish: async (step) => {
|
|
9710
9956
|
options.onStepFinish?.(step);
|
|
9711
9957
|
},
|
|
@@ -9741,7 +9987,7 @@ ${personality.trim()}`;
|
|
|
9741
9987
|
});
|
|
9742
9988
|
const messages = await this.context.getMessages();
|
|
9743
9989
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
9744
|
-
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
9990
|
+
const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
|
|
9745
9991
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9746
9992
|
const result = await generateText3({
|
|
9747
9993
|
model: resolveModel(this.session.model),
|
|
@@ -9752,7 +9998,13 @@ ${personality.trim()}`;
|
|
|
9752
9998
|
// Enable extended thinking/reasoning for models that support it
|
|
9753
9999
|
providerOptions: useAnthropic ? {
|
|
9754
10000
|
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
9755
|
-
} : void 0
|
|
10001
|
+
} : void 0,
|
|
10002
|
+
// Repair tool pairing before every step (see `stream()` for full rationale).
|
|
10003
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10004
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10005
|
+
if (repaired === stepMessages) return {};
|
|
10006
|
+
return { messages: repaired };
|
|
10007
|
+
}
|
|
9756
10008
|
});
|
|
9757
10009
|
const responseMessages = result.response.messages;
|
|
9758
10010
|
this.context.addResponseMessages(responseMessages);
|
|
@@ -9929,12 +10181,19 @@ ${p.text}` : p.text;
|
|
|
9929
10181
|
model: resolveModel(this.session.model),
|
|
9930
10182
|
system: systemPrompt,
|
|
9931
10183
|
messages,
|
|
9932
|
-
tools: taskTools,
|
|
10184
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
9933
10185
|
stopWhen: stepCountIs2(500),
|
|
9934
10186
|
abortSignal: combinedAbort,
|
|
9935
10187
|
providerOptions: useAnthropic ? {
|
|
9936
10188
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9937
10189
|
} : void 0,
|
|
10190
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
10191
|
+
// every step so we never feed the model an orphan tool-call.
|
|
10192
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10193
|
+
const repaired = repairToolPairing(stepMessages);
|
|
10194
|
+
if (repaired === stepMessages) return {};
|
|
10195
|
+
return { messages: repaired };
|
|
10196
|
+
},
|
|
9938
10197
|
onStepFinish: async (step) => {
|
|
9939
10198
|
options.onStepFinish?.(step);
|
|
9940
10199
|
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
@@ -10134,14 +10393,14 @@ ${p.text}` : p.text;
|
|
|
10134
10393
|
const result = await recorder.encode();
|
|
10135
10394
|
recorder.clear();
|
|
10136
10395
|
if (!result) return [];
|
|
10137
|
-
const { readFile:
|
|
10396
|
+
const { readFile: readFile13, unlink: unlink4 } = await import("fs/promises");
|
|
10138
10397
|
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
10139
10398
|
this.session.id,
|
|
10140
10399
|
`browser-recording-${Date.now()}.mp4`,
|
|
10141
10400
|
"video/mp4",
|
|
10142
10401
|
"browser-recording"
|
|
10143
10402
|
);
|
|
10144
|
-
const fileData = await
|
|
10403
|
+
const fileData = await readFile13(result.path);
|
|
10145
10404
|
await fetch(uploadInfo.uploadUrl, {
|
|
10146
10405
|
method: "PUT",
|
|
10147
10406
|
headers: { "Content-Type": "video/mp4" },
|
|
@@ -10149,7 +10408,7 @@ ${p.text}` : p.text;
|
|
|
10149
10408
|
});
|
|
10150
10409
|
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
10151
10410
|
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
10152
|
-
await
|
|
10411
|
+
await unlink4(result.path).catch(() => {
|
|
10153
10412
|
});
|
|
10154
10413
|
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
10155
10414
|
return [dlInfo.downloadUrl];
|
|
@@ -10167,13 +10426,13 @@ ${p.text}` : p.text;
|
|
|
10167
10426
|
try {
|
|
10168
10427
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10169
10428
|
if (!isRemoteConfigured2()) return [];
|
|
10170
|
-
const { readFile:
|
|
10171
|
-
const { join:
|
|
10429
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
10430
|
+
const { join: join18, basename: basename7 } = await import("path");
|
|
10172
10431
|
const urls = [];
|
|
10173
10432
|
for (const filePath of filePaths) {
|
|
10174
10433
|
try {
|
|
10175
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10176
|
-
const fileName =
|
|
10434
|
+
const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
|
|
10435
|
+
const fileName = basename7(fullPath);
|
|
10177
10436
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10178
10437
|
const mimeMap = {
|
|
10179
10438
|
pdf: "application/pdf",
|
|
@@ -10197,7 +10456,7 @@ ${p.text}` : p.text;
|
|
|
10197
10456
|
contentType,
|
|
10198
10457
|
"task-output"
|
|
10199
10458
|
);
|
|
10200
|
-
const fileData = await
|
|
10459
|
+
const fileData = await readFile13(fullPath);
|
|
10201
10460
|
await fetch(uploadInfo.uploadUrl, {
|
|
10202
10461
|
method: "PUT",
|
|
10203
10462
|
headers: { "Content-Type": contentType },
|
|
@@ -10246,8 +10505,8 @@ ${p.text}` : p.text;
|
|
|
10246
10505
|
this.pendingApprovals.set(toolCallId, await execution);
|
|
10247
10506
|
options.onApprovalRequired?.(await execution);
|
|
10248
10507
|
await sessionQueries.updateStatus(this.session.id, "waiting");
|
|
10249
|
-
const approved = await new Promise((
|
|
10250
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
10508
|
+
const approved = await new Promise((resolve13) => {
|
|
10509
|
+
approvalResolvers.set(toolCallId, { resolve: resolve13, sessionId: this.session.id });
|
|
10251
10510
|
});
|
|
10252
10511
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
10253
10512
|
approvalResolvers.delete(toolCallId);
|
|
@@ -10353,8 +10612,8 @@ async function withSessionLock(sessionId, fn) {
|
|
|
10353
10612
|
state2.pending++;
|
|
10354
10613
|
const prev = state2.tail;
|
|
10355
10614
|
let release;
|
|
10356
|
-
const next = new Promise((
|
|
10357
|
-
release =
|
|
10615
|
+
const next = new Promise((resolve13) => {
|
|
10616
|
+
release = resolve13;
|
|
10358
10617
|
});
|
|
10359
10618
|
state2.tail = prev.then(() => next);
|
|
10360
10619
|
await prev;
|
|
@@ -10377,19 +10636,19 @@ var init_session_lock = __esm({
|
|
|
10377
10636
|
});
|
|
10378
10637
|
|
|
10379
10638
|
// src/orchestrator/webhook-events.ts
|
|
10380
|
-
import { existsSync as
|
|
10381
|
-
import { dirname as
|
|
10639
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
10640
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
10382
10641
|
import { nanoid as nanoid9 } from "nanoid";
|
|
10383
10642
|
function logFilePath() {
|
|
10384
|
-
return
|
|
10643
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10385
10644
|
}
|
|
10386
10645
|
function ensureLoaded() {
|
|
10387
10646
|
if (cache !== null) return cache;
|
|
10388
10647
|
cache = [];
|
|
10389
10648
|
try {
|
|
10390
10649
|
const p = logFilePath();
|
|
10391
|
-
if (!
|
|
10392
|
-
const lines =
|
|
10650
|
+
if (!existsSync18(p)) return cache;
|
|
10651
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
10393
10652
|
for (const line of lines) {
|
|
10394
10653
|
try {
|
|
10395
10654
|
cache.push(JSON.parse(line));
|
|
@@ -10399,7 +10658,7 @@ function ensureLoaded() {
|
|
|
10399
10658
|
if (cache.length > MAX_EVENTS) {
|
|
10400
10659
|
cache = cache.slice(-MAX_EVENTS);
|
|
10401
10660
|
try {
|
|
10402
|
-
|
|
10661
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10403
10662
|
} catch {
|
|
10404
10663
|
}
|
|
10405
10664
|
}
|
|
@@ -10413,7 +10672,7 @@ function appendEvent(ev) {
|
|
|
10413
10672
|
if (list.length > MAX_EVENTS) list.shift();
|
|
10414
10673
|
try {
|
|
10415
10674
|
const p = logFilePath();
|
|
10416
|
-
|
|
10675
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10417
10676
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10418
10677
|
} catch {
|
|
10419
10678
|
}
|
|
@@ -10444,8 +10703,8 @@ function updateEvent(id, patch) {
|
|
|
10444
10703
|
list[i] = { ...list[i], ...patch };
|
|
10445
10704
|
try {
|
|
10446
10705
|
const p = logFilePath();
|
|
10447
|
-
|
|
10448
|
-
|
|
10706
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
10707
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10449
10708
|
} catch {
|
|
10450
10709
|
}
|
|
10451
10710
|
}
|
|
@@ -10477,7 +10736,7 @@ function listEvents(filter = {}) {
|
|
|
10477
10736
|
function clearAllEvents() {
|
|
10478
10737
|
cache = [];
|
|
10479
10738
|
try {
|
|
10480
|
-
|
|
10739
|
+
writeFileSync4(logFilePath(), "");
|
|
10481
10740
|
} catch {
|
|
10482
10741
|
}
|
|
10483
10742
|
}
|
|
@@ -10747,12 +11006,12 @@ init_agent();
|
|
|
10747
11006
|
|
|
10748
11007
|
// src/server/index.ts
|
|
10749
11008
|
import "dotenv/config";
|
|
10750
|
-
import { Hono as
|
|
11009
|
+
import { Hono as Hono10 } from "hono";
|
|
10751
11010
|
import { serve } from "@hono/node-server";
|
|
10752
11011
|
import { cors } from "hono/cors";
|
|
10753
11012
|
import { logger } from "hono/logger";
|
|
10754
|
-
import { existsSync as
|
|
10755
|
-
import { resolve as
|
|
11013
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
11014
|
+
import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
|
|
10756
11015
|
import { spawn as spawn2 } from "child_process";
|
|
10757
11016
|
import { createServer as createNetServer } from "net";
|
|
10758
11017
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -10766,9 +11025,9 @@ init_checkpoints();
|
|
|
10766
11025
|
import { Hono } from "hono";
|
|
10767
11026
|
import { zValidator } from "@hono/zod-validator";
|
|
10768
11027
|
import { z as z16 } from "zod";
|
|
10769
|
-
import { existsSync as
|
|
11028
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
10770
11029
|
import { readdir as readdir6 } from "fs/promises";
|
|
10771
|
-
import { join as
|
|
11030
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
10772
11031
|
import { nanoid as nanoid10 } from "nanoid";
|
|
10773
11032
|
|
|
10774
11033
|
// src/tasks/agent-status.ts
|
|
@@ -11406,12 +11665,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11406
11665
|
});
|
|
11407
11666
|
function getAttachmentsDir(sessionId) {
|
|
11408
11667
|
const appDataDir = getAppDataDirectory();
|
|
11409
|
-
return
|
|
11668
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
11410
11669
|
}
|
|
11411
11670
|
function ensureAttachmentsDir(sessionId) {
|
|
11412
11671
|
const dir = getAttachmentsDir(sessionId);
|
|
11413
|
-
if (!
|
|
11414
|
-
|
|
11672
|
+
if (!existsSync19(dir)) {
|
|
11673
|
+
mkdirSync8(dir, { recursive: true });
|
|
11415
11674
|
}
|
|
11416
11675
|
return dir;
|
|
11417
11676
|
}
|
|
@@ -11422,12 +11681,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11422
11681
|
return c.json({ error: "Session not found" }, 404);
|
|
11423
11682
|
}
|
|
11424
11683
|
const dir = getAttachmentsDir(sessionId);
|
|
11425
|
-
if (!
|
|
11684
|
+
if (!existsSync19(dir)) {
|
|
11426
11685
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
11427
11686
|
}
|
|
11428
11687
|
const files = readdirSync3(dir);
|
|
11429
11688
|
const attachments = files.map((filename) => {
|
|
11430
|
-
const filePath =
|
|
11689
|
+
const filePath = join13(dir, filename);
|
|
11431
11690
|
const stats = statSync2(filePath);
|
|
11432
11691
|
return {
|
|
11433
11692
|
id: filename.split("_")[0],
|
|
@@ -11462,9 +11721,9 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11462
11721
|
const id = nanoid10(10);
|
|
11463
11722
|
const ext = extname8(file.name) || "";
|
|
11464
11723
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11465
|
-
const filePath =
|
|
11724
|
+
const filePath = join13(dir, safeFilename);
|
|
11466
11725
|
const arrayBuffer = await file.arrayBuffer();
|
|
11467
|
-
|
|
11726
|
+
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
11468
11727
|
return c.json({
|
|
11469
11728
|
id,
|
|
11470
11729
|
filename: file.name,
|
|
@@ -11488,13 +11747,13 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11488
11747
|
const id = nanoid10(10);
|
|
11489
11748
|
const ext = extname8(body.filename) || "";
|
|
11490
11749
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11491
|
-
const filePath =
|
|
11750
|
+
const filePath = join13(dir, safeFilename);
|
|
11492
11751
|
let base64Data = body.data;
|
|
11493
11752
|
if (base64Data.includes(",")) {
|
|
11494
11753
|
base64Data = base64Data.split(",")[1];
|
|
11495
11754
|
}
|
|
11496
11755
|
const buffer = Buffer.from(base64Data, "base64");
|
|
11497
|
-
|
|
11756
|
+
writeFileSync5(filePath, buffer);
|
|
11498
11757
|
return c.json({
|
|
11499
11758
|
id,
|
|
11500
11759
|
filename: body.filename,
|
|
@@ -11517,7 +11776,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11517
11776
|
return c.json({ error: "Session not found" }, 404);
|
|
11518
11777
|
}
|
|
11519
11778
|
const dir = getAttachmentsDir(sessionId);
|
|
11520
|
-
if (!
|
|
11779
|
+
if (!existsSync19(dir)) {
|
|
11521
11780
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11522
11781
|
}
|
|
11523
11782
|
const files = readdirSync3(dir);
|
|
@@ -11525,7 +11784,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11525
11784
|
if (!file) {
|
|
11526
11785
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11527
11786
|
}
|
|
11528
|
-
const filePath =
|
|
11787
|
+
const filePath = join13(dir, file);
|
|
11529
11788
|
unlinkSync2(filePath);
|
|
11530
11789
|
return c.json({ success: true, id: attachmentId });
|
|
11531
11790
|
});
|
|
@@ -11608,7 +11867,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
11608
11867
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
11609
11868
|
for (const entry2 of entries) {
|
|
11610
11869
|
if (results.length >= limit * 2) break;
|
|
11611
|
-
const fullPath =
|
|
11870
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
11612
11871
|
const relativePath = relative9(baseDir, fullPath);
|
|
11613
11872
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
11614
11873
|
continue;
|
|
@@ -11656,7 +11915,7 @@ sessions2.get(
|
|
|
11656
11915
|
return c.json({ error: "Session not found" }, 404);
|
|
11657
11916
|
}
|
|
11658
11917
|
const workingDirectory = session.workingDirectory;
|
|
11659
|
-
if (!
|
|
11918
|
+
if (!existsSync19(workingDirectory)) {
|
|
11660
11919
|
return c.json({
|
|
11661
11920
|
sessionId,
|
|
11662
11921
|
workingDirectory,
|
|
@@ -11764,14 +12023,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
|
|
|
11764
12023
|
|
|
11765
12024
|
// src/server/routes/agents.ts
|
|
11766
12025
|
init_db();
|
|
11767
|
-
init_agent();
|
|
11768
|
-
init_session_lock();
|
|
11769
|
-
init_config();
|
|
11770
12026
|
import { Hono as Hono2 } from "hono";
|
|
11771
12027
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
11772
12028
|
import { z as z17 } from "zod";
|
|
11773
|
-
import { existsSync as
|
|
11774
|
-
import { join as
|
|
12029
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
12030
|
+
import { join as join14 } from "path";
|
|
12031
|
+
|
|
12032
|
+
// src/agent/missing-tool-recovery.ts
|
|
12033
|
+
init_db();
|
|
12034
|
+
function extractMissingToolCallIds(error) {
|
|
12035
|
+
if (!error) return [];
|
|
12036
|
+
const e = error;
|
|
12037
|
+
if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
|
|
12038
|
+
return e.toolCallIds;
|
|
12039
|
+
}
|
|
12040
|
+
const msg = typeof e.message === "string" ? e.message : "";
|
|
12041
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12042
|
+
for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
|
|
12043
|
+
for (const id of m[1].split(/\s*,\s*/)) {
|
|
12044
|
+
const trimmed = id.trim();
|
|
12045
|
+
if (trimmed) ids.add(trimmed);
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
return [...ids];
|
|
12049
|
+
}
|
|
12050
|
+
function isMissingToolResultsError(error) {
|
|
12051
|
+
if (!error) return false;
|
|
12052
|
+
const e = error;
|
|
12053
|
+
if (e.name === "AI_MissingToolResultsError") return true;
|
|
12054
|
+
if (Array.isArray(e.toolCallIds)) return true;
|
|
12055
|
+
return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
|
|
12056
|
+
}
|
|
12057
|
+
async function recoverFromMissingToolResults(sessionId, error) {
|
|
12058
|
+
const toolCallIds = extractMissingToolCallIds(error);
|
|
12059
|
+
if (toolCallIds.length === 0) return null;
|
|
12060
|
+
let history = [];
|
|
12061
|
+
try {
|
|
12062
|
+
history = await messageQueries.getModelMessages(sessionId);
|
|
12063
|
+
} catch (err) {
|
|
12064
|
+
console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
|
|
12065
|
+
}
|
|
12066
|
+
const toolNameByCallId = /* @__PURE__ */ new Map();
|
|
12067
|
+
const existingResultIds = /* @__PURE__ */ new Set();
|
|
12068
|
+
for (const msg of history) {
|
|
12069
|
+
if (!Array.isArray(msg.content)) continue;
|
|
12070
|
+
for (const part of msg.content) {
|
|
12071
|
+
if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
|
|
12072
|
+
if (typeof part.toolName === "string") toolNameByCallId.set(part.toolCallId, part.toolName);
|
|
12073
|
+
}
|
|
12074
|
+
if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
|
|
12075
|
+
existingResultIds.add(part.toolCallId);
|
|
12076
|
+
}
|
|
12077
|
+
}
|
|
12078
|
+
}
|
|
12079
|
+
const resolved = toolCallIds.map((id) => ({
|
|
12080
|
+
toolCallId: id,
|
|
12081
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
12082
|
+
foundInAssistantMessage: toolNameByCallId.has(id)
|
|
12083
|
+
}));
|
|
12084
|
+
const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
|
|
12085
|
+
let syntheticToolMessageSaved = false;
|
|
12086
|
+
if (stillOrphaned.length > 0) {
|
|
12087
|
+
const syntheticParts = stillOrphaned.map((id) => ({
|
|
12088
|
+
type: "tool-result",
|
|
12089
|
+
toolCallId: id,
|
|
12090
|
+
toolName: toolNameByCallId.get(id) ?? "unknown",
|
|
12091
|
+
output: {
|
|
12092
|
+
type: "text",
|
|
12093
|
+
value: "[Auto-recovered: the original tool execution never produced a result (crash, timeout, or message stripped during context compaction). This synthetic placeholder lets the conversation continue.]"
|
|
12094
|
+
}
|
|
12095
|
+
}));
|
|
12096
|
+
try {
|
|
12097
|
+
await messageQueries.create(sessionId, {
|
|
12098
|
+
role: "tool",
|
|
12099
|
+
content: syntheticParts
|
|
12100
|
+
});
|
|
12101
|
+
syntheticToolMessageSaved = true;
|
|
12102
|
+
} catch (err) {
|
|
12103
|
+
console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
|
|
12107
|
+
return {
|
|
12108
|
+
kind: "missing_tool_results",
|
|
12109
|
+
toolCallIds,
|
|
12110
|
+
resolved,
|
|
12111
|
+
syntheticToolMessageSaved,
|
|
12112
|
+
lastFewMessageRoles,
|
|
12113
|
+
hint: syntheticToolMessageSaved ? "Synthetic tool-result(s) saved. Re-send your message to continue." : "Could not auto-recover; please reset the session or revert to the previous checkpoint."
|
|
12114
|
+
};
|
|
12115
|
+
}
|
|
12116
|
+
|
|
12117
|
+
// src/server/routes/agents.ts
|
|
12118
|
+
init_agent();
|
|
12119
|
+
init_session_lock();
|
|
12120
|
+
init_config();
|
|
11775
12121
|
|
|
11776
12122
|
// src/server/resumable-stream.ts
|
|
11777
12123
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -11927,7 +12273,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
|
|
|
11927
12273
|
toolCallId,
|
|
11928
12274
|
argsTextDelta: chunk
|
|
11929
12275
|
}));
|
|
11930
|
-
await new Promise((
|
|
12276
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
11931
12277
|
}
|
|
11932
12278
|
}
|
|
11933
12279
|
function buildDevtoolsContextXml(sessionId) {
|
|
@@ -11978,12 +12324,12 @@ var rejectSchema = z17.object({
|
|
|
11978
12324
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
11979
12325
|
function getAttachmentsDirectory(sessionId) {
|
|
11980
12326
|
const appDataDir = getAppDataDirectory();
|
|
11981
|
-
return
|
|
12327
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
11982
12328
|
}
|
|
11983
12329
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
11984
12330
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
11985
|
-
if (!
|
|
11986
|
-
|
|
12331
|
+
if (!existsSync20(attachmentsDir)) {
|
|
12332
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
11987
12333
|
}
|
|
11988
12334
|
let filename = attachment.filename;
|
|
11989
12335
|
if (!filename) {
|
|
@@ -12001,8 +12347,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12001
12347
|
attachment.mediaType = resized.mediaType;
|
|
12002
12348
|
attachment.data = buffer.toString("base64");
|
|
12003
12349
|
}
|
|
12004
|
-
const filePath =
|
|
12005
|
-
|
|
12350
|
+
const filePath = join14(attachmentsDir, filename);
|
|
12351
|
+
writeFileSync6(filePath, buffer);
|
|
12006
12352
|
return filePath;
|
|
12007
12353
|
}
|
|
12008
12354
|
function stripDataUrlPrefix2(data) {
|
|
@@ -12182,7 +12528,7 @@ ${prompt}` });
|
|
|
12182
12528
|
chunkIndex,
|
|
12183
12529
|
chunkCount
|
|
12184
12530
|
}));
|
|
12185
|
-
await new Promise((
|
|
12531
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
12186
12532
|
}
|
|
12187
12533
|
const browserPort = progress.data?.browserStreamPort;
|
|
12188
12534
|
const browserClosed = progress.data?.browserClosed;
|
|
@@ -12339,7 +12685,20 @@ ${prompt}` });
|
|
|
12339
12685
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12340
12686
|
} else {
|
|
12341
12687
|
console.error("Agent error:", error);
|
|
12342
|
-
|
|
12688
|
+
let debugPayload = void 0;
|
|
12689
|
+
if (isMissingToolResultsError(error)) {
|
|
12690
|
+
try {
|
|
12691
|
+
const recovery = await recoverFromMissingToolResults(sessionId, error);
|
|
12692
|
+
if (recovery) debugPayload = recovery;
|
|
12693
|
+
} catch (recErr) {
|
|
12694
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12697
|
+
await writeSSE(JSON.stringify({
|
|
12698
|
+
type: "error",
|
|
12699
|
+
errorText: error.message,
|
|
12700
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
12701
|
+
}));
|
|
12343
12702
|
try {
|
|
12344
12703
|
await activeStreamQueries.markError(streamId);
|
|
12345
12704
|
} catch {
|
|
@@ -12729,7 +13088,7 @@ agents.post(
|
|
|
12729
13088
|
chunkIndex,
|
|
12730
13089
|
chunkCount
|
|
12731
13090
|
}));
|
|
12732
|
-
await new Promise((
|
|
13091
|
+
await new Promise((resolve13) => setTimeout(resolve13, 0));
|
|
12733
13092
|
}
|
|
12734
13093
|
const browserPort = progress.data?.browserStreamPort;
|
|
12735
13094
|
const browserClosed = progress.data?.browserClosed;
|
|
@@ -12879,7 +13238,20 @@ agents.post(
|
|
|
12879
13238
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
12880
13239
|
} else {
|
|
12881
13240
|
console.error("Agent error:", error);
|
|
12882
|
-
|
|
13241
|
+
let debugPayload = void 0;
|
|
13242
|
+
if (isMissingToolResultsError(error)) {
|
|
13243
|
+
try {
|
|
13244
|
+
const recovery = await recoverFromMissingToolResults(session.id, error);
|
|
13245
|
+
if (recovery) debugPayload = recovery;
|
|
13246
|
+
} catch (recErr) {
|
|
13247
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
13248
|
+
}
|
|
13249
|
+
}
|
|
13250
|
+
await writeSSE(JSON.stringify({
|
|
13251
|
+
type: "error",
|
|
13252
|
+
errorText: error.message,
|
|
13253
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
13254
|
+
}));
|
|
12883
13255
|
await activeStreamQueries.markError(streamId);
|
|
12884
13256
|
}
|
|
12885
13257
|
} finally {
|
|
@@ -12962,26 +13334,26 @@ init_config();
|
|
|
12962
13334
|
import { Hono as Hono3 } from "hono";
|
|
12963
13335
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
12964
13336
|
import { z as z18 } from "zod";
|
|
12965
|
-
import { readFileSync as
|
|
13337
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
12966
13338
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12967
|
-
import { dirname as
|
|
13339
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
12968
13340
|
var __filename = fileURLToPath3(import.meta.url);
|
|
12969
|
-
var __dirname =
|
|
13341
|
+
var __dirname = dirname8(__filename);
|
|
12970
13342
|
var possiblePaths = [
|
|
12971
|
-
|
|
13343
|
+
join15(__dirname, "../package.json"),
|
|
12972
13344
|
// From dist/server -> dist/../package.json
|
|
12973
|
-
|
|
13345
|
+
join15(__dirname, "../../package.json"),
|
|
12974
13346
|
// From dist/server (if nested differently)
|
|
12975
|
-
|
|
13347
|
+
join15(__dirname, "../../../package.json"),
|
|
12976
13348
|
// From src/server/routes (development)
|
|
12977
|
-
|
|
13349
|
+
join15(process.cwd(), "package.json")
|
|
12978
13350
|
// From current working directory
|
|
12979
13351
|
];
|
|
12980
13352
|
var currentVersion = "0.0.0";
|
|
12981
13353
|
var packageName = "sparkecoder";
|
|
12982
13354
|
for (const packageJsonPath of possiblePaths) {
|
|
12983
13355
|
try {
|
|
12984
|
-
const packageJson = JSON.parse(
|
|
13356
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
12985
13357
|
if (packageJson.name === "sparkecoder") {
|
|
12986
13358
|
currentVersion = packageJson.version || "0.0.0";
|
|
12987
13359
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -13799,12 +14171,13 @@ slack.post("/events", async (c) => {
|
|
|
13799
14171
|
if (inbound) {
|
|
13800
14172
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
13801
14173
|
if (isThreadReply) {
|
|
13802
|
-
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await
|
|
14174
|
+
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
|
|
13803
14175
|
if (!ours) {
|
|
13804
14176
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
13805
14177
|
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
13806
14178
|
return c.json({ ok: true });
|
|
13807
14179
|
}
|
|
14180
|
+
markThreadOwned(ev.channel, ev.thread_ts);
|
|
13808
14181
|
}
|
|
13809
14182
|
if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
|
|
13810
14183
|
markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
|
|
@@ -13843,18 +14216,6 @@ slack.post("/events", async (c) => {
|
|
|
13843
14216
|
}
|
|
13844
14217
|
return c.json({ ok: true });
|
|
13845
14218
|
});
|
|
13846
|
-
async function threadBelongsToUs(channel, threadTs) {
|
|
13847
|
-
try {
|
|
13848
|
-
const sessions3 = await sessionQueries.list(500, 0);
|
|
13849
|
-
return sessions3.some((s) => {
|
|
13850
|
-
const slack2 = s.config?.slack;
|
|
13851
|
-
return slack2?.channel === channel && slack2?.threadTs === threadTs;
|
|
13852
|
-
});
|
|
13853
|
-
} catch (err) {
|
|
13854
|
-
console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
|
|
13855
|
-
return false;
|
|
13856
|
-
}
|
|
13857
|
-
}
|
|
13858
14219
|
async function findOrCreateOrchestratorId() {
|
|
13859
14220
|
try {
|
|
13860
14221
|
const all = await sessionQueries.list(500, 0);
|
|
@@ -14237,6 +14598,224 @@ mcpRouter.post("/:id/test", async (c) => {
|
|
|
14237
14598
|
return c.json(result);
|
|
14238
14599
|
});
|
|
14239
14600
|
|
|
14601
|
+
// src/server/routes/skills.ts
|
|
14602
|
+
init_config();
|
|
14603
|
+
init_skills();
|
|
14604
|
+
import { Hono as Hono9 } from "hono";
|
|
14605
|
+
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
14606
|
+
import { z as z22 } from "zod";
|
|
14607
|
+
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
14608
|
+
import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
14609
|
+
import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
14610
|
+
var skills = new Hono9();
|
|
14611
|
+
function encodeId(filePath) {
|
|
14612
|
+
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
14613
|
+
}
|
|
14614
|
+
function decodeId(id) {
|
|
14615
|
+
try {
|
|
14616
|
+
const decoded = Buffer.from(id, "base64url").toString("utf-8");
|
|
14617
|
+
return decoded || null;
|
|
14618
|
+
} catch {
|
|
14619
|
+
return null;
|
|
14620
|
+
}
|
|
14621
|
+
}
|
|
14622
|
+
function listAllDirectories() {
|
|
14623
|
+
const cfg = getConfig();
|
|
14624
|
+
const discovered = discoverSkillDirectories(cfg.resolvedWorkingDirectory);
|
|
14625
|
+
const out = [];
|
|
14626
|
+
for (const { path, priority } of discovered.alwaysLoadedDirs) {
|
|
14627
|
+
out.push({
|
|
14628
|
+
path,
|
|
14629
|
+
source: pathToSource(path),
|
|
14630
|
+
label: pathToLabel(path),
|
|
14631
|
+
priority,
|
|
14632
|
+
alwaysApply: true
|
|
14633
|
+
});
|
|
14634
|
+
}
|
|
14635
|
+
for (const { path, priority } of discovered.onDemandDirs) {
|
|
14636
|
+
out.push({
|
|
14637
|
+
path,
|
|
14638
|
+
source: pathToSource(path),
|
|
14639
|
+
label: pathToLabel(path),
|
|
14640
|
+
priority,
|
|
14641
|
+
alwaysApply: false
|
|
14642
|
+
});
|
|
14643
|
+
}
|
|
14644
|
+
const additional = cfg.skills?.additionalDirectories || [];
|
|
14645
|
+
for (const dir of additional) {
|
|
14646
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, dir);
|
|
14647
|
+
if (out.some((d) => d.path === abs)) continue;
|
|
14648
|
+
out.push({
|
|
14649
|
+
path: abs,
|
|
14650
|
+
source: "additional",
|
|
14651
|
+
label: pathToLabel(abs),
|
|
14652
|
+
priority: 50,
|
|
14653
|
+
alwaysApply: false
|
|
14654
|
+
});
|
|
14655
|
+
}
|
|
14656
|
+
return out;
|
|
14657
|
+
}
|
|
14658
|
+
function pathToSource(path) {
|
|
14659
|
+
if (path.includes("/skills/default") || path.endsWith("/skills/default")) return "builtin";
|
|
14660
|
+
return "project";
|
|
14661
|
+
}
|
|
14662
|
+
function pathToLabel(path) {
|
|
14663
|
+
if (path.includes("/skills/default")) return "Built-in (default)";
|
|
14664
|
+
if (path.includes("/.sparkecoder/rules")) return ".sparkecoder/rules";
|
|
14665
|
+
if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
|
|
14666
|
+
if (path.includes("/.cursor/rules")) return ".cursor/rules";
|
|
14667
|
+
if (path.includes("/.claude/skills")) return ".claude/skills";
|
|
14668
|
+
return basename6(dirname9(path)) + "/" + basename6(path);
|
|
14669
|
+
}
|
|
14670
|
+
skills.get("/", async (c) => {
|
|
14671
|
+
const dirs = listAllDirectories();
|
|
14672
|
+
const allSkills = [];
|
|
14673
|
+
for (const dir of dirs) {
|
|
14674
|
+
if (!existsSync21(dir.path)) continue;
|
|
14675
|
+
try {
|
|
14676
|
+
const list = await loadSkillsFromDirectory(dir.path, {
|
|
14677
|
+
priority: dir.priority,
|
|
14678
|
+
defaultLoadType: dir.alwaysApply ? "always" : "on_demand",
|
|
14679
|
+
forceAlwaysApply: dir.alwaysApply
|
|
14680
|
+
});
|
|
14681
|
+
for (const s of list) {
|
|
14682
|
+
let size = 0;
|
|
14683
|
+
try {
|
|
14684
|
+
size = statSync3(s.filePath).size;
|
|
14685
|
+
} catch {
|
|
14686
|
+
}
|
|
14687
|
+
allSkills.push({
|
|
14688
|
+
id: encodeId(s.filePath),
|
|
14689
|
+
name: s.name,
|
|
14690
|
+
description: s.description,
|
|
14691
|
+
filePath: s.filePath,
|
|
14692
|
+
fileName: basename6(s.filePath),
|
|
14693
|
+
sourceDir: dir.path,
|
|
14694
|
+
sourceLabel: dir.label,
|
|
14695
|
+
sourceType: dir.source,
|
|
14696
|
+
alwaysApply: s.alwaysApply,
|
|
14697
|
+
globs: s.globs,
|
|
14698
|
+
sizeBytes: size
|
|
14699
|
+
});
|
|
14700
|
+
}
|
|
14701
|
+
} catch (err) {
|
|
14702
|
+
console.warn("[skills] failed to read", dir.path, err?.message || err);
|
|
14703
|
+
}
|
|
14704
|
+
}
|
|
14705
|
+
return c.json({
|
|
14706
|
+
directories: dirs.map((d) => ({
|
|
14707
|
+
path: d.path,
|
|
14708
|
+
label: d.label,
|
|
14709
|
+
source: d.source,
|
|
14710
|
+
alwaysApply: d.alwaysApply,
|
|
14711
|
+
exists: existsSync21(d.path),
|
|
14712
|
+
writable: isWritable(d.path)
|
|
14713
|
+
})),
|
|
14714
|
+
skills: allSkills
|
|
14715
|
+
});
|
|
14716
|
+
});
|
|
14717
|
+
function isWritable(dir) {
|
|
14718
|
+
try {
|
|
14719
|
+
if (!existsSync21(dir)) return false;
|
|
14720
|
+
if (dir.includes("/skills/default")) return false;
|
|
14721
|
+
return true;
|
|
14722
|
+
} catch {
|
|
14723
|
+
return false;
|
|
14724
|
+
}
|
|
14725
|
+
}
|
|
14726
|
+
skills.get("/:id", async (c) => {
|
|
14727
|
+
const filePath = decodeId(c.req.param("id"));
|
|
14728
|
+
if (!filePath || !existsSync21(filePath)) {
|
|
14729
|
+
return c.json({ error: "skill not found" }, 404);
|
|
14730
|
+
}
|
|
14731
|
+
const content = await readFile12(filePath, "utf-8");
|
|
14732
|
+
return c.json({
|
|
14733
|
+
id: c.req.param("id"),
|
|
14734
|
+
filePath,
|
|
14735
|
+
fileName: basename6(filePath),
|
|
14736
|
+
content,
|
|
14737
|
+
writable: !filePath.includes("/skills/default")
|
|
14738
|
+
});
|
|
14739
|
+
});
|
|
14740
|
+
skills.post(
|
|
14741
|
+
"/",
|
|
14742
|
+
zValidator7("json", z22.object({
|
|
14743
|
+
directory: z22.string().min(1),
|
|
14744
|
+
fileName: z22.string().min(1),
|
|
14745
|
+
content: z22.string()
|
|
14746
|
+
})),
|
|
14747
|
+
async (c) => {
|
|
14748
|
+
const { directory, fileName, content } = c.req.valid("json");
|
|
14749
|
+
const cfg = getConfig();
|
|
14750
|
+
const targetDir = resolve11(cfg.resolvedWorkingDirectory, directory);
|
|
14751
|
+
if (targetDir.includes("/skills/default")) {
|
|
14752
|
+
return c.json({ error: "cannot write into built-in skills directory" }, 400);
|
|
14753
|
+
}
|
|
14754
|
+
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
14755
|
+
const ext = extname9(safeName).toLowerCase();
|
|
14756
|
+
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
14757
|
+
const filePath = join16(targetDir, finalName);
|
|
14758
|
+
if (existsSync21(filePath)) {
|
|
14759
|
+
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
14760
|
+
}
|
|
14761
|
+
try {
|
|
14762
|
+
await mkdir5(targetDir, { recursive: true });
|
|
14763
|
+
await writeFile6(filePath, content, "utf-8");
|
|
14764
|
+
} catch (err) {
|
|
14765
|
+
return c.json({ error: err?.message || "write failed" }, 500);
|
|
14766
|
+
}
|
|
14767
|
+
return c.json({ id: encodeId(filePath), filePath, fileName: finalName }, 201);
|
|
14768
|
+
}
|
|
14769
|
+
);
|
|
14770
|
+
skills.put(
|
|
14771
|
+
"/:id",
|
|
14772
|
+
zValidator7("json", z22.object({ content: z22.string() })),
|
|
14773
|
+
async (c) => {
|
|
14774
|
+
const filePath = decodeId(c.req.param("id"));
|
|
14775
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14776
|
+
if (filePath.includes("/skills/default")) {
|
|
14777
|
+
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14778
|
+
}
|
|
14779
|
+
await writeFile6(filePath, c.req.valid("json").content, "utf-8");
|
|
14780
|
+
return c.json({ ok: true });
|
|
14781
|
+
}
|
|
14782
|
+
);
|
|
14783
|
+
skills.delete("/:id", async (c) => {
|
|
14784
|
+
const filePath = decodeId(c.req.param("id"));
|
|
14785
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
14786
|
+
if (filePath.includes("/skills/default")) {
|
|
14787
|
+
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
14788
|
+
}
|
|
14789
|
+
await unlink3(filePath);
|
|
14790
|
+
return c.json({ ok: true });
|
|
14791
|
+
});
|
|
14792
|
+
skills.post(
|
|
14793
|
+
"/directories",
|
|
14794
|
+
zValidator7("json", z22.object({ path: z22.string().min(1) })),
|
|
14795
|
+
(c) => {
|
|
14796
|
+
const cfg = getConfig();
|
|
14797
|
+
const inputPath = c.req.valid("json").path.trim();
|
|
14798
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
|
|
14799
|
+
const current = cfg.skills?.additionalDirectories || [];
|
|
14800
|
+
if (current.some((d) => resolve11(cfg.resolvedWorkingDirectory, d) === abs)) {
|
|
14801
|
+
return c.json({ error: "directory already added" }, 409);
|
|
14802
|
+
}
|
|
14803
|
+
const next = [...current, abs];
|
|
14804
|
+
setSkillsAdditionalDirectories(next);
|
|
14805
|
+
return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
|
|
14806
|
+
}
|
|
14807
|
+
);
|
|
14808
|
+
skills.delete("/directories", (c) => {
|
|
14809
|
+
const inputPath = c.req.query("path");
|
|
14810
|
+
if (!inputPath) return c.json({ error: "path query param required" }, 400);
|
|
14811
|
+
const cfg = getConfig();
|
|
14812
|
+
const abs = resolve11(cfg.resolvedWorkingDirectory, inputPath);
|
|
14813
|
+
const current = cfg.skills?.additionalDirectories || [];
|
|
14814
|
+
const next = current.filter((d) => resolve11(cfg.resolvedWorkingDirectory, d) !== abs);
|
|
14815
|
+
setSkillsAdditionalDirectories(next);
|
|
14816
|
+
return c.json({ ok: true });
|
|
14817
|
+
});
|
|
14818
|
+
|
|
14240
14819
|
// src/server/auth/cf-access.ts
|
|
14241
14820
|
init_config();
|
|
14242
14821
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
@@ -14397,13 +14976,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
14397
14976
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
14398
14977
|
function getWebDirectory() {
|
|
14399
14978
|
try {
|
|
14400
|
-
const currentDir =
|
|
14401
|
-
const webDir =
|
|
14402
|
-
if (
|
|
14979
|
+
const currentDir = dirname10(fileURLToPath4(import.meta.url));
|
|
14980
|
+
const webDir = resolve12(currentDir, "..", "web");
|
|
14981
|
+
if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
|
|
14403
14982
|
return webDir;
|
|
14404
14983
|
}
|
|
14405
|
-
const altWebDir =
|
|
14406
|
-
if (
|
|
14984
|
+
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
14985
|
+
if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
|
|
14407
14986
|
return altWebDir;
|
|
14408
14987
|
}
|
|
14409
14988
|
return null;
|
|
@@ -14426,18 +15005,18 @@ async function isSparkcoderWebRunning(port) {
|
|
|
14426
15005
|
}
|
|
14427
15006
|
}
|
|
14428
15007
|
function isPortInUse(port) {
|
|
14429
|
-
return new Promise((
|
|
15008
|
+
return new Promise((resolve13) => {
|
|
14430
15009
|
const server = createNetServer();
|
|
14431
15010
|
server.once("error", (err) => {
|
|
14432
15011
|
if (err.code === "EADDRINUSE") {
|
|
14433
|
-
|
|
15012
|
+
resolve13(true);
|
|
14434
15013
|
} else {
|
|
14435
|
-
|
|
15014
|
+
resolve13(false);
|
|
14436
15015
|
}
|
|
14437
15016
|
});
|
|
14438
15017
|
server.once("listening", () => {
|
|
14439
15018
|
server.close();
|
|
14440
|
-
|
|
15019
|
+
resolve13(false);
|
|
14441
15020
|
});
|
|
14442
15021
|
server.listen(port, "0.0.0.0");
|
|
14443
15022
|
});
|
|
@@ -14461,30 +15040,30 @@ async function findWebPort(preferredPort) {
|
|
|
14461
15040
|
return { port: preferredPort, alreadyRunning: false };
|
|
14462
15041
|
}
|
|
14463
15042
|
function hasProductionBuild(webDir) {
|
|
14464
|
-
const buildIdPath =
|
|
14465
|
-
return
|
|
15043
|
+
const buildIdPath = join17(webDir, ".next", "BUILD_ID");
|
|
15044
|
+
return existsSync22(buildIdPath);
|
|
14466
15045
|
}
|
|
14467
15046
|
function hasSourceFiles(webDir) {
|
|
14468
|
-
const appDir =
|
|
14469
|
-
const pagesDir =
|
|
14470
|
-
const rootAppDir =
|
|
14471
|
-
const rootPagesDir =
|
|
14472
|
-
return
|
|
15047
|
+
const appDir = join17(webDir, "src", "app");
|
|
15048
|
+
const pagesDir = join17(webDir, "src", "pages");
|
|
15049
|
+
const rootAppDir = join17(webDir, "app");
|
|
15050
|
+
const rootPagesDir = join17(webDir, "pages");
|
|
15051
|
+
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
14473
15052
|
}
|
|
14474
15053
|
function getStandaloneServerPath(webDir) {
|
|
14475
15054
|
const possiblePaths2 = [
|
|
14476
|
-
|
|
14477
|
-
|
|
15055
|
+
join17(webDir, ".next", "standalone", "server.js"),
|
|
15056
|
+
join17(webDir, ".next", "standalone", "web", "server.js")
|
|
14478
15057
|
];
|
|
14479
15058
|
for (const serverPath of possiblePaths2) {
|
|
14480
|
-
if (
|
|
15059
|
+
if (existsSync22(serverPath)) {
|
|
14481
15060
|
return serverPath;
|
|
14482
15061
|
}
|
|
14483
15062
|
}
|
|
14484
15063
|
return null;
|
|
14485
15064
|
}
|
|
14486
15065
|
function runCommand(command, args, cwd, env) {
|
|
14487
|
-
return new Promise((
|
|
15066
|
+
return new Promise((resolve13) => {
|
|
14488
15067
|
const child = spawn2(command, args, {
|
|
14489
15068
|
cwd,
|
|
14490
15069
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -14499,10 +15078,10 @@ function runCommand(command, args, cwd, env) {
|
|
|
14499
15078
|
output += data.toString();
|
|
14500
15079
|
});
|
|
14501
15080
|
child.on("close", (code) => {
|
|
14502
|
-
|
|
15081
|
+
resolve13({ success: code === 0, output });
|
|
14503
15082
|
});
|
|
14504
15083
|
child.on("error", (err) => {
|
|
14505
|
-
|
|
15084
|
+
resolve13({ success: false, output: err.message });
|
|
14506
15085
|
});
|
|
14507
15086
|
});
|
|
14508
15087
|
}
|
|
@@ -14517,15 +15096,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14517
15096
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
14518
15097
|
return { process: null, port: actualPort };
|
|
14519
15098
|
}
|
|
14520
|
-
const usePnpm =
|
|
14521
|
-
const useNpm = !usePnpm &&
|
|
15099
|
+
const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
|
|
15100
|
+
const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
|
|
14522
15101
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
14523
15102
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
14524
15103
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
14525
15104
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
14526
|
-
const runtimeConfigPath =
|
|
15105
|
+
const runtimeConfigPath = join17(webDir, "runtime-config.json");
|
|
14527
15106
|
try {
|
|
14528
|
-
|
|
15107
|
+
writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
14529
15108
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
14530
15109
|
} catch (err) {
|
|
14531
15110
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -14545,7 +15124,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14545
15124
|
if (standaloneServerPath) {
|
|
14546
15125
|
command = "node";
|
|
14547
15126
|
args = ["server.js"];
|
|
14548
|
-
cwd =
|
|
15127
|
+
cwd = dirname10(standaloneServerPath);
|
|
14549
15128
|
webEnv.PORT = String(actualPort);
|
|
14550
15129
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
14551
15130
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -14586,10 +15165,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14586
15165
|
let started = false;
|
|
14587
15166
|
let exited = false;
|
|
14588
15167
|
let exitCode = null;
|
|
14589
|
-
const startedPromise = new Promise((
|
|
15168
|
+
const startedPromise = new Promise((resolve13) => {
|
|
14590
15169
|
const timeout = setTimeout(() => {
|
|
14591
15170
|
if (!started && !exited) {
|
|
14592
|
-
|
|
15171
|
+
resolve13(false);
|
|
14593
15172
|
}
|
|
14594
15173
|
}, startupTimeout);
|
|
14595
15174
|
child.stdout?.on("data", (data) => {
|
|
@@ -14603,7 +15182,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14603
15182
|
if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
14604
15183
|
started = true;
|
|
14605
15184
|
clearTimeout(timeout);
|
|
14606
|
-
|
|
15185
|
+
resolve13(true);
|
|
14607
15186
|
}
|
|
14608
15187
|
});
|
|
14609
15188
|
child.stderr?.on("data", (data) => {
|
|
@@ -14615,14 +15194,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14615
15194
|
child.on("error", (err) => {
|
|
14616
15195
|
if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
|
|
14617
15196
|
clearTimeout(timeout);
|
|
14618
|
-
|
|
15197
|
+
resolve13(false);
|
|
14619
15198
|
});
|
|
14620
15199
|
child.on("exit", (code) => {
|
|
14621
15200
|
exited = true;
|
|
14622
15201
|
exitCode = code;
|
|
14623
15202
|
if (!started) {
|
|
14624
15203
|
clearTimeout(timeout);
|
|
14625
|
-
|
|
15204
|
+
resolve13(false);
|
|
14626
15205
|
}
|
|
14627
15206
|
webUIProcess = null;
|
|
14628
15207
|
});
|
|
@@ -14645,7 +15224,7 @@ function stopWebUI() {
|
|
|
14645
15224
|
}
|
|
14646
15225
|
}
|
|
14647
15226
|
async function createApp(options = {}) {
|
|
14648
|
-
const app = new
|
|
15227
|
+
const app = new Hono10();
|
|
14649
15228
|
app.use("*", cors({
|
|
14650
15229
|
origin: "*",
|
|
14651
15230
|
// Allow all origins
|
|
@@ -14673,6 +15252,7 @@ async function createApp(options = {}) {
|
|
|
14673
15252
|
app.route("/api/schedules", schedulesRouter);
|
|
14674
15253
|
app.route("/api/orchestrator", orchestratorRouter);
|
|
14675
15254
|
app.route("/api/mcp", mcpRouter);
|
|
15255
|
+
app.route("/api/skills", skills);
|
|
14676
15256
|
app.route("/api/webhooks", webhooksRouter);
|
|
14677
15257
|
const config = getConfig();
|
|
14678
15258
|
const webhookToken = config?.webhooks?.token;
|
|
@@ -14738,8 +15318,8 @@ async function startServer(options = {}) {
|
|
|
14738
15318
|
if (options.workingDirectory) {
|
|
14739
15319
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
14740
15320
|
}
|
|
14741
|
-
if (!
|
|
14742
|
-
|
|
15321
|
+
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
15322
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
14743
15323
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
14744
15324
|
}
|
|
14745
15325
|
if (!config.resolvedRemoteServer.url) {
|