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/cli.js
CHANGED
|
@@ -10,6 +10,25 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/db/remote.ts
|
|
13
|
+
var remote_exports = {};
|
|
14
|
+
__export(remote_exports, {
|
|
15
|
+
closeRemoteDatabase: () => closeRemoteDatabase,
|
|
16
|
+
initRemoteDatabase: () => initRemoteDatabase,
|
|
17
|
+
isRemoteConfigured: () => isRemoteConfigured,
|
|
18
|
+
remoteActiveStreamQueries: () => remoteActiveStreamQueries,
|
|
19
|
+
remoteCheckpointQueries: () => remoteCheckpointQueries,
|
|
20
|
+
remoteFileBackupQueries: () => remoteFileBackupQueries,
|
|
21
|
+
remoteIndexStatusQueries: () => remoteIndexStatusQueries,
|
|
22
|
+
remoteIndexedChunkQueries: () => remoteIndexedChunkQueries,
|
|
23
|
+
remoteMessageQueries: () => remoteMessageQueries,
|
|
24
|
+
remoteSessionQueries: () => remoteSessionQueries,
|
|
25
|
+
remoteSkillQueries: () => remoteSkillQueries,
|
|
26
|
+
remoteSubagentQueries: () => remoteSubagentQueries,
|
|
27
|
+
remoteTerminalQueries: () => remoteTerminalQueries,
|
|
28
|
+
remoteTodoQueries: () => remoteTodoQueries,
|
|
29
|
+
remoteToolExecutionQueries: () => remoteToolExecutionQueries,
|
|
30
|
+
storageQueries: () => storageQueries
|
|
31
|
+
});
|
|
13
32
|
function initRemoteDatabase(serverUrl, key) {
|
|
14
33
|
remoteServerUrl = serverUrl.replace(/\/$/, "");
|
|
15
34
|
authKey = key;
|
|
@@ -18,6 +37,9 @@ function closeRemoteDatabase() {
|
|
|
18
37
|
remoteServerUrl = null;
|
|
19
38
|
authKey = null;
|
|
20
39
|
}
|
|
40
|
+
function isRemoteConfigured() {
|
|
41
|
+
return !!remoteServerUrl && !!authKey;
|
|
42
|
+
}
|
|
21
43
|
function parseDates(obj) {
|
|
22
44
|
if (obj === null || obj === void 0) return obj;
|
|
23
45
|
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
@@ -65,7 +87,29 @@ async function api(path, options = {}) {
|
|
|
65
87
|
}
|
|
66
88
|
return parseDates(parsed);
|
|
67
89
|
}
|
|
68
|
-
|
|
90
|
+
async function storageApi(path, options = {}) {
|
|
91
|
+
if (!remoteServerUrl || !authKey) {
|
|
92
|
+
throw new Error("Remote database not initialized");
|
|
93
|
+
}
|
|
94
|
+
const url = `${remoteServerUrl}/storage${path}`;
|
|
95
|
+
const init = {
|
|
96
|
+
method: options.method || "GET",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
"Authorization": `Bearer ${authKey}`
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
if (options.body) {
|
|
103
|
+
init.body = JSON.stringify(options.body);
|
|
104
|
+
}
|
|
105
|
+
const response = await fetch(url, init);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
108
|
+
throw new Error(`Storage API error ${response.status}: ${errorText}`);
|
|
109
|
+
}
|
|
110
|
+
return response.json();
|
|
111
|
+
}
|
|
112
|
+
var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteTerminalQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries, storageQueries;
|
|
69
113
|
var init_remote = __esm({
|
|
70
114
|
"src/db/remote.ts"() {
|
|
71
115
|
"use strict";
|
|
@@ -204,6 +248,34 @@ var init_remote = __esm({
|
|
|
204
248
|
return result.isLoaded;
|
|
205
249
|
}
|
|
206
250
|
};
|
|
251
|
+
remoteTerminalQueries = {
|
|
252
|
+
create(data) {
|
|
253
|
+
return api("/terminals", { method: "POST", body: data });
|
|
254
|
+
},
|
|
255
|
+
getById(id) {
|
|
256
|
+
return api(`/terminals/${id}`).catch(() => void 0);
|
|
257
|
+
},
|
|
258
|
+
getBySession(sessionId) {
|
|
259
|
+
return api(`/terminals/session/${sessionId}`);
|
|
260
|
+
},
|
|
261
|
+
getRunning(sessionId) {
|
|
262
|
+
return api(`/terminals/session/${sessionId}/running`);
|
|
263
|
+
},
|
|
264
|
+
updateStatus(id, status, exitCode, error) {
|
|
265
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { status, exitCode, error } });
|
|
266
|
+
},
|
|
267
|
+
updatePid(id, pid) {
|
|
268
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { pid } });
|
|
269
|
+
},
|
|
270
|
+
async delete(id) {
|
|
271
|
+
const result = await api(`/terminals/${id}`, { method: "DELETE" });
|
|
272
|
+
return result?.success ?? false;
|
|
273
|
+
},
|
|
274
|
+
async deleteBySession(sessionId) {
|
|
275
|
+
const result = await api(`/terminals/session/${sessionId}`, { method: "DELETE" });
|
|
276
|
+
return result.deleted;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
207
279
|
remoteActiveStreamQueries = {
|
|
208
280
|
create(sessionId, streamId) {
|
|
209
281
|
return api("/streams", { method: "POST", body: { sessionId, streamId } });
|
|
@@ -364,6 +436,27 @@ var init_remote = __esm({
|
|
|
364
436
|
return api("/index-status");
|
|
365
437
|
}
|
|
366
438
|
};
|
|
439
|
+
storageQueries = {
|
|
440
|
+
async getUploadUrl(sessionId, fileName, contentType, category) {
|
|
441
|
+
return storageApi("/upload-url", {
|
|
442
|
+
method: "POST",
|
|
443
|
+
body: { sessionId, fileName, contentType, category }
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
async getSessionFiles(sessionId) {
|
|
447
|
+
const result = await storageApi(`/files/${sessionId}`);
|
|
448
|
+
return result.files;
|
|
449
|
+
},
|
|
450
|
+
async getDownloadUrl(fileId) {
|
|
451
|
+
return storageApi(`/download/${fileId}`);
|
|
452
|
+
},
|
|
453
|
+
async deleteFile(fileId) {
|
|
454
|
+
await storageApi(`/files/${fileId}`, { method: "DELETE" });
|
|
455
|
+
},
|
|
456
|
+
async updateFile(fileId, data) {
|
|
457
|
+
await storageApi(`/files/${fileId}`, { method: "PATCH", body: data });
|
|
458
|
+
}
|
|
459
|
+
};
|
|
367
460
|
}
|
|
368
461
|
});
|
|
369
462
|
|
|
@@ -1911,7 +2004,7 @@ function isPathExcluded(relativePath, exclude) {
|
|
|
1911
2004
|
}
|
|
1912
2005
|
async function walkDirectory(dir, include, exclude, baseDir) {
|
|
1913
2006
|
const { readdirSync: readdirSync2 } = await import("fs");
|
|
1914
|
-
const { join:
|
|
2007
|
+
const { join: join12, relative: relative10 } = await import("path");
|
|
1915
2008
|
const files = [];
|
|
1916
2009
|
function walk(currentDir) {
|
|
1917
2010
|
let entries;
|
|
@@ -1921,7 +2014,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
|
|
|
1921
2014
|
return;
|
|
1922
2015
|
}
|
|
1923
2016
|
for (const entry of entries) {
|
|
1924
|
-
const fullPath =
|
|
2017
|
+
const fullPath = join12(currentDir, entry.name);
|
|
1925
2018
|
const relativePath = relative10(baseDir, fullPath);
|
|
1926
2019
|
if (isPathExcluded(relativePath, exclude)) {
|
|
1927
2020
|
continue;
|
|
@@ -2451,6 +2544,280 @@ var init_webhook = __esm({
|
|
|
2451
2544
|
}
|
|
2452
2545
|
});
|
|
2453
2546
|
|
|
2547
|
+
// src/browser/stream-proxy.ts
|
|
2548
|
+
var stream_proxy_exports = {};
|
|
2549
|
+
__export(stream_proxy_exports, {
|
|
2550
|
+
BrowserStreamProxy: () => BrowserStreamProxy,
|
|
2551
|
+
destroyProxy: () => destroyProxy,
|
|
2552
|
+
getOrCreateProxy: () => getOrCreateProxy,
|
|
2553
|
+
getProxy: () => getProxy
|
|
2554
|
+
});
|
|
2555
|
+
import WebSocket from "ws";
|
|
2556
|
+
import { EventEmitter } from "events";
|
|
2557
|
+
function getOrCreateProxy(sessionId, port) {
|
|
2558
|
+
const existing = activeProxies.get(sessionId);
|
|
2559
|
+
if (existing) return existing;
|
|
2560
|
+
const proxy = new BrowserStreamProxy(port);
|
|
2561
|
+
activeProxies.set(sessionId, proxy);
|
|
2562
|
+
proxy.on("close", () => {
|
|
2563
|
+
activeProxies.delete(sessionId);
|
|
2564
|
+
});
|
|
2565
|
+
proxy.connect();
|
|
2566
|
+
return proxy;
|
|
2567
|
+
}
|
|
2568
|
+
function getProxy(sessionId) {
|
|
2569
|
+
return activeProxies.get(sessionId);
|
|
2570
|
+
}
|
|
2571
|
+
function destroyProxy(sessionId) {
|
|
2572
|
+
const proxy = activeProxies.get(sessionId);
|
|
2573
|
+
if (proxy) {
|
|
2574
|
+
proxy.destroy();
|
|
2575
|
+
activeProxies.delete(sessionId);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
|
|
2579
|
+
var init_stream_proxy = __esm({
|
|
2580
|
+
"src/browser/stream-proxy.ts"() {
|
|
2581
|
+
"use strict";
|
|
2582
|
+
RECONNECT_DELAY_MS = 1e3;
|
|
2583
|
+
MAX_RECONNECT_ATTEMPTS = 20;
|
|
2584
|
+
FRAME_THROTTLE_MS = 100;
|
|
2585
|
+
BrowserStreamProxy = class extends EventEmitter {
|
|
2586
|
+
ws = null;
|
|
2587
|
+
port;
|
|
2588
|
+
reconnectAttempts = 0;
|
|
2589
|
+
reconnectTimer = null;
|
|
2590
|
+
destroyed = false;
|
|
2591
|
+
lastFrameTime = 0;
|
|
2592
|
+
_latestFrame = null;
|
|
2593
|
+
_connected = false;
|
|
2594
|
+
constructor(port) {
|
|
2595
|
+
super();
|
|
2596
|
+
this.port = port;
|
|
2597
|
+
}
|
|
2598
|
+
get connected() {
|
|
2599
|
+
return this._connected;
|
|
2600
|
+
}
|
|
2601
|
+
get latestFrame() {
|
|
2602
|
+
return this._latestFrame;
|
|
2603
|
+
}
|
|
2604
|
+
connect() {
|
|
2605
|
+
if (this.destroyed) return;
|
|
2606
|
+
this.doConnect();
|
|
2607
|
+
}
|
|
2608
|
+
doConnect() {
|
|
2609
|
+
if (this.destroyed) return;
|
|
2610
|
+
const url = `ws://localhost:${this.port}`;
|
|
2611
|
+
try {
|
|
2612
|
+
this.ws = new WebSocket(url);
|
|
2613
|
+
} catch {
|
|
2614
|
+
this.scheduleReconnect();
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
this.ws.on("open", () => {
|
|
2618
|
+
this.reconnectAttempts = 0;
|
|
2619
|
+
this._connected = true;
|
|
2620
|
+
this.emit("status", {
|
|
2621
|
+
connected: true,
|
|
2622
|
+
screencasting: true
|
|
2623
|
+
});
|
|
2624
|
+
});
|
|
2625
|
+
this.ws.on("message", (raw) => {
|
|
2626
|
+
try {
|
|
2627
|
+
const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
2628
|
+
this.handleMessage(msg);
|
|
2629
|
+
} catch {
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
this.ws.on("close", () => {
|
|
2633
|
+
const wasConnected = this._connected;
|
|
2634
|
+
this._connected = false;
|
|
2635
|
+
if (wasConnected) {
|
|
2636
|
+
this.emit("status", { connected: false, screencasting: false });
|
|
2637
|
+
}
|
|
2638
|
+
if (!this.destroyed) {
|
|
2639
|
+
this.scheduleReconnect();
|
|
2640
|
+
}
|
|
2641
|
+
});
|
|
2642
|
+
this.ws.on("error", () => {
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
handleMessage(msg) {
|
|
2646
|
+
if (msg.type === "frame") {
|
|
2647
|
+
const now = Date.now();
|
|
2648
|
+
if (now - this.lastFrameTime < FRAME_THROTTLE_MS) return;
|
|
2649
|
+
this.lastFrameTime = now;
|
|
2650
|
+
const frame = {
|
|
2651
|
+
data: msg.data,
|
|
2652
|
+
metadata: msg.metadata ?? {
|
|
2653
|
+
deviceWidth: 1280,
|
|
2654
|
+
deviceHeight: 720,
|
|
2655
|
+
pageScaleFactor: 1,
|
|
2656
|
+
offsetTop: 0,
|
|
2657
|
+
scrollOffsetX: 0,
|
|
2658
|
+
scrollOffsetY: 0
|
|
2659
|
+
},
|
|
2660
|
+
timestamp: now
|
|
2661
|
+
};
|
|
2662
|
+
this._latestFrame = frame;
|
|
2663
|
+
this.emit("frame", frame);
|
|
2664
|
+
} else if (msg.type === "status") {
|
|
2665
|
+
this.emit("status", {
|
|
2666
|
+
connected: msg.connected ?? true,
|
|
2667
|
+
screencasting: msg.screencasting ?? true,
|
|
2668
|
+
viewportWidth: msg.viewportWidth,
|
|
2669
|
+
viewportHeight: msg.viewportHeight
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
scheduleReconnect() {
|
|
2674
|
+
if (this.destroyed || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
2675
|
+
this.emit("close");
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
this.reconnectAttempts++;
|
|
2679
|
+
const delay = this.reconnectAttempts <= 5 ? RECONNECT_DELAY_MS : RECONNECT_DELAY_MS * (this.reconnectAttempts - 4);
|
|
2680
|
+
this.reconnectTimer = setTimeout(() => this.doConnect(), delay);
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Send an input event to the browser for pair-browsing.
|
|
2684
|
+
*/
|
|
2685
|
+
injectInput(event) {
|
|
2686
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
2687
|
+
this.ws.send(JSON.stringify(event));
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
destroy() {
|
|
2691
|
+
this.destroyed = true;
|
|
2692
|
+
if (this.reconnectTimer) {
|
|
2693
|
+
clearTimeout(this.reconnectTimer);
|
|
2694
|
+
this.reconnectTimer = null;
|
|
2695
|
+
}
|
|
2696
|
+
if (this.ws) {
|
|
2697
|
+
this.ws.removeAllListeners();
|
|
2698
|
+
this.ws.close();
|
|
2699
|
+
this.ws = null;
|
|
2700
|
+
}
|
|
2701
|
+
this._connected = false;
|
|
2702
|
+
this.removeAllListeners();
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
activeProxies = /* @__PURE__ */ new Map();
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
|
|
2709
|
+
// src/browser/recorder.ts
|
|
2710
|
+
var recorder_exports = {};
|
|
2711
|
+
__export(recorder_exports, {
|
|
2712
|
+
FrameRecorder: () => FrameRecorder
|
|
2713
|
+
});
|
|
2714
|
+
import { exec as exec5 } from "child_process";
|
|
2715
|
+
import { promisify as promisify5 } from "util";
|
|
2716
|
+
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
2717
|
+
import { join as join6 } from "path";
|
|
2718
|
+
import { tmpdir } from "os";
|
|
2719
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
2720
|
+
async function checkFfmpeg() {
|
|
2721
|
+
try {
|
|
2722
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
2723
|
+
return true;
|
|
2724
|
+
} catch {
|
|
2725
|
+
return false;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
async function cleanup(dir) {
|
|
2729
|
+
try {
|
|
2730
|
+
await rm(dir, { recursive: true, force: true });
|
|
2731
|
+
} catch {
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
var execAsync5, FrameRecorder;
|
|
2735
|
+
var init_recorder = __esm({
|
|
2736
|
+
"src/browser/recorder.ts"() {
|
|
2737
|
+
"use strict";
|
|
2738
|
+
execAsync5 = promisify5(exec5);
|
|
2739
|
+
FrameRecorder = class {
|
|
2740
|
+
frames = [];
|
|
2741
|
+
startTime = null;
|
|
2742
|
+
recording = false;
|
|
2743
|
+
sessionId;
|
|
2744
|
+
constructor(sessionId) {
|
|
2745
|
+
this.sessionId = sessionId;
|
|
2746
|
+
}
|
|
2747
|
+
get isRecording() {
|
|
2748
|
+
return this.recording;
|
|
2749
|
+
}
|
|
2750
|
+
get frameCount() {
|
|
2751
|
+
return this.frames.length;
|
|
2752
|
+
}
|
|
2753
|
+
start() {
|
|
2754
|
+
this.frames = [];
|
|
2755
|
+
this.startTime = Date.now();
|
|
2756
|
+
this.recording = true;
|
|
2757
|
+
}
|
|
2758
|
+
addFrame(frame) {
|
|
2759
|
+
if (!this.recording) return;
|
|
2760
|
+
this.frames.push({
|
|
2761
|
+
data: Buffer.from(frame.data, "base64"),
|
|
2762
|
+
timestamp: frame.timestamp
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
stop() {
|
|
2766
|
+
this.recording = false;
|
|
2767
|
+
}
|
|
2768
|
+
/**
|
|
2769
|
+
* Encode recorded frames into an MP4 using ffmpeg.
|
|
2770
|
+
* Returns the file path to the generated MP4, or null if encoding fails.
|
|
2771
|
+
*/
|
|
2772
|
+
async encode() {
|
|
2773
|
+
if (this.frames.length === 0) return null;
|
|
2774
|
+
const workDir = join6(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
|
|
2775
|
+
await mkdir4(workDir, { recursive: true });
|
|
2776
|
+
try {
|
|
2777
|
+
for (let i = 0; i < this.frames.length; i++) {
|
|
2778
|
+
const framePath = join6(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
2779
|
+
await writeFile4(framePath, this.frames[i].data);
|
|
2780
|
+
}
|
|
2781
|
+
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
2782
|
+
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
2783
|
+
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
2784
|
+
const outputPath = join6(workDir, `recording_${this.sessionId}.mp4`);
|
|
2785
|
+
const hasFfmpeg = await checkFfmpeg();
|
|
2786
|
+
if (hasFfmpeg) {
|
|
2787
|
+
await execAsync5(
|
|
2788
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join6(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
2789
|
+
{ timeout: 12e4 }
|
|
2790
|
+
);
|
|
2791
|
+
} else {
|
|
2792
|
+
console.warn("[RECORDER] ffmpeg not available, cannot encode recording");
|
|
2793
|
+
await cleanup(workDir);
|
|
2794
|
+
return null;
|
|
2795
|
+
}
|
|
2796
|
+
const outputBuf = await readFile10(outputPath);
|
|
2797
|
+
const files = await readdir5(workDir);
|
|
2798
|
+
for (const f of files) {
|
|
2799
|
+
if (f.startsWith("frame_")) {
|
|
2800
|
+
await unlink2(join6(workDir, f)).catch(() => {
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
return { path: outputPath, sizeBytes: outputBuf.length };
|
|
2805
|
+
} catch (error) {
|
|
2806
|
+
console.error("[RECORDER] Failed to encode recording:", error);
|
|
2807
|
+
await cleanup(workDir);
|
|
2808
|
+
return null;
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
/** Discard all frames and free memory */
|
|
2812
|
+
clear() {
|
|
2813
|
+
this.frames = [];
|
|
2814
|
+
this.startTime = null;
|
|
2815
|
+
this.recording = false;
|
|
2816
|
+
}
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
});
|
|
2820
|
+
|
|
2454
2821
|
// src/cli.ts
|
|
2455
2822
|
import { Command } from "commander";
|
|
2456
2823
|
import chalk from "chalk";
|
|
@@ -2465,7 +2832,7 @@ import { serve } from "@hono/node-server";
|
|
|
2465
2832
|
import { cors } from "hono/cors";
|
|
2466
2833
|
import { logger } from "hono/logger";
|
|
2467
2834
|
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2468
|
-
import { resolve as resolve10, dirname as dirname7, join as
|
|
2835
|
+
import { resolve as resolve10, dirname as dirname7, join as join10 } from "path";
|
|
2469
2836
|
import { spawn as spawn2 } from "child_process";
|
|
2470
2837
|
import { createServer as createNetServer } from "net";
|
|
2471
2838
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -2474,17 +2841,17 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
2474
2841
|
init_db();
|
|
2475
2842
|
import { Hono } from "hono";
|
|
2476
2843
|
import { zValidator } from "@hono/zod-validator";
|
|
2477
|
-
import { z as
|
|
2844
|
+
import { z as z15 } from "zod";
|
|
2478
2845
|
import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
|
|
2479
|
-
import { readdir as
|
|
2480
|
-
import { join as
|
|
2481
|
-
import { nanoid as
|
|
2846
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
2847
|
+
import { join as join7, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
2848
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
2482
2849
|
|
|
2483
2850
|
// src/agent/index.ts
|
|
2484
2851
|
import {
|
|
2485
2852
|
streamText as streamText2,
|
|
2486
2853
|
generateText as generateText3,
|
|
2487
|
-
tool as
|
|
2854
|
+
tool as tool13,
|
|
2488
2855
|
stepCountIs as stepCountIs2
|
|
2489
2856
|
} from "ai";
|
|
2490
2857
|
|
|
@@ -2665,8 +3032,8 @@ var SUBAGENT_MODELS = {
|
|
|
2665
3032
|
// src/agent/index.ts
|
|
2666
3033
|
init_db();
|
|
2667
3034
|
init_config();
|
|
2668
|
-
import { z as
|
|
2669
|
-
import { nanoid as
|
|
3035
|
+
import { z as z14 } from "zod";
|
|
3036
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
2670
3037
|
|
|
2671
3038
|
// src/tools/bash.ts
|
|
2672
3039
|
import { tool } from "ai";
|
|
@@ -2952,8 +3319,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
|
|
|
2952
3319
|
const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
|
|
2953
3320
|
const terminals2 = [];
|
|
2954
3321
|
try {
|
|
2955
|
-
const { readdir:
|
|
2956
|
-
const entries = await
|
|
3322
|
+
const { readdir: readdir7 } = await import("fs/promises");
|
|
3323
|
+
const entries = await readdir7(terminalsDir, { withFileTypes: true });
|
|
2957
3324
|
for (const entry of entries) {
|
|
2958
3325
|
if (entry.isDirectory()) {
|
|
2959
3326
|
const meta = await getMeta(entry.name, workingDirectory, sessionId);
|
|
@@ -3015,6 +3382,29 @@ function isBlockedCommand(command) {
|
|
|
3015
3382
|
(blocked) => normalizedCommand.includes(blocked.toLowerCase())
|
|
3016
3383
|
);
|
|
3017
3384
|
}
|
|
3385
|
+
var BROWSER_STREAM_BASE_PORT = 9223;
|
|
3386
|
+
var sessionBrowserPorts = /* @__PURE__ */ new Map();
|
|
3387
|
+
var nextPortOffset = 0;
|
|
3388
|
+
function getBrowserStreamPort(sessionId) {
|
|
3389
|
+
let port = sessionBrowserPorts.get(sessionId);
|
|
3390
|
+
if (!port) {
|
|
3391
|
+
port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
|
|
3392
|
+
sessionBrowserPorts.set(sessionId, port);
|
|
3393
|
+
}
|
|
3394
|
+
return port;
|
|
3395
|
+
}
|
|
3396
|
+
function hasAgentBrowserCommand(command) {
|
|
3397
|
+
return /\bagent-browser\b/.test(command);
|
|
3398
|
+
}
|
|
3399
|
+
function isAgentBrowserCloseCommand(command) {
|
|
3400
|
+
return /\bagent-browser\s+(close|close\s+--all)\b/.test(command);
|
|
3401
|
+
}
|
|
3402
|
+
function injectBrowserStreamPort(command, port) {
|
|
3403
|
+
return command.replace(
|
|
3404
|
+
/\bagent-browser\b/g,
|
|
3405
|
+
`AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3018
3408
|
var bashInputSchema = z2.object({
|
|
3019
3409
|
command: z2.string().optional().describe("The command to execute. Required for running new commands."),
|
|
3020
3410
|
background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
|
|
@@ -3180,6 +3570,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
3180
3570
|
exitCode: 1
|
|
3181
3571
|
};
|
|
3182
3572
|
}
|
|
3573
|
+
let actualCommand = command;
|
|
3574
|
+
const hasAgentBrowser = hasAgentBrowserCommand(command);
|
|
3575
|
+
const browserClose = isAgentBrowserCloseCommand(command);
|
|
3576
|
+
let browserPort;
|
|
3577
|
+
if (hasAgentBrowser) {
|
|
3578
|
+
browserPort = getBrowserStreamPort(options.sessionId);
|
|
3579
|
+
if (!browserClose) {
|
|
3580
|
+
actualCommand = injectBrowserStreamPort(command, browserPort);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3183
3583
|
const canUseTmux = await shouldUseTmux();
|
|
3184
3584
|
if (background) {
|
|
3185
3585
|
if (!canUseTmux) {
|
|
@@ -3189,8 +3589,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
3189
3589
|
};
|
|
3190
3590
|
}
|
|
3191
3591
|
const terminalId = generateTerminalId();
|
|
3192
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
3193
|
-
const result = await runBackground(
|
|
3592
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
3593
|
+
const result = await runBackground(actualCommand, options.workingDirectory, {
|
|
3194
3594
|
sessionId: options.sessionId,
|
|
3195
3595
|
terminalId
|
|
3196
3596
|
});
|
|
@@ -3203,16 +3603,22 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
3203
3603
|
}
|
|
3204
3604
|
if (canUseTmux) {
|
|
3205
3605
|
const terminalId = generateTerminalId();
|
|
3206
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
3606
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
3207
3607
|
try {
|
|
3208
|
-
const result = await runSync(
|
|
3608
|
+
const result = await runSync(actualCommand, options.workingDirectory, {
|
|
3209
3609
|
sessionId: options.sessionId,
|
|
3210
3610
|
timeout: COMMAND_TIMEOUT,
|
|
3211
3611
|
terminalId
|
|
3212
3612
|
});
|
|
3213
3613
|
const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
|
|
3214
3614
|
options.onOutput?.(truncatedOutput);
|
|
3215
|
-
options.onProgress?.({
|
|
3615
|
+
options.onProgress?.({
|
|
3616
|
+
terminalId,
|
|
3617
|
+
status: "completed",
|
|
3618
|
+
command,
|
|
3619
|
+
browserStreamPort: browserPort,
|
|
3620
|
+
browserClosed: browserClose || void 0
|
|
3621
|
+
});
|
|
3216
3622
|
return {
|
|
3217
3623
|
success: result.exitCode === 0,
|
|
3218
3624
|
id: result.id,
|
|
@@ -3230,7 +3636,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
3230
3636
|
};
|
|
3231
3637
|
}
|
|
3232
3638
|
} else {
|
|
3233
|
-
const result = await execFallback(
|
|
3639
|
+
const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
|
|
3234
3640
|
return {
|
|
3235
3641
|
success: result.success,
|
|
3236
3642
|
output: result.output,
|
|
@@ -3605,12 +4011,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
3605
4011
|
}
|
|
3606
4012
|
async function commandExists(cmd) {
|
|
3607
4013
|
try {
|
|
3608
|
-
const { exec:
|
|
3609
|
-
const { promisify:
|
|
3610
|
-
const
|
|
4014
|
+
const { exec: exec7 } = await import("child_process");
|
|
4015
|
+
const { promisify: promisify7 } = await import("util");
|
|
4016
|
+
const execAsync7 = promisify7(exec7);
|
|
3611
4017
|
const isWindows = process.platform === "win32";
|
|
3612
4018
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
3613
|
-
await
|
|
4019
|
+
await execAsync7(checkCmd);
|
|
3614
4020
|
return true;
|
|
3615
4021
|
} catch {
|
|
3616
4022
|
return false;
|
|
@@ -3908,7 +4314,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3908
4314
|
const startTime = Date.now();
|
|
3909
4315
|
let debounceTimer;
|
|
3910
4316
|
let resolved = false;
|
|
3911
|
-
const
|
|
4317
|
+
const cleanup2 = () => {
|
|
3912
4318
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
3913
4319
|
const listeners = diagnosticListeners.get(normalized);
|
|
3914
4320
|
if (listeners) {
|
|
@@ -3922,7 +4328,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3922
4328
|
const finish = () => {
|
|
3923
4329
|
if (resolved) return;
|
|
3924
4330
|
resolved = true;
|
|
3925
|
-
|
|
4331
|
+
cleanup2();
|
|
3926
4332
|
resolve12(diagnostics.get(normalized) || []);
|
|
3927
4333
|
};
|
|
3928
4334
|
const onDiagnostic = () => {
|
|
@@ -6027,8 +6433,106 @@ function createTaskFailedTool(options) {
|
|
|
6027
6433
|
});
|
|
6028
6434
|
}
|
|
6029
6435
|
|
|
6436
|
+
// src/tools/upload-file.ts
|
|
6437
|
+
import { tool as tool12 } from "ai";
|
|
6438
|
+
import { z as z13 } from "zod";
|
|
6439
|
+
import { readFile as readFile9, stat as stat4 } from "fs/promises";
|
|
6440
|
+
import { join as join5, basename as basename4, extname as extname7 } from "path";
|
|
6441
|
+
var MIME_TYPES = {
|
|
6442
|
+
".txt": "text/plain",
|
|
6443
|
+
".md": "text/markdown",
|
|
6444
|
+
".html": "text/html",
|
|
6445
|
+
".css": "text/css",
|
|
6446
|
+
".js": "application/javascript",
|
|
6447
|
+
".ts": "application/typescript",
|
|
6448
|
+
".json": "application/json",
|
|
6449
|
+
".csv": "text/csv",
|
|
6450
|
+
".xml": "application/xml",
|
|
6451
|
+
".pdf": "application/pdf",
|
|
6452
|
+
".png": "image/png",
|
|
6453
|
+
".jpg": "image/jpeg",
|
|
6454
|
+
".jpeg": "image/jpeg",
|
|
6455
|
+
".gif": "image/gif",
|
|
6456
|
+
".webp": "image/webp",
|
|
6457
|
+
".svg": "image/svg+xml",
|
|
6458
|
+
".mp4": "video/mp4",
|
|
6459
|
+
".webm": "video/webm",
|
|
6460
|
+
".mp3": "audio/mpeg",
|
|
6461
|
+
".wav": "audio/wav",
|
|
6462
|
+
".zip": "application/zip",
|
|
6463
|
+
".tar": "application/x-tar",
|
|
6464
|
+
".gz": "application/gzip"
|
|
6465
|
+
};
|
|
6466
|
+
function createUploadFileTool(options) {
|
|
6467
|
+
return tool12({
|
|
6468
|
+
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.`,
|
|
6469
|
+
inputSchema: z13.object({
|
|
6470
|
+
path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
|
|
6471
|
+
name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
|
|
6472
|
+
}),
|
|
6473
|
+
execute: async (input) => {
|
|
6474
|
+
try {
|
|
6475
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6476
|
+
if (!isRemoteConfigured2()) {
|
|
6477
|
+
return {
|
|
6478
|
+
success: false,
|
|
6479
|
+
error: "File upload is not available \u2014 remote server with GCS is not configured."
|
|
6480
|
+
};
|
|
6481
|
+
}
|
|
6482
|
+
const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
|
|
6483
|
+
try {
|
|
6484
|
+
await stat4(fullPath);
|
|
6485
|
+
} catch {
|
|
6486
|
+
return {
|
|
6487
|
+
success: false,
|
|
6488
|
+
error: `File not found: ${input.path}`
|
|
6489
|
+
};
|
|
6490
|
+
}
|
|
6491
|
+
const fileName = input.name || basename4(fullPath);
|
|
6492
|
+
const ext = extname7(fullPath).toLowerCase();
|
|
6493
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
6494
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6495
|
+
options.sessionId,
|
|
6496
|
+
fileName,
|
|
6497
|
+
contentType,
|
|
6498
|
+
"general"
|
|
6499
|
+
);
|
|
6500
|
+
const fileData = await readFile9(fullPath);
|
|
6501
|
+
const putRes = await fetch(uploadInfo.uploadUrl, {
|
|
6502
|
+
method: "PUT",
|
|
6503
|
+
headers: { "Content-Type": contentType },
|
|
6504
|
+
body: fileData
|
|
6505
|
+
});
|
|
6506
|
+
if (!putRes.ok) {
|
|
6507
|
+
return {
|
|
6508
|
+
success: false,
|
|
6509
|
+
error: `Upload failed: ${putRes.status} ${putRes.statusText}`
|
|
6510
|
+
};
|
|
6511
|
+
}
|
|
6512
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
6513
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6514
|
+
return {
|
|
6515
|
+
success: true,
|
|
6516
|
+
fileId: uploadInfo.fileId,
|
|
6517
|
+
fileName,
|
|
6518
|
+
sizeBytes: fileData.length,
|
|
6519
|
+
contentType,
|
|
6520
|
+
downloadUrl: downloadInfo.downloadUrl,
|
|
6521
|
+
expiresAt: downloadInfo.expiresAt
|
|
6522
|
+
};
|
|
6523
|
+
} catch (err) {
|
|
6524
|
+
return {
|
|
6525
|
+
success: false,
|
|
6526
|
+
error: `Upload failed: ${err.message}`
|
|
6527
|
+
};
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
});
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6030
6533
|
// src/tools/index.ts
|
|
6031
6534
|
init_semantic();
|
|
6535
|
+
init_remote();
|
|
6032
6536
|
init_semantic_search();
|
|
6033
6537
|
async function createTools(options) {
|
|
6034
6538
|
const tools = {
|
|
@@ -6066,6 +6570,12 @@ async function createTools(options) {
|
|
|
6066
6570
|
workingDirectory: options.workingDirectory
|
|
6067
6571
|
})
|
|
6068
6572
|
};
|
|
6573
|
+
if (isRemoteConfigured()) {
|
|
6574
|
+
tools.upload_file = createUploadFileTool({
|
|
6575
|
+
workingDirectory: options.workingDirectory,
|
|
6576
|
+
sessionId: options.sessionId
|
|
6577
|
+
});
|
|
6578
|
+
}
|
|
6069
6579
|
if (options.enableSemanticSearch !== false) {
|
|
6070
6580
|
try {
|
|
6071
6581
|
if (isVectorGatewayConfigured()) {
|
|
@@ -6160,6 +6670,7 @@ You have access to powerful tools for:
|
|
|
6160
6670
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
6161
6671
|
- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
|
|
6162
6672
|
- **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
|
|
6673
|
+
- **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
|
|
6163
6674
|
|
|
6164
6675
|
|
|
6165
6676
|
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.
|
|
@@ -6418,14 +6929,53 @@ function buildTaskPromptAddendum(outputSchema) {
|
|
|
6418
6929
|
## Task Mode
|
|
6419
6930
|
|
|
6420
6931
|
You are running in **task mode**. You have been given a specific task to complete autonomously.
|
|
6932
|
+
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.
|
|
6933
|
+
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.
|
|
6421
6934
|
|
|
6422
6935
|
### Rules
|
|
6423
6936
|
1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
|
|
6424
|
-
2. Keep working until the task is fully complete.
|
|
6937
|
+
2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
|
|
6425
6938
|
3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
6426
6939
|
4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
6427
6940
|
5. Do NOT stop without calling one of these two tools.
|
|
6428
6941
|
|
|
6942
|
+
### Verification \u2014 BE EXTREMELY THOROUGH
|
|
6943
|
+
Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
|
|
6944
|
+
|
|
6945
|
+
**After making code changes:**
|
|
6946
|
+
- Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
|
|
6947
|
+
- **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
|
|
6948
|
+
- If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
|
|
6949
|
+
- If you created new files, verify they exist and contain what you expect.
|
|
6950
|
+
|
|
6951
|
+
**For UI / web changes:**
|
|
6952
|
+
- Start the dev server if it isn't already running (it might be so double check ur context)
|
|
6953
|
+
- **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
|
|
6954
|
+
- Check the dev server logs for errors or warnings.
|
|
6955
|
+
- If the app crashes or shows errors, fix them before completing.
|
|
6956
|
+
|
|
6957
|
+
**For backend / API changes:**
|
|
6958
|
+
- Test the endpoint with curl or a quick script to confirm it works as expected.
|
|
6959
|
+
- Check server logs for errors.
|
|
6960
|
+
|
|
6961
|
+
**For search and exploration tasks:**
|
|
6962
|
+
- Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
|
|
6963
|
+
- Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
|
|
6964
|
+
- Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
|
|
6965
|
+
- Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
|
|
6966
|
+
|
|
6967
|
+
**General verification checklist:**
|
|
6968
|
+
- Re-read the original task prompt and confirm every requirement has been addressed.
|
|
6969
|
+
- If the task asked for multiple things, verify EACH one individually.
|
|
6970
|
+
- If something doesn't look right, fix it \u2014 don't complete with known issues.
|
|
6971
|
+
|
|
6972
|
+
### Use All Available Tools
|
|
6973
|
+
- **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
|
|
6974
|
+
- **explore_agent**: Use for codebase exploration and understanding before making changes.
|
|
6975
|
+
- **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
|
|
6976
|
+
- **todo**: Track your progress on multi-step tasks so you don't miss steps.
|
|
6977
|
+
- **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
|
|
6978
|
+
|
|
6429
6979
|
### Output Schema
|
|
6430
6980
|
The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
|
|
6431
6981
|
\`\`\`json
|
|
@@ -6433,7 +6983,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
6433
6983
|
\`\`\`
|
|
6434
6984
|
|
|
6435
6985
|
### Completion Tools
|
|
6436
|
-
- **\`complete_task({ result: ... })\`** \u2014 Call
|
|
6986
|
+
- **\`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.
|
|
6437
6987
|
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
6438
6988
|
`;
|
|
6439
6989
|
}
|
|
@@ -6941,11 +7491,29 @@ ${prompt}` });
|
|
|
6941
7491
|
const onComplete = (signal) => {
|
|
6942
7492
|
completion.signal = signal;
|
|
6943
7493
|
};
|
|
7494
|
+
let taskRecorder = null;
|
|
7495
|
+
const sessionId = this.session.id;
|
|
7496
|
+
const bashProgressHandler = (progress) => {
|
|
7497
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
7498
|
+
const port = progress.browserStreamPort;
|
|
7499
|
+
if (port && progress.status === "started") {
|
|
7500
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
7501
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
7502
|
+
if (!taskRecorder) {
|
|
7503
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
7504
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
7505
|
+
taskRecorder.start();
|
|
7506
|
+
proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
|
|
7507
|
+
});
|
|
7508
|
+
}
|
|
7509
|
+
});
|
|
7510
|
+
}
|
|
7511
|
+
};
|
|
6944
7512
|
const taskTools = await createTools({
|
|
6945
7513
|
sessionId: this.session.id,
|
|
6946
7514
|
workingDirectory: this.session.workingDirectory,
|
|
6947
7515
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
6948
|
-
onBashProgress:
|
|
7516
|
+
onBashProgress: bashProgressHandler,
|
|
6949
7517
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
6950
7518
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
|
|
6951
7519
|
taskTools: {
|
|
@@ -7016,12 +7584,24 @@ ${taskAddendum}`;
|
|
|
7016
7584
|
if (completion.signal) {
|
|
7017
7585
|
const sig = completion.signal;
|
|
7018
7586
|
const finalStatus = sig.status;
|
|
7587
|
+
let fileUrls;
|
|
7588
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
7589
|
+
const resultObj = sig.result;
|
|
7590
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
7591
|
+
if (filePaths.length > 0) {
|
|
7592
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
7596
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
7019
7597
|
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
7020
7598
|
fireWebhook(eventType, {
|
|
7021
7599
|
status: finalStatus,
|
|
7022
7600
|
result: sig.result,
|
|
7023
7601
|
error: sig.error,
|
|
7024
|
-
iterations: iteration
|
|
7602
|
+
iterations: iteration,
|
|
7603
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
7604
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
7025
7605
|
});
|
|
7026
7606
|
const updatedTask2 = {
|
|
7027
7607
|
...options.taskConfig,
|
|
@@ -7041,11 +7621,17 @@ ${taskAddendum}`;
|
|
|
7041
7621
|
};
|
|
7042
7622
|
}
|
|
7043
7623
|
await this.context.addUserMessage(
|
|
7044
|
-
"Continue working on the task. When
|
|
7624
|
+
"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."
|
|
7045
7625
|
);
|
|
7046
7626
|
}
|
|
7047
7627
|
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
7048
|
-
|
|
7628
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
7629
|
+
fireWebhook("task.failed", {
|
|
7630
|
+
status: "failed",
|
|
7631
|
+
error: timeoutError,
|
|
7632
|
+
iterations: iteration,
|
|
7633
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
7634
|
+
});
|
|
7049
7635
|
const updatedTask = {
|
|
7050
7636
|
...options.taskConfig,
|
|
7051
7637
|
status: "failed",
|
|
@@ -7057,6 +7643,113 @@ ${taskAddendum}`;
|
|
|
7057
7643
|
});
|
|
7058
7644
|
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
7059
7645
|
}
|
|
7646
|
+
/**
|
|
7647
|
+
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
7648
|
+
* Returns download URLs for any recordings produced.
|
|
7649
|
+
*/
|
|
7650
|
+
async finishTaskRecording(recorder) {
|
|
7651
|
+
try {
|
|
7652
|
+
const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
|
|
7653
|
+
destroyProxy2(this.session.id);
|
|
7654
|
+
} catch {
|
|
7655
|
+
}
|
|
7656
|
+
if (!recorder || recorder.frameCount === 0) {
|
|
7657
|
+
recorder?.clear();
|
|
7658
|
+
return [];
|
|
7659
|
+
}
|
|
7660
|
+
recorder.stop();
|
|
7661
|
+
try {
|
|
7662
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7663
|
+
if (!isRemoteConfigured2()) {
|
|
7664
|
+
recorder.clear();
|
|
7665
|
+
return [];
|
|
7666
|
+
}
|
|
7667
|
+
const result = await recorder.encode();
|
|
7668
|
+
recorder.clear();
|
|
7669
|
+
if (!result) return [];
|
|
7670
|
+
const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
|
|
7671
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
7672
|
+
this.session.id,
|
|
7673
|
+
`browser-recording-${Date.now()}.mp4`,
|
|
7674
|
+
"video/mp4",
|
|
7675
|
+
"browser-recording"
|
|
7676
|
+
);
|
|
7677
|
+
const fileData = await readFile11(result.path);
|
|
7678
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
7679
|
+
method: "PUT",
|
|
7680
|
+
headers: { "Content-Type": "video/mp4" },
|
|
7681
|
+
body: fileData
|
|
7682
|
+
});
|
|
7683
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
7684
|
+
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
7685
|
+
await unlink3(result.path).catch(() => {
|
|
7686
|
+
});
|
|
7687
|
+
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
7688
|
+
return [dlInfo.downloadUrl];
|
|
7689
|
+
} catch (err) {
|
|
7690
|
+
console.error("[TASK] Failed to upload browser recording:", err.message);
|
|
7691
|
+
recorder.clear();
|
|
7692
|
+
return [];
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
/**
|
|
7696
|
+
* Upload task output files to GCS via the remote server.
|
|
7697
|
+
* Returns an array of download URLs for successfully uploaded files.
|
|
7698
|
+
*/
|
|
7699
|
+
async uploadTaskFiles(filePaths) {
|
|
7700
|
+
try {
|
|
7701
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7702
|
+
if (!isRemoteConfigured2()) return [];
|
|
7703
|
+
const { readFile: readFile11 } = await import("fs/promises");
|
|
7704
|
+
const { join: join12, basename: basename6 } = await import("path");
|
|
7705
|
+
const urls = [];
|
|
7706
|
+
for (const filePath of filePaths) {
|
|
7707
|
+
try {
|
|
7708
|
+
const fullPath = filePath.startsWith("/") ? filePath : join12(this.session.workingDirectory, filePath);
|
|
7709
|
+
const fileName = basename6(fullPath);
|
|
7710
|
+
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
7711
|
+
const mimeMap = {
|
|
7712
|
+
pdf: "application/pdf",
|
|
7713
|
+
json: "application/json",
|
|
7714
|
+
csv: "text/csv",
|
|
7715
|
+
txt: "text/plain",
|
|
7716
|
+
md: "text/markdown",
|
|
7717
|
+
html: "text/html",
|
|
7718
|
+
png: "image/png",
|
|
7719
|
+
jpg: "image/jpeg",
|
|
7720
|
+
jpeg: "image/jpeg",
|
|
7721
|
+
gif: "image/gif",
|
|
7722
|
+
svg: "image/svg+xml",
|
|
7723
|
+
mp4: "video/mp4",
|
|
7724
|
+
zip: "application/zip"
|
|
7725
|
+
};
|
|
7726
|
+
const contentType = mimeMap[ext] || "application/octet-stream";
|
|
7727
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
7728
|
+
this.session.id,
|
|
7729
|
+
fileName,
|
|
7730
|
+
contentType,
|
|
7731
|
+
"task-output"
|
|
7732
|
+
);
|
|
7733
|
+
const fileData = await readFile11(fullPath);
|
|
7734
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
7735
|
+
method: "PUT",
|
|
7736
|
+
headers: { "Content-Type": contentType },
|
|
7737
|
+
body: fileData
|
|
7738
|
+
});
|
|
7739
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
7740
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
7741
|
+
urls.push(downloadInfo.downloadUrl);
|
|
7742
|
+
console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
|
|
7743
|
+
} catch (err) {
|
|
7744
|
+
console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
return urls;
|
|
7748
|
+
} catch (err) {
|
|
7749
|
+
console.error("[TASK] File upload failed:", err.message);
|
|
7750
|
+
return [];
|
|
7751
|
+
}
|
|
7752
|
+
}
|
|
7060
7753
|
/**
|
|
7061
7754
|
* Wrap tools to add approval checking
|
|
7062
7755
|
*/
|
|
@@ -7070,11 +7763,11 @@ ${taskAddendum}`;
|
|
|
7070
7763
|
wrappedTools[name] = originalTool;
|
|
7071
7764
|
continue;
|
|
7072
7765
|
}
|
|
7073
|
-
wrappedTools[name] =
|
|
7766
|
+
wrappedTools[name] = tool13({
|
|
7074
7767
|
description: originalTool.description || "",
|
|
7075
|
-
inputSchema: originalTool.inputSchema ||
|
|
7768
|
+
inputSchema: originalTool.inputSchema || z14.object({}),
|
|
7076
7769
|
execute: async (input, toolOptions) => {
|
|
7077
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
7770
|
+
const toolCallId = toolOptions.toolCallId || nanoid4();
|
|
7078
7771
|
const execution = toolExecutionQueries.create({
|
|
7079
7772
|
sessionId: this.session.id,
|
|
7080
7773
|
toolName: name,
|
|
@@ -7092,10 +7785,10 @@ ${taskAddendum}`;
|
|
|
7092
7785
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
7093
7786
|
approvalResolvers.delete(toolCallId);
|
|
7094
7787
|
this.pendingApprovals.delete(toolCallId);
|
|
7095
|
-
const
|
|
7788
|
+
const exec7 = await execution;
|
|
7096
7789
|
if (!approved) {
|
|
7097
7790
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
7098
|
-
await toolExecutionQueries.reject(
|
|
7791
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
7099
7792
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
7100
7793
|
return {
|
|
7101
7794
|
status: "rejected",
|
|
@@ -7105,14 +7798,14 @@ ${taskAddendum}`;
|
|
|
7105
7798
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
7106
7799
|
};
|
|
7107
7800
|
}
|
|
7108
|
-
await toolExecutionQueries.approve(
|
|
7801
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
7109
7802
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
7110
7803
|
try {
|
|
7111
7804
|
const result = await originalTool.execute(input, toolOptions);
|
|
7112
|
-
await toolExecutionQueries.complete(
|
|
7805
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
7113
7806
|
return result;
|
|
7114
7807
|
} catch (error) {
|
|
7115
|
-
await toolExecutionQueries.complete(
|
|
7808
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
7116
7809
|
throw error;
|
|
7117
7810
|
}
|
|
7118
7811
|
}
|
|
@@ -7213,18 +7906,18 @@ function cleanupPendingInputs() {
|
|
|
7213
7906
|
}
|
|
7214
7907
|
}
|
|
7215
7908
|
}
|
|
7216
|
-
var createSessionSchema =
|
|
7217
|
-
name:
|
|
7218
|
-
workingDirectory:
|
|
7219
|
-
model:
|
|
7220
|
-
toolApprovals:
|
|
7909
|
+
var createSessionSchema = z15.object({
|
|
7910
|
+
name: z15.string().optional(),
|
|
7911
|
+
workingDirectory: z15.string().optional(),
|
|
7912
|
+
model: z15.string().optional(),
|
|
7913
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
7221
7914
|
});
|
|
7222
|
-
var paginationQuerySchema =
|
|
7223
|
-
limit:
|
|
7224
|
-
offset:
|
|
7915
|
+
var paginationQuerySchema = z15.object({
|
|
7916
|
+
limit: z15.string().optional(),
|
|
7917
|
+
offset: z15.string().optional()
|
|
7225
7918
|
});
|
|
7226
|
-
var messagesQuerySchema =
|
|
7227
|
-
limit:
|
|
7919
|
+
var messagesQuerySchema = z15.object({
|
|
7920
|
+
limit: z15.string().optional()
|
|
7228
7921
|
});
|
|
7229
7922
|
sessions.get(
|
|
7230
7923
|
"/",
|
|
@@ -7363,10 +8056,10 @@ sessions.get("/:id/tools", async (c) => {
|
|
|
7363
8056
|
count: executions.length
|
|
7364
8057
|
});
|
|
7365
8058
|
});
|
|
7366
|
-
var updateSessionSchema =
|
|
7367
|
-
model:
|
|
7368
|
-
name:
|
|
7369
|
-
toolApprovals:
|
|
8059
|
+
var updateSessionSchema = z15.object({
|
|
8060
|
+
model: z15.string().optional(),
|
|
8061
|
+
name: z15.string().optional(),
|
|
8062
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
7370
8063
|
});
|
|
7371
8064
|
sessions.patch(
|
|
7372
8065
|
"/:id",
|
|
@@ -7436,8 +8129,8 @@ sessions.post("/:id/clear", async (c) => {
|
|
|
7436
8129
|
await agent.clearContext();
|
|
7437
8130
|
return c.json({ success: true, sessionId: id });
|
|
7438
8131
|
});
|
|
7439
|
-
var pendingInputSchema =
|
|
7440
|
-
text:
|
|
8132
|
+
var pendingInputSchema = z15.object({
|
|
8133
|
+
text: z15.string()
|
|
7441
8134
|
});
|
|
7442
8135
|
sessions.post(
|
|
7443
8136
|
"/:id/pending-input",
|
|
@@ -7468,13 +8161,13 @@ sessions.get("/:id/pending-input", async (c) => {
|
|
|
7468
8161
|
createdAt: pending.createdAt.toISOString()
|
|
7469
8162
|
});
|
|
7470
8163
|
});
|
|
7471
|
-
var devtoolsContextSchema =
|
|
7472
|
-
url:
|
|
7473
|
-
path:
|
|
7474
|
-
pageName:
|
|
7475
|
-
screenWidth:
|
|
7476
|
-
screenHeight:
|
|
7477
|
-
devicePixelRatio:
|
|
8164
|
+
var devtoolsContextSchema = z15.object({
|
|
8165
|
+
url: z15.string(),
|
|
8166
|
+
path: z15.string(),
|
|
8167
|
+
pageName: z15.string().optional(),
|
|
8168
|
+
screenWidth: z15.number().optional(),
|
|
8169
|
+
screenHeight: z15.number().optional(),
|
|
8170
|
+
devicePixelRatio: z15.number().optional()
|
|
7478
8171
|
});
|
|
7479
8172
|
sessions.post(
|
|
7480
8173
|
"/:id/devtools-context",
|
|
@@ -7642,7 +8335,7 @@ sessions.get("/:id/diff/:filePath", async (c) => {
|
|
|
7642
8335
|
});
|
|
7643
8336
|
function getAttachmentsDir(sessionId) {
|
|
7644
8337
|
const appDataDir = getAppDataDirectory();
|
|
7645
|
-
return
|
|
8338
|
+
return join7(appDataDir, "attachments", sessionId);
|
|
7646
8339
|
}
|
|
7647
8340
|
function ensureAttachmentsDir(sessionId) {
|
|
7648
8341
|
const dir = getAttachmentsDir(sessionId);
|
|
@@ -7663,7 +8356,7 @@ sessions.get("/:id/attachments", async (c) => {
|
|
|
7663
8356
|
}
|
|
7664
8357
|
const files = readdirSync(dir);
|
|
7665
8358
|
const attachments = files.map((filename) => {
|
|
7666
|
-
const filePath =
|
|
8359
|
+
const filePath = join7(dir, filename);
|
|
7667
8360
|
const stats = statSync2(filePath);
|
|
7668
8361
|
return {
|
|
7669
8362
|
id: filename.split("_")[0],
|
|
@@ -7695,10 +8388,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
7695
8388
|
return c.json({ error: "No file provided" }, 400);
|
|
7696
8389
|
}
|
|
7697
8390
|
const dir = ensureAttachmentsDir(sessionId);
|
|
7698
|
-
const id =
|
|
7699
|
-
const ext =
|
|
7700
|
-
const safeFilename = `${id}_${
|
|
7701
|
-
const filePath =
|
|
8391
|
+
const id = nanoid5(10);
|
|
8392
|
+
const ext = extname8(file.name) || "";
|
|
8393
|
+
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
8394
|
+
const filePath = join7(dir, safeFilename);
|
|
7702
8395
|
const arrayBuffer = await file.arrayBuffer();
|
|
7703
8396
|
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
7704
8397
|
return c.json({
|
|
@@ -7721,10 +8414,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
7721
8414
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
7722
8415
|
}
|
|
7723
8416
|
const dir = ensureAttachmentsDir(sessionId);
|
|
7724
|
-
const id =
|
|
7725
|
-
const ext =
|
|
7726
|
-
const safeFilename = `${id}_${
|
|
7727
|
-
const filePath =
|
|
8417
|
+
const id = nanoid5(10);
|
|
8418
|
+
const ext = extname8(body.filename) || "";
|
|
8419
|
+
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
8420
|
+
const filePath = join7(dir, safeFilename);
|
|
7728
8421
|
let base64Data = body.data;
|
|
7729
8422
|
if (base64Data.includes(",")) {
|
|
7730
8423
|
base64Data = base64Data.split(",")[1];
|
|
@@ -7761,14 +8454,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
7761
8454
|
if (!file) {
|
|
7762
8455
|
return c.json({ error: "Attachment not found" }, 404);
|
|
7763
8456
|
}
|
|
7764
|
-
const filePath =
|
|
8457
|
+
const filePath = join7(dir, file);
|
|
7765
8458
|
unlinkSync(filePath);
|
|
7766
8459
|
return c.json({ success: true, id: attachmentId });
|
|
7767
8460
|
});
|
|
7768
|
-
var filesQuerySchema =
|
|
7769
|
-
query:
|
|
8461
|
+
var filesQuerySchema = z15.object({
|
|
8462
|
+
query: z15.string().optional(),
|
|
7770
8463
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
7771
|
-
limit:
|
|
8464
|
+
limit: z15.string().optional()
|
|
7772
8465
|
// Max results (default 50)
|
|
7773
8466
|
});
|
|
7774
8467
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -7841,10 +8534,10 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7841
8534
|
return results;
|
|
7842
8535
|
}
|
|
7843
8536
|
try {
|
|
7844
|
-
const entries = await
|
|
8537
|
+
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
7845
8538
|
for (const entry of entries) {
|
|
7846
8539
|
if (results.length >= limit * 2) break;
|
|
7847
|
-
const fullPath =
|
|
8540
|
+
const fullPath = join7(currentDir, entry.name);
|
|
7848
8541
|
const relativePath = relative9(baseDir, fullPath);
|
|
7849
8542
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
7850
8543
|
continue;
|
|
@@ -7852,7 +8545,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7852
8545
|
if (entry.name.startsWith(".")) {
|
|
7853
8546
|
continue;
|
|
7854
8547
|
}
|
|
7855
|
-
const ext =
|
|
8548
|
+
const ext = extname8(entry.name).toLowerCase();
|
|
7856
8549
|
if (IGNORED_EXTENSIONS.has(ext)) {
|
|
7857
8550
|
continue;
|
|
7858
8551
|
}
|
|
@@ -7941,14 +8634,70 @@ sessions.get(
|
|
|
7941
8634
|
}
|
|
7942
8635
|
}
|
|
7943
8636
|
);
|
|
8637
|
+
sessions.get("/:id/session-files", async (c) => {
|
|
8638
|
+
const sessionId = c.req.param("id");
|
|
8639
|
+
try {
|
|
8640
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
8641
|
+
if (!isRemoteConfigured2()) {
|
|
8642
|
+
return c.json({ files: [] });
|
|
8643
|
+
}
|
|
8644
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
8645
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
8646
|
+
return c.json({ sessionId, files });
|
|
8647
|
+
} catch (err) {
|
|
8648
|
+
console.error("Failed to get session files:", err.message);
|
|
8649
|
+
return c.json({ sessionId, files: [] });
|
|
8650
|
+
}
|
|
8651
|
+
});
|
|
8652
|
+
sessions.get("/files/:fileId/download", async (c) => {
|
|
8653
|
+
const fileId = c.req.param("fileId");
|
|
8654
|
+
try {
|
|
8655
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
8656
|
+
if (!isRemoteConfigured2()) {
|
|
8657
|
+
return c.json({ error: "Remote server not configured" }, 503);
|
|
8658
|
+
}
|
|
8659
|
+
const result = await storageQueries2.getDownloadUrl(fileId);
|
|
8660
|
+
return c.json(result);
|
|
8661
|
+
} catch (err) {
|
|
8662
|
+
return c.json({ error: err.message }, 500);
|
|
8663
|
+
}
|
|
8664
|
+
});
|
|
8665
|
+
sessions.get("/:id/browser-recording", async (c) => {
|
|
8666
|
+
const sessionId = c.req.param("id");
|
|
8667
|
+
try {
|
|
8668
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
8669
|
+
if (!isRemoteConfigured2()) {
|
|
8670
|
+
return c.json({ sessionId, recordings: [] });
|
|
8671
|
+
}
|
|
8672
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
8673
|
+
const recordings = files.filter((f) => f.category === "browser-recording");
|
|
8674
|
+
if (recordings.length === 0) {
|
|
8675
|
+
return c.json({ sessionId, recordings: [], message: "No browser recordings for this session" });
|
|
8676
|
+
}
|
|
8677
|
+
return c.json({
|
|
8678
|
+
sessionId,
|
|
8679
|
+
recordings: recordings.map((r) => ({
|
|
8680
|
+
id: r.id,
|
|
8681
|
+
fileName: r.fileName,
|
|
8682
|
+
sizeBytes: r.sizeBytes,
|
|
8683
|
+
createdAt: r.createdAt,
|
|
8684
|
+
downloadUrl: r.downloadUrl,
|
|
8685
|
+
expiresAt: r.downloadUrlExpiresAt
|
|
8686
|
+
}))
|
|
8687
|
+
});
|
|
8688
|
+
} catch (err) {
|
|
8689
|
+
console.error("Failed to get browser recordings:", err.message);
|
|
8690
|
+
return c.json({ sessionId, recordings: [], error: err.message });
|
|
8691
|
+
}
|
|
8692
|
+
});
|
|
7944
8693
|
|
|
7945
8694
|
// src/server/routes/agents.ts
|
|
7946
8695
|
init_db();
|
|
7947
8696
|
import { Hono as Hono2 } from "hono";
|
|
7948
8697
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
7949
|
-
import { z as
|
|
8698
|
+
import { z as z16 } from "zod";
|
|
7950
8699
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
7951
|
-
import { join as
|
|
8700
|
+
import { join as join8 } from "path";
|
|
7952
8701
|
init_config();
|
|
7953
8702
|
|
|
7954
8703
|
// src/server/resumable-stream.ts
|
|
@@ -8024,7 +8773,11 @@ var streamContext = createResumableStreamContext({
|
|
|
8024
8773
|
});
|
|
8025
8774
|
|
|
8026
8775
|
// src/server/routes/agents.ts
|
|
8027
|
-
import { nanoid as
|
|
8776
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
8777
|
+
init_stream_proxy();
|
|
8778
|
+
init_recorder();
|
|
8779
|
+
init_remote();
|
|
8780
|
+
var sessionRecorders = /* @__PURE__ */ new Map();
|
|
8028
8781
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
8029
8782
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
8030
8783
|
var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
|
|
@@ -8111,35 +8864,35 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
8111
8864
|
${prompt}`;
|
|
8112
8865
|
}
|
|
8113
8866
|
var agents = new Hono2();
|
|
8114
|
-
var attachmentSchema =
|
|
8115
|
-
type:
|
|
8116
|
-
data:
|
|
8867
|
+
var attachmentSchema = z16.object({
|
|
8868
|
+
type: z16.enum(["image", "file"]),
|
|
8869
|
+
data: z16.string(),
|
|
8117
8870
|
// base64 data URL or raw base64
|
|
8118
|
-
mediaType:
|
|
8119
|
-
filename:
|
|
8871
|
+
mediaType: z16.string().optional(),
|
|
8872
|
+
filename: z16.string().optional()
|
|
8120
8873
|
});
|
|
8121
|
-
var runPromptSchema =
|
|
8122
|
-
prompt:
|
|
8874
|
+
var runPromptSchema = z16.object({
|
|
8875
|
+
prompt: z16.string(),
|
|
8123
8876
|
// Can be empty if attachments are provided
|
|
8124
|
-
attachments:
|
|
8877
|
+
attachments: z16.array(attachmentSchema).optional()
|
|
8125
8878
|
}).refine(
|
|
8126
8879
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
8127
8880
|
{ message: "Either prompt or attachments must be provided" }
|
|
8128
8881
|
);
|
|
8129
|
-
var quickStartSchema =
|
|
8130
|
-
prompt:
|
|
8131
|
-
name:
|
|
8132
|
-
workingDirectory:
|
|
8133
|
-
model:
|
|
8134
|
-
toolApprovals:
|
|
8882
|
+
var quickStartSchema = z16.object({
|
|
8883
|
+
prompt: z16.string().min(1),
|
|
8884
|
+
name: z16.string().optional(),
|
|
8885
|
+
workingDirectory: z16.string().optional(),
|
|
8886
|
+
model: z16.string().optional(),
|
|
8887
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
8135
8888
|
});
|
|
8136
|
-
var rejectSchema =
|
|
8137
|
-
reason:
|
|
8889
|
+
var rejectSchema = z16.object({
|
|
8890
|
+
reason: z16.string().optional()
|
|
8138
8891
|
}).optional();
|
|
8139
8892
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
8140
8893
|
function getAttachmentsDirectory(sessionId) {
|
|
8141
8894
|
const appDataDir = getAppDataDirectory();
|
|
8142
|
-
return
|
|
8895
|
+
return join8(appDataDir, "attachments", sessionId);
|
|
8143
8896
|
}
|
|
8144
8897
|
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
8145
8898
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
@@ -8155,7 +8908,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
8155
8908
|
if (base64Data.includes(",")) {
|
|
8156
8909
|
base64Data = base64Data.split(",")[1];
|
|
8157
8910
|
}
|
|
8158
|
-
const filePath =
|
|
8911
|
+
const filePath = join8(attachmentsDir, filename);
|
|
8159
8912
|
const buffer = Buffer.from(base64Data, "base64");
|
|
8160
8913
|
writeFileSync3(filePath, buffer);
|
|
8161
8914
|
return filePath;
|
|
@@ -8323,6 +9076,40 @@ ${prompt}` });
|
|
|
8323
9076
|
}));
|
|
8324
9077
|
await new Promise((resolve12) => setTimeout(resolve12, 0));
|
|
8325
9078
|
}
|
|
9079
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
9080
|
+
const browserClosed = progress.data?.browserClosed;
|
|
9081
|
+
if (progress.toolName === "bash" && browserClosed) {
|
|
9082
|
+
console.log(`[BROWSER-STREAM] agent-browser close detected, destroying proxy`);
|
|
9083
|
+
destroyProxy(sessionId);
|
|
9084
|
+
} else if (progress.toolName === "bash" && browserPort) {
|
|
9085
|
+
console.log(`[BROWSER-STREAM] agent-browser command detected, ensuring proxy on port ${browserPort}`);
|
|
9086
|
+
const proxy = getOrCreateProxy(sessionId, browserPort);
|
|
9087
|
+
if (!sessionRecorders.has(sessionId)) {
|
|
9088
|
+
const recorder = new FrameRecorder(sessionId);
|
|
9089
|
+
recorder.start();
|
|
9090
|
+
sessionRecorders.set(sessionId, recorder);
|
|
9091
|
+
}
|
|
9092
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
9093
|
+
proxy.on("frame", (frame) => {
|
|
9094
|
+
const rec = sessionRecorders.get(sessionId);
|
|
9095
|
+
rec?.addFrame(frame);
|
|
9096
|
+
writeSSE(JSON.stringify({
|
|
9097
|
+
type: "browser-frame",
|
|
9098
|
+
data: frame.data,
|
|
9099
|
+
metadata: frame.metadata
|
|
9100
|
+
})).catch(() => {
|
|
9101
|
+
});
|
|
9102
|
+
});
|
|
9103
|
+
proxy.on("status", (s) => {
|
|
9104
|
+
console.log(`[BROWSER-STREAM] Status:`, s);
|
|
9105
|
+
writeSSE(JSON.stringify({
|
|
9106
|
+
type: "browser-status",
|
|
9107
|
+
...s
|
|
9108
|
+
})).catch(() => {
|
|
9109
|
+
});
|
|
9110
|
+
});
|
|
9111
|
+
}
|
|
9112
|
+
}
|
|
8326
9113
|
},
|
|
8327
9114
|
onStepFinish: async () => {
|
|
8328
9115
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -8509,7 +9296,7 @@ ${prompt}` });
|
|
|
8509
9296
|
userMessageContent = prompt;
|
|
8510
9297
|
}
|
|
8511
9298
|
await messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
8512
|
-
const streamId = `stream_${id}_${
|
|
9299
|
+
const streamId = `stream_${id}_${nanoid6(10)}`;
|
|
8513
9300
|
await activeStreamQueries.create(id, streamId);
|
|
8514
9301
|
const stream = await streamContext.resumableStream(
|
|
8515
9302
|
streamId,
|
|
@@ -8708,7 +9495,7 @@ agents.post(
|
|
|
8708
9495
|
});
|
|
8709
9496
|
const session = agent.getSession();
|
|
8710
9497
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
8711
|
-
const streamId = `stream_${session.id}_${
|
|
9498
|
+
const streamId = `stream_${session.id}_${nanoid6(10)}`;
|
|
8712
9499
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
8713
9500
|
await activeStreamQueries.create(session.id, streamId);
|
|
8714
9501
|
const createQuickStreamProducer = () => {
|
|
@@ -8787,6 +9574,40 @@ agents.post(
|
|
|
8787
9574
|
}));
|
|
8788
9575
|
await new Promise((resolve12) => setTimeout(resolve12, 0));
|
|
8789
9576
|
}
|
|
9577
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
9578
|
+
const browserClosed = progress.data?.browserClosed;
|
|
9579
|
+
if (progress.toolName === "bash" && browserClosed) {
|
|
9580
|
+
console.log(`[BROWSER-STREAM] agent-browser close detected`);
|
|
9581
|
+
destroyProxy(session.id);
|
|
9582
|
+
} else if (progress.toolName === "bash" && browserPort) {
|
|
9583
|
+
console.log(`[BROWSER-STREAM] agent-browser command detected, port ${browserPort}`);
|
|
9584
|
+
const proxy = getOrCreateProxy(session.id, browserPort);
|
|
9585
|
+
if (!sessionRecorders.has(session.id)) {
|
|
9586
|
+
const recorder = new FrameRecorder(session.id);
|
|
9587
|
+
recorder.start();
|
|
9588
|
+
sessionRecorders.set(session.id, recorder);
|
|
9589
|
+
}
|
|
9590
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
9591
|
+
proxy.on("frame", (frame) => {
|
|
9592
|
+
const rec = sessionRecorders.get(session.id);
|
|
9593
|
+
rec?.addFrame(frame);
|
|
9594
|
+
writeSSE(JSON.stringify({
|
|
9595
|
+
type: "browser-frame",
|
|
9596
|
+
data: frame.data,
|
|
9597
|
+
metadata: frame.metadata
|
|
9598
|
+
})).catch(() => {
|
|
9599
|
+
});
|
|
9600
|
+
});
|
|
9601
|
+
proxy.on("status", (s) => {
|
|
9602
|
+
console.log(`[BROWSER-STREAM] Status:`, s);
|
|
9603
|
+
writeSSE(JSON.stringify({
|
|
9604
|
+
type: "browser-status",
|
|
9605
|
+
...s
|
|
9606
|
+
})).catch(() => {
|
|
9607
|
+
});
|
|
9608
|
+
});
|
|
9609
|
+
}
|
|
9610
|
+
}
|
|
8790
9611
|
},
|
|
8791
9612
|
onStepFinish: async () => {
|
|
8792
9613
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -8917,25 +9738,71 @@ agents.post(
|
|
|
8917
9738
|
});
|
|
8918
9739
|
}
|
|
8919
9740
|
);
|
|
9741
|
+
var browserInputSchema = z16.object({
|
|
9742
|
+
type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
9743
|
+
eventType: z16.string(),
|
|
9744
|
+
x: z16.number().optional(),
|
|
9745
|
+
y: z16.number().optional(),
|
|
9746
|
+
button: z16.string().optional(),
|
|
9747
|
+
clickCount: z16.number().optional(),
|
|
9748
|
+
deltaX: z16.number().optional(),
|
|
9749
|
+
deltaY: z16.number().optional(),
|
|
9750
|
+
key: z16.string().optional(),
|
|
9751
|
+
code: z16.string().optional(),
|
|
9752
|
+
text: z16.string().optional(),
|
|
9753
|
+
modifiers: z16.number().optional(),
|
|
9754
|
+
touchPoints: z16.array(z16.object({
|
|
9755
|
+
x: z16.number(),
|
|
9756
|
+
y: z16.number(),
|
|
9757
|
+
id: z16.number().optional()
|
|
9758
|
+
})).optional()
|
|
9759
|
+
});
|
|
9760
|
+
agents.post(
|
|
9761
|
+
"/:id/browser-input",
|
|
9762
|
+
zValidator2("json", browserInputSchema),
|
|
9763
|
+
async (c) => {
|
|
9764
|
+
const sessionId = c.req.param("id");
|
|
9765
|
+
const event = c.req.valid("json");
|
|
9766
|
+
const proxy = getProxy(sessionId);
|
|
9767
|
+
if (!proxy || !proxy.connected) {
|
|
9768
|
+
return c.json({ error: "No active browser stream for this session" }, 404);
|
|
9769
|
+
}
|
|
9770
|
+
proxy.injectInput(event);
|
|
9771
|
+
return c.json({ success: true });
|
|
9772
|
+
}
|
|
9773
|
+
);
|
|
9774
|
+
agents.get("/:id/browser-stream", async (c) => {
|
|
9775
|
+
const sessionId = c.req.param("id");
|
|
9776
|
+
const proxy = getProxy(sessionId);
|
|
9777
|
+
return c.json({
|
|
9778
|
+
sessionId,
|
|
9779
|
+
active: !!proxy?.connected,
|
|
9780
|
+
hasProxy: !!proxy,
|
|
9781
|
+
latestFrame: proxy?.latestFrame ? {
|
|
9782
|
+
metadata: proxy.latestFrame.metadata,
|
|
9783
|
+
timestamp: proxy.latestFrame.timestamp
|
|
9784
|
+
} : null
|
|
9785
|
+
});
|
|
9786
|
+
});
|
|
8920
9787
|
|
|
8921
9788
|
// src/server/routes/health.ts
|
|
8922
9789
|
init_config();
|
|
8923
9790
|
import { Hono as Hono3 } from "hono";
|
|
8924
9791
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
8925
|
-
import { z as
|
|
9792
|
+
import { z as z17 } from "zod";
|
|
8926
9793
|
import { readFileSync as readFileSync5 } from "fs";
|
|
8927
9794
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8928
|
-
import { dirname as dirname6, join as
|
|
9795
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
8929
9796
|
var __filename = fileURLToPath3(import.meta.url);
|
|
8930
9797
|
var __dirname = dirname6(__filename);
|
|
8931
9798
|
var possiblePaths = [
|
|
8932
|
-
|
|
9799
|
+
join9(__dirname, "../package.json"),
|
|
8933
9800
|
// From dist/server -> dist/../package.json
|
|
8934
|
-
|
|
9801
|
+
join9(__dirname, "../../package.json"),
|
|
8935
9802
|
// From dist/server (if nested differently)
|
|
8936
|
-
|
|
9803
|
+
join9(__dirname, "../../../package.json"),
|
|
8937
9804
|
// From src/server/routes (development)
|
|
8938
|
-
|
|
9805
|
+
join9(process.cwd(), "package.json")
|
|
8939
9806
|
// From current working directory
|
|
8940
9807
|
];
|
|
8941
9808
|
var currentVersion = "0.0.0";
|
|
@@ -9032,9 +9899,9 @@ health.get("/api-keys", async (c) => {
|
|
|
9032
9899
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
9033
9900
|
});
|
|
9034
9901
|
});
|
|
9035
|
-
var setApiKeySchema =
|
|
9036
|
-
provider:
|
|
9037
|
-
apiKey:
|
|
9902
|
+
var setApiKeySchema = z17.object({
|
|
9903
|
+
provider: z17.string(),
|
|
9904
|
+
apiKey: z17.string().min(1)
|
|
9038
9905
|
});
|
|
9039
9906
|
health.post(
|
|
9040
9907
|
"/api-keys",
|
|
@@ -9073,13 +9940,13 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
9073
9940
|
// src/server/routes/terminals.ts
|
|
9074
9941
|
import { Hono as Hono4 } from "hono";
|
|
9075
9942
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
9076
|
-
import { z as
|
|
9943
|
+
import { z as z18 } from "zod";
|
|
9077
9944
|
init_db();
|
|
9078
9945
|
var terminals = new Hono4();
|
|
9079
|
-
var spawnSchema =
|
|
9080
|
-
command:
|
|
9081
|
-
cwd:
|
|
9082
|
-
name:
|
|
9946
|
+
var spawnSchema = z18.object({
|
|
9947
|
+
command: z18.string(),
|
|
9948
|
+
cwd: z18.string().optional(),
|
|
9949
|
+
name: z18.string().optional()
|
|
9083
9950
|
});
|
|
9084
9951
|
terminals.post(
|
|
9085
9952
|
"/:sessionId/terminals",
|
|
@@ -9160,8 +10027,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
9160
10027
|
// We don't track exit codes in tmux mode
|
|
9161
10028
|
});
|
|
9162
10029
|
});
|
|
9163
|
-
var logsQuerySchema =
|
|
9164
|
-
tail:
|
|
10030
|
+
var logsQuerySchema = z18.object({
|
|
10031
|
+
tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
9165
10032
|
});
|
|
9166
10033
|
terminals.get(
|
|
9167
10034
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -9185,8 +10052,8 @@ terminals.get(
|
|
|
9185
10052
|
});
|
|
9186
10053
|
}
|
|
9187
10054
|
);
|
|
9188
|
-
var killSchema =
|
|
9189
|
-
signal:
|
|
10055
|
+
var killSchema = z18.object({
|
|
10056
|
+
signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
9190
10057
|
});
|
|
9191
10058
|
terminals.post(
|
|
9192
10059
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -9200,8 +10067,8 @@ terminals.post(
|
|
|
9200
10067
|
return c.json({ success: true, message: "Terminal killed" });
|
|
9201
10068
|
}
|
|
9202
10069
|
);
|
|
9203
|
-
var writeSchema =
|
|
9204
|
-
input:
|
|
10070
|
+
var writeSchema = z18.object({
|
|
10071
|
+
input: z18.string()
|
|
9205
10072
|
});
|
|
9206
10073
|
terminals.post(
|
|
9207
10074
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -9386,18 +10253,18 @@ data: ${JSON.stringify({ status: "stopped" })}
|
|
|
9386
10253
|
init_db();
|
|
9387
10254
|
import { Hono as Hono5 } from "hono";
|
|
9388
10255
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
9389
|
-
import { z as
|
|
10256
|
+
import { z as z19 } from "zod";
|
|
9390
10257
|
init_config();
|
|
9391
10258
|
var tasks = new Hono5();
|
|
9392
10259
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
9393
|
-
var createTaskSchema =
|
|
9394
|
-
prompt:
|
|
9395
|
-
outputSchema:
|
|
9396
|
-
webhookUrl:
|
|
9397
|
-
model:
|
|
9398
|
-
workingDirectory:
|
|
9399
|
-
name:
|
|
9400
|
-
maxIterations:
|
|
10260
|
+
var createTaskSchema = z19.object({
|
|
10261
|
+
prompt: z19.string().min(1),
|
|
10262
|
+
outputSchema: z19.record(z19.string(), z19.unknown()),
|
|
10263
|
+
webhookUrl: z19.string().url().optional(),
|
|
10264
|
+
model: z19.string().optional(),
|
|
10265
|
+
workingDirectory: z19.string().optional(),
|
|
10266
|
+
name: z19.string().optional(),
|
|
10267
|
+
maxIterations: z19.number().int().min(1).max(500).optional()
|
|
9401
10268
|
});
|
|
9402
10269
|
tasks.post(
|
|
9403
10270
|
"/",
|
|
@@ -9476,6 +10343,15 @@ tasks.get("/:id", async (c) => {
|
|
|
9476
10343
|
if (!task?.enabled) {
|
|
9477
10344
|
return c.json({ error: "Session is not a task" }, 400);
|
|
9478
10345
|
}
|
|
10346
|
+
let browserRecordings = [];
|
|
10347
|
+
try {
|
|
10348
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10349
|
+
if (isRemoteConfigured2()) {
|
|
10350
|
+
const files = await storageQueries2.getSessionFiles(id);
|
|
10351
|
+
browserRecordings = files.filter((f) => f.category === "browser-recording").map((f) => ({ fileName: f.fileName, downloadUrl: f.downloadUrl, sizeBytes: f.sizeBytes }));
|
|
10352
|
+
}
|
|
10353
|
+
} catch {
|
|
10354
|
+
}
|
|
9479
10355
|
return c.json({
|
|
9480
10356
|
taskId: id,
|
|
9481
10357
|
status: task.status,
|
|
@@ -9485,7 +10361,8 @@ tasks.get("/:id", async (c) => {
|
|
|
9485
10361
|
model: session.model,
|
|
9486
10362
|
name: session.name,
|
|
9487
10363
|
createdAt: session.createdAt.toISOString(),
|
|
9488
|
-
updatedAt: session.updatedAt.toISOString()
|
|
10364
|
+
updatedAt: session.updatedAt.toISOString(),
|
|
10365
|
+
browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
|
|
9489
10366
|
});
|
|
9490
10367
|
});
|
|
9491
10368
|
tasks.post("/:id/cancel", async (c) => {
|
|
@@ -9533,10 +10410,10 @@ init_config();
|
|
|
9533
10410
|
init_db();
|
|
9534
10411
|
|
|
9535
10412
|
// src/utils/dependencies.ts
|
|
9536
|
-
import { exec as
|
|
9537
|
-
import { promisify as
|
|
10413
|
+
import { exec as exec6 } from "child_process";
|
|
10414
|
+
import { promisify as promisify6 } from "util";
|
|
9538
10415
|
import { platform as platform2 } from "os";
|
|
9539
|
-
var
|
|
10416
|
+
var execAsync6 = promisify6(exec6);
|
|
9540
10417
|
function getInstallInstructions() {
|
|
9541
10418
|
const os2 = platform2();
|
|
9542
10419
|
if (os2 === "darwin") {
|
|
@@ -9569,7 +10446,7 @@ Install tmux:
|
|
|
9569
10446
|
}
|
|
9570
10447
|
async function checkTmux() {
|
|
9571
10448
|
try {
|
|
9572
|
-
const { stdout } = await
|
|
10449
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
9573
10450
|
const version = stdout.trim();
|
|
9574
10451
|
return {
|
|
9575
10452
|
available: true,
|
|
@@ -9610,7 +10487,7 @@ async function checkDependencies(options = {}) {
|
|
|
9610
10487
|
}
|
|
9611
10488
|
async function checkAgentBrowser() {
|
|
9612
10489
|
try {
|
|
9613
|
-
const { stdout } = await
|
|
10490
|
+
const { stdout } = await execAsync6("agent-browser --version", { timeout: 1e4 });
|
|
9614
10491
|
const version = stdout.trim();
|
|
9615
10492
|
return { available: true, version };
|
|
9616
10493
|
} catch {
|
|
@@ -9626,12 +10503,12 @@ async function tryInstallAgentBrowser(options = {}) {
|
|
|
9626
10503
|
if (!options.quiet) {
|
|
9627
10504
|
console.log("\u{1F4E6} Installing agent-browser globally...");
|
|
9628
10505
|
}
|
|
9629
|
-
await
|
|
10506
|
+
await execAsync6("npm install -g agent-browser", { timeout: 12e4 });
|
|
9630
10507
|
try {
|
|
9631
10508
|
if (!options.quiet) {
|
|
9632
10509
|
console.log("\u{1F4E6} Installing Chromium for browser automation...");
|
|
9633
10510
|
}
|
|
9634
|
-
await
|
|
10511
|
+
await execAsync6("agent-browser install", { timeout: 12e4 });
|
|
9635
10512
|
} catch {
|
|
9636
10513
|
}
|
|
9637
10514
|
if (!options.quiet) {
|
|
@@ -9650,21 +10527,21 @@ async function tryAutoInstallTmux() {
|
|
|
9650
10527
|
try {
|
|
9651
10528
|
if (os2 === "darwin") {
|
|
9652
10529
|
try {
|
|
9653
|
-
await
|
|
10530
|
+
await execAsync6("which brew", { timeout: 5e3 });
|
|
9654
10531
|
} catch {
|
|
9655
10532
|
return false;
|
|
9656
10533
|
}
|
|
9657
10534
|
console.log("\u{1F4E6} Installing tmux via Homebrew...");
|
|
9658
|
-
await
|
|
10535
|
+
await execAsync6("brew install tmux", { timeout: 3e5 });
|
|
9659
10536
|
console.log("\u2705 tmux installed successfully");
|
|
9660
10537
|
return true;
|
|
9661
10538
|
}
|
|
9662
10539
|
if (os2 === "linux") {
|
|
9663
10540
|
try {
|
|
9664
|
-
await
|
|
10541
|
+
await execAsync6("which apt-get", { timeout: 5e3 });
|
|
9665
10542
|
console.log("\u{1F4E6} Installing tmux via apt-get...");
|
|
9666
10543
|
console.log(" (This may require sudo password)");
|
|
9667
|
-
await
|
|
10544
|
+
await execAsync6("sudo apt-get update && sudo apt-get install -y tmux", {
|
|
9668
10545
|
timeout: 3e5
|
|
9669
10546
|
});
|
|
9670
10547
|
console.log("\u2705 tmux installed successfully");
|
|
@@ -9672,9 +10549,9 @@ async function tryAutoInstallTmux() {
|
|
|
9672
10549
|
} catch {
|
|
9673
10550
|
}
|
|
9674
10551
|
try {
|
|
9675
|
-
await
|
|
10552
|
+
await execAsync6("which dnf", { timeout: 5e3 });
|
|
9676
10553
|
console.log("\u{1F4E6} Installing tmux via dnf...");
|
|
9677
|
-
await
|
|
10554
|
+
await execAsync6("sudo dnf install -y tmux", { timeout: 3e5 });
|
|
9678
10555
|
console.log("\u2705 tmux installed successfully");
|
|
9679
10556
|
return true;
|
|
9680
10557
|
} catch {
|
|
@@ -9714,11 +10591,11 @@ function getWebDirectory() {
|
|
|
9714
10591
|
try {
|
|
9715
10592
|
const currentDir = dirname7(fileURLToPath4(import.meta.url));
|
|
9716
10593
|
const webDir = resolve10(currentDir, "..", "web");
|
|
9717
|
-
if (existsSync15(webDir) && existsSync15(
|
|
10594
|
+
if (existsSync15(webDir) && existsSync15(join10(webDir, "package.json"))) {
|
|
9718
10595
|
return webDir;
|
|
9719
10596
|
}
|
|
9720
10597
|
const altWebDir = resolve10(currentDir, "..", "..", "web");
|
|
9721
|
-
if (existsSync15(altWebDir) && existsSync15(
|
|
10598
|
+
if (existsSync15(altWebDir) && existsSync15(join10(altWebDir, "package.json"))) {
|
|
9722
10599
|
return altWebDir;
|
|
9723
10600
|
}
|
|
9724
10601
|
return null;
|
|
@@ -9776,20 +10653,20 @@ async function findWebPort(preferredPort) {
|
|
|
9776
10653
|
return { port: preferredPort, alreadyRunning: false };
|
|
9777
10654
|
}
|
|
9778
10655
|
function hasProductionBuild(webDir) {
|
|
9779
|
-
const buildIdPath =
|
|
10656
|
+
const buildIdPath = join10(webDir, ".next", "BUILD_ID");
|
|
9780
10657
|
return existsSync15(buildIdPath);
|
|
9781
10658
|
}
|
|
9782
10659
|
function hasSourceFiles(webDir) {
|
|
9783
|
-
const appDir =
|
|
9784
|
-
const pagesDir =
|
|
9785
|
-
const rootAppDir =
|
|
9786
|
-
const rootPagesDir =
|
|
10660
|
+
const appDir = join10(webDir, "src", "app");
|
|
10661
|
+
const pagesDir = join10(webDir, "src", "pages");
|
|
10662
|
+
const rootAppDir = join10(webDir, "app");
|
|
10663
|
+
const rootPagesDir = join10(webDir, "pages");
|
|
9787
10664
|
return existsSync15(appDir) || existsSync15(pagesDir) || existsSync15(rootAppDir) || existsSync15(rootPagesDir);
|
|
9788
10665
|
}
|
|
9789
10666
|
function getStandaloneServerPath(webDir) {
|
|
9790
10667
|
const possiblePaths2 = [
|
|
9791
|
-
|
|
9792
|
-
|
|
10668
|
+
join10(webDir, ".next", "standalone", "server.js"),
|
|
10669
|
+
join10(webDir, ".next", "standalone", "web", "server.js")
|
|
9793
10670
|
];
|
|
9794
10671
|
for (const serverPath of possiblePaths2) {
|
|
9795
10672
|
if (existsSync15(serverPath)) {
|
|
@@ -9832,13 +10709,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
9832
10709
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
9833
10710
|
return { process: null, port: actualPort };
|
|
9834
10711
|
}
|
|
9835
|
-
const usePnpm = existsSync15(
|
|
9836
|
-
const useNpm = !usePnpm && existsSync15(
|
|
10712
|
+
const usePnpm = existsSync15(join10(webDir, "pnpm-lock.yaml"));
|
|
10713
|
+
const useNpm = !usePnpm && existsSync15(join10(webDir, "package-lock.json"));
|
|
9837
10714
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
9838
10715
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
9839
10716
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
9840
10717
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
9841
|
-
const runtimeConfigPath =
|
|
10718
|
+
const runtimeConfigPath = join10(webDir, "runtime-config.json");
|
|
9842
10719
|
try {
|
|
9843
10720
|
writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
9844
10721
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -10549,7 +11426,7 @@ init_config();
|
|
|
10549
11426
|
init_semantic();
|
|
10550
11427
|
init_db();
|
|
10551
11428
|
import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync16 } from "fs";
|
|
10552
|
-
import { resolve as resolve11, join as
|
|
11429
|
+
import { resolve as resolve11, join as join11 } from "path";
|
|
10553
11430
|
async function apiRequest(baseUrl, path, options = {}) {
|
|
10554
11431
|
const url = `${baseUrl}${path}`;
|
|
10555
11432
|
const init = {
|
|
@@ -10769,14 +11646,14 @@ async function runChat(options) {
|
|
|
10769
11646
|
} else {
|
|
10770
11647
|
spinner.succeed(`Web UI: ${chalk.cyan(webUrl)}`);
|
|
10771
11648
|
}
|
|
10772
|
-
const
|
|
11649
|
+
const cleanup2 = () => {
|
|
10773
11650
|
if (serverStartedByUs) {
|
|
10774
11651
|
stopServer();
|
|
10775
11652
|
}
|
|
10776
11653
|
process.exit(0);
|
|
10777
11654
|
};
|
|
10778
|
-
process.on("SIGINT",
|
|
10779
|
-
process.on("SIGTERM",
|
|
11655
|
+
process.on("SIGINT", cleanup2);
|
|
11656
|
+
process.on("SIGTERM", cleanup2);
|
|
10780
11657
|
} catch (err) {
|
|
10781
11658
|
spinner.fail(chalk.red(`Failed to start server: ${err.message}`));
|
|
10782
11659
|
process.exit(1);
|
|
@@ -11140,13 +12017,13 @@ program.command("server").description("Start the SparkECoder server (API + Web U
|
|
|
11140
12017
|
console.log(chalk.dim(` Swagger: http://${host}:${port}/swagger`));
|
|
11141
12018
|
console.log("");
|
|
11142
12019
|
console.log(chalk.dim("Press Ctrl+C to stop"));
|
|
11143
|
-
const
|
|
12020
|
+
const cleanup2 = () => {
|
|
11144
12021
|
console.log(chalk.dim("\nShutting down..."));
|
|
11145
12022
|
stopServer();
|
|
11146
12023
|
process.exit(0);
|
|
11147
12024
|
};
|
|
11148
|
-
process.on("SIGINT",
|
|
11149
|
-
process.on("SIGTERM",
|
|
12025
|
+
process.on("SIGINT", cleanup2);
|
|
12026
|
+
process.on("SIGTERM", cleanup2);
|
|
11150
12027
|
} catch (error) {
|
|
11151
12028
|
spinner.fail(chalk.red(`Failed to start server: ${error.message}`));
|
|
11152
12029
|
process.exit(1);
|
|
@@ -11251,7 +12128,7 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
11251
12128
|
let configLocation;
|
|
11252
12129
|
if (options.global) {
|
|
11253
12130
|
const appDataDir = ensureAppDataDirectory();
|
|
11254
|
-
configPath =
|
|
12131
|
+
configPath = join11(appDataDir, "sparkecoder.config.json");
|
|
11255
12132
|
configLocation = "global";
|
|
11256
12133
|
} else {
|
|
11257
12134
|
configPath = resolve11(process.cwd(), "sparkecoder.config.json");
|