sparkecoder 0.1.121 → 0.1.123
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.d.ts +3 -3
- package/dist/agent/index.js +240 -26
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +588 -162
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-DczYH89U.d.ts → index-Bcz0aCAR.d.ts} +104 -104
- package/dist/index.d.ts +5 -5
- package/dist/index.js +549 -123
- package/dist/index.js.map +1 -1
- package/dist/{schema-DxrKyetI.d.ts → schema-BWbWmfDQ.d.ts} +3 -3
- package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
- package/dist/server/index.js +549 -123
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +3 -3
- 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 → MP4p8_EldjbZ69dONoEcM}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/MP4p8_EldjbZ69dONoEcM}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/MP4p8_EldjbZ69dONoEcM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → static/MP4p8_EldjbZ69dONoEcM}/_ssgManifest.js +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_buildManifest.js +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{wP9z41wtqT4k-O6AlEXqw → MP4p8_EldjbZ69dONoEcM}/_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,84 @@ 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
|
+
}
|
|
8205
|
+
function ensureToolResultsFollowCalls(messages) {
|
|
8206
|
+
if (!Array.isArray(messages) || messages.length < 3) return messages;
|
|
8207
|
+
let mutated = false;
|
|
8208
|
+
const result = messages.slice();
|
|
8209
|
+
let i = 0;
|
|
8210
|
+
while (i < result.length) {
|
|
8211
|
+
const msg = result[i];
|
|
8212
|
+
if (msg?.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
8213
|
+
i++;
|
|
8214
|
+
continue;
|
|
8215
|
+
}
|
|
8216
|
+
const callIds = /* @__PURE__ */ new Set();
|
|
8217
|
+
for (const part of msg.content) {
|
|
8218
|
+
if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
|
|
8219
|
+
callIds.add(part.toolCallId);
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
if (callIds.size === 0) {
|
|
8223
|
+
i++;
|
|
8224
|
+
continue;
|
|
8225
|
+
}
|
|
8226
|
+
let toolIdx = -1;
|
|
8227
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
8228
|
+
const m = result[j];
|
|
8229
|
+
if (m?.role === "assistant" && Array.isArray(m.content) && m.content.some((p) => p?.type === "tool-call")) {
|
|
8230
|
+
break;
|
|
8231
|
+
}
|
|
8232
|
+
if (m?.role === "tool" && Array.isArray(m.content)) {
|
|
8233
|
+
const answersOne = m.content.some(
|
|
8234
|
+
(p) => p?.type === "tool-result" && typeof p.toolCallId === "string" && callIds.has(p.toolCallId)
|
|
8235
|
+
);
|
|
8236
|
+
if (answersOne) {
|
|
8237
|
+
toolIdx = j;
|
|
8238
|
+
break;
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
if (toolIdx > i + 1) {
|
|
8243
|
+
const [toolMsg] = result.splice(toolIdx, 1);
|
|
8244
|
+
result.splice(i + 1, 0, toolMsg);
|
|
8245
|
+
mutated = true;
|
|
8246
|
+
console.warn(
|
|
8247
|
+
`[tool-repair] Reordered tool-result message from index ${toolIdx} to ${i + 1} to immediately follow assistant tool-call(s) (${[...callIds].join(", ")}). ${toolIdx - i - 1} message(s) were wedged between them.`
|
|
8248
|
+
);
|
|
8249
|
+
}
|
|
8250
|
+
i++;
|
|
8251
|
+
}
|
|
8252
|
+
return mutated ? result : messages;
|
|
8253
|
+
}
|
|
8176
8254
|
function repairToolPairing(messages) {
|
|
8177
8255
|
const toolCallIds = /* @__PURE__ */ new Set();
|
|
8178
8256
|
const toolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -8292,6 +8370,7 @@ ${summaryContent}`
|
|
|
8292
8370
|
];
|
|
8293
8371
|
}
|
|
8294
8372
|
messages = repairToolPairing(messages);
|
|
8373
|
+
messages = ensureToolResultsFollowCalls(messages);
|
|
8295
8374
|
messages = ensureEndsWithUserOrTool(messages);
|
|
8296
8375
|
return messages;
|
|
8297
8376
|
}
|
|
@@ -8486,7 +8565,7 @@ ${summaryContent}`
|
|
|
8486
8565
|
}
|
|
8487
8566
|
}
|
|
8488
8567
|
async addResponseMessages(messages) {
|
|
8489
|
-
const safe = repairToolPairing(messages);
|
|
8568
|
+
const safe = ensureToolResultsFollowCalls(repairToolPairing(messages));
|
|
8490
8569
|
await messageQueries.addMany(this.sessionId, safe);
|
|
8491
8570
|
try {
|
|
8492
8571
|
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
@@ -8538,6 +8617,120 @@ var init_web = __esm({
|
|
|
8538
8617
|
}
|
|
8539
8618
|
});
|
|
8540
8619
|
|
|
8620
|
+
// src/integrations/slack/persistence.ts
|
|
8621
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
8622
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
8623
|
+
function cachePath() {
|
|
8624
|
+
return join9(ensureAppDataDirectory(), FILENAME);
|
|
8625
|
+
}
|
|
8626
|
+
function load() {
|
|
8627
|
+
if (loaded) return;
|
|
8628
|
+
loaded = true;
|
|
8629
|
+
const path = cachePath();
|
|
8630
|
+
if (!existsSync16(path)) return;
|
|
8631
|
+
try {
|
|
8632
|
+
const raw = readFileSync7(path, "utf-8");
|
|
8633
|
+
const parsed = JSON.parse(raw);
|
|
8634
|
+
if (parsed?.version !== FILE_VERSION) return;
|
|
8635
|
+
const now = Date.now();
|
|
8636
|
+
for (const [id, e] of Object.entries(parsed.users || {})) {
|
|
8637
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
8638
|
+
userMap.set(id, { name: e.name ?? null, expiresAt: e.expiresAt });
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
for (const [key2, e] of Object.entries(parsed.threads || {})) {
|
|
8642
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
8643
|
+
threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
|
|
8644
|
+
}
|
|
8645
|
+
}
|
|
8646
|
+
} catch (err) {
|
|
8647
|
+
console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
|
|
8648
|
+
}
|
|
8649
|
+
}
|
|
8650
|
+
function evictIfOversized(map, max) {
|
|
8651
|
+
if (map.size <= max) return;
|
|
8652
|
+
const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
8653
|
+
const toRemove = map.size - max;
|
|
8654
|
+
for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
|
|
8655
|
+
}
|
|
8656
|
+
function scheduleSave() {
|
|
8657
|
+
dirty = true;
|
|
8658
|
+
if (saveTimer) return;
|
|
8659
|
+
saveTimer = setTimeout(() => {
|
|
8660
|
+
saveTimer = null;
|
|
8661
|
+
if (dirty) saveSync();
|
|
8662
|
+
}, SAVE_DEBOUNCE_MS);
|
|
8663
|
+
saveTimer.unref?.();
|
|
8664
|
+
}
|
|
8665
|
+
function saveSync() {
|
|
8666
|
+
dirty = false;
|
|
8667
|
+
try {
|
|
8668
|
+
const path = cachePath();
|
|
8669
|
+
const dir = dirname6(path);
|
|
8670
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
8671
|
+
const payload = {
|
|
8672
|
+
version: FILE_VERSION,
|
|
8673
|
+
users: Object.fromEntries(userMap),
|
|
8674
|
+
threads: Object.fromEntries(threadMap)
|
|
8675
|
+
};
|
|
8676
|
+
const tmp = `${path}.tmp`;
|
|
8677
|
+
writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
|
|
8678
|
+
renameSync(tmp, path);
|
|
8679
|
+
} catch (err) {
|
|
8680
|
+
console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
|
|
8681
|
+
}
|
|
8682
|
+
}
|
|
8683
|
+
function hookExit() {
|
|
8684
|
+
if (exitHooked) return;
|
|
8685
|
+
exitHooked = true;
|
|
8686
|
+
const flush2 = () => {
|
|
8687
|
+
if (dirty) saveSync();
|
|
8688
|
+
};
|
|
8689
|
+
process.once("beforeExit", flush2);
|
|
8690
|
+
process.once("SIGINT", flush2);
|
|
8691
|
+
process.once("SIGTERM", flush2);
|
|
8692
|
+
}
|
|
8693
|
+
function getCachedUserName(userId) {
|
|
8694
|
+
load();
|
|
8695
|
+
return userMap.get(userId);
|
|
8696
|
+
}
|
|
8697
|
+
function setCachedUserName(userId, entry2) {
|
|
8698
|
+
load();
|
|
8699
|
+
hookExit();
|
|
8700
|
+
userMap.set(userId, entry2);
|
|
8701
|
+
evictIfOversized(userMap, MAX_USERS);
|
|
8702
|
+
scheduleSave();
|
|
8703
|
+
}
|
|
8704
|
+
function getCachedThreadOwnership(key2) {
|
|
8705
|
+
load();
|
|
8706
|
+
return threadMap.get(key2);
|
|
8707
|
+
}
|
|
8708
|
+
function setCachedThreadOwnership(key2, entry2) {
|
|
8709
|
+
load();
|
|
8710
|
+
hookExit();
|
|
8711
|
+
threadMap.set(key2, entry2);
|
|
8712
|
+
evictIfOversized(threadMap, MAX_THREADS);
|
|
8713
|
+
scheduleSave();
|
|
8714
|
+
}
|
|
8715
|
+
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
8716
|
+
var init_persistence = __esm({
|
|
8717
|
+
"src/integrations/slack/persistence.ts"() {
|
|
8718
|
+
"use strict";
|
|
8719
|
+
init_config();
|
|
8720
|
+
FILENAME = "slack-cache.json";
|
|
8721
|
+
FILE_VERSION = 1;
|
|
8722
|
+
SAVE_DEBOUNCE_MS = 500;
|
|
8723
|
+
MAX_USERS = 5e3;
|
|
8724
|
+
MAX_THREADS = 5e3;
|
|
8725
|
+
loaded = false;
|
|
8726
|
+
userMap = /* @__PURE__ */ new Map();
|
|
8727
|
+
threadMap = /* @__PURE__ */ new Map();
|
|
8728
|
+
dirty = false;
|
|
8729
|
+
saveTimer = null;
|
|
8730
|
+
exitHooked = false;
|
|
8731
|
+
}
|
|
8732
|
+
});
|
|
8733
|
+
|
|
8541
8734
|
// src/integrations/slack/client.ts
|
|
8542
8735
|
function readSlackConfig() {
|
|
8543
8736
|
try {
|
|
@@ -8660,13 +8853,13 @@ async function fetchSlackUserName(userId) {
|
|
|
8660
8853
|
async function resolveSlackUserName(userId) {
|
|
8661
8854
|
if (!userId) return null;
|
|
8662
8855
|
const now = Date.now();
|
|
8663
|
-
const hit =
|
|
8856
|
+
const hit = getCachedUserName(userId);
|
|
8664
8857
|
if (hit && hit.expiresAt > now) return hit.name;
|
|
8665
8858
|
const inflight = userInflight.get(userId);
|
|
8666
8859
|
if (inflight) return inflight;
|
|
8667
8860
|
const p = (async () => {
|
|
8668
8861
|
const name = await fetchSlackUserName(userId);
|
|
8669
|
-
|
|
8862
|
+
setCachedUserName(userId, {
|
|
8670
8863
|
name,
|
|
8671
8864
|
expiresAt: now + (name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
8672
8865
|
});
|
|
@@ -8688,11 +8881,63 @@ async function normalizeSlackMentions(text) {
|
|
|
8688
8881
|
}
|
|
8689
8882
|
return text.replace(userMentionRe, (_full, id, label) => {
|
|
8690
8883
|
if (label) return `${label} <@${id}>`;
|
|
8691
|
-
const cached =
|
|
8884
|
+
const cached = getCachedUserName(id);
|
|
8692
8885
|
const name = cached?.name;
|
|
8693
8886
|
return name ? `${name} <@${id}>` : `<@${id}>`;
|
|
8694
8887
|
});
|
|
8695
8888
|
}
|
|
8889
|
+
function threadCacheKey(channel, threadTs) {
|
|
8890
|
+
return `${channel}\u241F${threadTs}`;
|
|
8891
|
+
}
|
|
8892
|
+
async function fetchBotParticipatedInThread(channel, threadTs) {
|
|
8893
|
+
const token = getSlackBotToken();
|
|
8894
|
+
if (!token) return false;
|
|
8895
|
+
const self = await ensureSlackSelfIdentity();
|
|
8896
|
+
if (!self) return false;
|
|
8897
|
+
try {
|
|
8898
|
+
const url = `https://slack.com/api/conversations.replies?channel=${encodeURIComponent(channel)}&ts=${encodeURIComponent(threadTs)}&limit=200`;
|
|
8899
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
8900
|
+
const data = await res.json().catch(() => ({}));
|
|
8901
|
+
if (!data?.ok) {
|
|
8902
|
+
console.warn(`[slack] conversations.replies(${channel}/${threadTs}) failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
8903
|
+
return false;
|
|
8904
|
+
}
|
|
8905
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
8906
|
+
return messages.some(
|
|
8907
|
+
(m) => self.botId && m.bot_id === self.botId || self.botUserId && m.user === self.botUserId
|
|
8908
|
+
);
|
|
8909
|
+
} catch (err) {
|
|
8910
|
+
console.warn(`[slack] conversations.replies error:`, err?.message || err);
|
|
8911
|
+
return false;
|
|
8912
|
+
}
|
|
8913
|
+
}
|
|
8914
|
+
async function botParticipatedInThread(channel, threadTs) {
|
|
8915
|
+
if (!channel || !threadTs) return false;
|
|
8916
|
+
const key2 = threadCacheKey(channel, threadTs);
|
|
8917
|
+
const now = Date.now();
|
|
8918
|
+
const hit = getCachedThreadOwnership(key2);
|
|
8919
|
+
if (hit && hit.expiresAt > now) return hit.owned;
|
|
8920
|
+
const inflight = threadOwnedInflight.get(key2);
|
|
8921
|
+
if (inflight) return inflight;
|
|
8922
|
+
const p = (async () => {
|
|
8923
|
+
const owned = await fetchBotParticipatedInThread(channel, threadTs);
|
|
8924
|
+
setCachedThreadOwnership(key2, {
|
|
8925
|
+
owned,
|
|
8926
|
+
expiresAt: now + (owned ? THREAD_OWNED_TTL_MS : THREAD_NEG_TTL_MS)
|
|
8927
|
+
});
|
|
8928
|
+
threadOwnedInflight.delete(key2);
|
|
8929
|
+
return owned;
|
|
8930
|
+
})();
|
|
8931
|
+
threadOwnedInflight.set(key2, p);
|
|
8932
|
+
return p;
|
|
8933
|
+
}
|
|
8934
|
+
function noteBotPostedInThread(channel, threadTs) {
|
|
8935
|
+
if (!channel || !threadTs) return;
|
|
8936
|
+
setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
|
|
8937
|
+
owned: true,
|
|
8938
|
+
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
8939
|
+
});
|
|
8940
|
+
}
|
|
8696
8941
|
function getSlackDeniedReplyPolicy() {
|
|
8697
8942
|
try {
|
|
8698
8943
|
const cfg = getConfig();
|
|
@@ -8705,17 +8950,20 @@ function getSlackDeniedReplyPolicy() {
|
|
|
8705
8950
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
8706
8951
|
}
|
|
8707
8952
|
}
|
|
8708
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS,
|
|
8953
|
+
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
8709
8954
|
var init_client3 = __esm({
|
|
8710
8955
|
"src/integrations/slack/client.ts"() {
|
|
8711
8956
|
"use strict";
|
|
8712
8957
|
init_config();
|
|
8958
|
+
init_persistence();
|
|
8713
8959
|
cachedSelf = null;
|
|
8714
8960
|
selfInflight = null;
|
|
8715
8961
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
8716
8962
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
8717
|
-
userNameCache = /* @__PURE__ */ new Map();
|
|
8718
8963
|
userInflight = /* @__PURE__ */ new Map();
|
|
8964
|
+
THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
8965
|
+
THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
8966
|
+
threadOwnedInflight = /* @__PURE__ */ new Map();
|
|
8719
8967
|
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
8968
|
}
|
|
8721
8969
|
});
|
|
@@ -8815,6 +9063,7 @@ var init_slack = __esm({
|
|
|
8815
9063
|
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
8816
9064
|
if (r.slackChannel && r.threadTs) {
|
|
8817
9065
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
9066
|
+
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8818
9067
|
}
|
|
8819
9068
|
},
|
|
8820
9069
|
displayLabel(ref) {
|
|
@@ -9472,8 +9721,8 @@ var init_orchestrator_actions = __esm({
|
|
|
9472
9721
|
|
|
9473
9722
|
// src/integrations/mcp/store.ts
|
|
9474
9723
|
import { nanoid as nanoid6 } from "nanoid";
|
|
9475
|
-
import { existsSync as
|
|
9476
|
-
import { resolve as resolve10, join as
|
|
9724
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
|
|
9725
|
+
import { resolve as resolve10, join as join10 } from "path";
|
|
9477
9726
|
function readServers() {
|
|
9478
9727
|
try {
|
|
9479
9728
|
const cfg = getConfig();
|
|
@@ -9485,12 +9734,12 @@ function readServers() {
|
|
|
9485
9734
|
function refreshMcpServersFromDisk() {
|
|
9486
9735
|
const candidates = [
|
|
9487
9736
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9488
|
-
|
|
9737
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9489
9738
|
];
|
|
9490
9739
|
for (const path of candidates) {
|
|
9491
|
-
if (!
|
|
9740
|
+
if (!existsSync17(path)) continue;
|
|
9492
9741
|
try {
|
|
9493
|
-
const raw = JSON.parse(
|
|
9742
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
9494
9743
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9495
9744
|
setMcpServers(servers2);
|
|
9496
9745
|
return servers2;
|
|
@@ -10103,7 +10352,7 @@ __export(recorder_exports, {
|
|
|
10103
10352
|
import { exec as exec5 } from "child_process";
|
|
10104
10353
|
import { promisify as promisify5 } from "util";
|
|
10105
10354
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
10106
|
-
import { join as
|
|
10355
|
+
import { join as join11 } from "path";
|
|
10107
10356
|
import { tmpdir } from "os";
|
|
10108
10357
|
import { nanoid as nanoid7 } from "nanoid";
|
|
10109
10358
|
async function checkFfmpeg() {
|
|
@@ -10160,21 +10409,21 @@ var init_recorder = __esm({
|
|
|
10160
10409
|
*/
|
|
10161
10410
|
async encode() {
|
|
10162
10411
|
if (this.frames.length === 0) return null;
|
|
10163
|
-
const workDir =
|
|
10412
|
+
const workDir = join11(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
10164
10413
|
await mkdir4(workDir, { recursive: true });
|
|
10165
10414
|
try {
|
|
10166
10415
|
for (let i = 0; i < this.frames.length; i++) {
|
|
10167
|
-
const framePath =
|
|
10416
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
10168
10417
|
await writeFile5(framePath, this.frames[i].data);
|
|
10169
10418
|
}
|
|
10170
10419
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
10171
10420
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
10172
10421
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
10173
|
-
const outputPath =
|
|
10422
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
10174
10423
|
const hasFfmpeg = await checkFfmpeg();
|
|
10175
10424
|
if (hasFfmpeg) {
|
|
10176
10425
|
await execAsync5(
|
|
10177
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
10426
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
10178
10427
|
{ timeout: 12e4 }
|
|
10179
10428
|
);
|
|
10180
10429
|
} else {
|
|
@@ -10186,7 +10435,7 @@ var init_recorder = __esm({
|
|
|
10186
10435
|
const files = await readdir5(workDir);
|
|
10187
10436
|
for (const f of files) {
|
|
10188
10437
|
if (f.startsWith("frame_")) {
|
|
10189
|
-
await unlink2(
|
|
10438
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
10190
10439
|
});
|
|
10191
10440
|
}
|
|
10192
10441
|
}
|
|
@@ -10427,7 +10676,7 @@ ${prompt}` });
|
|
|
10427
10676
|
const config = getConfig();
|
|
10428
10677
|
const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
|
|
10429
10678
|
if (!options.skipSaveUserMessage) {
|
|
10430
|
-
this.context.addUserMessage(userContent);
|
|
10679
|
+
await this.context.addUserMessage(userContent);
|
|
10431
10680
|
}
|
|
10432
10681
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10433
10682
|
let systemPrompt = await buildSystemPrompt({
|
|
@@ -10453,7 +10702,8 @@ ${personality.trim()}`;
|
|
|
10453
10702
|
}
|
|
10454
10703
|
const messages = await this.context.getMessages();
|
|
10455
10704
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
10456
|
-
const
|
|
10705
|
+
const approvalWrapped = this.wrapToolsWithApproval(options, tools);
|
|
10706
|
+
const wrappedTools = wrapToolsNeverThrow(approvalWrapped);
|
|
10457
10707
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10458
10708
|
const stream = streamText2({
|
|
10459
10709
|
model: resolveModel(this.session.model),
|
|
@@ -10467,6 +10717,18 @@ ${personality.trim()}`;
|
|
|
10467
10717
|
providerOptions: useAnthropic ? {
|
|
10468
10718
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10469
10719
|
} : void 0,
|
|
10720
|
+
// Run repairToolPairing before EVERY step's model call, not just the
|
|
10721
|
+
// first one. The AI SDK's multi-step loop can otherwise feed the model
|
|
10722
|
+
// a prompt containing an orphan tool-call (e.g. when a previous step's
|
|
10723
|
+
// tool result was lost, dropped during compaction, or the stream was
|
|
10724
|
+
// aborted mid-tool). Repairing in `prepareStep` guarantees no orphan
|
|
10725
|
+
// ever reaches the model and we never hit AI_MissingToolResultsError.
|
|
10726
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10727
|
+
const paired = repairToolPairing(stepMessages);
|
|
10728
|
+
const ordered = ensureToolResultsFollowCalls(paired);
|
|
10729
|
+
if (ordered === stepMessages) return {};
|
|
10730
|
+
return { messages: ordered };
|
|
10731
|
+
},
|
|
10470
10732
|
onStepFinish: async (step) => {
|
|
10471
10733
|
options.onStepFinish?.(step);
|
|
10472
10734
|
},
|
|
@@ -10478,7 +10740,7 @@ ${personality.trim()}`;
|
|
|
10478
10740
|
const result = await stream;
|
|
10479
10741
|
const response = await result.response;
|
|
10480
10742
|
const responseMessages = response.messages;
|
|
10481
|
-
this.context.addResponseMessages(responseMessages);
|
|
10743
|
+
await this.context.addResponseMessages(responseMessages);
|
|
10482
10744
|
};
|
|
10483
10745
|
return {
|
|
10484
10746
|
sessionId: this.session.id,
|
|
@@ -10492,7 +10754,7 @@ ${personality.trim()}`;
|
|
|
10492
10754
|
*/
|
|
10493
10755
|
async run(options) {
|
|
10494
10756
|
const config = getConfig();
|
|
10495
|
-
this.context.addUserMessage(options.prompt);
|
|
10757
|
+
await this.context.addUserMessage(options.prompt);
|
|
10496
10758
|
const systemPrompt = await buildSystemPrompt({
|
|
10497
10759
|
workingDirectory: this.session.workingDirectory,
|
|
10498
10760
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
@@ -10502,7 +10764,7 @@ ${personality.trim()}`;
|
|
|
10502
10764
|
});
|
|
10503
10765
|
const messages = await this.context.getMessages();
|
|
10504
10766
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
10505
|
-
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
10767
|
+
const wrappedTools = wrapToolsNeverThrow(this.wrapToolsWithApproval(options, tools));
|
|
10506
10768
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10507
10769
|
const result = await generateText3({
|
|
10508
10770
|
model: resolveModel(this.session.model),
|
|
@@ -10513,10 +10775,17 @@ ${personality.trim()}`;
|
|
|
10513
10775
|
// Enable extended thinking/reasoning for models that support it
|
|
10514
10776
|
providerOptions: useAnthropic ? {
|
|
10515
10777
|
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
10516
|
-
} : void 0
|
|
10778
|
+
} : void 0,
|
|
10779
|
+
// Repair tool pairing before every step (see `stream()` for full rationale).
|
|
10780
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10781
|
+
const paired = repairToolPairing(stepMessages);
|
|
10782
|
+
const ordered = ensureToolResultsFollowCalls(paired);
|
|
10783
|
+
if (ordered === stepMessages) return {};
|
|
10784
|
+
return { messages: ordered };
|
|
10785
|
+
}
|
|
10517
10786
|
});
|
|
10518
10787
|
const responseMessages = result.response.messages;
|
|
10519
|
-
this.context.addResponseMessages(responseMessages);
|
|
10788
|
+
await this.context.addResponseMessages(responseMessages);
|
|
10520
10789
|
return {
|
|
10521
10790
|
text: result.text,
|
|
10522
10791
|
steps: result.steps
|
|
@@ -10690,12 +10959,20 @@ ${p.text}` : p.text;
|
|
|
10690
10959
|
model: resolveModel(this.session.model),
|
|
10691
10960
|
system: systemPrompt,
|
|
10692
10961
|
messages,
|
|
10693
|
-
tools: taskTools,
|
|
10962
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
10694
10963
|
stopWhen: stepCountIs2(500),
|
|
10695
10964
|
abortSignal: combinedAbort,
|
|
10696
10965
|
providerOptions: useAnthropic ? {
|
|
10697
10966
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10698
10967
|
} : void 0,
|
|
10968
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
10969
|
+
// every step so we never feed the model an orphan tool-call.
|
|
10970
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
10971
|
+
const paired = repairToolPairing(stepMessages);
|
|
10972
|
+
const ordered = ensureToolResultsFollowCalls(paired);
|
|
10973
|
+
if (ordered === stepMessages) return {};
|
|
10974
|
+
return { messages: ordered };
|
|
10975
|
+
},
|
|
10699
10976
|
onStepFinish: async (step) => {
|
|
10700
10977
|
options.onStepFinish?.(step);
|
|
10701
10978
|
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
@@ -10929,11 +11206,11 @@ ${p.text}` : p.text;
|
|
|
10929
11206
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10930
11207
|
if (!isRemoteConfigured2()) return [];
|
|
10931
11208
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
10932
|
-
const { join:
|
|
11209
|
+
const { join: join19, basename: basename7 } = await import("path");
|
|
10933
11210
|
const urls = [];
|
|
10934
11211
|
for (const filePath of filePaths) {
|
|
10935
11212
|
try {
|
|
10936
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
11213
|
+
const fullPath = filePath.startsWith("/") ? filePath : join19(this.session.workingDirectory, filePath);
|
|
10937
11214
|
const fileName = basename7(fullPath);
|
|
10938
11215
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10939
11216
|
const mimeMap = {
|
|
@@ -11138,19 +11415,19 @@ var init_session_lock = __esm({
|
|
|
11138
11415
|
});
|
|
11139
11416
|
|
|
11140
11417
|
// src/orchestrator/webhook-events.ts
|
|
11141
|
-
import { existsSync as
|
|
11142
|
-
import { dirname as
|
|
11418
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
11419
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
11143
11420
|
import { nanoid as nanoid9 } from "nanoid";
|
|
11144
11421
|
function logFilePath() {
|
|
11145
|
-
return
|
|
11422
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
11146
11423
|
}
|
|
11147
11424
|
function ensureLoaded() {
|
|
11148
11425
|
if (cache !== null) return cache;
|
|
11149
11426
|
cache = [];
|
|
11150
11427
|
try {
|
|
11151
11428
|
const p = logFilePath();
|
|
11152
|
-
if (!
|
|
11153
|
-
const lines =
|
|
11429
|
+
if (!existsSync18(p)) return cache;
|
|
11430
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
11154
11431
|
for (const line of lines) {
|
|
11155
11432
|
try {
|
|
11156
11433
|
cache.push(JSON.parse(line));
|
|
@@ -11160,7 +11437,7 @@ function ensureLoaded() {
|
|
|
11160
11437
|
if (cache.length > MAX_EVENTS) {
|
|
11161
11438
|
cache = cache.slice(-MAX_EVENTS);
|
|
11162
11439
|
try {
|
|
11163
|
-
|
|
11440
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11164
11441
|
} catch {
|
|
11165
11442
|
}
|
|
11166
11443
|
}
|
|
@@ -11174,7 +11451,7 @@ function appendEvent(ev) {
|
|
|
11174
11451
|
if (list.length > MAX_EVENTS) list.shift();
|
|
11175
11452
|
try {
|
|
11176
11453
|
const p = logFilePath();
|
|
11177
|
-
|
|
11454
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
11178
11455
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
11179
11456
|
} catch {
|
|
11180
11457
|
}
|
|
@@ -11205,8 +11482,8 @@ function updateEvent(id, patch) {
|
|
|
11205
11482
|
list[i] = { ...list[i], ...patch };
|
|
11206
11483
|
try {
|
|
11207
11484
|
const p = logFilePath();
|
|
11208
|
-
|
|
11209
|
-
|
|
11485
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
11486
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11210
11487
|
} catch {
|
|
11211
11488
|
}
|
|
11212
11489
|
}
|
|
@@ -11238,7 +11515,7 @@ function listEvents(filter = {}) {
|
|
|
11238
11515
|
function clearAllEvents() {
|
|
11239
11516
|
cache = [];
|
|
11240
11517
|
try {
|
|
11241
|
-
|
|
11518
|
+
writeFileSync4(logFilePath(), "");
|
|
11242
11519
|
} catch {
|
|
11243
11520
|
}
|
|
11244
11521
|
}
|
|
@@ -11728,7 +12005,7 @@ import chalk from "chalk";
|
|
|
11728
12005
|
import ora from "ora";
|
|
11729
12006
|
import "dotenv/config";
|
|
11730
12007
|
import { createInterface } from "readline";
|
|
11731
|
-
import { dirname as
|
|
12008
|
+
import { dirname as dirname11 } from "path";
|
|
11732
12009
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11733
12010
|
|
|
11734
12011
|
// src/server/index.ts
|
|
@@ -11737,8 +12014,8 @@ import { Hono as Hono10 } from "hono";
|
|
|
11737
12014
|
import { serve } from "@hono/node-server";
|
|
11738
12015
|
import { cors } from "hono/cors";
|
|
11739
12016
|
import { logger } from "hono/logger";
|
|
11740
|
-
import { existsSync as
|
|
11741
|
-
import { resolve as resolve12, dirname as
|
|
12017
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
12018
|
+
import { resolve as resolve12, dirname as dirname10, join as join17 } from "path";
|
|
11742
12019
|
import { spawn as spawn2 } from "child_process";
|
|
11743
12020
|
import { createServer as createNetServer } from "net";
|
|
11744
12021
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -11752,9 +12029,9 @@ init_checkpoints();
|
|
|
11752
12029
|
import { Hono } from "hono";
|
|
11753
12030
|
import { zValidator } from "@hono/zod-validator";
|
|
11754
12031
|
import { z as z16 } from "zod";
|
|
11755
|
-
import { existsSync as
|
|
12032
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
11756
12033
|
import { readdir as readdir6 } from "fs/promises";
|
|
11757
|
-
import { join as
|
|
12034
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
11758
12035
|
import { nanoid as nanoid10 } from "nanoid";
|
|
11759
12036
|
|
|
11760
12037
|
// src/tasks/agent-status.ts
|
|
@@ -12392,12 +12669,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
12392
12669
|
});
|
|
12393
12670
|
function getAttachmentsDir(sessionId) {
|
|
12394
12671
|
const appDataDir = getAppDataDirectory();
|
|
12395
|
-
return
|
|
12672
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
12396
12673
|
}
|
|
12397
12674
|
function ensureAttachmentsDir(sessionId) {
|
|
12398
12675
|
const dir = getAttachmentsDir(sessionId);
|
|
12399
|
-
if (!
|
|
12400
|
-
|
|
12676
|
+
if (!existsSync19(dir)) {
|
|
12677
|
+
mkdirSync8(dir, { recursive: true });
|
|
12401
12678
|
}
|
|
12402
12679
|
return dir;
|
|
12403
12680
|
}
|
|
@@ -12408,12 +12685,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
12408
12685
|
return c.json({ error: "Session not found" }, 404);
|
|
12409
12686
|
}
|
|
12410
12687
|
const dir = getAttachmentsDir(sessionId);
|
|
12411
|
-
if (!
|
|
12688
|
+
if (!existsSync19(dir)) {
|
|
12412
12689
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
12413
12690
|
}
|
|
12414
12691
|
const files = readdirSync3(dir);
|
|
12415
12692
|
const attachments = files.map((filename) => {
|
|
12416
|
-
const filePath =
|
|
12693
|
+
const filePath = join13(dir, filename);
|
|
12417
12694
|
const stats = statSync2(filePath);
|
|
12418
12695
|
return {
|
|
12419
12696
|
id: filename.split("_")[0],
|
|
@@ -12448,9 +12725,9 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12448
12725
|
const id = nanoid10(10);
|
|
12449
12726
|
const ext = extname8(file.name) || "";
|
|
12450
12727
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12451
|
-
const filePath =
|
|
12728
|
+
const filePath = join13(dir, safeFilename);
|
|
12452
12729
|
const arrayBuffer = await file.arrayBuffer();
|
|
12453
|
-
|
|
12730
|
+
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
12454
12731
|
return c.json({
|
|
12455
12732
|
id,
|
|
12456
12733
|
filename: file.name,
|
|
@@ -12474,13 +12751,13 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12474
12751
|
const id = nanoid10(10);
|
|
12475
12752
|
const ext = extname8(body.filename) || "";
|
|
12476
12753
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12477
|
-
const filePath =
|
|
12754
|
+
const filePath = join13(dir, safeFilename);
|
|
12478
12755
|
let base64Data = body.data;
|
|
12479
12756
|
if (base64Data.includes(",")) {
|
|
12480
12757
|
base64Data = base64Data.split(",")[1];
|
|
12481
12758
|
}
|
|
12482
12759
|
const buffer = Buffer.from(base64Data, "base64");
|
|
12483
|
-
|
|
12760
|
+
writeFileSync5(filePath, buffer);
|
|
12484
12761
|
return c.json({
|
|
12485
12762
|
id,
|
|
12486
12763
|
filename: body.filename,
|
|
@@ -12503,7 +12780,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12503
12780
|
return c.json({ error: "Session not found" }, 404);
|
|
12504
12781
|
}
|
|
12505
12782
|
const dir = getAttachmentsDir(sessionId);
|
|
12506
|
-
if (!
|
|
12783
|
+
if (!existsSync19(dir)) {
|
|
12507
12784
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12508
12785
|
}
|
|
12509
12786
|
const files = readdirSync3(dir);
|
|
@@ -12511,7 +12788,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12511
12788
|
if (!file) {
|
|
12512
12789
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12513
12790
|
}
|
|
12514
|
-
const filePath =
|
|
12791
|
+
const filePath = join13(dir, file);
|
|
12515
12792
|
unlinkSync2(filePath);
|
|
12516
12793
|
return c.json({ success: true, id: attachmentId });
|
|
12517
12794
|
});
|
|
@@ -12594,7 +12871,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12594
12871
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12595
12872
|
for (const entry2 of entries) {
|
|
12596
12873
|
if (results.length >= limit * 2) break;
|
|
12597
|
-
const fullPath =
|
|
12874
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
12598
12875
|
const relativePath = relative9(baseDir, fullPath);
|
|
12599
12876
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12600
12877
|
continue;
|
|
@@ -12642,7 +12919,7 @@ sessions2.get(
|
|
|
12642
12919
|
return c.json({ error: "Session not found" }, 404);
|
|
12643
12920
|
}
|
|
12644
12921
|
const workingDirectory = session.workingDirectory;
|
|
12645
|
-
if (!
|
|
12922
|
+
if (!existsSync19(workingDirectory)) {
|
|
12646
12923
|
return c.json({
|
|
12647
12924
|
sessionId,
|
|
12648
12925
|
workingDirectory,
|
|
@@ -12750,14 +13027,148 @@ sessions2.get("/:id/browser-recording", async (c) => {
|
|
|
12750
13027
|
|
|
12751
13028
|
// src/server/routes/agents.ts
|
|
12752
13029
|
init_db();
|
|
12753
|
-
init_agent();
|
|
12754
|
-
init_session_lock();
|
|
12755
|
-
init_config();
|
|
12756
13030
|
import { Hono as Hono2 } from "hono";
|
|
12757
13031
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12758
13032
|
import { z as z17 } from "zod";
|
|
12759
|
-
import { existsSync as
|
|
12760
|
-
import { join as
|
|
13033
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
13034
|
+
import { join as join14 } from "path";
|
|
13035
|
+
|
|
13036
|
+
// src/agent/missing-tool-recovery.ts
|
|
13037
|
+
init_db();
|
|
13038
|
+
function extractMissingToolCallIds(error) {
|
|
13039
|
+
if (!error) return [];
|
|
13040
|
+
const e = error;
|
|
13041
|
+
if (Array.isArray(e.toolCallIds) && e.toolCallIds.every((x) => typeof x === "string")) {
|
|
13042
|
+
return e.toolCallIds;
|
|
13043
|
+
}
|
|
13044
|
+
const msg = typeof e.message === "string" ? e.message : "";
|
|
13045
|
+
const ids = /* @__PURE__ */ new Set();
|
|
13046
|
+
for (const m of msg.matchAll(/(?:tool call|tool calls)\s+([a-zA-Z0-9_\-, ]+?)(?:\.|$)/g)) {
|
|
13047
|
+
for (const id of m[1].split(/\s*,\s*/)) {
|
|
13048
|
+
const trimmed = id.trim();
|
|
13049
|
+
if (trimmed) ids.add(trimmed);
|
|
13050
|
+
}
|
|
13051
|
+
}
|
|
13052
|
+
return [...ids];
|
|
13053
|
+
}
|
|
13054
|
+
function isMissingToolResultsError(error) {
|
|
13055
|
+
if (!error) return false;
|
|
13056
|
+
const e = error;
|
|
13057
|
+
if (e.name === "AI_MissingToolResultsError") return true;
|
|
13058
|
+
if (Array.isArray(e.toolCallIds)) return true;
|
|
13059
|
+
return typeof e.message === "string" && /tool result.*is missing for tool call/i.test(e.message);
|
|
13060
|
+
}
|
|
13061
|
+
async function recoverFromMissingToolResults(sessionId, error) {
|
|
13062
|
+
const toolCallIds = extractMissingToolCallIds(error);
|
|
13063
|
+
if (toolCallIds.length === 0) return null;
|
|
13064
|
+
let history = [];
|
|
13065
|
+
try {
|
|
13066
|
+
history = await messageQueries.getModelMessages(sessionId);
|
|
13067
|
+
} catch (err) {
|
|
13068
|
+
console.warn("[missing-tool-recovery] could not load messages:", err?.message || err);
|
|
13069
|
+
}
|
|
13070
|
+
const toolInfoByCallId = /* @__PURE__ */ new Map();
|
|
13071
|
+
const resultMessageIndexByCallId = /* @__PURE__ */ new Map();
|
|
13072
|
+
const existingResultIds = /* @__PURE__ */ new Set();
|
|
13073
|
+
for (let idx = 0; idx < history.length; idx++) {
|
|
13074
|
+
const msg = history[idx];
|
|
13075
|
+
if (!Array.isArray(msg.content)) continue;
|
|
13076
|
+
for (const part of msg.content) {
|
|
13077
|
+
if (part?.type === "tool-call" && typeof part.toolCallId === "string") {
|
|
13078
|
+
if (typeof part.toolName === "string") {
|
|
13079
|
+
toolInfoByCallId.set(part.toolCallId, { toolName: part.toolName, callMessageIndex: idx });
|
|
13080
|
+
}
|
|
13081
|
+
}
|
|
13082
|
+
if (part?.type === "tool-result" && typeof part.toolCallId === "string") {
|
|
13083
|
+
existingResultIds.add(part.toolCallId);
|
|
13084
|
+
resultMessageIndexByCallId.set(part.toolCallId, idx);
|
|
13085
|
+
}
|
|
13086
|
+
}
|
|
13087
|
+
}
|
|
13088
|
+
const resolved = toolCallIds.map((id) => {
|
|
13089
|
+
const info = toolInfoByCallId.get(id);
|
|
13090
|
+
const resultIdx = resultMessageIndexByCallId.get(id);
|
|
13091
|
+
const wedgedRoles = info && typeof resultIdx === "number" && resultIdx > info.callMessageIndex + 1 ? history.slice(info.callMessageIndex + 1, resultIdx).map((m) => m.role) : void 0;
|
|
13092
|
+
return {
|
|
13093
|
+
toolCallId: id,
|
|
13094
|
+
toolName: info?.toolName ?? "unknown",
|
|
13095
|
+
foundInAssistantMessage: !!info,
|
|
13096
|
+
callMessageIndex: info?.callMessageIndex,
|
|
13097
|
+
resultMessageIndex: resultIdx,
|
|
13098
|
+
wedgedMessageRoles: wedgedRoles
|
|
13099
|
+
};
|
|
13100
|
+
});
|
|
13101
|
+
const stillOrphaned = toolCallIds.filter((id) => !existingResultIds.has(id));
|
|
13102
|
+
let syntheticToolMessageSaved = false;
|
|
13103
|
+
if (stillOrphaned.length > 0) {
|
|
13104
|
+
const syntheticParts = stillOrphaned.map((id) => ({
|
|
13105
|
+
type: "tool-result",
|
|
13106
|
+
toolCallId: id,
|
|
13107
|
+
toolName: toolInfoByCallId.get(id)?.toolName ?? "unknown",
|
|
13108
|
+
output: {
|
|
13109
|
+
type: "text",
|
|
13110
|
+
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.]"
|
|
13111
|
+
}
|
|
13112
|
+
}));
|
|
13113
|
+
try {
|
|
13114
|
+
await messageQueries.create(sessionId, {
|
|
13115
|
+
role: "tool",
|
|
13116
|
+
content: syntheticParts
|
|
13117
|
+
});
|
|
13118
|
+
syntheticToolMessageSaved = true;
|
|
13119
|
+
} catch (err) {
|
|
13120
|
+
console.error("[missing-tool-recovery] failed to persist synthetic tool message:", err?.message || err);
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
13123
|
+
const lastFewMessageRoles = history.slice(-6).map((m) => m.role);
|
|
13124
|
+
const first = resolved.find((r) => r.callMessageIndex !== void 0);
|
|
13125
|
+
let contextSnapshot;
|
|
13126
|
+
if (first?.callMessageIndex !== void 0) {
|
|
13127
|
+
const start = Math.max(0, first.callMessageIndex - 1);
|
|
13128
|
+
const end = Math.min(
|
|
13129
|
+
history.length,
|
|
13130
|
+
Math.max(first.callMessageIndex, first.resultMessageIndex ?? first.callMessageIndex) + 2
|
|
13131
|
+
);
|
|
13132
|
+
contextSnapshot = history.slice(start, end).map((m, offset) => ({
|
|
13133
|
+
index: start + offset,
|
|
13134
|
+
role: m.role,
|
|
13135
|
+
summary: summarizeContent(m.content)
|
|
13136
|
+
}));
|
|
13137
|
+
}
|
|
13138
|
+
const anyWedged = resolved.some((r) => r.wedgedMessageRoles && r.wedgedMessageRoles.length > 0);
|
|
13139
|
+
return {
|
|
13140
|
+
kind: "missing_tool_results",
|
|
13141
|
+
toolCallIds,
|
|
13142
|
+
resolved,
|
|
13143
|
+
syntheticToolMessageSaved,
|
|
13144
|
+
lastFewMessageRoles,
|
|
13145
|
+
contextSnapshot,
|
|
13146
|
+
hint: anyWedged ? "A non-tool message was wedged between the assistant tool-call and its tool-result (likely a Slack/system inbox event that arrived mid-turn). " + (syntheticToolMessageSaved ? "Synthetic tool-result was added as a fallback, but the proper fix (now in place) is the reordering pass that moves tool-results to immediately follow their tool-calls." : "The reordering pass should fix this on the next turn; if not, revert to the previous checkpoint.") : 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."
|
|
13147
|
+
};
|
|
13148
|
+
}
|
|
13149
|
+
function summarizeContent(content) {
|
|
13150
|
+
if (typeof content === "string") {
|
|
13151
|
+
return content.length > 160 ? content.slice(0, 160) + "\u2026" : content;
|
|
13152
|
+
}
|
|
13153
|
+
if (!Array.isArray(content)) return String(content);
|
|
13154
|
+
const parts = content.map((p) => {
|
|
13155
|
+
if (!p || typeof p !== "object") return String(p);
|
|
13156
|
+
if (p.type === "text") {
|
|
13157
|
+
const t = String(p.text ?? "");
|
|
13158
|
+
return `text(${t.length > 80 ? t.slice(0, 80) + "\u2026" : t})`;
|
|
13159
|
+
}
|
|
13160
|
+
if (p.type === "tool-call") return `tool-call(${p.toolName}:${p.toolCallId})`;
|
|
13161
|
+
if (p.type === "tool-result") return `tool-result(${p.toolName ?? "?"}:${p.toolCallId})`;
|
|
13162
|
+
if (p.type === "reasoning") return "reasoning";
|
|
13163
|
+
return p.type ?? "unknown";
|
|
13164
|
+
});
|
|
13165
|
+
return parts.join(", ");
|
|
13166
|
+
}
|
|
13167
|
+
|
|
13168
|
+
// src/server/routes/agents.ts
|
|
13169
|
+
init_agent();
|
|
13170
|
+
init_session_lock();
|
|
13171
|
+
init_config();
|
|
12761
13172
|
|
|
12762
13173
|
// src/server/resumable-stream.ts
|
|
12763
13174
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -12964,12 +13375,12 @@ var rejectSchema = z17.object({
|
|
|
12964
13375
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12965
13376
|
function getAttachmentsDirectory(sessionId) {
|
|
12966
13377
|
const appDataDir = getAppDataDirectory();
|
|
12967
|
-
return
|
|
13378
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
12968
13379
|
}
|
|
12969
13380
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12970
13381
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
12971
|
-
if (!
|
|
12972
|
-
|
|
13382
|
+
if (!existsSync20(attachmentsDir)) {
|
|
13383
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
12973
13384
|
}
|
|
12974
13385
|
let filename = attachment.filename;
|
|
12975
13386
|
if (!filename) {
|
|
@@ -12987,8 +13398,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12987
13398
|
attachment.mediaType = resized.mediaType;
|
|
12988
13399
|
attachment.data = buffer.toString("base64");
|
|
12989
13400
|
}
|
|
12990
|
-
const filePath =
|
|
12991
|
-
|
|
13401
|
+
const filePath = join14(attachmentsDir, filename);
|
|
13402
|
+
writeFileSync6(filePath, buffer);
|
|
12992
13403
|
return filePath;
|
|
12993
13404
|
}
|
|
12994
13405
|
function stripDataUrlPrefix2(data) {
|
|
@@ -13325,7 +13736,20 @@ ${prompt}` });
|
|
|
13325
13736
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
13326
13737
|
} else {
|
|
13327
13738
|
console.error("Agent error:", error);
|
|
13328
|
-
|
|
13739
|
+
let debugPayload = void 0;
|
|
13740
|
+
if (isMissingToolResultsError(error)) {
|
|
13741
|
+
try {
|
|
13742
|
+
const recovery = await recoverFromMissingToolResults(sessionId, error);
|
|
13743
|
+
if (recovery) debugPayload = recovery;
|
|
13744
|
+
} catch (recErr) {
|
|
13745
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
13746
|
+
}
|
|
13747
|
+
}
|
|
13748
|
+
await writeSSE(JSON.stringify({
|
|
13749
|
+
type: "error",
|
|
13750
|
+
errorText: error.message,
|
|
13751
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
13752
|
+
}));
|
|
13329
13753
|
try {
|
|
13330
13754
|
await activeStreamQueries.markError(streamId);
|
|
13331
13755
|
} catch {
|
|
@@ -13865,7 +14289,20 @@ agents.post(
|
|
|
13865
14289
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
13866
14290
|
} else {
|
|
13867
14291
|
console.error("Agent error:", error);
|
|
13868
|
-
|
|
14292
|
+
let debugPayload = void 0;
|
|
14293
|
+
if (isMissingToolResultsError(error)) {
|
|
14294
|
+
try {
|
|
14295
|
+
const recovery = await recoverFromMissingToolResults(session.id, error);
|
|
14296
|
+
if (recovery) debugPayload = recovery;
|
|
14297
|
+
} catch (recErr) {
|
|
14298
|
+
console.error("[missing-tool-recovery] failed:", recErr?.message || recErr);
|
|
14299
|
+
}
|
|
14300
|
+
}
|
|
14301
|
+
await writeSSE(JSON.stringify({
|
|
14302
|
+
type: "error",
|
|
14303
|
+
errorText: error.message,
|
|
14304
|
+
...debugPayload ? { debug: debugPayload } : {}
|
|
14305
|
+
}));
|
|
13869
14306
|
await activeStreamQueries.markError(streamId);
|
|
13870
14307
|
}
|
|
13871
14308
|
} finally {
|
|
@@ -13948,26 +14385,26 @@ init_config();
|
|
|
13948
14385
|
import { Hono as Hono3 } from "hono";
|
|
13949
14386
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
13950
14387
|
import { z as z18 } from "zod";
|
|
13951
|
-
import { readFileSync as
|
|
14388
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
13952
14389
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13953
|
-
import { dirname as
|
|
14390
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
13954
14391
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13955
|
-
var __dirname =
|
|
14392
|
+
var __dirname = dirname8(__filename);
|
|
13956
14393
|
var possiblePaths = [
|
|
13957
|
-
|
|
14394
|
+
join15(__dirname, "../package.json"),
|
|
13958
14395
|
// From dist/server -> dist/../package.json
|
|
13959
|
-
|
|
14396
|
+
join15(__dirname, "../../package.json"),
|
|
13960
14397
|
// From dist/server (if nested differently)
|
|
13961
|
-
|
|
14398
|
+
join15(__dirname, "../../../package.json"),
|
|
13962
14399
|
// From src/server/routes (development)
|
|
13963
|
-
|
|
14400
|
+
join15(process.cwd(), "package.json")
|
|
13964
14401
|
// From current working directory
|
|
13965
14402
|
];
|
|
13966
14403
|
var currentVersion = "0.0.0";
|
|
13967
14404
|
var packageName = "sparkecoder";
|
|
13968
14405
|
for (const packageJsonPath of possiblePaths) {
|
|
13969
14406
|
try {
|
|
13970
|
-
const packageJson = JSON.parse(
|
|
14407
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
13971
14408
|
if (packageJson.name === "sparkecoder") {
|
|
13972
14409
|
currentVersion = packageJson.version || "0.0.0";
|
|
13973
14410
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -14785,12 +15222,13 @@ slack.post("/events", async (c) => {
|
|
|
14785
15222
|
if (inbound) {
|
|
14786
15223
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
14787
15224
|
if (isThreadReply) {
|
|
14788
|
-
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await
|
|
15225
|
+
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await botParticipatedInThread(ev.channel, ev.thread_ts);
|
|
14789
15226
|
if (!ours) {
|
|
14790
15227
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
14791
15228
|
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
14792
15229
|
return c.json({ ok: true });
|
|
14793
15230
|
}
|
|
15231
|
+
markThreadOwned(ev.channel, ev.thread_ts);
|
|
14794
15232
|
}
|
|
14795
15233
|
if (ev.type === "app_mention" && ev.channel && (ev.thread_ts || ev.ts)) {
|
|
14796
15234
|
markThreadOwned(ev.channel, ev.thread_ts || ev.ts);
|
|
@@ -14829,18 +15267,6 @@ slack.post("/events", async (c) => {
|
|
|
14829
15267
|
}
|
|
14830
15268
|
return c.json({ ok: true });
|
|
14831
15269
|
});
|
|
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
15270
|
async function findOrCreateOrchestratorId() {
|
|
14845
15271
|
try {
|
|
14846
15272
|
const all = await sessionQueries.list(500, 0);
|
|
@@ -15229,9 +15655,9 @@ init_skills();
|
|
|
15229
15655
|
import { Hono as Hono9 } from "hono";
|
|
15230
15656
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
15231
15657
|
import { z as z22 } from "zod";
|
|
15232
|
-
import { existsSync as
|
|
15658
|
+
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
15233
15659
|
import { readFile as readFile12, writeFile as writeFile6, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
15234
|
-
import { resolve as resolve11, join as
|
|
15660
|
+
import { resolve as resolve11, join as join16, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
15235
15661
|
var skills = new Hono9();
|
|
15236
15662
|
function encodeId(filePath) {
|
|
15237
15663
|
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
@@ -15290,13 +15716,13 @@ function pathToLabel(path) {
|
|
|
15290
15716
|
if (path.includes("/.sparkecoder/skills")) return ".sparkecoder/skills";
|
|
15291
15717
|
if (path.includes("/.cursor/rules")) return ".cursor/rules";
|
|
15292
15718
|
if (path.includes("/.claude/skills")) return ".claude/skills";
|
|
15293
|
-
return basename6(
|
|
15719
|
+
return basename6(dirname9(path)) + "/" + basename6(path);
|
|
15294
15720
|
}
|
|
15295
15721
|
skills.get("/", async (c) => {
|
|
15296
15722
|
const dirs = listAllDirectories();
|
|
15297
15723
|
const allSkills = [];
|
|
15298
15724
|
for (const dir of dirs) {
|
|
15299
|
-
if (!
|
|
15725
|
+
if (!existsSync21(dir.path)) continue;
|
|
15300
15726
|
try {
|
|
15301
15727
|
const list = await loadSkillsFromDirectory(dir.path, {
|
|
15302
15728
|
priority: dir.priority,
|
|
@@ -15333,7 +15759,7 @@ skills.get("/", async (c) => {
|
|
|
15333
15759
|
label: d.label,
|
|
15334
15760
|
source: d.source,
|
|
15335
15761
|
alwaysApply: d.alwaysApply,
|
|
15336
|
-
exists:
|
|
15762
|
+
exists: existsSync21(d.path),
|
|
15337
15763
|
writable: isWritable(d.path)
|
|
15338
15764
|
})),
|
|
15339
15765
|
skills: allSkills
|
|
@@ -15341,7 +15767,7 @@ skills.get("/", async (c) => {
|
|
|
15341
15767
|
});
|
|
15342
15768
|
function isWritable(dir) {
|
|
15343
15769
|
try {
|
|
15344
|
-
if (!
|
|
15770
|
+
if (!existsSync21(dir)) return false;
|
|
15345
15771
|
if (dir.includes("/skills/default")) return false;
|
|
15346
15772
|
return true;
|
|
15347
15773
|
} catch {
|
|
@@ -15350,7 +15776,7 @@ function isWritable(dir) {
|
|
|
15350
15776
|
}
|
|
15351
15777
|
skills.get("/:id", async (c) => {
|
|
15352
15778
|
const filePath = decodeId(c.req.param("id"));
|
|
15353
|
-
if (!filePath || !
|
|
15779
|
+
if (!filePath || !existsSync21(filePath)) {
|
|
15354
15780
|
return c.json({ error: "skill not found" }, 404);
|
|
15355
15781
|
}
|
|
15356
15782
|
const content = await readFile12(filePath, "utf-8");
|
|
@@ -15379,8 +15805,8 @@ skills.post(
|
|
|
15379
15805
|
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
15380
15806
|
const ext = extname9(safeName).toLowerCase();
|
|
15381
15807
|
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
15382
|
-
const filePath =
|
|
15383
|
-
if (
|
|
15808
|
+
const filePath = join16(targetDir, finalName);
|
|
15809
|
+
if (existsSync21(filePath)) {
|
|
15384
15810
|
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
15385
15811
|
}
|
|
15386
15812
|
try {
|
|
@@ -15397,7 +15823,7 @@ skills.put(
|
|
|
15397
15823
|
zValidator7("json", z22.object({ content: z22.string() })),
|
|
15398
15824
|
async (c) => {
|
|
15399
15825
|
const filePath = decodeId(c.req.param("id"));
|
|
15400
|
-
if (!filePath || !
|
|
15826
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
15401
15827
|
if (filePath.includes("/skills/default")) {
|
|
15402
15828
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15403
15829
|
}
|
|
@@ -15407,7 +15833,7 @@ skills.put(
|
|
|
15407
15833
|
);
|
|
15408
15834
|
skills.delete("/:id", async (c) => {
|
|
15409
15835
|
const filePath = decodeId(c.req.param("id"));
|
|
15410
|
-
if (!filePath || !
|
|
15836
|
+
if (!filePath || !existsSync21(filePath)) return c.json({ error: "skill not found" }, 404);
|
|
15411
15837
|
if (filePath.includes("/skills/default")) {
|
|
15412
15838
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15413
15839
|
}
|
|
@@ -15427,7 +15853,7 @@ skills.post(
|
|
|
15427
15853
|
}
|
|
15428
15854
|
const next = [...current, abs];
|
|
15429
15855
|
setSkillsAdditionalDirectories(next);
|
|
15430
|
-
return c.json({ ok: true, path: abs, exists:
|
|
15856
|
+
return c.json({ ok: true, path: abs, exists: existsSync21(abs) }, 201);
|
|
15431
15857
|
}
|
|
15432
15858
|
);
|
|
15433
15859
|
skills.delete("/directories", (c) => {
|
|
@@ -15697,13 +16123,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
15697
16123
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
15698
16124
|
function getWebDirectory() {
|
|
15699
16125
|
try {
|
|
15700
|
-
const currentDir =
|
|
16126
|
+
const currentDir = dirname10(fileURLToPath4(import.meta.url));
|
|
15701
16127
|
const webDir = resolve12(currentDir, "..", "web");
|
|
15702
|
-
if (
|
|
16128
|
+
if (existsSync22(webDir) && existsSync22(join17(webDir, "package.json"))) {
|
|
15703
16129
|
return webDir;
|
|
15704
16130
|
}
|
|
15705
16131
|
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
15706
|
-
if (
|
|
16132
|
+
if (existsSync22(altWebDir) && existsSync22(join17(altWebDir, "package.json"))) {
|
|
15707
16133
|
return altWebDir;
|
|
15708
16134
|
}
|
|
15709
16135
|
return null;
|
|
@@ -15761,23 +16187,23 @@ async function findWebPort(preferredPort) {
|
|
|
15761
16187
|
return { port: preferredPort, alreadyRunning: false };
|
|
15762
16188
|
}
|
|
15763
16189
|
function hasProductionBuild(webDir) {
|
|
15764
|
-
const buildIdPath =
|
|
15765
|
-
return
|
|
16190
|
+
const buildIdPath = join17(webDir, ".next", "BUILD_ID");
|
|
16191
|
+
return existsSync22(buildIdPath);
|
|
15766
16192
|
}
|
|
15767
16193
|
function hasSourceFiles(webDir) {
|
|
15768
|
-
const appDir =
|
|
15769
|
-
const pagesDir =
|
|
15770
|
-
const rootAppDir =
|
|
15771
|
-
const rootPagesDir =
|
|
15772
|
-
return
|
|
16194
|
+
const appDir = join17(webDir, "src", "app");
|
|
16195
|
+
const pagesDir = join17(webDir, "src", "pages");
|
|
16196
|
+
const rootAppDir = join17(webDir, "app");
|
|
16197
|
+
const rootPagesDir = join17(webDir, "pages");
|
|
16198
|
+
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
15773
16199
|
}
|
|
15774
16200
|
function getStandaloneServerPath(webDir) {
|
|
15775
16201
|
const possiblePaths2 = [
|
|
15776
|
-
|
|
15777
|
-
|
|
16202
|
+
join17(webDir, ".next", "standalone", "server.js"),
|
|
16203
|
+
join17(webDir, ".next", "standalone", "web", "server.js")
|
|
15778
16204
|
];
|
|
15779
16205
|
for (const serverPath of possiblePaths2) {
|
|
15780
|
-
if (
|
|
16206
|
+
if (existsSync22(serverPath)) {
|
|
15781
16207
|
return serverPath;
|
|
15782
16208
|
}
|
|
15783
16209
|
}
|
|
@@ -15817,15 +16243,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15817
16243
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15818
16244
|
return { process: null, port: actualPort };
|
|
15819
16245
|
}
|
|
15820
|
-
const usePnpm =
|
|
15821
|
-
const useNpm = !usePnpm &&
|
|
16246
|
+
const usePnpm = existsSync22(join17(webDir, "pnpm-lock.yaml"));
|
|
16247
|
+
const useNpm = !usePnpm && existsSync22(join17(webDir, "package-lock.json"));
|
|
15822
16248
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15823
16249
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15824
16250
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15825
16251
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15826
|
-
const runtimeConfigPath =
|
|
16252
|
+
const runtimeConfigPath = join17(webDir, "runtime-config.json");
|
|
15827
16253
|
try {
|
|
15828
|
-
|
|
16254
|
+
writeFileSync7(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15829
16255
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
15830
16256
|
} catch (err) {
|
|
15831
16257
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -15845,7 +16271,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15845
16271
|
if (standaloneServerPath) {
|
|
15846
16272
|
command = "node";
|
|
15847
16273
|
args = ["server.js"];
|
|
15848
|
-
cwd =
|
|
16274
|
+
cwd = dirname10(standaloneServerPath);
|
|
15849
16275
|
webEnv.PORT = String(actualPort);
|
|
15850
16276
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
15851
16277
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -16039,8 +16465,8 @@ async function startServer(options = {}) {
|
|
|
16039
16465
|
if (options.workingDirectory) {
|
|
16040
16466
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
16041
16467
|
}
|
|
16042
|
-
if (!
|
|
16043
|
-
|
|
16468
|
+
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
16469
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
16044
16470
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
16045
16471
|
}
|
|
16046
16472
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -16598,18 +17024,18 @@ function generateOpenAPISpec() {
|
|
|
16598
17024
|
init_config();
|
|
16599
17025
|
init_semantic();
|
|
16600
17026
|
init_db();
|
|
16601
|
-
import { mkdirSync as
|
|
16602
|
-
import { resolve as resolve13, join as
|
|
17027
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, readFileSync as readFileSync11, existsSync as existsSync23, statSync as statSync4 } from "fs";
|
|
17028
|
+
import { resolve as resolve13, join as join18 } from "path";
|
|
16603
17029
|
function getCliVersion() {
|
|
16604
|
-
const here =
|
|
17030
|
+
const here = dirname11(fileURLToPath5(import.meta.url));
|
|
16605
17031
|
const candidates = [
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
17032
|
+
join18(here, "..", "package.json"),
|
|
17033
|
+
join18(here, "..", "..", "package.json"),
|
|
17034
|
+
join18(process.cwd(), "package.json")
|
|
16609
17035
|
];
|
|
16610
17036
|
for (const p of candidates) {
|
|
16611
17037
|
try {
|
|
16612
|
-
const pkg = JSON.parse(
|
|
17038
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
16613
17039
|
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
16614
17040
|
} catch {
|
|
16615
17041
|
}
|
|
@@ -17256,8 +17682,8 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17256
17682
|
let outputSchema;
|
|
17257
17683
|
try {
|
|
17258
17684
|
const schemaStr = options.schema;
|
|
17259
|
-
if (
|
|
17260
|
-
outputSchema = JSON.parse(
|
|
17685
|
+
if (existsSync23(schemaStr)) {
|
|
17686
|
+
outputSchema = JSON.parse(readFileSync11(schemaStr, "utf-8"));
|
|
17261
17687
|
} else {
|
|
17262
17688
|
outputSchema = JSON.parse(schemaStr);
|
|
17263
17689
|
}
|
|
@@ -17324,19 +17750,19 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
17324
17750
|
let configLocation;
|
|
17325
17751
|
if (options.global) {
|
|
17326
17752
|
const appDataDir = ensureAppDataDirectory();
|
|
17327
|
-
configPath =
|
|
17753
|
+
configPath = join18(appDataDir, "sparkecoder.config.json");
|
|
17328
17754
|
configLocation = "global";
|
|
17329
17755
|
} else {
|
|
17330
17756
|
configPath = resolve13(process.cwd(), "sparkecoder.config.json");
|
|
17331
17757
|
configLocation = "local";
|
|
17332
17758
|
}
|
|
17333
|
-
if (
|
|
17759
|
+
if (existsSync23(configPath) && !options.force) {
|
|
17334
17760
|
console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
|
|
17335
17761
|
console.log(chalk.dim(` ${configPath}`));
|
|
17336
17762
|
return;
|
|
17337
17763
|
}
|
|
17338
17764
|
const config = createDefaultConfig();
|
|
17339
|
-
|
|
17765
|
+
writeFileSync8(configPath, JSON.stringify(config, null, 2));
|
|
17340
17766
|
console.log(chalk.green(`\u2713 Created ${configLocation} config`));
|
|
17341
17767
|
console.log(chalk.dim(` ${configPath}`));
|
|
17342
17768
|
console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
|
|
@@ -17355,11 +17781,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17355
17781
|
console.error(chalk.red("Both bot token and signing secret are required."));
|
|
17356
17782
|
process.exit(1);
|
|
17357
17783
|
}
|
|
17358
|
-
const configPath = options.global ?
|
|
17784
|
+
const configPath = options.global ? join18(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve13(process.cwd(), "sparkecoder.config.json");
|
|
17359
17785
|
let existing = {};
|
|
17360
|
-
if (
|
|
17786
|
+
if (existsSync23(configPath)) {
|
|
17361
17787
|
try {
|
|
17362
|
-
existing = JSON.parse(
|
|
17788
|
+
existing = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
17363
17789
|
} catch {
|
|
17364
17790
|
}
|
|
17365
17791
|
} else {
|
|
@@ -17371,7 +17797,7 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17371
17797
|
signingSecret,
|
|
17372
17798
|
defaultOrchestratorName: existing.slack?.defaultOrchestratorName ?? "orchestrator"
|
|
17373
17799
|
};
|
|
17374
|
-
|
|
17800
|
+
writeFileSync8(configPath, JSON.stringify(existing, null, 2));
|
|
17375
17801
|
console.log(chalk.green(`
|
|
17376
17802
|
\u2713 Slack configured`));
|
|
17377
17803
|
console.log(chalk.dim(` ${configPath}`));
|
|
@@ -17623,9 +18049,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17623
18049
|
}
|
|
17624
18050
|
const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
|
|
17625
18051
|
console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
|
|
17626
|
-
const cfDir =
|
|
17627
|
-
const certPath =
|
|
17628
|
-
if (!
|
|
18052
|
+
const cfDir = join18(homedir2(), ".cloudflared");
|
|
18053
|
+
const certPath = join18(cfDir, "cert.pem");
|
|
18054
|
+
if (!existsSync23(certPath)) {
|
|
17629
18055
|
console.log(chalk.yellow("No Cloudflare login cert found."));
|
|
17630
18056
|
if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
|
|
17631
18057
|
run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
|
|
@@ -17669,8 +18095,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17669
18095
|
return;
|
|
17670
18096
|
}
|
|
17671
18097
|
}
|
|
17672
|
-
const credsFile = tunnel.credentials_file ||
|
|
17673
|
-
if (!
|
|
18098
|
+
const credsFile = tunnel.credentials_file || join18(cfDir, `${tunnel.id}.json`);
|
|
18099
|
+
if (!existsSync23(credsFile)) {
|
|
17674
18100
|
console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
|
|
17675
18101
|
}
|
|
17676
18102
|
let hostname = options.hostname;
|
|
@@ -17693,7 +18119,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17693
18119
|
console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
|
|
17694
18120
|
}
|
|
17695
18121
|
}
|
|
17696
|
-
const configPath =
|
|
18122
|
+
const configPath = join18(cfDir, "config.yml");
|
|
17697
18123
|
const configBody = `tunnel: ${tunnel.id}
|
|
17698
18124
|
credentials-file: ${credsFile}
|
|
17699
18125
|
ingress:
|
|
@@ -17704,14 +18130,14 @@ ingress:
|
|
|
17704
18130
|
- service: http_status:404
|
|
17705
18131
|
`;
|
|
17706
18132
|
let wroteConfig = false;
|
|
17707
|
-
if (
|
|
17708
|
-
const existing =
|
|
18133
|
+
if (existsSync23(configPath)) {
|
|
18134
|
+
const existing = readFileSync11(configPath, "utf8");
|
|
17709
18135
|
if (existing.trim() === configBody.trim()) {
|
|
17710
18136
|
console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
|
|
17711
18137
|
wroteConfig = true;
|
|
17712
18138
|
} else if (await confirm(`A different ${configPath} exists. Overwrite (a backup will be saved)?`, false)) {
|
|
17713
18139
|
copyFileSync(configPath, `${configPath}.bak.${Date.now()}`);
|
|
17714
|
-
|
|
18140
|
+
writeFileSync8(configPath, configBody);
|
|
17715
18141
|
console.log(chalk.green("\u2713"), `wrote ${configPath} (previous saved as .bak.*)`);
|
|
17716
18142
|
wroteConfig = true;
|
|
17717
18143
|
} else {
|
|
@@ -17719,7 +18145,7 @@ ingress:
|
|
|
17719
18145
|
console.log(chalk.cyan(configBody));
|
|
17720
18146
|
}
|
|
17721
18147
|
} else {
|
|
17722
|
-
|
|
18148
|
+
writeFileSync8(configPath, configBody);
|
|
17723
18149
|
console.log(chalk.green("\u2713"), `wrote ${configPath}`);
|
|
17724
18150
|
wroteConfig = true;
|
|
17725
18151
|
}
|
|
@@ -18209,17 +18635,17 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18209
18635
|
});
|
|
18210
18636
|
{
|
|
18211
18637
|
let stateFilePath = function() {
|
|
18212
|
-
return
|
|
18638
|
+
return join18(ensureAppDataDirectory(), "recordings.json");
|
|
18213
18639
|
}, readState = function() {
|
|
18214
18640
|
const p = stateFilePath();
|
|
18215
|
-
if (!
|
|
18641
|
+
if (!existsSync23(p)) return [];
|
|
18216
18642
|
try {
|
|
18217
|
-
return JSON.parse(
|
|
18643
|
+
return JSON.parse(readFileSync11(p, "utf-8"));
|
|
18218
18644
|
} catch {
|
|
18219
18645
|
return [];
|
|
18220
18646
|
}
|
|
18221
18647
|
}, writeState = function(rows) {
|
|
18222
|
-
|
|
18648
|
+
writeFileSync8(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
|
|
18223
18649
|
}, isAlive = function(pid) {
|
|
18224
18650
|
try {
|
|
18225
18651
|
process.kill(pid, 0);
|
|
@@ -18234,16 +18660,16 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18234
18660
|
const record = program.command("record").description("Start/stop screen recordings");
|
|
18235
18661
|
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
18662
|
const { homedir: homedir2, platform: osPlatform } = await import("os");
|
|
18237
|
-
const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) :
|
|
18238
|
-
|
|
18663
|
+
const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join18(homedir2(), "recordings");
|
|
18664
|
+
mkdirSync11(outDir, { recursive: true });
|
|
18239
18665
|
const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
18240
18666
|
const ext = osPlatform() === "darwin" ? "mov" : "mp4";
|
|
18241
18667
|
const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
|
|
18242
|
-
const path =
|
|
18668
|
+
const path = join18(outDir, filename);
|
|
18243
18669
|
let cmd;
|
|
18244
18670
|
let args;
|
|
18245
18671
|
if (osPlatform() === "darwin") {
|
|
18246
|
-
cmd =
|
|
18672
|
+
cmd = existsSync23("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
|
|
18247
18673
|
args = ["-v", "-C", "-k", path];
|
|
18248
18674
|
} else if (osPlatform() === "linux") {
|
|
18249
18675
|
const display = process.env.DISPLAY || ":0.0";
|
|
@@ -18346,7 +18772,7 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18346
18772
|
}
|
|
18347
18773
|
}
|
|
18348
18774
|
writeState(rows.filter((r) => r.id !== id));
|
|
18349
|
-
const fileExists =
|
|
18775
|
+
const fileExists = existsSync23(row.path);
|
|
18350
18776
|
const sizeMb = fileExists ? Math.round(statSync4(row.path).size / (1024 * 1024) * 10) / 10 : 0;
|
|
18351
18777
|
console.log(JSON.stringify({
|
|
18352
18778
|
id,
|
|
@@ -18381,7 +18807,7 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18381
18807
|
}
|
|
18382
18808
|
}
|
|
18383
18809
|
}
|
|
18384
|
-
stopped.push({ id: r.id, path: r.path, ok:
|
|
18810
|
+
stopped.push({ id: r.id, path: r.path, ok: existsSync23(r.path) });
|
|
18385
18811
|
}
|
|
18386
18812
|
writeState([]);
|
|
18387
18813
|
console.log(JSON.stringify({ stopped }));
|