sparkecoder 0.1.121 → 0.1.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.js +182 -21
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +483 -157
- package/dist/cli.js.map +1 -1
- package/dist/index.js +444 -118
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +444 -118
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_buildManifest.js +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → BEIBC9-dP0_AWGmRy97hJ}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -2330,10 +2330,10 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2330
2330
|
const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
|
|
2331
2331
|
const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
|
|
2332
2332
|
const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
|
|
2333
|
-
const
|
|
2334
|
-
if (existsSync3(
|
|
2333
|
+
const cachePath2 = join3(cacheDir, key2 + ext);
|
|
2334
|
+
if (existsSync3(cachePath2)) {
|
|
2335
2335
|
console.log(`[image-resize] Cache hit for ${width}x${height} image`);
|
|
2336
|
-
return { buffer: readFileSync2(
|
|
2336
|
+
return { buffer: readFileSync2(cachePath2), mediaType: outputMediaType };
|
|
2337
2337
|
}
|
|
2338
2338
|
let pipeline = sharp(buffer);
|
|
2339
2339
|
if (needsResize) {
|
|
@@ -2358,7 +2358,7 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2358
2358
|
}
|
|
2359
2359
|
finalMediaType = "image/jpeg";
|
|
2360
2360
|
}
|
|
2361
|
-
writeFileSync2(
|
|
2361
|
+
writeFileSync2(cachePath2, result);
|
|
2362
2362
|
const resultMeta = await sharp(result).metadata();
|
|
2363
2363
|
console.log(
|
|
2364
2364
|
`[image-resize] ${width}x${height} -> ${resultMeta.width}x${resultMeta.height} (${(buffer.length / 1024).toFixed(0)}KB -> ${(result.length / 1024).toFixed(0)}KB, ${finalMediaType})`
|
|
@@ -5415,7 +5415,7 @@ function isPathExcluded(relativePath, exclude) {
|
|
|
5415
5415
|
}
|
|
5416
5416
|
async function walkDirectory(dir, include, exclude, baseDir) {
|
|
5417
5417
|
const { readdirSync: readdirSync4 } = await import("fs");
|
|
5418
|
-
const { join:
|
|
5418
|
+
const { join: join19, relative: relative10 } = await import("path");
|
|
5419
5419
|
const files = [];
|
|
5420
5420
|
function walk(currentDir) {
|
|
5421
5421
|
let entries;
|
|
@@ -5425,7 +5425,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
|
|
|
5425
5425
|
return;
|
|
5426
5426
|
}
|
|
5427
5427
|
for (const entry2 of entries) {
|
|
5428
|
-
const fullPath =
|
|
5428
|
+
const fullPath = join19(currentDir, entry2.name);
|
|
5429
5429
|
const relativePath = relative10(baseDir, fullPath);
|
|
5430
5430
|
if (isPathExcluded(relativePath, exclude)) {
|
|
5431
5431
|
continue;
|
|
@@ -8173,6 +8173,35 @@ function stripOrphanedToolResults(msg, removedIds) {
|
|
|
8173
8173
|
if (parts.length === 0) return null;
|
|
8174
8174
|
return { ...msg, content: parts };
|
|
8175
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
|
+
}
|
|
8176
8205
|
function repairToolPairing(messages) {
|
|
8177
8206
|
const toolCallIds = /* @__PURE__ */ new Set();
|
|
8178
8207
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -8538,6 +8567,120 @@ var init_web = __esm({
|
|
|
8538
8567
|
}
|
|
8539
8568
|
});
|
|
8540
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
|
+
|
|
8541
8684
|
// src/integrations/slack/client.ts
|
|
8542
8685
|
function readSlackConfig() {
|
|
8543
8686
|
try {
|
|
@@ -8660,13 +8803,13 @@ async function fetchSlackUserName(userId) {
|
|
|
8660
8803
|
async function resolveSlackUserName(userId) {
|
|
8661
8804
|
if (!userId) return null;
|
|
8662
8805
|
const now = Date.now();
|
|
8663
|
-
const hit =
|
|
8806
|
+
const hit = getCachedUserName(userId);
|
|
8664
8807
|
if (hit && hit.expiresAt > now) return hit.name;
|
|
8665
8808
|
const inflight = userInflight.get(userId);
|
|
8666
8809
|
if (inflight) return inflight;
|
|
8667
8810
|
const p = (async () => {
|
|
8668
8811
|
const name = await fetchSlackUserName(userId);
|
|
8669
|
-
|
|
8812
|
+
setCachedUserName(userId, {
|
|
8670
8813
|
name,
|
|
8671
8814
|
expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
8672
8815
|
});
|
|
@@ -8688,11 +8831,63 @@ async function normalizeSlackMentions(text) {
|
|
|
8688
8831
|
}
|
|
8689
8832
|
return text.replace(userMentionRe, (_full, id, label) => {
|
|
8690
8833
|
if (label) return `${label} <@${id}>`;
|
|
8691
|
-
const cached =
|
|
8834
|
+
const cached = getCachedUserName(id);
|
|
8692
8835
|
const name = cached?.name;
|
|
8693
8836
|
return name ? `${name} <@${id}>` : `<@${id}>`;
|
|
8694
8837
|
});
|
|
8695
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
|
+
}
|
|
8696
8891
|
function getSlackDeniedReplyPolicy() {
|
|
8697
8892
|
try {
|
|
8698
8893
|
const cfg = getConfig();
|
|
@@ -8705,17 +8900,20 @@ function getSlackDeniedReplyPolicy() {
|
|
|
8705
8900
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
8706
8901
|
}
|
|
8707
8902
|
}
|
|
8708
|
-
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;
|
|
8709
8904
|
var init_client3 = __esm({
|
|
8710
8905
|
"src/integrations/slack/client.ts"() {
|
|
8711
8906
|
"use strict";
|
|
8712
8907
|
init_config();
|
|
8908
|
+
init_persistence();
|
|
8713
8909
|
cachedSelf = null;
|
|
8714
8910
|
selfInflight = null;
|
|
8715
8911
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
8716
8912
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
8717
|
-
userNameCache = /* @__PURE__ */ new Map();
|
|
8718
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();
|
|
8719
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.)";
|
|
8720
8918
|
}
|
|
8721
8919
|
});
|
|
@@ -8815,6 +9013,7 @@ var init_slack = __esm({
|
|
|
8815
9013
|
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
8816
9014
|
if (r.slackChannel && r.threadTs) {
|
|
8817
9015
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
9016
|
+
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8818
9017
|
}
|
|
8819
9018
|
},
|
|
8820
9019
|
displayLabel(ref) {
|
|
@@ -9472,8 +9671,8 @@ var init_orchestrator_actions = __esm({
|
|
|
9472
9671
|
|
|
9473
9672
|
// src/integrations/mcp/store.ts
|
|
9474
9673
|
import { nanoid as nanoid6 } from "nanoid";
|
|
9475
|
-
import { existsSync as
|
|
9476
|
-
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";
|
|
9477
9676
|
function readServers() {
|
|
9478
9677
|
try {
|
|
9479
9678
|
const cfg = getConfig();
|
|
@@ -9485,12 +9684,12 @@ function readServers() {
|
|
|
9485
9684
|
function refreshMcpServersFromDisk() {
|
|
9486
9685
|
const candidates = [
|
|
9487
9686
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9488
|
-
|
|
9687
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9489
9688
|
];
|
|
9490
9689
|
for (const path of candidates) {
|
|
9491
|
-
if (!
|
|
9690
|
+
if (!existsSync17(path)) continue;
|
|
9492
9691
|
try {
|
|
9493
|
-
const raw = JSON.parse(
|
|
9692
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
9494
9693
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9495
9694
|
setMcpServers(servers2);
|
|
9496
9695
|
return servers2;
|
|
@@ -10103,7 +10302,7 @@ __export(recorder_exports, {
|
|
|
10103
10302
|
import { exec as exec5 } from "child_process";
|
|
10104
10303
|
import { promisify as promisify5 } from "util";
|
|
10105
10304
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
10106
|
-
import { join as
|
|
10305
|
+
import { join as join11 } from "path";
|
|
10107
10306
|
import { tmpdir } from "os";
|
|
10108
10307
|
import { nanoid as nanoid7 } from "nanoid";
|
|
10109
10308
|
async function checkFfmpeg() {
|
|
@@ -10160,21 +10359,21 @@ var init_recorder = __esm({
|
|
|
10160
10359
|
*/
|
|
10161
10360
|
async encode() {
|
|
10162
10361
|
if (this.frames.length === 0) return null;
|
|
10163
|
-
const workDir =
|
|
10362
|
+
const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
10164
10363
|
await mkdir4(workDir, { recursive: true });
|
|
10165
10364
|
try {
|
|
10166
10365
|
for (let i = 0; i < this.frames.length; i++) {
|
|
10167
|
-
const framePath =
|
|
10366
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
10168
10367
|
await writeFile5(framePath, this.frames[i].data);
|
|
10169
10368
|
}
|
|
10170
10369
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
10171
10370
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
10172
10371
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
10173
|
-
const outputPath =
|
|
10372
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
10174
10373
|
const hasFfmpeg = await checkFfmpeg();
|
|
10175
10374
|
if (hasFfmpeg) {
|
|
10176
10375
|
await execAsync5(
|
|
10177
|
-
`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}"`,
|
|
10178
10377
|
{ timeout: 12e4 }
|
|
10179
10378
|
);
|
|
10180
10379
|
} else {
|
|
@@ -10186,7 +10385,7 @@ var init_recorder = __esm({
|
|
|
10186
10385
|
const files = await readdir5(workDir);
|
|
10187
10386
|
for (const f of files) {
|
|
10188
10387
|
if (f.startsWith("frame_")) {
|
|
10189
|
-
await unlink2(
|
|
10388
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
10190
10389
|
});
|
|
10191
10390
|
}
|
|
10192
10391
|
}
|
|
@@ -10453,7 +10652,8 @@ ${personality.trim()}`;
|
|
|
10453
10652
|
}
|
|
10454
10653
|
const messages = await this.context.getMessages();
|
|
10455
10654
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
10456
|
-
const
|
|
10655
|
+
const approvalWrapped = this.wrapToolsWithApproval(options, tools);
|
|
10656
|
+
const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
|
|
10457
10657
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10458
10658
|
const stream = streamText2({
|
|
10459
10659
|
model: resolveModel(this.session.model),
|
|
@@ -10467,6 +10667,17 @@ ${personality.trim()}`;
|
|
|
10467
10667
|
providerOptions: useAnthropic ? {
|
|
10468
10668
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10469
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
|
+
},
|
|
10470
10681
|
onStepFinish: async (step) => {
|
|
10471
10682
|
options.onStepFinish?.(step);
|
|
10472
10683
|
},
|
|
@@ -10502,7 +10713,7 @@ ${personality.trim()}`;
|
|
|
10502
10713
|
});
|
|
10503
10714
|
const messages = await this.context.getMessages();
|
|
10504
10715
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
10505
|
-
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
10716
|
+
const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
|
|
10506
10717
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10507
10718
|
const result = await generateText3({
|
|
10508
10719
|
model: resolveModel(this.session.model),
|
|
@@ -10513,7 +10724,13 @@ ${personality.trim()}`;
|
|
|
10513
10724
|
// Enable extended thinking/reasoning for models that support it
|
|
10514
10725
|
providerOptions: useAnthropic ? {
|
|
10515
10726
|
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
10516
|
-
} : 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
|
+
}
|
|
10517
10734
|
});
|
|
10518
10735
|
const responseMessages = result.response.messages;
|
|
10519
10736
|
this.context.addResponseMessages(responseMessages);
|
|
@@ -10690,12 +10907,19 @@ ${p.text}` : p.text;
|
|
|
10690
10907
|
model: resolveModel(this.session.model),
|
|
10691
10908
|
system: systemPrompt,
|
|
10692
10909
|
messages,
|
|
10693
|
-
tools: taskTools,
|
|
10910
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
10694
10911
|
stopWhen: stepCountIs2(500),
|
|
10695
10912
|
abortSignal: combinedAbort,
|
|
10696
10913
|
providerOptions: useAnthropic ? {
|
|
10697
10914
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10698
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
|
+
},
|
|
10699
10923
|
onStepFinish: async (step) => {
|
|
10700
10924
|
options.onStepFinish?.(step);
|
|
10701
10925
|
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
@@ -10929,11 +11153,11 @@ ${p.text}` : p.text;
|
|
|
10929
11153
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10930
11154
|
if (!isRemoteConfigured2()) return [];
|
|
10931
11155
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
10932
|
-
const { join:
|
|
11156
|
+
const { join: join19, basename: basename7 } = await import("path");
|
|
10933
11157
|
const urls = [];
|
|
10934
11158
|
for (const filePath of filePaths) {
|
|
10935
11159
|
try {
|
|
10936
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
11160
|
+
const fullPath = filePath.startsWith("/") ? filePath : join19(this.session.workingDirectory, filePath);
|
|
10937
11161
|
const fileName = basename7(fullPath);
|
|
10938
11162
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10939
11163
|
const mimeMap = {
|
|
@@ -11138,19 +11362,19 @@ var init_session_lock = __esm({
|
|
|
11138
11362
|
});
|
|
11139
11363
|
|
|
11140
11364
|
// src/orchestrator/webhook-events.ts
|
|
11141
|
-
import { existsSync as
|
|
11142
|
-
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";
|
|
11143
11367
|
import { nanoid as nanoid9 } from "nanoid";
|
|
11144
11368
|
function logFilePath() {
|
|
11145
|
-
return
|
|
11369
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
11146
11370
|
}
|
|
11147
11371
|
function ensureLoaded() {
|
|
11148
11372
|
if (cache !== null) return cache;
|
|
11149
11373
|
cache = [];
|
|
11150
11374
|
try {
|
|
11151
11375
|
const p = logFilePath();
|
|
11152
|
-
if (!
|
|
11153
|
-
const lines =
|
|
11376
|
+
if (!existsSync18(p)) return cache;
|
|
11377
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
11154
11378
|
for (const line of lines) {
|
|
11155
11379
|
try {
|
|
11156
11380
|
cache.push(JSON.parse(line));
|
|
@@ -11160,7 +11384,7 @@ function ensureLoaded() {
|
|
|
11160
11384
|
if (cache.length > MAX_EVENTS) {
|
|
11161
11385
|
cache = cache.slice(-MAX_EVENTS);
|
|
11162
11386
|
try {
|
|
11163
|
-
|
|
11387
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11164
11388
|
} catch {
|
|
11165
11389
|
}
|
|
11166
11390
|
}
|
|
@@ -11174,7 +11398,7 @@ function appendEvent(ev) {
|
|
|
11174
11398
|
if (list.length > MAX_EVENTS) list.shift();
|
|
11175
11399
|
try {
|
|
11176
11400
|
const p = logFilePath();
|
|
11177
|
-
|
|
11401
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
11178
11402
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
11179
11403
|
} catch {
|
|
11180
11404
|
}
|
|
@@ -11205,8 +11429,8 @@ function updateEvent(id, patch) {
|
|
|
11205
11429
|
list[i] = { ...list[i], ...patch };
|
|
11206
11430
|
try {
|
|
11207
11431
|
const p = logFilePath();
|
|
11208
|
-
|
|
11209
|
-
|
|
11432
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
11433
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11210
11434
|
} catch {
|
|
11211
11435
|
}
|
|
11212
11436
|
}
|
|
@@ -11238,7 +11462,7 @@ function listEvents(filter = {}) {
|
|
|
11238
11462
|
function clearAllEvents() {
|
|
11239
11463
|
cache = [];
|
|
11240
11464
|
try {
|
|
11241
|
-
|
|
11465
|
+
writeFileSync4(logFilePath(), "");
|
|
11242
11466
|
} catch {
|
|
11243
11467
|
}
|
|
11244
11468
|
}
|
|
@@ -11728,7 +11952,7 @@ import chalk from "chalk";
|
|
|
11728
11952
|
import ora from "ora";
|
|
11729
11953
|
import "dotenv/config";
|
|
11730
11954
|
import { createInterface } from "readline";
|
|
11731
|
-
import { dirname as
|
|
11955
|
+
import { dirname as dirname11 } from "path";
|
|
11732
11956
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11733
11957
|
|
|
11734
11958
|
// src/server/index.ts
|
|
@@ -11737,8 +11961,8 @@ import { Hono as Hono10 } from "hono";
|
|
|
11737
11961
|
import { serve } from "@hono/node-server";
|
|
11738
11962
|
import { cors } from "hono/cors";
|
|
11739
11963
|
import { logger } from "hono/logger";
|
|
11740
|
-
import { existsSync as
|
|
11741
|
-
import { resolve as resolve12, dirname 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";
|
|
11742
11966
|
import { spawn as spawn2 } from "child_process";
|
|
11743
11967
|
import { createServer as createNetServer } from "net";
|
|
11744
11968
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -11752,9 +11976,9 @@ init_checkpoints();
|
|
|
11752
11976
|
import { Hono } from "hono";
|
|
11753
11977
|
import { zValidator } from "@hono/zod-validator";
|
|
11754
11978
|
import { z as z16 } from "zod";
|
|
11755
|
-
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";
|
|
11756
11980
|
import { readdir as readdir6 } from "fs/promises";
|
|
11757
|
-
import { join as
|
|
11981
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
11758
11982
|
import { nanoid as nanoid10 } from "nanoid";
|
|
11759
11983
|
|
|
11760
11984
|
// src/tasks/agent-status.ts
|
|
@@ -12392,12 +12616,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
12392
12616
|
});
|
|
12393
12617
|
function getAttachmentsDir(sessionId) {
|
|
12394
12618
|
const appDataDir = getAppDataDirectory();
|
|
12395
|
-
return
|
|
12619
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
12396
12620
|
}
|
|
12397
12621
|
function ensureAttachmentsDir(sessionId) {
|
|
12398
12622
|
const dir = getAttachmentsDir(sessionId);
|
|
12399
|
-
if (!
|
|
12400
|
-
|
|
12623
|
+
if (!existsSync19(dir)) {
|
|
12624
|
+
mkdirSync8(dir, { recursive: true });
|
|
12401
12625
|
}
|
|
12402
12626
|
return dir;
|
|
12403
12627
|
}
|
|
@@ -12408,12 +12632,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
12408
12632
|
return c.json({ error: "Session not found" }, 404);
|
|
12409
12633
|
}
|
|
12410
12634
|
const dir = getAttachmentsDir(sessionId);
|
|
12411
|
-
if (!
|
|
12635
|
+
if (!existsSync19(dir)) {
|
|
12412
12636
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
12413
12637
|
}
|
|
12414
12638
|
const files = readdirSync3(dir);
|
|
12415
12639
|
const attachments = files.map((filename) => {
|
|
12416
|
-
const filePath =
|
|
12640
|
+
const filePath = join13(dir, filename);
|
|
12417
12641
|
const stats = statSync2(filePath);
|
|
12418
12642
|
return {
|
|
12419
12643
|
id: filename.split("_")[0],
|
|
@@ -12448,9 +12672,9 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12448
12672
|
const id = nanoid10(10);
|
|
12449
12673
|
const ext = extname8(file.name) || "";
|
|
12450
12674
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12451
|
-
const filePath =
|
|
12675
|
+
const filePath = join13(dir, safeFilename);
|
|
12452
12676
|
const arrayBuffer = await file.arrayBuffer();
|
|
12453
|
-
|
|
12677
|
+
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
12454
12678
|
return c.json({
|
|
12455
12679
|
id,
|
|
12456
12680
|
filename: file.name,
|
|
@@ -12474,13 +12698,13 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12474
12698
|
const id = nanoid10(10);
|
|
12475
12699
|
const ext = extname8(body.filename) || "";
|
|
12476
12700
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12477
|
-
const filePath =
|
|
12701
|
+
const filePath = join13(dir, safeFilename);
|
|
12478
12702
|
let base64Data = body.data;
|
|
12479
12703
|
if (base64Data.includes(",")) {
|
|
12480
12704
|
base64Data = base64Data.split(",")[1];
|
|
12481
12705
|
}
|
|
12482
12706
|
const buffer = Buffer.from(base64Data, "base64");
|
|
12483
|
-
|
|
12707
|
+
writeFileSync5(filePath, buffer);
|
|
12484
12708
|
return c.json({
|
|
12485
12709
|
id,
|
|
12486
12710
|
filename: body.filename,
|
|
@@ -12503,7 +12727,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12503
12727
|
return c.json({ error: "Session not found" }, 404);
|
|
12504
12728
|
}
|
|
12505
12729
|
const dir = getAttachmentsDir(sessionId);
|
|
12506
|
-
if (!
|
|
12730
|
+
if (!existsSync19(dir)) {
|
|
12507
12731
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12508
12732
|
}
|
|
12509
12733
|
const files = readdirSync3(dir);
|
|
@@ -12511,7 +12735,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12511
12735
|
if (!file) {
|
|
12512
12736
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12513
12737
|
}
|
|
12514
|
-
const filePath =
|
|
12738
|
+
const filePath = join13(dir, file);
|
|
12515
12739
|
unlinkSync2(filePath);
|
|
12516
12740
|
return c.json({ success: true, id: attachmentId });
|
|
12517
12741
|
});
|
|
@@ -12594,7 +12818,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12594
12818
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12595
12819
|
for (const entry2 of entries) {
|
|
12596
12820
|
if (results.length >= limit * 2) break;
|
|
12597
|
-
const fullPath =
|
|
12821
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
12598
12822
|
const relativePath = relative9(baseDir, fullPath);
|
|
12599
12823
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12600
12824
|
continue;
|
|
@@ -12642,7 +12866,7 @@ sessions2.get(
|
|
|
12642
12866
|
return c.json({ error: "Session not found" }, 404);
|
|
12643
12867
|
}
|
|
12644
12868
|
const workingDirectory = session.workingDirectory;
|
|
12645
|
-
if (!
|
|
12869
|
+
if (!existsSync19(workingDirectory)) {
|
|
12646
12870
|
return c.json({
|
|
12647
12871
|
sessionId,
|
|
12648
12872
|
workingDirectory,
|
|
@@ -12750,14 +12974,101 @@ sessions2.get("/:id/browser-recording", async (c) => {
|
|
|
12750
12974
|
|
|
12751
12975
|
// src/server/routes/agents.ts
|
|
12752
12976
|
init_db();
|
|
12753
|
-
init_agent();
|
|
12754
|
-
init_session_lock();
|
|
12755
|
-
init_config();
|
|
12756
12977
|
import { Hono as Hono2 } from "hono";
|
|
12757
12978
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12758
12979
|
import { z as z17 } from "zod";
|
|
12759
|
-
import { existsSync as
|
|
12760
|
-
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();
|
|
12761
13072
|
|
|
12762
13073
|
// src/server/resumable-stream.ts
|
|
12763
13074
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -12964,12 +13275,12 @@ var rejectSchema = z17.object({
|
|
|
12964
13275
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12965
13276
|
function getAttachmentsDirectory(sessionId) {
|
|
12966
13277
|
const appDataDir = getAppDataDirectory();
|
|
12967
|
-
return
|
|
13278
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
12968
13279
|
}
|
|
12969
13280
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12970
13281
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
12971
|
-
if (!
|
|
12972
|
-
|
|
13282
|
+
if (!existsSync20(attachmentsDir)) {
|
|
13283
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
12973
13284
|
}
|
|
12974
13285
|
let filename = attachment.filename;
|
|
12975
13286
|
if (!filename) {
|
|
@@ -12987,8 +13298,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12987
13298
|
attachment.mediaType = resized.mediaType;
|
|
12988
13299
|
attachment.data = buffer.toString("base64");
|
|
12989
13300
|
}
|
|
12990
|
-
const filePath =
|
|
12991
|
-
|
|
13301
|
+
const filePath = join14(attachmentsDir, filename);
|
|
13302
|
+
writeFileSync6(filePath, buffer);
|
|
12992
13303
|
return filePath;
|
|
12993
13304
|
}
|
|
12994
13305
|
function stripDataUrlPrefix2(data) {
|
|
@@ -13325,7 +13636,20 @@ ${prompt}` });
|
|
|
13325
13636
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
13326
13637
|
} else {
|
|
13327
13638
|
console.error("Agent error:", error);
|
|
13328
|
-
|
|
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
|
+
}));
|
|
13329
13653
|
try {
|
|
13330
13654
|
await activeStreamQueries.markError(streamId);
|
|
13331
13655
|
} catch {
|
|
@@ -13865,7 +14189,20 @@ agents.post(
|
|
|
13865
14189
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
13866
14190
|
} else {
|
|
13867
14191
|
console.error("Agent error:", error);
|
|
13868
|
-
|
|
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
|
+
}));
|
|
13869
14206
|
await activeStreamQueries.markError(streamId);
|
|
13870
14207
|
}
|
|
13871
14208
|
} finally {
|
|
@@ -13948,26 +14285,26 @@ init_config();
|
|
|
13948
14285
|
import { Hono as Hono3 } from "hono";
|
|
13949
14286
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
13950
14287
|
import { z as z18 } from "zod";
|
|
13951
|
-
import { readFileSync as
|
|
14288
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
13952
14289
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13953
|
-
import { dirname as
|
|
14290
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
13954
14291
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13955
|
-
var __dirname =
|
|
14292
|
+
var __dirname = dirname8(__filename);
|
|
13956
14293
|
var possiblePaths = [
|
|
13957
|
-
|
|
14294
|
+
join15(__dirname, "../package.json"),
|
|
13958
14295
|
// From dist/server -> dist/../package.json
|
|
13959
|
-
|
|
14296
|
+
join15(__dirname, "../../package.json"),
|
|
13960
14297
|
// From dist/server (if nested differently)
|
|
13961
|
-
|
|
14298
|
+
join15(__dirname, "../../../package.json"),
|
|
13962
14299
|
// From src/server/routes (development)
|
|
13963
|
-
|
|
14300
|
+
join15(process.cwd(), "package.json")
|
|
13964
14301
|
// From current working directory
|
|
13965
14302
|
];
|
|
13966
14303
|
var currentVersion = "0.0.0";
|
|
13967
14304
|
var packageName = "sparkecoder";
|
|
13968
14305
|
for (const packageJsonPath of possiblePaths) {
|
|
13969
14306
|
try {
|
|
13970
|
-
const packageJson = JSON.parse(
|
|
14307
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
13971
14308
|
if (packageJson.name === "sparkecoder") {
|
|
13972
14309
|
currentVersion = packageJson.version || "0.0.0";
|
|
13973
14310
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -14785,12 +15122,13 @@ slack.post("/events", async (c) => {
|
|
|
14785
15122
|
if (inbound) {
|
|
14786
15123
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
14787
15124
|
if (isThreadReply) {
|
|
14788
|
-
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);
|
|
14789
15126
|
if (!ours) {
|
|
14790
15127
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
14791
15128
|
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
14792
15129
|
return c.json({ ok: true });
|
|
14793
15130
|
}
|
|
15131
|
+
markThreadOwned(ev.channel, ev.thread_ts);
|
|
14794
15132
|
}
|
|
14795
15133
|
if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
|
|
14796
15134
|
markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
|
|
@@ -14829,18 +15167,6 @@ slack.post("/events", async (c) => {
|
|
|
14829
15167
|
}
|
|
14830
15168
|
return c.json({ ok: true });
|
|
14831
15169
|
});
|
|
14832
|
-
async function threadBelongsToUs(channel, threadTs) {
|
|
14833
|
-
try {
|
|
14834
|
-
const sessions3 = await sessionQueries.list(500, 0);
|
|
14835
|
-
return sessions3.some((s) => {
|
|
14836
|
-
const slack2 = s.config?.slack;
|
|
14837
|
-
return slack2?.channel === channel && slack2?.threadTs === threadTs;
|
|
14838
|
-
});
|
|
14839
|
-
} catch (err) {
|
|
14840
|
-
console.warn("[slack] threadBelongsToUs lookup failed:", err?.message ?? err);
|
|
14841
|
-
return false;
|
|
14842
|
-
}
|
|
14843
|
-
}
|
|
14844
15170
|
async function findOrCreateOrchestratorId() {
|
|
14845
15171
|
try {
|
|
14846
15172
|
const all = await sessionQueries.list(500, 0);
|
|
@@ -15229,9 +15555,9 @@ init_skills();
|
|
|
15229
15555
|
import { Hono as Hono9 } from "hono";
|
|
15230
15556
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
15231
15557
|
import { z as z22 } from "zod";
|
|
15232
|
-
import { existsSync as
|
|
15558
|
+
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
15233
15559
|
import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
15234
|
-
import { resolve as resolve11, join as
|
|
15560
|
+
import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
15235
15561
|
var skills = new Hono9();
|
|
15236
15562
|
function encodeId(filePath) {
|
|
15237
15563
|
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
@@ -15290,13 +15616,13 @@ function pathToLabel(path) {
|
|
|
15290
15616
|
if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
|
|
15291
15617
|
if (path.includes("/.cursor/rules")) return ".cursor/rules";
|
|
15292
15618
|
if (path.includes("/.claude/skills")) return ".claude/skills";
|
|
15293
|
-
return basename6(
|
|
15619
|
+
return basename6(dirname9(path)) + "/" + basename6(path);
|
|
15294
15620
|
}
|
|
15295
15621
|
skills.get("/", async (c) => {
|
|
15296
15622
|
const dirs = listAllDirectories();
|
|
15297
15623
|
const allSkills = [];
|
|
15298
15624
|
for (const dir of dirs) {
|
|
15299
|
-
if (!
|
|
15625
|
+
if (!existsSync21(dir.path)) continue;
|
|
15300
15626
|
try {
|
|
15301
15627
|
const list = await loadSkillsFromDirectory(dir.path, {
|
|
15302
15628
|
priority: dir.priority,
|
|
@@ -15333,7 +15659,7 @@ skills.get("/", async (c) => {
|
|
|
15333
15659
|
label: d.label,
|
|
15334
15660
|
source: d.source,
|
|
15335
15661
|
alwaysApply: d.alwaysApply,
|
|
15336
|
-
exists:
|
|
15662
|
+
exists: existsSync21(d.path),
|
|
15337
15663
|
writable: isWritable(d.path)
|
|
15338
15664
|
})),
|
|
15339
15665
|
skills: allSkills
|
|
@@ -15341,7 +15667,7 @@ skills.get("/", async (c) => {
|
|
|
15341
15667
|
});
|
|
15342
15668
|
function isWritable(dir) {
|
|
15343
15669
|
try {
|
|
15344
|
-
if (!
|
|
15670
|
+
if (!existsSync21(dir)) return false;
|
|
15345
15671
|
if (dir.includes("/skills/default")) return false;
|
|
15346
15672
|
return true;
|
|
15347
15673
|
} catch {
|
|
@@ -15350,7 +15676,7 @@ function isWritable(dir) {
|
|
|
15350
15676
|
}
|
|
15351
15677
|
skills.get("/:id", async (c) => {
|
|
15352
15678
|
const filePath = decodeId(c.req.param("id"));
|
|
15353
|
-
if (!filePath || !
|
|
15679
|
+
if (!filePath || !existsSync21(filePath)) {
|
|
15354
15680
|
return c.json({ error: "skill not found" }, 404);
|
|
15355
15681
|
}
|
|
15356
15682
|
const content = await readFile12(filePath, "utf-8");
|
|
@@ -15379,8 +15705,8 @@ skills.post(
|
|
|
15379
15705
|
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
15380
15706
|
const ext = extname9(safeName).toLowerCase();
|
|
15381
15707
|
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
15382
|
-
const filePath =
|
|
15383
|
-
if (
|
|
15708
|
+
const filePath = join16(targetDir, finalName);
|
|
15709
|
+
if (existsSync21(filePath)) {
|
|
15384
15710
|
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
15385
15711
|
}
|
|
15386
15712
|
try {
|
|
@@ -15397,7 +15723,7 @@ skills.put(
|
|
|
15397
15723
|
zValidator7("json", z22.object({ content: z22.string() })),
|
|
15398
15724
|
async (c) => {
|
|
15399
15725
|
const filePath = decodeId(c.req.param("id"));
|
|
15400
|
-
if (!filePath || !
|
|
15726
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
15401
15727
|
if (filePath.includes("/skills/default")) {
|
|
15402
15728
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15403
15729
|
}
|
|
@@ -15407,7 +15733,7 @@ skills.put(
|
|
|
15407
15733
|
);
|
|
15408
15734
|
skills.delete("/:id", async (c) => {
|
|
15409
15735
|
const filePath = decodeId(c.req.param("id"));
|
|
15410
|
-
if (!filePath || !
|
|
15736
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
15411
15737
|
if (filePath.includes("/skills/default")) {
|
|
15412
15738
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15413
15739
|
}
|
|
@@ -15427,7 +15753,7 @@ skills.post(
|
|
|
15427
15753
|
}
|
|
15428
15754
|
const next = [...current, abs];
|
|
15429
15755
|
setSkillsAdditionalDirectories(next);
|
|
15430
|
-
return c.json({ ok: true, path: abs, exists:
|
|
15756
|
+
return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
|
|
15431
15757
|
}
|
|
15432
15758
|
);
|
|
15433
15759
|
skills.delete("/directories", (c) => {
|
|
@@ -15697,13 +16023,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
15697
16023
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
15698
16024
|
function getWebDirectory() {
|
|
15699
16025
|
try {
|
|
15700
|
-
const currentDir =
|
|
16026
|
+
const currentDir = dirname10(fileURLToPath4(import.meta.url));
|
|
15701
16027
|
const webDir = resolve12(currentDir, "..", "web");
|
|
15702
|
-
if (
|
|
16028
|
+
if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
|
|
15703
16029
|
return webDir;
|
|
15704
16030
|
}
|
|
15705
16031
|
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
15706
|
-
if (
|
|
16032
|
+
if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
|
|
15707
16033
|
return altWebDir;
|
|
15708
16034
|
}
|
|
15709
16035
|
return null;
|
|
@@ -15761,23 +16087,23 @@ async function findWebPort(preferredPort) {
|
|
|
15761
16087
|
return { port: preferredPort, alreadyRunning: false };
|
|
15762
16088
|
}
|
|
15763
16089
|
function hasProductionBuild(webDir) {
|
|
15764
|
-
const buildIdPath =
|
|
15765
|
-
return
|
|
16090
|
+
const buildIdPath = join17(webDir, ".next", "BUILD_ID");
|
|
16091
|
+
return existsSync22(buildIdPath);
|
|
15766
16092
|
}
|
|
15767
16093
|
function hasSourceFiles(webDir) {
|
|
15768
|
-
const appDir =
|
|
15769
|
-
const pagesDir =
|
|
15770
|
-
const rootAppDir =
|
|
15771
|
-
const rootPagesDir =
|
|
15772
|
-
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);
|
|
15773
16099
|
}
|
|
15774
16100
|
function getStandaloneServerPath(webDir) {
|
|
15775
16101
|
const possiblePaths2 = [
|
|
15776
|
-
|
|
15777
|
-
|
|
16102
|
+
join17(webDir, ".next", "standalone", "server.js"),
|
|
16103
|
+
join17(webDir, ".next", "standalone", "web", "server.js")
|
|
15778
16104
|
];
|
|
15779
16105
|
for (const serverPath of possiblePaths2) {
|
|
15780
|
-
if (
|
|
16106
|
+
if (existsSync22(serverPath)) {
|
|
15781
16107
|
return serverPath;
|
|
15782
16108
|
}
|
|
15783
16109
|
}
|
|
@@ -15817,15 +16143,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15817
16143
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15818
16144
|
return { process: null, port: actualPort };
|
|
15819
16145
|
}
|
|
15820
|
-
const usePnpm =
|
|
15821
|
-
const useNpm = !usePnpm &&
|
|
16146
|
+
const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
|
|
16147
|
+
const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
|
|
15822
16148
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15823
16149
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15824
16150
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15825
16151
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15826
|
-
const runtimeConfigPath =
|
|
16152
|
+
const runtimeConfigPath = join17(webDir, "runtime-config.json");
|
|
15827
16153
|
try {
|
|
15828
|
-
|
|
16154
|
+
writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15829
16155
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
15830
16156
|
} catch (err) {
|
|
15831
16157
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -15845,7 +16171,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15845
16171
|
if (standaloneServerPath) {
|
|
15846
16172
|
command = "node";
|
|
15847
16173
|
args = ["server.js"];
|
|
15848
|
-
cwd =
|
|
16174
|
+
cwd = dirname10(standaloneServerPath);
|
|
15849
16175
|
webEnv.PORT = String(actualPort);
|
|
15850
16176
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
15851
16177
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -16039,8 +16365,8 @@ async function startServer(options = {}) {
|
|
|
16039
16365
|
if (options.workingDirectory) {
|
|
16040
16366
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
16041
16367
|
}
|
|
16042
|
-
if (!
|
|
16043
|
-
|
|
16368
|
+
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
16369
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
16044
16370
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
16045
16371
|
}
|
|
16046
16372
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -16598,18 +16924,18 @@ function generateOpenAPISpec() {
|
|
|
16598
16924
|
init_config();
|
|
16599
16925
|
init_semantic();
|
|
16600
16926
|
init_db();
|
|
16601
|
-
import { mkdirSync as
|
|
16602
|
-
import { resolve as resolve13, join 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";
|
|
16603
16929
|
function getCliVersion() {
|
|
16604
|
-
const here =
|
|
16930
|
+
const here = dirname11(fileURLToPath5(import.meta.url));
|
|
16605
16931
|
const candidates = [
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16932
|
+
join18(here, "..", "package.json"),
|
|
16933
|
+
join18(here, "..", "..", "package.json"),
|
|
16934
|
+
join18(process.cwd(), "package.json")
|
|
16609
16935
|
];
|
|
16610
16936
|
for (const p of candidates) {
|
|
16611
16937
|
try {
|
|
16612
|
-
const pkg = JSON.parse(
|
|
16938
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
16613
16939
|
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
16614
16940
|
} catch {
|
|
16615
16941
|
}
|
|
@@ -17256,8 +17582,8 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17256
17582
|
let outputSchema;
|
|
17257
17583
|
try {
|
|
17258
17584
|
const schemaStr = options.schema;
|
|
17259
|
-
if (
|
|
17260
|
-
outputSchema = JSON.parse(
|
|
17585
|
+
if (existsSync23(schemaStr)) {
|
|
17586
|
+
outputSchema = JSON.parse(readFileSync11(schemaStr, "utf-8"));
|
|
17261
17587
|
} else {
|
|
17262
17588
|
outputSchema = JSON.parse(schemaStr);
|
|
17263
17589
|
}
|
|
@@ -17324,19 +17650,19 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
17324
17650
|
let configLocation;
|
|
17325
17651
|
if (options.global) {
|
|
17326
17652
|
const appDataDir = ensureAppDataDirectory();
|
|
17327
|
-
configPath =
|
|
17653
|
+
configPath = join18(appDataDir, "sparkecoder.config.json");
|
|
17328
17654
|
configLocation = "global";
|
|
17329
17655
|
} else {
|
|
17330
17656
|
configPath = resolve13(process.cwd(), "sparkecoder.config.json");
|
|
17331
17657
|
configLocation = "local";
|
|
17332
17658
|
}
|
|
17333
|
-
if (
|
|
17659
|
+
if (existsSync23(configPath) && !options.force) {
|
|
17334
17660
|
console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
|
|
17335
17661
|
console.log(chalk.dim(` ${configPath}`));
|
|
17336
17662
|
return;
|
|
17337
17663
|
}
|
|
17338
17664
|
const config = createDefaultConfig();
|
|
17339
|
-
|
|
17665
|
+
writeFileSync8(configPath, JSON.stringify(config, null, 2));
|
|
17340
17666
|
console.log(chalk.green(`\u2713 Created ${configLocation} config`));
|
|
17341
17667
|
console.log(chalk.dim(` ${configPath}`));
|
|
17342
17668
|
console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
|
|
@@ -17355,11 +17681,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17355
17681
|
console.error(chalk.red("Both bot token and signing secret are required."));
|
|
17356
17682
|
process.exit(1);
|
|
17357
17683
|
}
|
|
17358
|
-
const configPath = options.global ?
|
|
17684
|
+
const configPath = options.global ? join18(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve13(process.cwd(), "sparkecoder.config.json");
|
|
17359
17685
|
let existing = {};
|
|
17360
|
-
if (
|
|
17686
|
+
if (existsSync23(configPath)) {
|
|
17361
17687
|
try {
|
|
17362
|
-
existing = JSON.parse(
|
|
17688
|
+
existing = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
17363
17689
|
} catch {
|
|
17364
17690
|
}
|
|
17365
17691
|
} else {
|
|
@@ -17371,7 +17697,7 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17371
17697
|
signingSecret,
|
|
17372
17698
|
defaultOrchestratorName: existing.slack?.defaultOrchestratorName ?? "orchestrator"
|
|
17373
17699
|
};
|
|
17374
|
-
|
|
17700
|
+
writeFileSync8(configPath, JSON.stringify(existing, null, 2));
|
|
17375
17701
|
console.log(chalk.green(`
|
|
17376
17702
|
\u2713 Slack configured`));
|
|
17377
17703
|
console.log(chalk.dim(` ${configPath}`));
|
|
@@ -17623,9 +17949,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17623
17949
|
}
|
|
17624
17950
|
const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
|
|
17625
17951
|
console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
|
|
17626
|
-
const cfDir =
|
|
17627
|
-
const certPath =
|
|
17628
|
-
if (!
|
|
17952
|
+
const cfDir = join18(homedir2(), ".cloudflared");
|
|
17953
|
+
const certPath = join18(cfDir, "cert.pem");
|
|
17954
|
+
if (!existsSync23(certPath)) {
|
|
17629
17955
|
console.log(chalk.yellow("No Cloudflare login cert found."));
|
|
17630
17956
|
if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
|
|
17631
17957
|
run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
|
|
@@ -17669,8 +17995,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17669
17995
|
return;
|
|
17670
17996
|
}
|
|
17671
17997
|
}
|
|
17672
|
-
const credsFile = tunnel.credentials_file ||
|
|
17673
|
-
if (!
|
|
17998
|
+
const credsFile = tunnel.credentials_file || join18(cfDir, `${tunnel.id}.json`);
|
|
17999
|
+
if (!existsSync23(credsFile)) {
|
|
17674
18000
|
console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
|
|
17675
18001
|
}
|
|
17676
18002
|
let hostname = options.hostname;
|
|
@@ -17693,7 +18019,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17693
18019
|
console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
|
|
17694
18020
|
}
|
|
17695
18021
|
}
|
|
17696
|
-
const configPath =
|
|
18022
|
+
const configPath = join18(cfDir, "config.yml");
|
|
17697
18023
|
const configBody = `tunnel: ${tunnel.id}
|
|
17698
18024
|
credentials-file: ${credsFile}
|
|
17699
18025
|
ingress:
|
|
@@ -17704,14 +18030,14 @@ ingress:
|
|
|
17704
18030
|
- service: http_status:404
|
|
17705
18031
|
`;
|
|
17706
18032
|
let wroteConfig = false;
|
|
17707
|
-
if (
|
|
17708
|
-
const existing =
|
|
18033
|
+
if (existsSync23(configPath)) {
|
|
18034
|
+
const existing = readFileSync11(configPath, "utf8");
|
|
17709
18035
|
if (existing.trim() === configBody.trim()) {
|
|
17710
18036
|
console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
|
|
17711
18037
|
wroteConfig = true;
|
|
17712
18038
|
} else if (await confirm(`A different ${configPath} exists. Overwrite (a backup will be saved)?`, false)) {
|
|
17713
18039
|
copyFileSync(configPath, `${configPath}.bak.${Date.now()}`);
|
|
17714
|
-
|
|
18040
|
+
writeFileSync8(configPath, configBody);
|
|
17715
18041
|
console.log(chalk.green("\u2713"), `wrote ${configPath} (previous saved as .bak.*)`);
|
|
17716
18042
|
wroteConfig = true;
|
|
17717
18043
|
} else {
|
|
@@ -17719,7 +18045,7 @@ ingress:
|
|
|
17719
18045
|
console.log(chalk.cyan(configBody));
|
|
17720
18046
|
}
|
|
17721
18047
|
} else {
|
|
17722
|
-
|
|
18048
|
+
writeFileSync8(configPath, configBody);
|
|
17723
18049
|
console.log(chalk.green("\u2713"), `wrote ${configPath}`);
|
|
17724
18050
|
wroteConfig = true;
|
|
17725
18051
|
}
|
|
@@ -18209,17 +18535,17 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18209
18535
|
});
|
|
18210
18536
|
{
|
|
18211
18537
|
let stateFilePath = function() {
|
|
18212
|
-
return
|
|
18538
|
+
return join18(ensureAppDataDirectory(), "recordings.json");
|
|
18213
18539
|
}, readState = function() {
|
|
18214
18540
|
const p = stateFilePath();
|
|
18215
|
-
if (!
|
|
18541
|
+
if (!existsSync23(p)) return [];
|
|
18216
18542
|
try {
|
|
18217
|
-
return JSON.parse(
|
|
18543
|
+
return JSON.parse(readFileSync11(p, "utf-8"));
|
|
18218
18544
|
} catch {
|
|
18219
18545
|
return [];
|
|
18220
18546
|
}
|
|
18221
18547
|
}, writeState = function(rows) {
|
|
18222
|
-
|
|
18548
|
+
writeFileSync8(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
|
|
18223
18549
|
}, isAlive = function(pid) {
|
|
18224
18550
|
try {
|
|
18225
18551
|
process.kill(pid, 0);
|
|
@@ -18234,16 +18560,16 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18234
18560
|
const record = program.command("record").description("Start/stop screen recordings");
|
|
18235
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) => {
|
|
18236
18562
|
const { homedir: homedir2, platform: osPlatform } = await import("os");
|
|
18237
|
-
const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) :
|
|
18238
|
-
|
|
18563
|
+
const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join18(homedir2(), "recordings");
|
|
18564
|
+
mkdirSync11(outDir, { recursive: true });
|
|
18239
18565
|
const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
18240
18566
|
const ext = osPlatform() === "darwin" ? "mov" : "mp4";
|
|
18241
18567
|
const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
|
|
18242
|
-
const path =
|
|
18568
|
+
const path = join18(outDir, filename);
|
|
18243
18569
|
let cmd;
|
|
18244
18570
|
let args;
|
|
18245
18571
|
if (osPlatform() === "darwin") {
|
|
18246
|
-
cmd =
|
|
18572
|
+
cmd = existsSync23("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
|
|
18247
18573
|
args = ["-v", "-C", "-k", path];
|
|
18248
18574
|
} else if (osPlatform() === "linux") {
|
|
18249
18575
|
const display = process.env.DISPLAY || ":0.0";
|
|
@@ -18346,7 +18672,7 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18346
18672
|
}
|
|
18347
18673
|
}
|
|
18348
18674
|
writeState(rows.filter((r) => r.id !== id));
|
|
18349
|
-
const fileExists =
|
|
18675
|
+
const fileExists = existsSync23(row.path);
|
|
18350
18676
|
const sizeMb = fileExists ? Math.round(statSync4(row.path).size / (1024 * 1024) * 10) / 10 : 0;
|
|
18351
18677
|
console.log(JSON.stringify({
|
|
18352
18678
|
id,
|
|
@@ -18381,7 +18707,7 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18381
18707
|
}
|
|
18382
18708
|
}
|
|
18383
18709
|
}
|
|
18384
|
-
stopped.push({ id: r.id, path: r.path, ok:
|
|
18710
|
+
stopped.push({ id: r.id, path: r.path, ok: existsSync23(r.path) });
|
|
18385
18711
|
}
|
|
18386
18712
|
writeState([]);
|
|
18387
18713
|
console.log(JSON.stringify({ stopped }));
|