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/index.js
CHANGED
|
@@ -211,12 +211,6 @@ var init_types = __esm({
|
|
|
211
211
|
skillsDirectory: z.string().optional(),
|
|
212
212
|
maxContextChars: z.number().optional().default(2e5),
|
|
213
213
|
task: TaskConfigSchema.optional(),
|
|
214
|
-
// Anthropic computer use tool — opt-in. When true, the `computer` tool is
|
|
215
|
-
// included in the toolset for Anthropic models. Default false.
|
|
216
|
-
computerUseEnabled: z.boolean().optional(),
|
|
217
|
-
// Display dimensions for the computer use tool (defaults: 1280x800).
|
|
218
|
-
computerUseDisplayWidth: z.number().int().positive().optional(),
|
|
219
|
-
computerUseDisplayHeight: z.number().int().positive().optional(),
|
|
220
214
|
// 'orchestrator' = supervisor session; 'worker' = task spawned by an orchestrator.
|
|
221
215
|
role: z.enum(["orchestrator", "worker", "chat"]).optional(),
|
|
222
216
|
// Optional persona / extra system-prompt text appended to the orchestrator's
|
|
@@ -652,7 +646,7 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
652
646
|
...config,
|
|
653
647
|
server: {
|
|
654
648
|
port: config.server.port,
|
|
655
|
-
host: config.server.host ?? "
|
|
649
|
+
host: config.server.host ?? "0.0.0.0",
|
|
656
650
|
publicUrl: config.server.publicUrl
|
|
657
651
|
},
|
|
658
652
|
resolvedWorkingDirectory,
|
|
@@ -819,7 +813,7 @@ function createDefaultConfig() {
|
|
|
819
813
|
},
|
|
820
814
|
server: {
|
|
821
815
|
port: 3141,
|
|
822
|
-
host: "
|
|
816
|
+
host: "0.0.0.0"
|
|
823
817
|
},
|
|
824
818
|
databasePath: "./sparkecoder.db"
|
|
825
819
|
};
|
|
@@ -2731,12 +2725,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2731
2725
|
}
|
|
2732
2726
|
async function commandExists(cmd) {
|
|
2733
2727
|
try {
|
|
2734
|
-
const { exec:
|
|
2735
|
-
const { promisify:
|
|
2736
|
-
const
|
|
2728
|
+
const { exec: exec7 } = await import("child_process");
|
|
2729
|
+
const { promisify: promisify7 } = await import("util");
|
|
2730
|
+
const execAsync7 = promisify7(exec7);
|
|
2737
2731
|
const isWindows = process.platform === "win32";
|
|
2738
2732
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2739
|
-
await
|
|
2733
|
+
await execAsync7(checkCmd);
|
|
2740
2734
|
return true;
|
|
2741
2735
|
} catch {
|
|
2742
2736
|
return false;
|
|
@@ -6381,581 +6375,6 @@ var init_upload_file = __esm({
|
|
|
6381
6375
|
}
|
|
6382
6376
|
});
|
|
6383
6377
|
|
|
6384
|
-
// src/tools/computer-use.ts
|
|
6385
|
-
import { anthropic } from "@ai-sdk/anthropic";
|
|
6386
|
-
import { exec as exec5 } from "child_process";
|
|
6387
|
-
import { promisify as promisify5 } from "util";
|
|
6388
|
-
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
6389
|
-
import { join as join8 } from "path";
|
|
6390
|
-
import { tmpdir } from "os";
|
|
6391
|
-
import { nanoid as nanoid4 } from "nanoid";
|
|
6392
|
-
function isMacOs() {
|
|
6393
|
-
return process.platform === "darwin";
|
|
6394
|
-
}
|
|
6395
|
-
async function isCliclickInstalled() {
|
|
6396
|
-
try {
|
|
6397
|
-
await execAsync5("command -v cliclick", { timeout: 2e3 });
|
|
6398
|
-
return true;
|
|
6399
|
-
} catch {
|
|
6400
|
-
return false;
|
|
6401
|
-
}
|
|
6402
|
-
}
|
|
6403
|
-
async function runJxa(script) {
|
|
6404
|
-
try {
|
|
6405
|
-
const escaped = script.replace(/'/g, `'\\''`);
|
|
6406
|
-
const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
|
|
6407
|
-
timeout: 5e3
|
|
6408
|
-
});
|
|
6409
|
-
return JSON.parse(stdout.trim());
|
|
6410
|
-
} catch {
|
|
6411
|
-
return null;
|
|
6412
|
-
}
|
|
6413
|
-
}
|
|
6414
|
-
async function hasAccessibilityPermissions() {
|
|
6415
|
-
try {
|
|
6416
|
-
const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
|
|
6417
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
6418
|
-
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
6419
|
-
}
|
|
6420
|
-
return { ok: true };
|
|
6421
|
-
} catch (err) {
|
|
6422
|
-
return { ok: false, error: err?.message || String(err) };
|
|
6423
|
-
}
|
|
6424
|
-
}
|
|
6425
|
-
async function hasScreenRecordingPermissions() {
|
|
6426
|
-
const result = await runJxa(
|
|
6427
|
-
`ObjC.import("Cocoa");
|
|
6428
|
-
ObjC.import("CoreGraphics");
|
|
6429
|
-
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
6430
|
-
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
6431
|
-
);
|
|
6432
|
-
return result?.hasAccess ?? false;
|
|
6433
|
-
}
|
|
6434
|
-
async function requestAccessibilityPrompt() {
|
|
6435
|
-
const result = await runJxa(
|
|
6436
|
-
`ObjC.import("ApplicationServices");
|
|
6437
|
-
var key = $.kAXTrustedCheckOptionPrompt;
|
|
6438
|
-
var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
|
|
6439
|
-
var trusted = $.AXIsProcessTrustedWithOptions(dict);
|
|
6440
|
-
JSON.stringify({ trusted: !!trusted });`
|
|
6441
|
-
);
|
|
6442
|
-
return result?.trusted ?? false;
|
|
6443
|
-
}
|
|
6444
|
-
async function requestScreenRecordingPrompt() {
|
|
6445
|
-
const result = await runJxa(
|
|
6446
|
-
`ObjC.import("Cocoa");
|
|
6447
|
-
ObjC.import("CoreGraphics");
|
|
6448
|
-
ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
|
|
6449
|
-
JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
|
|
6450
|
-
);
|
|
6451
|
-
return result?.granted ?? false;
|
|
6452
|
-
}
|
|
6453
|
-
async function openSystemSettings(pane) {
|
|
6454
|
-
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
6455
|
-
try {
|
|
6456
|
-
await execAsync5(`open '${url}'`, { timeout: 3e3 });
|
|
6457
|
-
} catch {
|
|
6458
|
-
}
|
|
6459
|
-
}
|
|
6460
|
-
async function detectScreenSize() {
|
|
6461
|
-
try {
|
|
6462
|
-
const { stdout } = await execAsync5(
|
|
6463
|
-
`osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
|
|
6464
|
-
{ timeout: 3e3 }
|
|
6465
|
-
);
|
|
6466
|
-
const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
|
|
6467
|
-
if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
|
|
6468
|
-
const [x1, y1, x2, y2] = parts;
|
|
6469
|
-
return { width: x2 - x1, height: y2 - y1 };
|
|
6470
|
-
}
|
|
6471
|
-
} catch {
|
|
6472
|
-
}
|
|
6473
|
-
return null;
|
|
6474
|
-
}
|
|
6475
|
-
async function runCliclick(args) {
|
|
6476
|
-
const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
6477
|
-
const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
|
|
6478
|
-
timeout: 15e3,
|
|
6479
|
-
maxBuffer: 1024 * 1024
|
|
6480
|
-
});
|
|
6481
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
6482
|
-
throw new Error(
|
|
6483
|
-
"Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
|
|
6484
|
-
);
|
|
6485
|
-
}
|
|
6486
|
-
if (stderr && !stdout) throw new Error(stderr.trim());
|
|
6487
|
-
return (stdout || "").trim();
|
|
6488
|
-
}
|
|
6489
|
-
async function runScreencapture(path) {
|
|
6490
|
-
await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
|
|
6491
|
-
timeout: 5e3
|
|
6492
|
-
});
|
|
6493
|
-
}
|
|
6494
|
-
async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
|
|
6495
|
-
const sharpModule = await import("sharp");
|
|
6496
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
6497
|
-
const meta = await sharp2(path).metadata();
|
|
6498
|
-
if (meta.width === targetWidth && meta.height === targetHeight) {
|
|
6499
|
-
return readFileSync7(path);
|
|
6500
|
-
}
|
|
6501
|
-
return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
|
|
6502
|
-
}
|
|
6503
|
-
async function runScroll(dx, dy) {
|
|
6504
|
-
const wheelY = -Math.round(dy);
|
|
6505
|
-
const wheelX = -Math.round(dx);
|
|
6506
|
-
const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
|
|
6507
|
-
await execAsync5(
|
|
6508
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
6509
|
-
{ timeout: 5e3 }
|
|
6510
|
-
);
|
|
6511
|
-
}
|
|
6512
|
-
function translateKeyForCliclick(key2) {
|
|
6513
|
-
if (!key2) return [];
|
|
6514
|
-
const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
|
|
6515
|
-
if (parts.length === 0) return [];
|
|
6516
|
-
const modMap = {
|
|
6517
|
-
ctrl: "ctrl",
|
|
6518
|
-
control: "ctrl",
|
|
6519
|
-
alt: "alt",
|
|
6520
|
-
option: "alt",
|
|
6521
|
-
shift: "shift",
|
|
6522
|
-
cmd: "cmd",
|
|
6523
|
-
super: "cmd",
|
|
6524
|
-
meta: "cmd",
|
|
6525
|
-
win: "cmd",
|
|
6526
|
-
fn: "fn"
|
|
6527
|
-
};
|
|
6528
|
-
const keyMap = {
|
|
6529
|
-
return: "enter",
|
|
6530
|
-
enter: "enter",
|
|
6531
|
-
esc: "esc",
|
|
6532
|
-
escape: "esc",
|
|
6533
|
-
backspace: "delete",
|
|
6534
|
-
back_space: "delete",
|
|
6535
|
-
delete: "fwd-delete",
|
|
6536
|
-
fwd_delete: "fwd-delete",
|
|
6537
|
-
forward_delete: "fwd-delete",
|
|
6538
|
-
tab: "tab",
|
|
6539
|
-
space: "space",
|
|
6540
|
-
up: "arrow-up",
|
|
6541
|
-
arrow_up: "arrow-up",
|
|
6542
|
-
down: "arrow-down",
|
|
6543
|
-
arrow_down: "arrow-down",
|
|
6544
|
-
left: "arrow-left",
|
|
6545
|
-
arrow_left: "arrow-left",
|
|
6546
|
-
right: "arrow-right",
|
|
6547
|
-
arrow_right: "arrow-right",
|
|
6548
|
-
page_up: "page-up",
|
|
6549
|
-
pageup: "page-up",
|
|
6550
|
-
page_down: "page-down",
|
|
6551
|
-
pagedown: "page-down",
|
|
6552
|
-
home: "home",
|
|
6553
|
-
end: "end",
|
|
6554
|
-
f1: "f1",
|
|
6555
|
-
f2: "f2",
|
|
6556
|
-
f3: "f3",
|
|
6557
|
-
f4: "f4",
|
|
6558
|
-
f5: "f5",
|
|
6559
|
-
f6: "f6",
|
|
6560
|
-
f7: "f7",
|
|
6561
|
-
f8: "f8",
|
|
6562
|
-
f9: "f9",
|
|
6563
|
-
f10: "f10",
|
|
6564
|
-
f11: "f11",
|
|
6565
|
-
f12: "f12"
|
|
6566
|
-
};
|
|
6567
|
-
const modifiers = [];
|
|
6568
|
-
let mainKey = null;
|
|
6569
|
-
for (let i = 0; i < parts.length; i++) {
|
|
6570
|
-
const lower = parts[i].toLowerCase().replace(/-/g, "_");
|
|
6571
|
-
if (i < parts.length - 1 && modMap[lower]) {
|
|
6572
|
-
modifiers.push(modMap[lower]);
|
|
6573
|
-
} else {
|
|
6574
|
-
mainKey = keyMap[lower] || lower;
|
|
6575
|
-
}
|
|
6576
|
-
}
|
|
6577
|
-
const args = [];
|
|
6578
|
-
if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
|
|
6579
|
-
if (mainKey) {
|
|
6580
|
-
const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
|
|
6581
|
-
if (isNamedKey) {
|
|
6582
|
-
args.push(`kp:${mainKey}`);
|
|
6583
|
-
} else {
|
|
6584
|
-
args.push(`t:${mainKey}`);
|
|
6585
|
-
}
|
|
6586
|
-
}
|
|
6587
|
-
if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
|
|
6588
|
-
return args;
|
|
6589
|
-
}
|
|
6590
|
-
function modifierStringToCliclick(text) {
|
|
6591
|
-
return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
|
|
6592
|
-
if (p === "ctrl" || p === "control") return "ctrl";
|
|
6593
|
-
if (p === "alt" || p === "option") return "alt";
|
|
6594
|
-
if (p === "shift") return "shift";
|
|
6595
|
-
if (p === "super" || p === "meta" || p === "cmd") return "cmd";
|
|
6596
|
-
return "";
|
|
6597
|
-
}).filter(Boolean);
|
|
6598
|
-
}
|
|
6599
|
-
function createComputerUseTool(options) {
|
|
6600
|
-
const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
|
|
6601
|
-
const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
|
|
6602
|
-
return anthropic.tools.computer_20251124({
|
|
6603
|
-
displayWidthPx: displayWidth,
|
|
6604
|
-
displayHeightPx: displayHeight,
|
|
6605
|
-
enableZoom: true,
|
|
6606
|
-
execute: async (input) => {
|
|
6607
|
-
try {
|
|
6608
|
-
switch (input.action) {
|
|
6609
|
-
case "screenshot": {
|
|
6610
|
-
const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
|
|
6611
|
-
await runScreencapture(path);
|
|
6612
|
-
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
6613
|
-
try {
|
|
6614
|
-
unlinkSync2(path);
|
|
6615
|
-
} catch {
|
|
6616
|
-
}
|
|
6617
|
-
return { type: "image", data: resized.toString("base64") };
|
|
6618
|
-
}
|
|
6619
|
-
case "left_click": {
|
|
6620
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6621
|
-
if (input.text) {
|
|
6622
|
-
const mods = modifierStringToCliclick(input.text);
|
|
6623
|
-
if (mods.length > 0) {
|
|
6624
|
-
await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
|
|
6625
|
-
} else {
|
|
6626
|
-
await runCliclick([`c:${x},${y}`]);
|
|
6627
|
-
}
|
|
6628
|
-
} else {
|
|
6629
|
-
await runCliclick([`c:${x},${y}`]);
|
|
6630
|
-
}
|
|
6631
|
-
return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
|
|
6632
|
-
}
|
|
6633
|
-
case "right_click": {
|
|
6634
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6635
|
-
await runCliclick([`rc:${x},${y}`]);
|
|
6636
|
-
return `right-clicked at (${x}, ${y})`;
|
|
6637
|
-
}
|
|
6638
|
-
case "middle_click": {
|
|
6639
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6640
|
-
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);`;
|
|
6641
|
-
await execAsync5(
|
|
6642
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
6643
|
-
{ timeout: 3e3 }
|
|
6644
|
-
);
|
|
6645
|
-
return `middle-clicked at (${x}, ${y})`;
|
|
6646
|
-
}
|
|
6647
|
-
case "double_click": {
|
|
6648
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6649
|
-
await runCliclick([`dc:${x},${y}`]);
|
|
6650
|
-
return `double-clicked at (${x}, ${y})`;
|
|
6651
|
-
}
|
|
6652
|
-
case "triple_click": {
|
|
6653
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6654
|
-
await runCliclick([`tc:${x},${y}`]);
|
|
6655
|
-
return `triple-clicked at (${x}, ${y})`;
|
|
6656
|
-
}
|
|
6657
|
-
case "mouse_move": {
|
|
6658
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6659
|
-
await runCliclick([`m:${x},${y}`]);
|
|
6660
|
-
return `moved cursor to (${x}, ${y})`;
|
|
6661
|
-
}
|
|
6662
|
-
case "left_mouse_down": {
|
|
6663
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6664
|
-
await runCliclick([`dd:${x},${y}`]);
|
|
6665
|
-
return `left mouse button pressed at (${x}, ${y})`;
|
|
6666
|
-
}
|
|
6667
|
-
case "left_mouse_up": {
|
|
6668
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6669
|
-
await runCliclick([`du:${x},${y}`]);
|
|
6670
|
-
return `left mouse button released at (${x}, ${y})`;
|
|
6671
|
-
}
|
|
6672
|
-
case "left_click_drag": {
|
|
6673
|
-
const [sx, sy] = input.start_coordinate ?? [0, 0];
|
|
6674
|
-
const [ex, ey] = input.coordinate ?? [0, 0];
|
|
6675
|
-
await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
|
|
6676
|
-
return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
|
|
6677
|
-
}
|
|
6678
|
-
case "type": {
|
|
6679
|
-
const text = input.text ?? "";
|
|
6680
|
-
await runCliclick([`t:${text}`]);
|
|
6681
|
-
return `typed ${text.length} character(s)`;
|
|
6682
|
-
}
|
|
6683
|
-
case "key": {
|
|
6684
|
-
const args = translateKeyForCliclick(input.text ?? "");
|
|
6685
|
-
if (args.length === 0) return "no key specified";
|
|
6686
|
-
await runCliclick(args);
|
|
6687
|
-
return `pressed ${input.text}`;
|
|
6688
|
-
}
|
|
6689
|
-
case "hold_key": {
|
|
6690
|
-
const text = (input.text ?? "").toLowerCase();
|
|
6691
|
-
const duration = input.duration ?? 1;
|
|
6692
|
-
const modMap = {
|
|
6693
|
-
ctrl: "ctrl",
|
|
6694
|
-
control: "ctrl",
|
|
6695
|
-
alt: "alt",
|
|
6696
|
-
option: "alt",
|
|
6697
|
-
shift: "shift",
|
|
6698
|
-
cmd: "cmd",
|
|
6699
|
-
super: "cmd",
|
|
6700
|
-
meta: "cmd",
|
|
6701
|
-
fn: "fn"
|
|
6702
|
-
};
|
|
6703
|
-
const cliName = modMap[text] || text;
|
|
6704
|
-
await runCliclick([`kd:${cliName}`]);
|
|
6705
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
6706
|
-
await runCliclick([`ku:${cliName}`]);
|
|
6707
|
-
return `held ${text} for ${duration}s`;
|
|
6708
|
-
}
|
|
6709
|
-
case "scroll": {
|
|
6710
|
-
const direction = input.scroll_direction ?? "down";
|
|
6711
|
-
const amount = input.scroll_amount ?? 3;
|
|
6712
|
-
const px = amount * 100;
|
|
6713
|
-
const dx = direction === "left" ? -px : direction === "right" ? px : 0;
|
|
6714
|
-
const dy = direction === "up" ? -px : direction === "down" ? px : 0;
|
|
6715
|
-
if (input.coordinate) {
|
|
6716
|
-
const [x, y] = input.coordinate;
|
|
6717
|
-
await runCliclick([`m:${x},${y}`]);
|
|
6718
|
-
}
|
|
6719
|
-
const mods = input.text ? modifierStringToCliclick(input.text) : [];
|
|
6720
|
-
if (mods.length > 0) {
|
|
6721
|
-
await runCliclick([`kd:${mods.join(",")}`]);
|
|
6722
|
-
}
|
|
6723
|
-
await runScroll(dx, dy);
|
|
6724
|
-
if (mods.length > 0) {
|
|
6725
|
-
await runCliclick([`ku:${mods.join(",")}`]);
|
|
6726
|
-
}
|
|
6727
|
-
return `scrolled ${direction} by ${amount}`;
|
|
6728
|
-
}
|
|
6729
|
-
case "wait": {
|
|
6730
|
-
const duration = input.duration ?? 1;
|
|
6731
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
6732
|
-
return `waited ${duration}s`;
|
|
6733
|
-
}
|
|
6734
|
-
case "cursor_position": {
|
|
6735
|
-
const out = await runCliclick(["p:."]);
|
|
6736
|
-
return `cursor at ${out}`;
|
|
6737
|
-
}
|
|
6738
|
-
case "zoom": {
|
|
6739
|
-
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
6740
|
-
const [x1, y1, x2, y2] = region;
|
|
6741
|
-
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
|
|
6742
|
-
await runScreencapture(tmpPath);
|
|
6743
|
-
const sharpModule = await import("sharp");
|
|
6744
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
6745
|
-
const meta = await sharp2(tmpPath).metadata();
|
|
6746
|
-
const scaleX = (meta.width || displayWidth) / displayWidth;
|
|
6747
|
-
const scaleY = (meta.height || displayHeight) / displayHeight;
|
|
6748
|
-
const px = {
|
|
6749
|
-
left: Math.max(0, Math.round(x1 * scaleX)),
|
|
6750
|
-
top: Math.max(0, Math.round(y1 * scaleY)),
|
|
6751
|
-
width: Math.max(1, Math.round((x2 - x1) * scaleX)),
|
|
6752
|
-
height: Math.max(1, Math.round((y2 - y1) * scaleY))
|
|
6753
|
-
};
|
|
6754
|
-
const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
|
|
6755
|
-
try {
|
|
6756
|
-
unlinkSync2(tmpPath);
|
|
6757
|
-
} catch {
|
|
6758
|
-
}
|
|
6759
|
-
return { type: "image", data: buf.toString("base64") };
|
|
6760
|
-
}
|
|
6761
|
-
default: {
|
|
6762
|
-
const exhaustive = input.action;
|
|
6763
|
-
return `unsupported action: ${String(exhaustive)}`;
|
|
6764
|
-
}
|
|
6765
|
-
}
|
|
6766
|
-
} catch (err) {
|
|
6767
|
-
const msg = err?.message || String(err);
|
|
6768
|
-
let hint = "";
|
|
6769
|
-
if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
|
|
6770
|
-
hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
|
|
6771
|
-
} else if (/command not found/i.test(msg)) {
|
|
6772
|
-
hint = " (Hint: install cliclick with `brew install cliclick`)";
|
|
6773
|
-
}
|
|
6774
|
-
return `Error: ${msg}${hint}`;
|
|
6775
|
-
}
|
|
6776
|
-
},
|
|
6777
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6778
|
-
toModelOutput({ output }) {
|
|
6779
|
-
if (typeof output === "string") {
|
|
6780
|
-
return { type: "content", value: [{ type: "text", text: output }] };
|
|
6781
|
-
}
|
|
6782
|
-
return {
|
|
6783
|
-
type: "content",
|
|
6784
|
-
value: [{ type: "media", data: output.data, mediaType: "image/png" }]
|
|
6785
|
-
};
|
|
6786
|
-
}
|
|
6787
|
-
});
|
|
6788
|
-
}
|
|
6789
|
-
var execAsync5, DEFAULT_WIDTH, DEFAULT_HEIGHT;
|
|
6790
|
-
var init_computer_use = __esm({
|
|
6791
|
-
"src/tools/computer-use.ts"() {
|
|
6792
|
-
"use strict";
|
|
6793
|
-
execAsync5 = promisify5(exec5);
|
|
6794
|
-
DEFAULT_WIDTH = 1280;
|
|
6795
|
-
DEFAULT_HEIGHT = 800;
|
|
6796
|
-
}
|
|
6797
|
-
});
|
|
6798
|
-
|
|
6799
|
-
// src/tools/enable-computer-use.ts
|
|
6800
|
-
import { tool as tool13 } from "ai";
|
|
6801
|
-
import { z as z14 } from "zod";
|
|
6802
|
-
function createEnableComputerUseTool(options) {
|
|
6803
|
-
return tool13({
|
|
6804
|
-
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.",
|
|
6805
|
-
inputSchema,
|
|
6806
|
-
execute: async ({ display_width, display_height, request_permissions }) => {
|
|
6807
|
-
try {
|
|
6808
|
-
if (!isMacOs()) {
|
|
6809
|
-
return {
|
|
6810
|
-
success: false,
|
|
6811
|
-
error: "Computer use is currently only supported on macOS.",
|
|
6812
|
-
platform: process.platform
|
|
6813
|
-
};
|
|
6814
|
-
}
|
|
6815
|
-
if (!await isCliclickInstalled()) {
|
|
6816
|
-
return {
|
|
6817
|
-
success: false,
|
|
6818
|
-
error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
|
|
6819
|
-
installCommand: "brew install cliclick",
|
|
6820
|
-
fixSteps: [
|
|
6821
|
-
"In a terminal on this Mac, run: brew install cliclick",
|
|
6822
|
-
"(If Homebrew is not installed, install it first from https://brew.sh)",
|
|
6823
|
-
"Then call enable_computer_use again"
|
|
6824
|
-
]
|
|
6825
|
-
};
|
|
6826
|
-
}
|
|
6827
|
-
const acc = await hasAccessibilityPermissions();
|
|
6828
|
-
const screen = await hasScreenRecordingPermissions();
|
|
6829
|
-
const missing = [];
|
|
6830
|
-
if (!acc.ok) {
|
|
6831
|
-
let prompted = false;
|
|
6832
|
-
let panelOpened = false;
|
|
6833
|
-
if (request_permissions) {
|
|
6834
|
-
prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
|
|
6835
|
-
await openSystemSettings("accessibility").then(() => {
|
|
6836
|
-
panelOpened = true;
|
|
6837
|
-
}).catch(() => void 0);
|
|
6838
|
-
}
|
|
6839
|
-
missing.push({
|
|
6840
|
-
name: "Accessibility",
|
|
6841
|
-
reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
|
|
6842
|
-
pane: "accessibility",
|
|
6843
|
-
settingsUrl: ACCESSIBILITY_URL,
|
|
6844
|
-
fixSteps: [
|
|
6845
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
|
|
6846
|
-
"Click the + button",
|
|
6847
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6848
|
-
"Toggle the switch ON",
|
|
6849
|
-
"Restart the agent process so the new permission takes effect",
|
|
6850
|
-
"Then call enable_computer_use again"
|
|
6851
|
-
],
|
|
6852
|
-
prompted,
|
|
6853
|
-
panelOpened
|
|
6854
|
-
});
|
|
6855
|
-
}
|
|
6856
|
-
if (!screen) {
|
|
6857
|
-
let prompted = false;
|
|
6858
|
-
let panelOpened = false;
|
|
6859
|
-
if (request_permissions) {
|
|
6860
|
-
prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
|
|
6861
|
-
await openSystemSettings("screen-recording").then(() => {
|
|
6862
|
-
panelOpened = true;
|
|
6863
|
-
}).catch(() => void 0);
|
|
6864
|
-
}
|
|
6865
|
-
missing.push({
|
|
6866
|
-
name: "Screen Recording",
|
|
6867
|
-
reason: "CGPreflightScreenCaptureAccess returned false",
|
|
6868
|
-
pane: "screen-recording",
|
|
6869
|
-
settingsUrl: SCREEN_RECORDING_URL,
|
|
6870
|
-
fixSteps: [
|
|
6871
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
|
|
6872
|
-
"Click the + button",
|
|
6873
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6874
|
-
"Toggle the switch ON",
|
|
6875
|
-
"Restart the agent process so the new permission takes effect",
|
|
6876
|
-
"Then call enable_computer_use again"
|
|
6877
|
-
],
|
|
6878
|
-
prompted,
|
|
6879
|
-
panelOpened
|
|
6880
|
-
});
|
|
6881
|
-
}
|
|
6882
|
-
if (missing.length > 0) {
|
|
6883
|
-
return {
|
|
6884
|
-
success: false,
|
|
6885
|
-
error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
|
|
6886
|
-
missingPermissions: missing,
|
|
6887
|
-
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."
|
|
6888
|
-
};
|
|
6889
|
-
}
|
|
6890
|
-
let width = display_width;
|
|
6891
|
-
let height = display_height;
|
|
6892
|
-
let detected = null;
|
|
6893
|
-
if (width === void 0 || height === void 0) {
|
|
6894
|
-
detected = await detectScreenSize();
|
|
6895
|
-
width = width ?? detected?.width ?? 1280;
|
|
6896
|
-
height = height ?? detected?.height ?? 800;
|
|
6897
|
-
}
|
|
6898
|
-
const session = await sessionQueries.getById(options.sessionId);
|
|
6899
|
-
if (!session) {
|
|
6900
|
-
return { success: false, error: "Session not found" };
|
|
6901
|
-
}
|
|
6902
|
-
const config = session.config || {};
|
|
6903
|
-
if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
|
|
6904
|
-
return {
|
|
6905
|
-
success: true,
|
|
6906
|
-
alreadyEnabled: true,
|
|
6907
|
-
message: "Computer use was already enabled for this session.",
|
|
6908
|
-
displayWidth: width,
|
|
6909
|
-
displayHeight: height
|
|
6910
|
-
};
|
|
6911
|
-
}
|
|
6912
|
-
const updated = {
|
|
6913
|
-
...config,
|
|
6914
|
-
computerUseEnabled: true,
|
|
6915
|
-
computerUseDisplayWidth: width,
|
|
6916
|
-
computerUseDisplayHeight: height
|
|
6917
|
-
};
|
|
6918
|
-
await sessionQueries.update(options.sessionId, { config: updated });
|
|
6919
|
-
return {
|
|
6920
|
-
success: true,
|
|
6921
|
-
enabled: true,
|
|
6922
|
-
platform: "darwin",
|
|
6923
|
-
displayWidth: width,
|
|
6924
|
-
displayHeight: height,
|
|
6925
|
-
detectedScreenSize: detected || void 0,
|
|
6926
|
-
permissions: {
|
|
6927
|
-
accessibility: "granted",
|
|
6928
|
-
screenRecording: "granted"
|
|
6929
|
-
},
|
|
6930
|
-
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.`
|
|
6931
|
-
};
|
|
6932
|
-
} catch (err) {
|
|
6933
|
-
return {
|
|
6934
|
-
success: false,
|
|
6935
|
-
error: err?.message || String(err)
|
|
6936
|
-
};
|
|
6937
|
-
}
|
|
6938
|
-
}
|
|
6939
|
-
});
|
|
6940
|
-
}
|
|
6941
|
-
var inputSchema, ACCESSIBILITY_URL, SCREEN_RECORDING_URL;
|
|
6942
|
-
var init_enable_computer_use = __esm({
|
|
6943
|
-
"src/tools/enable-computer-use.ts"() {
|
|
6944
|
-
"use strict";
|
|
6945
|
-
init_db();
|
|
6946
|
-
init_computer_use();
|
|
6947
|
-
inputSchema = z14.object({
|
|
6948
|
-
display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
|
|
6949
|
-
display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
|
|
6950
|
-
request_permissions: z14.boolean().optional().default(true).describe(
|
|
6951
|
-
"When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
|
|
6952
|
-
)
|
|
6953
|
-
});
|
|
6954
|
-
ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
6955
|
-
SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
6956
|
-
}
|
|
6957
|
-
});
|
|
6958
|
-
|
|
6959
6378
|
// src/tools/index.ts
|
|
6960
6379
|
async function createTools(options) {
|
|
6961
6380
|
const tools = {
|
|
@@ -7000,20 +6419,6 @@ async function createTools(options) {
|
|
|
7000
6419
|
sessionId: options.sessionId
|
|
7001
6420
|
});
|
|
7002
6421
|
}
|
|
7003
|
-
if (process.platform === "darwin") {
|
|
7004
|
-
if (options.enableComputerUse) {
|
|
7005
|
-
tools.computer = createComputerUseTool({
|
|
7006
|
-
workingDirectory: options.workingDirectory,
|
|
7007
|
-
sessionId: options.sessionId,
|
|
7008
|
-
displayWidth: options.computerUseDisplayWidth,
|
|
7009
|
-
displayHeight: options.computerUseDisplayHeight
|
|
7010
|
-
});
|
|
7011
|
-
} else {
|
|
7012
|
-
tools.enable_computer_use = createEnableComputerUseTool({
|
|
7013
|
-
sessionId: options.sessionId
|
|
7014
|
-
});
|
|
7015
|
-
}
|
|
7016
|
-
}
|
|
7017
6422
|
if (options.enableSemanticSearch !== false) {
|
|
7018
6423
|
try {
|
|
7019
6424
|
if (isVectorGatewayConfigured()) {
|
|
@@ -7048,8 +6453,6 @@ var init_tools = __esm({
|
|
|
7048
6453
|
init_code_graph();
|
|
7049
6454
|
init_task();
|
|
7050
6455
|
init_upload_file();
|
|
7051
|
-
init_computer_use();
|
|
7052
|
-
init_enable_computer_use();
|
|
7053
6456
|
init_semantic();
|
|
7054
6457
|
init_remote();
|
|
7055
6458
|
init_bash();
|
|
@@ -7063,8 +6466,6 @@ var init_tools = __esm({
|
|
|
7063
6466
|
init_code_graph();
|
|
7064
6467
|
init_task();
|
|
7065
6468
|
init_upload_file();
|
|
7066
|
-
init_computer_use();
|
|
7067
|
-
init_enable_computer_use();
|
|
7068
6469
|
}
|
|
7069
6470
|
});
|
|
7070
6471
|
|
|
@@ -7540,8 +6941,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
7540
6941
|
`;
|
|
7541
6942
|
}
|
|
7542
6943
|
function buildOrchestratorPromptAddendum() {
|
|
7543
|
-
const
|
|
7544
|
-
const computerUseAvailable = platform3 === "darwin";
|
|
6944
|
+
const desktopAvailable = process.platform === "darwin";
|
|
7545
6945
|
return `
|
|
7546
6946
|
## Orchestrator Mode
|
|
7547
6947
|
|
|
@@ -7640,14 +7040,14 @@ When NOT to split (keep as one worker):
|
|
|
7640
7040
|
When spawning a worker, push it toward the *cheapest tool that gets the job done*:
|
|
7641
7041
|
|
|
7642
7042
|
1. **Bash / file tools** for anything with a CLI (git, npm, brew, builds, tests, file editing, HTTP via curl, scripting).
|
|
7643
|
-
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.${
|
|
7644
|
-
3. **
|
|
7043
|
+
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 ? `
|
|
7044
|
+
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.
|
|
7645
7045
|
|
|
7646
|
-
A common anti-pattern: a worker reaches for
|
|
7046
|
+
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."*
|
|
7647
7047
|
|
|
7648
|
-
### Serialize desktop
|
|
7048
|
+
### Serialize desktop-automation tasks
|
|
7649
7049
|
|
|
7650
|
-
There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both
|
|
7050
|
+
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.
|
|
7651
7051
|
|
|
7652
7052
|
**Rule**: when spawning workers, look at each one's goal:
|
|
7653
7053
|
|
|
@@ -7668,7 +7068,7 @@ Example: *"Take a screenshot of Calculator AND run the test suite AND open Syste
|
|
|
7668
7068
|
|
|
7669
7069
|
Headless workers never interfere with desktop workers (they don't touch the screen), so they always run in parallel.
|
|
7670
7070
|
|
|
7671
|
-
When you spawn a **desktop worker**,
|
|
7071
|
+
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.` : ""}
|
|
7672
7072
|
|
|
7673
7073
|
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.
|
|
7674
7074
|
|
|
@@ -7699,7 +7099,7 @@ You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
|
7699
7099
|
**DO** put in the goal:
|
|
7700
7100
|
|
|
7701
7101
|
- The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
|
|
7702
|
-
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill
|
|
7102
|
+
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill desktop-automation\`).
|
|
7703
7103
|
- Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
|
|
7704
7104
|
- Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
|
|
7705
7105
|
|
|
@@ -7732,7 +7132,7 @@ Bad goal (don't do this):
|
|
|
7732
7132
|
> "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'..."
|
|
7733
7133
|
|
|
7734
7134
|
Good goal (do this):
|
|
7735
|
-
> "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill
|
|
7135
|
+
> "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."
|
|
7736
7136
|
`;
|
|
7737
7137
|
}
|
|
7738
7138
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -7951,17 +7351,17 @@ __export(conversation_archive_exports, {
|
|
|
7951
7351
|
getHistoryDir: () => getHistoryDir,
|
|
7952
7352
|
listSessionArchives: () => listSessionArchives
|
|
7953
7353
|
});
|
|
7954
|
-
import { existsSync as
|
|
7955
|
-
import { join as
|
|
7354
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
7355
|
+
import { join as join8 } from "path";
|
|
7956
7356
|
function getHistoryDir() {
|
|
7957
|
-
const dir =
|
|
7958
|
-
if (!
|
|
7357
|
+
const dir = join8(ensureAppDataDirectory(), "history");
|
|
7358
|
+
if (!existsSync15(dir)) mkdirSync5(dir, { recursive: true });
|
|
7959
7359
|
return dir;
|
|
7960
7360
|
}
|
|
7961
7361
|
function appendTurn(turn) {
|
|
7962
7362
|
try {
|
|
7963
7363
|
const dir = getHistoryDir();
|
|
7964
|
-
const path =
|
|
7364
|
+
const path = join8(dir, `${turn.sessionId}.jsonl`);
|
|
7965
7365
|
const line = JSON.stringify({
|
|
7966
7366
|
ts: turn.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7967
7367
|
sessionId: turn.sessionId,
|
|
@@ -7990,7 +7390,7 @@ function flattenContent(content) {
|
|
|
7990
7390
|
}
|
|
7991
7391
|
function listSessionArchives() {
|
|
7992
7392
|
const dir = getHistoryDir();
|
|
7993
|
-
if (!
|
|
7393
|
+
if (!existsSync15(dir)) return [];
|
|
7994
7394
|
return readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
7995
7395
|
}
|
|
7996
7396
|
var init_conversation_archive = __esm({
|
|
@@ -8062,6 +7462,18 @@ function repairToolPairing(messages) {
|
|
|
8062
7462
|
}
|
|
8063
7463
|
return repaired;
|
|
8064
7464
|
}
|
|
7465
|
+
function ensureEndsWithUserOrTool(messages) {
|
|
7466
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
7467
|
+
const last = messages[messages.length - 1];
|
|
7468
|
+
if (last?.role !== "assistant") return messages;
|
|
7469
|
+
console.warn(
|
|
7470
|
+
"[context] Trailing assistant message detected \u2014 appending synthetic user turn to satisfy prefill restrictions"
|
|
7471
|
+
);
|
|
7472
|
+
return [
|
|
7473
|
+
...messages,
|
|
7474
|
+
{ role: "user", content: [{ type: "text", text: "Please continue." }] }
|
|
7475
|
+
];
|
|
7476
|
+
}
|
|
8065
7477
|
var TOOL_OUTPUT_TRIM_CHARS, COMPACTABLE_TOOLS, ContextManager;
|
|
8066
7478
|
var init_context = __esm({
|
|
8067
7479
|
"src/agent/context.ts"() {
|
|
@@ -8119,6 +7531,7 @@ ${summaryContent}`
|
|
|
8119
7531
|
];
|
|
8120
7532
|
}
|
|
8121
7533
|
messages = repairToolPairing(messages);
|
|
7534
|
+
messages = ensureEndsWithUserOrTool(messages);
|
|
8122
7535
|
return messages;
|
|
8123
7536
|
}
|
|
8124
7537
|
// ---------------------------------------------------------------------------
|
|
@@ -8312,7 +7725,8 @@ ${summaryContent}`
|
|
|
8312
7725
|
}
|
|
8313
7726
|
}
|
|
8314
7727
|
async addResponseMessages(messages) {
|
|
8315
|
-
|
|
7728
|
+
const safe = repairToolPairing(messages);
|
|
7729
|
+
await messageQueries.addMany(this.sessionId, safe);
|
|
8316
7730
|
try {
|
|
8317
7731
|
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
8318
7732
|
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
@@ -8408,6 +7822,44 @@ function getSlackSigningSecret() {
|
|
|
8408
7822
|
function getDefaultOrchestratorName() {
|
|
8409
7823
|
return readSlackConfig()?.defaultOrchestratorName ?? null;
|
|
8410
7824
|
}
|
|
7825
|
+
function getCachedSlackSelfIdentity() {
|
|
7826
|
+
const cfg = readSlackConfig();
|
|
7827
|
+
if (!cfg) return null;
|
|
7828
|
+
if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
|
|
7829
|
+
return null;
|
|
7830
|
+
}
|
|
7831
|
+
async function ensureSlackSelfIdentity() {
|
|
7832
|
+
const cfg = readSlackConfig();
|
|
7833
|
+
if (!cfg) return null;
|
|
7834
|
+
if (cachedSelf && cachedSelf.token === cfg.botToken) return cachedSelf.identity;
|
|
7835
|
+
if (selfInflight) return selfInflight;
|
|
7836
|
+
selfInflight = (async () => {
|
|
7837
|
+
try {
|
|
7838
|
+
const res = await fetch("https://slack.com/api/auth.test", {
|
|
7839
|
+
method: "POST",
|
|
7840
|
+
headers: { Authorization: `Bearer ${cfg.botToken}` }
|
|
7841
|
+
});
|
|
7842
|
+
const data = await res.json().catch(() => ({}));
|
|
7843
|
+
if (!data?.ok) {
|
|
7844
|
+
console.warn(`[slack] auth.test failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
7845
|
+
return null;
|
|
7846
|
+
}
|
|
7847
|
+
const identity = {
|
|
7848
|
+
botUserId: String(data.user_id || ""),
|
|
7849
|
+
botId: String(data.bot_id || ""),
|
|
7850
|
+
teamId: data.team_id ? String(data.team_id) : void 0
|
|
7851
|
+
};
|
|
7852
|
+
cachedSelf = { token: cfg.botToken, identity };
|
|
7853
|
+
return identity;
|
|
7854
|
+
} catch (err) {
|
|
7855
|
+
console.warn("[slack] auth.test error:", err?.message || err);
|
|
7856
|
+
return null;
|
|
7857
|
+
} finally {
|
|
7858
|
+
selfInflight = null;
|
|
7859
|
+
}
|
|
7860
|
+
})();
|
|
7861
|
+
return selfInflight;
|
|
7862
|
+
}
|
|
8411
7863
|
function getSlackAllowlistPolicy() {
|
|
8412
7864
|
try {
|
|
8413
7865
|
const cfg = getConfig();
|
|
@@ -8433,11 +7885,13 @@ function getSlackDeniedReplyPolicy() {
|
|
|
8433
7885
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
8434
7886
|
}
|
|
8435
7887
|
}
|
|
8436
|
-
var DEFAULT_DENIED_TEMPLATE;
|
|
7888
|
+
var cachedSelf, selfInflight, DEFAULT_DENIED_TEMPLATE;
|
|
8437
7889
|
var init_client3 = __esm({
|
|
8438
7890
|
"src/integrations/slack/client.ts"() {
|
|
8439
7891
|
"use strict";
|
|
8440
7892
|
init_config();
|
|
7893
|
+
cachedSelf = null;
|
|
7894
|
+
selfInflight = null;
|
|
8441
7895
|
DEFAULT_DENIED_TEMPLATE = "Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)";
|
|
8442
7896
|
}
|
|
8443
7897
|
});
|
|
@@ -8455,9 +7909,19 @@ function isThreadOwned(channel, threadTs) {
|
|
|
8455
7909
|
function stripMention(text) {
|
|
8456
7910
|
return String(text || "").replace(/<@[^>]+>/g, "").trim();
|
|
8457
7911
|
}
|
|
8458
|
-
function
|
|
7912
|
+
function isSelfAuthored(event, self) {
|
|
7913
|
+
if (!self) return true;
|
|
7914
|
+
if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
|
|
7915
|
+
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
7916
|
+
return false;
|
|
7917
|
+
}
|
|
7918
|
+
function slackEventToInboundResult(event, opts = {}) {
|
|
8459
7919
|
if (!event) return { event: null, dropReason: "empty_text" };
|
|
8460
|
-
|
|
7920
|
+
const self = opts.self ?? getCachedSlackSelfIdentity();
|
|
7921
|
+
const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
|
|
7922
|
+
if (isBotAuthored && isSelfAuthored(event, self)) {
|
|
7923
|
+
return { event: null, dropReason: "bot_message" };
|
|
7924
|
+
}
|
|
8461
7925
|
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
8462
7926
|
return { event: null, dropReason: "bot_message" };
|
|
8463
7927
|
}
|
|
@@ -8534,7 +7998,6 @@ var init_slack = __esm({
|
|
|
8534
7998
|
}
|
|
8535
7999
|
};
|
|
8536
8000
|
IGNORED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
|
|
8537
|
-
"bot_message",
|
|
8538
8001
|
"message_changed",
|
|
8539
8002
|
"message_deleted",
|
|
8540
8003
|
"channel_join",
|
|
@@ -8753,7 +8216,7 @@ var init_messenger = __esm({
|
|
|
8753
8216
|
});
|
|
8754
8217
|
|
|
8755
8218
|
// src/orchestrator/schedules-store.ts
|
|
8756
|
-
import { nanoid as
|
|
8219
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
8757
8220
|
async function readOrch(orchestratorSessionId) {
|
|
8758
8221
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
8759
8222
|
if (!s) return null;
|
|
@@ -8768,7 +8231,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
8768
8231
|
const data = await readOrch(orchestratorSessionId);
|
|
8769
8232
|
if (!data) throw new Error("orchestrator session not found");
|
|
8770
8233
|
const row = {
|
|
8771
|
-
id: `sch_${
|
|
8234
|
+
id: `sch_${nanoid4(10)}`,
|
|
8772
8235
|
name: input.name,
|
|
8773
8236
|
cron: input.cron,
|
|
8774
8237
|
prompt: input.prompt,
|
|
@@ -8805,7 +8268,7 @@ var init_schedules_store = __esm({
|
|
|
8805
8268
|
|
|
8806
8269
|
// src/orchestrator/webhooks-store.ts
|
|
8807
8270
|
import { randomBytes } from "crypto";
|
|
8808
|
-
import { nanoid as
|
|
8271
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
8809
8272
|
function newToken() {
|
|
8810
8273
|
return randomBytes(24).toString("base64url");
|
|
8811
8274
|
}
|
|
@@ -8822,7 +8285,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
8822
8285
|
const data = await readOrch2(orchestratorSessionId);
|
|
8823
8286
|
if (!data) throw new Error("orchestrator session not found");
|
|
8824
8287
|
const row = {
|
|
8825
|
-
id: `whk_${
|
|
8288
|
+
id: `whk_${nanoid5(10)}`,
|
|
8826
8289
|
name: input.name,
|
|
8827
8290
|
token: newToken(),
|
|
8828
8291
|
wake: input.wake ?? "now",
|
|
@@ -8878,8 +8341,8 @@ var init_webhooks_store = __esm({
|
|
|
8878
8341
|
});
|
|
8879
8342
|
|
|
8880
8343
|
// src/tools/orchestrator-actions.ts
|
|
8881
|
-
import { tool as
|
|
8882
|
-
import { z as
|
|
8344
|
+
import { tool as tool13 } from "ai";
|
|
8345
|
+
import { z as z14 } from "zod";
|
|
8883
8346
|
async function api2(baseUrl, path, init = {}) {
|
|
8884
8347
|
const res = await fetch(`${baseUrl}${path}`, {
|
|
8885
8348
|
method: init.method || "GET",
|
|
@@ -8905,7 +8368,7 @@ function previewMessageContent(content) {
|
|
|
8905
8368
|
return "";
|
|
8906
8369
|
}
|
|
8907
8370
|
function buildAgentTool(opts) {
|
|
8908
|
-
return
|
|
8371
|
+
return tool13({
|
|
8909
8372
|
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).",
|
|
8910
8373
|
inputSchema: agentInputSchema,
|
|
8911
8374
|
execute: async (input) => {
|
|
@@ -9008,7 +8471,7 @@ function buildAgentTool(opts) {
|
|
|
9008
8471
|
});
|
|
9009
8472
|
}
|
|
9010
8473
|
function buildMessengerTool() {
|
|
9011
|
-
return
|
|
8474
|
+
return tool13({
|
|
9012
8475
|
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.",
|
|
9013
8476
|
inputSchema: messengerInputSchema,
|
|
9014
8477
|
execute: async (input) => {
|
|
@@ -9030,7 +8493,7 @@ function buildMessengerTool() {
|
|
|
9030
8493
|
});
|
|
9031
8494
|
}
|
|
9032
8495
|
function buildScheduleTool(opts) {
|
|
9033
|
-
return
|
|
8496
|
+
return tool13({
|
|
9034
8497
|
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.",
|
|
9035
8498
|
inputSchema: scheduleInputSchema,
|
|
9036
8499
|
execute: async (input) => {
|
|
@@ -9073,7 +8536,7 @@ function buildWebhookUrl(opts, token) {
|
|
|
9073
8536
|
return `${base}${webhookPrefix2}/inbox/${token}`;
|
|
9074
8537
|
}
|
|
9075
8538
|
function buildWebhookTool(opts) {
|
|
9076
|
-
return
|
|
8539
|
+
return tool13({
|
|
9077
8540
|
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.",
|
|
9078
8541
|
inputSchema: webhookInputSchema,
|
|
9079
8542
|
execute: async (input) => {
|
|
@@ -9122,66 +8585,66 @@ var init_orchestrator_actions = __esm({
|
|
|
9122
8585
|
init_schedules_store();
|
|
9123
8586
|
init_config();
|
|
9124
8587
|
init_webhooks_store();
|
|
9125
|
-
AGENT_STATUS_ENUM =
|
|
9126
|
-
agentInputSchema =
|
|
9127
|
-
action:
|
|
8588
|
+
AGENT_STATUS_ENUM = z14.enum(["running", "needs_attention", "completed", "failed", "idle"]);
|
|
8589
|
+
agentInputSchema = z14.object({
|
|
8590
|
+
action: z14.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
|
|
9128
8591
|
// list
|
|
9129
8592
|
status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
|
|
9130
|
-
limit:
|
|
8593
|
+
limit: z14.number().int().min(1).max(100).optional().describe("list only: max rows."),
|
|
9131
8594
|
// get / message / answer_question / stop
|
|
9132
|
-
id:
|
|
9133
|
-
recentMessages:
|
|
8595
|
+
id: z14.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
|
|
8596
|
+
recentMessages: z14.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
|
|
9134
8597
|
// spawn
|
|
9135
|
-
name:
|
|
9136
|
-
goal:
|
|
9137
|
-
outputSchema:
|
|
8598
|
+
name: z14.string().optional().describe("spawn only: short human-readable label."),
|
|
8599
|
+
goal: z14.string().optional().describe("spawn only: the worker's self-contained instruction."),
|
|
8600
|
+
outputSchema: z14.record(z14.string(), z14.unknown()).optional().describe(
|
|
9138
8601
|
'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
|
|
9139
8602
|
),
|
|
9140
|
-
model:
|
|
9141
|
-
workingDirectory:
|
|
9142
|
-
maxIterations:
|
|
8603
|
+
model: z14.string().optional().describe("spawn only: model override."),
|
|
8604
|
+
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
8605
|
+
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
9143
8606
|
// message
|
|
9144
|
-
text:
|
|
9145
|
-
force:
|
|
8607
|
+
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
8608
|
+
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
9146
8609
|
// answer_question
|
|
9147
|
-
questionId:
|
|
9148
|
-
answer:
|
|
8610
|
+
questionId: z14.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
|
|
8611
|
+
answer: z14.string().optional().describe("answer_question only: your answer.")
|
|
9149
8612
|
});
|
|
9150
|
-
messengerInputSchema =
|
|
9151
|
-
action:
|
|
8613
|
+
messengerInputSchema = z14.object({
|
|
8614
|
+
action: z14.enum(["list_channels", "post"]),
|
|
9152
8615
|
// post
|
|
9153
|
-
channel:
|
|
9154
|
-
to:
|
|
9155
|
-
text:
|
|
9156
|
-
threadTs:
|
|
9157
|
-
subject:
|
|
8616
|
+
channel: z14.string().optional().describe('post only: channel id (e.g. "slack").'),
|
|
8617
|
+
to: z14.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
|
|
8618
|
+
text: z14.string().optional().describe("post only: message body."),
|
|
8619
|
+
threadTs: z14.string().optional().describe("post + slack: reply in this thread."),
|
|
8620
|
+
subject: z14.string().optional().describe("post + email: subject (future).")
|
|
9158
8621
|
});
|
|
9159
|
-
scheduleInputSchema =
|
|
9160
|
-
action:
|
|
8622
|
+
scheduleInputSchema = z14.object({
|
|
8623
|
+
action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
9161
8624
|
// create / update
|
|
9162
|
-
name:
|
|
9163
|
-
cron:
|
|
9164
|
-
prompt:
|
|
9165
|
-
replyChannel:
|
|
8625
|
+
name: z14.string().optional().describe("create | update"),
|
|
8626
|
+
cron: z14.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
|
|
8627
|
+
prompt: z14.string().optional().describe("create | update: the prompt injected when the schedule fires."),
|
|
8628
|
+
replyChannel: z14.string().optional().describe("create | update: default channel id for orchestrator replies."),
|
|
9166
8629
|
// update / delete / pause / resume
|
|
9167
|
-
id:
|
|
9168
|
-
enabled:
|
|
8630
|
+
id: z14.string().optional().describe("update | delete | pause | resume: schedule id."),
|
|
8631
|
+
enabled: z14.boolean().optional().describe("update only.")
|
|
9169
8632
|
});
|
|
9170
|
-
webhookInputSchema =
|
|
9171
|
-
action:
|
|
9172
|
-
name:
|
|
9173
|
-
wake:
|
|
9174
|
-
template:
|
|
9175
|
-
id:
|
|
9176
|
-
rotateToken:
|
|
8633
|
+
webhookInputSchema = z14.object({
|
|
8634
|
+
action: z14.enum(["create", "list", "update", "delete"]),
|
|
8635
|
+
name: z14.string().optional().describe("create | update."),
|
|
8636
|
+
wake: z14.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
|
|
8637
|
+
template: z14.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
|
|
8638
|
+
id: z14.string().optional().describe("update | delete: webhook id."),
|
|
8639
|
+
rotateToken: z14.boolean().optional().describe("update only: regenerate the URL token.")
|
|
9177
8640
|
});
|
|
9178
8641
|
}
|
|
9179
8642
|
});
|
|
9180
8643
|
|
|
9181
8644
|
// src/integrations/mcp/store.ts
|
|
9182
|
-
import { nanoid as
|
|
9183
|
-
import { existsSync as
|
|
9184
|
-
import { resolve as resolve10, join as
|
|
8645
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
8646
|
+
import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
|
|
8647
|
+
import { resolve as resolve10, join as join9 } from "path";
|
|
9185
8648
|
function readServers() {
|
|
9186
8649
|
try {
|
|
9187
8650
|
const cfg = getConfig();
|
|
@@ -9193,12 +8656,12 @@ function readServers() {
|
|
|
9193
8656
|
function refreshMcpServersFromDisk() {
|
|
9194
8657
|
const candidates = [
|
|
9195
8658
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9196
|
-
|
|
8659
|
+
join9(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9197
8660
|
];
|
|
9198
8661
|
for (const path of candidates) {
|
|
9199
|
-
if (!
|
|
8662
|
+
if (!existsSync16(path)) continue;
|
|
9200
8663
|
try {
|
|
9201
|
-
const raw = JSON.parse(
|
|
8664
|
+
const raw = JSON.parse(readFileSync7(path, "utf-8"));
|
|
9202
8665
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9203
8666
|
setMcpServers(servers2);
|
|
9204
8667
|
return servers2;
|
|
@@ -9217,7 +8680,7 @@ function createMcpServer(input) {
|
|
|
9217
8680
|
const all = readServers();
|
|
9218
8681
|
validateInput(input);
|
|
9219
8682
|
const row = {
|
|
9220
|
-
id: `mcp_${
|
|
8683
|
+
id: `mcp_${nanoid6(10)}`,
|
|
9221
8684
|
name: sanitizeName(input.name),
|
|
9222
8685
|
transport: input.transport,
|
|
9223
8686
|
url: input.url,
|
|
@@ -9808,15 +9271,15 @@ var recorder_exports = {};
|
|
|
9808
9271
|
__export(recorder_exports, {
|
|
9809
9272
|
FrameRecorder: () => FrameRecorder
|
|
9810
9273
|
});
|
|
9811
|
-
import { exec as
|
|
9812
|
-
import { promisify as
|
|
9274
|
+
import { exec as exec5 } from "child_process";
|
|
9275
|
+
import { promisify as promisify5 } from "util";
|
|
9813
9276
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
9814
|
-
import { join as
|
|
9815
|
-
import { tmpdir
|
|
9816
|
-
import { nanoid as
|
|
9277
|
+
import { join as join10 } from "path";
|
|
9278
|
+
import { tmpdir } from "os";
|
|
9279
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
9817
9280
|
async function checkFfmpeg() {
|
|
9818
9281
|
try {
|
|
9819
|
-
await
|
|
9282
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
9820
9283
|
return true;
|
|
9821
9284
|
} catch {
|
|
9822
9285
|
return false;
|
|
@@ -9828,11 +9291,11 @@ async function cleanup(dir) {
|
|
|
9828
9291
|
} catch {
|
|
9829
9292
|
}
|
|
9830
9293
|
}
|
|
9831
|
-
var
|
|
9294
|
+
var execAsync5, FrameRecorder;
|
|
9832
9295
|
var init_recorder = __esm({
|
|
9833
9296
|
"src/browser/recorder.ts"() {
|
|
9834
9297
|
"use strict";
|
|
9835
|
-
|
|
9298
|
+
execAsync5 = promisify5(exec5);
|
|
9836
9299
|
FrameRecorder = class {
|
|
9837
9300
|
frames = [];
|
|
9838
9301
|
startTime = null;
|
|
@@ -9868,21 +9331,21 @@ var init_recorder = __esm({
|
|
|
9868
9331
|
*/
|
|
9869
9332
|
async encode() {
|
|
9870
9333
|
if (this.frames.length === 0) return null;
|
|
9871
|
-
const workDir =
|
|
9334
|
+
const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
9872
9335
|
await mkdir4(workDir, { recursive: true });
|
|
9873
9336
|
try {
|
|
9874
9337
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9875
|
-
const framePath =
|
|
9338
|
+
const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
9876
9339
|
await writeFile5(framePath, this.frames[i].data);
|
|
9877
9340
|
}
|
|
9878
9341
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9879
9342
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9880
9343
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9881
|
-
const outputPath =
|
|
9344
|
+
const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
|
|
9882
9345
|
const hasFfmpeg = await checkFfmpeg();
|
|
9883
9346
|
if (hasFfmpeg) {
|
|
9884
|
-
await
|
|
9885
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
9347
|
+
await execAsync5(
|
|
9348
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
9886
9349
|
{ timeout: 12e4 }
|
|
9887
9350
|
);
|
|
9888
9351
|
} else {
|
|
@@ -9894,7 +9357,7 @@ var init_recorder = __esm({
|
|
|
9894
9357
|
const files = await readdir5(workDir);
|
|
9895
9358
|
for (const f of files) {
|
|
9896
9359
|
if (f.startsWith("frame_")) {
|
|
9897
|
-
await unlink2(
|
|
9360
|
+
await unlink2(join10(workDir, f)).catch(() => {
|
|
9898
9361
|
});
|
|
9899
9362
|
}
|
|
9900
9363
|
}
|
|
@@ -9919,11 +9382,11 @@ var init_recorder = __esm({
|
|
|
9919
9382
|
import {
|
|
9920
9383
|
streamText as streamText2,
|
|
9921
9384
|
generateText as generateText3,
|
|
9922
|
-
tool as
|
|
9385
|
+
tool as tool14,
|
|
9923
9386
|
stepCountIs as stepCountIs2
|
|
9924
9387
|
} from "ai";
|
|
9925
|
-
import { z as
|
|
9926
|
-
import { nanoid as
|
|
9388
|
+
import { z as z15 } from "zod";
|
|
9389
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
9927
9390
|
function anySignal(signals) {
|
|
9928
9391
|
const ctrl = new AbortController();
|
|
9929
9392
|
for (const s of signals) {
|
|
@@ -9992,14 +9455,10 @@ var init_agent = __esm({
|
|
|
9992
9455
|
*/
|
|
9993
9456
|
async createToolsWithCallbacks(options) {
|
|
9994
9457
|
const config = getConfig();
|
|
9995
|
-
const sessionConfig = this.session.config || {};
|
|
9996
9458
|
const tools = await createTools({
|
|
9997
9459
|
sessionId: this.session.id,
|
|
9998
9460
|
workingDirectory: this.session.workingDirectory,
|
|
9999
9461
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
10000
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
10001
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
10002
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
10003
9462
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
10004
9463
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
10005
9464
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -10049,14 +9508,10 @@ var init_agent = __esm({
|
|
|
10049
9508
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
10050
9509
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
10051
9510
|
});
|
|
10052
|
-
const sessionConfig = session.config || {};
|
|
10053
9511
|
const tools = await createTools({
|
|
10054
9512
|
sessionId: session.id,
|
|
10055
9513
|
workingDirectory: session.workingDirectory,
|
|
10056
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
10057
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
10058
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
10059
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
9514
|
+
skillsDirectories: config.resolvedSkillsDirectories
|
|
10060
9515
|
});
|
|
10061
9516
|
if (session.config?.role === "orchestrator") {
|
|
10062
9517
|
const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
|
|
@@ -10294,14 +9749,10 @@ ${personality.trim()}`;
|
|
|
10294
9749
|
});
|
|
10295
9750
|
}
|
|
10296
9751
|
};
|
|
10297
|
-
const taskSessionConfig = this.session.config || {};
|
|
10298
9752
|
const taskTools = await createTools({
|
|
10299
9753
|
sessionId: this.session.id,
|
|
10300
9754
|
workingDirectory: this.session.workingDirectory,
|
|
10301
9755
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
10302
|
-
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
10303
|
-
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
10304
|
-
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
10305
9756
|
onBashProgress: bashProgressHandler,
|
|
10306
9757
|
onWriteFileProgress: (progress) => {
|
|
10307
9758
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -10649,11 +10100,11 @@ ${p.text}` : p.text;
|
|
|
10649
10100
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10650
10101
|
if (!isRemoteConfigured2()) return [];
|
|
10651
10102
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
10652
|
-
const { join:
|
|
10103
|
+
const { join: join16, basename: basename6 } = await import("path");
|
|
10653
10104
|
const urls = [];
|
|
10654
10105
|
for (const filePath of filePaths) {
|
|
10655
10106
|
try {
|
|
10656
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
10107
|
+
const fullPath = filePath.startsWith("/") ? filePath : join16(this.session.workingDirectory, filePath);
|
|
10657
10108
|
const fileName = basename6(fullPath);
|
|
10658
10109
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10659
10110
|
const mimeMap = {
|
|
@@ -10711,11 +10162,11 @@ ${p.text}` : p.text;
|
|
|
10711
10162
|
wrappedTools[name] = originalTool;
|
|
10712
10163
|
continue;
|
|
10713
10164
|
}
|
|
10714
|
-
wrappedTools[name] =
|
|
10165
|
+
wrappedTools[name] = tool14({
|
|
10715
10166
|
description: originalTool.description || "",
|
|
10716
|
-
inputSchema: originalTool.inputSchema ||
|
|
10167
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
10717
10168
|
execute: async (input, toolOptions) => {
|
|
10718
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
10169
|
+
const toolCallId = toolOptions.toolCallId || nanoid8();
|
|
10719
10170
|
const execution = toolExecutionQueries.create({
|
|
10720
10171
|
sessionId: this.session.id,
|
|
10721
10172
|
toolName: name,
|
|
@@ -10733,10 +10184,10 @@ ${p.text}` : p.text;
|
|
|
10733
10184
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
10734
10185
|
approvalResolvers.delete(toolCallId);
|
|
10735
10186
|
this.pendingApprovals.delete(toolCallId);
|
|
10736
|
-
const
|
|
10187
|
+
const exec7 = await execution;
|
|
10737
10188
|
if (!approved) {
|
|
10738
10189
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
10739
|
-
await toolExecutionQueries.reject(
|
|
10190
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
10740
10191
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10741
10192
|
return {
|
|
10742
10193
|
status: "rejected",
|
|
@@ -10746,14 +10197,14 @@ ${p.text}` : p.text;
|
|
|
10746
10197
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
10747
10198
|
};
|
|
10748
10199
|
}
|
|
10749
|
-
await toolExecutionQueries.approve(
|
|
10200
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
10750
10201
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10751
10202
|
try {
|
|
10752
10203
|
const result = await originalTool.execute(input, toolOptions);
|
|
10753
|
-
await toolExecutionQueries.complete(
|
|
10204
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
10754
10205
|
return result;
|
|
10755
10206
|
} catch (error) {
|
|
10756
|
-
await toolExecutionQueries.complete(
|
|
10207
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
10757
10208
|
throw error;
|
|
10758
10209
|
}
|
|
10759
10210
|
}
|
|
@@ -10858,19 +10309,19 @@ var init_session_lock = __esm({
|
|
|
10858
10309
|
});
|
|
10859
10310
|
|
|
10860
10311
|
// src/orchestrator/webhook-events.ts
|
|
10861
|
-
import { existsSync as
|
|
10862
|
-
import { dirname as dirname6, join as
|
|
10863
|
-
import { nanoid as
|
|
10312
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
10313
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
10314
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
10864
10315
|
function logFilePath() {
|
|
10865
|
-
return
|
|
10316
|
+
return join11(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10866
10317
|
}
|
|
10867
10318
|
function ensureLoaded() {
|
|
10868
10319
|
if (cache !== null) return cache;
|
|
10869
10320
|
cache = [];
|
|
10870
10321
|
try {
|
|
10871
10322
|
const p = logFilePath();
|
|
10872
|
-
if (!
|
|
10873
|
-
const lines =
|
|
10323
|
+
if (!existsSync17(p)) return cache;
|
|
10324
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
10874
10325
|
for (const line of lines) {
|
|
10875
10326
|
try {
|
|
10876
10327
|
cache.push(JSON.parse(line));
|
|
@@ -10894,14 +10345,14 @@ function appendEvent(ev) {
|
|
|
10894
10345
|
if (list.length > MAX_EVENTS) list.shift();
|
|
10895
10346
|
try {
|
|
10896
10347
|
const p = logFilePath();
|
|
10897
|
-
|
|
10348
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
10898
10349
|
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10899
10350
|
} catch {
|
|
10900
10351
|
}
|
|
10901
10352
|
}
|
|
10902
10353
|
function recordEvent(ev) {
|
|
10903
10354
|
const full = {
|
|
10904
|
-
id: ev.id ??
|
|
10355
|
+
id: ev.id ?? nanoid9(),
|
|
10905
10356
|
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10906
10357
|
source: ev.source,
|
|
10907
10358
|
status: ev.status,
|
|
@@ -10925,7 +10376,7 @@ function updateEvent(id, patch) {
|
|
|
10925
10376
|
list[i] = { ...list[i], ...patch };
|
|
10926
10377
|
try {
|
|
10927
10378
|
const p = logFilePath();
|
|
10928
|
-
|
|
10379
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
10929
10380
|
writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10930
10381
|
} catch {
|
|
10931
10382
|
}
|
|
@@ -11232,8 +10683,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
11232
10683
|
import { serve } from "@hono/node-server";
|
|
11233
10684
|
import { cors } from "hono/cors";
|
|
11234
10685
|
import { logger } from "hono/logger";
|
|
11235
|
-
import { existsSync as
|
|
11236
|
-
import { resolve as resolve11, dirname as dirname8, join as
|
|
10686
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
10687
|
+
import { resolve as resolve11, dirname as dirname8, join as join15 } from "path";
|
|
11237
10688
|
import { spawn as spawn2 } from "child_process";
|
|
11238
10689
|
import { createServer as createNetServer } from "net";
|
|
11239
10690
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -11246,11 +10697,11 @@ init_tmux();
|
|
|
11246
10697
|
init_checkpoints();
|
|
11247
10698
|
import { Hono } from "hono";
|
|
11248
10699
|
import { zValidator } from "@hono/zod-validator";
|
|
11249
|
-
import { z as
|
|
11250
|
-
import { existsSync as
|
|
10700
|
+
import { z as z16 } from "zod";
|
|
10701
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
11251
10702
|
import { readdir as readdir6 } from "fs/promises";
|
|
11252
|
-
import { join as
|
|
11253
|
-
import { nanoid as
|
|
10703
|
+
import { join as join12, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
10704
|
+
import { nanoid as nanoid10 } from "nanoid";
|
|
11254
10705
|
|
|
11255
10706
|
// src/tasks/agent-status.ts
|
|
11256
10707
|
init_questions();
|
|
@@ -11308,22 +10759,22 @@ function cleanupPendingInputs() {
|
|
|
11308
10759
|
}
|
|
11309
10760
|
}
|
|
11310
10761
|
}
|
|
11311
|
-
var createSessionSchema =
|
|
11312
|
-
name:
|
|
11313
|
-
workingDirectory:
|
|
11314
|
-
model:
|
|
11315
|
-
toolApprovals:
|
|
11316
|
-
// Optional full session-config passthrough (
|
|
11317
|
-
config:
|
|
11318
|
-
role:
|
|
10762
|
+
var createSessionSchema = z16.object({
|
|
10763
|
+
name: z16.string().optional(),
|
|
10764
|
+
workingDirectory: z16.string().optional(),
|
|
10765
|
+
model: z16.string().optional(),
|
|
10766
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
|
|
10767
|
+
// Optional full session-config passthrough (role, persona, etc.)
|
|
10768
|
+
config: z16.record(z16.string(), z16.unknown()).optional(),
|
|
10769
|
+
role: z16.enum(["orchestrator", "worker", "chat"]).optional()
|
|
11319
10770
|
});
|
|
11320
|
-
var paginationQuerySchema =
|
|
11321
|
-
limit:
|
|
11322
|
-
offset:
|
|
11323
|
-
role:
|
|
10771
|
+
var paginationQuerySchema = z16.object({
|
|
10772
|
+
limit: z16.string().optional(),
|
|
10773
|
+
offset: z16.string().optional(),
|
|
10774
|
+
role: z16.enum(["orchestrator", "worker", "chat", "all"]).optional()
|
|
11324
10775
|
});
|
|
11325
|
-
var messagesQuerySchema =
|
|
11326
|
-
limit:
|
|
10776
|
+
var messagesQuerySchema = z16.object({
|
|
10777
|
+
limit: z16.string().optional()
|
|
11327
10778
|
});
|
|
11328
10779
|
sessions2.get(
|
|
11329
10780
|
"/",
|
|
@@ -11392,15 +10843,11 @@ sessions2.post(
|
|
|
11392
10843
|
async (c) => {
|
|
11393
10844
|
const body = c.req.valid("json");
|
|
11394
10845
|
const config = getConfig();
|
|
11395
|
-
const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
|
|
11396
10846
|
const baseConfig = body.config || {};
|
|
11397
10847
|
const mergedConfig = {
|
|
11398
10848
|
...baseConfig,
|
|
11399
10849
|
...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
|
|
11400
|
-
...body.role ? { role: body.role } : {}
|
|
11401
|
-
// Turn on computer use by default if the server was launched with --enable-computer-use,
|
|
11402
|
-
// unless the client explicitly provided a value.
|
|
11403
|
-
...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
|
|
10850
|
+
...body.role ? { role: body.role } : {}
|
|
11404
10851
|
};
|
|
11405
10852
|
const agent = await Agent.create({
|
|
11406
10853
|
name: body.name,
|
|
@@ -11577,10 +11024,10 @@ sessions2.get("/:id/tools", async (c) => {
|
|
|
11577
11024
|
count: executions.length
|
|
11578
11025
|
});
|
|
11579
11026
|
});
|
|
11580
|
-
var updateSessionSchema =
|
|
11581
|
-
model:
|
|
11582
|
-
name:
|
|
11583
|
-
toolApprovals:
|
|
11027
|
+
var updateSessionSchema = z16.object({
|
|
11028
|
+
model: z16.string().optional(),
|
|
11029
|
+
name: z16.string().optional(),
|
|
11030
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
11584
11031
|
});
|
|
11585
11032
|
sessions2.patch(
|
|
11586
11033
|
"/:id",
|
|
@@ -11650,10 +11097,10 @@ sessions2.post("/:id/clear", async (c) => {
|
|
|
11650
11097
|
await agent.clearContext();
|
|
11651
11098
|
return c.json({ success: true, sessionId: id });
|
|
11652
11099
|
});
|
|
11653
|
-
var injectMessageSchema =
|
|
11654
|
-
text:
|
|
11655
|
-
source:
|
|
11656
|
-
force:
|
|
11100
|
+
var injectMessageSchema = z16.object({
|
|
11101
|
+
text: z16.string().min(1),
|
|
11102
|
+
source: z16.enum(["orchestrator", "user", "system"]).optional(),
|
|
11103
|
+
force: z16.boolean().optional()
|
|
11657
11104
|
});
|
|
11658
11105
|
sessions2.post(
|
|
11659
11106
|
"/:id/messages",
|
|
@@ -11667,8 +11114,8 @@ sessions2.post(
|
|
|
11667
11114
|
return c.json({ success: true, sessionId: id, queued: true, force });
|
|
11668
11115
|
}
|
|
11669
11116
|
);
|
|
11670
|
-
var pendingInputSchema =
|
|
11671
|
-
text:
|
|
11117
|
+
var pendingInputSchema = z16.object({
|
|
11118
|
+
text: z16.string()
|
|
11672
11119
|
});
|
|
11673
11120
|
sessions2.post(
|
|
11674
11121
|
"/:id/pending-input",
|
|
@@ -11699,13 +11146,13 @@ sessions2.get("/:id/pending-input", async (c) => {
|
|
|
11699
11146
|
createdAt: pending.createdAt.toISOString()
|
|
11700
11147
|
});
|
|
11701
11148
|
});
|
|
11702
|
-
var devtoolsContextSchema =
|
|
11703
|
-
url:
|
|
11704
|
-
path:
|
|
11705
|
-
pageName:
|
|
11706
|
-
screenWidth:
|
|
11707
|
-
screenHeight:
|
|
11708
|
-
devicePixelRatio:
|
|
11149
|
+
var devtoolsContextSchema = z16.object({
|
|
11150
|
+
url: z16.string(),
|
|
11151
|
+
path: z16.string(),
|
|
11152
|
+
pageName: z16.string().optional(),
|
|
11153
|
+
screenWidth: z16.number().optional(),
|
|
11154
|
+
screenHeight: z16.number().optional(),
|
|
11155
|
+
devicePixelRatio: z16.number().optional()
|
|
11709
11156
|
});
|
|
11710
11157
|
sessions2.post(
|
|
11711
11158
|
"/:id/devtools-context",
|
|
@@ -11891,12 +11338,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11891
11338
|
});
|
|
11892
11339
|
function getAttachmentsDir(sessionId) {
|
|
11893
11340
|
const appDataDir = getAppDataDirectory();
|
|
11894
|
-
return
|
|
11341
|
+
return join12(appDataDir, "attachments", sessionId);
|
|
11895
11342
|
}
|
|
11896
11343
|
function ensureAttachmentsDir(sessionId) {
|
|
11897
11344
|
const dir = getAttachmentsDir(sessionId);
|
|
11898
|
-
if (!
|
|
11899
|
-
|
|
11345
|
+
if (!existsSync18(dir)) {
|
|
11346
|
+
mkdirSync7(dir, { recursive: true });
|
|
11900
11347
|
}
|
|
11901
11348
|
return dir;
|
|
11902
11349
|
}
|
|
@@ -11907,12 +11354,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11907
11354
|
return c.json({ error: "Session not found" }, 404);
|
|
11908
11355
|
}
|
|
11909
11356
|
const dir = getAttachmentsDir(sessionId);
|
|
11910
|
-
if (!
|
|
11357
|
+
if (!existsSync18(dir)) {
|
|
11911
11358
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
11912
11359
|
}
|
|
11913
11360
|
const files = readdirSync3(dir);
|
|
11914
11361
|
const attachments = files.map((filename) => {
|
|
11915
|
-
const filePath =
|
|
11362
|
+
const filePath = join12(dir, filename);
|
|
11916
11363
|
const stats = statSync2(filePath);
|
|
11917
11364
|
return {
|
|
11918
11365
|
id: filename.split("_")[0],
|
|
@@ -11944,10 +11391,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11944
11391
|
return c.json({ error: "No file provided" }, 400);
|
|
11945
11392
|
}
|
|
11946
11393
|
const dir = ensureAttachmentsDir(sessionId);
|
|
11947
|
-
const id =
|
|
11394
|
+
const id = nanoid10(10);
|
|
11948
11395
|
const ext = extname8(file.name) || "";
|
|
11949
11396
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11950
|
-
const filePath =
|
|
11397
|
+
const filePath = join12(dir, safeFilename);
|
|
11951
11398
|
const arrayBuffer = await file.arrayBuffer();
|
|
11952
11399
|
writeFileSync4(filePath, Buffer.from(arrayBuffer));
|
|
11953
11400
|
return c.json({
|
|
@@ -11970,10 +11417,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11970
11417
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
11971
11418
|
}
|
|
11972
11419
|
const dir = ensureAttachmentsDir(sessionId);
|
|
11973
|
-
const id =
|
|
11420
|
+
const id = nanoid10(10);
|
|
11974
11421
|
const ext = extname8(body.filename) || "";
|
|
11975
11422
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11976
|
-
const filePath =
|
|
11423
|
+
const filePath = join12(dir, safeFilename);
|
|
11977
11424
|
let base64Data = body.data;
|
|
11978
11425
|
if (base64Data.includes(",")) {
|
|
11979
11426
|
base64Data = base64Data.split(",")[1];
|
|
@@ -12002,7 +11449,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12002
11449
|
return c.json({ error: "Session not found" }, 404);
|
|
12003
11450
|
}
|
|
12004
11451
|
const dir = getAttachmentsDir(sessionId);
|
|
12005
|
-
if (!
|
|
11452
|
+
if (!existsSync18(dir)) {
|
|
12006
11453
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12007
11454
|
}
|
|
12008
11455
|
const files = readdirSync3(dir);
|
|
@@ -12010,14 +11457,14 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12010
11457
|
if (!file) {
|
|
12011
11458
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12012
11459
|
}
|
|
12013
|
-
const filePath =
|
|
12014
|
-
|
|
11460
|
+
const filePath = join12(dir, file);
|
|
11461
|
+
unlinkSync2(filePath);
|
|
12015
11462
|
return c.json({ success: true, id: attachmentId });
|
|
12016
11463
|
});
|
|
12017
|
-
var filesQuerySchema =
|
|
12018
|
-
query:
|
|
11464
|
+
var filesQuerySchema = z16.object({
|
|
11465
|
+
query: z16.string().optional(),
|
|
12019
11466
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
12020
|
-
limit:
|
|
11467
|
+
limit: z16.string().optional()
|
|
12021
11468
|
// Max results (default 50)
|
|
12022
11469
|
});
|
|
12023
11470
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -12093,7 +11540,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12093
11540
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12094
11541
|
for (const entry2 of entries) {
|
|
12095
11542
|
if (results.length >= limit * 2) break;
|
|
12096
|
-
const fullPath =
|
|
11543
|
+
const fullPath = join12(currentDir, entry2.name);
|
|
12097
11544
|
const relativePath = relative9(baseDir, fullPath);
|
|
12098
11545
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12099
11546
|
continue;
|
|
@@ -12141,7 +11588,7 @@ sessions2.get(
|
|
|
12141
11588
|
return c.json({ error: "Session not found" }, 404);
|
|
12142
11589
|
}
|
|
12143
11590
|
const workingDirectory = session.workingDirectory;
|
|
12144
|
-
if (!
|
|
11591
|
+
if (!existsSync18(workingDirectory)) {
|
|
12145
11592
|
return c.json({
|
|
12146
11593
|
sessionId,
|
|
12147
11594
|
workingDirectory,
|
|
@@ -12254,9 +11701,9 @@ init_session_lock();
|
|
|
12254
11701
|
init_config();
|
|
12255
11702
|
import { Hono as Hono2 } from "hono";
|
|
12256
11703
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12257
|
-
import { z as
|
|
12258
|
-
import { existsSync as
|
|
12259
|
-
import { join as
|
|
11704
|
+
import { z as z17 } from "zod";
|
|
11705
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
11706
|
+
import { join as join13 } from "path";
|
|
12260
11707
|
|
|
12261
11708
|
// src/server/resumable-stream.ts
|
|
12262
11709
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -12343,7 +11790,7 @@ var streamContext = createResumableStreamContext({
|
|
|
12343
11790
|
|
|
12344
11791
|
// src/server/routes/agents.ts
|
|
12345
11792
|
init_checkpoints();
|
|
12346
|
-
import { nanoid as
|
|
11793
|
+
import { nanoid as nanoid11 } from "nanoid";
|
|
12347
11794
|
init_stream_proxy();
|
|
12348
11795
|
init_recorder();
|
|
12349
11796
|
init_remote();
|
|
@@ -12435,40 +11882,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
12435
11882
|
${prompt}`;
|
|
12436
11883
|
}
|
|
12437
11884
|
var agents = new Hono2();
|
|
12438
|
-
var attachmentSchema =
|
|
12439
|
-
type:
|
|
12440
|
-
data:
|
|
11885
|
+
var attachmentSchema = z17.object({
|
|
11886
|
+
type: z17.enum(["image", "file"]),
|
|
11887
|
+
data: z17.string(),
|
|
12441
11888
|
// base64 data URL or raw base64
|
|
12442
|
-
mediaType:
|
|
12443
|
-
filename:
|
|
11889
|
+
mediaType: z17.string().optional(),
|
|
11890
|
+
filename: z17.string().optional()
|
|
12444
11891
|
});
|
|
12445
|
-
var runPromptSchema =
|
|
12446
|
-
prompt:
|
|
11892
|
+
var runPromptSchema = z17.object({
|
|
11893
|
+
prompt: z17.string(),
|
|
12447
11894
|
// Can be empty if attachments are provided
|
|
12448
|
-
attachments:
|
|
11895
|
+
attachments: z17.array(attachmentSchema).optional()
|
|
12449
11896
|
}).refine(
|
|
12450
11897
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
12451
11898
|
{ message: "Either prompt or attachments must be provided" }
|
|
12452
11899
|
);
|
|
12453
|
-
var quickStartSchema =
|
|
12454
|
-
prompt:
|
|
12455
|
-
name:
|
|
12456
|
-
workingDirectory:
|
|
12457
|
-
model:
|
|
12458
|
-
toolApprovals:
|
|
12459
|
-
});
|
|
12460
|
-
var rejectSchema =
|
|
12461
|
-
reason:
|
|
11900
|
+
var quickStartSchema = z17.object({
|
|
11901
|
+
prompt: z17.string().min(1),
|
|
11902
|
+
name: z17.string().optional(),
|
|
11903
|
+
workingDirectory: z17.string().optional(),
|
|
11904
|
+
model: z17.string().optional(),
|
|
11905
|
+
toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
|
|
11906
|
+
});
|
|
11907
|
+
var rejectSchema = z17.object({
|
|
11908
|
+
reason: z17.string().optional()
|
|
12462
11909
|
}).optional();
|
|
12463
11910
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12464
11911
|
function getAttachmentsDirectory(sessionId) {
|
|
12465
11912
|
const appDataDir = getAppDataDirectory();
|
|
12466
|
-
return
|
|
11913
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
12467
11914
|
}
|
|
12468
11915
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12469
11916
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
12470
|
-
if (!
|
|
12471
|
-
|
|
11917
|
+
if (!existsSync19(attachmentsDir)) {
|
|
11918
|
+
mkdirSync8(attachmentsDir, { recursive: true });
|
|
12472
11919
|
}
|
|
12473
11920
|
let filename = attachment.filename;
|
|
12474
11921
|
if (!filename) {
|
|
@@ -12486,7 +11933,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12486
11933
|
attachment.mediaType = resized.mediaType;
|
|
12487
11934
|
attachment.data = buffer.toString("base64");
|
|
12488
11935
|
}
|
|
12489
|
-
const filePath =
|
|
11936
|
+
const filePath = join13(attachmentsDir, filename);
|
|
12490
11937
|
writeFileSync5(filePath, buffer);
|
|
12491
11938
|
return filePath;
|
|
12492
11939
|
}
|
|
@@ -12923,7 +12370,7 @@ ${prompt}` });
|
|
|
12923
12370
|
});
|
|
12924
12371
|
} catch {
|
|
12925
12372
|
}
|
|
12926
|
-
const streamId = `stream_${id}_${
|
|
12373
|
+
const streamId = `stream_${id}_${nanoid11(10)}`;
|
|
12927
12374
|
console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
|
|
12928
12375
|
await activeStreamQueries.create(id, streamId);
|
|
12929
12376
|
const stream = await streamContext.resumableStream(
|
|
@@ -13128,7 +12575,7 @@ agents.post(
|
|
|
13128
12575
|
});
|
|
13129
12576
|
const session = agent.getSession();
|
|
13130
12577
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
13131
|
-
const streamId = `stream_${session.id}_${
|
|
12578
|
+
const streamId = `stream_${session.id}_${nanoid11(10)}`;
|
|
13132
12579
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
13133
12580
|
await activeStreamQueries.create(session.id, streamId);
|
|
13134
12581
|
const createQuickStreamProducer = () => {
|
|
@@ -13395,23 +12842,23 @@ agents.post(
|
|
|
13395
12842
|
});
|
|
13396
12843
|
}
|
|
13397
12844
|
);
|
|
13398
|
-
var browserInputSchema =
|
|
13399
|
-
type:
|
|
13400
|
-
eventType:
|
|
13401
|
-
x:
|
|
13402
|
-
y:
|
|
13403
|
-
button:
|
|
13404
|
-
clickCount:
|
|
13405
|
-
deltaX:
|
|
13406
|
-
deltaY:
|
|
13407
|
-
key:
|
|
13408
|
-
code:
|
|
13409
|
-
text:
|
|
13410
|
-
modifiers:
|
|
13411
|
-
touchPoints:
|
|
13412
|
-
x:
|
|
13413
|
-
y:
|
|
13414
|
-
id:
|
|
12845
|
+
var browserInputSchema = z17.object({
|
|
12846
|
+
type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
12847
|
+
eventType: z17.string(),
|
|
12848
|
+
x: z17.number().optional(),
|
|
12849
|
+
y: z17.number().optional(),
|
|
12850
|
+
button: z17.string().optional(),
|
|
12851
|
+
clickCount: z17.number().optional(),
|
|
12852
|
+
deltaX: z17.number().optional(),
|
|
12853
|
+
deltaY: z17.number().optional(),
|
|
12854
|
+
key: z17.string().optional(),
|
|
12855
|
+
code: z17.string().optional(),
|
|
12856
|
+
text: z17.string().optional(),
|
|
12857
|
+
modifiers: z17.number().optional(),
|
|
12858
|
+
touchPoints: z17.array(z17.object({
|
|
12859
|
+
x: z17.number(),
|
|
12860
|
+
y: z17.number(),
|
|
12861
|
+
id: z17.number().optional()
|
|
13415
12862
|
})).optional()
|
|
13416
12863
|
});
|
|
13417
12864
|
agents.post(
|
|
@@ -13446,27 +12893,27 @@ agents.get("/:id/browser-stream", async (c) => {
|
|
|
13446
12893
|
init_config();
|
|
13447
12894
|
import { Hono as Hono3 } from "hono";
|
|
13448
12895
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
13449
|
-
import { z as
|
|
13450
|
-
import { readFileSync as
|
|
12896
|
+
import { z as z18 } from "zod";
|
|
12897
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
13451
12898
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13452
|
-
import { dirname as dirname7, join as
|
|
12899
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
13453
12900
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13454
12901
|
var __dirname = dirname7(__filename);
|
|
13455
12902
|
var possiblePaths = [
|
|
13456
|
-
|
|
12903
|
+
join14(__dirname, "../package.json"),
|
|
13457
12904
|
// From dist/server -> dist/../package.json
|
|
13458
|
-
|
|
12905
|
+
join14(__dirname, "../../package.json"),
|
|
13459
12906
|
// From dist/server (if nested differently)
|
|
13460
|
-
|
|
12907
|
+
join14(__dirname, "../../../package.json"),
|
|
13461
12908
|
// From src/server/routes (development)
|
|
13462
|
-
|
|
12909
|
+
join14(process.cwd(), "package.json")
|
|
13463
12910
|
// From current working directory
|
|
13464
12911
|
];
|
|
13465
12912
|
var currentVersion = "0.0.0";
|
|
13466
12913
|
var packageName = "sparkecoder";
|
|
13467
12914
|
for (const packageJsonPath of possiblePaths) {
|
|
13468
12915
|
try {
|
|
13469
|
-
const packageJson = JSON.parse(
|
|
12916
|
+
const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
13470
12917
|
if (packageJson.name === "sparkecoder") {
|
|
13471
12918
|
currentVersion = packageJson.version || "0.0.0";
|
|
13472
12919
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -13558,9 +13005,9 @@ health.get("/api-keys", async (c) => {
|
|
|
13558
13005
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
13559
13006
|
});
|
|
13560
13007
|
});
|
|
13561
|
-
var setApiKeySchema =
|
|
13562
|
-
provider:
|
|
13563
|
-
apiKey:
|
|
13008
|
+
var setApiKeySchema = z18.object({
|
|
13009
|
+
provider: z18.string(),
|
|
13010
|
+
apiKey: z18.string().min(1)
|
|
13564
13011
|
});
|
|
13565
13012
|
health.post(
|
|
13566
13013
|
"/api-keys",
|
|
@@ -13601,12 +13048,12 @@ init_tmux();
|
|
|
13601
13048
|
init_db();
|
|
13602
13049
|
import { Hono as Hono4 } from "hono";
|
|
13603
13050
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
13604
|
-
import { z as
|
|
13051
|
+
import { z as z19 } from "zod";
|
|
13605
13052
|
var terminals = new Hono4();
|
|
13606
|
-
var spawnSchema =
|
|
13607
|
-
command:
|
|
13608
|
-
cwd:
|
|
13609
|
-
name:
|
|
13053
|
+
var spawnSchema = z19.object({
|
|
13054
|
+
command: z19.string(),
|
|
13055
|
+
cwd: z19.string().optional(),
|
|
13056
|
+
name: z19.string().optional()
|
|
13610
13057
|
});
|
|
13611
13058
|
terminals.post(
|
|
13612
13059
|
"/:sessionId/terminals",
|
|
@@ -13687,8 +13134,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
13687
13134
|
// We don't track exit codes in tmux mode
|
|
13688
13135
|
});
|
|
13689
13136
|
});
|
|
13690
|
-
var logsQuerySchema =
|
|
13691
|
-
tail:
|
|
13137
|
+
var logsQuerySchema = z19.object({
|
|
13138
|
+
tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
13692
13139
|
});
|
|
13693
13140
|
terminals.get(
|
|
13694
13141
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -13712,8 +13159,8 @@ terminals.get(
|
|
|
13712
13159
|
});
|
|
13713
13160
|
}
|
|
13714
13161
|
);
|
|
13715
|
-
var killSchema =
|
|
13716
|
-
signal:
|
|
13162
|
+
var killSchema = z19.object({
|
|
13163
|
+
signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
13717
13164
|
});
|
|
13718
13165
|
terminals.post(
|
|
13719
13166
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -13727,8 +13174,8 @@ terminals.post(
|
|
|
13727
13174
|
return c.json({ success: true, message: "Terminal killed" });
|
|
13728
13175
|
}
|
|
13729
13176
|
);
|
|
13730
|
-
var writeSchema =
|
|
13731
|
-
input:
|
|
13177
|
+
var writeSchema = z19.object({
|
|
13178
|
+
input: z19.string()
|
|
13732
13179
|
});
|
|
13733
13180
|
terminals.post(
|
|
13734
13181
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -13915,23 +13362,23 @@ init_agent();
|
|
|
13915
13362
|
init_config();
|
|
13916
13363
|
import { Hono as Hono5 } from "hono";
|
|
13917
13364
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
13918
|
-
import { z as
|
|
13919
|
-
import { nanoid as
|
|
13365
|
+
import { z as z20 } from "zod";
|
|
13366
|
+
import { nanoid as nanoid12 } from "nanoid";
|
|
13920
13367
|
init_questions();
|
|
13921
13368
|
var tasks = new Hono5();
|
|
13922
13369
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
13923
|
-
var createTaskSchema =
|
|
13924
|
-
prompt:
|
|
13925
|
-
outputSchema:
|
|
13926
|
-
webhookUrl:
|
|
13927
|
-
model:
|
|
13928
|
-
workingDirectory:
|
|
13929
|
-
name:
|
|
13930
|
-
maxIterations:
|
|
13931
|
-
parentTaskId:
|
|
13370
|
+
var createTaskSchema = z20.object({
|
|
13371
|
+
prompt: z20.string().min(1),
|
|
13372
|
+
outputSchema: z20.record(z20.string(), z20.unknown()),
|
|
13373
|
+
webhookUrl: z20.string().url().optional(),
|
|
13374
|
+
model: z20.string().optional(),
|
|
13375
|
+
workingDirectory: z20.string().optional(),
|
|
13376
|
+
name: z20.string().optional(),
|
|
13377
|
+
maxIterations: z20.number().int().min(1).max(500).optional(),
|
|
13378
|
+
parentTaskId: z20.string().optional(),
|
|
13932
13379
|
/** When set, the spawning orchestrator's session id. Stamped on the
|
|
13933
13380
|
* worker's config so terminal events can wake the orchestrator. */
|
|
13934
|
-
orchestratorSessionId:
|
|
13381
|
+
orchestratorSessionId: z20.string().optional()
|
|
13935
13382
|
});
|
|
13936
13383
|
tasks.post(
|
|
13937
13384
|
"/",
|
|
@@ -13997,7 +13444,7 @@ tasks.post(
|
|
|
13997
13444
|
const taskId = agent.sessionId;
|
|
13998
13445
|
const abortController = new AbortController();
|
|
13999
13446
|
taskAbortControllers.set(taskId, abortController);
|
|
14000
|
-
const streamId = `stream_${taskId}_${
|
|
13447
|
+
const streamId = `stream_${taskId}_${nanoid12(10)}`;
|
|
14001
13448
|
await activeStreamQueries.create(taskId, streamId);
|
|
14002
13449
|
const taskStreamProducer = () => {
|
|
14003
13450
|
const { readable, writable } = new TransformStream();
|
|
@@ -14146,9 +13593,9 @@ tasks.post("/:id/cancel", async (c) => {
|
|
|
14146
13593
|
}
|
|
14147
13594
|
return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
|
|
14148
13595
|
});
|
|
14149
|
-
var answerQuestionSchema =
|
|
14150
|
-
answer:
|
|
14151
|
-
answeredBy:
|
|
13596
|
+
var answerQuestionSchema = z20.object({
|
|
13597
|
+
answer: z20.string().min(1),
|
|
13598
|
+
answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
|
|
14152
13599
|
});
|
|
14153
13600
|
tasks.post(
|
|
14154
13601
|
"/:id/questions/:questionId/answer",
|
|
@@ -14277,7 +13724,8 @@ slack.post("/events", async (c) => {
|
|
|
14277
13724
|
updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
|
|
14278
13725
|
return c.json({ ok: true });
|
|
14279
13726
|
}
|
|
14280
|
-
const
|
|
13727
|
+
const self = await ensureSlackSelfIdentity();
|
|
13728
|
+
const { event: inbound, dropReason } = slackEventToInboundResult(ev, { self });
|
|
14281
13729
|
if (inbound) {
|
|
14282
13730
|
const isThreadReply = ev.type === "message" && ev.channel_type !== "im" && typeof ev.thread_ts === "string" && ev.thread_ts !== ev.ts;
|
|
14283
13731
|
if (isThreadReply) {
|
|
@@ -14432,7 +13880,7 @@ init_pool();
|
|
|
14432
13880
|
init_webhook_events();
|
|
14433
13881
|
import { Hono as Hono8 } from "hono";
|
|
14434
13882
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
14435
|
-
import { z as
|
|
13883
|
+
import { z as z21 } from "zod";
|
|
14436
13884
|
var integrations = new Hono8();
|
|
14437
13885
|
var orchestratorRouter = new Hono8();
|
|
14438
13886
|
async function getOrchestratorId() {
|
|
@@ -14455,9 +13903,9 @@ orchestratorRouter.get("/", async (c) => {
|
|
|
14455
13903
|
});
|
|
14456
13904
|
orchestratorRouter.patch(
|
|
14457
13905
|
"/",
|
|
14458
|
-
zValidator6("json",
|
|
14459
|
-
name:
|
|
14460
|
-
personality:
|
|
13906
|
+
zValidator6("json", z21.object({
|
|
13907
|
+
name: z21.string().min(1).optional(),
|
|
13908
|
+
personality: z21.string().optional()
|
|
14461
13909
|
})),
|
|
14462
13910
|
async (c) => {
|
|
14463
13911
|
const id = await getOrchestratorId();
|
|
@@ -14533,15 +13981,15 @@ integrations.get("/", async (c) => {
|
|
|
14533
13981
|
}
|
|
14534
13982
|
});
|
|
14535
13983
|
});
|
|
14536
|
-
var slackConfigSchema =
|
|
14537
|
-
botToken:
|
|
14538
|
-
signingSecret:
|
|
14539
|
-
defaultOrchestratorName:
|
|
14540
|
-
allowedUsers:
|
|
14541
|
-
allowedChannels:
|
|
14542
|
-
allowDmsFromAnyone:
|
|
14543
|
-
deniedReplyEnabled:
|
|
14544
|
-
deniedReplyTemplate:
|
|
13984
|
+
var slackConfigSchema = z21.object({
|
|
13985
|
+
botToken: z21.string().optional(),
|
|
13986
|
+
signingSecret: z21.string().optional(),
|
|
13987
|
+
defaultOrchestratorName: z21.string().optional(),
|
|
13988
|
+
allowedUsers: z21.array(z21.string()).optional(),
|
|
13989
|
+
allowedChannels: z21.array(z21.string()).optional(),
|
|
13990
|
+
allowDmsFromAnyone: z21.boolean().optional(),
|
|
13991
|
+
deniedReplyEnabled: z21.boolean().optional(),
|
|
13992
|
+
deniedReplyTemplate: z21.string().optional()
|
|
14545
13993
|
});
|
|
14546
13994
|
integrations.post("/slack", zValidator6("json", slackConfigSchema), async (c) => {
|
|
14547
13995
|
const body = c.req.valid("json");
|
|
@@ -14570,11 +14018,11 @@ schedulesRouter.get("/", async (c) => {
|
|
|
14570
14018
|
});
|
|
14571
14019
|
schedulesRouter.post(
|
|
14572
14020
|
"/",
|
|
14573
|
-
zValidator6("json",
|
|
14574
|
-
name:
|
|
14575
|
-
cron:
|
|
14576
|
-
prompt:
|
|
14577
|
-
replyChannel:
|
|
14021
|
+
zValidator6("json", z21.object({
|
|
14022
|
+
name: z21.string().min(1),
|
|
14023
|
+
cron: z21.string().min(1),
|
|
14024
|
+
prompt: z21.string().min(1),
|
|
14025
|
+
replyChannel: z21.string().optional()
|
|
14578
14026
|
})),
|
|
14579
14027
|
async (c) => {
|
|
14580
14028
|
const orcId = await getOrchestratorId();
|
|
@@ -14585,12 +14033,12 @@ schedulesRouter.post(
|
|
|
14585
14033
|
);
|
|
14586
14034
|
schedulesRouter.patch(
|
|
14587
14035
|
"/:id",
|
|
14588
|
-
zValidator6("json",
|
|
14589
|
-
name:
|
|
14590
|
-
cron:
|
|
14591
|
-
prompt:
|
|
14592
|
-
enabled:
|
|
14593
|
-
replyChannel:
|
|
14036
|
+
zValidator6("json", z21.object({
|
|
14037
|
+
name: z21.string().optional(),
|
|
14038
|
+
cron: z21.string().optional(),
|
|
14039
|
+
prompt: z21.string().optional(),
|
|
14040
|
+
enabled: z21.boolean().optional(),
|
|
14041
|
+
replyChannel: z21.string().optional()
|
|
14594
14042
|
})),
|
|
14595
14043
|
async (c) => {
|
|
14596
14044
|
const orcId = await getOrchestratorId();
|
|
@@ -14618,10 +14066,10 @@ webhooksRouter.get("/", async (c) => {
|
|
|
14618
14066
|
});
|
|
14619
14067
|
webhooksRouter.post(
|
|
14620
14068
|
"/",
|
|
14621
|
-
zValidator6("json",
|
|
14622
|
-
name:
|
|
14623
|
-
wake:
|
|
14624
|
-
template:
|
|
14069
|
+
zValidator6("json", z21.object({
|
|
14070
|
+
name: z21.string().min(1),
|
|
14071
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
14072
|
+
template: z21.string().optional()
|
|
14625
14073
|
})),
|
|
14626
14074
|
async (c) => {
|
|
14627
14075
|
const orcId = await getOrchestratorId();
|
|
@@ -14632,11 +14080,11 @@ webhooksRouter.post(
|
|
|
14632
14080
|
);
|
|
14633
14081
|
webhooksRouter.patch(
|
|
14634
14082
|
"/:id",
|
|
14635
|
-
zValidator6("json",
|
|
14636
|
-
name:
|
|
14637
|
-
wake:
|
|
14638
|
-
template:
|
|
14639
|
-
rotateToken:
|
|
14083
|
+
zValidator6("json", z21.object({
|
|
14084
|
+
name: z21.string().optional(),
|
|
14085
|
+
wake: z21.enum(["now", "next"]).optional(),
|
|
14086
|
+
template: z21.string().optional(),
|
|
14087
|
+
rotateToken: z21.boolean().optional()
|
|
14640
14088
|
})),
|
|
14641
14089
|
async (c) => {
|
|
14642
14090
|
const orcId = await getOrchestratorId();
|
|
@@ -14653,22 +14101,22 @@ webhooksRouter.delete("/:id", async (c) => {
|
|
|
14653
14101
|
return c.json({ deleted: ok });
|
|
14654
14102
|
});
|
|
14655
14103
|
var mcpRouter = new Hono8();
|
|
14656
|
-
var mcpServerSchema =
|
|
14657
|
-
name:
|
|
14658
|
-
transport:
|
|
14659
|
-
url:
|
|
14660
|
-
headers:
|
|
14661
|
-
command:
|
|
14662
|
-
args:
|
|
14663
|
-
enabled:
|
|
14664
|
-
});
|
|
14665
|
-
var mcpPatchSchema =
|
|
14666
|
-
name:
|
|
14667
|
-
url:
|
|
14668
|
-
headers:
|
|
14669
|
-
command:
|
|
14670
|
-
args:
|
|
14671
|
-
enabled:
|
|
14104
|
+
var mcpServerSchema = z21.object({
|
|
14105
|
+
name: z21.string().min(1),
|
|
14106
|
+
transport: z21.enum(["http", "sse", "stdio"]),
|
|
14107
|
+
url: z21.string().optional(),
|
|
14108
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
14109
|
+
command: z21.string().optional(),
|
|
14110
|
+
args: z21.array(z21.string()).optional(),
|
|
14111
|
+
enabled: z21.boolean().optional()
|
|
14112
|
+
});
|
|
14113
|
+
var mcpPatchSchema = z21.object({
|
|
14114
|
+
name: z21.string().optional(),
|
|
14115
|
+
url: z21.string().optional(),
|
|
14116
|
+
headers: z21.record(z21.string(), z21.string()).optional(),
|
|
14117
|
+
command: z21.string().optional(),
|
|
14118
|
+
args: z21.array(z21.string()).optional(),
|
|
14119
|
+
enabled: z21.boolean().optional()
|
|
14672
14120
|
});
|
|
14673
14121
|
mcpRouter.get("/", async (c) => {
|
|
14674
14122
|
const rows = listMcpServers().map((s) => ({
|
|
@@ -14785,10 +14233,10 @@ init_config();
|
|
|
14785
14233
|
init_db();
|
|
14786
14234
|
|
|
14787
14235
|
// src/utils/dependencies.ts
|
|
14788
|
-
import { exec as
|
|
14789
|
-
import { promisify as
|
|
14236
|
+
import { exec as exec6 } from "child_process";
|
|
14237
|
+
import { promisify as promisify6 } from "util";
|
|
14790
14238
|
import { platform as platform2 } from "os";
|
|
14791
|
-
var
|
|
14239
|
+
var execAsync6 = promisify6(exec6);
|
|
14792
14240
|
function getInstallInstructions() {
|
|
14793
14241
|
const os2 = platform2();
|
|
14794
14242
|
if (os2 === "darwin") {
|
|
@@ -14821,7 +14269,7 @@ Install tmux:
|
|
|
14821
14269
|
}
|
|
14822
14270
|
async function checkTmux() {
|
|
14823
14271
|
try {
|
|
14824
|
-
const { stdout } = await
|
|
14272
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
14825
14273
|
const version = stdout.trim();
|
|
14826
14274
|
return {
|
|
14827
14275
|
available: true,
|
|
@@ -14870,11 +14318,11 @@ function getWebDirectory() {
|
|
|
14870
14318
|
try {
|
|
14871
14319
|
const currentDir = dirname8(fileURLToPath4(import.meta.url));
|
|
14872
14320
|
const webDir = resolve11(currentDir, "..", "web");
|
|
14873
|
-
if (
|
|
14321
|
+
if (existsSync20(webDir) && existsSync20(join15(webDir, "package.json"))) {
|
|
14874
14322
|
return webDir;
|
|
14875
14323
|
}
|
|
14876
14324
|
const altWebDir = resolve11(currentDir, "..", "..", "web");
|
|
14877
|
-
if (
|
|
14325
|
+
if (existsSync20(altWebDir) && existsSync20(join15(altWebDir, "package.json"))) {
|
|
14878
14326
|
return altWebDir;
|
|
14879
14327
|
}
|
|
14880
14328
|
return null;
|
|
@@ -14932,23 +14380,23 @@ async function findWebPort(preferredPort) {
|
|
|
14932
14380
|
return { port: preferredPort, alreadyRunning: false };
|
|
14933
14381
|
}
|
|
14934
14382
|
function hasProductionBuild(webDir) {
|
|
14935
|
-
const buildIdPath =
|
|
14936
|
-
return
|
|
14383
|
+
const buildIdPath = join15(webDir, ".next", "BUILD_ID");
|
|
14384
|
+
return existsSync20(buildIdPath);
|
|
14937
14385
|
}
|
|
14938
14386
|
function hasSourceFiles(webDir) {
|
|
14939
|
-
const appDir =
|
|
14940
|
-
const pagesDir =
|
|
14941
|
-
const rootAppDir =
|
|
14942
|
-
const rootPagesDir =
|
|
14943
|
-
return
|
|
14387
|
+
const appDir = join15(webDir, "src", "app");
|
|
14388
|
+
const pagesDir = join15(webDir, "src", "pages");
|
|
14389
|
+
const rootAppDir = join15(webDir, "app");
|
|
14390
|
+
const rootPagesDir = join15(webDir, "pages");
|
|
14391
|
+
return existsSync20(appDir) || existsSync20(pagesDir) || existsSync20(rootAppDir) || existsSync20(rootPagesDir);
|
|
14944
14392
|
}
|
|
14945
14393
|
function getStandaloneServerPath(webDir) {
|
|
14946
14394
|
const possiblePaths2 = [
|
|
14947
|
-
|
|
14948
|
-
|
|
14395
|
+
join15(webDir, ".next", "standalone", "server.js"),
|
|
14396
|
+
join15(webDir, ".next", "standalone", "web", "server.js")
|
|
14949
14397
|
];
|
|
14950
14398
|
for (const serverPath of possiblePaths2) {
|
|
14951
|
-
if (
|
|
14399
|
+
if (existsSync20(serverPath)) {
|
|
14952
14400
|
return serverPath;
|
|
14953
14401
|
}
|
|
14954
14402
|
}
|
|
@@ -14988,13 +14436,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
14988
14436
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
14989
14437
|
return { process: null, port: actualPort };
|
|
14990
14438
|
}
|
|
14991
|
-
const usePnpm =
|
|
14992
|
-
const useNpm = !usePnpm &&
|
|
14439
|
+
const usePnpm = existsSync20(join15(webDir, "pnpm-lock.yaml"));
|
|
14440
|
+
const useNpm = !usePnpm && existsSync20(join15(webDir, "package-lock.json"));
|
|
14993
14441
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
14994
14442
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
14995
14443
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
14996
14444
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
14997
|
-
const runtimeConfigPath =
|
|
14445
|
+
const runtimeConfigPath = join15(webDir, "runtime-config.json");
|
|
14998
14446
|
try {
|
|
14999
14447
|
writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15000
14448
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -15209,8 +14657,8 @@ async function startServer(options = {}) {
|
|
|
15209
14657
|
if (options.workingDirectory) {
|
|
15210
14658
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
15211
14659
|
}
|
|
15212
|
-
if (!
|
|
15213
|
-
|
|
14660
|
+
if (!existsSync20(config.resolvedWorkingDirectory)) {
|
|
14661
|
+
mkdirSync9(config.resolvedWorkingDirectory, { recursive: true });
|
|
15214
14662
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
15215
14663
|
}
|
|
15216
14664
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -15251,13 +14699,15 @@ async function startServer(options = {}) {
|
|
|
15251
14699
|
if (!options.quiet) console.warn(`[scheduler] start skipped: ${err.message}`);
|
|
15252
14700
|
}
|
|
15253
14701
|
const port = options.port || config.server.port;
|
|
15254
|
-
const
|
|
14702
|
+
const envHost = process.env.SPARKECODER_API_HOST || process.env.SPARKECODER_HOST;
|
|
14703
|
+
const host = envHost || options.host || config.server.host || "0.0.0.0";
|
|
14704
|
+
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";
|
|
15255
14705
|
const publicUrl = options.publicUrl || config.server.publicUrl;
|
|
15256
14706
|
const app = await createApp({ quiet: options.quiet });
|
|
15257
14707
|
if (!options.quiet) {
|
|
15258
14708
|
console.log(`
|
|
15259
14709
|
\u{1F680} SparkECoder API Server`);
|
|
15260
|
-
console.log(` \u2192
|
|
14710
|
+
console.log(` \u2192 Binding to ${host}:${port} (source: ${hostSource})`);
|
|
15261
14711
|
if (publicUrl) {
|
|
15262
14712
|
console.log(` \u2192 Public URL: ${publicUrl}`);
|
|
15263
14713
|
}
|
|
@@ -15266,10 +14716,22 @@ async function startServer(options = {}) {
|
|
|
15266
14716
|
console.log(` \u2192 OpenAPI spec: http://${host}:${port}/openapi.json
|
|
15267
14717
|
`);
|
|
15268
14718
|
}
|
|
14719
|
+
if (host === "127.0.0.1" || host === "localhost") {
|
|
14720
|
+
console.log(`[sparkecoder] \u26A0 API bound to ${host} only \u2014 not reachable from outside the machine.`);
|
|
14721
|
+
console.log(`[sparkecoder] For tunnels/reverse proxies (Modal/Fly/Docker), set --host 0.0.0.0 or SPARKECODER_API_HOST=0.0.0.0.`);
|
|
14722
|
+
}
|
|
15269
14723
|
serverInstance = serve({
|
|
15270
14724
|
fetch: app.fetch,
|
|
15271
14725
|
port,
|
|
15272
14726
|
hostname: host
|
|
14727
|
+
}, (info) => {
|
|
14728
|
+
const actual = `${info.address}:${info.port}`;
|
|
14729
|
+
const requested = `${host}:${port}`;
|
|
14730
|
+
if (info.address === "127.0.0.1" && host !== "127.0.0.1" && host !== "localhost") {
|
|
14731
|
+
console.warn(`[sparkecoder] \u2717 API listener bound to ${actual} but ${requested} was requested. External requests will be refused.`);
|
|
14732
|
+
} else {
|
|
14733
|
+
console.log(`[sparkecoder] \u2713 API listening on ${actual}`);
|
|
14734
|
+
}
|
|
15273
14735
|
});
|
|
15274
14736
|
let webPort;
|
|
15275
14737
|
let webStarted;
|