sparkecoder 0.1.117 → 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 +2 -2
- package/dist/agent/index.js +116 -697
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +566 -1033
- 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 +333 -935
- package/dist/index.js.map +1 -1
- package/dist/{schema-ecQSnCMz.d.ts → schema-BWbWmfDQ.d.ts} +0 -2
- package/dist/server/index.js +333 -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 +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 → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vLqK4jK7EKdLCpQ-D6-qL → T8x1J_CS0n9FaWBr5GhLe}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → static/T8x1J_CS0n9FaWBr5GhLe}/_ssgManifest.js +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → T8x1J_CS0n9FaWBr5GhLe}/_buildManifest.js +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → T8x1J_CS0n9FaWBr5GhLe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{vLqK4jK7EKdLCpQ-D6-qL → 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,7 +6565,7 @@ 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
|
|
|
@@ -7179,7 +6596,7 @@ You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
|
7179
6596
|
**DO** put in the goal:
|
|
7180
6597
|
|
|
7181
6598
|
- The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
|
|
7182
|
-
- 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\`).
|
|
7183
6600
|
- Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
|
|
7184
6601
|
- Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
|
|
7185
6602
|
|
|
@@ -7212,7 +6629,7 @@ Bad goal (don't do this):
|
|
|
7212
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'..."
|
|
7213
6630
|
|
|
7214
6631
|
Good goal (do this):
|
|
7215
|
-
> "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."
|
|
7216
6633
|
`;
|
|
7217
6634
|
}
|
|
7218
6635
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -7448,6 +6865,7 @@ ${summaryContent}`
|
|
|
7448
6865
|
];
|
|
7449
6866
|
}
|
|
7450
6867
|
messages = repairToolPairing(messages);
|
|
6868
|
+
messages = ensureEndsWithUserOrTool(messages);
|
|
7451
6869
|
return messages;
|
|
7452
6870
|
}
|
|
7453
6871
|
// ---------------------------------------------------------------------------
|
|
@@ -7641,7 +7059,8 @@ ${summaryContent}`
|
|
|
7641
7059
|
}
|
|
7642
7060
|
}
|
|
7643
7061
|
async addResponseMessages(messages) {
|
|
7644
|
-
|
|
7062
|
+
const safe = repairToolPairing(messages);
|
|
7063
|
+
await messageQueries.addMany(this.sessionId, safe);
|
|
7645
7064
|
try {
|
|
7646
7065
|
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
7647
7066
|
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
@@ -7735,10 +7154,22 @@ function repairToolPairing(messages) {
|
|
|
7735
7154
|
}
|
|
7736
7155
|
return repaired;
|
|
7737
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
|
+
}
|
|
7738
7169
|
|
|
7739
7170
|
// src/tools/orchestrator-actions.ts
|
|
7740
|
-
import { tool as
|
|
7741
|
-
import { z as
|
|
7171
|
+
import { tool as tool13 } from "ai";
|
|
7172
|
+
import { z as z14 } from "zod";
|
|
7742
7173
|
|
|
7743
7174
|
// src/integrations/channels/web.ts
|
|
7744
7175
|
var webChannel = {
|
|
@@ -7933,7 +7364,7 @@ function describeConfiguredChannels() {
|
|
|
7933
7364
|
|
|
7934
7365
|
// src/orchestrator/schedules-store.ts
|
|
7935
7366
|
init_db();
|
|
7936
|
-
import { nanoid as
|
|
7367
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
7937
7368
|
async function readOrch(orchestratorSessionId) {
|
|
7938
7369
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
7939
7370
|
if (!s) return null;
|
|
@@ -7948,7 +7379,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
7948
7379
|
const data = await readOrch(orchestratorSessionId);
|
|
7949
7380
|
if (!data) throw new Error("orchestrator session not found");
|
|
7950
7381
|
const row = {
|
|
7951
|
-
id: `sch_${
|
|
7382
|
+
id: `sch_${nanoid4(10)}`,
|
|
7952
7383
|
name: input.name,
|
|
7953
7384
|
cron: input.cron,
|
|
7954
7385
|
prompt: input.prompt,
|
|
@@ -7983,7 +7414,7 @@ init_config();
|
|
|
7983
7414
|
// src/orchestrator/webhooks-store.ts
|
|
7984
7415
|
init_db();
|
|
7985
7416
|
import { randomBytes } from "crypto";
|
|
7986
|
-
import { nanoid as
|
|
7417
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
7987
7418
|
function newToken() {
|
|
7988
7419
|
return randomBytes(24).toString("base64url");
|
|
7989
7420
|
}
|
|
@@ -8000,7 +7431,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
8000
7431
|
const data = await readOrch2(orchestratorSessionId);
|
|
8001
7432
|
if (!data) throw new Error("orchestrator session not found");
|
|
8002
7433
|
const row = {
|
|
8003
|
-
id: `whk_${
|
|
7434
|
+
id: `whk_${nanoid5(10)}`,
|
|
8004
7435
|
name: input.name,
|
|
8005
7436
|
token: newToken(),
|
|
8006
7437
|
wake: input.wake ?? "now",
|
|
@@ -8058,33 +7489,33 @@ function previewMessageContent(content) {
|
|
|
8058
7489
|
}
|
|
8059
7490
|
return "";
|
|
8060
7491
|
}
|
|
8061
|
-
var AGENT_STATUS_ENUM =
|
|
8062
|
-
var agentInputSchema =
|
|
8063
|
-
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."),
|
|
8064
7495
|
// list
|
|
8065
7496
|
status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
|
|
8066
|
-
limit:
|
|
7497
|
+
limit: z14.number().int().min(1).max(100).optional().describe("list only: max rows."),
|
|
8067
7498
|
// get / message / answer_question / stop
|
|
8068
|
-
id:
|
|
8069
|
-
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."),
|
|
8070
7501
|
// spawn
|
|
8071
|
-
name:
|
|
8072
|
-
goal:
|
|
8073
|
-
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(
|
|
8074
7505
|
'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
|
|
8075
7506
|
),
|
|
8076
|
-
model:
|
|
8077
|
-
workingDirectory:
|
|
8078
|
-
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."),
|
|
8079
7510
|
// message
|
|
8080
|
-
text:
|
|
8081
|
-
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."),
|
|
8082
7513
|
// answer_question
|
|
8083
|
-
questionId:
|
|
8084
|
-
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.")
|
|
8085
7516
|
});
|
|
8086
7517
|
function buildAgentTool(opts) {
|
|
8087
|
-
return
|
|
7518
|
+
return tool13({
|
|
8088
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).",
|
|
8089
7520
|
inputSchema: agentInputSchema,
|
|
8090
7521
|
execute: async (input) => {
|
|
@@ -8186,17 +7617,17 @@ function buildAgentTool(opts) {
|
|
|
8186
7617
|
}
|
|
8187
7618
|
});
|
|
8188
7619
|
}
|
|
8189
|
-
var messengerInputSchema =
|
|
8190
|
-
action:
|
|
7620
|
+
var messengerInputSchema = z14.object({
|
|
7621
|
+
action: z14.enum(["list_channels", "post"]),
|
|
8191
7622
|
// post
|
|
8192
|
-
channel:
|
|
8193
|
-
to:
|
|
8194
|
-
text:
|
|
8195
|
-
threadTs:
|
|
8196
|
-
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).")
|
|
8197
7628
|
});
|
|
8198
7629
|
function buildMessengerTool() {
|
|
8199
|
-
return
|
|
7630
|
+
return tool13({
|
|
8200
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.",
|
|
8201
7632
|
inputSchema: messengerInputSchema,
|
|
8202
7633
|
execute: async (input) => {
|
|
@@ -8217,19 +7648,19 @@ function buildMessengerTool() {
|
|
|
8217
7648
|
}
|
|
8218
7649
|
});
|
|
8219
7650
|
}
|
|
8220
|
-
var scheduleInputSchema =
|
|
8221
|
-
action:
|
|
7651
|
+
var scheduleInputSchema = z14.object({
|
|
7652
|
+
action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
8222
7653
|
// create / update
|
|
8223
|
-
name:
|
|
8224
|
-
cron:
|
|
8225
|
-
prompt:
|
|
8226
|
-
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."),
|
|
8227
7658
|
// update / delete / pause / resume
|
|
8228
|
-
id:
|
|
8229
|
-
enabled:
|
|
7659
|
+
id: z14.string().optional().describe("update | delete | pause | resume: schedule id."),
|
|
7660
|
+
enabled: z14.boolean().optional().describe("update only.")
|
|
8230
7661
|
});
|
|
8231
7662
|
function buildScheduleTool(opts) {
|
|
8232
|
-
return
|
|
7663
|
+
return tool13({
|
|
8233
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.",
|
|
8234
7665
|
inputSchema: scheduleInputSchema,
|
|
8235
7666
|
execute: async (input) => {
|
|
@@ -8265,13 +7696,13 @@ function buildScheduleTool(opts) {
|
|
|
8265
7696
|
}
|
|
8266
7697
|
});
|
|
8267
7698
|
}
|
|
8268
|
-
var webhookInputSchema =
|
|
8269
|
-
action:
|
|
8270
|
-
name:
|
|
8271
|
-
wake:
|
|
8272
|
-
template:
|
|
8273
|
-
id:
|
|
8274
|
-
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.")
|
|
8275
7706
|
});
|
|
8276
7707
|
function buildWebhookUrl(opts, token) {
|
|
8277
7708
|
const base = opts.publicBaseUrl?.replace(/\/$/, "") || opts.baseUrl.replace(/\/$/, "");
|
|
@@ -8280,7 +7711,7 @@ function buildWebhookUrl(opts, token) {
|
|
|
8280
7711
|
return `${base}${webhookPrefix}/inbox/${token}`;
|
|
8281
7712
|
}
|
|
8282
7713
|
function buildWebhookTool(opts) {
|
|
8283
|
-
return
|
|
7714
|
+
return tool13({
|
|
8284
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.",
|
|
8285
7716
|
inputSchema: webhookInputSchema,
|
|
8286
7717
|
execute: async (input) => {
|
|
@@ -8327,9 +7758,9 @@ import { createMCPClient } from "@ai-sdk/mcp";
|
|
|
8327
7758
|
|
|
8328
7759
|
// src/integrations/mcp/store.ts
|
|
8329
7760
|
init_config();
|
|
8330
|
-
import { nanoid as
|
|
8331
|
-
import { existsSync as
|
|
8332
|
-
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";
|
|
8333
7764
|
function readServers() {
|
|
8334
7765
|
try {
|
|
8335
7766
|
const cfg = getConfig();
|
|
@@ -8341,12 +7772,12 @@ function readServers() {
|
|
|
8341
7772
|
function refreshMcpServersFromDisk() {
|
|
8342
7773
|
const candidates = [
|
|
8343
7774
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8344
|
-
|
|
7775
|
+
join9(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8345
7776
|
];
|
|
8346
7777
|
for (const path of candidates) {
|
|
8347
|
-
if (!
|
|
7778
|
+
if (!existsSync16(path)) continue;
|
|
8348
7779
|
try {
|
|
8349
|
-
const raw = JSON.parse(
|
|
7780
|
+
const raw = JSON.parse(readFileSync7(path, "utf-8"));
|
|
8350
7781
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8351
7782
|
setMcpServers(servers2);
|
|
8352
7783
|
return servers2;
|
|
@@ -8615,14 +8046,10 @@ var Agent = class _Agent {
|
|
|
8615
8046
|
*/
|
|
8616
8047
|
async createToolsWithCallbacks(options) {
|
|
8617
8048
|
const config = getConfig();
|
|
8618
|
-
const sessionConfig = this.session.config || {};
|
|
8619
8049
|
const tools = await createTools({
|
|
8620
8050
|
sessionId: this.session.id,
|
|
8621
8051
|
workingDirectory: this.session.workingDirectory,
|
|
8622
8052
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
8623
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
8624
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
8625
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
8626
8053
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
8627
8054
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
8628
8055
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -8672,14 +8099,10 @@ var Agent = class _Agent {
|
|
|
8672
8099
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
8673
8100
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
8674
8101
|
});
|
|
8675
|
-
const sessionConfig = session.config || {};
|
|
8676
8102
|
const tools = await createTools({
|
|
8677
8103
|
sessionId: session.id,
|
|
8678
8104
|
workingDirectory: session.workingDirectory,
|
|
8679
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
8680
|
-
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
8681
|
-
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
8682
|
-
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
8105
|
+
skillsDirectories: config.resolvedSkillsDirectories
|
|
8683
8106
|
});
|
|
8684
8107
|
if (session.config?.role === "orchestrator") {
|
|
8685
8108
|
const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
|
|
@@ -8917,14 +8340,10 @@ ${personality.trim()}`;
|
|
|
8917
8340
|
});
|
|
8918
8341
|
}
|
|
8919
8342
|
};
|
|
8920
|
-
const taskSessionConfig = this.session.config || {};
|
|
8921
8343
|
const taskTools = await createTools({
|
|
8922
8344
|
sessionId: this.session.id,
|
|
8923
8345
|
workingDirectory: this.session.workingDirectory,
|
|
8924
8346
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
8925
|
-
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
8926
|
-
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
8927
|
-
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
8928
8347
|
onBashProgress: bashProgressHandler,
|
|
8929
8348
|
onWriteFileProgress: (progress) => {
|
|
8930
8349
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -9272,11 +8691,11 @@ ${p.text}` : p.text;
|
|
|
9272
8691
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
9273
8692
|
if (!isRemoteConfigured2()) return [];
|
|
9274
8693
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
9275
|
-
const { join:
|
|
8694
|
+
const { join: join11, basename: basename5 } = await import("path");
|
|
9276
8695
|
const urls = [];
|
|
9277
8696
|
for (const filePath of filePaths) {
|
|
9278
8697
|
try {
|
|
9279
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
8698
|
+
const fullPath = filePath.startsWith("/") ? filePath : join11(this.session.workingDirectory, filePath);
|
|
9280
8699
|
const fileName = basename5(fullPath);
|
|
9281
8700
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
9282
8701
|
const mimeMap = {
|
|
@@ -9334,11 +8753,11 @@ ${p.text}` : p.text;
|
|
|
9334
8753
|
wrappedTools[name] = originalTool;
|
|
9335
8754
|
continue;
|
|
9336
8755
|
}
|
|
9337
|
-
wrappedTools[name] =
|
|
8756
|
+
wrappedTools[name] = tool14({
|
|
9338
8757
|
description: originalTool.description || "",
|
|
9339
|
-
inputSchema: originalTool.inputSchema ||
|
|
8758
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
9340
8759
|
execute: async (input, toolOptions) => {
|
|
9341
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
8760
|
+
const toolCallId = toolOptions.toolCallId || nanoid8();
|
|
9342
8761
|
const execution = toolExecutionQueries.create({
|
|
9343
8762
|
sessionId: this.session.id,
|
|
9344
8763
|
toolName: name,
|
|
@@ -9356,10 +8775,10 @@ ${p.text}` : p.text;
|
|
|
9356
8775
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
9357
8776
|
approvalResolvers.delete(toolCallId);
|
|
9358
8777
|
this.pendingApprovals.delete(toolCallId);
|
|
9359
|
-
const
|
|
8778
|
+
const exec6 = await execution;
|
|
9360
8779
|
if (!approved) {
|
|
9361
8780
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
9362
|
-
await toolExecutionQueries.reject(
|
|
8781
|
+
await toolExecutionQueries.reject(exec6.id);
|
|
9363
8782
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
9364
8783
|
return {
|
|
9365
8784
|
status: "rejected",
|
|
@@ -9369,14 +8788,14 @@ ${p.text}` : p.text;
|
|
|
9369
8788
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
9370
8789
|
};
|
|
9371
8790
|
}
|
|
9372
|
-
await toolExecutionQueries.approve(
|
|
8791
|
+
await toolExecutionQueries.approve(exec6.id);
|
|
9373
8792
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
9374
8793
|
try {
|
|
9375
8794
|
const result = await originalTool.execute(input, toolOptions);
|
|
9376
|
-
await toolExecutionQueries.complete(
|
|
8795
|
+
await toolExecutionQueries.complete(exec6.id, result);
|
|
9377
8796
|
return result;
|
|
9378
8797
|
} catch (error) {
|
|
9379
|
-
await toolExecutionQueries.complete(
|
|
8798
|
+
await toolExecutionQueries.complete(exec6.id, null, error.message);
|
|
9380
8799
|
throw error;
|
|
9381
8800
|
}
|
|
9382
8801
|
}
|