sparkecoder 0.1.117 → 0.1.119
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 +2 -2
- package/dist/agent/index.js +117 -698
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +639 -1042
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-Bi8Ek02A.d.ts → index-Bcz0aCAR.d.ts} +1 -10
- package/dist/index.d.ts +4 -4
- package/dist/index.js +406 -944
- package/dist/index.js.map +1 -1
- package/dist/{schema-ecQSnCMz.d.ts → schema-BWbWmfDQ.d.ts} +0 -2
- package/dist/server/index.js +406 -944
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/desktop-automation.md +290 -0
- package/dist/skills/default/recording.md +3 -3
- package/dist/tools/index.d.ts +1 -167
- package/dist/tools/index.js +5 -590
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/desktop-automation.md +290 -0
- package/src/skills/default/recording.md +3 -3
- 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/dist/skills/default/computer-use.md +0 -225
- package/src/skills/default/computer-use.md +0 -225
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_buildManifest.js +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → Bt00m8W4k5F79ALhN700F}/_ssgManifest.js +0 -0
package/dist/server/index.js
CHANGED
|
@@ -724,12 +724,6 @@ var init_types = __esm({
|
|
|
724
724
|
skillsDirectory: z.string().optional(),
|
|
725
725
|
maxContextChars: z.number().optional().default(2e5),
|
|
726
726
|
task: TaskConfigSchema.optional(),
|
|
727
|
-
// Anthropic computer use tool — opt-in. When true, the `computer` tool is
|
|
728
|
-
// included in the toolset for Anthropic models. Default false.
|
|
729
|
-
computerUseEnabled: z.boolean().optional(),
|
|
730
|
-
// Display dimensions for the computer use tool (defaults: 1280x800).
|
|
731
|
-
computerUseDisplayWidth: z.number().int().positive().optional(),
|
|
732
|
-
computerUseDisplayHeight: z.number().int().positive().optional(),
|
|
733
727
|
// 'orchestrator' = supervisor session; 'worker' = task spawned by an orchestrator.
|
|
734
728
|
role: z.enum(["orchestrator", "worker", "chat"]).optional(),
|
|
735
729
|
// Optional persona / extra system-prompt text appended to the orchestrator's
|
|
@@ -1165,7 +1159,7 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
1165
1159
|
...config,
|
|
1166
1160
|
server: {
|
|
1167
1161
|
port: config.server.port,
|
|
1168
|
-
host: config.server.host ?? "
|
|
1162
|
+
host: config.server.host ?? "0.0.0.0",
|
|
1169
1163
|
publicUrl: config.server.publicUrl
|
|
1170
1164
|
},
|
|
1171
1165
|
resolvedWorkingDirectory,
|
|
@@ -1332,7 +1326,7 @@ function createDefaultConfig() {
|
|
|
1332
1326
|
},
|
|
1333
1327
|
server: {
|
|
1334
1328
|
port: 3141,
|
|
1335
|
-
host: "
|
|
1329
|
+
host: "0.0.0.0"
|
|
1336
1330
|
},
|
|
1337
1331
|
databasePath: "./sparkecoder.db"
|
|
1338
1332
|
};
|
|
@@ -2714,12 +2708,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2714
2708
|
}
|
|
2715
2709
|
async function commandExists(cmd) {
|
|
2716
2710
|
try {
|
|
2717
|
-
const { exec:
|
|
2718
|
-
const { promisify:
|
|
2719
|
-
const
|
|
2711
|
+
const { exec: exec7 } = await import("child_process");
|
|
2712
|
+
const { promisify: promisify7 } = await import("util");
|
|
2713
|
+
const execAsync7 = promisify7(exec7);
|
|
2720
2714
|
const isWindows = process.platform === "win32";
|
|
2721
2715
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2722
|
-
await
|
|
2716
|
+
await execAsync7(checkCmd);
|
|
2723
2717
|
return true;
|
|
2724
2718
|
} catch {
|
|
2725
2719
|
return false;
|
|
@@ -6364,581 +6358,6 @@ var init_upload_file = __esm({
|
|
|
6364
6358
|
}
|
|
6365
6359
|
});
|
|
6366
6360
|
|
|
6367
|
-
// src/tools/computer-use.ts
|
|
6368
|
-
import { anthropic } from "@ai-sdk/anthropic";
|
|
6369
|
-
import { exec as exec5 } from "child_process";
|
|
6370
|
-
import { promisify as promisify5 } from "util";
|
|
6371
|
-
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
6372
|
-
import { join as join8 } from "path";
|
|
6373
|
-
import { tmpdir } from "os";
|
|
6374
|
-
import { nanoid as nanoid4 } from "nanoid";
|
|
6375
|
-
function isMacOs() {
|
|
6376
|
-
return process.platform === "darwin";
|
|
6377
|
-
}
|
|
6378
|
-
async function isCliclickInstalled() {
|
|
6379
|
-
try {
|
|
6380
|
-
await execAsync5("command -v cliclick", { timeout: 2e3 });
|
|
6381
|
-
return true;
|
|
6382
|
-
} catch {
|
|
6383
|
-
return false;
|
|
6384
|
-
}
|
|
6385
|
-
}
|
|
6386
|
-
async function runJxa(script) {
|
|
6387
|
-
try {
|
|
6388
|
-
const escaped = script.replace(/'/g, `'\\''`);
|
|
6389
|
-
const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
|
|
6390
|
-
timeout: 5e3
|
|
6391
|
-
});
|
|
6392
|
-
return JSON.parse(stdout.trim());
|
|
6393
|
-
} catch {
|
|
6394
|
-
return null;
|
|
6395
|
-
}
|
|
6396
|
-
}
|
|
6397
|
-
async function hasAccessibilityPermissions() {
|
|
6398
|
-
try {
|
|
6399
|
-
const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
|
|
6400
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
6401
|
-
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
6402
|
-
}
|
|
6403
|
-
return { ok: true };
|
|
6404
|
-
} catch (err) {
|
|
6405
|
-
return { ok: false, error: err?.message || String(err) };
|
|
6406
|
-
}
|
|
6407
|
-
}
|
|
6408
|
-
async function hasScreenRecordingPermissions() {
|
|
6409
|
-
const result = await runJxa(
|
|
6410
|
-
`ObjC.import("Cocoa");
|
|
6411
|
-
ObjC.import("CoreGraphics");
|
|
6412
|
-
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
6413
|
-
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
6414
|
-
);
|
|
6415
|
-
return result?.hasAccess ?? false;
|
|
6416
|
-
}
|
|
6417
|
-
async function requestAccessibilityPrompt() {
|
|
6418
|
-
const result = await runJxa(
|
|
6419
|
-
`ObjC.import("ApplicationServices");
|
|
6420
|
-
var key = $.kAXTrustedCheckOptionPrompt;
|
|
6421
|
-
var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
|
|
6422
|
-
var trusted = $.AXIsProcessTrustedWithOptions(dict);
|
|
6423
|
-
JSON.stringify({ trusted: !!trusted });`
|
|
6424
|
-
);
|
|
6425
|
-
return result?.trusted ?? false;
|
|
6426
|
-
}
|
|
6427
|
-
async function requestScreenRecordingPrompt() {
|
|
6428
|
-
const result = await runJxa(
|
|
6429
|
-
`ObjC.import("Cocoa");
|
|
6430
|
-
ObjC.import("CoreGraphics");
|
|
6431
|
-
ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
|
|
6432
|
-
JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
|
|
6433
|
-
);
|
|
6434
|
-
return result?.granted ?? false;
|
|
6435
|
-
}
|
|
6436
|
-
async function openSystemSettings(pane) {
|
|
6437
|
-
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
6438
|
-
try {
|
|
6439
|
-
await execAsync5(`open '${url}'`, { timeout: 3e3 });
|
|
6440
|
-
} catch {
|
|
6441
|
-
}
|
|
6442
|
-
}
|
|
6443
|
-
async function detectScreenSize() {
|
|
6444
|
-
try {
|
|
6445
|
-
const { stdout } = await execAsync5(
|
|
6446
|
-
`osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
|
|
6447
|
-
{ timeout: 3e3 }
|
|
6448
|
-
);
|
|
6449
|
-
const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
|
|
6450
|
-
if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
|
|
6451
|
-
const [x1, y1, x2, y2] = parts;
|
|
6452
|
-
return { width: x2 - x1, height: y2 - y1 };
|
|
6453
|
-
}
|
|
6454
|
-
} catch {
|
|
6455
|
-
}
|
|
6456
|
-
return null;
|
|
6457
|
-
}
|
|
6458
|
-
async function runCliclick(args) {
|
|
6459
|
-
const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
6460
|
-
const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
|
|
6461
|
-
timeout: 15e3,
|
|
6462
|
-
maxBuffer: 1024 * 1024
|
|
6463
|
-
});
|
|
6464
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
6465
|
-
throw new Error(
|
|
6466
|
-
"Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
|
|
6467
|
-
);
|
|
6468
|
-
}
|
|
6469
|
-
if (stderr && !stdout) throw new Error(stderr.trim());
|
|
6470
|
-
return (stdout || "").trim();
|
|
6471
|
-
}
|
|
6472
|
-
async function runScreencapture(path) {
|
|
6473
|
-
await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
|
|
6474
|
-
timeout: 5e3
|
|
6475
|
-
});
|
|
6476
|
-
}
|
|
6477
|
-
async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
|
|
6478
|
-
const sharpModule = await import("sharp");
|
|
6479
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
6480
|
-
const meta = await sharp2(path).metadata();
|
|
6481
|
-
if (meta.width === targetWidth && meta.height === targetHeight) {
|
|
6482
|
-
return readFileSync7(path);
|
|
6483
|
-
}
|
|
6484
|
-
return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
|
|
6485
|
-
}
|
|
6486
|
-
async function runScroll(dx, dy) {
|
|
6487
|
-
const wheelY = -Math.round(dy);
|
|
6488
|
-
const wheelX = -Math.round(dx);
|
|
6489
|
-
const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
|
|
6490
|
-
await execAsync5(
|
|
6491
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
6492
|
-
{ timeout: 5e3 }
|
|
6493
|
-
);
|
|
6494
|
-
}
|
|
6495
|
-
function translateKeyForCliclick(key2) {
|
|
6496
|
-
if (!key2) return [];
|
|
6497
|
-
const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
|
|
6498
|
-
if (parts.length === 0) return [];
|
|
6499
|
-
const modMap = {
|
|
6500
|
-
ctrl: "ctrl",
|
|
6501
|
-
control: "ctrl",
|
|
6502
|
-
alt: "alt",
|
|
6503
|
-
option: "alt",
|
|
6504
|
-
shift: "shift",
|
|
6505
|
-
cmd: "cmd",
|
|
6506
|
-
super: "cmd",
|
|
6507
|
-
meta: "cmd",
|
|
6508
|
-
win: "cmd",
|
|
6509
|
-
fn: "fn"
|
|
6510
|
-
};
|
|
6511
|
-
const keyMap = {
|
|
6512
|
-
return: "enter",
|
|
6513
|
-
enter: "enter",
|
|
6514
|
-
esc: "esc",
|
|
6515
|
-
escape: "esc",
|
|
6516
|
-
backspace: "delete",
|
|
6517
|
-
back_space: "delete",
|
|
6518
|
-
delete: "fwd-delete",
|
|
6519
|
-
fwd_delete: "fwd-delete",
|
|
6520
|
-
forward_delete: "fwd-delete",
|
|
6521
|
-
tab: "tab",
|
|
6522
|
-
space: "space",
|
|
6523
|
-
up: "arrow-up",
|
|
6524
|
-
arrow_up: "arrow-up",
|
|
6525
|
-
down: "arrow-down",
|
|
6526
|
-
arrow_down: "arrow-down",
|
|
6527
|
-
left: "arrow-left",
|
|
6528
|
-
arrow_left: "arrow-left",
|
|
6529
|
-
right: "arrow-right",
|
|
6530
|
-
arrow_right: "arrow-right",
|
|
6531
|
-
page_up: "page-up",
|
|
6532
|
-
pageup: "page-up",
|
|
6533
|
-
page_down: "page-down",
|
|
6534
|
-
pagedown: "page-down",
|
|
6535
|
-
home: "home",
|
|
6536
|
-
end: "end",
|
|
6537
|
-
f1: "f1",
|
|
6538
|
-
f2: "f2",
|
|
6539
|
-
f3: "f3",
|
|
6540
|
-
f4: "f4",
|
|
6541
|
-
f5: "f5",
|
|
6542
|
-
f6: "f6",
|
|
6543
|
-
f7: "f7",
|
|
6544
|
-
f8: "f8",
|
|
6545
|
-
f9: "f9",
|
|
6546
|
-
f10: "f10",
|
|
6547
|
-
f11: "f11",
|
|
6548
|
-
f12: "f12"
|
|
6549
|
-
};
|
|
6550
|
-
const modifiers = [];
|
|
6551
|
-
let mainKey = null;
|
|
6552
|
-
for (let i = 0; i < parts.length; i++) {
|
|
6553
|
-
const lower = parts[i].toLowerCase().replace(/-/g, "_");
|
|
6554
|
-
if (i < parts.length - 1 && modMap[lower]) {
|
|
6555
|
-
modifiers.push(modMap[lower]);
|
|
6556
|
-
} else {
|
|
6557
|
-
mainKey = keyMap[lower] || lower;
|
|
6558
|
-
}
|
|
6559
|
-
}
|
|
6560
|
-
const args = [];
|
|
6561
|
-
if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
|
|
6562
|
-
if (mainKey) {
|
|
6563
|
-
const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
|
|
6564
|
-
if (isNamedKey) {
|
|
6565
|
-
args.push(`kp:${mainKey}`);
|
|
6566
|
-
} else {
|
|
6567
|
-
args.push(`t:${mainKey}`);
|
|
6568
|
-
}
|
|
6569
|
-
}
|
|
6570
|
-
if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
|
|
6571
|
-
return args;
|
|
6572
|
-
}
|
|
6573
|
-
function modifierStringToCliclick(text) {
|
|
6574
|
-
return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
|
|
6575
|
-
if (p === "ctrl" || p === "control") return "ctrl";
|
|
6576
|
-
if (p === "alt" || p === "option") return "alt";
|
|
6577
|
-
if (p === "shift") return "shift";
|
|
6578
|
-
if (p === "super" || p === "meta" || p === "cmd") return "cmd";
|
|
6579
|
-
return "";
|
|
6580
|
-
}).filter(Boolean);
|
|
6581
|
-
}
|
|
6582
|
-
function createComputerUseTool(options) {
|
|
6583
|
-
const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
|
|
6584
|
-
const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
|
|
6585
|
-
return anthropic.tools.computer_20251124({
|
|
6586
|
-
displayWidthPx: displayWidth,
|
|
6587
|
-
displayHeightPx: displayHeight,
|
|
6588
|
-
enableZoom: true,
|
|
6589
|
-
execute: async (input) => {
|
|
6590
|
-
try {
|
|
6591
|
-
switch (input.action) {
|
|
6592
|
-
case "screenshot": {
|
|
6593
|
-
const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
|
|
6594
|
-
await runScreencapture(path);
|
|
6595
|
-
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
6596
|
-
try {
|
|
6597
|
-
unlinkSync2(path);
|
|
6598
|
-
} catch {
|
|
6599
|
-
}
|
|
6600
|
-
return { type: "image", data: resized.toString("base64") };
|
|
6601
|
-
}
|
|
6602
|
-
case "left_click": {
|
|
6603
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6604
|
-
if (input.text) {
|
|
6605
|
-
const mods = modifierStringToCliclick(input.text);
|
|
6606
|
-
if (mods.length > 0) {
|
|
6607
|
-
await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
|
|
6608
|
-
} else {
|
|
6609
|
-
await runCliclick([`c:${x},${y}`]);
|
|
6610
|
-
}
|
|
6611
|
-
} else {
|
|
6612
|
-
await runCliclick([`c:${x},${y}`]);
|
|
6613
|
-
}
|
|
6614
|
-
return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
|
|
6615
|
-
}
|
|
6616
|
-
case "right_click": {
|
|
6617
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6618
|
-
await runCliclick([`rc:${x},${y}`]);
|
|
6619
|
-
return `right-clicked at (${x}, ${y})`;
|
|
6620
|
-
}
|
|
6621
|
-
case "middle_click": {
|
|
6622
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6623
|
-
const script = `ObjC.import('CoreGraphics');var loc = $.CGPointMake(${x}, ${y});var down = $.CGEventCreateMouseEvent(null, 25, loc, 2);var up = $.CGEventCreateMouseEvent(null, 26, loc, 2);$.CGEventPost(0, down); $.CGEventPost(0, up);`;
|
|
6624
|
-
await execAsync5(
|
|
6625
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
6626
|
-
{ timeout: 3e3 }
|
|
6627
|
-
);
|
|
6628
|
-
return `middle-clicked at (${x}, ${y})`;
|
|
6629
|
-
}
|
|
6630
|
-
case "double_click": {
|
|
6631
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6632
|
-
await runCliclick([`dc:${x},${y}`]);
|
|
6633
|
-
return `double-clicked at (${x}, ${y})`;
|
|
6634
|
-
}
|
|
6635
|
-
case "triple_click": {
|
|
6636
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6637
|
-
await runCliclick([`tc:${x},${y}`]);
|
|
6638
|
-
return `triple-clicked at (${x}, ${y})`;
|
|
6639
|
-
}
|
|
6640
|
-
case "mouse_move": {
|
|
6641
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6642
|
-
await runCliclick([`m:${x},${y}`]);
|
|
6643
|
-
return `moved cursor to (${x}, ${y})`;
|
|
6644
|
-
}
|
|
6645
|
-
case "left_mouse_down": {
|
|
6646
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6647
|
-
await runCliclick([`dd:${x},${y}`]);
|
|
6648
|
-
return `left mouse button pressed at (${x}, ${y})`;
|
|
6649
|
-
}
|
|
6650
|
-
case "left_mouse_up": {
|
|
6651
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6652
|
-
await runCliclick([`du:${x},${y}`]);
|
|
6653
|
-
return `left mouse button released at (${x}, ${y})`;
|
|
6654
|
-
}
|
|
6655
|
-
case "left_click_drag": {
|
|
6656
|
-
const [sx, sy] = input.start_coordinate ?? [0, 0];
|
|
6657
|
-
const [ex, ey] = input.coordinate ?? [0, 0];
|
|
6658
|
-
await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
|
|
6659
|
-
return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
|
|
6660
|
-
}
|
|
6661
|
-
case "type": {
|
|
6662
|
-
const text = input.text ?? "";
|
|
6663
|
-
await runCliclick([`t:${text}`]);
|
|
6664
|
-
return `typed ${text.length} character(s)`;
|
|
6665
|
-
}
|
|
6666
|
-
case "key": {
|
|
6667
|
-
const args = translateKeyForCliclick(input.text ?? "");
|
|
6668
|
-
if (args.length === 0) return "no key specified";
|
|
6669
|
-
await runCliclick(args);
|
|
6670
|
-
return `pressed ${input.text}`;
|
|
6671
|
-
}
|
|
6672
|
-
case "hold_key": {
|
|
6673
|
-
const text = (input.text ?? "").toLowerCase();
|
|
6674
|
-
const duration = input.duration ?? 1;
|
|
6675
|
-
const modMap = {
|
|
6676
|
-
ctrl: "ctrl",
|
|
6677
|
-
control: "ctrl",
|
|
6678
|
-
alt: "alt",
|
|
6679
|
-
option: "alt",
|
|
6680
|
-
shift: "shift",
|
|
6681
|
-
cmd: "cmd",
|
|
6682
|
-
super: "cmd",
|
|
6683
|
-
meta: "cmd",
|
|
6684
|
-
fn: "fn"
|
|
6685
|
-
};
|
|
6686
|
-
const cliName = modMap[text] || text;
|
|
6687
|
-
await runCliclick([`kd:${cliName}`]);
|
|
6688
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
6689
|
-
await runCliclick([`ku:${cliName}`]);
|
|
6690
|
-
return `held ${text} for ${duration}s`;
|
|
6691
|
-
}
|
|
6692
|
-
case "scroll": {
|
|
6693
|
-
const direction = input.scroll_direction ?? "down";
|
|
6694
|
-
const amount = input.scroll_amount ?? 3;
|
|
6695
|
-
const px = amount * 100;
|
|
6696
|
-
const dx = direction === "left" ? -px : direction === "right" ? px : 0;
|
|
6697
|
-
const dy = direction === "up" ? -px : direction === "down" ? px : 0;
|
|
6698
|
-
if (input.coordinate) {
|
|
6699
|
-
const [x, y] = input.coordinate;
|
|
6700
|
-
await runCliclick([`m:${x},${y}`]);
|
|
6701
|
-
}
|
|
6702
|
-
const mods = input.text ? modifierStringToCliclick(input.text) : [];
|
|
6703
|
-
if (mods.length > 0) {
|
|
6704
|
-
await runCliclick([`kd:${mods.join(",")}`]);
|
|
6705
|
-
}
|
|
6706
|
-
await runScroll(dx, dy);
|
|
6707
|
-
if (mods.length > 0) {
|
|
6708
|
-
await runCliclick([`ku:${mods.join(",")}`]);
|
|
6709
|
-
}
|
|
6710
|
-
return `scrolled ${direction} by ${amount}`;
|
|
6711
|
-
}
|
|
6712
|
-
case "wait": {
|
|
6713
|
-
const duration = input.duration ?? 1;
|
|
6714
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
6715
|
-
return `waited ${duration}s`;
|
|
6716
|
-
}
|
|
6717
|
-
case "cursor_position": {
|
|
6718
|
-
const out = await runCliclick(["p:."]);
|
|
6719
|
-
return `cursor at ${out}`;
|
|
6720
|
-
}
|
|
6721
|
-
case "zoom": {
|
|
6722
|
-
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
6723
|
-
const [x1, y1, x2, y2] = region;
|
|
6724
|
-
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
|
|
6725
|
-
await runScreencapture(tmpPath);
|
|
6726
|
-
const sharpModule = await import("sharp");
|
|
6727
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
6728
|
-
const meta = await sharp2(tmpPath).metadata();
|
|
6729
|
-
const scaleX = (meta.width || displayWidth) / displayWidth;
|
|
6730
|
-
const scaleY = (meta.height || displayHeight) / displayHeight;
|
|
6731
|
-
const px = {
|
|
6732
|
-
left: Math.max(0, Math.round(x1 * scaleX)),
|
|
6733
|
-
top: Math.max(0, Math.round(y1 * scaleY)),
|
|
6734
|
-
width: Math.max(1, Math.round((x2 - x1) * scaleX)),
|
|
6735
|
-
height: Math.max(1, Math.round((y2 - y1) * scaleY))
|
|
6736
|
-
};
|
|
6737
|
-
const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
|
|
6738
|
-
try {
|
|
6739
|
-
unlinkSync2(tmpPath);
|
|
6740
|
-
} catch {
|
|
6741
|
-
}
|
|
6742
|
-
return { type: "image", data: buf.toString("base64") };
|
|
6743
|
-
}
|
|
6744
|
-
default: {
|
|
6745
|
-
const exhaustive = input.action;
|
|
6746
|
-
return `unsupported action: ${String(exhaustive)}`;
|
|
6747
|
-
}
|
|
6748
|
-
}
|
|
6749
|
-
} catch (err) {
|
|
6750
|
-
const msg = err?.message || String(err);
|
|
6751
|
-
let hint = "";
|
|
6752
|
-
if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
|
|
6753
|
-
hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
|
|
6754
|
-
} else if (/command not found/i.test(msg)) {
|
|
6755
|
-
hint = " (Hint: install cliclick with `brew install cliclick`)";
|
|
6756
|
-
}
|
|
6757
|
-
return `Error: ${msg}${hint}`;
|
|
6758
|
-
}
|
|
6759
|
-
},
|
|
6760
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6761
|
-
toModelOutput({ output }) {
|
|
6762
|
-
if (typeof output === "string") {
|
|
6763
|
-
return { type: "content", value: [{ type: "text", text: output }] };
|
|
6764
|
-
}
|
|
6765
|
-
return {
|
|
6766
|
-
type: "content",
|
|
6767
|
-
value: [{ type: "media", data: output.data, mediaType: "image/png" }]
|
|
6768
|
-
};
|
|
6769
|
-
}
|
|
6770
|
-
});
|
|
6771
|
-
}
|
|
6772
|
-
var execAsync5, DEFAULT_WIDTH, DEFAULT_HEIGHT;
|
|
6773
|
-
var init_computer_use = __esm({
|
|
6774
|
-
"src/tools/computer-use.ts"() {
|
|
6775
|
-
"use strict";
|
|
6776
|
-
execAsync5 = promisify5(exec5);
|
|
6777
|
-
DEFAULT_WIDTH = 1280;
|
|
6778
|
-
DEFAULT_HEIGHT = 800;
|
|
6779
|
-
}
|
|
6780
|
-
});
|
|
6781
|
-
|
|
6782
|
-
// src/tools/enable-computer-use.ts
|
|
6783
|
-
import { tool as tool13 } from "ai";
|
|
6784
|
-
import { z as z14 } from "zod";
|
|
6785
|
-
function createEnableComputerUseTool(options) {
|
|
6786
|
-
return tool13({
|
|
6787
|
-
description: "Enable Anthropic's computer use beta tool for this session. macOS only. Drives the actual desktop (mouse, keyboard, screenshots) at pixel coordinates. Requires `cliclick` (brew install cliclick), Accessibility permissions, and Screen Recording permissions. When called, this tool will automatically request any missing permissions and open System Settings to the right pane. Only works on Anthropic Claude models. After this tool succeeds, you MUST stop the current turn and ask the user to send another message \u2014 the `computer` tool only becomes available on the NEXT message because the toolset is fixed for the current turn.",
|
|
6788
|
-
inputSchema,
|
|
6789
|
-
execute: async ({ display_width, display_height, request_permissions }) => {
|
|
6790
|
-
try {
|
|
6791
|
-
if (!isMacOs()) {
|
|
6792
|
-
return {
|
|
6793
|
-
success: false,
|
|
6794
|
-
error: "Computer use is currently only supported on macOS.",
|
|
6795
|
-
platform: process.platform
|
|
6796
|
-
};
|
|
6797
|
-
}
|
|
6798
|
-
if (!await isCliclickInstalled()) {
|
|
6799
|
-
return {
|
|
6800
|
-
success: false,
|
|
6801
|
-
error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
|
|
6802
|
-
installCommand: "brew install cliclick",
|
|
6803
|
-
fixSteps: [
|
|
6804
|
-
"In a terminal on this Mac, run: brew install cliclick",
|
|
6805
|
-
"(If Homebrew is not installed, install it first from https://brew.sh)",
|
|
6806
|
-
"Then call enable_computer_use again"
|
|
6807
|
-
]
|
|
6808
|
-
};
|
|
6809
|
-
}
|
|
6810
|
-
const acc = await hasAccessibilityPermissions();
|
|
6811
|
-
const screen = await hasScreenRecordingPermissions();
|
|
6812
|
-
const missing = [];
|
|
6813
|
-
if (!acc.ok) {
|
|
6814
|
-
let prompted = false;
|
|
6815
|
-
let panelOpened = false;
|
|
6816
|
-
if (request_permissions) {
|
|
6817
|
-
prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
|
|
6818
|
-
await openSystemSettings("accessibility").then(() => {
|
|
6819
|
-
panelOpened = true;
|
|
6820
|
-
}).catch(() => void 0);
|
|
6821
|
-
}
|
|
6822
|
-
missing.push({
|
|
6823
|
-
name: "Accessibility",
|
|
6824
|
-
reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
|
|
6825
|
-
pane: "accessibility",
|
|
6826
|
-
settingsUrl: ACCESSIBILITY_URL,
|
|
6827
|
-
fixSteps: [
|
|
6828
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
|
|
6829
|
-
"Click the + button",
|
|
6830
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6831
|
-
"Toggle the switch ON",
|
|
6832
|
-
"Restart the agent process so the new permission takes effect",
|
|
6833
|
-
"Then call enable_computer_use again"
|
|
6834
|
-
],
|
|
6835
|
-
prompted,
|
|
6836
|
-
panelOpened
|
|
6837
|
-
});
|
|
6838
|
-
}
|
|
6839
|
-
if (!screen) {
|
|
6840
|
-
let prompted = false;
|
|
6841
|
-
let panelOpened = false;
|
|
6842
|
-
if (request_permissions) {
|
|
6843
|
-
prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
|
|
6844
|
-
await openSystemSettings("screen-recording").then(() => {
|
|
6845
|
-
panelOpened = true;
|
|
6846
|
-
}).catch(() => void 0);
|
|
6847
|
-
}
|
|
6848
|
-
missing.push({
|
|
6849
|
-
name: "Screen Recording",
|
|
6850
|
-
reason: "CGPreflightScreenCaptureAccess returned false",
|
|
6851
|
-
pane: "screen-recording",
|
|
6852
|
-
settingsUrl: SCREEN_RECORDING_URL,
|
|
6853
|
-
fixSteps: [
|
|
6854
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
|
|
6855
|
-
"Click the + button",
|
|
6856
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6857
|
-
"Toggle the switch ON",
|
|
6858
|
-
"Restart the agent process so the new permission takes effect",
|
|
6859
|
-
"Then call enable_computer_use again"
|
|
6860
|
-
],
|
|
6861
|
-
prompted,
|
|
6862
|
-
panelOpened
|
|
6863
|
-
});
|
|
6864
|
-
}
|
|
6865
|
-
if (missing.length > 0) {
|
|
6866
|
-
return {
|
|
6867
|
-
success: false,
|
|
6868
|
-
error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
|
|
6869
|
-
missingPermissions: missing,
|
|
6870
|
-
note: request_permissions ? "System permission prompts have been triggered (best-effort) and System Settings has been opened to the relevant pane(s). After granting and restarting the agent, call enable_computer_use again." : "Re-run with request_permissions: true to auto-open System Settings."
|
|
6871
|
-
};
|
|
6872
|
-
}
|
|
6873
|
-
let width = display_width;
|
|
6874
|
-
let height = display_height;
|
|
6875
|
-
let detected = null;
|
|
6876
|
-
if (width === void 0 || height === void 0) {
|
|
6877
|
-
detected = await detectScreenSize();
|
|
6878
|
-
width = width ?? detected?.width ?? 1280;
|
|
6879
|
-
height = height ?? detected?.height ?? 800;
|
|
6880
|
-
}
|
|
6881
|
-
const session = await sessionQueries.getById(options.sessionId);
|
|
6882
|
-
if (!session) {
|
|
6883
|
-
return { success: false, error: "Session not found" };
|
|
6884
|
-
}
|
|
6885
|
-
const config = session.config || {};
|
|
6886
|
-
if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
|
|
6887
|
-
return {
|
|
6888
|
-
success: true,
|
|
6889
|
-
alreadyEnabled: true,
|
|
6890
|
-
message: "Computer use was already enabled for this session.",
|
|
6891
|
-
displayWidth: width,
|
|
6892
|
-
displayHeight: height
|
|
6893
|
-
};
|
|
6894
|
-
}
|
|
6895
|
-
const updated = {
|
|
6896
|
-
...config,
|
|
6897
|
-
computerUseEnabled: true,
|
|
6898
|
-
computerUseDisplayWidth: width,
|
|
6899
|
-
computerUseDisplayHeight: height
|
|
6900
|
-
};
|
|
6901
|
-
await sessionQueries.update(options.sessionId, { config: updated });
|
|
6902
|
-
return {
|
|
6903
|
-
success: true,
|
|
6904
|
-
enabled: true,
|
|
6905
|
-
platform: "darwin",
|
|
6906
|
-
displayWidth: width,
|
|
6907
|
-
displayHeight: height,
|
|
6908
|
-
detectedScreenSize: detected || void 0,
|
|
6909
|
-
permissions: {
|
|
6910
|
-
accessibility: "granted",
|
|
6911
|
-
screenRecording: "granted"
|
|
6912
|
-
},
|
|
6913
|
-
message: `Computer use is now enabled for this session. IMPORTANT: The \`computer\` tool is NOT yet available in this turn. Stop here, send a brief message to the user telling them computer use is enabled (display: ${width}x${height}), and ask them to send their next message to begin using it.`
|
|
6914
|
-
};
|
|
6915
|
-
} catch (err) {
|
|
6916
|
-
return {
|
|
6917
|
-
success: false,
|
|
6918
|
-
error: err?.message || String(err)
|
|
6919
|
-
};
|
|
6920
|
-
}
|
|
6921
|
-
}
|
|
6922
|
-
});
|
|
6923
|
-
}
|
|
6924
|
-
var inputSchema, ACCESSIBILITY_URL, SCREEN_RECORDING_URL;
|
|
6925
|
-
var init_enable_computer_use = __esm({
|
|
6926
|
-
"src/tools/enable-computer-use.ts"() {
|
|
6927
|
-
"use strict";
|
|
6928
|
-
init_db();
|
|
6929
|
-
init_computer_use();
|
|
6930
|
-
inputSchema = z14.object({
|
|
6931
|
-
display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
|
|
6932
|
-
display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
|
|
6933
|
-
request_permissions: z14.boolean().optional().default(true).describe(
|
|
6934
|
-
"When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
|
|
6935
|
-
)
|
|
6936
|
-
});
|
|
6937
|
-
ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
6938
|
-
SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
6939
|
-
}
|
|
6940
|
-
});
|
|
6941
|
-
|
|
6942
6361
|
// src/tools/index.ts
|
|
6943
6362
|
async function createTools(options) {
|
|
6944
6363
|
const tools = {
|
|
@@ -6983,20 +6402,6 @@ async function createTools(options) {
|
|
|
6983
6402
|
sessionId: options.sessionId
|
|
6984
6403
|
});
|
|
6985
6404
|
}
|
|
6986
|
-
if (process.platform === "darwin") {
|
|
6987
|
-
if (options.enableComputerUse) {
|
|
6988
|
-
tools.computer = createComputerUseTool({
|
|
6989
|
-
workingDirectory: options.workingDirectory,
|
|
6990
|
-
sessionId: options.sessionId,
|
|
6991
|
-
displayWidth: options.computerUseDisplayWidth,
|
|
6992
|
-
displayHeight: options.computerUseDisplayHeight
|
|
6993
|
-
});
|
|
6994
|
-
} else {
|
|
6995
|
-
tools.enable_computer_use = createEnableComputerUseTool({
|
|
6996
|
-
sessionId: options.sessionId
|
|
6997
|
-
});
|
|
6998
|
-
}
|
|
6999
|
-
}
|
|
7000
6405
|
if (options.enableSemanticSearch !== false) {
|
|
7001
6406
|
try {
|
|
7002
6407
|
if (isVectorGatewayConfigured()) {
|
|
@@ -7031,8 +6436,6 @@ var init_tools = __esm({
|
|
|
7031
6436
|
init_code_graph();
|
|
7032
6437
|
init_task();
|
|
7033
6438
|
init_upload_file();
|
|
7034
|
-
init_computer_use();
|
|
7035
|
-
init_enable_computer_use();
|
|
7036
6439
|
init_semantic();
|
|
7037
6440
|
init_remote();
|
|
7038
6441
|
init_bash();
|
|
@@ -7046,8 +6449,6 @@ var init_tools = __esm({
|
|
|
7046
6449
|
init_code_graph();
|
|
7047
6450
|
init_task();
|
|
7048
6451
|
init_upload_file();
|
|
7049
|
-
init_computer_use();
|
|
7050
|
-
init_enable_computer_use();
|
|
7051
6452
|
}
|
|
7052
6453
|
});
|
|
7053
6454
|
|
|
@@ -7523,8 +6924,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
7523
6924
|
`;
|
|
7524
6925
|
}
|
|
7525
6926
|
function buildOrchestratorPromptAddendum() {
|
|
7526
|
-
const
|
|
7527
|
-
const computerUseAvailable = platform3 === "darwin";
|
|
6927
|
+
const desktopAvailable = process.platform === "darwin";
|
|
7528
6928
|
return `
|
|
7529
6929
|
## Orchestrator Mode
|
|
7530
6930
|
|
|
@@ -7623,14 +7023,14 @@ When NOT to split (keep as one worker):
|
|
|
7623
7023
|
When spawning a worker, push it toward the *cheapest tool that gets the job done*:
|
|
7624
7024
|
|
|
7625
7025
|
1. **Bash / file tools** for anything with a CLI (git, npm, brew, builds, tests, file editing, HTTP via curl, scripting).
|
|
7626
|
-
2. **agent-browser** (\`load_skill browser\`) for *anything* in a web browser \u2014 refs from \`snapshot -i\` are deterministic, ~100\xD7 cheaper in tokens than pixel coordinates, work cross-platform, and don't need any host permissions.${
|
|
7627
|
-
3. **
|
|
7026
|
+
2. **agent-browser** (\`load_skill browser\`) for *anything* in a web browser \u2014 refs from \`snapshot -i\` are deterministic, ~100\xD7 cheaper in tokens than pixel coordinates, work cross-platform, and don't need any host permissions.${desktopAvailable ? `
|
|
7027
|
+
3. **Desktop automation** (\`load_skill desktop-automation\`) is the last resort \u2014 only when the task genuinely requires a native macOS GUI app with no CLI / API equivalent (System Settings, Calculator, Finder operations that don't have CLI flags, complex cross-app drag/drop, demos where the user wants to *see* the screen). It's all shell \u2014 \`cliclick\`, \`screencapture\`, and \`osascript\` \u2014 invoked from \`bash\`. No special tool registration; no vendor lock-in.
|
|
7628
7028
|
|
|
7629
|
-
A common anti-pattern: a worker reaches for
|
|
7029
|
+
A common anti-pattern: a worker reaches for desktop automation because the user phrased the request visually ("open the website and click the button"). Almost always wrong \u2014 that's a job for the browser skill, not the desktop. Coach the worker in its goal text: *"Use the browser skill (\`load_skill browser\` + \`agent-browser\` with refs from \`snapshot -i\`) to open the site and click the button. Don't use desktop automation for browser work."*
|
|
7630
7030
|
|
|
7631
|
-
### Serialize desktop
|
|
7031
|
+
### Serialize desktop-automation tasks
|
|
7632
7032
|
|
|
7633
|
-
There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both
|
|
7033
|
+
There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both drive the desktop (clicking with \`cliclick\`, taking screenshots with \`screencapture\`, opening apps, switching windows), they will **fight over the same screen** \u2014 windows will steal focus from each other, screenshots will catch the wrong app, mouse clicks will land on the wrong target.
|
|
7634
7034
|
|
|
7635
7035
|
**Rule**: when spawning workers, look at each one's goal:
|
|
7636
7036
|
|
|
@@ -7651,7 +7051,7 @@ Example: *"Take a screenshot of Calculator AND run the test suite AND open Syste
|
|
|
7651
7051
|
|
|
7652
7052
|
Headless workers never interfere with desktop workers (they don't touch the screen), so they always run in parallel.
|
|
7653
7053
|
|
|
7654
|
-
When you spawn a **desktop worker**,
|
|
7054
|
+
When you spawn a **desktop worker**, tell it to bracket the work with \`sparkecoder record start\` / \`sparkecoder record stop\` (per the \`recording\` skill) so the user can replay what happened on screen, unless the task is long-running / boring / contains sensitive content. When the worker reports back, mention the recording path in your reply via the original channel.` : ""}
|
|
7655
7055
|
|
|
7656
7056
|
Default bias: **when in doubt, decompose**. Two workers running in parallel and reporting independently is almost always better UX than one worker doing things sequentially.
|
|
7657
7057
|
|
|
@@ -7682,7 +7082,7 @@ You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
|
7682
7082
|
**DO** put in the goal:
|
|
7683
7083
|
|
|
7684
7084
|
- The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
|
|
7685
|
-
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill
|
|
7085
|
+
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill desktop-automation\`).
|
|
7686
7086
|
- Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
|
|
7687
7087
|
- Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
|
|
7688
7088
|
|
|
@@ -7715,7 +7115,7 @@ Bad goal (don't do this):
|
|
|
7715
7115
|
> "Start a screen recording with \`screencapture -v -V 45 -C /tmp/weather.mov &\`, then open Weather with \`open -a Weather\`, then click the search bar with cliclick, type 'Anchorage'..."
|
|
7716
7116
|
|
|
7717
7117
|
Good goal (do this):
|
|
7718
|
-
> "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill
|
|
7118
|
+
> "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill desktop-automation\` first; use the canonical commands from those skills. Verify the Anchorage temperature is visible in a final screenshot before completing. Return the recording path + a one-line summary of the forecast."
|
|
7719
7119
|
`;
|
|
7720
7120
|
}
|
|
7721
7121
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -7934,17 +7334,17 @@ __export(conversation_archive_exports, {
|
|
|
7934
7334
|
getHistoryDir: () => getHistoryDir,
|
|
7935
7335
|
listSessionArchives: () => listSessionArchives
|
|
7936
7336
|
});
|
|
7937
|
-
import { existsSync as
|
|
7938
|
-
import { join as
|
|
7337
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
7338
|
+
import { join as join8 } from "path";
|
|
7939
7339
|
function getHistoryDir() {
|
|
7940
|
-
const dir =
|
|
7941
|
-
if (!
|
|
7340
|
+
const dir = join8(ensureAppDataDirectory(), "history");
|
|
7341
|
+
if (!existsSync15(dir)) mkdirSync5(dir, { recursive: true });
|
|
7942
7342
|
return dir;
|
|
7943
7343
|
}
|
|
7944
7344
|
function appendTurn(turn) {
|
|
7945
7345
|
try {
|
|
7946
7346
|
const dir = getHistoryDir();
|
|
7947
|
-
const path =
|
|
7347
|
+
const path = join8(dir, `${turn.sessionId}.jsonl`);
|
|
7948
7348
|
const line = JSON.stringify({
|
|
7949
7349
|
ts: turn.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7950
7350
|
sessionId: turn.sessionId,
|
|
@@ -7973,7 +7373,7 @@ function flattenContent(content) {
|
|
|
7973
7373
|
}
|
|
7974
7374
|
function listSessionArchives() {
|
|
7975
7375
|
const dir = getHistoryDir();
|
|
7976
|
-
if (!
|
|
7376
|
+
if (!existsSync15(dir)) return [];
|
|
7977
7377
|
return readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
7978
7378
|
}
|
|
7979
7379
|
var init_conversation_archive = __esm({
|
|
@@ -8045,6 +7445,18 @@ function repairToolPairing(messages) {
|
|
|
8045
7445
|
}
|
|
8046
7446
|
return repaired;
|
|
8047
7447
|
}
|
|
7448
|
+
function ensureEndsWithUserOrTool(messages) {
|
|
7449
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
7450
|
+
const last = messages[messages.length - 1];
|
|
7451
|
+
if (last?.role !== "assistant") return messages;
|
|
7452
|
+
console.warn(
|
|
7453
|
+
"[context] Trailing assistant message detected \u2014 appending synthetic user turn to satisfy prefill restrictions"
|
|
7454
|
+
);
|
|
7455
|
+
return [
|
|
7456
|
+
...messages,
|
|
7457
|
+
{ role: "user", content: [{ type: "text", text: "Please continue." }] }
|
|
7458
|
+
];
|
|
7459
|
+
}
|
|
8048
7460
|
var TOOL_OUTPUT_TRIM_CHARS, COMPACTABLE_TOOLS, ContextManager;
|
|
8049
7461
|
var init_context = __esm({
|
|
8050
7462
|
"src/agent/context.ts"() {
|
|
@@ -8102,6 +7514,7 @@ ${summaryContent}`
|
|
|
8102
7514
|
];
|
|
8103
7515
|
}
|
|
8104
7516
|
messages = repairToolPairing(messages);
|
|
7517
|
+
messages = ensureEndsWithUserOrTool(messages);
|
|
8105
7518
|
return messages;
|
|
8106
7519
|
}
|
|
8107
7520
|
// ---------------------------------------------------------------------------
|
|
@@ -8295,7 +7708,8 @@ ${summaryContent}`
|
|
|
8295
7708
|
}
|
|
8296
7709
|
}
|
|
8297
7710
|
async addResponseMessages(messages) {
|
|
8298
|
-
|
|
7711
|
+
const safe = repairToolPairing(messages);
|
|
7712
|
+
await messageQueries.addMany(this.sessionId, safe);
|
|
8299
7713
|
try {
|
|
8300
7714
|
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
8301
7715
|
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
@@ -8391,6 +7805,44 @@ function getSlackSigningSecret() {
|
|
|
8391
7805
|
function getDefaultOrchestratorName() {
|
|
8392
7806
|
return readSlackConfig()?.defaultOrchestratorName ?? null;
|
|
8393
7807
|
}
|
|
7808
|
+
function getCachedSlackSelfIdentity() {
|
|
7809
|
+
const cfg = readSlackConfig();
|
|
7810
|
+
if (!cfg) return null;
|
|
7811
|
+
if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
|
|
7812
|
+
return null;
|
|
7813
|
+
}
|
|
7814
|
+
async function ensureSlackSelfIdentity() {
|
|
7815
|
+
const cfg = readSlackConfig();
|
|
7816
|
+
if (!cfg) return null;
|
|
7817
|
+
if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
|
|
7818
|
+
if (selfInflight) return selfInflight;
|
|
7819
|
+
selfInflight = (async () => {
|
|
7820
|
+
try {
|
|
7821
|
+
const res = await fetch("https://slack.com/api/auth.test", {
|
|
7822
|
+
method: "POST",
|
|
7823
|
+
headers: { Authorization: `Bearer ${cfg.botToken}` }
|
|
7824
|
+
});
|
|
7825
|
+
const data = await res.json().catch(() => ({}));
|
|
7826
|
+
if (!data?.ok) {
|
|
7827
|
+
console.warn(`[slack] auth.test failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
7828
|
+
return null;
|
|
7829
|
+
}
|
|
7830
|
+
const identity = {
|
|
7831
|
+
botUserId: String(data.user_id || ""),
|
|
7832
|
+
botId: String(data.bot_id || ""),
|
|
7833
|
+
teamId: data.team_id ? String(data.team_id) : void 0
|
|
7834
|
+
};
|
|
7835
|
+
cachedSelf = { token: cfg.botToken, identity };
|
|
7836
|
+
return identity;
|
|
7837
|
+
} catch (err) {
|
|
7838
|
+
console.warn("[slack] auth.test error:", err?.message || err);
|
|
7839
|
+
return null;
|
|
7840
|
+
} finally {
|
|
7841
|
+
selfInflight = null;
|
|
7842
|
+
}
|
|
7843
|
+
})();
|
|
7844
|
+
return selfInflight;
|
|
7845
|
+
}
|
|
8394
7846
|
function getSlackAllowlistPolicy() {
|
|
8395
7847
|
try {
|
|
8396
7848
|
const cfg = getConfig();
|
|
@@ -8416,11 +7868,13 @@ function getSlackDeniedReplyPolicy() {
|
|
|
8416
7868
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
8417
7869
|
}
|
|
8418
7870
|
}
|
|
8419
|
-
var DEFAULT_DENIED_TEMPLATE;
|
|
7871
|
+
var cachedSelf, selfInflight, DEFAULT_DENIED_TEMPLATE;
|
|
8420
7872
|
var init_client3 = __esm({
|
|
8421
7873
|
"src/integrations/slack/client.ts"() {
|
|
8422
7874
|
"use strict";
|
|
8423
7875
|
init_config();
|
|
7876
|
+
cachedSelf = null;
|
|
7877
|
+
selfInflight = null;
|
|
8424
7878
|
DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
|
|
8425
7879
|
}
|
|
8426
7880
|
});
|
|
@@ -8438,9 +7892,19 @@ function isThreadOwned(channel, threadTs) {
|
|
|
8438
7892
|
function stripMention(text) {
|
|
8439
7893
|
return String(text || "").replace(/<@[^>]+>/g, "").trim();
|
|
8440
7894
|
}
|
|
8441
|
-
function
|
|
7895
|
+
function isSelfAuthored(event, self) {
|
|
7896
|
+
if (!self) return true;
|
|
7897
|
+
if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
|
|
7898
|
+
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
7899
|
+
return false;
|
|
7900
|
+
}
|
|
7901
|
+
function slackEventToInboundResult(event, opts = {}) {
|
|
8442
7902
|
if (!event) return { event: null, dropReason: "empty_text" };
|
|
8443
|
-
|
|
7903
|
+
const self = opts.self ?? getCachedSlackSelfIdentity();
|
|
7904
|
+
const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
|
|
7905
|
+
if (isBotAuthored && isSelfAuthored(event, self)) {
|
|
7906
|
+
return { event: null, dropReason: "bot_message" };
|
|
7907
|
+
}
|
|
8444
7908
|
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
8445
7909
|
return { event: null, dropReason: "bot_message" };
|
|
8446
7910
|
}
|
|
@@ -8517,7 +7981,6 @@ var init_slack = __esm({
|
|
|
8517
7981
|
}
|
|
8518
7982
|
};
|
|
8519
7983
|
IGNORED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
|
|
8520
|
-
"bot_message",
|
|
8521
7984
|
"message_changed",
|
|
8522
7985
|
"message_deleted",
|
|
8523
7986
|
"channel_join",
|
|
@@ -8736,7 +8199,7 @@ var init_messenger = __esm({
|
|
|
8736
8199
|
});
|
|
8737
8200
|
|
|
8738
8201
|
// src/orchestrator/schedules-store.ts
|
|
8739
|
-
import { nanoid as
|
|
8202
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
8740
8203
|
async function readOrch(orchestratorSessionId) {
|
|
8741
8204
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
8742
8205
|
if (!s) return null;
|
|
@@ -8751,7 +8214,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
8751
8214
|
const data = await readOrch(orchestratorSessionId);
|
|
8752
8215
|
if (!data) throw new Error("orchestrator session not found");
|
|
8753
8216
|
const row = {
|
|
8754
|
-
id: `sch_${
|
|
8217
|
+
id: `sch_${nanoid4(10)}`,
|
|
8755
8218
|
name: input.name,
|
|
8756
8219
|
cron: input.cron,
|
|
8757
8220
|
prompt: input.prompt,
|
|
@@ -8788,7 +8251,7 @@ var init_schedules_store = __esm({
|
|
|
8788
8251
|
|
|
8789
8252
|
// src/orchestrator/webhooks-store.ts
|
|
8790
8253
|
import { randomBytes } from "crypto";
|
|
8791
|
-
import { nanoid as
|
|
8254
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
8792
8255
|
function newToken() {
|
|
8793
8256
|
return randomBytes(24).toString("base64url");
|
|
8794
8257
|
}
|
|
@@ -8805,7 +8268,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
8805
8268
|
const data = await readOrch2(orchestratorSessionId);
|
|
8806
8269
|
if (!data) throw new Error("orchestrator session not found");
|
|
8807
8270
|
const row = {
|
|
8808
|
-
id: `whk_${
|
|
8271
|
+
id: `whk_${nanoid5(10)}`,
|
|
8809
8272
|
name: input.name,
|
|
8810
8273
|
token: newToken(),
|
|
8811
8274
|
wake: input.wake ?? "now",
|
|
@@ -8861,8 +8324,8 @@ var init_webhooks_store = __esm({
|
|
|
8861
8324
|
});
|
|
8862
8325
|
|
|
8863
8326
|
// src/tools/orchestrator-actions.ts
|
|
8864
|
-
import { tool as
|
|
8865
|
-
import { z as
|
|
8327
|
+
import { tool as tool13 } from "ai";
|
|
8328
|
+
import { z as z14 } from "zod";
|
|
8866
8329
|
async function api2(baseUrl, path, init = {}) {
|
|
8867
8330
|
const res = await fetch(`${baseUrl}${path}`, {
|
|
8868
8331
|
method: init.method || "GET",
|
|
@@ -8888,7 +8351,7 @@ function previewMessageContent(content) {
|
|
|
8888
8351
|
return "";
|
|
8889
8352
|
}
|
|
8890
8353
|
function buildAgentTool(opts) {
|
|
8891
|
-
return
|
|
8354
|
+
return tool13({
|
|
8892
8355
|
description: "Manage worker agents. Actions: list (browse with optional status filter), get (deep dive: status, todos, pending question, recent messages, final result; required: id), spawn (start a new worker; required: name, goal), message (post a message to a running worker; force=true to soft-interrupt; required: id, text), answer_question (resolve a worker's ask_question_to_user prompt; required: id, questionId, answer), stop (hard-cancel a running worker; required: id).",
|
|
8893
8356
|
inputSchema: agentInputSchema,
|
|
8894
8357
|
execute: async (input) => {
|
|
@@ -8991,7 +8454,7 @@ function buildAgentTool(opts) {
|
|
|
8991
8454
|
});
|
|
8992
8455
|
}
|
|
8993
8456
|
function buildMessengerTool() {
|
|
8994
|
-
return
|
|
8457
|
+
return tool13({
|
|
8995
8458
|
description: "Send messages on configured external channels. Actions: list_channels (no args; returns which integrations are configured), post (required: channel, to, text). Use this to ping the user on Slack when a worker finishes, when a schedule fires, etc.",
|
|
8996
8459
|
inputSchema: messengerInputSchema,
|
|
8997
8460
|
execute: async (input) => {
|
|
@@ -9013,7 +8476,7 @@ function buildMessengerTool() {
|
|
|
9013
8476
|
});
|
|
9014
8477
|
}
|
|
9015
8478
|
function buildScheduleTool(opts) {
|
|
9016
|
-
return
|
|
8479
|
+
return tool13({
|
|
9017
8480
|
description: "Recurring prompts. Actions: create (required: name, cron, prompt), list, update (required: id; any of name/cron/prompt/enabled/replyChannel), delete (required: id), pause (required: id), resume (required: id). Cron is standard 5-field syntax.",
|
|
9018
8481
|
inputSchema: scheduleInputSchema,
|
|
9019
8482
|
execute: async (input) => {
|
|
@@ -9056,7 +8519,7 @@ function buildWebhookUrl(opts, token) {
|
|
|
9056
8519
|
return `${base}${webhookPrefix2}/inbox/${token}`;
|
|
9057
8520
|
}
|
|
9058
8521
|
function buildWebhookTool(opts) {
|
|
9059
|
-
return
|
|
8522
|
+
return tool13({
|
|
9060
8523
|
description: "Custom token-protected inbound URLs. Anyone POSTing JSON to the URL pings the orchestrator. Actions: create (required: name), list, update (required: id; optional: name, wake, template, rotateToken), delete (required: id). Use these to wire up GitHub, IFTTT, n8n, etc.",
|
|
9061
8524
|
inputSchema: webhookInputSchema,
|
|
9062
8525
|
execute: async (input) => {
|
|
@@ -9105,66 +8568,66 @@ var init_orchestrator_actions = __esm({
|
|
|
9105
8568
|
init_schedules_store();
|
|
9106
8569
|
init_config();
|
|
9107
8570
|
init_webhooks_store();
|
|
9108
|
-
AGENT_STATUS_ENUM =
|
|
9109
|
-
agentInputSchema =
|
|
9110
|
-
action:
|
|
8571
|
+
AGENT_STATUS_ENUM = z14.enum(["running", "needs_attention", "completed", "failed", "idle"]);
|
|
8572
|
+
agentInputSchema = z14.object({
|
|
8573
|
+
action: z14.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
|
|
9111
8574
|
// list
|
|
9112
8575
|
status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
|
|
9113
|
-
limit:
|
|
8576
|
+
limit: z14.number().int().min(1).max(100).optional().describe("list only: max rows."),
|
|
9114
8577
|
// get / message / answer_question / stop
|
|
9115
|
-
id:
|
|
9116
|
-
recentMessages:
|
|
8578
|
+
id: z14.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
|
|
8579
|
+
recentMessages: z14.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
|
|
9117
8580
|
// spawn
|
|
9118
|
-
name:
|
|
9119
|
-
goal:
|
|
9120
|
-
outputSchema:
|
|
8581
|
+
name: z14.string().optional().describe("spawn only: short human-readable label."),
|
|
8582
|
+
goal: z14.string().optional().describe("spawn only: the worker's self-contained instruction."),
|
|
8583
|
+
outputSchema: z14.record(z14.string(), z14.unknown()).optional().describe(
|
|
9121
8584
|
'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
|
|
9122
8585
|
),
|
|
9123
|
-
model:
|
|
9124
|
-
workingDirectory:
|
|
9125
|
-
maxIterations:
|
|
8586
|
+
model: z14.string().optional().describe("spawn only: model override."),
|
|
8587
|
+
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
8588
|
+
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
9126
8589
|
// message
|
|
9127
|
-
text:
|
|
9128
|
-
force:
|
|
8590
|
+
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
8591
|
+
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
9129
8592
|
// answer_question
|
|
9130
|
-
questionId:
|
|
9131
|
-
answer:
|
|
8593
|
+
questionId: z14.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
|
|
8594
|
+
answer: z14.string().optional().describe("answer_question only: your answer.")
|
|
9132
8595
|
});
|
|
9133
|
-
messengerInputSchema =
|
|
9134
|
-
action:
|
|
8596
|
+
messengerInputSchema = z14.object({
|
|
8597
|
+
action: z14.enum(["list_channels", "post"]),
|
|
9135
8598
|
// post
|
|
9136
|
-
channel:
|
|
9137
|
-
to:
|
|
9138
|
-
text:
|
|
9139
|
-
threadTs:
|
|
9140
|
-
subject:
|
|
8599
|
+
channel: z14.string().optional().describe('post only: channel id (e.g. "slack").'),
|
|
8600
|
+
to: z14.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
|
|
8601
|
+
text: z14.string().optional().describe("post only: message body."),
|
|
8602
|
+
threadTs: z14.string().optional().describe("post + slack: reply in this thread."),
|
|
8603
|
+
subject: z14.string().optional().describe("post + email: subject (future).")
|
|
9141
8604
|
});
|
|
9142
|
-
scheduleInputSchema =
|
|
9143
|
-
action:
|
|
8605
|
+
scheduleInputSchema = z14.object({
|
|
8606
|
+
action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
9144
8607
|
// create / update
|
|
9145
|
-
name:
|
|
9146
|
-
cron:
|
|
9147
|
-
prompt:
|
|
9148
|
-
replyChannel:
|
|
8608
|
+
name: z14.string().optional().describe("create | update"),
|
|
8609
|
+
cron: z14.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
|
|
8610
|
+
prompt: z14.string().optional().describe("create | update: the prompt injected when the schedule fires."),
|
|
8611
|
+
replyChannel: z14.string().optional().describe("create | update: default channel id for orchestrator replies."),
|
|
9149
8612
|
// update / delete / pause / resume
|
|
9150
|
-
id:
|
|
9151
|
-
enabled:
|
|
8613
|
+
id: z14.string().optional().describe("update | delete | pause | resume: schedule id."),
|
|
8614
|
+
enabled: z14.boolean().optional().describe("update only.")
|
|
9152
8615
|
});
|
|
9153
|
-
webhookInputSchema =
|
|
9154
|
-
action:
|
|
9155
|
-
name:
|
|
9156
|
-
wake:
|
|
9157
|
-
template:
|
|
9158
|
-
id:
|
|
9159
|
-
rotateToken:
|
|
8616
|
+
webhookInputSchema = z14.object({
|
|
8617
|
+
action: z14.enum(["create", "list", "update", "delete"]),
|
|
8618
|
+
name: z14.string().optional().describe("create | update."),
|
|
8619
|
+
wake: z14.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
|
|
8620
|
+
template: z14.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
|
|
8621
|
+
id: z14.string().optional().describe("update | delete: webhook id."),
|
|
8622
|
+
rotateToken: z14.boolean().optional().describe("update only: regenerate the URL token.")
|
|
9160
8623
|
});
|
|
9161
8624
|
}
|
|
9162
8625
|
});
|
|
9163
8626
|
|
|
9164
8627
|
// src/integrations/mcp/store.ts
|
|
9165
|
-
import { nanoid as
|
|
9166
|
-
import { existsSync as
|
|
9167
|
-
import { resolve as resolve10, join as
|
|
8628
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
8629
|
+
import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
|
|
8630
|
+
import { resolve as resolve10, join as join9 } from "path";
|
|
9168
8631
|
function readServers() {
|
|
9169
8632
|
try {
|
|
9170
8633
|
const cfg = getConfig();
|
|
@@ -9176,12 +8639,12 @@ function readServers() {
|
|
|
9176
8639
|
function refreshMcpServersFromDisk() {
|
|
9177
8640
|
const candidates = [
|
|
9178
8641
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9179
|
-
|
|
8642
|
+
join9(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9180
8643
|
];
|
|
9181
8644
|
for (const path of candidates) {
|
|
9182
|
-
if (!
|
|
8645
|
+
if (!existsSync16(path)) continue;
|
|
9183
8646
|
try {
|
|
9184
|
-
const raw = JSON.parse(
|
|
8647
|
+
const raw = JSON.parse(readFileSync7(path, "utf-8"));
|
|
9185
8648
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9186
8649
|
setMcpServers(servers2);
|
|
9187
8650
|
return servers2;
|
|
@@ -9200,7 +8663,7 @@ function createMcpServer(input) {
|
|
|
9200
8663
|
const all = readServers();
|
|
9201
8664
|
validateInput(input);
|
|
9202
8665
|
const row = {
|
|
9203
|
-
id: `mcp_${
|
|
8666
|
+
id: `mcp_${nanoid6(10)}`,
|
|
9204
8667
|
name: sanitizeName(input.name),
|
|
9205
8668
|
transport: input.transport,
|
|
9206
8669
|
url: input.url,
|
|
@@ -9791,15 +9254,15 @@ var recorder_exports = {};
|
|
|
9791
9254
|
__export(recorder_exports, {
|
|
9792
9255
|
FrameRecorder: () => FrameRecorder
|
|
9793
9256
|
});
|
|
9794
|
-
import { exec as
|
|
9795
|
-
import { promisify as
|
|
9257
|
+
import { exec as exec5 } from "child_process";
|
|
9258
|
+
import { promisify as promisify5 } from "util";
|
|
9796
9259
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
9797
|
-
import { join as
|
|
9798
|
-
import { tmpdir
|
|
9799
|
-
import { nanoid as
|
|
9260
|
+
import { join as join10 } from "path";
|
|
9261
|
+
import { tmpdir } from "os";
|
|
9262
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
9800
9263
|
async function checkFfmpeg() {
|
|
9801
9264
|
try {
|
|
9802
|
-
await
|
|
9265
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
9803
9266
|
return true;
|
|
9804
9267
|
} catch {
|
|
9805
9268
|
return false;
|
|
@@ -9811,11 +9274,11 @@ async function cleanup(dir) {
|
|
|
9811
9274
|
} catch {
|
|
9812
9275
|
}
|
|
9813
9276
|
}
|
|
9814
|
-
var
|
|
9277
|
+
var execAsync5, FrameRecorder;
|
|
9815
9278
|
var init_recorder = __esm({
|
|
9816
9279
|
"src/browser/recorder.ts"() {
|
|
9817
9280
|
"use strict";
|
|
9818
|
-
|
|
9281
|
+
execAsync5 = promisify5(exec5);
|
|
9819
9282
|
FrameRecorder = class {
|
|
9820
9283
|
frames = [];
|
|
9821
9284
|
startTime = null;
|
|
@@ -9851,21 +9314,21 @@ var init_recorder = __esm({
|
|
|
9851
9314
|
*/
|
|
9852
9315
|
async encode() {
|
|
9853
9316
|
if (this.frames.length === 0) return null;
|
|
9854
|
-
const workDir =
|
|
9317
|
+
const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
9855
9318
|
await mkdir4(workDir, { recursive: true });
|
|
9856
9319
|
try {
|
|
9857
9320
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9858
|
-
const framePath =
|
|
9321
|
+
const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
9859
9322
|
await writeFile5(framePath, this.frames[i].data);
|
|
9860
9323
|
}
|
|
9861
9324
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9862
9325
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9863
9326
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9864
|
-
const outputPath =
|
|
9327
|
+
const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
|
|
9865
9328
|
const hasFfmpeg = await checkFfmpeg();
|
|
9866
9329
|
if (hasFfmpeg) {
|
|
9867
|
-
await
|
|
9868
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
9330
|
+
await execAsync5(
|
|
9331
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
9869
9332
|
{ timeout: 12e4 }
|
|
9870
9333
|
);
|
|
9871
9334
|
} else {
|
|
@@ -9877,7 +9340,7 @@ var init_recorder = __esm({
|
|
|
9877
9340
|
const files = await readdir5(workDir);
|
|
9878
9341
|
for (const f of files) {
|
|
9879
9342
|
if (f.startsWith("frame_")) {
|
|
9880
|
-
await unlink2(
|
|
9343
|
+
await unlink2(join10(workDir, f)).catch(() => {
|
|
9881
9344
|
});
|
|
9882
9345
|
}
|
|
9883
9346
|
}
|
|
@@ -9902,11 +9365,11 @@ var init_recorder = __esm({
|
|
|
9902
9365
|
import {
|
|
9903
9366
|
streamText as streamText2,
|
|
9904
9367
|
generateText as generateText3,
|
|
9905
|
-
tool as
|
|
9368
|
+
tool as tool14,
|
|
9906
9369
|
stepCountIs as stepCountIs2
|
|
9907
9370
|
} from "ai";
|
|
9908
|
-
import { z as
|
|
9909
|
-
import { nanoid as
|
|
9371
|
+
import { z as z15 } from "zod";
|
|
9372
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
9910
9373
|
function anySignal(signals) {
|
|
9911
9374
|
const ctrl = new AbortController();
|
|
9912
9375
|
for (const s of signals) {
|
|
@@ -9975,14 +9438,10 @@ var init_agent = __esm({
|
|
|
9975
9438
|
*/
|
|
9976
9439
|
async createToolsWithCallbacks(options) {
|
|
9977
9440
|
const config = getConfig();
|
|
9978
|
-
const sessionConfig = this.session.config || {};
|
|
9979
9441
|
const tools = await createTools({
|
|
9980
9442
|
sessionId: this.session.id,
|
|
9981
9443
|
workingDirectory: this.session.workingDirectory,
|
|
9982
9444
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
9983
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
9984
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
9985
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
9986
9445
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
9987
9446
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
9988
9447
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -10032,14 +9491,10 @@ var init_agent = __esm({
|
|
|
10032
9491
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
10033
9492
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
10034
9493
|
});
|
|
10035
|
-
const sessionConfig = session.config || {};
|
|
10036
9494
|
const tools = await createTools({
|
|
10037
9495
|
sessionId: session.id,
|
|
10038
9496
|
workingDirectory: session.workingDirectory,
|
|
10039
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
10040
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
10041
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
10042
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
9497
|
+
skillsDirectories: config.resolvedSkillsDirectories
|
|
10043
9498
|
});
|
|
10044
9499
|
if (session.config?.role === "orchestrator") {
|
|
10045
9500
|
const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
|
|
@@ -10277,14 +9732,10 @@ ${personality.trim()}`;
|
|
|
10277
9732
|
});
|
|
10278
9733
|
}
|
|
10279
9734
|
};
|
|
10280
|
-
const taskSessionConfig = this.session.config || {};
|
|
10281
9735
|
const taskTools = await createTools({
|
|
10282
9736
|
sessionId: this.session.id,
|
|
10283
9737
|
workingDirectory: this.session.workingDirectory,
|
|
10284
9738
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
10285
|
-
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
10286
|
-
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
10287
|
-
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
10288
9739
|
onBashProgress: bashProgressHandler,
|
|
10289
9740
|
onWriteFileProgress: (progress) => {
|
|
10290
9741
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -10632,11 +10083,11 @@ ${p.text}` : p.text;
|
|
|
10632
10083
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10633
10084
|
if (!isRemoteConfigured2()) return [];
|
|
10634
10085
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
10635
|
-
const { join:
|
|
10086
|
+
const { join: join16, basename: basename6 } = await import("path");
|
|
10636
10087
|
const urls = [];
|
|
10637
10088
|
for (const filePath of filePaths) {
|
|
10638
10089
|
try {
|
|
10639
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10090
|
+
const fullPath = filePath.startsWith("/") ? filePath : join16(this.session.workingDirectory, filePath);
|
|
10640
10091
|
const fileName = basename6(fullPath);
|
|
10641
10092
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10642
10093
|
const mimeMap = {
|
|
@@ -10694,11 +10145,11 @@ ${p.text}` : p.text;
|
|
|
10694
10145
|
wrappedTools[name] = originalTool;
|
|
10695
10146
|
continue;
|
|
10696
10147
|
}
|
|
10697
|
-
wrappedTools[name] =
|
|
10148
|
+
wrappedTools[name] = tool14({
|
|
10698
10149
|
description: originalTool.description || "",
|
|
10699
|
-
inputSchema: originalTool.inputSchema ||
|
|
10150
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
10700
10151
|
execute: async (input, toolOptions) => {
|
|
10701
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
10152
|
+
const toolCallId = toolOptions.toolCallId || nanoid8();
|
|
10702
10153
|
const execution = toolExecutionQueries.create({
|
|
10703
10154
|
sessionId: this.session.id,
|
|
10704
10155
|
toolName: name,
|
|
@@ -10716,10 +10167,10 @@ ${p.text}` : p.text;
|
|
|
10716
10167
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
10717
10168
|
approvalResolvers.delete(toolCallId);
|
|
10718
10169
|
this.pendingApprovals.delete(toolCallId);
|
|
10719
|
-
const
|
|
10170
|
+
const exec7 = await execution;
|
|
10720
10171
|
if (!approved) {
|
|
10721
10172
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
10722
|
-
await toolExecutionQueries.reject(
|
|
10173
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
10723
10174
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10724
10175
|
return {
|
|
10725
10176
|
status: "rejected",
|
|
@@ -10729,14 +10180,14 @@ ${p.text}` : p.text;
|
|
|
10729
10180
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
10730
10181
|
};
|
|
10731
10182
|
}
|
|
10732
|
-
await toolExecutionQueries.approve(
|
|
10183
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
10733
10184
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10734
10185
|
try {
|
|
10735
10186
|
const result = await originalTool.execute(input, toolOptions);
|
|
10736
|
-
await toolExecutionQueries.complete(
|
|
10187
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
10737
10188
|
return result;
|
|
10738
10189
|
} catch (error) {
|
|
10739
|
-
await toolExecutionQueries.complete(
|
|
10190
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
10740
10191
|
throw error;
|
|
10741
10192
|
}
|
|
10742
10193
|
}
|
|
@@ -10841,19 +10292,19 @@ var init_session_lock = __esm({
|
|
|
10841
10292
|
});
|
|
10842
10293
|
|
|
10843
10294
|
// src/orchestrator/webhook-events.ts
|
|
10844
|
-
import { existsSync as
|
|
10845
|
-
import { dirname as dirname6, join as
|
|
10846
|
-
import { nanoid as
|
|
10295
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
10296
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
10297
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
10847
10298
|
function logFilePath() {
|
|
10848
|
-
return
|
|
10299
|
+
return join11(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10849
10300
|
}
|
|
10850
10301
|
function ensureLoaded() {
|
|
10851
10302
|
if (cache !== null) return cache;
|
|
10852
10303
|
cache = [];
|
|
10853
10304
|
try {
|
|
10854
10305
|
const p = logFilePath();
|
|
10855
|
-
if (!
|
|
10856
|
-
const lines =
|
|
10306
|
+
if (!existsSync17(p)) return cache;
|
|
10307
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
10857
10308
|
for (const line of lines) {
|
|
10858
10309
|
try {
|
|
10859
10310
|
cache.push(JSON.parse(line));
|
|
@@ -10877,14 +10328,14 @@ function appendEvent(ev) {
|
|
|
10877
10328
|
if (list.length > MAX_EVENTS) list.shift();
|
|
10878
10329
|
try {
|
|
10879
10330
|
const p = logFilePath();
|
|
10880
|
-
|
|
10331
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
10881
10332
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10882
10333
|
} catch {
|
|
10883
10334
|
}
|
|
10884
10335
|
}
|
|
10885
10336
|
function recordEvent(ev) {
|
|
10886
10337
|
const full = {
|
|
10887
|
-
id: ev.id ??
|
|
10338
|
+
id: ev.id ?? nanoid9(),
|
|
10888
10339
|
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10889
10340
|
source: ev.source,
|
|
10890
10341
|
status: ev.status,
|
|
@@ -10908,7 +10359,7 @@ function updateEvent(id, patch) {
|
|
|
10908
10359
|
list[i] = { ...list[i], ...patch };
|
|
10909
10360
|
try {
|
|
10910
10361
|
const p = logFilePath();
|
|
10911
|
-
|
|
10362
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
10912
10363
|
writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10913
10364
|
} catch {
|
|
10914
10365
|
}
|
|
@@ -11212,8 +10663,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
11212
10663
|
import { serve } from "@hono/node-server";
|
|
11213
10664
|
import { cors } from "hono/cors";
|
|
11214
10665
|
import { logger } from "hono/logger";
|
|
11215
|
-
import { existsSync as
|
|
11216
|
-
import { resolve as resolve11, dirname as dirname8, join as
|
|
10666
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
10667
|
+
import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
|
|
11217
10668
|
import { spawn as spawn2 } from "child_process";
|
|
11218
10669
|
import { createServer as createNetServer } from "net";
|
|
11219
10670
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -11226,11 +10677,11 @@ init_tmux();
|
|
|
11226
10677
|
init_checkpoints();
|
|
11227
10678
|
import { Hono } from "hono";
|
|
11228
10679
|
import { zValidator } from "@hono/zod-validator";
|
|
11229
|
-
import { z as
|
|
11230
|
-
import { existsSync as
|
|
10680
|
+
import { z as z16 } from "zod";
|
|
10681
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
11231
10682
|
import { readdir as readdir6 } from "fs/promises";
|
|
11232
|
-
import { join as
|
|
11233
|
-
import { nanoid as
|
|
10683
|
+
import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
10684
|
+
import { nanoid as nanoid10 } from "nanoid";
|
|
11234
10685
|
|
|
11235
10686
|
// src/tasks/agent-status.ts
|
|
11236
10687
|
init_questions();
|
|
@@ -11288,22 +10739,22 @@ function cleanupPendingInputs() {
|
|
|
11288
10739
|
}
|
|
11289
10740
|
}
|
|
11290
10741
|
}
|
|
11291
|
-
var createSessionSchema =
|
|
11292
|
-
name:
|
|
11293
|
-
workingDirectory:
|
|
11294
|
-
model:
|
|
11295
|
-
toolApprovals:
|
|
11296
|
-
// Optional full session-config passthrough (
|
|
11297
|
-
config:
|
|
11298
|
-
role:
|
|
10742
|
+
var createSessionSchema = z16.object({
|
|
10743
|
+
name: z16.string().optional(),
|
|
10744
|
+
workingDirectory: z16.string().optional(),
|
|
10745
|
+
model: z16.string().optional(),
|
|
10746
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
|
|
10747
|
+
// Optional full session-config passthrough (role, persona, etc.)
|
|
10748
|
+
config: z16.record(z16.string(), z16.unknown()).optional(),
|
|
10749
|
+
role: z16.enum(["orchestrator", "worker", "chat"]).optional()
|
|
11299
10750
|
});
|
|
11300
|
-
var paginationQuerySchema =
|
|
11301
|
-
limit:
|
|
11302
|
-
offset:
|
|
11303
|
-
role:
|
|
10751
|
+
var paginationQuerySchema = z16.object({
|
|
10752
|
+
limit: z16.string().optional(),
|
|
10753
|
+
offset: z16.string().optional(),
|
|
10754
|
+
role: z16.enum(["orchestrator", "worker", "chat", "all"]).optional()
|
|
11304
10755
|
});
|
|
11305
|
-
var messagesQuerySchema =
|
|
11306
|
-
limit:
|
|
10756
|
+
var messagesQuerySchema = z16.object({
|
|
10757
|
+
limit: z16.string().optional()
|
|
11307
10758
|
});
|
|
11308
10759
|
sessions2.get(
|
|
11309
10760
|
"/",
|
|
@@ -11372,15 +10823,11 @@ sessions2.post(
|
|
|
11372
10823
|
async (c) => {
|
|
11373
10824
|
const body = c.req.valid("json");
|
|
11374
10825
|
const config = getConfig();
|
|
11375
|
-
const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
|
|
11376
10826
|
const baseConfig = body.config || {};
|
|
11377
10827
|
const mergedConfig = {
|
|
11378
10828
|
...baseConfig,
|
|
11379
10829
|
...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
|
|
11380
|
-
...body.role ? { role: body.role } : {}
|
|
11381
|
-
// Turn on computer use by default if the server was launched with --enable-computer-use,
|
|
11382
|
-
// unless the client explicitly provided a value.
|
|
11383
|
-
...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
|
|
10830
|
+
...body.role ? { role: body.role } : {}
|
|
11384
10831
|
};
|
|
11385
10832
|
const agent = await Agent.create({
|
|
11386
10833
|
name: body.name,
|
|
@@ -11557,10 +11004,10 @@ sessions2.get("/:id/tools", async (c) => {
|
|
|
11557
11004
|
count: executions.length
|
|
11558
11005
|
});
|
|
11559
11006
|
});
|
|
11560
|
-
var updateSessionSchema =
|
|
11561
|
-
model:
|
|
11562
|
-
name:
|
|
11563
|
-
toolApprovals:
|
|
11007
|
+
var updateSessionSchema = z16.object({
|
|
11008
|
+
model: z16.string().optional(),
|
|
11009
|
+
name: z16.string().optional(),
|
|
11010
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
11564
11011
|
});
|
|
11565
11012
|
sessions2.patch(
|
|
11566
11013
|
"/:id",
|
|
@@ -11630,10 +11077,10 @@ sessions2.post("/:id/clear", async (c) => {
|
|
|
11630
11077
|
await agent.clearContext();
|
|
11631
11078
|
return c.json({ success: true, sessionId: id });
|
|
11632
11079
|
});
|
|
11633
|
-
var injectMessageSchema =
|
|
11634
|
-
text:
|
|
11635
|
-
source:
|
|
11636
|
-
force:
|
|
11080
|
+
var injectMessageSchema = z16.object({
|
|
11081
|
+
text: z16.string().min(1),
|
|
11082
|
+
source: z16.enum(["orchestrator", "user", "system"]).optional(),
|
|
11083
|
+
force: z16.boolean().optional()
|
|
11637
11084
|
});
|
|
11638
11085
|
sessions2.post(
|
|
11639
11086
|
"/:id/messages",
|
|
@@ -11647,8 +11094,8 @@ sessions2.post(
|
|
|
11647
11094
|
return c.json({ success: true, sessionId: id, queued: true, force });
|
|
11648
11095
|
}
|
|
11649
11096
|
);
|
|
11650
|
-
var pendingInputSchema =
|
|
11651
|
-
text:
|
|
11097
|
+
var pendingInputSchema = z16.object({
|
|
11098
|
+
text: z16.string()
|
|
11652
11099
|
});
|
|
11653
11100
|
sessions2.post(
|
|
11654
11101
|
"/:id/pending-input",
|
|
@@ -11679,13 +11126,13 @@ sessions2.get("/:id/pending-input", async (c) => {
|
|
|
11679
11126
|
createdAt: pending.createdAt.toISOString()
|
|
11680
11127
|
});
|
|
11681
11128
|
});
|
|
11682
|
-
var devtoolsContextSchema =
|
|
11683
|
-
url:
|
|
11684
|
-
path:
|
|
11685
|
-
pageName:
|
|
11686
|
-
screenWidth:
|
|
11687
|
-
screenHeight:
|
|
11688
|
-
devicePixelRatio:
|
|
11129
|
+
var devtoolsContextSchema = z16.object({
|
|
11130
|
+
url: z16.string(),
|
|
11131
|
+
path: z16.string(),
|
|
11132
|
+
pageName: z16.string().optional(),
|
|
11133
|
+
screenWidth: z16.number().optional(),
|
|
11134
|
+
screenHeight: z16.number().optional(),
|
|
11135
|
+
devicePixelRatio: z16.number().optional()
|
|
11689
11136
|
});
|
|
11690
11137
|
sessions2.post(
|
|
11691
11138
|
"/:id/devtools-context",
|
|
@@ -11871,12 +11318,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11871
11318
|
});
|
|
11872
11319
|
function getAttachmentsDir(sessionId) {
|
|
11873
11320
|
const appDataDir = getAppDataDirectory();
|
|
11874
|
-
return
|
|
11321
|
+
return join12(appDataDir, "attachments", sessionId);
|
|
11875
11322
|
}
|
|
11876
11323
|
function ensureAttachmentsDir(sessionId) {
|
|
11877
11324
|
const dir = getAttachmentsDir(sessionId);
|
|
11878
|
-
if (!
|
|
11879
|
-
|
|
11325
|
+
if (!existsSync18(dir)) {
|
|
11326
|
+
mkdirSync7(dir, { recursive: true });
|
|
11880
11327
|
}
|
|
11881
11328
|
return dir;
|
|
11882
11329
|
}
|
|
@@ -11887,12 +11334,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11887
11334
|
return c.json({ error: "Session not found" }, 404);
|
|
11888
11335
|
}
|
|
11889
11336
|
const dir = getAttachmentsDir(sessionId);
|
|
11890
|
-
if (!
|
|
11337
|
+
if (!existsSync18(dir)) {
|
|
11891
11338
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
11892
11339
|
}
|
|
11893
11340
|
const files = readdirSync3(dir);
|
|
11894
11341
|
const attachments = files.map((filename) => {
|
|
11895
|
-
const filePath =
|
|
11342
|
+
const filePath = join12(dir, filename);
|
|
11896
11343
|
const stats = statSync2(filePath);
|
|
11897
11344
|
return {
|
|
11898
11345
|
id: filename.split("_")[0],
|
|
@@ -11924,10 +11371,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11924
11371
|
return c.json({ error: "No file provided" }, 400);
|
|
11925
11372
|
}
|
|
11926
11373
|
const dir = ensureAttachmentsDir(sessionId);
|
|
11927
|
-
const id =
|
|
11374
|
+
const id = nanoid10(10);
|
|
11928
11375
|
const ext = extname8(file.name) || "";
|
|
11929
11376
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11930
|
-
const filePath =
|
|
11377
|
+
const filePath = join12(dir, safeFilename);
|
|
11931
11378
|
const arrayBuffer = await file.arrayBuffer();
|
|
11932
11379
|
writeFileSync4(filePath, Buffer.from(arrayBuffer));
|
|
11933
11380
|
return c.json({
|
|
@@ -11950,10 +11397,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11950
11397
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
11951
11398
|
}
|
|
11952
11399
|
const dir = ensureAttachmentsDir(sessionId);
|
|
11953
|
-
const id =
|
|
11400
|
+
const id = nanoid10(10);
|
|
11954
11401
|
const ext = extname8(body.filename) || "";
|
|
11955
11402
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11956
|
-
const filePath =
|
|
11403
|
+
const filePath = join12(dir, safeFilename);
|
|
11957
11404
|
let base64Data = body.data;
|
|
11958
11405
|
if (base64Data.includes(",")) {
|
|
11959
11406
|
base64Data = base64Data.split(",")[1];
|
|
@@ -11982,7 +11429,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11982
11429
|
return c.json({ error: "Session not found" }, 404);
|
|
11983
11430
|
}
|
|
11984
11431
|
const dir = getAttachmentsDir(sessionId);
|
|
11985
|
-
if (!
|
|
11432
|
+
if (!existsSync18(dir)) {
|
|
11986
11433
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11987
11434
|
}
|
|
11988
11435
|
const files = readdirSync3(dir);
|
|
@@ -11990,14 +11437,14 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
11990
11437
|
if (!file) {
|
|
11991
11438
|
return c.json({ error: "Attachment not found" }, 404);
|
|
11992
11439
|
}
|
|
11993
|
-
const filePath =
|
|
11994
|
-
|
|
11440
|
+
const filePath = join12(dir, file);
|
|
11441
|
+
unlinkSync2(filePath);
|
|
11995
11442
|
return c.json({ success: true, id: attachmentId });
|
|
11996
11443
|
});
|
|
11997
|
-
var filesQuerySchema =
|
|
11998
|
-
query:
|
|
11444
|
+
var filesQuerySchema = z16.object({
|
|
11445
|
+
query: z16.string().optional(),
|
|
11999
11446
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
12000
|
-
limit:
|
|
11447
|
+
limit: z16.string().optional()
|
|
12001
11448
|
// Max results (default 50)
|
|
12002
11449
|
});
|
|
12003
11450
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -12073,7 +11520,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12073
11520
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12074
11521
|
for (const entry2 of entries) {
|
|
12075
11522
|
if (results.length >= limit * 2) break;
|
|
12076
|
-
const fullPath =
|
|
11523
|
+
const fullPath = join12(currentDir, entry2.name);
|
|
12077
11524
|
const relativePath = relative9(baseDir, fullPath);
|
|
12078
11525
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12079
11526
|
continue;
|
|
@@ -12121,7 +11568,7 @@ sessions2.get(
|
|
|
12121
11568
|
return c.json({ error: "Session not found" }, 404);
|
|
12122
11569
|
}
|
|
12123
11570
|
const workingDirectory = session.workingDirectory;
|
|
12124
|
-
if (!
|
|
11571
|
+
if (!existsSync18(workingDirectory)) {
|
|
12125
11572
|
return c.json({
|
|
12126
11573
|
sessionId,
|
|
12127
11574
|
workingDirectory,
|
|
@@ -12234,9 +11681,9 @@ init_session_lock();
|
|
|
12234
11681
|
init_config();
|
|
12235
11682
|
import { Hono as Hono2 } from "hono";
|
|
12236
11683
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12237
|
-
import { z as
|
|
12238
|
-
import { existsSync as
|
|
12239
|
-
import { join as
|
|
11684
|
+
import { z as z17 } from "zod";
|
|
11685
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
11686
|
+
import { join as join13 } from "path";
|
|
12240
11687
|
|
|
12241
11688
|
// src/server/resumable-stream.ts
|
|
12242
11689
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -12323,7 +11770,7 @@ var streamContext = createResumableStreamContext({
|
|
|
12323
11770
|
|
|
12324
11771
|
// src/server/routes/agents.ts
|
|
12325
11772
|
init_checkpoints();
|
|
12326
|
-
import { nanoid as
|
|
11773
|
+
import { nanoid as nanoid11 } from "nanoid";
|
|
12327
11774
|
init_stream_proxy();
|
|
12328
11775
|
init_recorder();
|
|
12329
11776
|
init_remote();
|
|
@@ -12415,40 +11862,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
12415
11862
|
${prompt}`;
|
|
12416
11863
|
}
|
|
12417
11864
|
var agents = new Hono2();
|
|
12418
|
-
var attachmentSchema =
|
|
12419
|
-
type:
|
|
12420
|
-
data:
|
|
11865
|
+
var attachmentSchema = z17.object({
|
|
11866
|
+
type: z17.enum(["image", "file"]),
|
|
11867
|
+
data: z17.string(),
|
|
12421
11868
|
// base64 data URL or raw base64
|
|
12422
|
-
mediaType:
|
|
12423
|
-
filename:
|
|
11869
|
+
mediaType: z17.string().optional(),
|
|
11870
|
+
filename: z17.string().optional()
|
|
12424
11871
|
});
|
|
12425
|
-
var runPromptSchema =
|
|
12426
|
-
prompt:
|
|
11872
|
+
var runPromptSchema = z17.object({
|
|
11873
|
+
prompt: z17.string(),
|
|
12427
11874
|
// Can be empty if attachments are provided
|
|
12428
|
-
attachments:
|
|
11875
|
+
attachments: z17.array(attachmentSchema).optional()
|
|
12429
11876
|
}).refine(
|
|
12430
11877
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
12431
11878
|
{ message: "Either prompt or attachments must be provided" }
|
|
12432
11879
|
);
|
|
12433
|
-
var quickStartSchema =
|
|
12434
|
-
prompt:
|
|
12435
|
-
name:
|
|
12436
|
-
workingDirectory:
|
|
12437
|
-
model:
|
|
12438
|
-
toolApprovals:
|
|
12439
|
-
});
|
|
12440
|
-
var rejectSchema =
|
|
12441
|
-
reason:
|
|
11880
|
+
var quickStartSchema = z17.object({
|
|
11881
|
+
prompt: z17.string().min(1),
|
|
11882
|
+
name: z17.string().optional(),
|
|
11883
|
+
workingDirectory: z17.string().optional(),
|
|
11884
|
+
model: z17.string().optional(),
|
|
11885
|
+
toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
|
|
11886
|
+
});
|
|
11887
|
+
var rejectSchema = z17.object({
|
|
11888
|
+
reason: z17.string().optional()
|
|
12442
11889
|
}).optional();
|
|
12443
11890
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12444
11891
|
function getAttachmentsDirectory(sessionId) {
|
|
12445
11892
|
const appDataDir = getAppDataDirectory();
|
|
12446
|
-
return
|
|
11893
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
12447
11894
|
}
|
|
12448
11895
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12449
11896
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
12450
|
-
if (!
|
|
12451
|
-
|
|
11897
|
+
if (!existsSync19(attachmentsDir)) {
|
|
11898
|
+
mkdirSync8(attachmentsDir, { recursive: true });
|
|
12452
11899
|
}
|
|
12453
11900
|
let filename = attachment.filename;
|
|
12454
11901
|
if (!filename) {
|
|
@@ -12466,7 +11913,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12466
11913
|
attachment.mediaType = resized.mediaType;
|
|
12467
11914
|
attachment.data = buffer.toString("base64");
|
|
12468
11915
|
}
|
|
12469
|
-
const filePath =
|
|
11916
|
+
const filePath = join13(attachmentsDir, filename);
|
|
12470
11917
|
writeFileSync5(filePath, buffer);
|
|
12471
11918
|
return filePath;
|
|
12472
11919
|
}
|
|
@@ -12903,7 +12350,7 @@ ${prompt}` });
|
|
|
12903
12350
|
});
|
|
12904
12351
|
} catch {
|
|
12905
12352
|
}
|
|
12906
|
-
const streamId = `stream_${id}_${
|
|
12353
|
+
const streamId = `stream_${id}_${nanoid11(10)}`;
|
|
12907
12354
|
console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
|
|
12908
12355
|
await activeStreamQueries.create(id, streamId);
|
|
12909
12356
|
const stream = await streamContext.resumableStream(
|
|
@@ -13108,7 +12555,7 @@ agents.post(
|
|
|
13108
12555
|
});
|
|
13109
12556
|
const session = agent.getSession();
|
|
13110
12557
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
13111
|
-
const streamId = `stream_${session.id}_${
|
|
12558
|
+
const streamId = `stream_${session.id}_${nanoid11(10)}`;
|
|
13112
12559
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
13113
12560
|
await activeStreamQueries.create(session.id, streamId);
|
|
13114
12561
|
const createQuickStreamProducer = () => {
|
|
@@ -13375,23 +12822,23 @@ agents.post(
|
|
|
13375
12822
|
});
|
|
13376
12823
|
}
|
|
13377
12824
|
);
|
|
13378
|
-
var browserInputSchema =
|
|
13379
|
-
type:
|
|
13380
|
-
eventType:
|
|
13381
|
-
x:
|
|
13382
|
-
y:
|
|
13383
|
-
button:
|
|
13384
|
-
clickCount:
|
|
13385
|
-
deltaX:
|
|
13386
|
-
deltaY:
|
|
13387
|
-
key:
|
|
13388
|
-
code:
|
|
13389
|
-
text:
|
|
13390
|
-
modifiers:
|
|
13391
|
-
touchPoints:
|
|
13392
|
-
x:
|
|
13393
|
-
y:
|
|
13394
|
-
id:
|
|
12825
|
+
var browserInputSchema = z17.object({
|
|
12826
|
+
type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
12827
|
+
eventType: z17.string(),
|
|
12828
|
+
x: z17.number().optional(),
|
|
12829
|
+
y: z17.number().optional(),
|
|
12830
|
+
button: z17.string().optional(),
|
|
12831
|
+
clickCount: z17.number().optional(),
|
|
12832
|
+
deltaX: z17.number().optional(),
|
|
12833
|
+
deltaY: z17.number().optional(),
|
|
12834
|
+
key: z17.string().optional(),
|
|
12835
|
+
code: z17.string().optional(),
|
|
12836
|
+
text: z17.string().optional(),
|
|
12837
|
+
modifiers: z17.number().optional(),
|
|
12838
|
+
touchPoints: z17.array(z17.object({
|
|
12839
|
+
x: z17.number(),
|
|
12840
|
+
y: z17.number(),
|
|
12841
|
+
id: z17.number().optional()
|
|
13395
12842
|
})).optional()
|
|
13396
12843
|
});
|
|
13397
12844
|
agents.post(
|
|
@@ -13426,27 +12873,27 @@ agents.get("/:id/browser-stream", async (c) => {
|
|
|
13426
12873
|
init_config();
|
|
13427
12874
|
import { Hono as Hono3 } from "hono";
|
|
13428
12875
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
13429
|
-
import { z as
|
|
13430
|
-
import { readFileSync as
|
|
12876
|
+
import { z as z18 } from "zod";
|
|
12877
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
13431
12878
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13432
|
-
import { dirname as dirname7, join as
|
|
12879
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
13433
12880
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13434
12881
|
var __dirname = dirname7(__filename);
|
|
13435
12882
|
var possiblePaths = [
|
|
13436
|
-
|
|
12883
|
+
join14(__dirname, "../package.json"),
|
|
13437
12884
|
// From dist/server -> dist/../package.json
|
|
13438
|
-
|
|
12885
|
+
join14(__dirname, "../../package.json"),
|
|
13439
12886
|
// From dist/server (if nested differently)
|
|
13440
|
-
|
|
12887
|
+
join14(__dirname, "../../../package.json"),
|
|
13441
12888
|
// From src/server/routes (development)
|
|
13442
|
-
|
|
12889
|
+
join14(process.cwd(), "package.json")
|
|
13443
12890
|
// From current working directory
|
|
13444
12891
|
];
|
|
13445
12892
|
var currentVersion = "0.0.0";
|
|
13446
12893
|
var packageName = "sparkecoder";
|
|
13447
12894
|
for (const packageJsonPath of possiblePaths) {
|
|
13448
12895
|
try {
|
|
13449
|
-
const packageJson = JSON.parse(
|
|
12896
|
+
const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
13450
12897
|
if (packageJson.name === "sparkecoder") {
|
|
13451
12898
|
currentVersion = packageJson.version || "0.0.0";
|
|
13452
12899
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -13538,9 +12985,9 @@ health.get("/api-keys", async (c) => {
|
|
|
13538
12985
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
13539
12986
|
});
|
|
13540
12987
|
});
|
|
13541
|
-
var setApiKeySchema =
|
|
13542
|
-
provider:
|
|
13543
|
-
apiKey:
|
|
12988
|
+
var setApiKeySchema = z18.object({
|
|
12989
|
+
provider: z18.string(),
|
|
12990
|
+
apiKey: z18.string().min(1)
|
|
13544
12991
|
});
|
|
13545
12992
|
health.post(
|
|
13546
12993
|
"/api-keys",
|
|
@@ -13581,12 +13028,12 @@ init_tmux();
|
|
|
13581
13028
|
init_db();
|
|
13582
13029
|
import { Hono as Hono4 } from "hono";
|
|
13583
13030
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
13584
|
-
import { z as
|
|
13031
|
+
import { z as z19 } from "zod";
|
|
13585
13032
|
var terminals = new Hono4();
|
|
13586
|
-
var spawnSchema =
|
|
13587
|
-
command:
|
|
13588
|
-
cwd:
|
|
13589
|
-
name:
|
|
13033
|
+
var spawnSchema = z19.object({
|
|
13034
|
+
command: z19.string(),
|
|
13035
|
+
cwd: z19.string().optional(),
|
|
13036
|
+
name: z19.string().optional()
|
|
13590
13037
|
});
|
|
13591
13038
|
terminals.post(
|
|
13592
13039
|
"/:sessionId/terminals",
|
|
@@ -13667,8 +13114,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
13667
13114
|
// We don't track exit codes in tmux mode
|
|
13668
13115
|
});
|
|
13669
13116
|
});
|
|
13670
|
-
var logsQuerySchema =
|
|
13671
|
-
tail:
|
|
13117
|
+
var logsQuerySchema = z19.object({
|
|
13118
|
+
tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
13672
13119
|
});
|
|
13673
13120
|
terminals.get(
|
|
13674
13121
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -13692,8 +13139,8 @@ terminals.get(
|
|
|
13692
13139
|
});
|
|
13693
13140
|
}
|
|
13694
13141
|
);
|
|
13695
|
-
var killSchema =
|
|
13696
|
-
signal:
|
|
13142
|
+
var killSchema = z19.object({
|
|
13143
|
+
signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
13697
13144
|
});
|
|
13698
13145
|
terminals.post(
|
|
13699
13146
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -13707,8 +13154,8 @@ terminals.post(
|
|
|
13707
13154
|
return c.json({ success: true, message: "Terminal killed" });
|
|
13708
13155
|
}
|
|
13709
13156
|
);
|
|
13710
|
-
var writeSchema =
|
|
13711
|
-
input:
|
|
13157
|
+
var writeSchema = z19.object({
|
|
13158
|
+
input: z19.string()
|
|
13712
13159
|
});
|
|
13713
13160
|
terminals.post(
|
|
13714
13161
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -13895,23 +13342,23 @@ init_agent();
|
|
|
13895
13342
|
init_config();
|
|
13896
13343
|
import { Hono as Hono5 } from "hono";
|
|
13897
13344
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
13898
|
-
import { z as
|
|
13899
|
-
import { nanoid as
|
|
13345
|
+
import { z as z20 } from "zod";
|
|
13346
|
+
import { nanoid as nanoid12 } from "nanoid";
|
|
13900
13347
|
init_questions();
|
|
13901
13348
|
var tasks = new Hono5();
|
|
13902
13349
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
13903
|
-
var createTaskSchema =
|
|
13904
|
-
prompt:
|
|
13905
|
-
outputSchema:
|
|
13906
|
-
webhookUrl:
|
|
13907
|
-
model:
|
|
13908
|
-
workingDirectory:
|
|
13909
|
-
name:
|
|
13910
|
-
maxIterations:
|
|
13911
|
-
parentTaskId:
|
|
13350
|
+
var createTaskSchema = z20.object({
|
|
13351
|
+
prompt: z20.string().min(1),
|
|
13352
|
+
outputSchema: z20.record(z20.string(), z20.unknown()),
|
|
13353
|
+
webhookUrl: z20.string().url().optional(),
|
|
13354
|
+
model: z20.string().optional(),
|
|
13355
|
+
workingDirectory: z20.string().optional(),
|
|
13356
|
+
name: z20.string().optional(),
|
|
13357
|
+
maxIterations: z20.number().int().min(1).max(500).optional(),
|
|
13358
|
+
parentTaskId: z20.string().optional(),
|
|
13912
13359
|
/** When set, the spawning orchestrator's session id. Stamped on the
|
|
13913
13360
|
* worker's config so terminal events can wake the orchestrator. */
|
|
13914
|
-
orchestratorSessionId:
|
|
13361
|
+
orchestratorSessionId: z20.string().optional()
|
|
13915
13362
|
});
|
|
13916
13363
|
tasks.post(
|
|
13917
13364
|
"/",
|
|
@@ -13977,7 +13424,7 @@ tasks.post(
|
|
|
13977
13424
|
const taskId = agent.sessionId;
|
|
13978
13425
|
const abortController = new AbortController();
|
|
13979
13426
|
taskAbortControllers.set(taskId, abortController);
|
|
13980
|
-
const streamId = `stream_${taskId}_${
|
|
13427
|
+
const streamId = `stream_${taskId}_${nanoid12(10)}`;
|
|
13981
13428
|
await activeStreamQueries.create(taskId, streamId);
|
|
13982
13429
|
const taskStreamProducer = () => {
|
|
13983
13430
|
const { readable, writable } = new TransformStream();
|
|
@@ -14126,9 +13573,9 @@ tasks.post("/:id/cancel", async (c) => {
|
|
|
14126
13573
|
}
|
|
14127
13574
|
return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
|
|
14128
13575
|
});
|
|
14129
|
-
var answerQuestionSchema =
|
|
14130
|
-
answer:
|
|
14131
|
-
answeredBy:
|
|
13576
|
+
var answerQuestionSchema = z20.object({
|
|
13577
|
+
answer: z20.string().min(1),
|
|
13578
|
+
answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
|
|
14132
13579
|
});
|
|
14133
13580
|
tasks.post(
|
|
14134
13581
|
"/:id/questions/:questionId/answer",
|
|
@@ -14257,7 +13704,8 @@ slack.post("/events", async (c) => {
|
|
|
14257
13704
|
updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
|
|
14258
13705
|
return c.json({ ok: true });
|
|
14259
13706
|
}
|
|
14260
|
-
const
|
|
13707
|
+
const self = await ensureSlackSelfIdentity();
|
|
13708
|
+
const { event: inbound, dropReason } = slackEventToInboundResult(ev, { self });
|
|
14261
13709
|
if (inbound) {
|
|
14262
13710
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
14263
13711
|
if (isThreadReply) {
|
|
@@ -14412,7 +13860,7 @@ init_pool();
|
|
|
14412
13860
|
init_webhook_events();
|
|
14413
13861
|
import { Hono as Hono8 } from "hono";
|
|
14414
13862
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
14415
|
-
import { z as
|
|
13863
|
+
import { z as z21 } from "zod";
|
|
14416
13864
|
var integrations = new Hono8();
|
|
14417
13865
|
var orchestratorRouter = new Hono8();
|
|
14418
13866
|
async function getOrchestratorId() {
|
|
@@ -14435,9 +13883,9 @@ orchestratorRouter.get("/", async (c) => {
|
|
|
14435
13883
|
});
|
|
14436
13884
|
orchestratorRouter.patch(
|
|
14437
13885
|
"/",
|
|
14438
|
-
zValidator6("json",
|
|
14439
|
-
name:
|
|
14440
|
-
personality:
|
|
13886
|
+
zValidator6("json", z21.object({
|
|
13887
|
+
name: z21.string().min(1).optional(),
|
|
13888
|
+
personality: z21.string().optional()
|
|
14441
13889
|
})),
|
|
14442
13890
|
async (c) => {
|
|
14443
13891
|
const id = await getOrchestratorId();
|
|
@@ -14513,15 +13961,15 @@ integrations.get("/", async (c) => {
|
|
|
14513
13961
|
}
|
|
14514
13962
|
});
|
|
14515
13963
|
});
|
|
14516
|
-
var slackConfigSchema =
|
|
14517
|
-
botToken:
|
|
14518
|
-
signingSecret:
|
|
14519
|
-
defaultOrchestratorName:
|
|
14520
|
-
allowedUsers:
|
|
14521
|
-
allowedChannels:
|
|
14522
|
-
allowDmsFromAnyone:
|
|
14523
|
-
deniedReplyEnabled:
|
|
14524
|
-
deniedReplyTemplate:
|
|
13964
|
+
var slackConfigSchema = z21.object({
|
|
13965
|
+
botToken: z21.string().optional(),
|
|
13966
|
+
signingSecret: z21.string().optional(),
|
|
13967
|
+
defaultOrchestratorName: z21.string().optional(),
|
|
13968
|
+
allowedUsers: z21.array(z21.string()).optional(),
|
|
13969
|
+
allowedChannels: z21.array(z21.string()).optional(),
|
|
13970
|
+
allowDmsFromAnyone: z21.boolean().optional(),
|
|
13971
|
+
deniedReplyEnabled: z21.boolean().optional(),
|
|
13972
|
+
deniedReplyTemplate: z21.string().optional()
|
|
14525
13973
|
});
|
|
14526
13974
|
integrations.post("/slack", zValidator6("json", slackConfigSchema), async (c) => {
|
|
14527
13975
|
const body = c.req.valid("json");
|
|
@@ -14550,11 +13998,11 @@ schedulesRouter.get("/", async (c) => {
|
|
|
14550
13998
|
});
|
|
14551
13999
|
schedulesRouter.post(
|
|
14552
14000
|
"/",
|
|
14553
|
-
zValidator6("json",
|
|
14554
|
-
name:
|
|
14555
|
-
cron:
|
|
14556
|
-
prompt:
|
|
14557
|
-
replyChannel:
|
|
14001
|
+
zValidator6("json", z21.object({
|
|
14002
|
+
name: z21.string().min(1),
|
|
14003
|
+
cron: z21.string().min(1),
|
|
14004
|
+
prompt: z21.string().min(1),
|
|
14005
|
+
replyChannel: z21.string().optional()
|
|
14558
14006
|
})),
|
|
14559
14007
|
async (c) => {
|
|
14560
14008
|
const orcId = await getOrchestratorId();
|
|
@@ -14565,12 +14013,12 @@ schedulesRouter.post(
|
|
|
14565
14013
|
);
|
|
14566
14014
|
schedulesRouter.patch(
|
|
14567
14015
|
"/:id",
|
|
14568
|
-
zValidator6("json",
|
|
14569
|
-
name:
|
|
14570
|
-
cron:
|
|
14571
|
-
prompt:
|
|
14572
|
-
enabled:
|
|
14573
|
-
replyChannel:
|
|
14016
|
+
zValidator6("json", z21.object({
|
|
14017
|
+
name: z21.string().optional(),
|
|
14018
|
+
cron: z21.string().optional(),
|
|
14019
|
+
prompt: z21.string().optional(),
|
|
14020
|
+
enabled: z21.boolean().optional(),
|
|
14021
|
+
replyChannel: z21.string().optional()
|
|
14574
14022
|
})),
|
|
14575
14023
|
async (c) => {
|
|
14576
14024
|
const orcId = await getOrchestratorId();
|
|
@@ -14598,10 +14046,10 @@ webhooksRouter.get("/", async (c) => {
|
|
|
14598
14046
|
});
|
|
14599
14047
|
webhooksRouter.post(
|
|
14600
14048
|
"/",
|
|
14601
|
-
zValidator6("json",
|
|
14602
|
-
name:
|
|
14603
|
-
wake:
|
|
14604
|
-
template:
|
|
14049
|
+
zValidator6("json", z21.object({
|
|
14050
|
+
name: z21.string().min(1),
|
|
14051
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
14052
|
+
template: z21.string().optional()
|
|
14605
14053
|
})),
|
|
14606
14054
|
async (c) => {
|
|
14607
14055
|
const orcId = await getOrchestratorId();
|
|
@@ -14612,11 +14060,11 @@ webhooksRouter.post(
|
|
|
14612
14060
|
);
|
|
14613
14061
|
webhooksRouter.patch(
|
|
14614
14062
|
"/:id",
|
|
14615
|
-
zValidator6("json",
|
|
14616
|
-
name:
|
|
14617
|
-
wake:
|
|
14618
|
-
template:
|
|
14619
|
-
rotateToken:
|
|
14063
|
+
zValidator6("json", z21.object({
|
|
14064
|
+
name: z21.string().optional(),
|
|
14065
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
14066
|
+
template: z21.string().optional(),
|
|
14067
|
+
rotateToken: z21.boolean().optional()
|
|
14620
14068
|
})),
|
|
14621
14069
|
async (c) => {
|
|
14622
14070
|
const orcId = await getOrchestratorId();
|
|
@@ -14633,22 +14081,22 @@ webhooksRouter.delete("/:id", async (c) => {
|
|
|
14633
14081
|
return c.json({ deleted: ok });
|
|
14634
14082
|
});
|
|
14635
14083
|
var mcpRouter = new Hono8();
|
|
14636
|
-
var mcpServerSchema =
|
|
14637
|
-
name:
|
|
14638
|
-
transport:
|
|
14639
|
-
url:
|
|
14640
|
-
headers:
|
|
14641
|
-
command:
|
|
14642
|
-
args:
|
|
14643
|
-
enabled:
|
|
14644
|
-
});
|
|
14645
|
-
var mcpPatchSchema =
|
|
14646
|
-
name:
|
|
14647
|
-
url:
|
|
14648
|
-
headers:
|
|
14649
|
-
command:
|
|
14650
|
-
args:
|
|
14651
|
-
enabled:
|
|
14084
|
+
var mcpServerSchema = z21.object({
|
|
14085
|
+
name: z21.string().min(1),
|
|
14086
|
+
transport: z21.enum(["http", "sse", "stdio"]),
|
|
14087
|
+
url: z21.string().optional(),
|
|
14088
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
14089
|
+
command: z21.string().optional(),
|
|
14090
|
+
args: z21.array(z21.string()).optional(),
|
|
14091
|
+
enabled: z21.boolean().optional()
|
|
14092
|
+
});
|
|
14093
|
+
var mcpPatchSchema = z21.object({
|
|
14094
|
+
name: z21.string().optional(),
|
|
14095
|
+
url: z21.string().optional(),
|
|
14096
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
14097
|
+
command: z21.string().optional(),
|
|
14098
|
+
args: z21.array(z21.string()).optional(),
|
|
14099
|
+
enabled: z21.boolean().optional()
|
|
14652
14100
|
});
|
|
14653
14101
|
mcpRouter.get("/", async (c) => {
|
|
14654
14102
|
const rows = listMcpServers().map((s) => ({
|
|
@@ -14765,10 +14213,10 @@ init_config();
|
|
|
14765
14213
|
init_db();
|
|
14766
14214
|
|
|
14767
14215
|
// src/utils/dependencies.ts
|
|
14768
|
-
import { exec as
|
|
14769
|
-
import { promisify as
|
|
14216
|
+
import { exec as exec6 } from "child_process";
|
|
14217
|
+
import { promisify as promisify6 } from "util";
|
|
14770
14218
|
import { platform as platform2 } from "os";
|
|
14771
|
-
var
|
|
14219
|
+
var execAsync6 = promisify6(exec6);
|
|
14772
14220
|
function getInstallInstructions() {
|
|
14773
14221
|
const os2 = platform2();
|
|
14774
14222
|
if (os2 === "darwin") {
|
|
@@ -14801,7 +14249,7 @@ Install tmux:
|
|
|
14801
14249
|
}
|
|
14802
14250
|
async function checkTmux() {
|
|
14803
14251
|
try {
|
|
14804
|
-
const { stdout } = await
|
|
14252
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
14805
14253
|
const version = stdout.trim();
|
|
14806
14254
|
return {
|
|
14807
14255
|
available: true,
|
|
@@ -14850,11 +14298,11 @@ function getWebDirectory() {
|
|
|
14850
14298
|
try {
|
|
14851
14299
|
const currentDir = dirname8(fileURLToPath4(import.meta.url));
|
|
14852
14300
|
const webDir = resolve11(currentDir, "..", "web");
|
|
14853
|
-
if (
|
|
14301
|
+
if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
|
|
14854
14302
|
return webDir;
|
|
14855
14303
|
}
|
|
14856
14304
|
const altWebDir = resolve11(currentDir, "..", "..", "web");
|
|
14857
|
-
if (
|
|
14305
|
+
if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
|
|
14858
14306
|
return altWebDir;
|
|
14859
14307
|
}
|
|
14860
14308
|
return null;
|
|
@@ -14912,23 +14360,23 @@ async function findWebPort(preferredPort) {
|
|
|
14912
14360
|
return { port: preferredPort, alreadyRunning: false };
|
|
14913
14361
|
}
|
|
14914
14362
|
function hasProductionBuild(webDir) {
|
|
14915
|
-
const buildIdPath =
|
|
14916
|
-
return
|
|
14363
|
+
const buildIdPath = join15(webDir, ".next", "BUILD_ID");
|
|
14364
|
+
return existsSync20(buildIdPath);
|
|
14917
14365
|
}
|
|
14918
14366
|
function hasSourceFiles(webDir) {
|
|
14919
|
-
const appDir =
|
|
14920
|
-
const pagesDir =
|
|
14921
|
-
const rootAppDir =
|
|
14922
|
-
const rootPagesDir =
|
|
14923
|
-
return
|
|
14367
|
+
const appDir = join15(webDir, "src", "app");
|
|
14368
|
+
const pagesDir = join15(webDir, "src", "pages");
|
|
14369
|
+
const rootAppDir = join15(webDir, "app");
|
|
14370
|
+
const rootPagesDir = join15(webDir, "pages");
|
|
14371
|
+
return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
|
|
14924
14372
|
}
|
|
14925
14373
|
function getStandaloneServerPath(webDir) {
|
|
14926
14374
|
const possiblePaths2 = [
|
|
14927
|
-
|
|
14928
|
-
|
|
14375
|
+
join15(webDir, ".next", "standalone", "server.js"),
|
|
14376
|
+
join15(webDir, ".next", "standalone", "web", "server.js")
|
|
14929
14377
|
];
|
|
14930
14378
|
for (const serverPath of possiblePaths2) {
|
|
14931
|
-
if (
|
|
14379
|
+
if (existsSync20(serverPath)) {
|
|
14932
14380
|
return serverPath;
|
|
14933
14381
|
}
|
|
14934
14382
|
}
|
|
@@ -14968,13 +14416,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14968
14416
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
14969
14417
|
return { process: null, port: actualPort };
|
|
14970
14418
|
}
|
|
14971
|
-
const usePnpm =
|
|
14972
|
-
const useNpm = !usePnpm &&
|
|
14419
|
+
const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
|
|
14420
|
+
const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
|
|
14973
14421
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
14974
14422
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
14975
14423
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
14976
14424
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
14977
|
-
const runtimeConfigPath =
|
|
14425
|
+
const runtimeConfigPath = join15(webDir, "runtime-config.json");
|
|
14978
14426
|
try {
|
|
14979
14427
|
writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
14980
14428
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -15189,8 +14637,8 @@ async function startServer(options = {}) {
|
|
|
15189
14637
|
if (options.workingDirectory) {
|
|
15190
14638
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
15191
14639
|
}
|
|
15192
|
-
if (!
|
|
15193
|
-
|
|
14640
|
+
if (!existsSync20(config.resolvedWorkingDirectory)) {
|
|
14641
|
+
mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
|
|
15194
14642
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
15195
14643
|
}
|
|
15196
14644
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -15231,13 +14679,15 @@ async function startServer(options = {}) {
|
|
|
15231
14679
|
if (!options.quiet) console.warn(`[scheduler] start skipped: ${err.message}`);
|
|
15232
14680
|
}
|
|
15233
14681
|
const port = options.port || config.server.port;
|
|
15234
|
-
const
|
|
14682
|
+
const envHost = process.env.SPARKECODER_API_HOST || process.env.SPARKECODER_HOST;
|
|
14683
|
+
const host = envHost || options.host || config.server.host || "0.0.0.0";
|
|
14684
|
+
const hostSource = envHost ? process.env.SPARKECODER_API_HOST ? "env SPARKECODER_API_HOST" : "env SPARKECODER_HOST" : options.host ? "--host flag" : config.server.host ? "config.server.host" : "default";
|
|
15235
14685
|
const publicUrl = options.publicUrl || config.server.publicUrl;
|
|
15236
14686
|
const app = await createApp({ quiet: options.quiet });
|
|
15237
14687
|
if (!options.quiet) {
|
|
15238
14688
|
console.log(`
|
|
15239
14689
|
\u{1F680} SparkECoder API Server`);
|
|
15240
|
-
console.log(` \u2192
|
|
14690
|
+
console.log(` \u2192 Binding to ${host}:${port} (source: ${hostSource})`);
|
|
15241
14691
|
if (publicUrl) {
|
|
15242
14692
|
console.log(` \u2192 Public URL: ${publicUrl}`);
|
|
15243
14693
|
}
|
|
@@ -15246,10 +14696,22 @@ async function startServer(options = {}) {
|
|
|
15246
14696
|
console.log(` \u2192 OpenAPI spec: http://${host}:${port}/openapi.json
|
|
15247
14697
|
`);
|
|
15248
14698
|
}
|
|
14699
|
+
if (host === "127.0.0.1" || host === "localhost") {
|
|
14700
|
+
console.log(`[sparkecoder] \u26A0 API bound to ${host} only \u2014 not reachable from outside the machine.`);
|
|
14701
|
+
console.log(`[sparkecoder] For tunnels/reverse proxies (Modal/Fly/Docker), set --host 0.0.0.0 or SPARKECODER_API_HOST=0.0.0.0.`);
|
|
14702
|
+
}
|
|
15249
14703
|
serverInstance = serve({
|
|
15250
14704
|
fetch: app.fetch,
|
|
15251
14705
|
port,
|
|
15252
14706
|
hostname: host
|
|
14707
|
+
}, (info) => {
|
|
14708
|
+
const actual = `${info.address}:${info.port}`;
|
|
14709
|
+
const requested = `${host}:${port}`;
|
|
14710
|
+
if (info.address === "127.0.0.1" && host !== "127.0.0.1" && host !== "localhost") {
|
|
14711
|
+
console.warn(`[sparkecoder] \u2717 API listener bound to ${actual} but ${requested} was requested. External requests will be refused.`);
|
|
14712
|
+
} else {
|
|
14713
|
+
console.log(`[sparkecoder] \u2713 API listening on ${actual}`);
|
|
14714
|
+
}
|
|
15253
14715
|
});
|
|
15254
14716
|
let webPort;
|
|
15255
14717
|
let webStarted;
|