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/agent/index.js
CHANGED
|
@@ -194,6 +194,36 @@ var init_config = __esm({
|
|
|
194
194
|
});
|
|
195
195
|
|
|
196
196
|
// src/db/remote.ts
|
|
197
|
+
var remote_exports = {};
|
|
198
|
+
__export(remote_exports, {
|
|
199
|
+
closeRemoteDatabase: () => closeRemoteDatabase,
|
|
200
|
+
initRemoteDatabase: () => initRemoteDatabase,
|
|
201
|
+
isRemoteConfigured: () => isRemoteConfigured,
|
|
202
|
+
remoteActiveStreamQueries: () => remoteActiveStreamQueries,
|
|
203
|
+
remoteCheckpointQueries: () => remoteCheckpointQueries,
|
|
204
|
+
remoteFileBackupQueries: () => remoteFileBackupQueries,
|
|
205
|
+
remoteIndexStatusQueries: () => remoteIndexStatusQueries,
|
|
206
|
+
remoteIndexedChunkQueries: () => remoteIndexedChunkQueries,
|
|
207
|
+
remoteMessageQueries: () => remoteMessageQueries,
|
|
208
|
+
remoteSessionQueries: () => remoteSessionQueries,
|
|
209
|
+
remoteSkillQueries: () => remoteSkillQueries,
|
|
210
|
+
remoteSubagentQueries: () => remoteSubagentQueries,
|
|
211
|
+
remoteTerminalQueries: () => remoteTerminalQueries,
|
|
212
|
+
remoteTodoQueries: () => remoteTodoQueries,
|
|
213
|
+
remoteToolExecutionQueries: () => remoteToolExecutionQueries,
|
|
214
|
+
storageQueries: () => storageQueries
|
|
215
|
+
});
|
|
216
|
+
function initRemoteDatabase(serverUrl, key) {
|
|
217
|
+
remoteServerUrl = serverUrl.replace(/\/$/, "");
|
|
218
|
+
authKey = key;
|
|
219
|
+
}
|
|
220
|
+
function closeRemoteDatabase() {
|
|
221
|
+
remoteServerUrl = null;
|
|
222
|
+
authKey = null;
|
|
223
|
+
}
|
|
224
|
+
function isRemoteConfigured() {
|
|
225
|
+
return !!remoteServerUrl && !!authKey;
|
|
226
|
+
}
|
|
197
227
|
function parseDates(obj) {
|
|
198
228
|
if (obj === null || obj === void 0) return obj;
|
|
199
229
|
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
@@ -241,7 +271,29 @@ async function api(path, options = {}) {
|
|
|
241
271
|
}
|
|
242
272
|
return parseDates(parsed);
|
|
243
273
|
}
|
|
244
|
-
|
|
274
|
+
async function storageApi(path, options = {}) {
|
|
275
|
+
if (!remoteServerUrl || !authKey) {
|
|
276
|
+
throw new Error("Remote database not initialized");
|
|
277
|
+
}
|
|
278
|
+
const url = `${remoteServerUrl}/storage${path}`;
|
|
279
|
+
const init = {
|
|
280
|
+
method: options.method || "GET",
|
|
281
|
+
headers: {
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
"Authorization": `Bearer ${authKey}`
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
if (options.body) {
|
|
287
|
+
init.body = JSON.stringify(options.body);
|
|
288
|
+
}
|
|
289
|
+
const response = await fetch(url, init);
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
292
|
+
throw new Error(`Storage API error ${response.status}: ${errorText}`);
|
|
293
|
+
}
|
|
294
|
+
return response.json();
|
|
295
|
+
}
|
|
296
|
+
var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteTerminalQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries, storageQueries;
|
|
245
297
|
var init_remote = __esm({
|
|
246
298
|
"src/db/remote.ts"() {
|
|
247
299
|
"use strict";
|
|
@@ -380,6 +432,83 @@ var init_remote = __esm({
|
|
|
380
432
|
return result.isLoaded;
|
|
381
433
|
}
|
|
382
434
|
};
|
|
435
|
+
remoteTerminalQueries = {
|
|
436
|
+
create(data) {
|
|
437
|
+
return api("/terminals", { method: "POST", body: data });
|
|
438
|
+
},
|
|
439
|
+
getById(id) {
|
|
440
|
+
return api(`/terminals/${id}`).catch(() => void 0);
|
|
441
|
+
},
|
|
442
|
+
getBySession(sessionId) {
|
|
443
|
+
return api(`/terminals/session/${sessionId}`);
|
|
444
|
+
},
|
|
445
|
+
getRunning(sessionId) {
|
|
446
|
+
return api(`/terminals/session/${sessionId}/running`);
|
|
447
|
+
},
|
|
448
|
+
updateStatus(id, status, exitCode, error) {
|
|
449
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { status, exitCode, error } });
|
|
450
|
+
},
|
|
451
|
+
updatePid(id, pid) {
|
|
452
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { pid } });
|
|
453
|
+
},
|
|
454
|
+
async delete(id) {
|
|
455
|
+
const result = await api(`/terminals/${id}`, { method: "DELETE" });
|
|
456
|
+
return result?.success ?? false;
|
|
457
|
+
},
|
|
458
|
+
async deleteBySession(sessionId) {
|
|
459
|
+
const result = await api(`/terminals/session/${sessionId}`, { method: "DELETE" });
|
|
460
|
+
return result.deleted;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
remoteActiveStreamQueries = {
|
|
464
|
+
create(sessionId, streamId) {
|
|
465
|
+
return api("/streams", { method: "POST", body: { sessionId, streamId } });
|
|
466
|
+
},
|
|
467
|
+
getBySessionId(sessionId) {
|
|
468
|
+
return api(`/streams/session/${sessionId}`).then((r) => r ?? void 0);
|
|
469
|
+
},
|
|
470
|
+
getByStreamId(streamId) {
|
|
471
|
+
return api(`/streams/by-stream-id/${streamId}`).catch(() => void 0);
|
|
472
|
+
},
|
|
473
|
+
finish(streamId) {
|
|
474
|
+
return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "finished" } });
|
|
475
|
+
},
|
|
476
|
+
markError(streamId) {
|
|
477
|
+
return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "error" } });
|
|
478
|
+
},
|
|
479
|
+
async deleteBySession(sessionId) {
|
|
480
|
+
const result = await api(`/streams/session/${sessionId}`, { method: "DELETE" });
|
|
481
|
+
return result.deleted;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
remoteCheckpointQueries = {
|
|
485
|
+
create(data) {
|
|
486
|
+
return api("/checkpoints", { method: "POST", body: data });
|
|
487
|
+
},
|
|
488
|
+
getById(id) {
|
|
489
|
+
return api(`/checkpoints/${id}`).catch(() => void 0);
|
|
490
|
+
},
|
|
491
|
+
getBySession(sessionId) {
|
|
492
|
+
return api(`/checkpoints/session/${sessionId}`);
|
|
493
|
+
},
|
|
494
|
+
getByMessageSequence(sessionId, messageSequence) {
|
|
495
|
+
return api(`/checkpoints/session/${sessionId}/by-sequence/${messageSequence}`).then((r) => r ?? void 0);
|
|
496
|
+
},
|
|
497
|
+
getLatest(sessionId) {
|
|
498
|
+
return api(`/checkpoints/session/${sessionId}/latest`).then((r) => r ?? void 0);
|
|
499
|
+
},
|
|
500
|
+
async deleteAfterSequence(sessionId, messageSequence) {
|
|
501
|
+
const result = await api(
|
|
502
|
+
`/checkpoints/session/${sessionId}/after-sequence/${messageSequence}`,
|
|
503
|
+
{ method: "DELETE" }
|
|
504
|
+
);
|
|
505
|
+
return result.deleted;
|
|
506
|
+
},
|
|
507
|
+
async deleteBySession(sessionId) {
|
|
508
|
+
const result = await api(`/checkpoints/session/${sessionId}`, { method: "DELETE" });
|
|
509
|
+
return result.deleted;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
383
512
|
remoteFileBackupQueries = {
|
|
384
513
|
create(data) {
|
|
385
514
|
return api("/file-backups", { method: "POST", body: data });
|
|
@@ -434,6 +563,41 @@ var init_remote = __esm({
|
|
|
434
563
|
return result.deleted;
|
|
435
564
|
}
|
|
436
565
|
};
|
|
566
|
+
remoteIndexedChunkQueries = {
|
|
567
|
+
upsert(_db, data) {
|
|
568
|
+
return api("/indexed-chunks", { method: "POST", body: data });
|
|
569
|
+
},
|
|
570
|
+
batchUpsert(_db, chunks) {
|
|
571
|
+
return api("/indexed-chunks/batch", {
|
|
572
|
+
method: "POST",
|
|
573
|
+
body: { chunks }
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
getById(_db, id) {
|
|
577
|
+
return api(`/indexed-chunks/${id}`).catch(() => void 0);
|
|
578
|
+
},
|
|
579
|
+
getByNamespace(_db, namespace) {
|
|
580
|
+
return api(`/indexed-chunks/namespace/${namespace}`);
|
|
581
|
+
},
|
|
582
|
+
getByFilePath(_db, namespace, filePath) {
|
|
583
|
+
return api(`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`);
|
|
584
|
+
},
|
|
585
|
+
async deleteByNamespace(_db, namespace) {
|
|
586
|
+
const result = await api(`/indexed-chunks/namespace/${namespace}`, { method: "DELETE" });
|
|
587
|
+
return result.deleted;
|
|
588
|
+
},
|
|
589
|
+
async deleteByFilePath(_db, namespace, filePath) {
|
|
590
|
+
const result = await api(
|
|
591
|
+
`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`,
|
|
592
|
+
{ method: "DELETE" }
|
|
593
|
+
);
|
|
594
|
+
return result.deleted;
|
|
595
|
+
},
|
|
596
|
+
async countByNamespace(_db, namespace) {
|
|
597
|
+
const result = await api(`/indexed-chunks/namespace/${namespace}/count`);
|
|
598
|
+
return result.count;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
437
601
|
remoteIndexStatusQueries = {
|
|
438
602
|
upsert(_db, data) {
|
|
439
603
|
return api("/index-status", {
|
|
@@ -456,6 +620,27 @@ var init_remote = __esm({
|
|
|
456
620
|
return api("/index-status");
|
|
457
621
|
}
|
|
458
622
|
};
|
|
623
|
+
storageQueries = {
|
|
624
|
+
async getUploadUrl(sessionId, fileName, contentType, category) {
|
|
625
|
+
return storageApi("/upload-url", {
|
|
626
|
+
method: "POST",
|
|
627
|
+
body: { sessionId, fileName, contentType, category }
|
|
628
|
+
});
|
|
629
|
+
},
|
|
630
|
+
async getSessionFiles(sessionId) {
|
|
631
|
+
const result = await storageApi(`/files/${sessionId}`);
|
|
632
|
+
return result.files;
|
|
633
|
+
},
|
|
634
|
+
async getDownloadUrl(fileId) {
|
|
635
|
+
return storageApi(`/download/${fileId}`);
|
|
636
|
+
},
|
|
637
|
+
async deleteFile(fileId) {
|
|
638
|
+
await storageApi(`/files/${fileId}`, { method: "DELETE" });
|
|
639
|
+
},
|
|
640
|
+
async updateFile(fileId, data) {
|
|
641
|
+
await storageApi(`/files/${fileId}`, { method: "PATCH", body: data });
|
|
642
|
+
}
|
|
643
|
+
};
|
|
459
644
|
}
|
|
460
645
|
});
|
|
461
646
|
|
|
@@ -1220,11 +1405,285 @@ var init_semantic_search = __esm({
|
|
|
1220
1405
|
}
|
|
1221
1406
|
});
|
|
1222
1407
|
|
|
1408
|
+
// src/browser/stream-proxy.ts
|
|
1409
|
+
var stream_proxy_exports = {};
|
|
1410
|
+
__export(stream_proxy_exports, {
|
|
1411
|
+
BrowserStreamProxy: () => BrowserStreamProxy,
|
|
1412
|
+
destroyProxy: () => destroyProxy,
|
|
1413
|
+
getOrCreateProxy: () => getOrCreateProxy,
|
|
1414
|
+
getProxy: () => getProxy
|
|
1415
|
+
});
|
|
1416
|
+
import WebSocket from "ws";
|
|
1417
|
+
import { EventEmitter } from "events";
|
|
1418
|
+
function getOrCreateProxy(sessionId, port) {
|
|
1419
|
+
const existing = activeProxies.get(sessionId);
|
|
1420
|
+
if (existing) return existing;
|
|
1421
|
+
const proxy = new BrowserStreamProxy(port);
|
|
1422
|
+
activeProxies.set(sessionId, proxy);
|
|
1423
|
+
proxy.on("close", () => {
|
|
1424
|
+
activeProxies.delete(sessionId);
|
|
1425
|
+
});
|
|
1426
|
+
proxy.connect();
|
|
1427
|
+
return proxy;
|
|
1428
|
+
}
|
|
1429
|
+
function getProxy(sessionId) {
|
|
1430
|
+
return activeProxies.get(sessionId);
|
|
1431
|
+
}
|
|
1432
|
+
function destroyProxy(sessionId) {
|
|
1433
|
+
const proxy = activeProxies.get(sessionId);
|
|
1434
|
+
if (proxy) {
|
|
1435
|
+
proxy.destroy();
|
|
1436
|
+
activeProxies.delete(sessionId);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
|
|
1440
|
+
var init_stream_proxy = __esm({
|
|
1441
|
+
"src/browser/stream-proxy.ts"() {
|
|
1442
|
+
"use strict";
|
|
1443
|
+
RECONNECT_DELAY_MS = 1e3;
|
|
1444
|
+
MAX_RECONNECT_ATTEMPTS = 20;
|
|
1445
|
+
FRAME_THROTTLE_MS = 100;
|
|
1446
|
+
BrowserStreamProxy = class extends EventEmitter {
|
|
1447
|
+
ws = null;
|
|
1448
|
+
port;
|
|
1449
|
+
reconnectAttempts = 0;
|
|
1450
|
+
reconnectTimer = null;
|
|
1451
|
+
destroyed = false;
|
|
1452
|
+
lastFrameTime = 0;
|
|
1453
|
+
_latestFrame = null;
|
|
1454
|
+
_connected = false;
|
|
1455
|
+
constructor(port) {
|
|
1456
|
+
super();
|
|
1457
|
+
this.port = port;
|
|
1458
|
+
}
|
|
1459
|
+
get connected() {
|
|
1460
|
+
return this._connected;
|
|
1461
|
+
}
|
|
1462
|
+
get latestFrame() {
|
|
1463
|
+
return this._latestFrame;
|
|
1464
|
+
}
|
|
1465
|
+
connect() {
|
|
1466
|
+
if (this.destroyed) return;
|
|
1467
|
+
this.doConnect();
|
|
1468
|
+
}
|
|
1469
|
+
doConnect() {
|
|
1470
|
+
if (this.destroyed) return;
|
|
1471
|
+
const url = `ws://localhost:${this.port}`;
|
|
1472
|
+
try {
|
|
1473
|
+
this.ws = new WebSocket(url);
|
|
1474
|
+
} catch {
|
|
1475
|
+
this.scheduleReconnect();
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
this.ws.on("open", () => {
|
|
1479
|
+
this.reconnectAttempts = 0;
|
|
1480
|
+
this._connected = true;
|
|
1481
|
+
this.emit("status", {
|
|
1482
|
+
connected: true,
|
|
1483
|
+
screencasting: true
|
|
1484
|
+
});
|
|
1485
|
+
});
|
|
1486
|
+
this.ws.on("message", (raw) => {
|
|
1487
|
+
try {
|
|
1488
|
+
const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
1489
|
+
this.handleMessage(msg);
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
this.ws.on("close", () => {
|
|
1494
|
+
const wasConnected = this._connected;
|
|
1495
|
+
this._connected = false;
|
|
1496
|
+
if (wasConnected) {
|
|
1497
|
+
this.emit("status", { connected: false, screencasting: false });
|
|
1498
|
+
}
|
|
1499
|
+
if (!this.destroyed) {
|
|
1500
|
+
this.scheduleReconnect();
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
this.ws.on("error", () => {
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
handleMessage(msg) {
|
|
1507
|
+
if (msg.type === "frame") {
|
|
1508
|
+
const now = Date.now();
|
|
1509
|
+
if (now - this.lastFrameTime < FRAME_THROTTLE_MS) return;
|
|
1510
|
+
this.lastFrameTime = now;
|
|
1511
|
+
const frame = {
|
|
1512
|
+
data: msg.data,
|
|
1513
|
+
metadata: msg.metadata ?? {
|
|
1514
|
+
deviceWidth: 1280,
|
|
1515
|
+
deviceHeight: 720,
|
|
1516
|
+
pageScaleFactor: 1,
|
|
1517
|
+
offsetTop: 0,
|
|
1518
|
+
scrollOffsetX: 0,
|
|
1519
|
+
scrollOffsetY: 0
|
|
1520
|
+
},
|
|
1521
|
+
timestamp: now
|
|
1522
|
+
};
|
|
1523
|
+
this._latestFrame = frame;
|
|
1524
|
+
this.emit("frame", frame);
|
|
1525
|
+
} else if (msg.type === "status") {
|
|
1526
|
+
this.emit("status", {
|
|
1527
|
+
connected: msg.connected ?? true,
|
|
1528
|
+
screencasting: msg.screencasting ?? true,
|
|
1529
|
+
viewportWidth: msg.viewportWidth,
|
|
1530
|
+
viewportHeight: msg.viewportHeight
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
scheduleReconnect() {
|
|
1535
|
+
if (this.destroyed || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
1536
|
+
this.emit("close");
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
this.reconnectAttempts++;
|
|
1540
|
+
const delay = this.reconnectAttempts <= 5 ? RECONNECT_DELAY_MS : RECONNECT_DELAY_MS * (this.reconnectAttempts - 4);
|
|
1541
|
+
this.reconnectTimer = setTimeout(() => this.doConnect(), delay);
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Send an input event to the browser for pair-browsing.
|
|
1545
|
+
*/
|
|
1546
|
+
injectInput(event) {
|
|
1547
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1548
|
+
this.ws.send(JSON.stringify(event));
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
destroy() {
|
|
1552
|
+
this.destroyed = true;
|
|
1553
|
+
if (this.reconnectTimer) {
|
|
1554
|
+
clearTimeout(this.reconnectTimer);
|
|
1555
|
+
this.reconnectTimer = null;
|
|
1556
|
+
}
|
|
1557
|
+
if (this.ws) {
|
|
1558
|
+
this.ws.removeAllListeners();
|
|
1559
|
+
this.ws.close();
|
|
1560
|
+
this.ws = null;
|
|
1561
|
+
}
|
|
1562
|
+
this._connected = false;
|
|
1563
|
+
this.removeAllListeners();
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
activeProxies = /* @__PURE__ */ new Map();
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
// src/browser/recorder.ts
|
|
1571
|
+
var recorder_exports = {};
|
|
1572
|
+
__export(recorder_exports, {
|
|
1573
|
+
FrameRecorder: () => FrameRecorder
|
|
1574
|
+
});
|
|
1575
|
+
import { exec as exec5 } from "child_process";
|
|
1576
|
+
import { promisify as promisify5 } from "util";
|
|
1577
|
+
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1578
|
+
import { join as join6 } from "path";
|
|
1579
|
+
import { tmpdir } from "os";
|
|
1580
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
1581
|
+
async function checkFfmpeg() {
|
|
1582
|
+
try {
|
|
1583
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
1584
|
+
return true;
|
|
1585
|
+
} catch {
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
async function cleanup(dir) {
|
|
1590
|
+
try {
|
|
1591
|
+
await rm(dir, { recursive: true, force: true });
|
|
1592
|
+
} catch {
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
var execAsync5, FrameRecorder;
|
|
1596
|
+
var init_recorder = __esm({
|
|
1597
|
+
"src/browser/recorder.ts"() {
|
|
1598
|
+
"use strict";
|
|
1599
|
+
execAsync5 = promisify5(exec5);
|
|
1600
|
+
FrameRecorder = class {
|
|
1601
|
+
frames = [];
|
|
1602
|
+
startTime = null;
|
|
1603
|
+
recording = false;
|
|
1604
|
+
sessionId;
|
|
1605
|
+
constructor(sessionId) {
|
|
1606
|
+
this.sessionId = sessionId;
|
|
1607
|
+
}
|
|
1608
|
+
get isRecording() {
|
|
1609
|
+
return this.recording;
|
|
1610
|
+
}
|
|
1611
|
+
get frameCount() {
|
|
1612
|
+
return this.frames.length;
|
|
1613
|
+
}
|
|
1614
|
+
start() {
|
|
1615
|
+
this.frames = [];
|
|
1616
|
+
this.startTime = Date.now();
|
|
1617
|
+
this.recording = true;
|
|
1618
|
+
}
|
|
1619
|
+
addFrame(frame) {
|
|
1620
|
+
if (!this.recording) return;
|
|
1621
|
+
this.frames.push({
|
|
1622
|
+
data: Buffer.from(frame.data, "base64"),
|
|
1623
|
+
timestamp: frame.timestamp
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
stop() {
|
|
1627
|
+
this.recording = false;
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Encode recorded frames into an MP4 using ffmpeg.
|
|
1631
|
+
* Returns the file path to the generated MP4, or null if encoding fails.
|
|
1632
|
+
*/
|
|
1633
|
+
async encode() {
|
|
1634
|
+
if (this.frames.length === 0) return null;
|
|
1635
|
+
const workDir = join6(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
|
|
1636
|
+
await mkdir4(workDir, { recursive: true });
|
|
1637
|
+
try {
|
|
1638
|
+
for (let i = 0; i < this.frames.length; i++) {
|
|
1639
|
+
const framePath = join6(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
1640
|
+
await writeFile4(framePath, this.frames[i].data);
|
|
1641
|
+
}
|
|
1642
|
+
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
1643
|
+
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
1644
|
+
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
1645
|
+
const outputPath = join6(workDir, `recording_${this.sessionId}.mp4`);
|
|
1646
|
+
const hasFfmpeg = await checkFfmpeg();
|
|
1647
|
+
if (hasFfmpeg) {
|
|
1648
|
+
await execAsync5(
|
|
1649
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join6(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
1650
|
+
{ timeout: 12e4 }
|
|
1651
|
+
);
|
|
1652
|
+
} else {
|
|
1653
|
+
console.warn("[RECORDER] ffmpeg not available, cannot encode recording");
|
|
1654
|
+
await cleanup(workDir);
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
const outputBuf = await readFile10(outputPath);
|
|
1658
|
+
const files = await readdir5(workDir);
|
|
1659
|
+
for (const f of files) {
|
|
1660
|
+
if (f.startsWith("frame_")) {
|
|
1661
|
+
await unlink2(join6(workDir, f)).catch(() => {
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return { path: outputPath, sizeBytes: outputBuf.length };
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
console.error("[RECORDER] Failed to encode recording:", error);
|
|
1668
|
+
await cleanup(workDir);
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
/** Discard all frames and free memory */
|
|
1673
|
+
clear() {
|
|
1674
|
+
this.frames = [];
|
|
1675
|
+
this.startTime = null;
|
|
1676
|
+
this.recording = false;
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1223
1682
|
// src/agent/index.ts
|
|
1224
1683
|
import {
|
|
1225
1684
|
streamText as streamText2,
|
|
1226
1685
|
generateText as generateText3,
|
|
1227
|
-
tool as
|
|
1686
|
+
tool as tool13,
|
|
1228
1687
|
stepCountIs as stepCountIs2
|
|
1229
1688
|
} from "ai";
|
|
1230
1689
|
|
|
@@ -1405,8 +1864,8 @@ var SUBAGENT_MODELS = {
|
|
|
1405
1864
|
// src/agent/index.ts
|
|
1406
1865
|
init_db();
|
|
1407
1866
|
init_config();
|
|
1408
|
-
import { z as
|
|
1409
|
-
import { nanoid as
|
|
1867
|
+
import { z as z14 } from "zod";
|
|
1868
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1410
1869
|
|
|
1411
1870
|
// src/tools/bash.ts
|
|
1412
1871
|
import { tool } from "ai";
|
|
@@ -1707,6 +2166,29 @@ function isBlockedCommand(command) {
|
|
|
1707
2166
|
(blocked) => normalizedCommand.includes(blocked.toLowerCase())
|
|
1708
2167
|
);
|
|
1709
2168
|
}
|
|
2169
|
+
var BROWSER_STREAM_BASE_PORT = 9223;
|
|
2170
|
+
var sessionBrowserPorts = /* @__PURE__ */ new Map();
|
|
2171
|
+
var nextPortOffset = 0;
|
|
2172
|
+
function getBrowserStreamPort(sessionId) {
|
|
2173
|
+
let port = sessionBrowserPorts.get(sessionId);
|
|
2174
|
+
if (!port) {
|
|
2175
|
+
port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
|
|
2176
|
+
sessionBrowserPorts.set(sessionId, port);
|
|
2177
|
+
}
|
|
2178
|
+
return port;
|
|
2179
|
+
}
|
|
2180
|
+
function hasAgentBrowserCommand(command) {
|
|
2181
|
+
return /\bagent-browser\b/.test(command);
|
|
2182
|
+
}
|
|
2183
|
+
function isAgentBrowserCloseCommand(command) {
|
|
2184
|
+
return /\bagent-browser\s+(close|close\s+--all)\b/.test(command);
|
|
2185
|
+
}
|
|
2186
|
+
function injectBrowserStreamPort(command, port) {
|
|
2187
|
+
return command.replace(
|
|
2188
|
+
/\bagent-browser\b/g,
|
|
2189
|
+
`AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
1710
2192
|
var bashInputSchema = z2.object({
|
|
1711
2193
|
command: z2.string().optional().describe("The command to execute. Required for running new commands."),
|
|
1712
2194
|
background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
|
|
@@ -1872,6 +2354,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
1872
2354
|
exitCode: 1
|
|
1873
2355
|
};
|
|
1874
2356
|
}
|
|
2357
|
+
let actualCommand = command;
|
|
2358
|
+
const hasAgentBrowser = hasAgentBrowserCommand(command);
|
|
2359
|
+
const browserClose = isAgentBrowserCloseCommand(command);
|
|
2360
|
+
let browserPort;
|
|
2361
|
+
if (hasAgentBrowser) {
|
|
2362
|
+
browserPort = getBrowserStreamPort(options.sessionId);
|
|
2363
|
+
if (!browserClose) {
|
|
2364
|
+
actualCommand = injectBrowserStreamPort(command, browserPort);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
1875
2367
|
const canUseTmux = await shouldUseTmux();
|
|
1876
2368
|
if (background) {
|
|
1877
2369
|
if (!canUseTmux) {
|
|
@@ -1881,8 +2373,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
1881
2373
|
};
|
|
1882
2374
|
}
|
|
1883
2375
|
const terminalId = generateTerminalId();
|
|
1884
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
1885
|
-
const result = await runBackground(
|
|
2376
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
2377
|
+
const result = await runBackground(actualCommand, options.workingDirectory, {
|
|
1886
2378
|
sessionId: options.sessionId,
|
|
1887
2379
|
terminalId
|
|
1888
2380
|
});
|
|
@@ -1895,16 +2387,22 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
1895
2387
|
}
|
|
1896
2388
|
if (canUseTmux) {
|
|
1897
2389
|
const terminalId = generateTerminalId();
|
|
1898
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
2390
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
1899
2391
|
try {
|
|
1900
|
-
const result = await runSync(
|
|
2392
|
+
const result = await runSync(actualCommand, options.workingDirectory, {
|
|
1901
2393
|
sessionId: options.sessionId,
|
|
1902
2394
|
timeout: COMMAND_TIMEOUT,
|
|
1903
2395
|
terminalId
|
|
1904
2396
|
});
|
|
1905
2397
|
const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
|
|
1906
2398
|
options.onOutput?.(truncatedOutput);
|
|
1907
|
-
options.onProgress?.({
|
|
2399
|
+
options.onProgress?.({
|
|
2400
|
+
terminalId,
|
|
2401
|
+
status: "completed",
|
|
2402
|
+
command,
|
|
2403
|
+
browserStreamPort: browserPort,
|
|
2404
|
+
browserClosed: browserClose || void 0
|
|
2405
|
+
});
|
|
1908
2406
|
return {
|
|
1909
2407
|
success: result.exitCode === 0,
|
|
1910
2408
|
id: result.id,
|
|
@@ -1922,7 +2420,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
1922
2420
|
};
|
|
1923
2421
|
}
|
|
1924
2422
|
} else {
|
|
1925
|
-
const result = await execFallback(
|
|
2423
|
+
const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
|
|
1926
2424
|
return {
|
|
1927
2425
|
success: result.success,
|
|
1928
2426
|
output: result.output,
|
|
@@ -2161,12 +2659,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2161
2659
|
}
|
|
2162
2660
|
async function commandExists(cmd) {
|
|
2163
2661
|
try {
|
|
2164
|
-
const { exec:
|
|
2165
|
-
const { promisify:
|
|
2166
|
-
const
|
|
2662
|
+
const { exec: exec6 } = await import("child_process");
|
|
2663
|
+
const { promisify: promisify6 } = await import("util");
|
|
2664
|
+
const execAsync6 = promisify6(exec6);
|
|
2167
2665
|
const isWindows = process.platform === "win32";
|
|
2168
2666
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2169
|
-
await
|
|
2667
|
+
await execAsync6(checkCmd);
|
|
2170
2668
|
return true;
|
|
2171
2669
|
} catch {
|
|
2172
2670
|
return false;
|
|
@@ -2464,7 +2962,7 @@ async function createClient(serverId, handle, root) {
|
|
|
2464
2962
|
const startTime = Date.now();
|
|
2465
2963
|
let debounceTimer;
|
|
2466
2964
|
let resolved = false;
|
|
2467
|
-
const
|
|
2965
|
+
const cleanup2 = () => {
|
|
2468
2966
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
2469
2967
|
const listeners = diagnosticListeners.get(normalized);
|
|
2470
2968
|
if (listeners) {
|
|
@@ -2478,7 +2976,7 @@ async function createClient(serverId, handle, root) {
|
|
|
2478
2976
|
const finish = () => {
|
|
2479
2977
|
if (resolved) return;
|
|
2480
2978
|
resolved = true;
|
|
2481
|
-
|
|
2979
|
+
cleanup2();
|
|
2482
2980
|
resolve10(diagnostics.get(normalized) || []);
|
|
2483
2981
|
};
|
|
2484
2982
|
const onDiagnostic = () => {
|
|
@@ -4583,8 +5081,106 @@ function createTaskFailedTool(options) {
|
|
|
4583
5081
|
});
|
|
4584
5082
|
}
|
|
4585
5083
|
|
|
5084
|
+
// src/tools/upload-file.ts
|
|
5085
|
+
import { tool as tool12 } from "ai";
|
|
5086
|
+
import { z as z13 } from "zod";
|
|
5087
|
+
import { readFile as readFile9, stat as stat4 } from "fs/promises";
|
|
5088
|
+
import { join as join5, basename as basename4, extname as extname7 } from "path";
|
|
5089
|
+
var MIME_TYPES = {
|
|
5090
|
+
".txt": "text/plain",
|
|
5091
|
+
".md": "text/markdown",
|
|
5092
|
+
".html": "text/html",
|
|
5093
|
+
".css": "text/css",
|
|
5094
|
+
".js": "application/javascript",
|
|
5095
|
+
".ts": "application/typescript",
|
|
5096
|
+
".json": "application/json",
|
|
5097
|
+
".csv": "text/csv",
|
|
5098
|
+
".xml": "application/xml",
|
|
5099
|
+
".pdf": "application/pdf",
|
|
5100
|
+
".png": "image/png",
|
|
5101
|
+
".jpg": "image/jpeg",
|
|
5102
|
+
".jpeg": "image/jpeg",
|
|
5103
|
+
".gif": "image/gif",
|
|
5104
|
+
".webp": "image/webp",
|
|
5105
|
+
".svg": "image/svg+xml",
|
|
5106
|
+
".mp4": "video/mp4",
|
|
5107
|
+
".webm": "video/webm",
|
|
5108
|
+
".mp3": "audio/mpeg",
|
|
5109
|
+
".wav": "audio/wav",
|
|
5110
|
+
".zip": "application/zip",
|
|
5111
|
+
".tar": "application/x-tar",
|
|
5112
|
+
".gz": "application/gzip"
|
|
5113
|
+
};
|
|
5114
|
+
function createUploadFileTool(options) {
|
|
5115
|
+
return tool12({
|
|
5116
|
+
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.`,
|
|
5117
|
+
inputSchema: z13.object({
|
|
5118
|
+
path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
|
|
5119
|
+
name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
|
|
5120
|
+
}),
|
|
5121
|
+
execute: async (input) => {
|
|
5122
|
+
try {
|
|
5123
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
5124
|
+
if (!isRemoteConfigured2()) {
|
|
5125
|
+
return {
|
|
5126
|
+
success: false,
|
|
5127
|
+
error: "File upload is not available \u2014 remote server with GCS is not configured."
|
|
5128
|
+
};
|
|
5129
|
+
}
|
|
5130
|
+
const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
|
|
5131
|
+
try {
|
|
5132
|
+
await stat4(fullPath);
|
|
5133
|
+
} catch {
|
|
5134
|
+
return {
|
|
5135
|
+
success: false,
|
|
5136
|
+
error: `File not found: ${input.path}`
|
|
5137
|
+
};
|
|
5138
|
+
}
|
|
5139
|
+
const fileName = input.name || basename4(fullPath);
|
|
5140
|
+
const ext = extname7(fullPath).toLowerCase();
|
|
5141
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
5142
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
5143
|
+
options.sessionId,
|
|
5144
|
+
fileName,
|
|
5145
|
+
contentType,
|
|
5146
|
+
"general"
|
|
5147
|
+
);
|
|
5148
|
+
const fileData = await readFile9(fullPath);
|
|
5149
|
+
const putRes = await fetch(uploadInfo.uploadUrl, {
|
|
5150
|
+
method: "PUT",
|
|
5151
|
+
headers: { "Content-Type": contentType },
|
|
5152
|
+
body: fileData
|
|
5153
|
+
});
|
|
5154
|
+
if (!putRes.ok) {
|
|
5155
|
+
return {
|
|
5156
|
+
success: false,
|
|
5157
|
+
error: `Upload failed: ${putRes.status} ${putRes.statusText}`
|
|
5158
|
+
};
|
|
5159
|
+
}
|
|
5160
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
5161
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
5162
|
+
return {
|
|
5163
|
+
success: true,
|
|
5164
|
+
fileId: uploadInfo.fileId,
|
|
5165
|
+
fileName,
|
|
5166
|
+
sizeBytes: fileData.length,
|
|
5167
|
+
contentType,
|
|
5168
|
+
downloadUrl: downloadInfo.downloadUrl,
|
|
5169
|
+
expiresAt: downloadInfo.expiresAt
|
|
5170
|
+
};
|
|
5171
|
+
} catch (err) {
|
|
5172
|
+
return {
|
|
5173
|
+
success: false,
|
|
5174
|
+
error: `Upload failed: ${err.message}`
|
|
5175
|
+
};
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
});
|
|
5179
|
+
}
|
|
5180
|
+
|
|
4586
5181
|
// src/tools/index.ts
|
|
4587
5182
|
init_semantic();
|
|
5183
|
+
init_remote();
|
|
4588
5184
|
init_semantic_search();
|
|
4589
5185
|
async function createTools(options) {
|
|
4590
5186
|
const tools = {
|
|
@@ -4622,6 +5218,12 @@ async function createTools(options) {
|
|
|
4622
5218
|
workingDirectory: options.workingDirectory
|
|
4623
5219
|
})
|
|
4624
5220
|
};
|
|
5221
|
+
if (isRemoteConfigured()) {
|
|
5222
|
+
tools.upload_file = createUploadFileTool({
|
|
5223
|
+
workingDirectory: options.workingDirectory,
|
|
5224
|
+
sessionId: options.sessionId
|
|
5225
|
+
});
|
|
5226
|
+
}
|
|
4625
5227
|
if (options.enableSemanticSearch !== false) {
|
|
4626
5228
|
try {
|
|
4627
5229
|
if (isVectorGatewayConfigured()) {
|
|
@@ -4716,6 +5318,7 @@ You have access to powerful tools for:
|
|
|
4716
5318
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
4717
5319
|
- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
|
|
4718
5320
|
- **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
|
|
5321
|
+
- **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
|
|
4719
5322
|
|
|
4720
5323
|
|
|
4721
5324
|
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.
|
|
@@ -4974,14 +5577,53 @@ function buildTaskPromptAddendum(outputSchema) {
|
|
|
4974
5577
|
## Task Mode
|
|
4975
5578
|
|
|
4976
5579
|
You are running in **task mode**. You have been given a specific task to complete autonomously.
|
|
5580
|
+
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.
|
|
5581
|
+
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.
|
|
4977
5582
|
|
|
4978
5583
|
### Rules
|
|
4979
5584
|
1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
|
|
4980
|
-
2. Keep working until the task is fully complete.
|
|
5585
|
+
2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
|
|
4981
5586
|
3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
4982
5587
|
4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
4983
5588
|
5. Do NOT stop without calling one of these two tools.
|
|
4984
5589
|
|
|
5590
|
+
### Verification \u2014 BE EXTREMELY THOROUGH
|
|
5591
|
+
Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
|
|
5592
|
+
|
|
5593
|
+
**After making code changes:**
|
|
5594
|
+
- Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
|
|
5595
|
+
- **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
|
|
5596
|
+
- If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
|
|
5597
|
+
- If you created new files, verify they exist and contain what you expect.
|
|
5598
|
+
|
|
5599
|
+
**For UI / web changes:**
|
|
5600
|
+
- Start the dev server if it isn't already running (it might be so double check ur context)
|
|
5601
|
+
- **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
|
|
5602
|
+
- Check the dev server logs for errors or warnings.
|
|
5603
|
+
- If the app crashes or shows errors, fix them before completing.
|
|
5604
|
+
|
|
5605
|
+
**For backend / API changes:**
|
|
5606
|
+
- Test the endpoint with curl or a quick script to confirm it works as expected.
|
|
5607
|
+
- Check server logs for errors.
|
|
5608
|
+
|
|
5609
|
+
**For search and exploration tasks:**
|
|
5610
|
+
- Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
|
|
5611
|
+
- Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
|
|
5612
|
+
- Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
|
|
5613
|
+
- Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
|
|
5614
|
+
|
|
5615
|
+
**General verification checklist:**
|
|
5616
|
+
- Re-read the original task prompt and confirm every requirement has been addressed.
|
|
5617
|
+
- If the task asked for multiple things, verify EACH one individually.
|
|
5618
|
+
- If something doesn't look right, fix it \u2014 don't complete with known issues.
|
|
5619
|
+
|
|
5620
|
+
### Use All Available Tools
|
|
5621
|
+
- **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
|
|
5622
|
+
- **explore_agent**: Use for codebase exploration and understanding before making changes.
|
|
5623
|
+
- **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
|
|
5624
|
+
- **todo**: Track your progress on multi-step tasks so you don't miss steps.
|
|
5625
|
+
- **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
|
|
5626
|
+
|
|
4985
5627
|
### Output Schema
|
|
4986
5628
|
The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
|
|
4987
5629
|
\`\`\`json
|
|
@@ -4989,7 +5631,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
4989
5631
|
\`\`\`
|
|
4990
5632
|
|
|
4991
5633
|
### Completion Tools
|
|
4992
|
-
- **\`complete_task({ result: ... })\`** \u2014 Call
|
|
5634
|
+
- **\`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.
|
|
4993
5635
|
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
4994
5636
|
`;
|
|
4995
5637
|
}
|
|
@@ -5515,11 +6157,29 @@ ${prompt}` });
|
|
|
5515
6157
|
const onComplete = (signal) => {
|
|
5516
6158
|
completion.signal = signal;
|
|
5517
6159
|
};
|
|
6160
|
+
let taskRecorder = null;
|
|
6161
|
+
const sessionId = this.session.id;
|
|
6162
|
+
const bashProgressHandler = (progress) => {
|
|
6163
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
6164
|
+
const port = progress.browserStreamPort;
|
|
6165
|
+
if (port && progress.status === "started") {
|
|
6166
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
6167
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
6168
|
+
if (!taskRecorder) {
|
|
6169
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
6170
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
6171
|
+
taskRecorder.start();
|
|
6172
|
+
proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
|
|
6173
|
+
});
|
|
6174
|
+
}
|
|
6175
|
+
});
|
|
6176
|
+
}
|
|
6177
|
+
};
|
|
5518
6178
|
const taskTools = await createTools({
|
|
5519
6179
|
sessionId: this.session.id,
|
|
5520
6180
|
workingDirectory: this.session.workingDirectory,
|
|
5521
6181
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
5522
|
-
onBashProgress:
|
|
6182
|
+
onBashProgress: bashProgressHandler,
|
|
5523
6183
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
5524
6184
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
|
|
5525
6185
|
taskTools: {
|
|
@@ -5590,12 +6250,24 @@ ${taskAddendum}`;
|
|
|
5590
6250
|
if (completion.signal) {
|
|
5591
6251
|
const sig = completion.signal;
|
|
5592
6252
|
const finalStatus = sig.status;
|
|
6253
|
+
let fileUrls;
|
|
6254
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
6255
|
+
const resultObj = sig.result;
|
|
6256
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
6257
|
+
if (filePaths.length > 0) {
|
|
6258
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6262
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
5593
6263
|
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
5594
6264
|
fireWebhook(eventType, {
|
|
5595
6265
|
status: finalStatus,
|
|
5596
6266
|
result: sig.result,
|
|
5597
6267
|
error: sig.error,
|
|
5598
|
-
iterations: iteration
|
|
6268
|
+
iterations: iteration,
|
|
6269
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
6270
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
5599
6271
|
});
|
|
5600
6272
|
const updatedTask2 = {
|
|
5601
6273
|
...options.taskConfig,
|
|
@@ -5615,11 +6287,17 @@ ${taskAddendum}`;
|
|
|
5615
6287
|
};
|
|
5616
6288
|
}
|
|
5617
6289
|
await this.context.addUserMessage(
|
|
5618
|
-
"Continue working on the task. When
|
|
6290
|
+
"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."
|
|
5619
6291
|
);
|
|
5620
6292
|
}
|
|
5621
6293
|
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
5622
|
-
|
|
6294
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6295
|
+
fireWebhook("task.failed", {
|
|
6296
|
+
status: "failed",
|
|
6297
|
+
error: timeoutError,
|
|
6298
|
+
iterations: iteration,
|
|
6299
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
6300
|
+
});
|
|
5623
6301
|
const updatedTask = {
|
|
5624
6302
|
...options.taskConfig,
|
|
5625
6303
|
status: "failed",
|
|
@@ -5631,6 +6309,113 @@ ${taskAddendum}`;
|
|
|
5631
6309
|
});
|
|
5632
6310
|
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
5633
6311
|
}
|
|
6312
|
+
/**
|
|
6313
|
+
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
6314
|
+
* Returns download URLs for any recordings produced.
|
|
6315
|
+
*/
|
|
6316
|
+
async finishTaskRecording(recorder) {
|
|
6317
|
+
try {
|
|
6318
|
+
const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
|
|
6319
|
+
destroyProxy2(this.session.id);
|
|
6320
|
+
} catch {
|
|
6321
|
+
}
|
|
6322
|
+
if (!recorder || recorder.frameCount === 0) {
|
|
6323
|
+
recorder?.clear();
|
|
6324
|
+
return [];
|
|
6325
|
+
}
|
|
6326
|
+
recorder.stop();
|
|
6327
|
+
try {
|
|
6328
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6329
|
+
if (!isRemoteConfigured2()) {
|
|
6330
|
+
recorder.clear();
|
|
6331
|
+
return [];
|
|
6332
|
+
}
|
|
6333
|
+
const result = await recorder.encode();
|
|
6334
|
+
recorder.clear();
|
|
6335
|
+
if (!result) return [];
|
|
6336
|
+
const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
|
|
6337
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6338
|
+
this.session.id,
|
|
6339
|
+
`browser-recording-${Date.now()}.mp4`,
|
|
6340
|
+
"video/mp4",
|
|
6341
|
+
"browser-recording"
|
|
6342
|
+
);
|
|
6343
|
+
const fileData = await readFile11(result.path);
|
|
6344
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6345
|
+
method: "PUT",
|
|
6346
|
+
headers: { "Content-Type": "video/mp4" },
|
|
6347
|
+
body: fileData
|
|
6348
|
+
});
|
|
6349
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
6350
|
+
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6351
|
+
await unlink3(result.path).catch(() => {
|
|
6352
|
+
});
|
|
6353
|
+
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
6354
|
+
return [dlInfo.downloadUrl];
|
|
6355
|
+
} catch (err) {
|
|
6356
|
+
console.error("[TASK] Failed to upload browser recording:", err.message);
|
|
6357
|
+
recorder.clear();
|
|
6358
|
+
return [];
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
6361
|
+
/**
|
|
6362
|
+
* Upload task output files to GCS via the remote server.
|
|
6363
|
+
* Returns an array of download URLs for successfully uploaded files.
|
|
6364
|
+
*/
|
|
6365
|
+
async uploadTaskFiles(filePaths) {
|
|
6366
|
+
try {
|
|
6367
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6368
|
+
if (!isRemoteConfigured2()) return [];
|
|
6369
|
+
const { readFile: readFile11 } = await import("fs/promises");
|
|
6370
|
+
const { join: join7, basename: basename5 } = await import("path");
|
|
6371
|
+
const urls = [];
|
|
6372
|
+
for (const filePath of filePaths) {
|
|
6373
|
+
try {
|
|
6374
|
+
const fullPath = filePath.startsWith("/") ? filePath : join7(this.session.workingDirectory, filePath);
|
|
6375
|
+
const fileName = basename5(fullPath);
|
|
6376
|
+
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
6377
|
+
const mimeMap = {
|
|
6378
|
+
pdf: "application/pdf",
|
|
6379
|
+
json: "application/json",
|
|
6380
|
+
csv: "text/csv",
|
|
6381
|
+
txt: "text/plain",
|
|
6382
|
+
md: "text/markdown",
|
|
6383
|
+
html: "text/html",
|
|
6384
|
+
png: "image/png",
|
|
6385
|
+
jpg: "image/jpeg",
|
|
6386
|
+
jpeg: "image/jpeg",
|
|
6387
|
+
gif: "image/gif",
|
|
6388
|
+
svg: "image/svg+xml",
|
|
6389
|
+
mp4: "video/mp4",
|
|
6390
|
+
zip: "application/zip"
|
|
6391
|
+
};
|
|
6392
|
+
const contentType = mimeMap[ext] || "application/octet-stream";
|
|
6393
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6394
|
+
this.session.id,
|
|
6395
|
+
fileName,
|
|
6396
|
+
contentType,
|
|
6397
|
+
"task-output"
|
|
6398
|
+
);
|
|
6399
|
+
const fileData = await readFile11(fullPath);
|
|
6400
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6401
|
+
method: "PUT",
|
|
6402
|
+
headers: { "Content-Type": contentType },
|
|
6403
|
+
body: fileData
|
|
6404
|
+
});
|
|
6405
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
6406
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6407
|
+
urls.push(downloadInfo.downloadUrl);
|
|
6408
|
+
console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
|
|
6409
|
+
} catch (err) {
|
|
6410
|
+
console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
6413
|
+
return urls;
|
|
6414
|
+
} catch (err) {
|
|
6415
|
+
console.error("[TASK] File upload failed:", err.message);
|
|
6416
|
+
return [];
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
5634
6419
|
/**
|
|
5635
6420
|
* Wrap tools to add approval checking
|
|
5636
6421
|
*/
|
|
@@ -5644,11 +6429,11 @@ ${taskAddendum}`;
|
|
|
5644
6429
|
wrappedTools[name] = originalTool;
|
|
5645
6430
|
continue;
|
|
5646
6431
|
}
|
|
5647
|
-
wrappedTools[name] =
|
|
6432
|
+
wrappedTools[name] = tool13({
|
|
5648
6433
|
description: originalTool.description || "",
|
|
5649
|
-
inputSchema: originalTool.inputSchema ||
|
|
6434
|
+
inputSchema: originalTool.inputSchema || z14.object({}),
|
|
5650
6435
|
execute: async (input, toolOptions) => {
|
|
5651
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
6436
|
+
const toolCallId = toolOptions.toolCallId || nanoid4();
|
|
5652
6437
|
const execution = toolExecutionQueries.create({
|
|
5653
6438
|
sessionId: this.session.id,
|
|
5654
6439
|
toolName: name,
|
|
@@ -5666,10 +6451,10 @@ ${taskAddendum}`;
|
|
|
5666
6451
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
5667
6452
|
approvalResolvers.delete(toolCallId);
|
|
5668
6453
|
this.pendingApprovals.delete(toolCallId);
|
|
5669
|
-
const
|
|
6454
|
+
const exec6 = await execution;
|
|
5670
6455
|
if (!approved) {
|
|
5671
6456
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
5672
|
-
await toolExecutionQueries.reject(
|
|
6457
|
+
await toolExecutionQueries.reject(exec6.id);
|
|
5673
6458
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
5674
6459
|
return {
|
|
5675
6460
|
status: "rejected",
|
|
@@ -5679,14 +6464,14 @@ ${taskAddendum}`;
|
|
|
5679
6464
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
5680
6465
|
};
|
|
5681
6466
|
}
|
|
5682
|
-
await toolExecutionQueries.approve(
|
|
6467
|
+
await toolExecutionQueries.approve(exec6.id);
|
|
5683
6468
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
5684
6469
|
try {
|
|
5685
6470
|
const result = await originalTool.execute(input, toolOptions);
|
|
5686
|
-
await toolExecutionQueries.complete(
|
|
6471
|
+
await toolExecutionQueries.complete(exec6.id, result);
|
|
5687
6472
|
return result;
|
|
5688
6473
|
} catch (error) {
|
|
5689
|
-
await toolExecutionQueries.complete(
|
|
6474
|
+
await toolExecutionQueries.complete(exec6.id, null, error.message);
|
|
5690
6475
|
throw error;
|
|
5691
6476
|
}
|
|
5692
6477
|
}
|