sparkecoder 0.1.116 → 0.1.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +136 -697
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +621 -1038
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-Biy5JTop.d.ts → index-Bcz0aCAR.d.ts} +104 -113
- package/dist/index.d.ts +5 -5
- package/dist/index.js +353 -935
- package/dist/index.js.map +1 -1
- package/dist/{schema-CYSKJZ3m.d.ts → schema-BWbWmfDQ.d.ts} +3 -5
- package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
- package/dist/server/index.js +353 -935
- 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 +4 -170
- 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/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_ssgManifest.js +0 -0
- /package/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{7tYFi20tEUZNGXmy2DJ1K → T8x1J_CS0n9FaWBr5GhLe}/_ssgManifest.js +0 -0
package/dist/agent/index.js
CHANGED
|
@@ -51,12 +51,6 @@ var init_types = __esm({
|
|
|
51
51
|
skillsDirectory: z.string().optional(),
|
|
52
52
|
maxContextChars: z.number().optional().default(2e5),
|
|
53
53
|
task: TaskConfigSchema.optional(),
|
|
54
|
-
// Anthropic computer use tool — opt-in. When true, the `computer` tool is
|
|
55
|
-
// included in the toolset for Anthropic models. Default false.
|
|
56
|
-
computerUseEnabled: z.boolean().optional(),
|
|
57
|
-
// Display dimensions for the computer use tool (defaults: 1280x800).
|
|
58
|
-
computerUseDisplayWidth: z.number().int().positive().optional(),
|
|
59
|
-
computerUseDisplayHeight: z.number().int().positive().optional(),
|
|
60
54
|
// 'orchestrator' = supervisor session; 'worker' = task spawned by an orchestrator.
|
|
61
55
|
role: z.enum(["orchestrator", "worker", "chat"]).optional(),
|
|
62
56
|
// Optional persona / extra system-prompt text appended to the orchestrator's
|
|
@@ -1642,17 +1636,17 @@ __export(conversation_archive_exports, {
|
|
|
1642
1636
|
getHistoryDir: () => getHistoryDir,
|
|
1643
1637
|
listSessionArchives: () => listSessionArchives
|
|
1644
1638
|
});
|
|
1645
|
-
import { existsSync as
|
|
1646
|
-
import { join as
|
|
1639
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
1640
|
+
import { join as join8 } from "path";
|
|
1647
1641
|
function getHistoryDir() {
|
|
1648
|
-
const dir =
|
|
1649
|
-
if (!
|
|
1642
|
+
const dir = join8(ensureAppDataDirectory(), "history");
|
|
1643
|
+
if (!existsSync15(dir)) mkdirSync5(dir, { recursive: true });
|
|
1650
1644
|
return dir;
|
|
1651
1645
|
}
|
|
1652
1646
|
function appendTurn(turn) {
|
|
1653
1647
|
try {
|
|
1654
1648
|
const dir = getHistoryDir();
|
|
1655
|
-
const path =
|
|
1649
|
+
const path = join8(dir, `${turn.sessionId}.jsonl`);
|
|
1656
1650
|
const line = JSON.stringify({
|
|
1657
1651
|
ts: turn.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1658
1652
|
sessionId: turn.sessionId,
|
|
@@ -1681,7 +1675,7 @@ function flattenContent(content) {
|
|
|
1681
1675
|
}
|
|
1682
1676
|
function listSessionArchives() {
|
|
1683
1677
|
const dir = getHistoryDir();
|
|
1684
|
-
if (!
|
|
1678
|
+
if (!existsSync15(dir)) return [];
|
|
1685
1679
|
return readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1686
1680
|
}
|
|
1687
1681
|
var init_conversation_archive = __esm({
|
|
@@ -1897,15 +1891,15 @@ var recorder_exports = {};
|
|
|
1897
1891
|
__export(recorder_exports, {
|
|
1898
1892
|
FrameRecorder: () => FrameRecorder
|
|
1899
1893
|
});
|
|
1900
|
-
import { exec as
|
|
1901
|
-
import { promisify as
|
|
1894
|
+
import { exec as exec5 } from "child_process";
|
|
1895
|
+
import { promisify as promisify5 } from "util";
|
|
1902
1896
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1903
|
-
import { join as
|
|
1904
|
-
import { tmpdir
|
|
1905
|
-
import { nanoid as
|
|
1897
|
+
import { join as join10 } from "path";
|
|
1898
|
+
import { tmpdir } from "os";
|
|
1899
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
1906
1900
|
async function checkFfmpeg() {
|
|
1907
1901
|
try {
|
|
1908
|
-
await
|
|
1902
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
1909
1903
|
return true;
|
|
1910
1904
|
} catch {
|
|
1911
1905
|
return false;
|
|
@@ -1917,11 +1911,11 @@ async function cleanup(dir) {
|
|
|
1917
1911
|
} catch {
|
|
1918
1912
|
}
|
|
1919
1913
|
}
|
|
1920
|
-
var
|
|
1914
|
+
var execAsync5, FrameRecorder;
|
|
1921
1915
|
var init_recorder = __esm({
|
|
1922
1916
|
"src/browser/recorder.ts"() {
|
|
1923
1917
|
"use strict";
|
|
1924
|
-
|
|
1918
|
+
execAsync5 = promisify5(exec5);
|
|
1925
1919
|
FrameRecorder = class {
|
|
1926
1920
|
frames = [];
|
|
1927
1921
|
startTime = null;
|
|
@@ -1957,21 +1951,21 @@ var init_recorder = __esm({
|
|
|
1957
1951
|
*/
|
|
1958
1952
|
async encode() {
|
|
1959
1953
|
if (this.frames.length === 0) return null;
|
|
1960
|
-
const workDir =
|
|
1954
|
+
const workDir = join10(tmpdir(), `sparkecoder-recording-${nanoid7(8)}`);
|
|
1961
1955
|
await mkdir4(workDir, { recursive: true });
|
|
1962
1956
|
try {
|
|
1963
1957
|
for (let i = 0; i < this.frames.length; i++) {
|
|
1964
|
-
const framePath =
|
|
1958
|
+
const framePath = join10(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
1965
1959
|
await writeFile5(framePath, this.frames[i].data);
|
|
1966
1960
|
}
|
|
1967
1961
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
1968
1962
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
1969
1963
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
1970
|
-
const outputPath =
|
|
1964
|
+
const outputPath = join10(workDir, `recording_${this.sessionId}.mp4`);
|
|
1971
1965
|
const hasFfmpeg = await checkFfmpeg();
|
|
1972
1966
|
if (hasFfmpeg) {
|
|
1973
|
-
await
|
|
1974
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
1967
|
+
await execAsync5(
|
|
1968
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join10(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
1975
1969
|
{ timeout: 12e4 }
|
|
1976
1970
|
);
|
|
1977
1971
|
} else {
|
|
@@ -1983,7 +1977,7 @@ var init_recorder = __esm({
|
|
|
1983
1977
|
const files = await readdir5(workDir);
|
|
1984
1978
|
for (const f of files) {
|
|
1985
1979
|
if (f.startsWith("frame_")) {
|
|
1986
|
-
await unlink2(
|
|
1980
|
+
await unlink2(join10(workDir, f)).catch(() => {
|
|
1987
1981
|
});
|
|
1988
1982
|
}
|
|
1989
1983
|
}
|
|
@@ -2008,7 +2002,7 @@ var init_recorder = __esm({
|
|
|
2008
2002
|
import {
|
|
2009
2003
|
streamText as streamText2,
|
|
2010
2004
|
generateText as generateText3,
|
|
2011
|
-
tool as
|
|
2005
|
+
tool as tool14,
|
|
2012
2006
|
stepCountIs as stepCountIs2
|
|
2013
2007
|
} from "ai";
|
|
2014
2008
|
|
|
@@ -2216,8 +2210,8 @@ var SUBAGENT_MODELS = {
|
|
|
2216
2210
|
// src/agent/index.ts
|
|
2217
2211
|
init_db();
|
|
2218
2212
|
init_config();
|
|
2219
|
-
import { z as
|
|
2220
|
-
import { nanoid as
|
|
2213
|
+
import { z as z15 } from "zod";
|
|
2214
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
2221
2215
|
|
|
2222
2216
|
// src/tools/bash.ts
|
|
2223
2217
|
import { tool } from "ai";
|
|
@@ -3100,12 +3094,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
3100
3094
|
}
|
|
3101
3095
|
async function commandExists(cmd) {
|
|
3102
3096
|
try {
|
|
3103
|
-
const { exec:
|
|
3104
|
-
const { promisify:
|
|
3105
|
-
const
|
|
3097
|
+
const { exec: exec6 } = await import("child_process");
|
|
3098
|
+
const { promisify: promisify6 } = await import("util");
|
|
3099
|
+
const execAsync6 = promisify6(exec6);
|
|
3106
3100
|
const isWindows = process.platform === "win32";
|
|
3107
3101
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
3108
|
-
await
|
|
3102
|
+
await execAsync6(checkCmd);
|
|
3109
3103
|
return true;
|
|
3110
3104
|
} catch {
|
|
3111
3105
|
return false;
|
|
@@ -5896,568 +5890,6 @@ function createUploadFileTool(options) {
|
|
|
5896
5890
|
});
|
|
5897
5891
|
}
|
|
5898
5892
|
|
|
5899
|
-
// src/tools/computer-use.ts
|
|
5900
|
-
import { anthropic } from "@ai-sdk/anthropic";
|
|
5901
|
-
import { exec as exec5 } from "child_process";
|
|
5902
|
-
import { promisify as promisify5 } from "util";
|
|
5903
|
-
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
5904
|
-
import { join as join8 } from "path";
|
|
5905
|
-
import { tmpdir } from "os";
|
|
5906
|
-
import { nanoid as nanoid4 } from "nanoid";
|
|
5907
|
-
var execAsync5 = promisify5(exec5);
|
|
5908
|
-
var DEFAULT_WIDTH = 1280;
|
|
5909
|
-
var DEFAULT_HEIGHT = 800;
|
|
5910
|
-
function isMacOs() {
|
|
5911
|
-
return process.platform === "darwin";
|
|
5912
|
-
}
|
|
5913
|
-
async function isCliclickInstalled() {
|
|
5914
|
-
try {
|
|
5915
|
-
await execAsync5("command -v cliclick", { timeout: 2e3 });
|
|
5916
|
-
return true;
|
|
5917
|
-
} catch {
|
|
5918
|
-
return false;
|
|
5919
|
-
}
|
|
5920
|
-
}
|
|
5921
|
-
async function runJxa(script) {
|
|
5922
|
-
try {
|
|
5923
|
-
const escaped = script.replace(/'/g, `'\\''`);
|
|
5924
|
-
const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
|
|
5925
|
-
timeout: 5e3
|
|
5926
|
-
});
|
|
5927
|
-
return JSON.parse(stdout.trim());
|
|
5928
|
-
} catch {
|
|
5929
|
-
return null;
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
async function hasAccessibilityPermissions() {
|
|
5933
|
-
try {
|
|
5934
|
-
const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
|
|
5935
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
5936
|
-
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
5937
|
-
}
|
|
5938
|
-
return { ok: true };
|
|
5939
|
-
} catch (err) {
|
|
5940
|
-
return { ok: false, error: err?.message || String(err) };
|
|
5941
|
-
}
|
|
5942
|
-
}
|
|
5943
|
-
async function hasScreenRecordingPermissions() {
|
|
5944
|
-
const result = await runJxa(
|
|
5945
|
-
`ObjC.import("Cocoa");
|
|
5946
|
-
ObjC.import("CoreGraphics");
|
|
5947
|
-
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
5948
|
-
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
5949
|
-
);
|
|
5950
|
-
return result?.hasAccess ?? false;
|
|
5951
|
-
}
|
|
5952
|
-
async function requestAccessibilityPrompt() {
|
|
5953
|
-
const result = await runJxa(
|
|
5954
|
-
`ObjC.import("ApplicationServices");
|
|
5955
|
-
var key = $.kAXTrustedCheckOptionPrompt;
|
|
5956
|
-
var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
|
|
5957
|
-
var trusted = $.AXIsProcessTrustedWithOptions(dict);
|
|
5958
|
-
JSON.stringify({ trusted: !!trusted });`
|
|
5959
|
-
);
|
|
5960
|
-
return result?.trusted ?? false;
|
|
5961
|
-
}
|
|
5962
|
-
async function requestScreenRecordingPrompt() {
|
|
5963
|
-
const result = await runJxa(
|
|
5964
|
-
`ObjC.import("Cocoa");
|
|
5965
|
-
ObjC.import("CoreGraphics");
|
|
5966
|
-
ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
|
|
5967
|
-
JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
|
|
5968
|
-
);
|
|
5969
|
-
return result?.granted ?? false;
|
|
5970
|
-
}
|
|
5971
|
-
async function openSystemSettings(pane) {
|
|
5972
|
-
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
5973
|
-
try {
|
|
5974
|
-
await execAsync5(`open '${url}'`, { timeout: 3e3 });
|
|
5975
|
-
} catch {
|
|
5976
|
-
}
|
|
5977
|
-
}
|
|
5978
|
-
async function detectScreenSize() {
|
|
5979
|
-
try {
|
|
5980
|
-
const { stdout } = await execAsync5(
|
|
5981
|
-
`osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
|
|
5982
|
-
{ timeout: 3e3 }
|
|
5983
|
-
);
|
|
5984
|
-
const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
|
|
5985
|
-
if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
|
|
5986
|
-
const [x1, y1, x2, y2] = parts;
|
|
5987
|
-
return { width: x2 - x1, height: y2 - y1 };
|
|
5988
|
-
}
|
|
5989
|
-
} catch {
|
|
5990
|
-
}
|
|
5991
|
-
return null;
|
|
5992
|
-
}
|
|
5993
|
-
async function runCliclick(args) {
|
|
5994
|
-
const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
5995
|
-
const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
|
|
5996
|
-
timeout: 15e3,
|
|
5997
|
-
maxBuffer: 1024 * 1024
|
|
5998
|
-
});
|
|
5999
|
-
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
6000
|
-
throw new Error(
|
|
6001
|
-
"Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
|
|
6002
|
-
);
|
|
6003
|
-
}
|
|
6004
|
-
if (stderr && !stdout) throw new Error(stderr.trim());
|
|
6005
|
-
return (stdout || "").trim();
|
|
6006
|
-
}
|
|
6007
|
-
async function runScreencapture(path) {
|
|
6008
|
-
await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
|
|
6009
|
-
timeout: 5e3
|
|
6010
|
-
});
|
|
6011
|
-
}
|
|
6012
|
-
async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
|
|
6013
|
-
const sharpModule = await import("sharp");
|
|
6014
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
6015
|
-
const meta = await sharp2(path).metadata();
|
|
6016
|
-
if (meta.width === targetWidth && meta.height === targetHeight) {
|
|
6017
|
-
return readFileSync7(path);
|
|
6018
|
-
}
|
|
6019
|
-
return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
|
|
6020
|
-
}
|
|
6021
|
-
async function runScroll(dx, dy) {
|
|
6022
|
-
const wheelY = -Math.round(dy);
|
|
6023
|
-
const wheelX = -Math.round(dx);
|
|
6024
|
-
const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
|
|
6025
|
-
await execAsync5(
|
|
6026
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
6027
|
-
{ timeout: 5e3 }
|
|
6028
|
-
);
|
|
6029
|
-
}
|
|
6030
|
-
function translateKeyForCliclick(key2) {
|
|
6031
|
-
if (!key2) return [];
|
|
6032
|
-
const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
|
|
6033
|
-
if (parts.length === 0) return [];
|
|
6034
|
-
const modMap = {
|
|
6035
|
-
ctrl: "ctrl",
|
|
6036
|
-
control: "ctrl",
|
|
6037
|
-
alt: "alt",
|
|
6038
|
-
option: "alt",
|
|
6039
|
-
shift: "shift",
|
|
6040
|
-
cmd: "cmd",
|
|
6041
|
-
super: "cmd",
|
|
6042
|
-
meta: "cmd",
|
|
6043
|
-
win: "cmd",
|
|
6044
|
-
fn: "fn"
|
|
6045
|
-
};
|
|
6046
|
-
const keyMap = {
|
|
6047
|
-
return: "enter",
|
|
6048
|
-
enter: "enter",
|
|
6049
|
-
esc: "esc",
|
|
6050
|
-
escape: "esc",
|
|
6051
|
-
backspace: "delete",
|
|
6052
|
-
back_space: "delete",
|
|
6053
|
-
delete: "fwd-delete",
|
|
6054
|
-
fwd_delete: "fwd-delete",
|
|
6055
|
-
forward_delete: "fwd-delete",
|
|
6056
|
-
tab: "tab",
|
|
6057
|
-
space: "space",
|
|
6058
|
-
up: "arrow-up",
|
|
6059
|
-
arrow_up: "arrow-up",
|
|
6060
|
-
down: "arrow-down",
|
|
6061
|
-
arrow_down: "arrow-down",
|
|
6062
|
-
left: "arrow-left",
|
|
6063
|
-
arrow_left: "arrow-left",
|
|
6064
|
-
right: "arrow-right",
|
|
6065
|
-
arrow_right: "arrow-right",
|
|
6066
|
-
page_up: "page-up",
|
|
6067
|
-
pageup: "page-up",
|
|
6068
|
-
page_down: "page-down",
|
|
6069
|
-
pagedown: "page-down",
|
|
6070
|
-
home: "home",
|
|
6071
|
-
end: "end",
|
|
6072
|
-
f1: "f1",
|
|
6073
|
-
f2: "f2",
|
|
6074
|
-
f3: "f3",
|
|
6075
|
-
f4: "f4",
|
|
6076
|
-
f5: "f5",
|
|
6077
|
-
f6: "f6",
|
|
6078
|
-
f7: "f7",
|
|
6079
|
-
f8: "f8",
|
|
6080
|
-
f9: "f9",
|
|
6081
|
-
f10: "f10",
|
|
6082
|
-
f11: "f11",
|
|
6083
|
-
f12: "f12"
|
|
6084
|
-
};
|
|
6085
|
-
const modifiers = [];
|
|
6086
|
-
let mainKey = null;
|
|
6087
|
-
for (let i = 0; i < parts.length; i++) {
|
|
6088
|
-
const lower = parts[i].toLowerCase().replace(/-/g, "_");
|
|
6089
|
-
if (i < parts.length - 1 && modMap[lower]) {
|
|
6090
|
-
modifiers.push(modMap[lower]);
|
|
6091
|
-
} else {
|
|
6092
|
-
mainKey = keyMap[lower] || lower;
|
|
6093
|
-
}
|
|
6094
|
-
}
|
|
6095
|
-
const args = [];
|
|
6096
|
-
if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
|
|
6097
|
-
if (mainKey) {
|
|
6098
|
-
const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
|
|
6099
|
-
if (isNamedKey) {
|
|
6100
|
-
args.push(`kp:${mainKey}`);
|
|
6101
|
-
} else {
|
|
6102
|
-
args.push(`t:${mainKey}`);
|
|
6103
|
-
}
|
|
6104
|
-
}
|
|
6105
|
-
if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
|
|
6106
|
-
return args;
|
|
6107
|
-
}
|
|
6108
|
-
function modifierStringToCliclick(text) {
|
|
6109
|
-
return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
|
|
6110
|
-
if (p === "ctrl" || p === "control") return "ctrl";
|
|
6111
|
-
if (p === "alt" || p === "option") return "alt";
|
|
6112
|
-
if (p === "shift") return "shift";
|
|
6113
|
-
if (p === "super" || p === "meta" || p === "cmd") return "cmd";
|
|
6114
|
-
return "";
|
|
6115
|
-
}).filter(Boolean);
|
|
6116
|
-
}
|
|
6117
|
-
function createComputerUseTool(options) {
|
|
6118
|
-
const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
|
|
6119
|
-
const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
|
|
6120
|
-
return anthropic.tools.computer_20251124({
|
|
6121
|
-
displayWidthPx: displayWidth,
|
|
6122
|
-
displayHeightPx: displayHeight,
|
|
6123
|
-
enableZoom: true,
|
|
6124
|
-
execute: async (input) => {
|
|
6125
|
-
try {
|
|
6126
|
-
switch (input.action) {
|
|
6127
|
-
case "screenshot": {
|
|
6128
|
-
const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
|
|
6129
|
-
await runScreencapture(path);
|
|
6130
|
-
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
6131
|
-
try {
|
|
6132
|
-
unlinkSync2(path);
|
|
6133
|
-
} catch {
|
|
6134
|
-
}
|
|
6135
|
-
return { type: "image", data: resized.toString("base64") };
|
|
6136
|
-
}
|
|
6137
|
-
case "left_click": {
|
|
6138
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6139
|
-
if (input.text) {
|
|
6140
|
-
const mods = modifierStringToCliclick(input.text);
|
|
6141
|
-
if (mods.length > 0) {
|
|
6142
|
-
await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
|
|
6143
|
-
} else {
|
|
6144
|
-
await runCliclick([`c:${x},${y}`]);
|
|
6145
|
-
}
|
|
6146
|
-
} else {
|
|
6147
|
-
await runCliclick([`c:${x},${y}`]);
|
|
6148
|
-
}
|
|
6149
|
-
return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
|
|
6150
|
-
}
|
|
6151
|
-
case "right_click": {
|
|
6152
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6153
|
-
await runCliclick([`rc:${x},${y}`]);
|
|
6154
|
-
return `right-clicked at (${x}, ${y})`;
|
|
6155
|
-
}
|
|
6156
|
-
case "middle_click": {
|
|
6157
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6158
|
-
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);`;
|
|
6159
|
-
await execAsync5(
|
|
6160
|
-
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
6161
|
-
{ timeout: 3e3 }
|
|
6162
|
-
);
|
|
6163
|
-
return `middle-clicked at (${x}, ${y})`;
|
|
6164
|
-
}
|
|
6165
|
-
case "double_click": {
|
|
6166
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6167
|
-
await runCliclick([`dc:${x},${y}`]);
|
|
6168
|
-
return `double-clicked at (${x}, ${y})`;
|
|
6169
|
-
}
|
|
6170
|
-
case "triple_click": {
|
|
6171
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6172
|
-
await runCliclick([`tc:${x},${y}`]);
|
|
6173
|
-
return `triple-clicked at (${x}, ${y})`;
|
|
6174
|
-
}
|
|
6175
|
-
case "mouse_move": {
|
|
6176
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6177
|
-
await runCliclick([`m:${x},${y}`]);
|
|
6178
|
-
return `moved cursor to (${x}, ${y})`;
|
|
6179
|
-
}
|
|
6180
|
-
case "left_mouse_down": {
|
|
6181
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6182
|
-
await runCliclick([`dd:${x},${y}`]);
|
|
6183
|
-
return `left mouse button pressed at (${x}, ${y})`;
|
|
6184
|
-
}
|
|
6185
|
-
case "left_mouse_up": {
|
|
6186
|
-
const [x, y] = input.coordinate ?? [0, 0];
|
|
6187
|
-
await runCliclick([`du:${x},${y}`]);
|
|
6188
|
-
return `left mouse button released at (${x}, ${y})`;
|
|
6189
|
-
}
|
|
6190
|
-
case "left_click_drag": {
|
|
6191
|
-
const [sx, sy] = input.start_coordinate ?? [0, 0];
|
|
6192
|
-
const [ex, ey] = input.coordinate ?? [0, 0];
|
|
6193
|
-
await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
|
|
6194
|
-
return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
|
|
6195
|
-
}
|
|
6196
|
-
case "type": {
|
|
6197
|
-
const text = input.text ?? "";
|
|
6198
|
-
await runCliclick([`t:${text}`]);
|
|
6199
|
-
return `typed ${text.length} character(s)`;
|
|
6200
|
-
}
|
|
6201
|
-
case "key": {
|
|
6202
|
-
const args = translateKeyForCliclick(input.text ?? "");
|
|
6203
|
-
if (args.length === 0) return "no key specified";
|
|
6204
|
-
await runCliclick(args);
|
|
6205
|
-
return `pressed ${input.text}`;
|
|
6206
|
-
}
|
|
6207
|
-
case "hold_key": {
|
|
6208
|
-
const text = (input.text ?? "").toLowerCase();
|
|
6209
|
-
const duration = input.duration ?? 1;
|
|
6210
|
-
const modMap = {
|
|
6211
|
-
ctrl: "ctrl",
|
|
6212
|
-
control: "ctrl",
|
|
6213
|
-
alt: "alt",
|
|
6214
|
-
option: "alt",
|
|
6215
|
-
shift: "shift",
|
|
6216
|
-
cmd: "cmd",
|
|
6217
|
-
super: "cmd",
|
|
6218
|
-
meta: "cmd",
|
|
6219
|
-
fn: "fn"
|
|
6220
|
-
};
|
|
6221
|
-
const cliName = modMap[text] || text;
|
|
6222
|
-
await runCliclick([`kd:${cliName}`]);
|
|
6223
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
6224
|
-
await runCliclick([`ku:${cliName}`]);
|
|
6225
|
-
return `held ${text} for ${duration}s`;
|
|
6226
|
-
}
|
|
6227
|
-
case "scroll": {
|
|
6228
|
-
const direction = input.scroll_direction ?? "down";
|
|
6229
|
-
const amount = input.scroll_amount ?? 3;
|
|
6230
|
-
const px = amount * 100;
|
|
6231
|
-
const dx = direction === "left" ? -px : direction === "right" ? px : 0;
|
|
6232
|
-
const dy = direction === "up" ? -px : direction === "down" ? px : 0;
|
|
6233
|
-
if (input.coordinate) {
|
|
6234
|
-
const [x, y] = input.coordinate;
|
|
6235
|
-
await runCliclick([`m:${x},${y}`]);
|
|
6236
|
-
}
|
|
6237
|
-
const mods = input.text ? modifierStringToCliclick(input.text) : [];
|
|
6238
|
-
if (mods.length > 0) {
|
|
6239
|
-
await runCliclick([`kd:${mods.join(",")}`]);
|
|
6240
|
-
}
|
|
6241
|
-
await runScroll(dx, dy);
|
|
6242
|
-
if (mods.length > 0) {
|
|
6243
|
-
await runCliclick([`ku:${mods.join(",")}`]);
|
|
6244
|
-
}
|
|
6245
|
-
return `scrolled ${direction} by ${amount}`;
|
|
6246
|
-
}
|
|
6247
|
-
case "wait": {
|
|
6248
|
-
const duration = input.duration ?? 1;
|
|
6249
|
-
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
6250
|
-
return `waited ${duration}s`;
|
|
6251
|
-
}
|
|
6252
|
-
case "cursor_position": {
|
|
6253
|
-
const out = await runCliclick(["p:."]);
|
|
6254
|
-
return `cursor at ${out}`;
|
|
6255
|
-
}
|
|
6256
|
-
case "zoom": {
|
|
6257
|
-
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
6258
|
-
const [x1, y1, x2, y2] = region;
|
|
6259
|
-
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
|
|
6260
|
-
await runScreencapture(tmpPath);
|
|
6261
|
-
const sharpModule = await import("sharp");
|
|
6262
|
-
const sharp2 = sharpModule.default || sharpModule;
|
|
6263
|
-
const meta = await sharp2(tmpPath).metadata();
|
|
6264
|
-
const scaleX = (meta.width || displayWidth) / displayWidth;
|
|
6265
|
-
const scaleY = (meta.height || displayHeight) / displayHeight;
|
|
6266
|
-
const px = {
|
|
6267
|
-
left: Math.max(0, Math.round(x1 * scaleX)),
|
|
6268
|
-
top: Math.max(0, Math.round(y1 * scaleY)),
|
|
6269
|
-
width: Math.max(1, Math.round((x2 - x1) * scaleX)),
|
|
6270
|
-
height: Math.max(1, Math.round((y2 - y1) * scaleY))
|
|
6271
|
-
};
|
|
6272
|
-
const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
|
|
6273
|
-
try {
|
|
6274
|
-
unlinkSync2(tmpPath);
|
|
6275
|
-
} catch {
|
|
6276
|
-
}
|
|
6277
|
-
return { type: "image", data: buf.toString("base64") };
|
|
6278
|
-
}
|
|
6279
|
-
default: {
|
|
6280
|
-
const exhaustive = input.action;
|
|
6281
|
-
return `unsupported action: ${String(exhaustive)}`;
|
|
6282
|
-
}
|
|
6283
|
-
}
|
|
6284
|
-
} catch (err) {
|
|
6285
|
-
const msg = err?.message || String(err);
|
|
6286
|
-
let hint = "";
|
|
6287
|
-
if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
|
|
6288
|
-
hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
|
|
6289
|
-
} else if (/command not found/i.test(msg)) {
|
|
6290
|
-
hint = " (Hint: install cliclick with `brew install cliclick`)";
|
|
6291
|
-
}
|
|
6292
|
-
return `Error: ${msg}${hint}`;
|
|
6293
|
-
}
|
|
6294
|
-
},
|
|
6295
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6296
|
-
toModelOutput({ output }) {
|
|
6297
|
-
if (typeof output === "string") {
|
|
6298
|
-
return { type: "content", value: [{ type: "text", text: output }] };
|
|
6299
|
-
}
|
|
6300
|
-
return {
|
|
6301
|
-
type: "content",
|
|
6302
|
-
value: [{ type: "media", data: output.data, mediaType: "image/png" }]
|
|
6303
|
-
};
|
|
6304
|
-
}
|
|
6305
|
-
});
|
|
6306
|
-
}
|
|
6307
|
-
|
|
6308
|
-
// src/tools/enable-computer-use.ts
|
|
6309
|
-
init_db();
|
|
6310
|
-
import { tool as tool13 } from "ai";
|
|
6311
|
-
import { z as z14 } from "zod";
|
|
6312
|
-
var inputSchema = z14.object({
|
|
6313
|
-
display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
|
|
6314
|
-
display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
|
|
6315
|
-
request_permissions: z14.boolean().optional().default(true).describe(
|
|
6316
|
-
"When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
|
|
6317
|
-
)
|
|
6318
|
-
});
|
|
6319
|
-
var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
6320
|
-
var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
6321
|
-
function createEnableComputerUseTool(options) {
|
|
6322
|
-
return tool13({
|
|
6323
|
-
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.",
|
|
6324
|
-
inputSchema,
|
|
6325
|
-
execute: async ({ display_width, display_height, request_permissions }) => {
|
|
6326
|
-
try {
|
|
6327
|
-
if (!isMacOs()) {
|
|
6328
|
-
return {
|
|
6329
|
-
success: false,
|
|
6330
|
-
error: "Computer use is currently only supported on macOS.",
|
|
6331
|
-
platform: process.platform
|
|
6332
|
-
};
|
|
6333
|
-
}
|
|
6334
|
-
if (!await isCliclickInstalled()) {
|
|
6335
|
-
return {
|
|
6336
|
-
success: false,
|
|
6337
|
-
error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
|
|
6338
|
-
installCommand: "brew install cliclick",
|
|
6339
|
-
fixSteps: [
|
|
6340
|
-
"In a terminal on this Mac, run: brew install cliclick",
|
|
6341
|
-
"(If Homebrew is not installed, install it first from https://brew.sh)",
|
|
6342
|
-
"Then call enable_computer_use again"
|
|
6343
|
-
]
|
|
6344
|
-
};
|
|
6345
|
-
}
|
|
6346
|
-
const acc = await hasAccessibilityPermissions();
|
|
6347
|
-
const screen = await hasScreenRecordingPermissions();
|
|
6348
|
-
const missing = [];
|
|
6349
|
-
if (!acc.ok) {
|
|
6350
|
-
let prompted = false;
|
|
6351
|
-
let panelOpened = false;
|
|
6352
|
-
if (request_permissions) {
|
|
6353
|
-
prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
|
|
6354
|
-
await openSystemSettings("accessibility").then(() => {
|
|
6355
|
-
panelOpened = true;
|
|
6356
|
-
}).catch(() => void 0);
|
|
6357
|
-
}
|
|
6358
|
-
missing.push({
|
|
6359
|
-
name: "Accessibility",
|
|
6360
|
-
reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
|
|
6361
|
-
pane: "accessibility",
|
|
6362
|
-
settingsUrl: ACCESSIBILITY_URL,
|
|
6363
|
-
fixSteps: [
|
|
6364
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
|
|
6365
|
-
"Click the + button",
|
|
6366
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6367
|
-
"Toggle the switch ON",
|
|
6368
|
-
"Restart the agent process so the new permission takes effect",
|
|
6369
|
-
"Then call enable_computer_use again"
|
|
6370
|
-
],
|
|
6371
|
-
prompted,
|
|
6372
|
-
panelOpened
|
|
6373
|
-
});
|
|
6374
|
-
}
|
|
6375
|
-
if (!screen) {
|
|
6376
|
-
let prompted = false;
|
|
6377
|
-
let panelOpened = false;
|
|
6378
|
-
if (request_permissions) {
|
|
6379
|
-
prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
|
|
6380
|
-
await openSystemSettings("screen-recording").then(() => {
|
|
6381
|
-
panelOpened = true;
|
|
6382
|
-
}).catch(() => void 0);
|
|
6383
|
-
}
|
|
6384
|
-
missing.push({
|
|
6385
|
-
name: "Screen Recording",
|
|
6386
|
-
reason: "CGPreflightScreenCaptureAccess returned false",
|
|
6387
|
-
pane: "screen-recording",
|
|
6388
|
-
settingsUrl: SCREEN_RECORDING_URL,
|
|
6389
|
-
fixSteps: [
|
|
6390
|
-
"In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
|
|
6391
|
-
"Click the + button",
|
|
6392
|
-
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6393
|
-
"Toggle the switch ON",
|
|
6394
|
-
"Restart the agent process so the new permission takes effect",
|
|
6395
|
-
"Then call enable_computer_use again"
|
|
6396
|
-
],
|
|
6397
|
-
prompted,
|
|
6398
|
-
panelOpened
|
|
6399
|
-
});
|
|
6400
|
-
}
|
|
6401
|
-
if (missing.length > 0) {
|
|
6402
|
-
return {
|
|
6403
|
-
success: false,
|
|
6404
|
-
error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
|
|
6405
|
-
missingPermissions: missing,
|
|
6406
|
-
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."
|
|
6407
|
-
};
|
|
6408
|
-
}
|
|
6409
|
-
let width = display_width;
|
|
6410
|
-
let height = display_height;
|
|
6411
|
-
let detected = null;
|
|
6412
|
-
if (width === void 0 || height === void 0) {
|
|
6413
|
-
detected = await detectScreenSize();
|
|
6414
|
-
width = width ?? detected?.width ?? 1280;
|
|
6415
|
-
height = height ?? detected?.height ?? 800;
|
|
6416
|
-
}
|
|
6417
|
-
const session = await sessionQueries.getById(options.sessionId);
|
|
6418
|
-
if (!session) {
|
|
6419
|
-
return { success: false, error: "Session not found" };
|
|
6420
|
-
}
|
|
6421
|
-
const config = session.config || {};
|
|
6422
|
-
if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
|
|
6423
|
-
return {
|
|
6424
|
-
success: true,
|
|
6425
|
-
alreadyEnabled: true,
|
|
6426
|
-
message: "Computer use was already enabled for this session.",
|
|
6427
|
-
displayWidth: width,
|
|
6428
|
-
displayHeight: height
|
|
6429
|
-
};
|
|
6430
|
-
}
|
|
6431
|
-
const updated = {
|
|
6432
|
-
...config,
|
|
6433
|
-
computerUseEnabled: true,
|
|
6434
|
-
computerUseDisplayWidth: width,
|
|
6435
|
-
computerUseDisplayHeight: height
|
|
6436
|
-
};
|
|
6437
|
-
await sessionQueries.update(options.sessionId, { config: updated });
|
|
6438
|
-
return {
|
|
6439
|
-
success: true,
|
|
6440
|
-
enabled: true,
|
|
6441
|
-
platform: "darwin",
|
|
6442
|
-
displayWidth: width,
|
|
6443
|
-
displayHeight: height,
|
|
6444
|
-
detectedScreenSize: detected || void 0,
|
|
6445
|
-
permissions: {
|
|
6446
|
-
accessibility: "granted",
|
|
6447
|
-
screenRecording: "granted"
|
|
6448
|
-
},
|
|
6449
|
-
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.`
|
|
6450
|
-
};
|
|
6451
|
-
} catch (err) {
|
|
6452
|
-
return {
|
|
6453
|
-
success: false,
|
|
6454
|
-
error: err?.message || String(err)
|
|
6455
|
-
};
|
|
6456
|
-
}
|
|
6457
|
-
}
|
|
6458
|
-
});
|
|
6459
|
-
}
|
|
6460
|
-
|
|
6461
5893
|
// src/tools/index.ts
|
|
6462
5894
|
init_semantic();
|
|
6463
5895
|
init_remote();
|
|
@@ -6505,20 +5937,6 @@ async function createTools(options) {
|
|
|
6505
5937
|
sessionId: options.sessionId
|
|
6506
5938
|
});
|
|
6507
5939
|
}
|
|
6508
|
-
if (process.platform === "darwin") {
|
|
6509
|
-
if (options.enableComputerUse) {
|
|
6510
|
-
tools.computer = createComputerUseTool({
|
|
6511
|
-
workingDirectory: options.workingDirectory,
|
|
6512
|
-
sessionId: options.sessionId,
|
|
6513
|
-
displayWidth: options.computerUseDisplayWidth,
|
|
6514
|
-
displayHeight: options.computerUseDisplayHeight
|
|
6515
|
-
});
|
|
6516
|
-
} else {
|
|
6517
|
-
tools.enable_computer_use = createEnableComputerUseTool({
|
|
6518
|
-
sessionId: options.sessionId
|
|
6519
|
-
});
|
|
6520
|
-
}
|
|
6521
|
-
}
|
|
6522
5940
|
if (options.enableSemanticSearch !== false) {
|
|
6523
5941
|
try {
|
|
6524
5942
|
if (isVectorGatewayConfigured()) {
|
|
@@ -7020,8 +6438,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
7020
6438
|
`;
|
|
7021
6439
|
}
|
|
7022
6440
|
function buildOrchestratorPromptAddendum() {
|
|
7023
|
-
const
|
|
7024
|
-
const computerUseAvailable = platform2 === "darwin";
|
|
6441
|
+
const desktopAvailable = process.platform === "darwin";
|
|
7025
6442
|
return `
|
|
7026
6443
|
## Orchestrator Mode
|
|
7027
6444
|
|
|
@@ -7120,14 +6537,14 @@ When NOT to split (keep as one worker):
|
|
|
7120
6537
|
When spawning a worker, push it toward the *cheapest tool that gets the job done*:
|
|
7121
6538
|
|
|
7122
6539
|
1. **Bash / file tools** for anything with a CLI (git, npm, brew, builds, tests, file editing, HTTP via curl, scripting).
|
|
7123
|
-
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.${
|
|
7124
|
-
3. **
|
|
6540
|
+
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 ? `
|
|
6541
|
+
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.
|
|
7125
6542
|
|
|
7126
|
-
A common anti-pattern: a worker reaches for
|
|
6543
|
+
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."*
|
|
7127
6544
|
|
|
7128
|
-
### Serialize desktop
|
|
6545
|
+
### Serialize desktop-automation tasks
|
|
7129
6546
|
|
|
7130
|
-
There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both
|
|
6547
|
+
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.
|
|
7131
6548
|
|
|
7132
6549
|
**Rule**: when spawning workers, look at each one's goal:
|
|
7133
6550
|
|
|
@@ -7148,10 +6565,30 @@ Example: *"Take a screenshot of Calculator AND run the test suite AND open Syste
|
|
|
7148
6565
|
|
|
7149
6566
|
Headless workers never interfere with desktop workers (they don't touch the screen), so they always run in parallel.
|
|
7150
6567
|
|
|
7151
|
-
When you spawn a **desktop worker**,
|
|
6568
|
+
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.` : ""}
|
|
7152
6569
|
|
|
7153
6570
|
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.
|
|
7154
6571
|
|
|
6572
|
+
### How to TALK to the user (versus how you reason internally)
|
|
6573
|
+
|
|
6574
|
+
All of the rules below \u2014 decomposition, parallel spawning, "don't invent commands", load-the-skill, etc. \u2014 are **your internal operating procedure**. They are how you decide what to do. They are NOT something to recite back to the user.
|
|
6575
|
+
|
|
6576
|
+
When replying to the user (Slack, web, or any channel), be a normal helpful assistant:
|
|
6577
|
+
|
|
6578
|
+
- Tell them *what you're doing*, not *how you're doing it internally*.
|
|
6579
|
+
- Don't quote your own prompt language ("just the goal", "read the skill", "no step-by-step from me", "decomposing into parallel workers"). The user doesn't care about your delegation pattern.
|
|
6580
|
+
- Don't narrate "handing off to a worker now" unless it's actually relevant (e.g. they asked how you work, or the work is going to take a noticeably long time).
|
|
6581
|
+
- Skip ceremony. A short acknowledgement + a brief description of the actual task + (optionally) a heads-up if there's anything they need to do or avoid is plenty.
|
|
6582
|
+
|
|
6583
|
+
| What you might be tempted to say | What you should say instead |
|
|
6584
|
+
|---|---|
|
|
6585
|
+
| *"Got it \u2014 handing it off with just the goal + 'read the skills and figure it out.' No step-by-step from me."* | *"On it. Recording a screen capture of the Weather app showing Anchorage. Don't touch the keyboard while it's running \u2014 I'll post the video when it's done."* |
|
|
6586
|
+
| *"Decomposing into two parallel workers: one headless, one desktop."* | *"Running the tests and pulling the diff at the same time. Back in ~30s."* |
|
|
6587
|
+
| *"Spawning worker \`screenshot-calc\` with goal: \u2026"* | *"Taking a screenshot of Calculator now."* |
|
|
6588
|
+
| *"I'll relay the user's instructions to the worker verbatim."* | *(say nothing \u2014 just do it)* |
|
|
6589
|
+
|
|
6590
|
+
If the user explicitly asks how you work, *then* you can explain the orchestrator/worker split. Otherwise: less is more.
|
|
6591
|
+
|
|
7155
6592
|
### How to write a worker goal (and what NOT to put in it)
|
|
7156
6593
|
|
|
7157
6594
|
You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
@@ -7159,7 +6596,7 @@ You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
|
7159
6596
|
**DO** put in the goal:
|
|
7160
6597
|
|
|
7161
6598
|
- The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
|
|
7162
|
-
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill
|
|
6599
|
+
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill desktop-automation\`).
|
|
7163
6600
|
- Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
|
|
7164
6601
|
- Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
|
|
7165
6602
|
|
|
@@ -7192,7 +6629,7 @@ Bad goal (don't do this):
|
|
|
7192
6629
|
> "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'..."
|
|
7193
6630
|
|
|
7194
6631
|
Good goal (do this):
|
|
7195
|
-
> "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill
|
|
6632
|
+
> "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."
|
|
7196
6633
|
`;
|
|
7197
6634
|
}
|
|
7198
6635
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -7428,6 +6865,7 @@ ${summaryContent}`
|
|
|
7428
6865
|
];
|
|
7429
6866
|
}
|
|
7430
6867
|
messages = repairToolPairing(messages);
|
|
6868
|
+
messages = ensureEndsWithUserOrTool(messages);
|
|
7431
6869
|
return messages;
|
|
7432
6870
|
}
|
|
7433
6871
|
// ---------------------------------------------------------------------------
|
|
@@ -7621,7 +7059,8 @@ ${summaryContent}`
|
|
|
7621
7059
|
}
|
|
7622
7060
|
}
|
|
7623
7061
|
async addResponseMessages(messages) {
|
|
7624
|
-
|
|
7062
|
+
const safe = repairToolPairing(messages);
|
|
7063
|
+
await messageQueries.addMany(this.sessionId, safe);
|
|
7625
7064
|
try {
|
|
7626
7065
|
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
7627
7066
|
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
@@ -7715,10 +7154,22 @@ function repairToolPairing(messages) {
|
|
|
7715
7154
|
}
|
|
7716
7155
|
return repaired;
|
|
7717
7156
|
}
|
|
7157
|
+
function ensureEndsWithUserOrTool(messages) {
|
|
7158
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
7159
|
+
const last = messages[messages.length - 1];
|
|
7160
|
+
if (last?.role !== "assistant") return messages;
|
|
7161
|
+
console.warn(
|
|
7162
|
+
"[context] Trailing assistant message detected \u2014 appending synthetic user turn to satisfy prefill restrictions"
|
|
7163
|
+
);
|
|
7164
|
+
return [
|
|
7165
|
+
...messages,
|
|
7166
|
+
{ role: "user", content: [{ type: "text", text: "Please continue." }] }
|
|
7167
|
+
];
|
|
7168
|
+
}
|
|
7718
7169
|
|
|
7719
7170
|
// src/tools/orchestrator-actions.ts
|
|
7720
|
-
import { tool as
|
|
7721
|
-
import { z as
|
|
7171
|
+
import { tool as tool13 } from "ai";
|
|
7172
|
+
import { z as z14 } from "zod";
|
|
7722
7173
|
|
|
7723
7174
|
// src/integrations/channels/web.ts
|
|
7724
7175
|
var webChannel = {
|
|
@@ -7913,7 +7364,7 @@ function describeConfiguredChannels() {
|
|
|
7913
7364
|
|
|
7914
7365
|
// src/orchestrator/schedules-store.ts
|
|
7915
7366
|
init_db();
|
|
7916
|
-
import { nanoid as
|
|
7367
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
7917
7368
|
async function readOrch(orchestratorSessionId) {
|
|
7918
7369
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
7919
7370
|
if (!s) return null;
|
|
@@ -7928,7 +7379,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
7928
7379
|
const data = await readOrch(orchestratorSessionId);
|
|
7929
7380
|
if (!data) throw new Error("orchestrator session not found");
|
|
7930
7381
|
const row = {
|
|
7931
|
-
id: `sch_${
|
|
7382
|
+
id: `sch_${nanoid4(10)}`,
|
|
7932
7383
|
name: input.name,
|
|
7933
7384
|
cron: input.cron,
|
|
7934
7385
|
prompt: input.prompt,
|
|
@@ -7963,7 +7414,7 @@ init_config();
|
|
|
7963
7414
|
// src/orchestrator/webhooks-store.ts
|
|
7964
7415
|
init_db();
|
|
7965
7416
|
import { randomBytes } from "crypto";
|
|
7966
|
-
import { nanoid as
|
|
7417
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
7967
7418
|
function newToken() {
|
|
7968
7419
|
return randomBytes(24).toString("base64url");
|
|
7969
7420
|
}
|
|
@@ -7980,7 +7431,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
7980
7431
|
const data = await readOrch2(orchestratorSessionId);
|
|
7981
7432
|
if (!data) throw new Error("orchestrator session not found");
|
|
7982
7433
|
const row = {
|
|
7983
|
-
id: `whk_${
|
|
7434
|
+
id: `whk_${nanoid5(10)}`,
|
|
7984
7435
|
name: input.name,
|
|
7985
7436
|
token: newToken(),
|
|
7986
7437
|
wake: input.wake ?? "now",
|
|
@@ -8038,33 +7489,33 @@ function previewMessageContent(content) {
|
|
|
8038
7489
|
}
|
|
8039
7490
|
return "";
|
|
8040
7491
|
}
|
|
8041
|
-
var AGENT_STATUS_ENUM =
|
|
8042
|
-
var agentInputSchema =
|
|
8043
|
-
action:
|
|
7492
|
+
var AGENT_STATUS_ENUM = z14.enum(["running", "needs_attention", "completed", "failed", "idle"]);
|
|
7493
|
+
var agentInputSchema = z14.object({
|
|
7494
|
+
action: z14.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
|
|
8044
7495
|
// list
|
|
8045
7496
|
status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
|
|
8046
|
-
limit:
|
|
7497
|
+
limit: z14.number().int().min(1).max(100).optional().describe("list only: max rows."),
|
|
8047
7498
|
// get / message / answer_question / stop
|
|
8048
|
-
id:
|
|
8049
|
-
recentMessages:
|
|
7499
|
+
id: z14.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
|
|
7500
|
+
recentMessages: z14.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
|
|
8050
7501
|
// spawn
|
|
8051
|
-
name:
|
|
8052
|
-
goal:
|
|
8053
|
-
outputSchema:
|
|
7502
|
+
name: z14.string().optional().describe("spawn only: short human-readable label."),
|
|
7503
|
+
goal: z14.string().optional().describe("spawn only: the worker's self-contained instruction."),
|
|
7504
|
+
outputSchema: z14.record(z14.string(), z14.unknown()).optional().describe(
|
|
8054
7505
|
'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
|
|
8055
7506
|
),
|
|
8056
|
-
model:
|
|
8057
|
-
workingDirectory:
|
|
8058
|
-
maxIterations:
|
|
7507
|
+
model: z14.string().optional().describe("spawn only: model override."),
|
|
7508
|
+
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
7509
|
+
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
8059
7510
|
// message
|
|
8060
|
-
text:
|
|
8061
|
-
force:
|
|
7511
|
+
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
7512
|
+
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
8062
7513
|
// answer_question
|
|
8063
|
-
questionId:
|
|
8064
|
-
answer:
|
|
7514
|
+
questionId: z14.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
|
|
7515
|
+
answer: z14.string().optional().describe("answer_question only: your answer.")
|
|
8065
7516
|
});
|
|
8066
7517
|
function buildAgentTool(opts) {
|
|
8067
|
-
return
|
|
7518
|
+
return tool13({
|
|
8068
7519
|
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).",
|
|
8069
7520
|
inputSchema: agentInputSchema,
|
|
8070
7521
|
execute: async (input) => {
|
|
@@ -8166,17 +7617,17 @@ function buildAgentTool(opts) {
|
|
|
8166
7617
|
}
|
|
8167
7618
|
});
|
|
8168
7619
|
}
|
|
8169
|
-
var messengerInputSchema =
|
|
8170
|
-
action:
|
|
7620
|
+
var messengerInputSchema = z14.object({
|
|
7621
|
+
action: z14.enum(["list_channels", "post"]),
|
|
8171
7622
|
// post
|
|
8172
|
-
channel:
|
|
8173
|
-
to:
|
|
8174
|
-
text:
|
|
8175
|
-
threadTs:
|
|
8176
|
-
subject:
|
|
7623
|
+
channel: z14.string().optional().describe('post only: channel id (e.g. "slack").'),
|
|
7624
|
+
to: z14.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
|
|
7625
|
+
text: z14.string().optional().describe("post only: message body."),
|
|
7626
|
+
threadTs: z14.string().optional().describe("post + slack: reply in this thread."),
|
|
7627
|
+
subject: z14.string().optional().describe("post + email: subject (future).")
|
|
8177
7628
|
});
|
|
8178
7629
|
function buildMessengerTool() {
|
|
8179
|
-
return
|
|
7630
|
+
return tool13({
|
|
8180
7631
|
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.",
|
|
8181
7632
|
inputSchema: messengerInputSchema,
|
|
8182
7633
|
execute: async (input) => {
|
|
@@ -8197,19 +7648,19 @@ function buildMessengerTool() {
|
|
|
8197
7648
|
}
|
|
8198
7649
|
});
|
|
8199
7650
|
}
|
|
8200
|
-
var scheduleInputSchema =
|
|
8201
|
-
action:
|
|
7651
|
+
var scheduleInputSchema = z14.object({
|
|
7652
|
+
action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
8202
7653
|
// create / update
|
|
8203
|
-
name:
|
|
8204
|
-
cron:
|
|
8205
|
-
prompt:
|
|
8206
|
-
replyChannel:
|
|
7654
|
+
name: z14.string().optional().describe("create | update"),
|
|
7655
|
+
cron: z14.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
|
|
7656
|
+
prompt: z14.string().optional().describe("create | update: the prompt injected when the schedule fires."),
|
|
7657
|
+
replyChannel: z14.string().optional().describe("create | update: default channel id for orchestrator replies."),
|
|
8207
7658
|
// update / delete / pause / resume
|
|
8208
|
-
id:
|
|
8209
|
-
enabled:
|
|
7659
|
+
id: z14.string().optional().describe("update | delete | pause | resume: schedule id."),
|
|
7660
|
+
enabled: z14.boolean().optional().describe("update only.")
|
|
8210
7661
|
});
|
|
8211
7662
|
function buildScheduleTool(opts) {
|
|
8212
|
-
return
|
|
7663
|
+
return tool13({
|
|
8213
7664
|
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.",
|
|
8214
7665
|
inputSchema: scheduleInputSchema,
|
|
8215
7666
|
execute: async (input) => {
|
|
@@ -8245,13 +7696,13 @@ function buildScheduleTool(opts) {
|
|
|
8245
7696
|
}
|
|
8246
7697
|
});
|
|
8247
7698
|
}
|
|
8248
|
-
var webhookInputSchema =
|
|
8249
|
-
action:
|
|
8250
|
-
name:
|
|
8251
|
-
wake:
|
|
8252
|
-
template:
|
|
8253
|
-
id:
|
|
8254
|
-
rotateToken:
|
|
7699
|
+
var webhookInputSchema = z14.object({
|
|
7700
|
+
action: z14.enum(["create", "list", "update", "delete"]),
|
|
7701
|
+
name: z14.string().optional().describe("create | update."),
|
|
7702
|
+
wake: z14.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
|
|
7703
|
+
template: z14.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
|
|
7704
|
+
id: z14.string().optional().describe("update | delete: webhook id."),
|
|
7705
|
+
rotateToken: z14.boolean().optional().describe("update only: regenerate the URL token.")
|
|
8255
7706
|
});
|
|
8256
7707
|
function buildWebhookUrl(opts, token) {
|
|
8257
7708
|
const base = opts.publicBaseUrl?.replace(/\/$/, "") || opts.baseUrl.replace(/\/$/, "");
|
|
@@ -8260,7 +7711,7 @@ function buildWebhookUrl(opts, token) {
|
|
|
8260
7711
|
return `${base}${webhookPrefix}/inbox/${token}`;
|
|
8261
7712
|
}
|
|
8262
7713
|
function buildWebhookTool(opts) {
|
|
8263
|
-
return
|
|
7714
|
+
return tool13({
|
|
8264
7715
|
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.",
|
|
8265
7716
|
inputSchema: webhookInputSchema,
|
|
8266
7717
|
execute: async (input) => {
|
|
@@ -8307,9 +7758,9 @@ import { createMCPClient } from "@ai-sdk/mcp";
|
|
|
8307
7758
|
|
|
8308
7759
|
// src/integrations/mcp/store.ts
|
|
8309
7760
|
init_config();
|
|
8310
|
-
import { nanoid as
|
|
8311
|
-
import { existsSync as
|
|
8312
|
-
import { resolve as resolve10, join as
|
|
7761
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
7762
|
+
import { existsSync as existsSync16, readFileSync as readFileSync7 } from "fs";
|
|
7763
|
+
import { resolve as resolve10, join as join9 } from "path";
|
|
8313
7764
|
function readServers() {
|
|
8314
7765
|
try {
|
|
8315
7766
|
const cfg = getConfig();
|
|
@@ -8321,12 +7772,12 @@ function readServers() {
|
|
|
8321
7772
|
function refreshMcpServersFromDisk() {
|
|
8322
7773
|
const candidates = [
|
|
8323
7774
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8324
|
-
|
|
7775
|
+
join9(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8325
7776
|
];
|
|
8326
7777
|
for (const path of candidates) {
|
|
8327
|
-
if (!
|
|
7778
|
+
if (!existsSync16(path)) continue;
|
|
8328
7779
|
try {
|
|
8329
|
-
const raw = JSON.parse(
|
|
7780
|
+
const raw = JSON.parse(readFileSync7(path, "utf-8"));
|
|
8330
7781
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8331
7782
|
setMcpServers(servers2);
|
|
8332
7783
|
return servers2;
|
|
@@ -8595,14 +8046,10 @@ var Agent = class _Agent {
|
|
|
8595
8046
|
*/
|
|
8596
8047
|
async createToolsWithCallbacks(options) {
|
|
8597
8048
|
const config = getConfig();
|
|
8598
|
-
const sessionConfig = this.session.config || {};
|
|
8599
8049
|
const tools = await createTools({
|
|
8600
8050
|
sessionId: this.session.id,
|
|
8601
8051
|
workingDirectory: this.session.workingDirectory,
|
|
8602
8052
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
8603
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
8604
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
8605
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
8606
8053
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
8607
8054
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
8608
8055
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -8652,14 +8099,10 @@ var Agent = class _Agent {
|
|
|
8652
8099
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
8653
8100
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
8654
8101
|
});
|
|
8655
|
-
const sessionConfig = session.config || {};
|
|
8656
8102
|
const tools = await createTools({
|
|
8657
8103
|
sessionId: session.id,
|
|
8658
8104
|
workingDirectory: session.workingDirectory,
|
|
8659
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
8660
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
8661
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
8662
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
8105
|
+
skillsDirectories: config.resolvedSkillsDirectories
|
|
8663
8106
|
});
|
|
8664
8107
|
if (session.config?.role === "orchestrator") {
|
|
8665
8108
|
const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
|
|
@@ -8897,14 +8340,10 @@ ${personality.trim()}`;
|
|
|
8897
8340
|
});
|
|
8898
8341
|
}
|
|
8899
8342
|
};
|
|
8900
|
-
const taskSessionConfig = this.session.config || {};
|
|
8901
8343
|
const taskTools = await createTools({
|
|
8902
8344
|
sessionId: this.session.id,
|
|
8903
8345
|
workingDirectory: this.session.workingDirectory,
|
|
8904
8346
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
8905
|
-
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
8906
|
-
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
8907
|
-
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
8908
8347
|
onBashProgress: bashProgressHandler,
|
|
8909
8348
|
onWriteFileProgress: (progress) => {
|
|
8910
8349
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -9252,11 +8691,11 @@ ${p.text}` : p.text;
|
|
|
9252
8691
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
9253
8692
|
if (!isRemoteConfigured2()) return [];
|
|
9254
8693
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
9255
|
-
const { join:
|
|
8694
|
+
const { join: join11, basename: basename5 } = await import("path");
|
|
9256
8695
|
const urls = [];
|
|
9257
8696
|
for (const filePath of filePaths) {
|
|
9258
8697
|
try {
|
|
9259
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
8698
|
+
const fullPath = filePath.startsWith("/") ? filePath : join11(this.session.workingDirectory, filePath);
|
|
9260
8699
|
const fileName = basename5(fullPath);
|
|
9261
8700
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
9262
8701
|
const mimeMap = {
|
|
@@ -9314,11 +8753,11 @@ ${p.text}` : p.text;
|
|
|
9314
8753
|
wrappedTools[name] = originalTool;
|
|
9315
8754
|
continue;
|
|
9316
8755
|
}
|
|
9317
|
-
wrappedTools[name] =
|
|
8756
|
+
wrappedTools[name] = tool14({
|
|
9318
8757
|
description: originalTool.description || "",
|
|
9319
|
-
inputSchema: originalTool.inputSchema ||
|
|
8758
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
9320
8759
|
execute: async (input, toolOptions) => {
|
|
9321
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
8760
|
+
const toolCallId = toolOptions.toolCallId || nanoid8();
|
|
9322
8761
|
const execution = toolExecutionQueries.create({
|
|
9323
8762
|
sessionId: this.session.id,
|
|
9324
8763
|
toolName: name,
|
|
@@ -9336,10 +8775,10 @@ ${p.text}` : p.text;
|
|
|
9336
8775
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
9337
8776
|
approvalResolvers.delete(toolCallId);
|
|
9338
8777
|
this.pendingApprovals.delete(toolCallId);
|
|
9339
|
-
const
|
|
8778
|
+
const exec6 = await execution;
|
|
9340
8779
|
if (!approved) {
|
|
9341
8780
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
9342
|
-
await toolExecutionQueries.reject(
|
|
8781
|
+
await toolExecutionQueries.reject(exec6.id);
|
|
9343
8782
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
9344
8783
|
return {
|
|
9345
8784
|
status: "rejected",
|
|
@@ -9349,14 +8788,14 @@ ${p.text}` : p.text;
|
|
|
9349
8788
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
9350
8789
|
};
|
|
9351
8790
|
}
|
|
9352
|
-
await toolExecutionQueries.approve(
|
|
8791
|
+
await toolExecutionQueries.approve(exec6.id);
|
|
9353
8792
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
9354
8793
|
try {
|
|
9355
8794
|
const result = await originalTool.execute(input, toolOptions);
|
|
9356
|
-
await toolExecutionQueries.complete(
|
|
8795
|
+
await toolExecutionQueries.complete(exec6.id, result);
|
|
9357
8796
|
return result;
|
|
9358
8797
|
} catch (error) {
|
|
9359
|
-
await toolExecutionQueries.complete(
|
|
8798
|
+
await toolExecutionQueries.complete(exec6.id, null, error.message);
|
|
9360
8799
|
throw error;
|
|
9361
8800
|
}
|
|
9362
8801
|
}
|