sparkecoder 0.1.86 → 0.1.93
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/README.md +1 -1
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +809 -97
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +2395 -316
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-OhuTM4a0.d.ts → index-Bn0Zox8f.d.ts} +32 -23
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1932 -273
- package/dist/index.js.map +1 -1
- package/dist/{schema-CohdIL13.d.ts → schema-EmpbnQeQ.d.ts} +3 -3
- package/dist/{search-CCffrVJE.d.ts → search-BRnGaIl-.d.ts} +7 -7
- package/dist/server/index.js +1932 -273
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/computer-use.md +150 -0
- package/dist/tools/index.d.ts +196 -3
- package/dist/tools/index.js +639 -11
- package/dist/tools/index.js.map +1 -1
- package/package.json +6 -5
- package/src/skills/default/computer-use.md +150 -0
- 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/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- 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/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +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 +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +3 -3
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +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 +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +3 -3
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +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 +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +3 -3
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +3 -3
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ecd2bdca._.js → 2374f_317b1fef._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9adc1edb._.js → 2374f_37dd9702._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8dc0f9aa._.js → 2374f_4d44e4ed._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_cc6c6363._.js → 2374f_54ac917f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_00f7fe07._.js → 2374f_86585101._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_369747ce._.js → 2374f_a383a4d9._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d58d0276._.js → 2374f_c59a35bb._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__25b25c9d._.js → [root-of-the-server]__9a826344._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/standalone/web/.next/static/{static/chunks/26eb5fab5216f3cc.js → chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/{static/chunks/8d3efc76109d2efc.js → standalone/web/.next/static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/{static/chunks/26eb5fab5216f3cc.js → standalone/web/.next/static/static/chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/standalone/web/.next/static/{chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/.next/standalone/web/package-lock.json +27 -27
- package/web/.next/standalone/web/package.json +1 -1
- package/web/.next/standalone/web/src/app/(main)/page.tsx +2 -2
- package/web/.next/standalone/web/src/app/api/config/route.ts +3 -2
- package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +12 -0
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +1 -1
- package/web/.next/standalone/web/src/lib/config.ts +2 -1
- package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +108 -0
- package/web/.next/static/chunks/275e8268daf318b2.js +7 -0
- package/web/.next/{standalone/web/.next/static/chunks/26eb5fab5216f3cc.js → static/chunks/58fd0aaa2746b444.js} +1 -1
- package/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
- package/web/package.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/b31b0765abe0c427.js +0 -1
- package/web/.next/static/chunks/5383c5717758f575.js +0 -7
- package/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
- /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
- /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
- /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
package/dist/agent/index.js
CHANGED
|
@@ -27,7 +27,12 @@ var init_types = __esm({
|
|
|
27
27
|
// Whether to always inject this skill into context (vs on-demand loading)
|
|
28
28
|
alwaysApply: z.boolean().optional().default(false),
|
|
29
29
|
// Glob patterns - auto-inject when working with matching files
|
|
30
|
-
globs: z.array(z.string()).optional().default([])
|
|
30
|
+
globs: z.array(z.string()).optional().default([]),
|
|
31
|
+
// Platform requirements — skill is hidden from the model on platforms
|
|
32
|
+
// not listed here. Values match `process.platform`
|
|
33
|
+
// (darwin, linux, win32, freebsd, ...). If omitted or empty, the skill is
|
|
34
|
+
// available on all platforms.
|
|
35
|
+
platforms: z.array(z.string()).optional().default([])
|
|
31
36
|
});
|
|
32
37
|
TaskConfigSchema = z.object({
|
|
33
38
|
enabled: z.boolean(),
|
|
@@ -45,7 +50,13 @@ var init_types = __esm({
|
|
|
45
50
|
approvalWebhook: z.string().url().optional(),
|
|
46
51
|
skillsDirectory: z.string().optional(),
|
|
47
52
|
maxContextChars: z.number().optional().default(2e5),
|
|
48
|
-
task: TaskConfigSchema.optional()
|
|
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()
|
|
49
60
|
});
|
|
50
61
|
VectorGatewayConfigSchema = z.object({
|
|
51
62
|
// Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
|
|
@@ -96,7 +107,7 @@ var init_types = __esm({
|
|
|
96
107
|
}).optional();
|
|
97
108
|
SparkcoderConfigSchema = z.object({
|
|
98
109
|
// Default model to use (Vercel AI Gateway format)
|
|
99
|
-
defaultModel: z.string().default("anthropic/claude-opus-4
|
|
110
|
+
defaultModel: z.string().default("anthropic/claude-opus-4.7"),
|
|
100
111
|
// Working directory for file operations
|
|
101
112
|
workingDirectory: z.string().optional(),
|
|
102
113
|
// Tool approval settings
|
|
@@ -214,9 +225,9 @@ __export(remote_exports, {
|
|
|
214
225
|
remoteToolExecutionQueries: () => remoteToolExecutionQueries,
|
|
215
226
|
storageQueries: () => storageQueries
|
|
216
227
|
});
|
|
217
|
-
function initRemoteDatabase(serverUrl,
|
|
228
|
+
function initRemoteDatabase(serverUrl, key2) {
|
|
218
229
|
remoteServerUrl = serverUrl.replace(/\/$/, "");
|
|
219
|
-
authKey =
|
|
230
|
+
authKey = key2;
|
|
220
231
|
}
|
|
221
232
|
function closeRemoteDatabase() {
|
|
222
233
|
remoteServerUrl = null;
|
|
@@ -230,14 +241,14 @@ function parseDates(obj) {
|
|
|
230
241
|
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
231
242
|
if (typeof obj !== "object" || obj instanceof Date) return obj;
|
|
232
243
|
const result = { ...obj };
|
|
233
|
-
for (const
|
|
234
|
-
if (MODEL_MESSAGE_FIELDS.includes(
|
|
244
|
+
for (const key2 of Object.keys(result)) {
|
|
245
|
+
if (MODEL_MESSAGE_FIELDS.includes(key2)) {
|
|
235
246
|
continue;
|
|
236
247
|
}
|
|
237
|
-
if (DATE_FIELDS.includes(
|
|
238
|
-
result[
|
|
239
|
-
} else if (typeof result[
|
|
240
|
-
result[
|
|
248
|
+
if (DATE_FIELDS.includes(key2) && typeof result[key2] === "string") {
|
|
249
|
+
result[key2] = new Date(result[key2]);
|
|
250
|
+
} else if (typeof result[key2] === "object") {
|
|
251
|
+
result[key2] = parseDates(result[key2]);
|
|
241
252
|
}
|
|
242
253
|
}
|
|
243
254
|
return result;
|
|
@@ -718,10 +729,10 @@ function parseSkillFrontmatter(content) {
|
|
|
718
729
|
}
|
|
719
730
|
const colonIndex = line.indexOf(":");
|
|
720
731
|
if (colonIndex > 0) {
|
|
721
|
-
const
|
|
732
|
+
const key2 = line.slice(0, colonIndex).trim();
|
|
722
733
|
let value = line.slice(colonIndex + 1).trim();
|
|
723
734
|
if (value === "" || value === "[]") {
|
|
724
|
-
currentArrayKey =
|
|
735
|
+
currentArrayKey = key2;
|
|
725
736
|
currentArray = [];
|
|
726
737
|
continue;
|
|
727
738
|
}
|
|
@@ -734,18 +745,18 @@ function parseSkillFrontmatter(content) {
|
|
|
734
745
|
}
|
|
735
746
|
return trimmed;
|
|
736
747
|
}).filter((item) => item.length > 0);
|
|
737
|
-
data[
|
|
748
|
+
data[key2] = items;
|
|
738
749
|
continue;
|
|
739
750
|
}
|
|
740
751
|
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
741
752
|
value = value.slice(1, -1);
|
|
742
753
|
}
|
|
743
754
|
if (value === "true") {
|
|
744
|
-
data[
|
|
755
|
+
data[key2] = true;
|
|
745
756
|
} else if (value === "false") {
|
|
746
|
-
data[
|
|
757
|
+
data[key2] = false;
|
|
747
758
|
} else {
|
|
748
|
-
data[
|
|
759
|
+
data[key2] = value;
|
|
749
760
|
}
|
|
750
761
|
}
|
|
751
762
|
}
|
|
@@ -802,7 +813,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
802
813
|
globs: parsed.metadata.globs,
|
|
803
814
|
loadType,
|
|
804
815
|
priority,
|
|
805
|
-
sourceDir: directory
|
|
816
|
+
sourceDir: directory,
|
|
817
|
+
platforms: parsed.metadata.platforms
|
|
806
818
|
});
|
|
807
819
|
} else {
|
|
808
820
|
const name = getSkillNameFromPath(filePath);
|
|
@@ -815,11 +827,14 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
815
827
|
globs: [],
|
|
816
828
|
loadType: forceAlwaysApply ? "always" : defaultLoadType,
|
|
817
829
|
priority,
|
|
818
|
-
sourceDir: directory
|
|
830
|
+
sourceDir: directory,
|
|
831
|
+
platforms: []
|
|
819
832
|
});
|
|
820
833
|
}
|
|
821
834
|
}
|
|
822
|
-
return skills
|
|
835
|
+
return skills.filter(
|
|
836
|
+
(s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
|
|
837
|
+
);
|
|
823
838
|
}
|
|
824
839
|
async function loadAllSkills(directories) {
|
|
825
840
|
const allSkills = [];
|
|
@@ -1073,9 +1088,9 @@ var init_chunker = __esm({
|
|
|
1073
1088
|
});
|
|
1074
1089
|
|
|
1075
1090
|
// src/semantic/client.ts
|
|
1076
|
-
function initVectorClient(serverUrl,
|
|
1091
|
+
function initVectorClient(serverUrl, key2) {
|
|
1077
1092
|
remoteServerUrl2 = serverUrl.replace(/\/$/, "");
|
|
1078
|
-
authKey2 =
|
|
1093
|
+
authKey2 = key2;
|
|
1079
1094
|
}
|
|
1080
1095
|
function isVectorClientConfigured() {
|
|
1081
1096
|
return !!remoteServerUrl2 && !!authKey2;
|
|
@@ -1616,15 +1631,15 @@ var recorder_exports = {};
|
|
|
1616
1631
|
__export(recorder_exports, {
|
|
1617
1632
|
FrameRecorder: () => FrameRecorder
|
|
1618
1633
|
});
|
|
1619
|
-
import { exec as
|
|
1620
|
-
import { promisify as
|
|
1634
|
+
import { exec as exec6 } from "child_process";
|
|
1635
|
+
import { promisify as promisify6 } from "util";
|
|
1621
1636
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1622
|
-
import { join as
|
|
1623
|
-
import { tmpdir } from "os";
|
|
1624
|
-
import { nanoid as
|
|
1637
|
+
import { join as join9 } from "path";
|
|
1638
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1639
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
1625
1640
|
async function checkFfmpeg() {
|
|
1626
1641
|
try {
|
|
1627
|
-
await
|
|
1642
|
+
await execAsync6("ffmpeg -version", { timeout: 5e3 });
|
|
1628
1643
|
return true;
|
|
1629
1644
|
} catch {
|
|
1630
1645
|
return false;
|
|
@@ -1636,11 +1651,11 @@ async function cleanup(dir) {
|
|
|
1636
1651
|
} catch {
|
|
1637
1652
|
}
|
|
1638
1653
|
}
|
|
1639
|
-
var
|
|
1654
|
+
var execAsync6, FrameRecorder;
|
|
1640
1655
|
var init_recorder = __esm({
|
|
1641
1656
|
"src/browser/recorder.ts"() {
|
|
1642
1657
|
"use strict";
|
|
1643
|
-
|
|
1658
|
+
execAsync6 = promisify6(exec6);
|
|
1644
1659
|
FrameRecorder = class {
|
|
1645
1660
|
frames = [];
|
|
1646
1661
|
startTime = null;
|
|
@@ -1676,21 +1691,21 @@ var init_recorder = __esm({
|
|
|
1676
1691
|
*/
|
|
1677
1692
|
async encode() {
|
|
1678
1693
|
if (this.frames.length === 0) return null;
|
|
1679
|
-
const workDir =
|
|
1694
|
+
const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid5(8)}`);
|
|
1680
1695
|
await mkdir4(workDir, { recursive: true });
|
|
1681
1696
|
try {
|
|
1682
1697
|
for (let i = 0; i < this.frames.length; i++) {
|
|
1683
|
-
const framePath =
|
|
1698
|
+
const framePath = join9(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
1684
1699
|
await writeFile5(framePath, this.frames[i].data);
|
|
1685
1700
|
}
|
|
1686
1701
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
1687
1702
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
1688
1703
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
1689
|
-
const outputPath =
|
|
1704
|
+
const outputPath = join9(workDir, `recording_${this.sessionId}.mp4`);
|
|
1690
1705
|
const hasFfmpeg = await checkFfmpeg();
|
|
1691
1706
|
if (hasFfmpeg) {
|
|
1692
|
-
await
|
|
1693
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
1707
|
+
await execAsync6(
|
|
1708
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join9(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
1694
1709
|
{ timeout: 12e4 }
|
|
1695
1710
|
);
|
|
1696
1711
|
} else {
|
|
@@ -1702,7 +1717,7 @@ var init_recorder = __esm({
|
|
|
1702
1717
|
const files = await readdir5(workDir);
|
|
1703
1718
|
for (const f of files) {
|
|
1704
1719
|
if (f.startsWith("frame_")) {
|
|
1705
|
-
await unlink2(
|
|
1720
|
+
await unlink2(join9(workDir, f)).catch(() => {
|
|
1706
1721
|
});
|
|
1707
1722
|
}
|
|
1708
1723
|
}
|
|
@@ -1727,7 +1742,7 @@ var init_recorder = __esm({
|
|
|
1727
1742
|
import {
|
|
1728
1743
|
streamText as streamText2,
|
|
1729
1744
|
generateText as generateText3,
|
|
1730
|
-
tool as
|
|
1745
|
+
tool as tool14,
|
|
1731
1746
|
stepCountIs as stepCountIs2
|
|
1732
1747
|
} from "ai";
|
|
1733
1748
|
|
|
@@ -1896,6 +1911,23 @@ function isAnthropicModel(modelId) {
|
|
|
1896
1911
|
const normalized = modelId.trim().toLowerCase();
|
|
1897
1912
|
return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
|
|
1898
1913
|
}
|
|
1914
|
+
function requiresAdaptiveThinking(modelId) {
|
|
1915
|
+
const m = modelId.toLowerCase().match(/claude-(?:opus|sonnet|haiku)-(\d+)[.-](\d{1,2})(?!\d)/);
|
|
1916
|
+
if (!m) return false;
|
|
1917
|
+
const major = Number(m[1]);
|
|
1918
|
+
const minor = Number(m[2]);
|
|
1919
|
+
if (Number.isNaN(major) || Number.isNaN(minor)) return false;
|
|
1920
|
+
if (major > 4) return true;
|
|
1921
|
+
if (major === 4 && minor >= 6) return true;
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
function getAnthropicProviderOptions(modelId, opts = {}) {
|
|
1925
|
+
const { toolStreaming, budgetTokens = 1e4 } = opts;
|
|
1926
|
+
const thinking = requiresAdaptiveThinking(modelId) ? { type: "adaptive" } : { type: "enabled", budgetTokens };
|
|
1927
|
+
const out = { thinking };
|
|
1928
|
+
if (toolStreaming) out.toolStreaming = true;
|
|
1929
|
+
return out;
|
|
1930
|
+
}
|
|
1899
1931
|
function resolveModel(modelId) {
|
|
1900
1932
|
try {
|
|
1901
1933
|
const config = getConfig();
|
|
@@ -1918,8 +1950,8 @@ var SUBAGENT_MODELS = {
|
|
|
1918
1950
|
// src/agent/index.ts
|
|
1919
1951
|
init_db();
|
|
1920
1952
|
init_config();
|
|
1921
|
-
import { z as
|
|
1922
|
-
import { nanoid as
|
|
1953
|
+
import { z as z15 } from "zod";
|
|
1954
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
1923
1955
|
|
|
1924
1956
|
// src/tools/bash.ts
|
|
1925
1957
|
import { tool } from "ai";
|
|
@@ -2204,11 +2236,11 @@ async function sendInput(terminalId, input, options = {}) {
|
|
|
2204
2236
|
return false;
|
|
2205
2237
|
}
|
|
2206
2238
|
}
|
|
2207
|
-
async function sendKey(terminalId,
|
|
2239
|
+
async function sendKey(terminalId, key2) {
|
|
2208
2240
|
const session = getSessionName(terminalId);
|
|
2209
2241
|
try {
|
|
2210
2242
|
await execAsync(`tmux has-session -t ${session}`, { timeout: 1e3 });
|
|
2211
|
-
await execAsync(`tmux send-keys -t ${session} ${
|
|
2243
|
+
await execAsync(`tmux send-keys -t ${session} ${key2}`, { timeout: 1e3 });
|
|
2212
2244
|
return true;
|
|
2213
2245
|
} catch {
|
|
2214
2246
|
return false;
|
|
@@ -2347,7 +2379,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
|
|
|
2347
2379
|
Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
|
|
2348
2380
|
inputSchema: bashInputSchema,
|
|
2349
2381
|
execute: async (inputArgs) => {
|
|
2350
|
-
const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
|
|
2382
|
+
const { command, background, id, kill, tail, input: textInput, key: key2 } = inputArgs;
|
|
2351
2383
|
if (id) {
|
|
2352
2384
|
if (kill) {
|
|
2353
2385
|
const success = await killTerminal(id);
|
|
@@ -2378,8 +2410,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2378
2410
|
message: `Sent input "${textInput}" to terminal`
|
|
2379
2411
|
};
|
|
2380
2412
|
}
|
|
2381
|
-
if (
|
|
2382
|
-
const success = await sendKey(id,
|
|
2413
|
+
if (key2) {
|
|
2414
|
+
const success = await sendKey(id, key2);
|
|
2383
2415
|
if (!success) {
|
|
2384
2416
|
return {
|
|
2385
2417
|
success: false,
|
|
@@ -2395,7 +2427,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2395
2427
|
id,
|
|
2396
2428
|
output: truncatedOutput2,
|
|
2397
2429
|
status: status2,
|
|
2398
|
-
message: `Sent key "${
|
|
2430
|
+
message: `Sent key "${key2}" to terminal`
|
|
2399
2431
|
};
|
|
2400
2432
|
}
|
|
2401
2433
|
const { output, status } = await getLogs(id, options.workingDirectory, { tail, sessionId: options.sessionId });
|
|
@@ -2537,13 +2569,13 @@ async function resizeImageIfNeeded(buffer, mediaType) {
|
|
|
2537
2569
|
const needsResize = longEdge > MAX_LONG_EDGE;
|
|
2538
2570
|
const needsShrink = buffer.length > MAX_FILE_BYTES;
|
|
2539
2571
|
if (!needsResize && !needsShrink) return { buffer, mediaType: inputMediaType };
|
|
2540
|
-
const
|
|
2572
|
+
const key2 = cacheKey(buffer);
|
|
2541
2573
|
const cacheDir = getCacheDir();
|
|
2542
2574
|
const isPng = inputMediaType.includes("png");
|
|
2543
2575
|
const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
|
|
2544
2576
|
const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
|
|
2545
2577
|
const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
|
|
2546
|
-
const cachePath = join3(cacheDir,
|
|
2578
|
+
const cachePath = join3(cacheDir, key2 + ext);
|
|
2547
2579
|
if (existsSync3(cachePath)) {
|
|
2548
2580
|
console.log(`[image-resize] Cache hit for ${width}x${height} image`);
|
|
2549
2581
|
return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
|
|
@@ -2802,12 +2834,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2802
2834
|
}
|
|
2803
2835
|
async function commandExists(cmd) {
|
|
2804
2836
|
try {
|
|
2805
|
-
const { exec:
|
|
2806
|
-
const { promisify:
|
|
2807
|
-
const
|
|
2837
|
+
const { exec: exec7 } = await import("child_process");
|
|
2838
|
+
const { promisify: promisify7 } = await import("util");
|
|
2839
|
+
const execAsync7 = promisify7(exec7);
|
|
2808
2840
|
const isWindows = process.platform === "win32";
|
|
2809
2841
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2810
|
-
await
|
|
2842
|
+
await execAsync7(checkCmd);
|
|
2811
2843
|
return true;
|
|
2812
2844
|
} catch {
|
|
2813
2845
|
return false;
|
|
@@ -3298,31 +3330,31 @@ async function getClientForFile(filePath) {
|
|
|
3298
3330
|
return null;
|
|
3299
3331
|
}
|
|
3300
3332
|
const root = dirname4(normalized);
|
|
3301
|
-
const
|
|
3302
|
-
const existing = state.clients.get(
|
|
3333
|
+
const key2 = `${serverDef.id}:${root}`;
|
|
3334
|
+
const existing = state.clients.get(key2);
|
|
3303
3335
|
if (existing) {
|
|
3304
3336
|
return existing;
|
|
3305
3337
|
}
|
|
3306
|
-
if (state.broken.has(
|
|
3338
|
+
if (state.broken.has(key2)) {
|
|
3307
3339
|
return null;
|
|
3308
3340
|
}
|
|
3309
3341
|
try {
|
|
3310
3342
|
const handle = await serverDef.spawn(root);
|
|
3311
3343
|
if (!handle) {
|
|
3312
|
-
state.broken.add(
|
|
3344
|
+
state.broken.add(key2);
|
|
3313
3345
|
return null;
|
|
3314
3346
|
}
|
|
3315
3347
|
console.log(`[lsp] Started ${serverDef.name} for ${root}`);
|
|
3316
3348
|
const client = await createClient(serverDef.id, handle, root);
|
|
3317
|
-
state.clients.set(
|
|
3349
|
+
state.clients.set(key2, client);
|
|
3318
3350
|
handle.process.on("exit", (code) => {
|
|
3319
3351
|
console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
|
|
3320
|
-
state.clients.delete(
|
|
3352
|
+
state.clients.delete(key2);
|
|
3321
3353
|
});
|
|
3322
3354
|
return client;
|
|
3323
3355
|
} catch (error) {
|
|
3324
3356
|
console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
|
|
3325
|
-
state.broken.add(
|
|
3357
|
+
state.broken.add(key2);
|
|
3326
3358
|
return null;
|
|
3327
3359
|
}
|
|
3328
3360
|
}
|
|
@@ -5422,6 +5454,7 @@ init_semantic_search();
|
|
|
5422
5454
|
import { tool as tool11 } from "ai";
|
|
5423
5455
|
import { z as z12 } from "zod";
|
|
5424
5456
|
import Ajv from "ajv";
|
|
5457
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
5425
5458
|
var ajv = new Ajv({ allErrors: true });
|
|
5426
5459
|
function createCompleteTaskTool(options) {
|
|
5427
5460
|
const validate = ajv.compile(options.outputSchema);
|
|
@@ -5468,6 +5501,37 @@ function createTaskFailedTool(options) {
|
|
|
5468
5501
|
}
|
|
5469
5502
|
});
|
|
5470
5503
|
}
|
|
5504
|
+
function createAskQuestionToUserTool(options) {
|
|
5505
|
+
return tool11({
|
|
5506
|
+
description: "Ask the user a blocking clarification question when you cannot safely continue without more information. Use this only after trying to infer the answer from the available context. The task will pause until the orchestrator or user answers.",
|
|
5507
|
+
inputSchema: z12.object({
|
|
5508
|
+
question: z12.string().min(1).describe("The concise question you need answered."),
|
|
5509
|
+
context: z12.string().optional().describe("Brief context explaining why this answer is needed and what you already tried."),
|
|
5510
|
+
choices: z12.array(z12.string().min(1)).max(10).optional().describe("Optional suggested answer choices when the question is multiple choice.")
|
|
5511
|
+
}),
|
|
5512
|
+
execute: async (input) => {
|
|
5513
|
+
if (!options.onQuestion) {
|
|
5514
|
+
return {
|
|
5515
|
+
status: "unavailable",
|
|
5516
|
+
message: "Question routing is not configured for this task. Continue with the best safe assumption or call task_failed if blocked."
|
|
5517
|
+
};
|
|
5518
|
+
}
|
|
5519
|
+
const questionId = `q_${nanoid3(12)}`;
|
|
5520
|
+
const answer = await options.onQuestion({
|
|
5521
|
+
questionId,
|
|
5522
|
+
question: input.question,
|
|
5523
|
+
context: input.context,
|
|
5524
|
+
choices: input.choices
|
|
5525
|
+
});
|
|
5526
|
+
return {
|
|
5527
|
+
status: "answered",
|
|
5528
|
+
questionId,
|
|
5529
|
+
answer: answer.answer,
|
|
5530
|
+
answeredBy: answer.answeredBy ?? "unknown"
|
|
5531
|
+
};
|
|
5532
|
+
}
|
|
5533
|
+
});
|
|
5534
|
+
}
|
|
5471
5535
|
|
|
5472
5536
|
// src/tools/upload-file.ts
|
|
5473
5537
|
import { tool as tool12 } from "ai";
|
|
@@ -5566,6 +5630,568 @@ function createUploadFileTool(options) {
|
|
|
5566
5630
|
});
|
|
5567
5631
|
}
|
|
5568
5632
|
|
|
5633
|
+
// src/tools/computer-use.ts
|
|
5634
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
5635
|
+
import { exec as exec5 } from "child_process";
|
|
5636
|
+
import { promisify as promisify5 } from "util";
|
|
5637
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
5638
|
+
import { join as join8 } from "path";
|
|
5639
|
+
import { tmpdir } from "os";
|
|
5640
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
5641
|
+
var execAsync5 = promisify5(exec5);
|
|
5642
|
+
var DEFAULT_WIDTH = 1280;
|
|
5643
|
+
var DEFAULT_HEIGHT = 800;
|
|
5644
|
+
function isMacOs() {
|
|
5645
|
+
return process.platform === "darwin";
|
|
5646
|
+
}
|
|
5647
|
+
async function isCliclickInstalled() {
|
|
5648
|
+
try {
|
|
5649
|
+
await execAsync5("command -v cliclick", { timeout: 2e3 });
|
|
5650
|
+
return true;
|
|
5651
|
+
} catch {
|
|
5652
|
+
return false;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
async function runJxa(script) {
|
|
5656
|
+
try {
|
|
5657
|
+
const escaped = script.replace(/'/g, `'\\''`);
|
|
5658
|
+
const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
|
|
5659
|
+
timeout: 5e3
|
|
5660
|
+
});
|
|
5661
|
+
return JSON.parse(stdout.trim());
|
|
5662
|
+
} catch {
|
|
5663
|
+
return null;
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
async function hasAccessibilityPermissions() {
|
|
5667
|
+
try {
|
|
5668
|
+
const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
|
|
5669
|
+
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
5670
|
+
return { ok: false, error: stderr.trim().split("\n")[0] };
|
|
5671
|
+
}
|
|
5672
|
+
return { ok: true };
|
|
5673
|
+
} catch (err) {
|
|
5674
|
+
return { ok: false, error: err?.message || String(err) };
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
async function hasScreenRecordingPermissions() {
|
|
5678
|
+
const result = await runJxa(
|
|
5679
|
+
`ObjC.import("Cocoa");
|
|
5680
|
+
ObjC.import("CoreGraphics");
|
|
5681
|
+
ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
|
|
5682
|
+
JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
|
|
5683
|
+
);
|
|
5684
|
+
return result?.hasAccess ?? false;
|
|
5685
|
+
}
|
|
5686
|
+
async function requestAccessibilityPrompt() {
|
|
5687
|
+
const result = await runJxa(
|
|
5688
|
+
`ObjC.import("ApplicationServices");
|
|
5689
|
+
var key = $.kAXTrustedCheckOptionPrompt;
|
|
5690
|
+
var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
|
|
5691
|
+
var trusted = $.AXIsProcessTrustedWithOptions(dict);
|
|
5692
|
+
JSON.stringify({ trusted: !!trusted });`
|
|
5693
|
+
);
|
|
5694
|
+
return result?.trusted ?? false;
|
|
5695
|
+
}
|
|
5696
|
+
async function requestScreenRecordingPrompt() {
|
|
5697
|
+
const result = await runJxa(
|
|
5698
|
+
`ObjC.import("Cocoa");
|
|
5699
|
+
ObjC.import("CoreGraphics");
|
|
5700
|
+
ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
|
|
5701
|
+
JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
|
|
5702
|
+
);
|
|
5703
|
+
return result?.granted ?? false;
|
|
5704
|
+
}
|
|
5705
|
+
async function openSystemSettings(pane) {
|
|
5706
|
+
const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
5707
|
+
try {
|
|
5708
|
+
await execAsync5(`open '${url}'`, { timeout: 3e3 });
|
|
5709
|
+
} catch {
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
async function detectScreenSize() {
|
|
5713
|
+
try {
|
|
5714
|
+
const { stdout } = await execAsync5(
|
|
5715
|
+
`osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
|
|
5716
|
+
{ timeout: 3e3 }
|
|
5717
|
+
);
|
|
5718
|
+
const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
|
|
5719
|
+
if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
|
|
5720
|
+
const [x1, y1, x2, y2] = parts;
|
|
5721
|
+
return { width: x2 - x1, height: y2 - y1 };
|
|
5722
|
+
}
|
|
5723
|
+
} catch {
|
|
5724
|
+
}
|
|
5725
|
+
return null;
|
|
5726
|
+
}
|
|
5727
|
+
async function runCliclick(args) {
|
|
5728
|
+
const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
5729
|
+
const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
|
|
5730
|
+
timeout: 15e3,
|
|
5731
|
+
maxBuffer: 1024 * 1024
|
|
5732
|
+
});
|
|
5733
|
+
if (/accessibility privileges not enabled/i.test(stderr)) {
|
|
5734
|
+
throw new Error(
|
|
5735
|
+
"Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
|
|
5736
|
+
);
|
|
5737
|
+
}
|
|
5738
|
+
if (stderr && !stdout) throw new Error(stderr.trim());
|
|
5739
|
+
return (stdout || "").trim();
|
|
5740
|
+
}
|
|
5741
|
+
async function runScreencapture(path) {
|
|
5742
|
+
await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
|
|
5743
|
+
timeout: 5e3
|
|
5744
|
+
});
|
|
5745
|
+
}
|
|
5746
|
+
async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
|
|
5747
|
+
const sharpModule = await import("sharp");
|
|
5748
|
+
const sharp2 = sharpModule.default || sharpModule;
|
|
5749
|
+
const meta = await sharp2(path).metadata();
|
|
5750
|
+
if (meta.width === targetWidth && meta.height === targetHeight) {
|
|
5751
|
+
return readFileSync7(path);
|
|
5752
|
+
}
|
|
5753
|
+
return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
|
|
5754
|
+
}
|
|
5755
|
+
async function runScroll(dx, dy) {
|
|
5756
|
+
const wheelY = -Math.round(dy);
|
|
5757
|
+
const wheelX = -Math.round(dx);
|
|
5758
|
+
const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
|
|
5759
|
+
await execAsync5(
|
|
5760
|
+
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
5761
|
+
{ timeout: 5e3 }
|
|
5762
|
+
);
|
|
5763
|
+
}
|
|
5764
|
+
function translateKeyForCliclick(key2) {
|
|
5765
|
+
if (!key2) return [];
|
|
5766
|
+
const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
|
|
5767
|
+
if (parts.length === 0) return [];
|
|
5768
|
+
const modMap = {
|
|
5769
|
+
ctrl: "ctrl",
|
|
5770
|
+
control: "ctrl",
|
|
5771
|
+
alt: "alt",
|
|
5772
|
+
option: "alt",
|
|
5773
|
+
shift: "shift",
|
|
5774
|
+
cmd: "cmd",
|
|
5775
|
+
super: "cmd",
|
|
5776
|
+
meta: "cmd",
|
|
5777
|
+
win: "cmd",
|
|
5778
|
+
fn: "fn"
|
|
5779
|
+
};
|
|
5780
|
+
const keyMap = {
|
|
5781
|
+
return: "enter",
|
|
5782
|
+
enter: "enter",
|
|
5783
|
+
esc: "esc",
|
|
5784
|
+
escape: "esc",
|
|
5785
|
+
backspace: "delete",
|
|
5786
|
+
back_space: "delete",
|
|
5787
|
+
delete: "fwd-delete",
|
|
5788
|
+
fwd_delete: "fwd-delete",
|
|
5789
|
+
forward_delete: "fwd-delete",
|
|
5790
|
+
tab: "tab",
|
|
5791
|
+
space: "space",
|
|
5792
|
+
up: "arrow-up",
|
|
5793
|
+
arrow_up: "arrow-up",
|
|
5794
|
+
down: "arrow-down",
|
|
5795
|
+
arrow_down: "arrow-down",
|
|
5796
|
+
left: "arrow-left",
|
|
5797
|
+
arrow_left: "arrow-left",
|
|
5798
|
+
right: "arrow-right",
|
|
5799
|
+
arrow_right: "arrow-right",
|
|
5800
|
+
page_up: "page-up",
|
|
5801
|
+
pageup: "page-up",
|
|
5802
|
+
page_down: "page-down",
|
|
5803
|
+
pagedown: "page-down",
|
|
5804
|
+
home: "home",
|
|
5805
|
+
end: "end",
|
|
5806
|
+
f1: "f1",
|
|
5807
|
+
f2: "f2",
|
|
5808
|
+
f3: "f3",
|
|
5809
|
+
f4: "f4",
|
|
5810
|
+
f5: "f5",
|
|
5811
|
+
f6: "f6",
|
|
5812
|
+
f7: "f7",
|
|
5813
|
+
f8: "f8",
|
|
5814
|
+
f9: "f9",
|
|
5815
|
+
f10: "f10",
|
|
5816
|
+
f11: "f11",
|
|
5817
|
+
f12: "f12"
|
|
5818
|
+
};
|
|
5819
|
+
const modifiers = [];
|
|
5820
|
+
let mainKey = null;
|
|
5821
|
+
for (let i = 0; i < parts.length; i++) {
|
|
5822
|
+
const lower = parts[i].toLowerCase().replace(/-/g, "_");
|
|
5823
|
+
if (i < parts.length - 1 && modMap[lower]) {
|
|
5824
|
+
modifiers.push(modMap[lower]);
|
|
5825
|
+
} else {
|
|
5826
|
+
mainKey = keyMap[lower] || lower;
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
const args = [];
|
|
5830
|
+
if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
|
|
5831
|
+
if (mainKey) {
|
|
5832
|
+
const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
|
|
5833
|
+
if (isNamedKey) {
|
|
5834
|
+
args.push(`kp:${mainKey}`);
|
|
5835
|
+
} else {
|
|
5836
|
+
args.push(`t:${mainKey}`);
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
|
|
5840
|
+
return args;
|
|
5841
|
+
}
|
|
5842
|
+
function modifierStringToCliclick(text) {
|
|
5843
|
+
return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
|
|
5844
|
+
if (p === "ctrl" || p === "control") return "ctrl";
|
|
5845
|
+
if (p === "alt" || p === "option") return "alt";
|
|
5846
|
+
if (p === "shift") return "shift";
|
|
5847
|
+
if (p === "super" || p === "meta" || p === "cmd") return "cmd";
|
|
5848
|
+
return "";
|
|
5849
|
+
}).filter(Boolean);
|
|
5850
|
+
}
|
|
5851
|
+
function createComputerUseTool(options) {
|
|
5852
|
+
const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
|
|
5853
|
+
const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
|
|
5854
|
+
return anthropic.tools.computer_20251124({
|
|
5855
|
+
displayWidthPx: displayWidth,
|
|
5856
|
+
displayHeightPx: displayHeight,
|
|
5857
|
+
enableZoom: true,
|
|
5858
|
+
execute: async (input) => {
|
|
5859
|
+
try {
|
|
5860
|
+
switch (input.action) {
|
|
5861
|
+
case "screenshot": {
|
|
5862
|
+
const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
|
|
5863
|
+
await runScreencapture(path);
|
|
5864
|
+
const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
|
|
5865
|
+
try {
|
|
5866
|
+
unlinkSync2(path);
|
|
5867
|
+
} catch {
|
|
5868
|
+
}
|
|
5869
|
+
return { type: "image", data: resized.toString("base64") };
|
|
5870
|
+
}
|
|
5871
|
+
case "left_click": {
|
|
5872
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5873
|
+
if (input.text) {
|
|
5874
|
+
const mods = modifierStringToCliclick(input.text);
|
|
5875
|
+
if (mods.length > 0) {
|
|
5876
|
+
await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
|
|
5877
|
+
} else {
|
|
5878
|
+
await runCliclick([`c:${x},${y}`]);
|
|
5879
|
+
}
|
|
5880
|
+
} else {
|
|
5881
|
+
await runCliclick([`c:${x},${y}`]);
|
|
5882
|
+
}
|
|
5883
|
+
return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
|
|
5884
|
+
}
|
|
5885
|
+
case "right_click": {
|
|
5886
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5887
|
+
await runCliclick([`rc:${x},${y}`]);
|
|
5888
|
+
return `right-clicked at (${x}, ${y})`;
|
|
5889
|
+
}
|
|
5890
|
+
case "middle_click": {
|
|
5891
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5892
|
+
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);`;
|
|
5893
|
+
await execAsync5(
|
|
5894
|
+
`osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
|
|
5895
|
+
{ timeout: 3e3 }
|
|
5896
|
+
);
|
|
5897
|
+
return `middle-clicked at (${x}, ${y})`;
|
|
5898
|
+
}
|
|
5899
|
+
case "double_click": {
|
|
5900
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5901
|
+
await runCliclick([`dc:${x},${y}`]);
|
|
5902
|
+
return `double-clicked at (${x}, ${y})`;
|
|
5903
|
+
}
|
|
5904
|
+
case "triple_click": {
|
|
5905
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5906
|
+
await runCliclick([`tc:${x},${y}`]);
|
|
5907
|
+
return `triple-clicked at (${x}, ${y})`;
|
|
5908
|
+
}
|
|
5909
|
+
case "mouse_move": {
|
|
5910
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5911
|
+
await runCliclick([`m:${x},${y}`]);
|
|
5912
|
+
return `moved cursor to (${x}, ${y})`;
|
|
5913
|
+
}
|
|
5914
|
+
case "left_mouse_down": {
|
|
5915
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5916
|
+
await runCliclick([`dd:${x},${y}`]);
|
|
5917
|
+
return `left mouse button pressed at (${x}, ${y})`;
|
|
5918
|
+
}
|
|
5919
|
+
case "left_mouse_up": {
|
|
5920
|
+
const [x, y] = input.coordinate ?? [0, 0];
|
|
5921
|
+
await runCliclick([`du:${x},${y}`]);
|
|
5922
|
+
return `left mouse button released at (${x}, ${y})`;
|
|
5923
|
+
}
|
|
5924
|
+
case "left_click_drag": {
|
|
5925
|
+
const [sx, sy] = input.start_coordinate ?? [0, 0];
|
|
5926
|
+
const [ex, ey] = input.coordinate ?? [0, 0];
|
|
5927
|
+
await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
|
|
5928
|
+
return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
|
|
5929
|
+
}
|
|
5930
|
+
case "type": {
|
|
5931
|
+
const text = input.text ?? "";
|
|
5932
|
+
await runCliclick([`t:${text}`]);
|
|
5933
|
+
return `typed ${text.length} character(s)`;
|
|
5934
|
+
}
|
|
5935
|
+
case "key": {
|
|
5936
|
+
const args = translateKeyForCliclick(input.text ?? "");
|
|
5937
|
+
if (args.length === 0) return "no key specified";
|
|
5938
|
+
await runCliclick(args);
|
|
5939
|
+
return `pressed ${input.text}`;
|
|
5940
|
+
}
|
|
5941
|
+
case "hold_key": {
|
|
5942
|
+
const text = (input.text ?? "").toLowerCase();
|
|
5943
|
+
const duration = input.duration ?? 1;
|
|
5944
|
+
const modMap = {
|
|
5945
|
+
ctrl: "ctrl",
|
|
5946
|
+
control: "ctrl",
|
|
5947
|
+
alt: "alt",
|
|
5948
|
+
option: "alt",
|
|
5949
|
+
shift: "shift",
|
|
5950
|
+
cmd: "cmd",
|
|
5951
|
+
super: "cmd",
|
|
5952
|
+
meta: "cmd",
|
|
5953
|
+
fn: "fn"
|
|
5954
|
+
};
|
|
5955
|
+
const cliName = modMap[text] || text;
|
|
5956
|
+
await runCliclick([`kd:${cliName}`]);
|
|
5957
|
+
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
5958
|
+
await runCliclick([`ku:${cliName}`]);
|
|
5959
|
+
return `held ${text} for ${duration}s`;
|
|
5960
|
+
}
|
|
5961
|
+
case "scroll": {
|
|
5962
|
+
const direction = input.scroll_direction ?? "down";
|
|
5963
|
+
const amount = input.scroll_amount ?? 3;
|
|
5964
|
+
const px = amount * 100;
|
|
5965
|
+
const dx = direction === "left" ? -px : direction === "right" ? px : 0;
|
|
5966
|
+
const dy = direction === "up" ? -px : direction === "down" ? px : 0;
|
|
5967
|
+
if (input.coordinate) {
|
|
5968
|
+
const [x, y] = input.coordinate;
|
|
5969
|
+
await runCliclick([`m:${x},${y}`]);
|
|
5970
|
+
}
|
|
5971
|
+
const mods = input.text ? modifierStringToCliclick(input.text) : [];
|
|
5972
|
+
if (mods.length > 0) {
|
|
5973
|
+
await runCliclick([`kd:${mods.join(",")}`]);
|
|
5974
|
+
}
|
|
5975
|
+
await runScroll(dx, dy);
|
|
5976
|
+
if (mods.length > 0) {
|
|
5977
|
+
await runCliclick([`ku:${mods.join(",")}`]);
|
|
5978
|
+
}
|
|
5979
|
+
return `scrolled ${direction} by ${amount}`;
|
|
5980
|
+
}
|
|
5981
|
+
case "wait": {
|
|
5982
|
+
const duration = input.duration ?? 1;
|
|
5983
|
+
await new Promise((r) => setTimeout(r, duration * 1e3));
|
|
5984
|
+
return `waited ${duration}s`;
|
|
5985
|
+
}
|
|
5986
|
+
case "cursor_position": {
|
|
5987
|
+
const out = await runCliclick(["p:."]);
|
|
5988
|
+
return `cursor at ${out}`;
|
|
5989
|
+
}
|
|
5990
|
+
case "zoom": {
|
|
5991
|
+
const region = input.region ?? [0, 0, displayWidth, displayHeight];
|
|
5992
|
+
const [x1, y1, x2, y2] = region;
|
|
5993
|
+
const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
|
|
5994
|
+
await runScreencapture(tmpPath);
|
|
5995
|
+
const sharpModule = await import("sharp");
|
|
5996
|
+
const sharp2 = sharpModule.default || sharpModule;
|
|
5997
|
+
const meta = await sharp2(tmpPath).metadata();
|
|
5998
|
+
const scaleX = (meta.width || displayWidth) / displayWidth;
|
|
5999
|
+
const scaleY = (meta.height || displayHeight) / displayHeight;
|
|
6000
|
+
const px = {
|
|
6001
|
+
left: Math.max(0, Math.round(x1 * scaleX)),
|
|
6002
|
+
top: Math.max(0, Math.round(y1 * scaleY)),
|
|
6003
|
+
width: Math.max(1, Math.round((x2 - x1) * scaleX)),
|
|
6004
|
+
height: Math.max(1, Math.round((y2 - y1) * scaleY))
|
|
6005
|
+
};
|
|
6006
|
+
const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
|
|
6007
|
+
try {
|
|
6008
|
+
unlinkSync2(tmpPath);
|
|
6009
|
+
} catch {
|
|
6010
|
+
}
|
|
6011
|
+
return { type: "image", data: buf.toString("base64") };
|
|
6012
|
+
}
|
|
6013
|
+
default: {
|
|
6014
|
+
const exhaustive = input.action;
|
|
6015
|
+
return `unsupported action: ${String(exhaustive)}`;
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
6018
|
+
} catch (err) {
|
|
6019
|
+
const msg = err?.message || String(err);
|
|
6020
|
+
let hint = "";
|
|
6021
|
+
if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
|
|
6022
|
+
hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
|
|
6023
|
+
} else if (/command not found/i.test(msg)) {
|
|
6024
|
+
hint = " (Hint: install cliclick with `brew install cliclick`)";
|
|
6025
|
+
}
|
|
6026
|
+
return `Error: ${msg}${hint}`;
|
|
6027
|
+
}
|
|
6028
|
+
},
|
|
6029
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6030
|
+
toModelOutput({ output }) {
|
|
6031
|
+
if (typeof output === "string") {
|
|
6032
|
+
return { type: "content", value: [{ type: "text", text: output }] };
|
|
6033
|
+
}
|
|
6034
|
+
return {
|
|
6035
|
+
type: "content",
|
|
6036
|
+
value: [{ type: "media", data: output.data, mediaType: "image/png" }]
|
|
6037
|
+
};
|
|
6038
|
+
}
|
|
6039
|
+
});
|
|
6040
|
+
}
|
|
6041
|
+
|
|
6042
|
+
// src/tools/enable-computer-use.ts
|
|
6043
|
+
init_db();
|
|
6044
|
+
import { tool as tool13 } from "ai";
|
|
6045
|
+
import { z as z14 } from "zod";
|
|
6046
|
+
var inputSchema = z14.object({
|
|
6047
|
+
display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
|
|
6048
|
+
display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
|
|
6049
|
+
request_permissions: z14.boolean().optional().default(true).describe(
|
|
6050
|
+
"When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
|
|
6051
|
+
)
|
|
6052
|
+
});
|
|
6053
|
+
var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
6054
|
+
var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
|
|
6055
|
+
function createEnableComputerUseTool(options) {
|
|
6056
|
+
return tool13({
|
|
6057
|
+
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.",
|
|
6058
|
+
inputSchema,
|
|
6059
|
+
execute: async ({ display_width, display_height, request_permissions }) => {
|
|
6060
|
+
try {
|
|
6061
|
+
if (!isMacOs()) {
|
|
6062
|
+
return {
|
|
6063
|
+
success: false,
|
|
6064
|
+
error: "Computer use is currently only supported on macOS.",
|
|
6065
|
+
platform: process.platform
|
|
6066
|
+
};
|
|
6067
|
+
}
|
|
6068
|
+
if (!await isCliclickInstalled()) {
|
|
6069
|
+
return {
|
|
6070
|
+
success: false,
|
|
6071
|
+
error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
|
|
6072
|
+
installCommand: "brew install cliclick",
|
|
6073
|
+
fixSteps: [
|
|
6074
|
+
"In a terminal on this Mac, run: brew install cliclick",
|
|
6075
|
+
"(If Homebrew is not installed, install it first from https://brew.sh)",
|
|
6076
|
+
"Then call enable_computer_use again"
|
|
6077
|
+
]
|
|
6078
|
+
};
|
|
6079
|
+
}
|
|
6080
|
+
const acc = await hasAccessibilityPermissions();
|
|
6081
|
+
const screen = await hasScreenRecordingPermissions();
|
|
6082
|
+
const missing = [];
|
|
6083
|
+
if (!acc.ok) {
|
|
6084
|
+
let prompted = false;
|
|
6085
|
+
let panelOpened = false;
|
|
6086
|
+
if (request_permissions) {
|
|
6087
|
+
prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
|
|
6088
|
+
await openSystemSettings("accessibility").then(() => {
|
|
6089
|
+
panelOpened = true;
|
|
6090
|
+
}).catch(() => void 0);
|
|
6091
|
+
}
|
|
6092
|
+
missing.push({
|
|
6093
|
+
name: "Accessibility",
|
|
6094
|
+
reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
|
|
6095
|
+
pane: "accessibility",
|
|
6096
|
+
settingsUrl: ACCESSIBILITY_URL,
|
|
6097
|
+
fixSteps: [
|
|
6098
|
+
"In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
|
|
6099
|
+
"Click the + button",
|
|
6100
|
+
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6101
|
+
"Toggle the switch ON",
|
|
6102
|
+
"Restart the agent process so the new permission takes effect",
|
|
6103
|
+
"Then call enable_computer_use again"
|
|
6104
|
+
],
|
|
6105
|
+
prompted,
|
|
6106
|
+
panelOpened
|
|
6107
|
+
});
|
|
6108
|
+
}
|
|
6109
|
+
if (!screen) {
|
|
6110
|
+
let prompted = false;
|
|
6111
|
+
let panelOpened = false;
|
|
6112
|
+
if (request_permissions) {
|
|
6113
|
+
prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
|
|
6114
|
+
await openSystemSettings("screen-recording").then(() => {
|
|
6115
|
+
panelOpened = true;
|
|
6116
|
+
}).catch(() => void 0);
|
|
6117
|
+
}
|
|
6118
|
+
missing.push({
|
|
6119
|
+
name: "Screen Recording",
|
|
6120
|
+
reason: "CGPreflightScreenCaptureAccess returned false",
|
|
6121
|
+
pane: "screen-recording",
|
|
6122
|
+
settingsUrl: SCREEN_RECORDING_URL,
|
|
6123
|
+
fixSteps: [
|
|
6124
|
+
"In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
|
|
6125
|
+
"Click the + button",
|
|
6126
|
+
"Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
|
|
6127
|
+
"Toggle the switch ON",
|
|
6128
|
+
"Restart the agent process so the new permission takes effect",
|
|
6129
|
+
"Then call enable_computer_use again"
|
|
6130
|
+
],
|
|
6131
|
+
prompted,
|
|
6132
|
+
panelOpened
|
|
6133
|
+
});
|
|
6134
|
+
}
|
|
6135
|
+
if (missing.length > 0) {
|
|
6136
|
+
return {
|
|
6137
|
+
success: false,
|
|
6138
|
+
error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
|
|
6139
|
+
missingPermissions: missing,
|
|
6140
|
+
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."
|
|
6141
|
+
};
|
|
6142
|
+
}
|
|
6143
|
+
let width = display_width;
|
|
6144
|
+
let height = display_height;
|
|
6145
|
+
let detected = null;
|
|
6146
|
+
if (width === void 0 || height === void 0) {
|
|
6147
|
+
detected = await detectScreenSize();
|
|
6148
|
+
width = width ?? detected?.width ?? 1280;
|
|
6149
|
+
height = height ?? detected?.height ?? 800;
|
|
6150
|
+
}
|
|
6151
|
+
const session = await sessionQueries.getById(options.sessionId);
|
|
6152
|
+
if (!session) {
|
|
6153
|
+
return { success: false, error: "Session not found" };
|
|
6154
|
+
}
|
|
6155
|
+
const config = session.config || {};
|
|
6156
|
+
if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
|
|
6157
|
+
return {
|
|
6158
|
+
success: true,
|
|
6159
|
+
alreadyEnabled: true,
|
|
6160
|
+
message: "Computer use was already enabled for this session.",
|
|
6161
|
+
displayWidth: width,
|
|
6162
|
+
displayHeight: height
|
|
6163
|
+
};
|
|
6164
|
+
}
|
|
6165
|
+
const updated = {
|
|
6166
|
+
...config,
|
|
6167
|
+
computerUseEnabled: true,
|
|
6168
|
+
computerUseDisplayWidth: width,
|
|
6169
|
+
computerUseDisplayHeight: height
|
|
6170
|
+
};
|
|
6171
|
+
await sessionQueries.update(options.sessionId, { config: updated });
|
|
6172
|
+
return {
|
|
6173
|
+
success: true,
|
|
6174
|
+
enabled: true,
|
|
6175
|
+
platform: "darwin",
|
|
6176
|
+
displayWidth: width,
|
|
6177
|
+
displayHeight: height,
|
|
6178
|
+
detectedScreenSize: detected || void 0,
|
|
6179
|
+
permissions: {
|
|
6180
|
+
accessibility: "granted",
|
|
6181
|
+
screenRecording: "granted"
|
|
6182
|
+
},
|
|
6183
|
+
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.`
|
|
6184
|
+
};
|
|
6185
|
+
} catch (err) {
|
|
6186
|
+
return {
|
|
6187
|
+
success: false,
|
|
6188
|
+
error: err?.message || String(err)
|
|
6189
|
+
};
|
|
6190
|
+
}
|
|
6191
|
+
}
|
|
6192
|
+
});
|
|
6193
|
+
}
|
|
6194
|
+
|
|
5569
6195
|
// src/tools/index.ts
|
|
5570
6196
|
init_semantic();
|
|
5571
6197
|
init_remote();
|
|
@@ -5613,6 +6239,20 @@ async function createTools(options) {
|
|
|
5613
6239
|
sessionId: options.sessionId
|
|
5614
6240
|
});
|
|
5615
6241
|
}
|
|
6242
|
+
if (process.platform === "darwin") {
|
|
6243
|
+
if (options.enableComputerUse) {
|
|
6244
|
+
tools.computer = createComputerUseTool({
|
|
6245
|
+
workingDirectory: options.workingDirectory,
|
|
6246
|
+
sessionId: options.sessionId,
|
|
6247
|
+
displayWidth: options.computerUseDisplayWidth,
|
|
6248
|
+
displayHeight: options.computerUseDisplayHeight
|
|
6249
|
+
});
|
|
6250
|
+
} else {
|
|
6251
|
+
tools.enable_computer_use = createEnableComputerUseTool({
|
|
6252
|
+
sessionId: options.sessionId
|
|
6253
|
+
});
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
5616
6256
|
if (options.enableSemanticSearch !== false) {
|
|
5617
6257
|
try {
|
|
5618
6258
|
if (isVectorGatewayConfigured()) {
|
|
@@ -5629,6 +6269,7 @@ async function createTools(options) {
|
|
|
5629
6269
|
if (options.taskTools) {
|
|
5630
6270
|
tools.complete_task = createCompleteTaskTool(options.taskTools);
|
|
5631
6271
|
tools.task_failed = createTaskFailedTool(options.taskTools);
|
|
6272
|
+
tools.ask_question_to_user = createAskQuestionToUserTool(options.taskTools);
|
|
5632
6273
|
}
|
|
5633
6274
|
return tools;
|
|
5634
6275
|
}
|
|
@@ -6035,9 +6676,10 @@ If you need to give the user a downloadable file (report, image, export, etc.),
|
|
|
6035
6676
|
### Rules
|
|
6036
6677
|
1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
|
|
6037
6678
|
2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
|
|
6038
|
-
3.
|
|
6039
|
-
4.
|
|
6040
|
-
5.
|
|
6679
|
+
3. If you are blocked by missing information, call \`ask_question_to_user\` with a concise question. The run will pause until the orchestrator or user answers, then you should continue from that answer.
|
|
6680
|
+
4. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
6681
|
+
5. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
6682
|
+
6. Do NOT stop without calling \`complete_task\`, \`task_failed\`, or \`ask_question_to_user\` when blocked.
|
|
6041
6683
|
|
|
6042
6684
|
### Verification \u2014 BE EXTREMELY THOROUGH
|
|
6043
6685
|
Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
|
|
@@ -6108,6 +6750,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
6108
6750
|
### Completion Tools
|
|
6109
6751
|
- **\`complete_task({ result: ... })\`** \u2014 Call ONLY after thorough verification. The result is validated against the schema above. If validation fails you will get errors back \u2014 fix and retry.
|
|
6110
6752
|
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
6753
|
+
- **\`ask_question_to_user({ question, context?, choices? })\`** \u2014 Call only when you need information that is not available in the repo, task prompt, files, logs, or tools. Ask one clear question; after the answer is returned, continue working.
|
|
6111
6754
|
`;
|
|
6112
6755
|
}
|
|
6113
6756
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -6263,6 +6906,7 @@ function sanitizeModelMessages(messages) {
|
|
|
6263
6906
|
|
|
6264
6907
|
// src/agent/model-limits.ts
|
|
6265
6908
|
var MODEL_LIMITS = {
|
|
6909
|
+
"anthropic/claude-opus-4.7": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6266
6910
|
"anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6267
6911
|
"anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6268
6912
|
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
@@ -6598,7 +7242,14 @@ function repairToolPairing(messages) {
|
|
|
6598
7242
|
}
|
|
6599
7243
|
|
|
6600
7244
|
// src/utils/webhook.ts
|
|
7245
|
+
var TERMINAL_EVENTS = /* @__PURE__ */ new Set([
|
|
7246
|
+
"task.started",
|
|
7247
|
+
"task.completed",
|
|
7248
|
+
"task.failed"
|
|
7249
|
+
]);
|
|
7250
|
+
var QUIET = process.env.SPARKECODER_QUIET_WEBHOOKS === "1";
|
|
6601
7251
|
async function sendWebhook(url, event) {
|
|
7252
|
+
const t0 = Date.now();
|
|
6602
7253
|
try {
|
|
6603
7254
|
const controller = new AbortController();
|
|
6604
7255
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
@@ -6612,27 +7263,59 @@ async function sendWebhook(url, event) {
|
|
|
6612
7263
|
signal: controller.signal
|
|
6613
7264
|
});
|
|
6614
7265
|
clearTimeout(timeout);
|
|
7266
|
+
const ms = Date.now() - t0;
|
|
6615
7267
|
if (!response.ok) {
|
|
6616
|
-
|
|
7268
|
+
const body = await response.text().catch(() => "");
|
|
7269
|
+
console.warn(
|
|
7270
|
+
`[WEBHOOK] ${event.type} task=${event.taskId} -> HTTP ${response.status} in ${ms}ms${body ? ` (${body.slice(0, 200)})` : ""}`
|
|
7271
|
+
);
|
|
7272
|
+
return;
|
|
7273
|
+
}
|
|
7274
|
+
if (!QUIET || TERMINAL_EVENTS.has(event.type)) {
|
|
7275
|
+
console.log(
|
|
7276
|
+
`[WEBHOOK] ${event.type} task=${event.taskId} -> 200 in ${ms}ms`
|
|
7277
|
+
);
|
|
6617
7278
|
}
|
|
6618
7279
|
} catch (err) {
|
|
6619
7280
|
const reason = err.name === "AbortError" ? "timeout (5s)" : err.message;
|
|
6620
|
-
console.warn(
|
|
7281
|
+
console.warn(
|
|
7282
|
+
`[WEBHOOK] ${event.type} task=${event.taskId} -> failed: ${reason}`
|
|
7283
|
+
);
|
|
6621
7284
|
}
|
|
6622
7285
|
}
|
|
6623
7286
|
|
|
7287
|
+
// src/tasks/questions.ts
|
|
7288
|
+
var pendingQuestions = /* @__PURE__ */ new Map();
|
|
7289
|
+
function key(taskId, questionId) {
|
|
7290
|
+
return `${taskId}:${questionId}`;
|
|
7291
|
+
}
|
|
7292
|
+
function waitForTaskQuestionAnswer(question) {
|
|
7293
|
+
const k = key(question.taskId, question.questionId);
|
|
7294
|
+
if (pendingQuestions.has(k)) {
|
|
7295
|
+
return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
|
|
7296
|
+
}
|
|
7297
|
+
return new Promise((resolve10, reject) => {
|
|
7298
|
+
pendingQuestions.set(k, {
|
|
7299
|
+
...question,
|
|
7300
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
7301
|
+
resolve: resolve10,
|
|
7302
|
+
reject
|
|
7303
|
+
});
|
|
7304
|
+
});
|
|
7305
|
+
}
|
|
7306
|
+
|
|
6624
7307
|
// src/agent/index.ts
|
|
6625
7308
|
var MAX_SSE_FIELD_LENGTH = 8 * 1024;
|
|
6626
7309
|
var SSE_PREVIEW_LENGTH = 2 * 1024;
|
|
6627
7310
|
function truncateWriteFileInput(input) {
|
|
6628
7311
|
const out = { ...input };
|
|
6629
|
-
for (const
|
|
6630
|
-
const val = out[
|
|
7312
|
+
for (const key2 of ["content", "old_string", "new_string"]) {
|
|
7313
|
+
const val = out[key2];
|
|
6631
7314
|
if (typeof val === "string" && val.length > MAX_SSE_FIELD_LENGTH) {
|
|
6632
|
-
out[
|
|
7315
|
+
out[key2] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
|
|
6633
7316
|
... (truncated)`;
|
|
6634
|
-
out[`${
|
|
6635
|
-
out[`${
|
|
7317
|
+
out[`${key2}Truncated`] = true;
|
|
7318
|
+
out[`${key2}Length`] = val.length;
|
|
6636
7319
|
}
|
|
6637
7320
|
}
|
|
6638
7321
|
return out;
|
|
@@ -6660,10 +7343,14 @@ var Agent = class _Agent {
|
|
|
6660
7343
|
*/
|
|
6661
7344
|
async createToolsWithCallbacks(options) {
|
|
6662
7345
|
const config = getConfig();
|
|
7346
|
+
const sessionConfig = this.session.config || {};
|
|
6663
7347
|
return createTools({
|
|
6664
7348
|
sessionId: this.session.id,
|
|
6665
7349
|
workingDirectory: this.session.workingDirectory,
|
|
6666
7350
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
7351
|
+
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
7352
|
+
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
7353
|
+
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
|
|
6667
7354
|
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
6668
7355
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
6669
7356
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
@@ -6696,10 +7383,14 @@ var Agent = class _Agent {
|
|
|
6696
7383
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
6697
7384
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
6698
7385
|
});
|
|
7386
|
+
const sessionConfig = session.config || {};
|
|
6699
7387
|
const tools = await createTools({
|
|
6700
7388
|
sessionId: session.id,
|
|
6701
7389
|
workingDirectory: session.workingDirectory,
|
|
6702
|
-
skillsDirectories: config.resolvedSkillsDirectories
|
|
7390
|
+
skillsDirectories: config.resolvedSkillsDirectories,
|
|
7391
|
+
enableComputerUse: sessionConfig.computerUseEnabled === true,
|
|
7392
|
+
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
7393
|
+
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
6703
7394
|
});
|
|
6704
7395
|
return new _Agent(session, context, tools);
|
|
6705
7396
|
}
|
|
@@ -6794,13 +7485,7 @@ ${prompt}` });
|
|
|
6794
7485
|
abortSignal: options.abortSignal,
|
|
6795
7486
|
// Enable extended thinking/reasoning for models that support it
|
|
6796
7487
|
providerOptions: useAnthropic ? {
|
|
6797
|
-
anthropic: {
|
|
6798
|
-
toolStreaming: true,
|
|
6799
|
-
thinking: {
|
|
6800
|
-
type: "enabled",
|
|
6801
|
-
budgetTokens: 1e4
|
|
6802
|
-
}
|
|
6803
|
-
}
|
|
7488
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
6804
7489
|
} : void 0,
|
|
6805
7490
|
onStepFinish: async (step) => {
|
|
6806
7491
|
options.onStepFinish?.(step);
|
|
@@ -6847,12 +7532,7 @@ ${prompt}` });
|
|
|
6847
7532
|
stopWhen: stepCountIs2(500),
|
|
6848
7533
|
// Enable extended thinking/reasoning for models that support it
|
|
6849
7534
|
providerOptions: useAnthropic ? {
|
|
6850
|
-
anthropic:
|
|
6851
|
-
thinking: {
|
|
6852
|
-
type: "enabled",
|
|
6853
|
-
budgetTokens: 1e4
|
|
6854
|
-
}
|
|
6855
|
-
}
|
|
7535
|
+
anthropic: getAnthropicProviderOptions(this.session.model)
|
|
6856
7536
|
} : void 0
|
|
6857
7537
|
});
|
|
6858
7538
|
const responseMessages = result.response.messages;
|
|
@@ -6918,10 +7598,14 @@ ${prompt}` });
|
|
|
6918
7598
|
});
|
|
6919
7599
|
}
|
|
6920
7600
|
};
|
|
7601
|
+
const taskSessionConfig = this.session.config || {};
|
|
6921
7602
|
const taskTools = await createTools({
|
|
6922
7603
|
sessionId: this.session.id,
|
|
6923
7604
|
workingDirectory: this.session.workingDirectory,
|
|
6924
7605
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
7606
|
+
enableComputerUse: taskSessionConfig.computerUseEnabled === true,
|
|
7607
|
+
computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
|
|
7608
|
+
computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
|
|
6925
7609
|
onBashProgress: bashProgressHandler,
|
|
6926
7610
|
onWriteFileProgress: (progress) => {
|
|
6927
7611
|
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
@@ -6935,7 +7619,38 @@ ${prompt}` });
|
|
|
6935
7619
|
},
|
|
6936
7620
|
taskTools: {
|
|
6937
7621
|
outputSchema: options.taskConfig.outputSchema,
|
|
6938
|
-
onComplete
|
|
7622
|
+
onComplete,
|
|
7623
|
+
onQuestion: async (question) => {
|
|
7624
|
+
const payload = {
|
|
7625
|
+
questionId: question.questionId,
|
|
7626
|
+
question: question.question,
|
|
7627
|
+
context: question.context,
|
|
7628
|
+
choices: question.choices,
|
|
7629
|
+
status: "pending"
|
|
7630
|
+
};
|
|
7631
|
+
const answerPromise = waitForTaskQuestionAnswer({
|
|
7632
|
+
taskId: this.session.id,
|
|
7633
|
+
questionId: question.questionId,
|
|
7634
|
+
question: question.question,
|
|
7635
|
+
context: question.context,
|
|
7636
|
+
choices: question.choices
|
|
7637
|
+
});
|
|
7638
|
+
fireWebhook("task.question", payload);
|
|
7639
|
+
if (emit) {
|
|
7640
|
+
await emit(JSON.stringify({ type: "task-question", data: payload }));
|
|
7641
|
+
}
|
|
7642
|
+
const answer = await answerPromise;
|
|
7643
|
+
const answeredPayload = {
|
|
7644
|
+
questionId: question.questionId,
|
|
7645
|
+
answer: answer.answer,
|
|
7646
|
+
answeredBy: answer.answeredBy
|
|
7647
|
+
};
|
|
7648
|
+
fireWebhook("task.question_answered", answeredPayload);
|
|
7649
|
+
if (emit) {
|
|
7650
|
+
await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
|
|
7651
|
+
}
|
|
7652
|
+
return answer;
|
|
7653
|
+
}
|
|
6939
7654
|
}
|
|
6940
7655
|
});
|
|
6941
7656
|
const baseSystemPrompt = await buildSystemPrompt({
|
|
@@ -6980,10 +7695,7 @@ ${taskAddendum}`;
|
|
|
6980
7695
|
stopWhen: stepCountIs2(500),
|
|
6981
7696
|
abortSignal: options.abortSignal,
|
|
6982
7697
|
providerOptions: useAnthropic ? {
|
|
6983
|
-
anthropic: {
|
|
6984
|
-
toolStreaming: true,
|
|
6985
|
-
thinking: { type: "enabled", budgetTokens: 1e4 }
|
|
6986
|
-
}
|
|
7698
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
6987
7699
|
} : void 0,
|
|
6988
7700
|
onStepFinish: async (step) => {
|
|
6989
7701
|
options.onStepFinish?.(step);
|
|
@@ -7204,11 +7916,11 @@ ${taskAddendum}`;
|
|
|
7204
7916
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7205
7917
|
if (!isRemoteConfigured2()) return [];
|
|
7206
7918
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
7207
|
-
const { join:
|
|
7919
|
+
const { join: join10, basename: basename5 } = await import("path");
|
|
7208
7920
|
const urls = [];
|
|
7209
7921
|
for (const filePath of filePaths) {
|
|
7210
7922
|
try {
|
|
7211
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
7923
|
+
const fullPath = filePath.startsWith("/") ? filePath : join10(this.session.workingDirectory, filePath);
|
|
7212
7924
|
const fileName = basename5(fullPath);
|
|
7213
7925
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
7214
7926
|
const mimeMap = {
|
|
@@ -7266,11 +7978,11 @@ ${taskAddendum}`;
|
|
|
7266
7978
|
wrappedTools[name] = originalTool;
|
|
7267
7979
|
continue;
|
|
7268
7980
|
}
|
|
7269
|
-
wrappedTools[name] =
|
|
7981
|
+
wrappedTools[name] = tool14({
|
|
7270
7982
|
description: originalTool.description || "",
|
|
7271
|
-
inputSchema: originalTool.inputSchema ||
|
|
7983
|
+
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
7272
7984
|
execute: async (input, toolOptions) => {
|
|
7273
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
7985
|
+
const toolCallId = toolOptions.toolCallId || nanoid6();
|
|
7274
7986
|
const execution = toolExecutionQueries.create({
|
|
7275
7987
|
sessionId: this.session.id,
|
|
7276
7988
|
toolName: name,
|
|
@@ -7288,10 +8000,10 @@ ${taskAddendum}`;
|
|
|
7288
8000
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
7289
8001
|
approvalResolvers.delete(toolCallId);
|
|
7290
8002
|
this.pendingApprovals.delete(toolCallId);
|
|
7291
|
-
const
|
|
8003
|
+
const exec7 = await execution;
|
|
7292
8004
|
if (!approved) {
|
|
7293
8005
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
7294
|
-
await toolExecutionQueries.reject(
|
|
8006
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
7295
8007
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
7296
8008
|
return {
|
|
7297
8009
|
status: "rejected",
|
|
@@ -7301,14 +8013,14 @@ ${taskAddendum}`;
|
|
|
7301
8013
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
7302
8014
|
};
|
|
7303
8015
|
}
|
|
7304
|
-
await toolExecutionQueries.approve(
|
|
8016
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
7305
8017
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
7306
8018
|
try {
|
|
7307
8019
|
const result = await originalTool.execute(input, toolOptions);
|
|
7308
|
-
await toolExecutionQueries.complete(
|
|
8020
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
7309
8021
|
return result;
|
|
7310
8022
|
} catch (error) {
|
|
7311
|
-
await toolExecutionQueries.complete(
|
|
8023
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
7312
8024
|
throw error;
|
|
7313
8025
|
}
|
|
7314
8026
|
}
|