sparkecoder 0.1.64 → 0.1.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +815 -30
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +1042 -165
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js.map +1 -1
- package/dist/{index-Dn-eCGLe.d.ts → index-Db23cukG.d.ts} +35 -25
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1058 -146
- package/dist/index.js.map +1 -1
- package/dist/{schema-XcP0dedO.d.ts → schema-C7Mm4Ykn.d.ts} +3 -3
- package/dist/{search-DINnDTgj.d.ts → search-CVVfuBPZ.d.ts} +6 -4
- package/dist/server/index.js +1058 -146
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/qa.md +317 -0
- package/dist/tools/index.d.ts +31 -4
- package/dist/tools/index.js +433 -7
- package/dist/tools/index.js.map +1 -1
- package/package.json +3 -1
- package/src/skills/default/qa.md +317 -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.js.nft.json +1 -1
- 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.js.nft.json +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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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/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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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/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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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/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 +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5c78460e._.js → 2374f_02a118f9._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bfc8ef7d._.js → 2374f_0ed477f8._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_04a544c8._.js → 2374f_12bad06e._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_e366206f._.js → 2374f_2526ca80._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_45534372._.js → 2374f_3b51a934._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d5f5b9ba._.js → 2374f_3e519469._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5e9eb6da._.js → 2374f_5ebfcf1a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_db790cfe._.js → 2374f_a0f483d1._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c61a33b3._.js → 2374f_acf3dfe4._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ab5b97d8._.js → 2374f_ad08e83a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_68abddfe._.js → 2374f_c1d54c16._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_de60e6ea._.js → 2374f_db3e363b._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_72fb9db7._.js → 2374f_f0d7e130._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5d0b3394._.js → 2374f_fc992d90._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__aa788b85._.js → [root-of-the-server]__06818a54._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__d04c460d._.js → [root-of-the-server]__c71f29f9._.js} +4 -4
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_2b3a5919._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_38156da8._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_5cca707f._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_935e81f5._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_6fb589ac._.js → web_cc5f7515._.js} +2 -2
- 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/{eea48be65cdb3f4b.js → 31208ade542a0fcb.js} +3 -3
- package/web/.next/{static/chunks/054deec0c7b19894.js → standalone/web/.next/static/chunks/4e673433173ad456.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
- package/web/.next/standalone/web/.next/static/chunks/fd39dd62879495e1.css +1 -0
- package/web/.next/{static/chunks/eea48be65cdb3f4b.js → standalone/web/.next/static/static/chunks/31208ade542a0fcb.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/{054deec0c7b19894.js → 4e673433173ad456.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
- package/web/.next/standalone/web/.next/static/static/chunks/fd39dd62879495e1.css +1 -0
- package/web/.next/standalone/web/src/components/ai-elements/browser-recording.tsx +100 -0
- package/web/.next/standalone/web/src/components/browser-preview.tsx +196 -0
- package/web/.next/standalone/web/src/components/chat-interface.tsx +188 -4
- package/web/.next/standalone/web/src/lib/api.ts +119 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/eea48be65cdb3f4b.js → static/chunks/31208ade542a0fcb.js} +3 -3
- package/web/.next/{standalone/web/.next/static/chunks/054deec0c7b19894.js → static/chunks/4e673433173ad456.js} +3 -3
- package/web/.next/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
- package/web/.next/static/chunks/fd39dd62879495e1.css +1 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_08bbd8c8._.js +0 -7
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_c729ad51._.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/1f42a42914068041.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/1f42a42914068041.css +0 -1
- package/web/.next/static/chunks/1f42a42914068041.css +0 -1
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
package/dist/server/index.js
CHANGED
|
@@ -9,6 +9,25 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/db/remote.ts
|
|
12
|
+
var remote_exports = {};
|
|
13
|
+
__export(remote_exports, {
|
|
14
|
+
closeRemoteDatabase: () => closeRemoteDatabase,
|
|
15
|
+
initRemoteDatabase: () => initRemoteDatabase,
|
|
16
|
+
isRemoteConfigured: () => isRemoteConfigured,
|
|
17
|
+
remoteActiveStreamQueries: () => remoteActiveStreamQueries,
|
|
18
|
+
remoteCheckpointQueries: () => remoteCheckpointQueries,
|
|
19
|
+
remoteFileBackupQueries: () => remoteFileBackupQueries,
|
|
20
|
+
remoteIndexStatusQueries: () => remoteIndexStatusQueries,
|
|
21
|
+
remoteIndexedChunkQueries: () => remoteIndexedChunkQueries,
|
|
22
|
+
remoteMessageQueries: () => remoteMessageQueries,
|
|
23
|
+
remoteSessionQueries: () => remoteSessionQueries,
|
|
24
|
+
remoteSkillQueries: () => remoteSkillQueries,
|
|
25
|
+
remoteSubagentQueries: () => remoteSubagentQueries,
|
|
26
|
+
remoteTerminalQueries: () => remoteTerminalQueries,
|
|
27
|
+
remoteTodoQueries: () => remoteTodoQueries,
|
|
28
|
+
remoteToolExecutionQueries: () => remoteToolExecutionQueries,
|
|
29
|
+
storageQueries: () => storageQueries
|
|
30
|
+
});
|
|
12
31
|
function initRemoteDatabase(serverUrl, key) {
|
|
13
32
|
remoteServerUrl = serverUrl.replace(/\/$/, "");
|
|
14
33
|
authKey = key;
|
|
@@ -17,6 +36,9 @@ function closeRemoteDatabase() {
|
|
|
17
36
|
remoteServerUrl = null;
|
|
18
37
|
authKey = null;
|
|
19
38
|
}
|
|
39
|
+
function isRemoteConfigured() {
|
|
40
|
+
return !!remoteServerUrl && !!authKey;
|
|
41
|
+
}
|
|
20
42
|
function parseDates(obj) {
|
|
21
43
|
if (obj === null || obj === void 0) return obj;
|
|
22
44
|
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
@@ -64,7 +86,29 @@ async function api(path, options = {}) {
|
|
|
64
86
|
}
|
|
65
87
|
return parseDates(parsed);
|
|
66
88
|
}
|
|
67
|
-
|
|
89
|
+
async function storageApi(path, options = {}) {
|
|
90
|
+
if (!remoteServerUrl || !authKey) {
|
|
91
|
+
throw new Error("Remote database not initialized");
|
|
92
|
+
}
|
|
93
|
+
const url = `${remoteServerUrl}/storage${path}`;
|
|
94
|
+
const init = {
|
|
95
|
+
method: options.method || "GET",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
"Authorization": `Bearer ${authKey}`
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
if (options.body) {
|
|
102
|
+
init.body = JSON.stringify(options.body);
|
|
103
|
+
}
|
|
104
|
+
const response = await fetch(url, init);
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
107
|
+
throw new Error(`Storage API error ${response.status}: ${errorText}`);
|
|
108
|
+
}
|
|
109
|
+
return response.json();
|
|
110
|
+
}
|
|
111
|
+
var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteTerminalQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries, storageQueries;
|
|
68
112
|
var init_remote = __esm({
|
|
69
113
|
"src/db/remote.ts"() {
|
|
70
114
|
"use strict";
|
|
@@ -203,6 +247,34 @@ var init_remote = __esm({
|
|
|
203
247
|
return result.isLoaded;
|
|
204
248
|
}
|
|
205
249
|
};
|
|
250
|
+
remoteTerminalQueries = {
|
|
251
|
+
create(data) {
|
|
252
|
+
return api("/terminals", { method: "POST", body: data });
|
|
253
|
+
},
|
|
254
|
+
getById(id) {
|
|
255
|
+
return api(`/terminals/${id}`).catch(() => void 0);
|
|
256
|
+
},
|
|
257
|
+
getBySession(sessionId) {
|
|
258
|
+
return api(`/terminals/session/${sessionId}`);
|
|
259
|
+
},
|
|
260
|
+
getRunning(sessionId) {
|
|
261
|
+
return api(`/terminals/session/${sessionId}/running`);
|
|
262
|
+
},
|
|
263
|
+
updateStatus(id, status, exitCode, error) {
|
|
264
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { status, exitCode, error } });
|
|
265
|
+
},
|
|
266
|
+
updatePid(id, pid) {
|
|
267
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { pid } });
|
|
268
|
+
},
|
|
269
|
+
async delete(id) {
|
|
270
|
+
const result = await api(`/terminals/${id}`, { method: "DELETE" });
|
|
271
|
+
return result?.success ?? false;
|
|
272
|
+
},
|
|
273
|
+
async deleteBySession(sessionId) {
|
|
274
|
+
const result = await api(`/terminals/session/${sessionId}`, { method: "DELETE" });
|
|
275
|
+
return result.deleted;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
206
278
|
remoteActiveStreamQueries = {
|
|
207
279
|
create(sessionId, streamId) {
|
|
208
280
|
return api("/streams", { method: "POST", body: { sessionId, streamId } });
|
|
@@ -306,6 +378,41 @@ var init_remote = __esm({
|
|
|
306
378
|
return result.deleted;
|
|
307
379
|
}
|
|
308
380
|
};
|
|
381
|
+
remoteIndexedChunkQueries = {
|
|
382
|
+
upsert(_db, data) {
|
|
383
|
+
return api("/indexed-chunks", { method: "POST", body: data });
|
|
384
|
+
},
|
|
385
|
+
batchUpsert(_db, chunks) {
|
|
386
|
+
return api("/indexed-chunks/batch", {
|
|
387
|
+
method: "POST",
|
|
388
|
+
body: { chunks }
|
|
389
|
+
});
|
|
390
|
+
},
|
|
391
|
+
getById(_db, id) {
|
|
392
|
+
return api(`/indexed-chunks/${id}`).catch(() => void 0);
|
|
393
|
+
},
|
|
394
|
+
getByNamespace(_db, namespace) {
|
|
395
|
+
return api(`/indexed-chunks/namespace/${namespace}`);
|
|
396
|
+
},
|
|
397
|
+
getByFilePath(_db, namespace, filePath) {
|
|
398
|
+
return api(`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`);
|
|
399
|
+
},
|
|
400
|
+
async deleteByNamespace(_db, namespace) {
|
|
401
|
+
const result = await api(`/indexed-chunks/namespace/${namespace}`, { method: "DELETE" });
|
|
402
|
+
return result.deleted;
|
|
403
|
+
},
|
|
404
|
+
async deleteByFilePath(_db, namespace, filePath) {
|
|
405
|
+
const result = await api(
|
|
406
|
+
`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`,
|
|
407
|
+
{ method: "DELETE" }
|
|
408
|
+
);
|
|
409
|
+
return result.deleted;
|
|
410
|
+
},
|
|
411
|
+
async countByNamespace(_db, namespace) {
|
|
412
|
+
const result = await api(`/indexed-chunks/namespace/${namespace}/count`);
|
|
413
|
+
return result.count;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
309
416
|
remoteIndexStatusQueries = {
|
|
310
417
|
upsert(_db, data) {
|
|
311
418
|
return api("/index-status", {
|
|
@@ -328,6 +435,27 @@ var init_remote = __esm({
|
|
|
328
435
|
return api("/index-status");
|
|
329
436
|
}
|
|
330
437
|
};
|
|
438
|
+
storageQueries = {
|
|
439
|
+
async getUploadUrl(sessionId, fileName, contentType, category) {
|
|
440
|
+
return storageApi("/upload-url", {
|
|
441
|
+
method: "POST",
|
|
442
|
+
body: { sessionId, fileName, contentType, category }
|
|
443
|
+
});
|
|
444
|
+
},
|
|
445
|
+
async getSessionFiles(sessionId) {
|
|
446
|
+
const result = await storageApi(`/files/${sessionId}`);
|
|
447
|
+
return result.files;
|
|
448
|
+
},
|
|
449
|
+
async getDownloadUrl(fileId) {
|
|
450
|
+
return storageApi(`/download/${fileId}`);
|
|
451
|
+
},
|
|
452
|
+
async deleteFile(fileId) {
|
|
453
|
+
await storageApi(`/files/${fileId}`, { method: "DELETE" });
|
|
454
|
+
},
|
|
455
|
+
async updateFile(fileId, data) {
|
|
456
|
+
await storageApi(`/files/${fileId}`, { method: "PATCH", body: data });
|
|
457
|
+
}
|
|
458
|
+
};
|
|
331
459
|
}
|
|
332
460
|
});
|
|
333
461
|
|
|
@@ -1645,6 +1773,280 @@ var init_webhook = __esm({
|
|
|
1645
1773
|
}
|
|
1646
1774
|
});
|
|
1647
1775
|
|
|
1776
|
+
// src/browser/stream-proxy.ts
|
|
1777
|
+
var stream_proxy_exports = {};
|
|
1778
|
+
__export(stream_proxy_exports, {
|
|
1779
|
+
BrowserStreamProxy: () => BrowserStreamProxy,
|
|
1780
|
+
destroyProxy: () => destroyProxy,
|
|
1781
|
+
getOrCreateProxy: () => getOrCreateProxy,
|
|
1782
|
+
getProxy: () => getProxy
|
|
1783
|
+
});
|
|
1784
|
+
import WebSocket from "ws";
|
|
1785
|
+
import { EventEmitter } from "events";
|
|
1786
|
+
function getOrCreateProxy(sessionId, port) {
|
|
1787
|
+
const existing = activeProxies.get(sessionId);
|
|
1788
|
+
if (existing) return existing;
|
|
1789
|
+
const proxy = new BrowserStreamProxy(port);
|
|
1790
|
+
activeProxies.set(sessionId, proxy);
|
|
1791
|
+
proxy.on("close", () => {
|
|
1792
|
+
activeProxies.delete(sessionId);
|
|
1793
|
+
});
|
|
1794
|
+
proxy.connect();
|
|
1795
|
+
return proxy;
|
|
1796
|
+
}
|
|
1797
|
+
function getProxy(sessionId) {
|
|
1798
|
+
return activeProxies.get(sessionId);
|
|
1799
|
+
}
|
|
1800
|
+
function destroyProxy(sessionId) {
|
|
1801
|
+
const proxy = activeProxies.get(sessionId);
|
|
1802
|
+
if (proxy) {
|
|
1803
|
+
proxy.destroy();
|
|
1804
|
+
activeProxies.delete(sessionId);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
|
|
1808
|
+
var init_stream_proxy = __esm({
|
|
1809
|
+
"src/browser/stream-proxy.ts"() {
|
|
1810
|
+
"use strict";
|
|
1811
|
+
RECONNECT_DELAY_MS = 1e3;
|
|
1812
|
+
MAX_RECONNECT_ATTEMPTS = 20;
|
|
1813
|
+
FRAME_THROTTLE_MS = 100;
|
|
1814
|
+
BrowserStreamProxy = class extends EventEmitter {
|
|
1815
|
+
ws = null;
|
|
1816
|
+
port;
|
|
1817
|
+
reconnectAttempts = 0;
|
|
1818
|
+
reconnectTimer = null;
|
|
1819
|
+
destroyed = false;
|
|
1820
|
+
lastFrameTime = 0;
|
|
1821
|
+
_latestFrame = null;
|
|
1822
|
+
_connected = false;
|
|
1823
|
+
constructor(port) {
|
|
1824
|
+
super();
|
|
1825
|
+
this.port = port;
|
|
1826
|
+
}
|
|
1827
|
+
get connected() {
|
|
1828
|
+
return this._connected;
|
|
1829
|
+
}
|
|
1830
|
+
get latestFrame() {
|
|
1831
|
+
return this._latestFrame;
|
|
1832
|
+
}
|
|
1833
|
+
connect() {
|
|
1834
|
+
if (this.destroyed) return;
|
|
1835
|
+
this.doConnect();
|
|
1836
|
+
}
|
|
1837
|
+
doConnect() {
|
|
1838
|
+
if (this.destroyed) return;
|
|
1839
|
+
const url = `ws://localhost:${this.port}`;
|
|
1840
|
+
try {
|
|
1841
|
+
this.ws = new WebSocket(url);
|
|
1842
|
+
} catch {
|
|
1843
|
+
this.scheduleReconnect();
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
this.ws.on("open", () => {
|
|
1847
|
+
this.reconnectAttempts = 0;
|
|
1848
|
+
this._connected = true;
|
|
1849
|
+
this.emit("status", {
|
|
1850
|
+
connected: true,
|
|
1851
|
+
screencasting: true
|
|
1852
|
+
});
|
|
1853
|
+
});
|
|
1854
|
+
this.ws.on("message", (raw) => {
|
|
1855
|
+
try {
|
|
1856
|
+
const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
1857
|
+
this.handleMessage(msg);
|
|
1858
|
+
} catch {
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
this.ws.on("close", () => {
|
|
1862
|
+
const wasConnected = this._connected;
|
|
1863
|
+
this._connected = false;
|
|
1864
|
+
if (wasConnected) {
|
|
1865
|
+
this.emit("status", { connected: false, screencasting: false });
|
|
1866
|
+
}
|
|
1867
|
+
if (!this.destroyed) {
|
|
1868
|
+
this.scheduleReconnect();
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
this.ws.on("error", () => {
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
handleMessage(msg) {
|
|
1875
|
+
if (msg.type === "frame") {
|
|
1876
|
+
const now = Date.now();
|
|
1877
|
+
if (now - this.lastFrameTime < FRAME_THROTTLE_MS) return;
|
|
1878
|
+
this.lastFrameTime = now;
|
|
1879
|
+
const frame = {
|
|
1880
|
+
data: msg.data,
|
|
1881
|
+
metadata: msg.metadata ?? {
|
|
1882
|
+
deviceWidth: 1280,
|
|
1883
|
+
deviceHeight: 720,
|
|
1884
|
+
pageScaleFactor: 1,
|
|
1885
|
+
offsetTop: 0,
|
|
1886
|
+
scrollOffsetX: 0,
|
|
1887
|
+
scrollOffsetY: 0
|
|
1888
|
+
},
|
|
1889
|
+
timestamp: now
|
|
1890
|
+
};
|
|
1891
|
+
this._latestFrame = frame;
|
|
1892
|
+
this.emit("frame", frame);
|
|
1893
|
+
} else if (msg.type === "status") {
|
|
1894
|
+
this.emit("status", {
|
|
1895
|
+
connected: msg.connected ?? true,
|
|
1896
|
+
screencasting: msg.screencasting ?? true,
|
|
1897
|
+
viewportWidth: msg.viewportWidth,
|
|
1898
|
+
viewportHeight: msg.viewportHeight
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
scheduleReconnect() {
|
|
1903
|
+
if (this.destroyed || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
1904
|
+
this.emit("close");
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
this.reconnectAttempts++;
|
|
1908
|
+
const delay = this.reconnectAttempts <= 5 ? RECONNECT_DELAY_MS : RECONNECT_DELAY_MS * (this.reconnectAttempts - 4);
|
|
1909
|
+
this.reconnectTimer = setTimeout(() => this.doConnect(), delay);
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Send an input event to the browser for pair-browsing.
|
|
1913
|
+
*/
|
|
1914
|
+
injectInput(event) {
|
|
1915
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1916
|
+
this.ws.send(JSON.stringify(event));
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
destroy() {
|
|
1920
|
+
this.destroyed = true;
|
|
1921
|
+
if (this.reconnectTimer) {
|
|
1922
|
+
clearTimeout(this.reconnectTimer);
|
|
1923
|
+
this.reconnectTimer = null;
|
|
1924
|
+
}
|
|
1925
|
+
if (this.ws) {
|
|
1926
|
+
this.ws.removeAllListeners();
|
|
1927
|
+
this.ws.close();
|
|
1928
|
+
this.ws = null;
|
|
1929
|
+
}
|
|
1930
|
+
this._connected = false;
|
|
1931
|
+
this.removeAllListeners();
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
activeProxies = /* @__PURE__ */ new Map();
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
// src/browser/recorder.ts
|
|
1939
|
+
var recorder_exports = {};
|
|
1940
|
+
__export(recorder_exports, {
|
|
1941
|
+
FrameRecorder: () => FrameRecorder
|
|
1942
|
+
});
|
|
1943
|
+
import { exec as exec5 } from "child_process";
|
|
1944
|
+
import { promisify as promisify5 } from "util";
|
|
1945
|
+
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1946
|
+
import { join as join6 } from "path";
|
|
1947
|
+
import { tmpdir } from "os";
|
|
1948
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
1949
|
+
async function checkFfmpeg() {
|
|
1950
|
+
try {
|
|
1951
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
1952
|
+
return true;
|
|
1953
|
+
} catch {
|
|
1954
|
+
return false;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
async function cleanup(dir) {
|
|
1958
|
+
try {
|
|
1959
|
+
await rm(dir, { recursive: true, force: true });
|
|
1960
|
+
} catch {
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
var execAsync5, FrameRecorder;
|
|
1964
|
+
var init_recorder = __esm({
|
|
1965
|
+
"src/browser/recorder.ts"() {
|
|
1966
|
+
"use strict";
|
|
1967
|
+
execAsync5 = promisify5(exec5);
|
|
1968
|
+
FrameRecorder = class {
|
|
1969
|
+
frames = [];
|
|
1970
|
+
startTime = null;
|
|
1971
|
+
recording = false;
|
|
1972
|
+
sessionId;
|
|
1973
|
+
constructor(sessionId) {
|
|
1974
|
+
this.sessionId = sessionId;
|
|
1975
|
+
}
|
|
1976
|
+
get isRecording() {
|
|
1977
|
+
return this.recording;
|
|
1978
|
+
}
|
|
1979
|
+
get frameCount() {
|
|
1980
|
+
return this.frames.length;
|
|
1981
|
+
}
|
|
1982
|
+
start() {
|
|
1983
|
+
this.frames = [];
|
|
1984
|
+
this.startTime = Date.now();
|
|
1985
|
+
this.recording = true;
|
|
1986
|
+
}
|
|
1987
|
+
addFrame(frame) {
|
|
1988
|
+
if (!this.recording) return;
|
|
1989
|
+
this.frames.push({
|
|
1990
|
+
data: Buffer.from(frame.data, "base64"),
|
|
1991
|
+
timestamp: frame.timestamp
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
stop() {
|
|
1995
|
+
this.recording = false;
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Encode recorded frames into an MP4 using ffmpeg.
|
|
1999
|
+
* Returns the file path to the generated MP4, or null if encoding fails.
|
|
2000
|
+
*/
|
|
2001
|
+
async encode() {
|
|
2002
|
+
if (this.frames.length === 0) return null;
|
|
2003
|
+
const workDir = join6(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
|
|
2004
|
+
await mkdir4(workDir, { recursive: true });
|
|
2005
|
+
try {
|
|
2006
|
+
for (let i = 0; i < this.frames.length; i++) {
|
|
2007
|
+
const framePath = join6(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
2008
|
+
await writeFile4(framePath, this.frames[i].data);
|
|
2009
|
+
}
|
|
2010
|
+
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
2011
|
+
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
2012
|
+
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
2013
|
+
const outputPath = join6(workDir, `recording_${this.sessionId}.mp4`);
|
|
2014
|
+
const hasFfmpeg = await checkFfmpeg();
|
|
2015
|
+
if (hasFfmpeg) {
|
|
2016
|
+
await execAsync5(
|
|
2017
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join6(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
2018
|
+
{ timeout: 12e4 }
|
|
2019
|
+
);
|
|
2020
|
+
} else {
|
|
2021
|
+
console.warn("[RECORDER] ffmpeg not available, cannot encode recording");
|
|
2022
|
+
await cleanup(workDir);
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
const outputBuf = await readFile10(outputPath);
|
|
2026
|
+
const files = await readdir5(workDir);
|
|
2027
|
+
for (const f of files) {
|
|
2028
|
+
if (f.startsWith("frame_")) {
|
|
2029
|
+
await unlink2(join6(workDir, f)).catch(() => {
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
return { path: outputPath, sizeBytes: outputBuf.length };
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
console.error("[RECORDER] Failed to encode recording:", error);
|
|
2036
|
+
await cleanup(workDir);
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
/** Discard all frames and free memory */
|
|
2041
|
+
clear() {
|
|
2042
|
+
this.frames = [];
|
|
2043
|
+
this.startTime = null;
|
|
2044
|
+
this.recording = false;
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
|
|
1648
2050
|
// src/server/index.ts
|
|
1649
2051
|
import "dotenv/config";
|
|
1650
2052
|
import { Hono as Hono6 } from "hono";
|
|
@@ -1652,7 +2054,7 @@ import { serve } from "@hono/node-server";
|
|
|
1652
2054
|
import { cors } from "hono/cors";
|
|
1653
2055
|
import { logger } from "hono/logger";
|
|
1654
2056
|
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1655
|
-
import { resolve as resolve10, dirname as dirname7, join as
|
|
2057
|
+
import { resolve as resolve10, dirname as dirname7, join as join10 } from "path";
|
|
1656
2058
|
import { spawn as spawn2 } from "child_process";
|
|
1657
2059
|
import { createServer as createNetServer } from "net";
|
|
1658
2060
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -1661,17 +2063,17 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
1661
2063
|
init_db();
|
|
1662
2064
|
import { Hono } from "hono";
|
|
1663
2065
|
import { zValidator } from "@hono/zod-validator";
|
|
1664
|
-
import { z as
|
|
2066
|
+
import { z as z15 } from "zod";
|
|
1665
2067
|
import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
|
|
1666
|
-
import { readdir as
|
|
1667
|
-
import { join as
|
|
1668
|
-
import { nanoid as
|
|
2068
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
2069
|
+
import { join as join7, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
2070
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
1669
2071
|
|
|
1670
2072
|
// src/agent/index.ts
|
|
1671
2073
|
import {
|
|
1672
2074
|
streamText as streamText2,
|
|
1673
2075
|
generateText as generateText3,
|
|
1674
|
-
tool as
|
|
2076
|
+
tool as tool13,
|
|
1675
2077
|
stepCountIs as stepCountIs2
|
|
1676
2078
|
} from "ai";
|
|
1677
2079
|
|
|
@@ -1852,8 +2254,8 @@ var SUBAGENT_MODELS = {
|
|
|
1852
2254
|
// src/agent/index.ts
|
|
1853
2255
|
init_db();
|
|
1854
2256
|
init_config();
|
|
1855
|
-
import { z as
|
|
1856
|
-
import { nanoid as
|
|
2257
|
+
import { z as z14 } from "zod";
|
|
2258
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1857
2259
|
|
|
1858
2260
|
// src/tools/bash.ts
|
|
1859
2261
|
import { tool } from "ai";
|
|
@@ -2139,8 +2541,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
|
|
|
2139
2541
|
const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
|
|
2140
2542
|
const terminals2 = [];
|
|
2141
2543
|
try {
|
|
2142
|
-
const { readdir:
|
|
2143
|
-
const entries = await
|
|
2544
|
+
const { readdir: readdir7 } = await import("fs/promises");
|
|
2545
|
+
const entries = await readdir7(terminalsDir, { withFileTypes: true });
|
|
2144
2546
|
for (const entry of entries) {
|
|
2145
2547
|
if (entry.isDirectory()) {
|
|
2146
2548
|
const meta = await getMeta(entry.name, workingDirectory, sessionId);
|
|
@@ -2202,6 +2604,29 @@ function isBlockedCommand(command) {
|
|
|
2202
2604
|
(blocked) => normalizedCommand.includes(blocked.toLowerCase())
|
|
2203
2605
|
);
|
|
2204
2606
|
}
|
|
2607
|
+
var BROWSER_STREAM_BASE_PORT = 9223;
|
|
2608
|
+
var sessionBrowserPorts = /* @__PURE__ */ new Map();
|
|
2609
|
+
var nextPortOffset = 0;
|
|
2610
|
+
function getBrowserStreamPort(sessionId) {
|
|
2611
|
+
let port = sessionBrowserPorts.get(sessionId);
|
|
2612
|
+
if (!port) {
|
|
2613
|
+
port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
|
|
2614
|
+
sessionBrowserPorts.set(sessionId, port);
|
|
2615
|
+
}
|
|
2616
|
+
return port;
|
|
2617
|
+
}
|
|
2618
|
+
function hasAgentBrowserCommand(command) {
|
|
2619
|
+
return /\bagent-browser\b/.test(command);
|
|
2620
|
+
}
|
|
2621
|
+
function isAgentBrowserCloseCommand(command) {
|
|
2622
|
+
return /\bagent-browser\s+(close|close\s+--all)\b/.test(command);
|
|
2623
|
+
}
|
|
2624
|
+
function injectBrowserStreamPort(command, port) {
|
|
2625
|
+
return command.replace(
|
|
2626
|
+
/\bagent-browser\b/g,
|
|
2627
|
+
`AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2205
2630
|
var bashInputSchema = z2.object({
|
|
2206
2631
|
command: z2.string().optional().describe("The command to execute. Required for running new commands."),
|
|
2207
2632
|
background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
|
|
@@ -2367,6 +2792,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2367
2792
|
exitCode: 1
|
|
2368
2793
|
};
|
|
2369
2794
|
}
|
|
2795
|
+
let actualCommand = command;
|
|
2796
|
+
const hasAgentBrowser = hasAgentBrowserCommand(command);
|
|
2797
|
+
const browserClose = isAgentBrowserCloseCommand(command);
|
|
2798
|
+
let browserPort;
|
|
2799
|
+
if (hasAgentBrowser) {
|
|
2800
|
+
browserPort = getBrowserStreamPort(options.sessionId);
|
|
2801
|
+
if (!browserClose) {
|
|
2802
|
+
actualCommand = injectBrowserStreamPort(command, browserPort);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2370
2805
|
const canUseTmux = await shouldUseTmux();
|
|
2371
2806
|
if (background) {
|
|
2372
2807
|
if (!canUseTmux) {
|
|
@@ -2376,8 +2811,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2376
2811
|
};
|
|
2377
2812
|
}
|
|
2378
2813
|
const terminalId = generateTerminalId();
|
|
2379
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
2380
|
-
const result = await runBackground(
|
|
2814
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
2815
|
+
const result = await runBackground(actualCommand, options.workingDirectory, {
|
|
2381
2816
|
sessionId: options.sessionId,
|
|
2382
2817
|
terminalId
|
|
2383
2818
|
});
|
|
@@ -2390,16 +2825,22 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2390
2825
|
}
|
|
2391
2826
|
if (canUseTmux) {
|
|
2392
2827
|
const terminalId = generateTerminalId();
|
|
2393
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
2828
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
2394
2829
|
try {
|
|
2395
|
-
const result = await runSync(
|
|
2830
|
+
const result = await runSync(actualCommand, options.workingDirectory, {
|
|
2396
2831
|
sessionId: options.sessionId,
|
|
2397
2832
|
timeout: COMMAND_TIMEOUT,
|
|
2398
2833
|
terminalId
|
|
2399
2834
|
});
|
|
2400
2835
|
const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
|
|
2401
2836
|
options.onOutput?.(truncatedOutput);
|
|
2402
|
-
options.onProgress?.({
|
|
2837
|
+
options.onProgress?.({
|
|
2838
|
+
terminalId,
|
|
2839
|
+
status: "completed",
|
|
2840
|
+
command,
|
|
2841
|
+
browserStreamPort: browserPort,
|
|
2842
|
+
browserClosed: browserClose || void 0
|
|
2843
|
+
});
|
|
2403
2844
|
return {
|
|
2404
2845
|
success: result.exitCode === 0,
|
|
2405
2846
|
id: result.id,
|
|
@@ -2417,7 +2858,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2417
2858
|
};
|
|
2418
2859
|
}
|
|
2419
2860
|
} else {
|
|
2420
|
-
const result = await execFallback(
|
|
2861
|
+
const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
|
|
2421
2862
|
return {
|
|
2422
2863
|
success: result.success,
|
|
2423
2864
|
output: result.output,
|
|
@@ -2792,12 +3233,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2792
3233
|
}
|
|
2793
3234
|
async function commandExists(cmd) {
|
|
2794
3235
|
try {
|
|
2795
|
-
const { exec:
|
|
2796
|
-
const { promisify:
|
|
2797
|
-
const
|
|
3236
|
+
const { exec: exec7 } = await import("child_process");
|
|
3237
|
+
const { promisify: promisify7 } = await import("util");
|
|
3238
|
+
const execAsync7 = promisify7(exec7);
|
|
2798
3239
|
const isWindows = process.platform === "win32";
|
|
2799
3240
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2800
|
-
await
|
|
3241
|
+
await execAsync7(checkCmd);
|
|
2801
3242
|
return true;
|
|
2802
3243
|
} catch {
|
|
2803
3244
|
return false;
|
|
@@ -3095,7 +3536,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3095
3536
|
const startTime = Date.now();
|
|
3096
3537
|
let debounceTimer;
|
|
3097
3538
|
let resolved = false;
|
|
3098
|
-
const
|
|
3539
|
+
const cleanup2 = () => {
|
|
3099
3540
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
3100
3541
|
const listeners = diagnosticListeners.get(normalized);
|
|
3101
3542
|
if (listeners) {
|
|
@@ -3109,7 +3550,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3109
3550
|
const finish = () => {
|
|
3110
3551
|
if (resolved) return;
|
|
3111
3552
|
resolved = true;
|
|
3112
|
-
|
|
3553
|
+
cleanup2();
|
|
3113
3554
|
resolve11(diagnostics.get(normalized) || []);
|
|
3114
3555
|
};
|
|
3115
3556
|
const onDiagnostic = () => {
|
|
@@ -5214,8 +5655,106 @@ function createTaskFailedTool(options) {
|
|
|
5214
5655
|
});
|
|
5215
5656
|
}
|
|
5216
5657
|
|
|
5658
|
+
// src/tools/upload-file.ts
|
|
5659
|
+
import { tool as tool12 } from "ai";
|
|
5660
|
+
import { z as z13 } from "zod";
|
|
5661
|
+
import { readFile as readFile9, stat as stat4 } from "fs/promises";
|
|
5662
|
+
import { join as join5, basename as basename4, extname as extname7 } from "path";
|
|
5663
|
+
var MIME_TYPES = {
|
|
5664
|
+
".txt": "text/plain",
|
|
5665
|
+
".md": "text/markdown",
|
|
5666
|
+
".html": "text/html",
|
|
5667
|
+
".css": "text/css",
|
|
5668
|
+
".js": "application/javascript",
|
|
5669
|
+
".ts": "application/typescript",
|
|
5670
|
+
".json": "application/json",
|
|
5671
|
+
".csv": "text/csv",
|
|
5672
|
+
".xml": "application/xml",
|
|
5673
|
+
".pdf": "application/pdf",
|
|
5674
|
+
".png": "image/png",
|
|
5675
|
+
".jpg": "image/jpeg",
|
|
5676
|
+
".jpeg": "image/jpeg",
|
|
5677
|
+
".gif": "image/gif",
|
|
5678
|
+
".webp": "image/webp",
|
|
5679
|
+
".svg": "image/svg+xml",
|
|
5680
|
+
".mp4": "video/mp4",
|
|
5681
|
+
".webm": "video/webm",
|
|
5682
|
+
".mp3": "audio/mpeg",
|
|
5683
|
+
".wav": "audio/wav",
|
|
5684
|
+
".zip": "application/zip",
|
|
5685
|
+
".tar": "application/x-tar",
|
|
5686
|
+
".gz": "application/gzip"
|
|
5687
|
+
};
|
|
5688
|
+
function createUploadFileTool(options) {
|
|
5689
|
+
return tool12({
|
|
5690
|
+
description: `Upload a file to cloud storage and get back a shareable download URL. Use this when the user needs a hosted link to a file (e.g. a generated report, image, export, or any artifact they want to download or share). The file must already exist on disk.`,
|
|
5691
|
+
inputSchema: z13.object({
|
|
5692
|
+
path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
|
|
5693
|
+
name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
|
|
5694
|
+
}),
|
|
5695
|
+
execute: async (input) => {
|
|
5696
|
+
try {
|
|
5697
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
5698
|
+
if (!isRemoteConfigured2()) {
|
|
5699
|
+
return {
|
|
5700
|
+
success: false,
|
|
5701
|
+
error: "File upload is not available \u2014 remote server with GCS is not configured."
|
|
5702
|
+
};
|
|
5703
|
+
}
|
|
5704
|
+
const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
|
|
5705
|
+
try {
|
|
5706
|
+
await stat4(fullPath);
|
|
5707
|
+
} catch {
|
|
5708
|
+
return {
|
|
5709
|
+
success: false,
|
|
5710
|
+
error: `File not found: ${input.path}`
|
|
5711
|
+
};
|
|
5712
|
+
}
|
|
5713
|
+
const fileName = input.name || basename4(fullPath);
|
|
5714
|
+
const ext = extname7(fullPath).toLowerCase();
|
|
5715
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
5716
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
5717
|
+
options.sessionId,
|
|
5718
|
+
fileName,
|
|
5719
|
+
contentType,
|
|
5720
|
+
"general"
|
|
5721
|
+
);
|
|
5722
|
+
const fileData = await readFile9(fullPath);
|
|
5723
|
+
const putRes = await fetch(uploadInfo.uploadUrl, {
|
|
5724
|
+
method: "PUT",
|
|
5725
|
+
headers: { "Content-Type": contentType },
|
|
5726
|
+
body: fileData
|
|
5727
|
+
});
|
|
5728
|
+
if (!putRes.ok) {
|
|
5729
|
+
return {
|
|
5730
|
+
success: false,
|
|
5731
|
+
error: `Upload failed: ${putRes.status} ${putRes.statusText}`
|
|
5732
|
+
};
|
|
5733
|
+
}
|
|
5734
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
5735
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
5736
|
+
return {
|
|
5737
|
+
success: true,
|
|
5738
|
+
fileId: uploadInfo.fileId,
|
|
5739
|
+
fileName,
|
|
5740
|
+
sizeBytes: fileData.length,
|
|
5741
|
+
contentType,
|
|
5742
|
+
downloadUrl: downloadInfo.downloadUrl,
|
|
5743
|
+
expiresAt: downloadInfo.expiresAt
|
|
5744
|
+
};
|
|
5745
|
+
} catch (err) {
|
|
5746
|
+
return {
|
|
5747
|
+
success: false,
|
|
5748
|
+
error: `Upload failed: ${err.message}`
|
|
5749
|
+
};
|
|
5750
|
+
}
|
|
5751
|
+
}
|
|
5752
|
+
});
|
|
5753
|
+
}
|
|
5754
|
+
|
|
5217
5755
|
// src/tools/index.ts
|
|
5218
5756
|
init_semantic();
|
|
5757
|
+
init_remote();
|
|
5219
5758
|
init_semantic_search();
|
|
5220
5759
|
async function createTools(options) {
|
|
5221
5760
|
const tools = {
|
|
@@ -5253,6 +5792,12 @@ async function createTools(options) {
|
|
|
5253
5792
|
workingDirectory: options.workingDirectory
|
|
5254
5793
|
})
|
|
5255
5794
|
};
|
|
5795
|
+
if (isRemoteConfigured()) {
|
|
5796
|
+
tools.upload_file = createUploadFileTool({
|
|
5797
|
+
workingDirectory: options.workingDirectory,
|
|
5798
|
+
sessionId: options.sessionId
|
|
5799
|
+
});
|
|
5800
|
+
}
|
|
5256
5801
|
if (options.enableSemanticSearch !== false) {
|
|
5257
5802
|
try {
|
|
5258
5803
|
if (isVectorGatewayConfigured()) {
|
|
@@ -5347,6 +5892,7 @@ You have access to powerful tools for:
|
|
|
5347
5892
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
5348
5893
|
- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
|
|
5349
5894
|
- **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
|
|
5895
|
+
- **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
|
|
5350
5896
|
|
|
5351
5897
|
|
|
5352
5898
|
IMPORTANT: If you have zero context of where you are working, always explore it first to understand the structure before doing things for the user.
|
|
@@ -5605,14 +6151,53 @@ function buildTaskPromptAddendum(outputSchema) {
|
|
|
5605
6151
|
## Task Mode
|
|
5606
6152
|
|
|
5607
6153
|
You are running in **task mode**. You have been given a specific task to complete autonomously.
|
|
6154
|
+
You have access to ALL the same tools as a normal session \u2014 bash, read_file, write_file, linter, todo, load_skill, explore_agent, code_graph, upload_file, and more. Use them all. This is not a limited session.
|
|
6155
|
+
If you need to give the user a downloadable file (report, image, export, etc.), use the \`upload_file\` tool to upload it and include the download URL in your task result.
|
|
5608
6156
|
|
|
5609
6157
|
### Rules
|
|
5610
6158
|
1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
|
|
5611
|
-
2. Keep working until the task is fully complete.
|
|
6159
|
+
2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
|
|
5612
6160
|
3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
5613
6161
|
4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
5614
6162
|
5. Do NOT stop without calling one of these two tools.
|
|
5615
6163
|
|
|
6164
|
+
### Verification \u2014 BE EXTREMELY THOROUGH
|
|
6165
|
+
Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
|
|
6166
|
+
|
|
6167
|
+
**After making code changes:**
|
|
6168
|
+
- Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
|
|
6169
|
+
- **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
|
|
6170
|
+
- If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
|
|
6171
|
+
- If you created new files, verify they exist and contain what you expect.
|
|
6172
|
+
|
|
6173
|
+
**For UI / web changes:**
|
|
6174
|
+
- Start the dev server if it isn't already running (it might be so double check ur context)
|
|
6175
|
+
- **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
|
|
6176
|
+
- Check the dev server logs for errors or warnings.
|
|
6177
|
+
- If the app crashes or shows errors, fix them before completing.
|
|
6178
|
+
|
|
6179
|
+
**For backend / API changes:**
|
|
6180
|
+
- Test the endpoint with curl or a quick script to confirm it works as expected.
|
|
6181
|
+
- Check server logs for errors.
|
|
6182
|
+
|
|
6183
|
+
**For search and exploration tasks:**
|
|
6184
|
+
- Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
|
|
6185
|
+
- Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
|
|
6186
|
+
- Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
|
|
6187
|
+
- Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
|
|
6188
|
+
|
|
6189
|
+
**General verification checklist:**
|
|
6190
|
+
- Re-read the original task prompt and confirm every requirement has been addressed.
|
|
6191
|
+
- If the task asked for multiple things, verify EACH one individually.
|
|
6192
|
+
- If something doesn't look right, fix it \u2014 don't complete with known issues.
|
|
6193
|
+
|
|
6194
|
+
### Use All Available Tools
|
|
6195
|
+
- **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
|
|
6196
|
+
- **explore_agent**: Use for codebase exploration and understanding before making changes.
|
|
6197
|
+
- **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
|
|
6198
|
+
- **todo**: Track your progress on multi-step tasks so you don't miss steps.
|
|
6199
|
+
- **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
|
|
6200
|
+
|
|
5616
6201
|
### Output Schema
|
|
5617
6202
|
The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
|
|
5618
6203
|
\`\`\`json
|
|
@@ -5620,7 +6205,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
5620
6205
|
\`\`\`
|
|
5621
6206
|
|
|
5622
6207
|
### Completion Tools
|
|
5623
|
-
- **\`complete_task({ result: ... })\`** \u2014 Call
|
|
6208
|
+
- **\`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.
|
|
5624
6209
|
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
5625
6210
|
`;
|
|
5626
6211
|
}
|
|
@@ -6128,11 +6713,29 @@ ${prompt}` });
|
|
|
6128
6713
|
const onComplete = (signal) => {
|
|
6129
6714
|
completion.signal = signal;
|
|
6130
6715
|
};
|
|
6716
|
+
let taskRecorder = null;
|
|
6717
|
+
const sessionId = this.session.id;
|
|
6718
|
+
const bashProgressHandler = (progress) => {
|
|
6719
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
6720
|
+
const port = progress.browserStreamPort;
|
|
6721
|
+
if (port && progress.status === "started") {
|
|
6722
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
6723
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
6724
|
+
if (!taskRecorder) {
|
|
6725
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
6726
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
6727
|
+
taskRecorder.start();
|
|
6728
|
+
proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
|
|
6729
|
+
});
|
|
6730
|
+
}
|
|
6731
|
+
});
|
|
6732
|
+
}
|
|
6733
|
+
};
|
|
6131
6734
|
const taskTools = await createTools({
|
|
6132
6735
|
sessionId: this.session.id,
|
|
6133
6736
|
workingDirectory: this.session.workingDirectory,
|
|
6134
6737
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
6135
|
-
onBashProgress:
|
|
6738
|
+
onBashProgress: bashProgressHandler,
|
|
6136
6739
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
6137
6740
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
|
|
6138
6741
|
taskTools: {
|
|
@@ -6203,12 +6806,24 @@ ${taskAddendum}`;
|
|
|
6203
6806
|
if (completion.signal) {
|
|
6204
6807
|
const sig = completion.signal;
|
|
6205
6808
|
const finalStatus = sig.status;
|
|
6809
|
+
let fileUrls;
|
|
6810
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
6811
|
+
const resultObj = sig.result;
|
|
6812
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
6813
|
+
if (filePaths.length > 0) {
|
|
6814
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6818
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
6206
6819
|
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
6207
6820
|
fireWebhook(eventType, {
|
|
6208
6821
|
status: finalStatus,
|
|
6209
6822
|
result: sig.result,
|
|
6210
6823
|
error: sig.error,
|
|
6211
|
-
iterations: iteration
|
|
6824
|
+
iterations: iteration,
|
|
6825
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
6826
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
6212
6827
|
});
|
|
6213
6828
|
const updatedTask2 = {
|
|
6214
6829
|
...options.taskConfig,
|
|
@@ -6228,11 +6843,17 @@ ${taskAddendum}`;
|
|
|
6228
6843
|
};
|
|
6229
6844
|
}
|
|
6230
6845
|
await this.context.addUserMessage(
|
|
6231
|
-
"Continue working on the task. When
|
|
6846
|
+
"Continue working on the task. Before calling `complete_task`, VERIFY your work is correct \u2014 re-read edited files, run the linter, run tests if applicable, and check the browser/server if you made UI or API changes. Make sure you searched the right directories and found everything relevant. When fully verified, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
|
|
6232
6847
|
);
|
|
6233
6848
|
}
|
|
6234
6849
|
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
6235
|
-
|
|
6850
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6851
|
+
fireWebhook("task.failed", {
|
|
6852
|
+
status: "failed",
|
|
6853
|
+
error: timeoutError,
|
|
6854
|
+
iterations: iteration,
|
|
6855
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
6856
|
+
});
|
|
6236
6857
|
const updatedTask = {
|
|
6237
6858
|
...options.taskConfig,
|
|
6238
6859
|
status: "failed",
|
|
@@ -6244,6 +6865,113 @@ ${taskAddendum}`;
|
|
|
6244
6865
|
});
|
|
6245
6866
|
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
6246
6867
|
}
|
|
6868
|
+
/**
|
|
6869
|
+
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
6870
|
+
* Returns download URLs for any recordings produced.
|
|
6871
|
+
*/
|
|
6872
|
+
async finishTaskRecording(recorder) {
|
|
6873
|
+
try {
|
|
6874
|
+
const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
|
|
6875
|
+
destroyProxy2(this.session.id);
|
|
6876
|
+
} catch {
|
|
6877
|
+
}
|
|
6878
|
+
if (!recorder || recorder.frameCount === 0) {
|
|
6879
|
+
recorder?.clear();
|
|
6880
|
+
return [];
|
|
6881
|
+
}
|
|
6882
|
+
recorder.stop();
|
|
6883
|
+
try {
|
|
6884
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6885
|
+
if (!isRemoteConfigured2()) {
|
|
6886
|
+
recorder.clear();
|
|
6887
|
+
return [];
|
|
6888
|
+
}
|
|
6889
|
+
const result = await recorder.encode();
|
|
6890
|
+
recorder.clear();
|
|
6891
|
+
if (!result) return [];
|
|
6892
|
+
const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
|
|
6893
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6894
|
+
this.session.id,
|
|
6895
|
+
`browser-recording-${Date.now()}.mp4`,
|
|
6896
|
+
"video/mp4",
|
|
6897
|
+
"browser-recording"
|
|
6898
|
+
);
|
|
6899
|
+
const fileData = await readFile11(result.path);
|
|
6900
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6901
|
+
method: "PUT",
|
|
6902
|
+
headers: { "Content-Type": "video/mp4" },
|
|
6903
|
+
body: fileData
|
|
6904
|
+
});
|
|
6905
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
6906
|
+
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6907
|
+
await unlink3(result.path).catch(() => {
|
|
6908
|
+
});
|
|
6909
|
+
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
6910
|
+
return [dlInfo.downloadUrl];
|
|
6911
|
+
} catch (err) {
|
|
6912
|
+
console.error("[TASK] Failed to upload browser recording:", err.message);
|
|
6913
|
+
recorder.clear();
|
|
6914
|
+
return [];
|
|
6915
|
+
}
|
|
6916
|
+
}
|
|
6917
|
+
/**
|
|
6918
|
+
* Upload task output files to GCS via the remote server.
|
|
6919
|
+
* Returns an array of download URLs for successfully uploaded files.
|
|
6920
|
+
*/
|
|
6921
|
+
async uploadTaskFiles(filePaths) {
|
|
6922
|
+
try {
|
|
6923
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6924
|
+
if (!isRemoteConfigured2()) return [];
|
|
6925
|
+
const { readFile: readFile11 } = await import("fs/promises");
|
|
6926
|
+
const { join: join11, basename: basename6 } = await import("path");
|
|
6927
|
+
const urls = [];
|
|
6928
|
+
for (const filePath of filePaths) {
|
|
6929
|
+
try {
|
|
6930
|
+
const fullPath = filePath.startsWith("/") ? filePath : join11(this.session.workingDirectory, filePath);
|
|
6931
|
+
const fileName = basename6(fullPath);
|
|
6932
|
+
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
6933
|
+
const mimeMap = {
|
|
6934
|
+
pdf: "application/pdf",
|
|
6935
|
+
json: "application/json",
|
|
6936
|
+
csv: "text/csv",
|
|
6937
|
+
txt: "text/plain",
|
|
6938
|
+
md: "text/markdown",
|
|
6939
|
+
html: "text/html",
|
|
6940
|
+
png: "image/png",
|
|
6941
|
+
jpg: "image/jpeg",
|
|
6942
|
+
jpeg: "image/jpeg",
|
|
6943
|
+
gif: "image/gif",
|
|
6944
|
+
svg: "image/svg+xml",
|
|
6945
|
+
mp4: "video/mp4",
|
|
6946
|
+
zip: "application/zip"
|
|
6947
|
+
};
|
|
6948
|
+
const contentType = mimeMap[ext] || "application/octet-stream";
|
|
6949
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6950
|
+
this.session.id,
|
|
6951
|
+
fileName,
|
|
6952
|
+
contentType,
|
|
6953
|
+
"task-output"
|
|
6954
|
+
);
|
|
6955
|
+
const fileData = await readFile11(fullPath);
|
|
6956
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6957
|
+
method: "PUT",
|
|
6958
|
+
headers: { "Content-Type": contentType },
|
|
6959
|
+
body: fileData
|
|
6960
|
+
});
|
|
6961
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
6962
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6963
|
+
urls.push(downloadInfo.downloadUrl);
|
|
6964
|
+
console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
|
|
6965
|
+
} catch (err) {
|
|
6966
|
+
console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
return urls;
|
|
6970
|
+
} catch (err) {
|
|
6971
|
+
console.error("[TASK] File upload failed:", err.message);
|
|
6972
|
+
return [];
|
|
6973
|
+
}
|
|
6974
|
+
}
|
|
6247
6975
|
/**
|
|
6248
6976
|
* Wrap tools to add approval checking
|
|
6249
6977
|
*/
|
|
@@ -6257,11 +6985,11 @@ ${taskAddendum}`;
|
|
|
6257
6985
|
wrappedTools[name] = originalTool;
|
|
6258
6986
|
continue;
|
|
6259
6987
|
}
|
|
6260
|
-
wrappedTools[name] =
|
|
6988
|
+
wrappedTools[name] = tool13({
|
|
6261
6989
|
description: originalTool.description || "",
|
|
6262
|
-
inputSchema: originalTool.inputSchema ||
|
|
6990
|
+
inputSchema: originalTool.inputSchema || z14.object({}),
|
|
6263
6991
|
execute: async (input, toolOptions) => {
|
|
6264
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
6992
|
+
const toolCallId = toolOptions.toolCallId || nanoid4();
|
|
6265
6993
|
const execution = toolExecutionQueries.create({
|
|
6266
6994
|
sessionId: this.session.id,
|
|
6267
6995
|
toolName: name,
|
|
@@ -6279,10 +7007,10 @@ ${taskAddendum}`;
|
|
|
6279
7007
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
6280
7008
|
approvalResolvers.delete(toolCallId);
|
|
6281
7009
|
this.pendingApprovals.delete(toolCallId);
|
|
6282
|
-
const
|
|
7010
|
+
const exec7 = await execution;
|
|
6283
7011
|
if (!approved) {
|
|
6284
7012
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
6285
|
-
await toolExecutionQueries.reject(
|
|
7013
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
6286
7014
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
6287
7015
|
return {
|
|
6288
7016
|
status: "rejected",
|
|
@@ -6292,14 +7020,14 @@ ${taskAddendum}`;
|
|
|
6292
7020
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
6293
7021
|
};
|
|
6294
7022
|
}
|
|
6295
|
-
await toolExecutionQueries.approve(
|
|
7023
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
6296
7024
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
6297
7025
|
try {
|
|
6298
7026
|
const result = await originalTool.execute(input, toolOptions);
|
|
6299
|
-
await toolExecutionQueries.complete(
|
|
7027
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
6300
7028
|
return result;
|
|
6301
7029
|
} catch (error) {
|
|
6302
|
-
await toolExecutionQueries.complete(
|
|
7030
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
6303
7031
|
throw error;
|
|
6304
7032
|
}
|
|
6305
7033
|
}
|
|
@@ -6400,18 +7128,18 @@ function cleanupPendingInputs() {
|
|
|
6400
7128
|
}
|
|
6401
7129
|
}
|
|
6402
7130
|
}
|
|
6403
|
-
var createSessionSchema =
|
|
6404
|
-
name:
|
|
6405
|
-
workingDirectory:
|
|
6406
|
-
model:
|
|
6407
|
-
toolApprovals:
|
|
7131
|
+
var createSessionSchema = z15.object({
|
|
7132
|
+
name: z15.string().optional(),
|
|
7133
|
+
workingDirectory: z15.string().optional(),
|
|
7134
|
+
model: z15.string().optional(),
|
|
7135
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
6408
7136
|
});
|
|
6409
|
-
var paginationQuerySchema =
|
|
6410
|
-
limit:
|
|
6411
|
-
offset:
|
|
7137
|
+
var paginationQuerySchema = z15.object({
|
|
7138
|
+
limit: z15.string().optional(),
|
|
7139
|
+
offset: z15.string().optional()
|
|
6412
7140
|
});
|
|
6413
|
-
var messagesQuerySchema =
|
|
6414
|
-
limit:
|
|
7141
|
+
var messagesQuerySchema = z15.object({
|
|
7142
|
+
limit: z15.string().optional()
|
|
6415
7143
|
});
|
|
6416
7144
|
sessions.get(
|
|
6417
7145
|
"/",
|
|
@@ -6550,10 +7278,10 @@ sessions.get("/:id/tools", async (c) => {
|
|
|
6550
7278
|
count: executions.length
|
|
6551
7279
|
});
|
|
6552
7280
|
});
|
|
6553
|
-
var updateSessionSchema =
|
|
6554
|
-
model:
|
|
6555
|
-
name:
|
|
6556
|
-
toolApprovals:
|
|
7281
|
+
var updateSessionSchema = z15.object({
|
|
7282
|
+
model: z15.string().optional(),
|
|
7283
|
+
name: z15.string().optional(),
|
|
7284
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
6557
7285
|
});
|
|
6558
7286
|
sessions.patch(
|
|
6559
7287
|
"/:id",
|
|
@@ -6623,8 +7351,8 @@ sessions.post("/:id/clear", async (c) => {
|
|
|
6623
7351
|
await agent.clearContext();
|
|
6624
7352
|
return c.json({ success: true, sessionId: id });
|
|
6625
7353
|
});
|
|
6626
|
-
var pendingInputSchema =
|
|
6627
|
-
text:
|
|
7354
|
+
var pendingInputSchema = z15.object({
|
|
7355
|
+
text: z15.string()
|
|
6628
7356
|
});
|
|
6629
7357
|
sessions.post(
|
|
6630
7358
|
"/:id/pending-input",
|
|
@@ -6655,13 +7383,13 @@ sessions.get("/:id/pending-input", async (c) => {
|
|
|
6655
7383
|
createdAt: pending.createdAt.toISOString()
|
|
6656
7384
|
});
|
|
6657
7385
|
});
|
|
6658
|
-
var devtoolsContextSchema =
|
|
6659
|
-
url:
|
|
6660
|
-
path:
|
|
6661
|
-
pageName:
|
|
6662
|
-
screenWidth:
|
|
6663
|
-
screenHeight:
|
|
6664
|
-
devicePixelRatio:
|
|
7386
|
+
var devtoolsContextSchema = z15.object({
|
|
7387
|
+
url: z15.string(),
|
|
7388
|
+
path: z15.string(),
|
|
7389
|
+
pageName: z15.string().optional(),
|
|
7390
|
+
screenWidth: z15.number().optional(),
|
|
7391
|
+
screenHeight: z15.number().optional(),
|
|
7392
|
+
devicePixelRatio: z15.number().optional()
|
|
6665
7393
|
});
|
|
6666
7394
|
sessions.post(
|
|
6667
7395
|
"/:id/devtools-context",
|
|
@@ -6829,7 +7557,7 @@ sessions.get("/:id/diff/:filePath", async (c) => {
|
|
|
6829
7557
|
});
|
|
6830
7558
|
function getAttachmentsDir(sessionId) {
|
|
6831
7559
|
const appDataDir = getAppDataDirectory();
|
|
6832
|
-
return
|
|
7560
|
+
return join7(appDataDir, "attachments", sessionId);
|
|
6833
7561
|
}
|
|
6834
7562
|
function ensureAttachmentsDir(sessionId) {
|
|
6835
7563
|
const dir = getAttachmentsDir(sessionId);
|
|
@@ -6850,7 +7578,7 @@ sessions.get("/:id/attachments", async (c) => {
|
|
|
6850
7578
|
}
|
|
6851
7579
|
const files = readdirSync(dir);
|
|
6852
7580
|
const attachments = files.map((filename) => {
|
|
6853
|
-
const filePath =
|
|
7581
|
+
const filePath = join7(dir, filename);
|
|
6854
7582
|
const stats = statSync2(filePath);
|
|
6855
7583
|
return {
|
|
6856
7584
|
id: filename.split("_")[0],
|
|
@@ -6882,10 +7610,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
6882
7610
|
return c.json({ error: "No file provided" }, 400);
|
|
6883
7611
|
}
|
|
6884
7612
|
const dir = ensureAttachmentsDir(sessionId);
|
|
6885
|
-
const id =
|
|
6886
|
-
const ext =
|
|
6887
|
-
const safeFilename = `${id}_${
|
|
6888
|
-
const filePath =
|
|
7613
|
+
const id = nanoid5(10);
|
|
7614
|
+
const ext = extname8(file.name) || "";
|
|
7615
|
+
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7616
|
+
const filePath = join7(dir, safeFilename);
|
|
6889
7617
|
const arrayBuffer = await file.arrayBuffer();
|
|
6890
7618
|
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
6891
7619
|
return c.json({
|
|
@@ -6908,10 +7636,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
6908
7636
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
6909
7637
|
}
|
|
6910
7638
|
const dir = ensureAttachmentsDir(sessionId);
|
|
6911
|
-
const id =
|
|
6912
|
-
const ext =
|
|
6913
|
-
const safeFilename = `${id}_${
|
|
6914
|
-
const filePath =
|
|
7639
|
+
const id = nanoid5(10);
|
|
7640
|
+
const ext = extname8(body.filename) || "";
|
|
7641
|
+
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7642
|
+
const filePath = join7(dir, safeFilename);
|
|
6915
7643
|
let base64Data = body.data;
|
|
6916
7644
|
if (base64Data.includes(",")) {
|
|
6917
7645
|
base64Data = base64Data.split(",")[1];
|
|
@@ -6948,14 +7676,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
6948
7676
|
if (!file) {
|
|
6949
7677
|
return c.json({ error: "Attachment not found" }, 404);
|
|
6950
7678
|
}
|
|
6951
|
-
const filePath =
|
|
7679
|
+
const filePath = join7(dir, file);
|
|
6952
7680
|
unlinkSync(filePath);
|
|
6953
7681
|
return c.json({ success: true, id: attachmentId });
|
|
6954
7682
|
});
|
|
6955
|
-
var filesQuerySchema =
|
|
6956
|
-
query:
|
|
7683
|
+
var filesQuerySchema = z15.object({
|
|
7684
|
+
query: z15.string().optional(),
|
|
6957
7685
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
6958
|
-
limit:
|
|
7686
|
+
limit: z15.string().optional()
|
|
6959
7687
|
// Max results (default 50)
|
|
6960
7688
|
});
|
|
6961
7689
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -7028,10 +7756,10 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7028
7756
|
return results;
|
|
7029
7757
|
}
|
|
7030
7758
|
try {
|
|
7031
|
-
const entries = await
|
|
7759
|
+
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
7032
7760
|
for (const entry of entries) {
|
|
7033
7761
|
if (results.length >= limit * 2) break;
|
|
7034
|
-
const fullPath =
|
|
7762
|
+
const fullPath = join7(currentDir, entry.name);
|
|
7035
7763
|
const relativePath = relative9(baseDir, fullPath);
|
|
7036
7764
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
7037
7765
|
continue;
|
|
@@ -7039,7 +7767,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7039
7767
|
if (entry.name.startsWith(".")) {
|
|
7040
7768
|
continue;
|
|
7041
7769
|
}
|
|
7042
|
-
const ext =
|
|
7770
|
+
const ext = extname8(entry.name).toLowerCase();
|
|
7043
7771
|
if (IGNORED_EXTENSIONS.has(ext)) {
|
|
7044
7772
|
continue;
|
|
7045
7773
|
}
|
|
@@ -7128,14 +7856,70 @@ sessions.get(
|
|
|
7128
7856
|
}
|
|
7129
7857
|
}
|
|
7130
7858
|
);
|
|
7859
|
+
sessions.get("/:id/session-files", async (c) => {
|
|
7860
|
+
const sessionId = c.req.param("id");
|
|
7861
|
+
try {
|
|
7862
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7863
|
+
if (!isRemoteConfigured2()) {
|
|
7864
|
+
return c.json({ files: [] });
|
|
7865
|
+
}
|
|
7866
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7867
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
7868
|
+
return c.json({ sessionId, files });
|
|
7869
|
+
} catch (err) {
|
|
7870
|
+
console.error("Failed to get session files:", err.message);
|
|
7871
|
+
return c.json({ sessionId, files: [] });
|
|
7872
|
+
}
|
|
7873
|
+
});
|
|
7874
|
+
sessions.get("/files/:fileId/download", async (c) => {
|
|
7875
|
+
const fileId = c.req.param("fileId");
|
|
7876
|
+
try {
|
|
7877
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7878
|
+
if (!isRemoteConfigured2()) {
|
|
7879
|
+
return c.json({ error: "Remote server not configured" }, 503);
|
|
7880
|
+
}
|
|
7881
|
+
const result = await storageQueries2.getDownloadUrl(fileId);
|
|
7882
|
+
return c.json(result);
|
|
7883
|
+
} catch (err) {
|
|
7884
|
+
return c.json({ error: err.message }, 500);
|
|
7885
|
+
}
|
|
7886
|
+
});
|
|
7887
|
+
sessions.get("/:id/browser-recording", async (c) => {
|
|
7888
|
+
const sessionId = c.req.param("id");
|
|
7889
|
+
try {
|
|
7890
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7891
|
+
if (!isRemoteConfigured2()) {
|
|
7892
|
+
return c.json({ sessionId, recordings: [] });
|
|
7893
|
+
}
|
|
7894
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
7895
|
+
const recordings = files.filter((f) => f.category === "browser-recording");
|
|
7896
|
+
if (recordings.length === 0) {
|
|
7897
|
+
return c.json({ sessionId, recordings: [], message: "No browser recordings for this session" });
|
|
7898
|
+
}
|
|
7899
|
+
return c.json({
|
|
7900
|
+
sessionId,
|
|
7901
|
+
recordings: recordings.map((r) => ({
|
|
7902
|
+
id: r.id,
|
|
7903
|
+
fileName: r.fileName,
|
|
7904
|
+
sizeBytes: r.sizeBytes,
|
|
7905
|
+
createdAt: r.createdAt,
|
|
7906
|
+
downloadUrl: r.downloadUrl,
|
|
7907
|
+
expiresAt: r.downloadUrlExpiresAt
|
|
7908
|
+
}))
|
|
7909
|
+
});
|
|
7910
|
+
} catch (err) {
|
|
7911
|
+
console.error("Failed to get browser recordings:", err.message);
|
|
7912
|
+
return c.json({ sessionId, recordings: [], error: err.message });
|
|
7913
|
+
}
|
|
7914
|
+
});
|
|
7131
7915
|
|
|
7132
7916
|
// src/server/routes/agents.ts
|
|
7133
7917
|
init_db();
|
|
7134
7918
|
import { Hono as Hono2 } from "hono";
|
|
7135
7919
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
7136
|
-
import { z as
|
|
7920
|
+
import { z as z16 } from "zod";
|
|
7137
7921
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
7138
|
-
import { join as
|
|
7922
|
+
import { join as join8 } from "path";
|
|
7139
7923
|
init_config();
|
|
7140
7924
|
|
|
7141
7925
|
// src/server/resumable-stream.ts
|
|
@@ -7211,7 +7995,11 @@ var streamContext = createResumableStreamContext({
|
|
|
7211
7995
|
});
|
|
7212
7996
|
|
|
7213
7997
|
// src/server/routes/agents.ts
|
|
7214
|
-
import { nanoid as
|
|
7998
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
7999
|
+
init_stream_proxy();
|
|
8000
|
+
init_recorder();
|
|
8001
|
+
init_remote();
|
|
8002
|
+
var sessionRecorders = /* @__PURE__ */ new Map();
|
|
7215
8003
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
7216
8004
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
7217
8005
|
var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
|
|
@@ -7298,35 +8086,35 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
7298
8086
|
${prompt}`;
|
|
7299
8087
|
}
|
|
7300
8088
|
var agents = new Hono2();
|
|
7301
|
-
var attachmentSchema =
|
|
7302
|
-
type:
|
|
7303
|
-
data:
|
|
8089
|
+
var attachmentSchema = z16.object({
|
|
8090
|
+
type: z16.enum(["image", "file"]),
|
|
8091
|
+
data: z16.string(),
|
|
7304
8092
|
// base64 data URL or raw base64
|
|
7305
|
-
mediaType:
|
|
7306
|
-
filename:
|
|
8093
|
+
mediaType: z16.string().optional(),
|
|
8094
|
+
filename: z16.string().optional()
|
|
7307
8095
|
});
|
|
7308
|
-
var runPromptSchema =
|
|
7309
|
-
prompt:
|
|
8096
|
+
var runPromptSchema = z16.object({
|
|
8097
|
+
prompt: z16.string(),
|
|
7310
8098
|
// Can be empty if attachments are provided
|
|
7311
|
-
attachments:
|
|
8099
|
+
attachments: z16.array(attachmentSchema).optional()
|
|
7312
8100
|
}).refine(
|
|
7313
8101
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
7314
8102
|
{ message: "Either prompt or attachments must be provided" }
|
|
7315
8103
|
);
|
|
7316
|
-
var quickStartSchema =
|
|
7317
|
-
prompt:
|
|
7318
|
-
name:
|
|
7319
|
-
workingDirectory:
|
|
7320
|
-
model:
|
|
7321
|
-
toolApprovals:
|
|
8104
|
+
var quickStartSchema = z16.object({
|
|
8105
|
+
prompt: z16.string().min(1),
|
|
8106
|
+
name: z16.string().optional(),
|
|
8107
|
+
workingDirectory: z16.string().optional(),
|
|
8108
|
+
model: z16.string().optional(),
|
|
8109
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
7322
8110
|
});
|
|
7323
|
-
var rejectSchema =
|
|
7324
|
-
reason:
|
|
8111
|
+
var rejectSchema = z16.object({
|
|
8112
|
+
reason: z16.string().optional()
|
|
7325
8113
|
}).optional();
|
|
7326
8114
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
7327
8115
|
function getAttachmentsDirectory(sessionId) {
|
|
7328
8116
|
const appDataDir = getAppDataDirectory();
|
|
7329
|
-
return
|
|
8117
|
+
return join8(appDataDir, "attachments", sessionId);
|
|
7330
8118
|
}
|
|
7331
8119
|
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
7332
8120
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
@@ -7342,7 +8130,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
7342
8130
|
if (base64Data.includes(",")) {
|
|
7343
8131
|
base64Data = base64Data.split(",")[1];
|
|
7344
8132
|
}
|
|
7345
|
-
const filePath =
|
|
8133
|
+
const filePath = join8(attachmentsDir, filename);
|
|
7346
8134
|
const buffer = Buffer.from(base64Data, "base64");
|
|
7347
8135
|
writeFileSync3(filePath, buffer);
|
|
7348
8136
|
return filePath;
|
|
@@ -7510,6 +8298,40 @@ ${prompt}` });
|
|
|
7510
8298
|
}));
|
|
7511
8299
|
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7512
8300
|
}
|
|
8301
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
8302
|
+
const browserClosed = progress.data?.browserClosed;
|
|
8303
|
+
if (progress.toolName === "bash" && browserClosed) {
|
|
8304
|
+
console.log(`[BROWSER-STREAM] agent-browser close detected, destroying proxy`);
|
|
8305
|
+
destroyProxy(sessionId);
|
|
8306
|
+
} else if (progress.toolName === "bash" && browserPort) {
|
|
8307
|
+
console.log(`[BROWSER-STREAM] agent-browser command detected, ensuring proxy on port ${browserPort}`);
|
|
8308
|
+
const proxy = getOrCreateProxy(sessionId, browserPort);
|
|
8309
|
+
if (!sessionRecorders.has(sessionId)) {
|
|
8310
|
+
const recorder = new FrameRecorder(sessionId);
|
|
8311
|
+
recorder.start();
|
|
8312
|
+
sessionRecorders.set(sessionId, recorder);
|
|
8313
|
+
}
|
|
8314
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
8315
|
+
proxy.on("frame", (frame) => {
|
|
8316
|
+
const rec = sessionRecorders.get(sessionId);
|
|
8317
|
+
rec?.addFrame(frame);
|
|
8318
|
+
writeSSE(JSON.stringify({
|
|
8319
|
+
type: "browser-frame",
|
|
8320
|
+
data: frame.data,
|
|
8321
|
+
metadata: frame.metadata
|
|
8322
|
+
})).catch(() => {
|
|
8323
|
+
});
|
|
8324
|
+
});
|
|
8325
|
+
proxy.on("status", (s) => {
|
|
8326
|
+
console.log(`[BROWSER-STREAM] Status:`, s);
|
|
8327
|
+
writeSSE(JSON.stringify({
|
|
8328
|
+
type: "browser-status",
|
|
8329
|
+
...s
|
|
8330
|
+
})).catch(() => {
|
|
8331
|
+
});
|
|
8332
|
+
});
|
|
8333
|
+
}
|
|
8334
|
+
}
|
|
7513
8335
|
},
|
|
7514
8336
|
onStepFinish: async () => {
|
|
7515
8337
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -7696,7 +8518,7 @@ ${prompt}` });
|
|
|
7696
8518
|
userMessageContent = prompt;
|
|
7697
8519
|
}
|
|
7698
8520
|
await messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
7699
|
-
const streamId = `stream_${id}_${
|
|
8521
|
+
const streamId = `stream_${id}_${nanoid6(10)}`;
|
|
7700
8522
|
await activeStreamQueries.create(id, streamId);
|
|
7701
8523
|
const stream = await streamContext.resumableStream(
|
|
7702
8524
|
streamId,
|
|
@@ -7895,7 +8717,7 @@ agents.post(
|
|
|
7895
8717
|
});
|
|
7896
8718
|
const session = agent.getSession();
|
|
7897
8719
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
7898
|
-
const streamId = `stream_${session.id}_${
|
|
8720
|
+
const streamId = `stream_${session.id}_${nanoid6(10)}`;
|
|
7899
8721
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
7900
8722
|
await activeStreamQueries.create(session.id, streamId);
|
|
7901
8723
|
const createQuickStreamProducer = () => {
|
|
@@ -7974,6 +8796,40 @@ agents.post(
|
|
|
7974
8796
|
}));
|
|
7975
8797
|
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7976
8798
|
}
|
|
8799
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
8800
|
+
const browserClosed = progress.data?.browserClosed;
|
|
8801
|
+
if (progress.toolName === "bash" && browserClosed) {
|
|
8802
|
+
console.log(`[BROWSER-STREAM] agent-browser close detected`);
|
|
8803
|
+
destroyProxy(session.id);
|
|
8804
|
+
} else if (progress.toolName === "bash" && browserPort) {
|
|
8805
|
+
console.log(`[BROWSER-STREAM] agent-browser command detected, port ${browserPort}`);
|
|
8806
|
+
const proxy = getOrCreateProxy(session.id, browserPort);
|
|
8807
|
+
if (!sessionRecorders.has(session.id)) {
|
|
8808
|
+
const recorder = new FrameRecorder(session.id);
|
|
8809
|
+
recorder.start();
|
|
8810
|
+
sessionRecorders.set(session.id, recorder);
|
|
8811
|
+
}
|
|
8812
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
8813
|
+
proxy.on("frame", (frame) => {
|
|
8814
|
+
const rec = sessionRecorders.get(session.id);
|
|
8815
|
+
rec?.addFrame(frame);
|
|
8816
|
+
writeSSE(JSON.stringify({
|
|
8817
|
+
type: "browser-frame",
|
|
8818
|
+
data: frame.data,
|
|
8819
|
+
metadata: frame.metadata
|
|
8820
|
+
})).catch(() => {
|
|
8821
|
+
});
|
|
8822
|
+
});
|
|
8823
|
+
proxy.on("status", (s) => {
|
|
8824
|
+
console.log(`[BROWSER-STREAM] Status:`, s);
|
|
8825
|
+
writeSSE(JSON.stringify({
|
|
8826
|
+
type: "browser-status",
|
|
8827
|
+
...s
|
|
8828
|
+
})).catch(() => {
|
|
8829
|
+
});
|
|
8830
|
+
});
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
7977
8833
|
},
|
|
7978
8834
|
onStepFinish: async () => {
|
|
7979
8835
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -8104,25 +8960,71 @@ agents.post(
|
|
|
8104
8960
|
});
|
|
8105
8961
|
}
|
|
8106
8962
|
);
|
|
8963
|
+
var browserInputSchema = z16.object({
|
|
8964
|
+
type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
8965
|
+
eventType: z16.string(),
|
|
8966
|
+
x: z16.number().optional(),
|
|
8967
|
+
y: z16.number().optional(),
|
|
8968
|
+
button: z16.string().optional(),
|
|
8969
|
+
clickCount: z16.number().optional(),
|
|
8970
|
+
deltaX: z16.number().optional(),
|
|
8971
|
+
deltaY: z16.number().optional(),
|
|
8972
|
+
key: z16.string().optional(),
|
|
8973
|
+
code: z16.string().optional(),
|
|
8974
|
+
text: z16.string().optional(),
|
|
8975
|
+
modifiers: z16.number().optional(),
|
|
8976
|
+
touchPoints: z16.array(z16.object({
|
|
8977
|
+
x: z16.number(),
|
|
8978
|
+
y: z16.number(),
|
|
8979
|
+
id: z16.number().optional()
|
|
8980
|
+
})).optional()
|
|
8981
|
+
});
|
|
8982
|
+
agents.post(
|
|
8983
|
+
"/:id/browser-input",
|
|
8984
|
+
zValidator2("json", browserInputSchema),
|
|
8985
|
+
async (c) => {
|
|
8986
|
+
const sessionId = c.req.param("id");
|
|
8987
|
+
const event = c.req.valid("json");
|
|
8988
|
+
const proxy = getProxy(sessionId);
|
|
8989
|
+
if (!proxy || !proxy.connected) {
|
|
8990
|
+
return c.json({ error: "No active browser stream for this session" }, 404);
|
|
8991
|
+
}
|
|
8992
|
+
proxy.injectInput(event);
|
|
8993
|
+
return c.json({ success: true });
|
|
8994
|
+
}
|
|
8995
|
+
);
|
|
8996
|
+
agents.get("/:id/browser-stream", async (c) => {
|
|
8997
|
+
const sessionId = c.req.param("id");
|
|
8998
|
+
const proxy = getProxy(sessionId);
|
|
8999
|
+
return c.json({
|
|
9000
|
+
sessionId,
|
|
9001
|
+
active: !!proxy?.connected,
|
|
9002
|
+
hasProxy: !!proxy,
|
|
9003
|
+
latestFrame: proxy?.latestFrame ? {
|
|
9004
|
+
metadata: proxy.latestFrame.metadata,
|
|
9005
|
+
timestamp: proxy.latestFrame.timestamp
|
|
9006
|
+
} : null
|
|
9007
|
+
});
|
|
9008
|
+
});
|
|
8107
9009
|
|
|
8108
9010
|
// src/server/routes/health.ts
|
|
8109
9011
|
init_config();
|
|
8110
9012
|
import { Hono as Hono3 } from "hono";
|
|
8111
9013
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
8112
|
-
import { z as
|
|
9014
|
+
import { z as z17 } from "zod";
|
|
8113
9015
|
import { readFileSync as readFileSync5 } from "fs";
|
|
8114
9016
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8115
|
-
import { dirname as dirname6, join as
|
|
9017
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
8116
9018
|
var __filename = fileURLToPath3(import.meta.url);
|
|
8117
9019
|
var __dirname = dirname6(__filename);
|
|
8118
9020
|
var possiblePaths = [
|
|
8119
|
-
|
|
9021
|
+
join9(__dirname, "../package.json"),
|
|
8120
9022
|
// From dist/server -> dist/../package.json
|
|
8121
|
-
|
|
9023
|
+
join9(__dirname, "../../package.json"),
|
|
8122
9024
|
// From dist/server (if nested differently)
|
|
8123
|
-
|
|
9025
|
+
join9(__dirname, "../../../package.json"),
|
|
8124
9026
|
// From src/server/routes (development)
|
|
8125
|
-
|
|
9027
|
+
join9(process.cwd(), "package.json")
|
|
8126
9028
|
// From current working directory
|
|
8127
9029
|
];
|
|
8128
9030
|
var currentVersion = "0.0.0";
|
|
@@ -8219,9 +9121,9 @@ health.get("/api-keys", async (c) => {
|
|
|
8219
9121
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
8220
9122
|
});
|
|
8221
9123
|
});
|
|
8222
|
-
var setApiKeySchema =
|
|
8223
|
-
provider:
|
|
8224
|
-
apiKey:
|
|
9124
|
+
var setApiKeySchema = z17.object({
|
|
9125
|
+
provider: z17.string(),
|
|
9126
|
+
apiKey: z17.string().min(1)
|
|
8225
9127
|
});
|
|
8226
9128
|
health.post(
|
|
8227
9129
|
"/api-keys",
|
|
@@ -8260,13 +9162,13 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
8260
9162
|
// src/server/routes/terminals.ts
|
|
8261
9163
|
import { Hono as Hono4 } from "hono";
|
|
8262
9164
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
8263
|
-
import { z as
|
|
9165
|
+
import { z as z18 } from "zod";
|
|
8264
9166
|
init_db();
|
|
8265
9167
|
var terminals = new Hono4();
|
|
8266
|
-
var spawnSchema =
|
|
8267
|
-
command:
|
|
8268
|
-
cwd:
|
|
8269
|
-
name:
|
|
9168
|
+
var spawnSchema = z18.object({
|
|
9169
|
+
command: z18.string(),
|
|
9170
|
+
cwd: z18.string().optional(),
|
|
9171
|
+
name: z18.string().optional()
|
|
8270
9172
|
});
|
|
8271
9173
|
terminals.post(
|
|
8272
9174
|
"/:sessionId/terminals",
|
|
@@ -8347,8 +9249,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
8347
9249
|
// We don't track exit codes in tmux mode
|
|
8348
9250
|
});
|
|
8349
9251
|
});
|
|
8350
|
-
var logsQuerySchema =
|
|
8351
|
-
tail:
|
|
9252
|
+
var logsQuerySchema = z18.object({
|
|
9253
|
+
tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
8352
9254
|
});
|
|
8353
9255
|
terminals.get(
|
|
8354
9256
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -8372,8 +9274,8 @@ terminals.get(
|
|
|
8372
9274
|
});
|
|
8373
9275
|
}
|
|
8374
9276
|
);
|
|
8375
|
-
var killSchema =
|
|
8376
|
-
signal:
|
|
9277
|
+
var killSchema = z18.object({
|
|
9278
|
+
signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
8377
9279
|
});
|
|
8378
9280
|
terminals.post(
|
|
8379
9281
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -8387,8 +9289,8 @@ terminals.post(
|
|
|
8387
9289
|
return c.json({ success: true, message: "Terminal killed" });
|
|
8388
9290
|
}
|
|
8389
9291
|
);
|
|
8390
|
-
var writeSchema =
|
|
8391
|
-
input:
|
|
9292
|
+
var writeSchema = z18.object({
|
|
9293
|
+
input: z18.string()
|
|
8392
9294
|
});
|
|
8393
9295
|
terminals.post(
|
|
8394
9296
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -8573,18 +9475,18 @@ data: ${JSON.stringify({ status: "stopped" })}
|
|
|
8573
9475
|
init_db();
|
|
8574
9476
|
import { Hono as Hono5 } from "hono";
|
|
8575
9477
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
8576
|
-
import { z as
|
|
9478
|
+
import { z as z19 } from "zod";
|
|
8577
9479
|
init_config();
|
|
8578
9480
|
var tasks = new Hono5();
|
|
8579
9481
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
8580
|
-
var createTaskSchema =
|
|
8581
|
-
prompt:
|
|
8582
|
-
outputSchema:
|
|
8583
|
-
webhookUrl:
|
|
8584
|
-
model:
|
|
8585
|
-
workingDirectory:
|
|
8586
|
-
name:
|
|
8587
|
-
maxIterations:
|
|
9482
|
+
var createTaskSchema = z19.object({
|
|
9483
|
+
prompt: z19.string().min(1),
|
|
9484
|
+
outputSchema: z19.record(z19.string(), z19.unknown()),
|
|
9485
|
+
webhookUrl: z19.string().url().optional(),
|
|
9486
|
+
model: z19.string().optional(),
|
|
9487
|
+
workingDirectory: z19.string().optional(),
|
|
9488
|
+
name: z19.string().optional(),
|
|
9489
|
+
maxIterations: z19.number().int().min(1).max(500).optional()
|
|
8588
9490
|
});
|
|
8589
9491
|
tasks.post(
|
|
8590
9492
|
"/",
|
|
@@ -8663,6 +9565,15 @@ tasks.get("/:id", async (c) => {
|
|
|
8663
9565
|
if (!task?.enabled) {
|
|
8664
9566
|
return c.json({ error: "Session is not a task" }, 400);
|
|
8665
9567
|
}
|
|
9568
|
+
let browserRecordings = [];
|
|
9569
|
+
try {
|
|
9570
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
9571
|
+
if (isRemoteConfigured2()) {
|
|
9572
|
+
const files = await storageQueries2.getSessionFiles(id);
|
|
9573
|
+
browserRecordings = files.filter((f) => f.category === "browser-recording").map((f) => ({ fileName: f.fileName, downloadUrl: f.downloadUrl, sizeBytes: f.sizeBytes }));
|
|
9574
|
+
}
|
|
9575
|
+
} catch {
|
|
9576
|
+
}
|
|
8666
9577
|
return c.json({
|
|
8667
9578
|
taskId: id,
|
|
8668
9579
|
status: task.status,
|
|
@@ -8672,7 +9583,8 @@ tasks.get("/:id", async (c) => {
|
|
|
8672
9583
|
model: session.model,
|
|
8673
9584
|
name: session.name,
|
|
8674
9585
|
createdAt: session.createdAt.toISOString(),
|
|
8675
|
-
updatedAt: session.updatedAt.toISOString()
|
|
9586
|
+
updatedAt: session.updatedAt.toISOString(),
|
|
9587
|
+
browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
|
|
8676
9588
|
});
|
|
8677
9589
|
});
|
|
8678
9590
|
tasks.post("/:id/cancel", async (c) => {
|
|
@@ -8720,10 +9632,10 @@ init_config();
|
|
|
8720
9632
|
init_db();
|
|
8721
9633
|
|
|
8722
9634
|
// src/utils/dependencies.ts
|
|
8723
|
-
import { exec as
|
|
8724
|
-
import { promisify as
|
|
9635
|
+
import { exec as exec6 } from "child_process";
|
|
9636
|
+
import { promisify as promisify6 } from "util";
|
|
8725
9637
|
import { platform as platform2 } from "os";
|
|
8726
|
-
var
|
|
9638
|
+
var execAsync6 = promisify6(exec6);
|
|
8727
9639
|
function getInstallInstructions() {
|
|
8728
9640
|
const os2 = platform2();
|
|
8729
9641
|
if (os2 === "darwin") {
|
|
@@ -8756,7 +9668,7 @@ Install tmux:
|
|
|
8756
9668
|
}
|
|
8757
9669
|
async function checkTmux() {
|
|
8758
9670
|
try {
|
|
8759
|
-
const { stdout } = await
|
|
9671
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
8760
9672
|
const version = stdout.trim();
|
|
8761
9673
|
return {
|
|
8762
9674
|
available: true,
|
|
@@ -8805,11 +9717,11 @@ function getWebDirectory() {
|
|
|
8805
9717
|
try {
|
|
8806
9718
|
const currentDir = dirname7(fileURLToPath4(import.meta.url));
|
|
8807
9719
|
const webDir = resolve10(currentDir, "..", "web");
|
|
8808
|
-
if (existsSync15(webDir) && existsSync15(
|
|
9720
|
+
if (existsSync15(webDir) && existsSync15(join10(webDir, "package.json"))) {
|
|
8809
9721
|
return webDir;
|
|
8810
9722
|
}
|
|
8811
9723
|
const altWebDir = resolve10(currentDir, "..", "..", "web");
|
|
8812
|
-
if (existsSync15(altWebDir) && existsSync15(
|
|
9724
|
+
if (existsSync15(altWebDir) && existsSync15(join10(altWebDir, "package.json"))) {
|
|
8813
9725
|
return altWebDir;
|
|
8814
9726
|
}
|
|
8815
9727
|
return null;
|
|
@@ -8867,20 +9779,20 @@ async function findWebPort(preferredPort) {
|
|
|
8867
9779
|
return { port: preferredPort, alreadyRunning: false };
|
|
8868
9780
|
}
|
|
8869
9781
|
function hasProductionBuild(webDir) {
|
|
8870
|
-
const buildIdPath =
|
|
9782
|
+
const buildIdPath = join10(webDir, ".next", "BUILD_ID");
|
|
8871
9783
|
return existsSync15(buildIdPath);
|
|
8872
9784
|
}
|
|
8873
9785
|
function hasSourceFiles(webDir) {
|
|
8874
|
-
const appDir =
|
|
8875
|
-
const pagesDir =
|
|
8876
|
-
const rootAppDir =
|
|
8877
|
-
const rootPagesDir =
|
|
9786
|
+
const appDir = join10(webDir, "src", "app");
|
|
9787
|
+
const pagesDir = join10(webDir, "src", "pages");
|
|
9788
|
+
const rootAppDir = join10(webDir, "app");
|
|
9789
|
+
const rootPagesDir = join10(webDir, "pages");
|
|
8878
9790
|
return existsSync15(appDir) || existsSync15(pagesDir) || existsSync15(rootAppDir) || existsSync15(rootPagesDir);
|
|
8879
9791
|
}
|
|
8880
9792
|
function getStandaloneServerPath(webDir) {
|
|
8881
9793
|
const possiblePaths2 = [
|
|
8882
|
-
|
|
8883
|
-
|
|
9794
|
+
join10(webDir, ".next", "standalone", "server.js"),
|
|
9795
|
+
join10(webDir, ".next", "standalone", "web", "server.js")
|
|
8884
9796
|
];
|
|
8885
9797
|
for (const serverPath of possiblePaths2) {
|
|
8886
9798
|
if (existsSync15(serverPath)) {
|
|
@@ -8923,13 +9835,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
8923
9835
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
8924
9836
|
return { process: null, port: actualPort };
|
|
8925
9837
|
}
|
|
8926
|
-
const usePnpm = existsSync15(
|
|
8927
|
-
const useNpm = !usePnpm && existsSync15(
|
|
9838
|
+
const usePnpm = existsSync15(join10(webDir, "pnpm-lock.yaml"));
|
|
9839
|
+
const useNpm = !usePnpm && existsSync15(join10(webDir, "package-lock.json"));
|
|
8928
9840
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
8929
9841
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
8930
9842
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
8931
9843
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
8932
|
-
const runtimeConfigPath =
|
|
9844
|
+
const runtimeConfigPath = join10(webDir, "runtime-config.json");
|
|
8933
9845
|
try {
|
|
8934
9846
|
writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
8935
9847
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|