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/index.js
CHANGED
|
@@ -524,6 +524,25 @@ var init_config = __esm({
|
|
|
524
524
|
});
|
|
525
525
|
|
|
526
526
|
// src/db/remote.ts
|
|
527
|
+
var remote_exports = {};
|
|
528
|
+
__export(remote_exports, {
|
|
529
|
+
closeRemoteDatabase: () => closeRemoteDatabase,
|
|
530
|
+
initRemoteDatabase: () => initRemoteDatabase,
|
|
531
|
+
isRemoteConfigured: () => isRemoteConfigured,
|
|
532
|
+
remoteActiveStreamQueries: () => remoteActiveStreamQueries,
|
|
533
|
+
remoteCheckpointQueries: () => remoteCheckpointQueries,
|
|
534
|
+
remoteFileBackupQueries: () => remoteFileBackupQueries,
|
|
535
|
+
remoteIndexStatusQueries: () => remoteIndexStatusQueries,
|
|
536
|
+
remoteIndexedChunkQueries: () => remoteIndexedChunkQueries,
|
|
537
|
+
remoteMessageQueries: () => remoteMessageQueries,
|
|
538
|
+
remoteSessionQueries: () => remoteSessionQueries,
|
|
539
|
+
remoteSkillQueries: () => remoteSkillQueries,
|
|
540
|
+
remoteSubagentQueries: () => remoteSubagentQueries,
|
|
541
|
+
remoteTerminalQueries: () => remoteTerminalQueries,
|
|
542
|
+
remoteTodoQueries: () => remoteTodoQueries,
|
|
543
|
+
remoteToolExecutionQueries: () => remoteToolExecutionQueries,
|
|
544
|
+
storageQueries: () => storageQueries
|
|
545
|
+
});
|
|
527
546
|
function initRemoteDatabase(serverUrl, key) {
|
|
528
547
|
remoteServerUrl = serverUrl.replace(/\/$/, "");
|
|
529
548
|
authKey = key;
|
|
@@ -532,6 +551,9 @@ function closeRemoteDatabase() {
|
|
|
532
551
|
remoteServerUrl = null;
|
|
533
552
|
authKey = null;
|
|
534
553
|
}
|
|
554
|
+
function isRemoteConfigured() {
|
|
555
|
+
return !!remoteServerUrl && !!authKey;
|
|
556
|
+
}
|
|
535
557
|
function parseDates(obj) {
|
|
536
558
|
if (obj === null || obj === void 0) return obj;
|
|
537
559
|
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
@@ -579,7 +601,29 @@ async function api(path, options = {}) {
|
|
|
579
601
|
}
|
|
580
602
|
return parseDates(parsed);
|
|
581
603
|
}
|
|
582
|
-
|
|
604
|
+
async function storageApi(path, options = {}) {
|
|
605
|
+
if (!remoteServerUrl || !authKey) {
|
|
606
|
+
throw new Error("Remote database not initialized");
|
|
607
|
+
}
|
|
608
|
+
const url = `${remoteServerUrl}/storage${path}`;
|
|
609
|
+
const init = {
|
|
610
|
+
method: options.method || "GET",
|
|
611
|
+
headers: {
|
|
612
|
+
"Content-Type": "application/json",
|
|
613
|
+
"Authorization": `Bearer ${authKey}`
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
if (options.body) {
|
|
617
|
+
init.body = JSON.stringify(options.body);
|
|
618
|
+
}
|
|
619
|
+
const response = await fetch(url, init);
|
|
620
|
+
if (!response.ok) {
|
|
621
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
622
|
+
throw new Error(`Storage API error ${response.status}: ${errorText}`);
|
|
623
|
+
}
|
|
624
|
+
return response.json();
|
|
625
|
+
}
|
|
626
|
+
var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteTerminalQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries, storageQueries;
|
|
583
627
|
var init_remote = __esm({
|
|
584
628
|
"src/db/remote.ts"() {
|
|
585
629
|
"use strict";
|
|
@@ -718,6 +762,34 @@ var init_remote = __esm({
|
|
|
718
762
|
return result.isLoaded;
|
|
719
763
|
}
|
|
720
764
|
};
|
|
765
|
+
remoteTerminalQueries = {
|
|
766
|
+
create(data) {
|
|
767
|
+
return api("/terminals", { method: "POST", body: data });
|
|
768
|
+
},
|
|
769
|
+
getById(id) {
|
|
770
|
+
return api(`/terminals/${id}`).catch(() => void 0);
|
|
771
|
+
},
|
|
772
|
+
getBySession(sessionId) {
|
|
773
|
+
return api(`/terminals/session/${sessionId}`);
|
|
774
|
+
},
|
|
775
|
+
getRunning(sessionId) {
|
|
776
|
+
return api(`/terminals/session/${sessionId}/running`);
|
|
777
|
+
},
|
|
778
|
+
updateStatus(id, status, exitCode, error) {
|
|
779
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { status, exitCode, error } });
|
|
780
|
+
},
|
|
781
|
+
updatePid(id, pid) {
|
|
782
|
+
return api(`/terminals/${id}`, { method: "PATCH", body: { pid } });
|
|
783
|
+
},
|
|
784
|
+
async delete(id) {
|
|
785
|
+
const result = await api(`/terminals/${id}`, { method: "DELETE" });
|
|
786
|
+
return result?.success ?? false;
|
|
787
|
+
},
|
|
788
|
+
async deleteBySession(sessionId) {
|
|
789
|
+
const result = await api(`/terminals/session/${sessionId}`, { method: "DELETE" });
|
|
790
|
+
return result.deleted;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
721
793
|
remoteActiveStreamQueries = {
|
|
722
794
|
create(sessionId, streamId) {
|
|
723
795
|
return api("/streams", { method: "POST", body: { sessionId, streamId } });
|
|
@@ -821,6 +893,41 @@ var init_remote = __esm({
|
|
|
821
893
|
return result.deleted;
|
|
822
894
|
}
|
|
823
895
|
};
|
|
896
|
+
remoteIndexedChunkQueries = {
|
|
897
|
+
upsert(_db, data) {
|
|
898
|
+
return api("/indexed-chunks", { method: "POST", body: data });
|
|
899
|
+
},
|
|
900
|
+
batchUpsert(_db, chunks) {
|
|
901
|
+
return api("/indexed-chunks/batch", {
|
|
902
|
+
method: "POST",
|
|
903
|
+
body: { chunks }
|
|
904
|
+
});
|
|
905
|
+
},
|
|
906
|
+
getById(_db, id) {
|
|
907
|
+
return api(`/indexed-chunks/${id}`).catch(() => void 0);
|
|
908
|
+
},
|
|
909
|
+
getByNamespace(_db, namespace) {
|
|
910
|
+
return api(`/indexed-chunks/namespace/${namespace}`);
|
|
911
|
+
},
|
|
912
|
+
getByFilePath(_db, namespace, filePath) {
|
|
913
|
+
return api(`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`);
|
|
914
|
+
},
|
|
915
|
+
async deleteByNamespace(_db, namespace) {
|
|
916
|
+
const result = await api(`/indexed-chunks/namespace/${namespace}`, { method: "DELETE" });
|
|
917
|
+
return result.deleted;
|
|
918
|
+
},
|
|
919
|
+
async deleteByFilePath(_db, namespace, filePath) {
|
|
920
|
+
const result = await api(
|
|
921
|
+
`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`,
|
|
922
|
+
{ method: "DELETE" }
|
|
923
|
+
);
|
|
924
|
+
return result.deleted;
|
|
925
|
+
},
|
|
926
|
+
async countByNamespace(_db, namespace) {
|
|
927
|
+
const result = await api(`/indexed-chunks/namespace/${namespace}/count`);
|
|
928
|
+
return result.count;
|
|
929
|
+
}
|
|
930
|
+
};
|
|
824
931
|
remoteIndexStatusQueries = {
|
|
825
932
|
upsert(_db, data) {
|
|
826
933
|
return api("/index-status", {
|
|
@@ -843,6 +950,27 @@ var init_remote = __esm({
|
|
|
843
950
|
return api("/index-status");
|
|
844
951
|
}
|
|
845
952
|
};
|
|
953
|
+
storageQueries = {
|
|
954
|
+
async getUploadUrl(sessionId, fileName, contentType, category) {
|
|
955
|
+
return storageApi("/upload-url", {
|
|
956
|
+
method: "POST",
|
|
957
|
+
body: { sessionId, fileName, contentType, category }
|
|
958
|
+
});
|
|
959
|
+
},
|
|
960
|
+
async getSessionFiles(sessionId) {
|
|
961
|
+
const result = await storageApi(`/files/${sessionId}`);
|
|
962
|
+
return result.files;
|
|
963
|
+
},
|
|
964
|
+
async getDownloadUrl(fileId) {
|
|
965
|
+
return storageApi(`/download/${fileId}`);
|
|
966
|
+
},
|
|
967
|
+
async deleteFile(fileId) {
|
|
968
|
+
await storageApi(`/files/${fileId}`, { method: "DELETE" });
|
|
969
|
+
},
|
|
970
|
+
async updateFile(fileId, data) {
|
|
971
|
+
await storageApi(`/files/${fileId}`, { method: "PATCH", body: data });
|
|
972
|
+
}
|
|
973
|
+
};
|
|
846
974
|
}
|
|
847
975
|
});
|
|
848
976
|
|
|
@@ -1645,11 +1773,285 @@ var init_webhook = __esm({
|
|
|
1645
1773
|
}
|
|
1646
1774
|
});
|
|
1647
1775
|
|
|
1776
|
+
// src/browser/stream-proxy.ts
|
|
1777
|
+
var stream_proxy_exports = {};
|
|
1778
|
+
__export(stream_proxy_exports, {
|
|
1779
|
+
BrowserStreamProxy: () => BrowserStreamProxy,
|
|
1780
|
+
destroyProxy: () => destroyProxy,
|
|
1781
|
+
getOrCreateProxy: () => getOrCreateProxy,
|
|
1782
|
+
getProxy: () => getProxy
|
|
1783
|
+
});
|
|
1784
|
+
import WebSocket from "ws";
|
|
1785
|
+
import { EventEmitter } from "events";
|
|
1786
|
+
function getOrCreateProxy(sessionId, port) {
|
|
1787
|
+
const existing = activeProxies.get(sessionId);
|
|
1788
|
+
if (existing) return existing;
|
|
1789
|
+
const proxy = new BrowserStreamProxy(port);
|
|
1790
|
+
activeProxies.set(sessionId, proxy);
|
|
1791
|
+
proxy.on("close", () => {
|
|
1792
|
+
activeProxies.delete(sessionId);
|
|
1793
|
+
});
|
|
1794
|
+
proxy.connect();
|
|
1795
|
+
return proxy;
|
|
1796
|
+
}
|
|
1797
|
+
function getProxy(sessionId) {
|
|
1798
|
+
return activeProxies.get(sessionId);
|
|
1799
|
+
}
|
|
1800
|
+
function destroyProxy(sessionId) {
|
|
1801
|
+
const proxy = activeProxies.get(sessionId);
|
|
1802
|
+
if (proxy) {
|
|
1803
|
+
proxy.destroy();
|
|
1804
|
+
activeProxies.delete(sessionId);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
|
|
1808
|
+
var init_stream_proxy = __esm({
|
|
1809
|
+
"src/browser/stream-proxy.ts"() {
|
|
1810
|
+
"use strict";
|
|
1811
|
+
RECONNECT_DELAY_MS = 1e3;
|
|
1812
|
+
MAX_RECONNECT_ATTEMPTS = 20;
|
|
1813
|
+
FRAME_THROTTLE_MS = 100;
|
|
1814
|
+
BrowserStreamProxy = class extends EventEmitter {
|
|
1815
|
+
ws = null;
|
|
1816
|
+
port;
|
|
1817
|
+
reconnectAttempts = 0;
|
|
1818
|
+
reconnectTimer = null;
|
|
1819
|
+
destroyed = false;
|
|
1820
|
+
lastFrameTime = 0;
|
|
1821
|
+
_latestFrame = null;
|
|
1822
|
+
_connected = false;
|
|
1823
|
+
constructor(port) {
|
|
1824
|
+
super();
|
|
1825
|
+
this.port = port;
|
|
1826
|
+
}
|
|
1827
|
+
get connected() {
|
|
1828
|
+
return this._connected;
|
|
1829
|
+
}
|
|
1830
|
+
get latestFrame() {
|
|
1831
|
+
return this._latestFrame;
|
|
1832
|
+
}
|
|
1833
|
+
connect() {
|
|
1834
|
+
if (this.destroyed) return;
|
|
1835
|
+
this.doConnect();
|
|
1836
|
+
}
|
|
1837
|
+
doConnect() {
|
|
1838
|
+
if (this.destroyed) return;
|
|
1839
|
+
const url = `ws://localhost:${this.port}`;
|
|
1840
|
+
try {
|
|
1841
|
+
this.ws = new WebSocket(url);
|
|
1842
|
+
} catch {
|
|
1843
|
+
this.scheduleReconnect();
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
this.ws.on("open", () => {
|
|
1847
|
+
this.reconnectAttempts = 0;
|
|
1848
|
+
this._connected = true;
|
|
1849
|
+
this.emit("status", {
|
|
1850
|
+
connected: true,
|
|
1851
|
+
screencasting: true
|
|
1852
|
+
});
|
|
1853
|
+
});
|
|
1854
|
+
this.ws.on("message", (raw) => {
|
|
1855
|
+
try {
|
|
1856
|
+
const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
1857
|
+
this.handleMessage(msg);
|
|
1858
|
+
} catch {
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
this.ws.on("close", () => {
|
|
1862
|
+
const wasConnected = this._connected;
|
|
1863
|
+
this._connected = false;
|
|
1864
|
+
if (wasConnected) {
|
|
1865
|
+
this.emit("status", { connected: false, screencasting: false });
|
|
1866
|
+
}
|
|
1867
|
+
if (!this.destroyed) {
|
|
1868
|
+
this.scheduleReconnect();
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
this.ws.on("error", () => {
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
handleMessage(msg) {
|
|
1875
|
+
if (msg.type === "frame") {
|
|
1876
|
+
const now = Date.now();
|
|
1877
|
+
if (now - this.lastFrameTime < FRAME_THROTTLE_MS) return;
|
|
1878
|
+
this.lastFrameTime = now;
|
|
1879
|
+
const frame = {
|
|
1880
|
+
data: msg.data,
|
|
1881
|
+
metadata: msg.metadata ?? {
|
|
1882
|
+
deviceWidth: 1280,
|
|
1883
|
+
deviceHeight: 720,
|
|
1884
|
+
pageScaleFactor: 1,
|
|
1885
|
+
offsetTop: 0,
|
|
1886
|
+
scrollOffsetX: 0,
|
|
1887
|
+
scrollOffsetY: 0
|
|
1888
|
+
},
|
|
1889
|
+
timestamp: now
|
|
1890
|
+
};
|
|
1891
|
+
this._latestFrame = frame;
|
|
1892
|
+
this.emit("frame", frame);
|
|
1893
|
+
} else if (msg.type === "status") {
|
|
1894
|
+
this.emit("status", {
|
|
1895
|
+
connected: msg.connected ?? true,
|
|
1896
|
+
screencasting: msg.screencasting ?? true,
|
|
1897
|
+
viewportWidth: msg.viewportWidth,
|
|
1898
|
+
viewportHeight: msg.viewportHeight
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
scheduleReconnect() {
|
|
1903
|
+
if (this.destroyed || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
1904
|
+
this.emit("close");
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
this.reconnectAttempts++;
|
|
1908
|
+
const delay = this.reconnectAttempts <= 5 ? RECONNECT_DELAY_MS : RECONNECT_DELAY_MS * (this.reconnectAttempts - 4);
|
|
1909
|
+
this.reconnectTimer = setTimeout(() => this.doConnect(), delay);
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Send an input event to the browser for pair-browsing.
|
|
1913
|
+
*/
|
|
1914
|
+
injectInput(event) {
|
|
1915
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1916
|
+
this.ws.send(JSON.stringify(event));
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
destroy() {
|
|
1920
|
+
this.destroyed = true;
|
|
1921
|
+
if (this.reconnectTimer) {
|
|
1922
|
+
clearTimeout(this.reconnectTimer);
|
|
1923
|
+
this.reconnectTimer = null;
|
|
1924
|
+
}
|
|
1925
|
+
if (this.ws) {
|
|
1926
|
+
this.ws.removeAllListeners();
|
|
1927
|
+
this.ws.close();
|
|
1928
|
+
this.ws = null;
|
|
1929
|
+
}
|
|
1930
|
+
this._connected = false;
|
|
1931
|
+
this.removeAllListeners();
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
activeProxies = /* @__PURE__ */ new Map();
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
// src/browser/recorder.ts
|
|
1939
|
+
var recorder_exports = {};
|
|
1940
|
+
__export(recorder_exports, {
|
|
1941
|
+
FrameRecorder: () => FrameRecorder
|
|
1942
|
+
});
|
|
1943
|
+
import { exec as exec5 } from "child_process";
|
|
1944
|
+
import { promisify as promisify5 } from "util";
|
|
1945
|
+
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1946
|
+
import { join as join6 } from "path";
|
|
1947
|
+
import { tmpdir } from "os";
|
|
1948
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
1949
|
+
async function checkFfmpeg() {
|
|
1950
|
+
try {
|
|
1951
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
1952
|
+
return true;
|
|
1953
|
+
} catch {
|
|
1954
|
+
return false;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
async function cleanup(dir) {
|
|
1958
|
+
try {
|
|
1959
|
+
await rm(dir, { recursive: true, force: true });
|
|
1960
|
+
} catch {
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
var execAsync5, FrameRecorder;
|
|
1964
|
+
var init_recorder = __esm({
|
|
1965
|
+
"src/browser/recorder.ts"() {
|
|
1966
|
+
"use strict";
|
|
1967
|
+
execAsync5 = promisify5(exec5);
|
|
1968
|
+
FrameRecorder = class {
|
|
1969
|
+
frames = [];
|
|
1970
|
+
startTime = null;
|
|
1971
|
+
recording = false;
|
|
1972
|
+
sessionId;
|
|
1973
|
+
constructor(sessionId) {
|
|
1974
|
+
this.sessionId = sessionId;
|
|
1975
|
+
}
|
|
1976
|
+
get isRecording() {
|
|
1977
|
+
return this.recording;
|
|
1978
|
+
}
|
|
1979
|
+
get frameCount() {
|
|
1980
|
+
return this.frames.length;
|
|
1981
|
+
}
|
|
1982
|
+
start() {
|
|
1983
|
+
this.frames = [];
|
|
1984
|
+
this.startTime = Date.now();
|
|
1985
|
+
this.recording = true;
|
|
1986
|
+
}
|
|
1987
|
+
addFrame(frame) {
|
|
1988
|
+
if (!this.recording) return;
|
|
1989
|
+
this.frames.push({
|
|
1990
|
+
data: Buffer.from(frame.data, "base64"),
|
|
1991
|
+
timestamp: frame.timestamp
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
stop() {
|
|
1995
|
+
this.recording = false;
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Encode recorded frames into an MP4 using ffmpeg.
|
|
1999
|
+
* Returns the file path to the generated MP4, or null if encoding fails.
|
|
2000
|
+
*/
|
|
2001
|
+
async encode() {
|
|
2002
|
+
if (this.frames.length === 0) return null;
|
|
2003
|
+
const workDir = join6(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
|
|
2004
|
+
await mkdir4(workDir, { recursive: true });
|
|
2005
|
+
try {
|
|
2006
|
+
for (let i = 0; i < this.frames.length; i++) {
|
|
2007
|
+
const framePath = join6(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
2008
|
+
await writeFile4(framePath, this.frames[i].data);
|
|
2009
|
+
}
|
|
2010
|
+
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
2011
|
+
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
2012
|
+
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
2013
|
+
const outputPath = join6(workDir, `recording_${this.sessionId}.mp4`);
|
|
2014
|
+
const hasFfmpeg = await checkFfmpeg();
|
|
2015
|
+
if (hasFfmpeg) {
|
|
2016
|
+
await execAsync5(
|
|
2017
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join6(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
2018
|
+
{ timeout: 12e4 }
|
|
2019
|
+
);
|
|
2020
|
+
} else {
|
|
2021
|
+
console.warn("[RECORDER] ffmpeg not available, cannot encode recording");
|
|
2022
|
+
await cleanup(workDir);
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
const outputBuf = await readFile10(outputPath);
|
|
2026
|
+
const files = await readdir5(workDir);
|
|
2027
|
+
for (const f of files) {
|
|
2028
|
+
if (f.startsWith("frame_")) {
|
|
2029
|
+
await unlink2(join6(workDir, f)).catch(() => {
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
return { path: outputPath, sizeBytes: outputBuf.length };
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
console.error("[RECORDER] Failed to encode recording:", error);
|
|
2036
|
+
await cleanup(workDir);
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
/** Discard all frames and free memory */
|
|
2041
|
+
clear() {
|
|
2042
|
+
this.frames = [];
|
|
2043
|
+
this.startTime = null;
|
|
2044
|
+
this.recording = false;
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
|
|
1648
2050
|
// src/agent/index.ts
|
|
1649
2051
|
import {
|
|
1650
2052
|
streamText as streamText2,
|
|
1651
2053
|
generateText as generateText3,
|
|
1652
|
-
tool as
|
|
2054
|
+
tool as tool13,
|
|
1653
2055
|
stepCountIs as stepCountIs2
|
|
1654
2056
|
} from "ai";
|
|
1655
2057
|
|
|
@@ -1830,8 +2232,8 @@ var SUBAGENT_MODELS = {
|
|
|
1830
2232
|
// src/agent/index.ts
|
|
1831
2233
|
init_db();
|
|
1832
2234
|
init_config();
|
|
1833
|
-
import { z as
|
|
1834
|
-
import { nanoid as
|
|
2235
|
+
import { z as z14 } from "zod";
|
|
2236
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1835
2237
|
|
|
1836
2238
|
// src/tools/bash.ts
|
|
1837
2239
|
import { tool } from "ai";
|
|
@@ -2134,8 +2536,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
|
|
|
2134
2536
|
const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
|
|
2135
2537
|
const terminals2 = [];
|
|
2136
2538
|
try {
|
|
2137
|
-
const { readdir:
|
|
2138
|
-
const entries = await
|
|
2539
|
+
const { readdir: readdir7 } = await import("fs/promises");
|
|
2540
|
+
const entries = await readdir7(terminalsDir, { withFileTypes: true });
|
|
2139
2541
|
for (const entry of entries) {
|
|
2140
2542
|
if (entry.isDirectory()) {
|
|
2141
2543
|
const meta = await getMeta(entry.name, workingDirectory, sessionId);
|
|
@@ -2197,6 +2599,29 @@ function isBlockedCommand(command) {
|
|
|
2197
2599
|
(blocked) => normalizedCommand.includes(blocked.toLowerCase())
|
|
2198
2600
|
);
|
|
2199
2601
|
}
|
|
2602
|
+
var BROWSER_STREAM_BASE_PORT = 9223;
|
|
2603
|
+
var sessionBrowserPorts = /* @__PURE__ */ new Map();
|
|
2604
|
+
var nextPortOffset = 0;
|
|
2605
|
+
function getBrowserStreamPort(sessionId) {
|
|
2606
|
+
let port = sessionBrowserPorts.get(sessionId);
|
|
2607
|
+
if (!port) {
|
|
2608
|
+
port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
|
|
2609
|
+
sessionBrowserPorts.set(sessionId, port);
|
|
2610
|
+
}
|
|
2611
|
+
return port;
|
|
2612
|
+
}
|
|
2613
|
+
function hasAgentBrowserCommand(command) {
|
|
2614
|
+
return /\bagent-browser\b/.test(command);
|
|
2615
|
+
}
|
|
2616
|
+
function isAgentBrowserCloseCommand(command) {
|
|
2617
|
+
return /\bagent-browser\s+(close|close\s+--all)\b/.test(command);
|
|
2618
|
+
}
|
|
2619
|
+
function injectBrowserStreamPort(command, port) {
|
|
2620
|
+
return command.replace(
|
|
2621
|
+
/\bagent-browser\b/g,
|
|
2622
|
+
`AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2200
2625
|
var bashInputSchema = z2.object({
|
|
2201
2626
|
command: z2.string().optional().describe("The command to execute. Required for running new commands."),
|
|
2202
2627
|
background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
|
|
@@ -2362,6 +2787,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2362
2787
|
exitCode: 1
|
|
2363
2788
|
};
|
|
2364
2789
|
}
|
|
2790
|
+
let actualCommand = command;
|
|
2791
|
+
const hasAgentBrowser = hasAgentBrowserCommand(command);
|
|
2792
|
+
const browserClose = isAgentBrowserCloseCommand(command);
|
|
2793
|
+
let browserPort;
|
|
2794
|
+
if (hasAgentBrowser) {
|
|
2795
|
+
browserPort = getBrowserStreamPort(options.sessionId);
|
|
2796
|
+
if (!browserClose) {
|
|
2797
|
+
actualCommand = injectBrowserStreamPort(command, browserPort);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2365
2800
|
const canUseTmux = await shouldUseTmux();
|
|
2366
2801
|
if (background) {
|
|
2367
2802
|
if (!canUseTmux) {
|
|
@@ -2371,8 +2806,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2371
2806
|
};
|
|
2372
2807
|
}
|
|
2373
2808
|
const terminalId = generateTerminalId();
|
|
2374
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
2375
|
-
const result = await runBackground(
|
|
2809
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
2810
|
+
const result = await runBackground(actualCommand, options.workingDirectory, {
|
|
2376
2811
|
sessionId: options.sessionId,
|
|
2377
2812
|
terminalId
|
|
2378
2813
|
});
|
|
@@ -2385,16 +2820,22 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2385
2820
|
}
|
|
2386
2821
|
if (canUseTmux) {
|
|
2387
2822
|
const terminalId = generateTerminalId();
|
|
2388
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
2823
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
2389
2824
|
try {
|
|
2390
|
-
const result = await runSync(
|
|
2825
|
+
const result = await runSync(actualCommand, options.workingDirectory, {
|
|
2391
2826
|
sessionId: options.sessionId,
|
|
2392
2827
|
timeout: COMMAND_TIMEOUT,
|
|
2393
2828
|
terminalId
|
|
2394
2829
|
});
|
|
2395
2830
|
const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
|
|
2396
2831
|
options.onOutput?.(truncatedOutput);
|
|
2397
|
-
options.onProgress?.({
|
|
2832
|
+
options.onProgress?.({
|
|
2833
|
+
terminalId,
|
|
2834
|
+
status: "completed",
|
|
2835
|
+
command,
|
|
2836
|
+
browserStreamPort: browserPort,
|
|
2837
|
+
browserClosed: browserClose || void 0
|
|
2838
|
+
});
|
|
2398
2839
|
return {
|
|
2399
2840
|
success: result.exitCode === 0,
|
|
2400
2841
|
id: result.id,
|
|
@@ -2412,7 +2853,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2412
2853
|
};
|
|
2413
2854
|
}
|
|
2414
2855
|
} else {
|
|
2415
|
-
const result = await execFallback(
|
|
2856
|
+
const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
|
|
2416
2857
|
return {
|
|
2417
2858
|
success: result.success,
|
|
2418
2859
|
output: result.output,
|
|
@@ -2787,12 +3228,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2787
3228
|
}
|
|
2788
3229
|
async function commandExists(cmd) {
|
|
2789
3230
|
try {
|
|
2790
|
-
const { exec:
|
|
2791
|
-
const { promisify:
|
|
2792
|
-
const
|
|
3231
|
+
const { exec: exec7 } = await import("child_process");
|
|
3232
|
+
const { promisify: promisify7 } = await import("util");
|
|
3233
|
+
const execAsync7 = promisify7(exec7);
|
|
2793
3234
|
const isWindows = process.platform === "win32";
|
|
2794
3235
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2795
|
-
await
|
|
3236
|
+
await execAsync7(checkCmd);
|
|
2796
3237
|
return true;
|
|
2797
3238
|
} catch {
|
|
2798
3239
|
return false;
|
|
@@ -3090,7 +3531,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3090
3531
|
const startTime = Date.now();
|
|
3091
3532
|
let debounceTimer;
|
|
3092
3533
|
let resolved = false;
|
|
3093
|
-
const
|
|
3534
|
+
const cleanup2 = () => {
|
|
3094
3535
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
3095
3536
|
const listeners = diagnosticListeners.get(normalized);
|
|
3096
3537
|
if (listeners) {
|
|
@@ -3104,7 +3545,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3104
3545
|
const finish = () => {
|
|
3105
3546
|
if (resolved) return;
|
|
3106
3547
|
resolved = true;
|
|
3107
|
-
|
|
3548
|
+
cleanup2();
|
|
3108
3549
|
resolve11(diagnostics.get(normalized) || []);
|
|
3109
3550
|
};
|
|
3110
3551
|
const onDiagnostic = () => {
|
|
@@ -5209,8 +5650,106 @@ function createTaskFailedTool(options) {
|
|
|
5209
5650
|
});
|
|
5210
5651
|
}
|
|
5211
5652
|
|
|
5653
|
+
// src/tools/upload-file.ts
|
|
5654
|
+
import { tool as tool12 } from "ai";
|
|
5655
|
+
import { z as z13 } from "zod";
|
|
5656
|
+
import { readFile as readFile9, stat as stat4 } from "fs/promises";
|
|
5657
|
+
import { join as join5, basename as basename4, extname as extname7 } from "path";
|
|
5658
|
+
var MIME_TYPES = {
|
|
5659
|
+
".txt": "text/plain",
|
|
5660
|
+
".md": "text/markdown",
|
|
5661
|
+
".html": "text/html",
|
|
5662
|
+
".css": "text/css",
|
|
5663
|
+
".js": "application/javascript",
|
|
5664
|
+
".ts": "application/typescript",
|
|
5665
|
+
".json": "application/json",
|
|
5666
|
+
".csv": "text/csv",
|
|
5667
|
+
".xml": "application/xml",
|
|
5668
|
+
".pdf": "application/pdf",
|
|
5669
|
+
".png": "image/png",
|
|
5670
|
+
".jpg": "image/jpeg",
|
|
5671
|
+
".jpeg": "image/jpeg",
|
|
5672
|
+
".gif": "image/gif",
|
|
5673
|
+
".webp": "image/webp",
|
|
5674
|
+
".svg": "image/svg+xml",
|
|
5675
|
+
".mp4": "video/mp4",
|
|
5676
|
+
".webm": "video/webm",
|
|
5677
|
+
".mp3": "audio/mpeg",
|
|
5678
|
+
".wav": "audio/wav",
|
|
5679
|
+
".zip": "application/zip",
|
|
5680
|
+
".tar": "application/x-tar",
|
|
5681
|
+
".gz": "application/gzip"
|
|
5682
|
+
};
|
|
5683
|
+
function createUploadFileTool(options) {
|
|
5684
|
+
return tool12({
|
|
5685
|
+
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.`,
|
|
5686
|
+
inputSchema: z13.object({
|
|
5687
|
+
path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
|
|
5688
|
+
name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
|
|
5689
|
+
}),
|
|
5690
|
+
execute: async (input) => {
|
|
5691
|
+
try {
|
|
5692
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
5693
|
+
if (!isRemoteConfigured2()) {
|
|
5694
|
+
return {
|
|
5695
|
+
success: false,
|
|
5696
|
+
error: "File upload is not available \u2014 remote server with GCS is not configured."
|
|
5697
|
+
};
|
|
5698
|
+
}
|
|
5699
|
+
const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
|
|
5700
|
+
try {
|
|
5701
|
+
await stat4(fullPath);
|
|
5702
|
+
} catch {
|
|
5703
|
+
return {
|
|
5704
|
+
success: false,
|
|
5705
|
+
error: `File not found: ${input.path}`
|
|
5706
|
+
};
|
|
5707
|
+
}
|
|
5708
|
+
const fileName = input.name || basename4(fullPath);
|
|
5709
|
+
const ext = extname7(fullPath).toLowerCase();
|
|
5710
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
5711
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
5712
|
+
options.sessionId,
|
|
5713
|
+
fileName,
|
|
5714
|
+
contentType,
|
|
5715
|
+
"general"
|
|
5716
|
+
);
|
|
5717
|
+
const fileData = await readFile9(fullPath);
|
|
5718
|
+
const putRes = await fetch(uploadInfo.uploadUrl, {
|
|
5719
|
+
method: "PUT",
|
|
5720
|
+
headers: { "Content-Type": contentType },
|
|
5721
|
+
body: fileData
|
|
5722
|
+
});
|
|
5723
|
+
if (!putRes.ok) {
|
|
5724
|
+
return {
|
|
5725
|
+
success: false,
|
|
5726
|
+
error: `Upload failed: ${putRes.status} ${putRes.statusText}`
|
|
5727
|
+
};
|
|
5728
|
+
}
|
|
5729
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
5730
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
5731
|
+
return {
|
|
5732
|
+
success: true,
|
|
5733
|
+
fileId: uploadInfo.fileId,
|
|
5734
|
+
fileName,
|
|
5735
|
+
sizeBytes: fileData.length,
|
|
5736
|
+
contentType,
|
|
5737
|
+
downloadUrl: downloadInfo.downloadUrl,
|
|
5738
|
+
expiresAt: downloadInfo.expiresAt
|
|
5739
|
+
};
|
|
5740
|
+
} catch (err) {
|
|
5741
|
+
return {
|
|
5742
|
+
success: false,
|
|
5743
|
+
error: `Upload failed: ${err.message}`
|
|
5744
|
+
};
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
});
|
|
5748
|
+
}
|
|
5749
|
+
|
|
5212
5750
|
// src/tools/index.ts
|
|
5213
5751
|
init_semantic();
|
|
5752
|
+
init_remote();
|
|
5214
5753
|
init_semantic_search();
|
|
5215
5754
|
async function createTools(options) {
|
|
5216
5755
|
const tools = {
|
|
@@ -5248,6 +5787,12 @@ async function createTools(options) {
|
|
|
5248
5787
|
workingDirectory: options.workingDirectory
|
|
5249
5788
|
})
|
|
5250
5789
|
};
|
|
5790
|
+
if (isRemoteConfigured()) {
|
|
5791
|
+
tools.upload_file = createUploadFileTool({
|
|
5792
|
+
workingDirectory: options.workingDirectory,
|
|
5793
|
+
sessionId: options.sessionId
|
|
5794
|
+
});
|
|
5795
|
+
}
|
|
5251
5796
|
if (options.enableSemanticSearch !== false) {
|
|
5252
5797
|
try {
|
|
5253
5798
|
if (isVectorGatewayConfigured()) {
|
|
@@ -5342,6 +5887,7 @@ You have access to powerful tools for:
|
|
|
5342
5887
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
5343
5888
|
- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
|
|
5344
5889
|
- **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
|
|
5890
|
+
- **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
|
|
5345
5891
|
|
|
5346
5892
|
|
|
5347
5893
|
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.
|
|
@@ -5600,14 +6146,53 @@ function buildTaskPromptAddendum(outputSchema) {
|
|
|
5600
6146
|
## Task Mode
|
|
5601
6147
|
|
|
5602
6148
|
You are running in **task mode**. You have been given a specific task to complete autonomously.
|
|
6149
|
+
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.
|
|
6150
|
+
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.
|
|
5603
6151
|
|
|
5604
6152
|
### Rules
|
|
5605
6153
|
1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
|
|
5606
|
-
2. Keep working until the task is fully complete.
|
|
6154
|
+
2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
|
|
5607
6155
|
3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
5608
6156
|
4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
5609
6157
|
5. Do NOT stop without calling one of these two tools.
|
|
5610
6158
|
|
|
6159
|
+
### Verification \u2014 BE EXTREMELY THOROUGH
|
|
6160
|
+
Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
|
|
6161
|
+
|
|
6162
|
+
**After making code changes:**
|
|
6163
|
+
- Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
|
|
6164
|
+
- **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
|
|
6165
|
+
- If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
|
|
6166
|
+
- If you created new files, verify they exist and contain what you expect.
|
|
6167
|
+
|
|
6168
|
+
**For UI / web changes:**
|
|
6169
|
+
- Start the dev server if it isn't already running (it might be so double check ur context)
|
|
6170
|
+
- **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
|
|
6171
|
+
- Check the dev server logs for errors or warnings.
|
|
6172
|
+
- If the app crashes or shows errors, fix them before completing.
|
|
6173
|
+
|
|
6174
|
+
**For backend / API changes:**
|
|
6175
|
+
- Test the endpoint with curl or a quick script to confirm it works as expected.
|
|
6176
|
+
- Check server logs for errors.
|
|
6177
|
+
|
|
6178
|
+
**For search and exploration tasks:**
|
|
6179
|
+
- Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
|
|
6180
|
+
- Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
|
|
6181
|
+
- Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
|
|
6182
|
+
- Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
|
|
6183
|
+
|
|
6184
|
+
**General verification checklist:**
|
|
6185
|
+
- Re-read the original task prompt and confirm every requirement has been addressed.
|
|
6186
|
+
- If the task asked for multiple things, verify EACH one individually.
|
|
6187
|
+
- If something doesn't look right, fix it \u2014 don't complete with known issues.
|
|
6188
|
+
|
|
6189
|
+
### Use All Available Tools
|
|
6190
|
+
- **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
|
|
6191
|
+
- **explore_agent**: Use for codebase exploration and understanding before making changes.
|
|
6192
|
+
- **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
|
|
6193
|
+
- **todo**: Track your progress on multi-step tasks so you don't miss steps.
|
|
6194
|
+
- **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
|
|
6195
|
+
|
|
5611
6196
|
### Output Schema
|
|
5612
6197
|
The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
|
|
5613
6198
|
\`\`\`json
|
|
@@ -5615,7 +6200,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
5615
6200
|
\`\`\`
|
|
5616
6201
|
|
|
5617
6202
|
### Completion Tools
|
|
5618
|
-
- **\`complete_task({ result: ... })\`** \u2014 Call
|
|
6203
|
+
- **\`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.
|
|
5619
6204
|
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
5620
6205
|
`;
|
|
5621
6206
|
}
|
|
@@ -6123,11 +6708,29 @@ ${prompt}` });
|
|
|
6123
6708
|
const onComplete = (signal) => {
|
|
6124
6709
|
completion.signal = signal;
|
|
6125
6710
|
};
|
|
6711
|
+
let taskRecorder = null;
|
|
6712
|
+
const sessionId = this.session.id;
|
|
6713
|
+
const bashProgressHandler = (progress) => {
|
|
6714
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
6715
|
+
const port = progress.browserStreamPort;
|
|
6716
|
+
if (port && progress.status === "started") {
|
|
6717
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
6718
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
6719
|
+
if (!taskRecorder) {
|
|
6720
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
6721
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
6722
|
+
taskRecorder.start();
|
|
6723
|
+
proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
|
|
6724
|
+
});
|
|
6725
|
+
}
|
|
6726
|
+
});
|
|
6727
|
+
}
|
|
6728
|
+
};
|
|
6126
6729
|
const taskTools = await createTools({
|
|
6127
6730
|
sessionId: this.session.id,
|
|
6128
6731
|
workingDirectory: this.session.workingDirectory,
|
|
6129
6732
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
6130
|
-
onBashProgress:
|
|
6733
|
+
onBashProgress: bashProgressHandler,
|
|
6131
6734
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
6132
6735
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
|
|
6133
6736
|
taskTools: {
|
|
@@ -6198,12 +6801,24 @@ ${taskAddendum}`;
|
|
|
6198
6801
|
if (completion.signal) {
|
|
6199
6802
|
const sig = completion.signal;
|
|
6200
6803
|
const finalStatus = sig.status;
|
|
6804
|
+
let fileUrls;
|
|
6805
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
6806
|
+
const resultObj = sig.result;
|
|
6807
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
6808
|
+
if (filePaths.length > 0) {
|
|
6809
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6813
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
6201
6814
|
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
6202
6815
|
fireWebhook(eventType, {
|
|
6203
6816
|
status: finalStatus,
|
|
6204
6817
|
result: sig.result,
|
|
6205
6818
|
error: sig.error,
|
|
6206
|
-
iterations: iteration
|
|
6819
|
+
iterations: iteration,
|
|
6820
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
6821
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
6207
6822
|
});
|
|
6208
6823
|
const updatedTask2 = {
|
|
6209
6824
|
...options.taskConfig,
|
|
@@ -6223,11 +6838,17 @@ ${taskAddendum}`;
|
|
|
6223
6838
|
};
|
|
6224
6839
|
}
|
|
6225
6840
|
await this.context.addUserMessage(
|
|
6226
|
-
"Continue working on the task. When
|
|
6841
|
+
"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."
|
|
6227
6842
|
);
|
|
6228
6843
|
}
|
|
6229
6844
|
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
6230
|
-
|
|
6845
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6846
|
+
fireWebhook("task.failed", {
|
|
6847
|
+
status: "failed",
|
|
6848
|
+
error: timeoutError,
|
|
6849
|
+
iterations: iteration,
|
|
6850
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
6851
|
+
});
|
|
6231
6852
|
const updatedTask = {
|
|
6232
6853
|
...options.taskConfig,
|
|
6233
6854
|
status: "failed",
|
|
@@ -6239,6 +6860,113 @@ ${taskAddendum}`;
|
|
|
6239
6860
|
});
|
|
6240
6861
|
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
6241
6862
|
}
|
|
6863
|
+
/**
|
|
6864
|
+
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
6865
|
+
* Returns download URLs for any recordings produced.
|
|
6866
|
+
*/
|
|
6867
|
+
async finishTaskRecording(recorder) {
|
|
6868
|
+
try {
|
|
6869
|
+
const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
|
|
6870
|
+
destroyProxy2(this.session.id);
|
|
6871
|
+
} catch {
|
|
6872
|
+
}
|
|
6873
|
+
if (!recorder || recorder.frameCount === 0) {
|
|
6874
|
+
recorder?.clear();
|
|
6875
|
+
return [];
|
|
6876
|
+
}
|
|
6877
|
+
recorder.stop();
|
|
6878
|
+
try {
|
|
6879
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6880
|
+
if (!isRemoteConfigured2()) {
|
|
6881
|
+
recorder.clear();
|
|
6882
|
+
return [];
|
|
6883
|
+
}
|
|
6884
|
+
const result = await recorder.encode();
|
|
6885
|
+
recorder.clear();
|
|
6886
|
+
if (!result) return [];
|
|
6887
|
+
const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
|
|
6888
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6889
|
+
this.session.id,
|
|
6890
|
+
`browser-recording-${Date.now()}.mp4`,
|
|
6891
|
+
"video/mp4",
|
|
6892
|
+
"browser-recording"
|
|
6893
|
+
);
|
|
6894
|
+
const fileData = await readFile11(result.path);
|
|
6895
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6896
|
+
method: "PUT",
|
|
6897
|
+
headers: { "Content-Type": "video/mp4" },
|
|
6898
|
+
body: fileData
|
|
6899
|
+
});
|
|
6900
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
6901
|
+
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6902
|
+
await unlink3(result.path).catch(() => {
|
|
6903
|
+
});
|
|
6904
|
+
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
6905
|
+
return [dlInfo.downloadUrl];
|
|
6906
|
+
} catch (err) {
|
|
6907
|
+
console.error("[TASK] Failed to upload browser recording:", err.message);
|
|
6908
|
+
recorder.clear();
|
|
6909
|
+
return [];
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
/**
|
|
6913
|
+
* Upload task output files to GCS via the remote server.
|
|
6914
|
+
* Returns an array of download URLs for successfully uploaded files.
|
|
6915
|
+
*/
|
|
6916
|
+
async uploadTaskFiles(filePaths) {
|
|
6917
|
+
try {
|
|
6918
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6919
|
+
if (!isRemoteConfigured2()) return [];
|
|
6920
|
+
const { readFile: readFile11 } = await import("fs/promises");
|
|
6921
|
+
const { join: join11, basename: basename6 } = await import("path");
|
|
6922
|
+
const urls = [];
|
|
6923
|
+
for (const filePath of filePaths) {
|
|
6924
|
+
try {
|
|
6925
|
+
const fullPath = filePath.startsWith("/") ? filePath : join11(this.session.workingDirectory, filePath);
|
|
6926
|
+
const fileName = basename6(fullPath);
|
|
6927
|
+
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
6928
|
+
const mimeMap = {
|
|
6929
|
+
pdf: "application/pdf",
|
|
6930
|
+
json: "application/json",
|
|
6931
|
+
csv: "text/csv",
|
|
6932
|
+
txt: "text/plain",
|
|
6933
|
+
md: "text/markdown",
|
|
6934
|
+
html: "text/html",
|
|
6935
|
+
png: "image/png",
|
|
6936
|
+
jpg: "image/jpeg",
|
|
6937
|
+
jpeg: "image/jpeg",
|
|
6938
|
+
gif: "image/gif",
|
|
6939
|
+
svg: "image/svg+xml",
|
|
6940
|
+
mp4: "video/mp4",
|
|
6941
|
+
zip: "application/zip"
|
|
6942
|
+
};
|
|
6943
|
+
const contentType = mimeMap[ext] || "application/octet-stream";
|
|
6944
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6945
|
+
this.session.id,
|
|
6946
|
+
fileName,
|
|
6947
|
+
contentType,
|
|
6948
|
+
"task-output"
|
|
6949
|
+
);
|
|
6950
|
+
const fileData = await readFile11(fullPath);
|
|
6951
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6952
|
+
method: "PUT",
|
|
6953
|
+
headers: { "Content-Type": contentType },
|
|
6954
|
+
body: fileData
|
|
6955
|
+
});
|
|
6956
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
6957
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6958
|
+
urls.push(downloadInfo.downloadUrl);
|
|
6959
|
+
console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
|
|
6960
|
+
} catch (err) {
|
|
6961
|
+
console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
|
|
6962
|
+
}
|
|
6963
|
+
}
|
|
6964
|
+
return urls;
|
|
6965
|
+
} catch (err) {
|
|
6966
|
+
console.error("[TASK] File upload failed:", err.message);
|
|
6967
|
+
return [];
|
|
6968
|
+
}
|
|
6969
|
+
}
|
|
6242
6970
|
/**
|
|
6243
6971
|
* Wrap tools to add approval checking
|
|
6244
6972
|
*/
|
|
@@ -6252,11 +6980,11 @@ ${taskAddendum}`;
|
|
|
6252
6980
|
wrappedTools[name] = originalTool;
|
|
6253
6981
|
continue;
|
|
6254
6982
|
}
|
|
6255
|
-
wrappedTools[name] =
|
|
6983
|
+
wrappedTools[name] = tool13({
|
|
6256
6984
|
description: originalTool.description || "",
|
|
6257
|
-
inputSchema: originalTool.inputSchema ||
|
|
6985
|
+
inputSchema: originalTool.inputSchema || z14.object({}),
|
|
6258
6986
|
execute: async (input, toolOptions) => {
|
|
6259
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
6987
|
+
const toolCallId = toolOptions.toolCallId || nanoid4();
|
|
6260
6988
|
const execution = toolExecutionQueries.create({
|
|
6261
6989
|
sessionId: this.session.id,
|
|
6262
6990
|
toolName: name,
|
|
@@ -6274,10 +7002,10 @@ ${taskAddendum}`;
|
|
|
6274
7002
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
6275
7003
|
approvalResolvers.delete(toolCallId);
|
|
6276
7004
|
this.pendingApprovals.delete(toolCallId);
|
|
6277
|
-
const
|
|
7005
|
+
const exec7 = await execution;
|
|
6278
7006
|
if (!approved) {
|
|
6279
7007
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
6280
|
-
await toolExecutionQueries.reject(
|
|
7008
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
6281
7009
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
6282
7010
|
return {
|
|
6283
7011
|
status: "rejected",
|
|
@@ -6287,14 +7015,14 @@ ${taskAddendum}`;
|
|
|
6287
7015
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
6288
7016
|
};
|
|
6289
7017
|
}
|
|
6290
|
-
await toolExecutionQueries.approve(
|
|
7018
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
6291
7019
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
6292
7020
|
try {
|
|
6293
7021
|
const result = await originalTool.execute(input, toolOptions);
|
|
6294
|
-
await toolExecutionQueries.complete(
|
|
7022
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
6295
7023
|
return result;
|
|
6296
7024
|
} catch (error) {
|
|
6297
|
-
await toolExecutionQueries.complete(
|
|
7025
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
6298
7026
|
throw error;
|
|
6299
7027
|
}
|
|
6300
7028
|
}
|
|
@@ -6370,7 +7098,7 @@ import { serve } from "@hono/node-server";
|
|
|
6370
7098
|
import { cors } from "hono/cors";
|
|
6371
7099
|
import { logger } from "hono/logger";
|
|
6372
7100
|
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
6373
|
-
import { resolve as resolve10, dirname as dirname7, join as
|
|
7101
|
+
import { resolve as resolve10, dirname as dirname7, join as join10 } from "path";
|
|
6374
7102
|
import { spawn as spawn2 } from "child_process";
|
|
6375
7103
|
import { createServer as createNetServer } from "net";
|
|
6376
7104
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -6379,11 +7107,11 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
6379
7107
|
init_db();
|
|
6380
7108
|
import { Hono } from "hono";
|
|
6381
7109
|
import { zValidator } from "@hono/zod-validator";
|
|
6382
|
-
import { z as
|
|
7110
|
+
import { z as z15 } from "zod";
|
|
6383
7111
|
import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
|
|
6384
|
-
import { readdir as
|
|
6385
|
-
import { join as
|
|
6386
|
-
import { nanoid as
|
|
7112
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
7113
|
+
import { join as join7, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
7114
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
6387
7115
|
init_config();
|
|
6388
7116
|
|
|
6389
7117
|
// src/server/devtools-store.ts
|
|
@@ -6415,18 +7143,18 @@ function cleanupPendingInputs() {
|
|
|
6415
7143
|
}
|
|
6416
7144
|
}
|
|
6417
7145
|
}
|
|
6418
|
-
var createSessionSchema =
|
|
6419
|
-
name:
|
|
6420
|
-
workingDirectory:
|
|
6421
|
-
model:
|
|
6422
|
-
toolApprovals:
|
|
7146
|
+
var createSessionSchema = z15.object({
|
|
7147
|
+
name: z15.string().optional(),
|
|
7148
|
+
workingDirectory: z15.string().optional(),
|
|
7149
|
+
model: z15.string().optional(),
|
|
7150
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
6423
7151
|
});
|
|
6424
|
-
var paginationQuerySchema =
|
|
6425
|
-
limit:
|
|
6426
|
-
offset:
|
|
7152
|
+
var paginationQuerySchema = z15.object({
|
|
7153
|
+
limit: z15.string().optional(),
|
|
7154
|
+
offset: z15.string().optional()
|
|
6427
7155
|
});
|
|
6428
|
-
var messagesQuerySchema =
|
|
6429
|
-
limit:
|
|
7156
|
+
var messagesQuerySchema = z15.object({
|
|
7157
|
+
limit: z15.string().optional()
|
|
6430
7158
|
});
|
|
6431
7159
|
sessions.get(
|
|
6432
7160
|
"/",
|
|
@@ -6565,10 +7293,10 @@ sessions.get("/:id/tools", async (c) => {
|
|
|
6565
7293
|
count: executions.length
|
|
6566
7294
|
});
|
|
6567
7295
|
});
|
|
6568
|
-
var updateSessionSchema =
|
|
6569
|
-
model:
|
|
6570
|
-
name:
|
|
6571
|
-
toolApprovals:
|
|
7296
|
+
var updateSessionSchema = z15.object({
|
|
7297
|
+
model: z15.string().optional(),
|
|
7298
|
+
name: z15.string().optional(),
|
|
7299
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
6572
7300
|
});
|
|
6573
7301
|
sessions.patch(
|
|
6574
7302
|
"/:id",
|
|
@@ -6638,8 +7366,8 @@ sessions.post("/:id/clear", async (c) => {
|
|
|
6638
7366
|
await agent.clearContext();
|
|
6639
7367
|
return c.json({ success: true, sessionId: id });
|
|
6640
7368
|
});
|
|
6641
|
-
var pendingInputSchema =
|
|
6642
|
-
text:
|
|
7369
|
+
var pendingInputSchema = z15.object({
|
|
7370
|
+
text: z15.string()
|
|
6643
7371
|
});
|
|
6644
7372
|
sessions.post(
|
|
6645
7373
|
"/:id/pending-input",
|
|
@@ -6670,13 +7398,13 @@ sessions.get("/:id/pending-input", async (c) => {
|
|
|
6670
7398
|
createdAt: pending.createdAt.toISOString()
|
|
6671
7399
|
});
|
|
6672
7400
|
});
|
|
6673
|
-
var devtoolsContextSchema =
|
|
6674
|
-
url:
|
|
6675
|
-
path:
|
|
6676
|
-
pageName:
|
|
6677
|
-
screenWidth:
|
|
6678
|
-
screenHeight:
|
|
6679
|
-
devicePixelRatio:
|
|
7401
|
+
var devtoolsContextSchema = z15.object({
|
|
7402
|
+
url: z15.string(),
|
|
7403
|
+
path: z15.string(),
|
|
7404
|
+
pageName: z15.string().optional(),
|
|
7405
|
+
screenWidth: z15.number().optional(),
|
|
7406
|
+
screenHeight: z15.number().optional(),
|
|
7407
|
+
devicePixelRatio: z15.number().optional()
|
|
6680
7408
|
});
|
|
6681
7409
|
sessions.post(
|
|
6682
7410
|
"/:id/devtools-context",
|
|
@@ -6844,7 +7572,7 @@ sessions.get("/:id/diff/:filePath", async (c) => {
|
|
|
6844
7572
|
});
|
|
6845
7573
|
function getAttachmentsDir(sessionId) {
|
|
6846
7574
|
const appDataDir = getAppDataDirectory();
|
|
6847
|
-
return
|
|
7575
|
+
return join7(appDataDir, "attachments", sessionId);
|
|
6848
7576
|
}
|
|
6849
7577
|
function ensureAttachmentsDir(sessionId) {
|
|
6850
7578
|
const dir = getAttachmentsDir(sessionId);
|
|
@@ -6865,7 +7593,7 @@ sessions.get("/:id/attachments", async (c) => {
|
|
|
6865
7593
|
}
|
|
6866
7594
|
const files = readdirSync(dir);
|
|
6867
7595
|
const attachments = files.map((filename) => {
|
|
6868
|
-
const filePath =
|
|
7596
|
+
const filePath = join7(dir, filename);
|
|
6869
7597
|
const stats = statSync2(filePath);
|
|
6870
7598
|
return {
|
|
6871
7599
|
id: filename.split("_")[0],
|
|
@@ -6897,10 +7625,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
6897
7625
|
return c.json({ error: "No file provided" }, 400);
|
|
6898
7626
|
}
|
|
6899
7627
|
const dir = ensureAttachmentsDir(sessionId);
|
|
6900
|
-
const id =
|
|
6901
|
-
const ext =
|
|
6902
|
-
const safeFilename = `${id}_${
|
|
6903
|
-
const filePath =
|
|
7628
|
+
const id = nanoid5(10);
|
|
7629
|
+
const ext = extname8(file.name) || "";
|
|
7630
|
+
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7631
|
+
const filePath = join7(dir, safeFilename);
|
|
6904
7632
|
const arrayBuffer = await file.arrayBuffer();
|
|
6905
7633
|
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
6906
7634
|
return c.json({
|
|
@@ -6923,10 +7651,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
6923
7651
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
6924
7652
|
}
|
|
6925
7653
|
const dir = ensureAttachmentsDir(sessionId);
|
|
6926
|
-
const id =
|
|
6927
|
-
const ext =
|
|
6928
|
-
const safeFilename = `${id}_${
|
|
6929
|
-
const filePath =
|
|
7654
|
+
const id = nanoid5(10);
|
|
7655
|
+
const ext = extname8(body.filename) || "";
|
|
7656
|
+
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7657
|
+
const filePath = join7(dir, safeFilename);
|
|
6930
7658
|
let base64Data = body.data;
|
|
6931
7659
|
if (base64Data.includes(",")) {
|
|
6932
7660
|
base64Data = base64Data.split(",")[1];
|
|
@@ -6963,14 +7691,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
6963
7691
|
if (!file) {
|
|
6964
7692
|
return c.json({ error: "Attachment not found" }, 404);
|
|
6965
7693
|
}
|
|
6966
|
-
const filePath =
|
|
7694
|
+
const filePath = join7(dir, file);
|
|
6967
7695
|
unlinkSync(filePath);
|
|
6968
7696
|
return c.json({ success: true, id: attachmentId });
|
|
6969
7697
|
});
|
|
6970
|
-
var filesQuerySchema =
|
|
6971
|
-
query:
|
|
7698
|
+
var filesQuerySchema = z15.object({
|
|
7699
|
+
query: z15.string().optional(),
|
|
6972
7700
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
6973
|
-
limit:
|
|
7701
|
+
limit: z15.string().optional()
|
|
6974
7702
|
// Max results (default 50)
|
|
6975
7703
|
});
|
|
6976
7704
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -7043,10 +7771,10 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7043
7771
|
return results;
|
|
7044
7772
|
}
|
|
7045
7773
|
try {
|
|
7046
|
-
const entries = await
|
|
7774
|
+
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
7047
7775
|
for (const entry of entries) {
|
|
7048
7776
|
if (results.length >= limit * 2) break;
|
|
7049
|
-
const fullPath =
|
|
7777
|
+
const fullPath = join7(currentDir, entry.name);
|
|
7050
7778
|
const relativePath = relative9(baseDir, fullPath);
|
|
7051
7779
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
7052
7780
|
continue;
|
|
@@ -7054,7 +7782,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7054
7782
|
if (entry.name.startsWith(".")) {
|
|
7055
7783
|
continue;
|
|
7056
7784
|
}
|
|
7057
|
-
const ext =
|
|
7785
|
+
const ext = extname8(entry.name).toLowerCase();
|
|
7058
7786
|
if (IGNORED_EXTENSIONS.has(ext)) {
|
|
7059
7787
|
continue;
|
|
7060
7788
|
}
|
|
@@ -7143,14 +7871,70 @@ sessions.get(
|
|
|
7143
7871
|
}
|
|
7144
7872
|
}
|
|
7145
7873
|
);
|
|
7874
|
+
sessions.get("/:id/session-files", async (c) => {
|
|
7875
|
+
const sessionId = c.req.param("id");
|
|
7876
|
+
try {
|
|
7877
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7878
|
+
if (!isRemoteConfigured2()) {
|
|
7879
|
+
return c.json({ files: [] });
|
|
7880
|
+
}
|
|
7881
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7882
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
7883
|
+
return c.json({ sessionId, files });
|
|
7884
|
+
} catch (err) {
|
|
7885
|
+
console.error("Failed to get session files:", err.message);
|
|
7886
|
+
return c.json({ sessionId, files: [] });
|
|
7887
|
+
}
|
|
7888
|
+
});
|
|
7889
|
+
sessions.get("/files/:fileId/download", async (c) => {
|
|
7890
|
+
const fileId = c.req.param("fileId");
|
|
7891
|
+
try {
|
|
7892
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7893
|
+
if (!isRemoteConfigured2()) {
|
|
7894
|
+
return c.json({ error: "Remote server not configured" }, 503);
|
|
7895
|
+
}
|
|
7896
|
+
const result = await storageQueries2.getDownloadUrl(fileId);
|
|
7897
|
+
return c.json(result);
|
|
7898
|
+
} catch (err) {
|
|
7899
|
+
return c.json({ error: err.message }, 500);
|
|
7900
|
+
}
|
|
7901
|
+
});
|
|
7902
|
+
sessions.get("/:id/browser-recording", async (c) => {
|
|
7903
|
+
const sessionId = c.req.param("id");
|
|
7904
|
+
try {
|
|
7905
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7906
|
+
if (!isRemoteConfigured2()) {
|
|
7907
|
+
return c.json({ sessionId, recordings: [] });
|
|
7908
|
+
}
|
|
7909
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
7910
|
+
const recordings = files.filter((f) => f.category === "browser-recording");
|
|
7911
|
+
if (recordings.length === 0) {
|
|
7912
|
+
return c.json({ sessionId, recordings: [], message: "No browser recordings for this session" });
|
|
7913
|
+
}
|
|
7914
|
+
return c.json({
|
|
7915
|
+
sessionId,
|
|
7916
|
+
recordings: recordings.map((r) => ({
|
|
7917
|
+
id: r.id,
|
|
7918
|
+
fileName: r.fileName,
|
|
7919
|
+
sizeBytes: r.sizeBytes,
|
|
7920
|
+
createdAt: r.createdAt,
|
|
7921
|
+
downloadUrl: r.downloadUrl,
|
|
7922
|
+
expiresAt: r.downloadUrlExpiresAt
|
|
7923
|
+
}))
|
|
7924
|
+
});
|
|
7925
|
+
} catch (err) {
|
|
7926
|
+
console.error("Failed to get browser recordings:", err.message);
|
|
7927
|
+
return c.json({ sessionId, recordings: [], error: err.message });
|
|
7928
|
+
}
|
|
7929
|
+
});
|
|
7146
7930
|
|
|
7147
7931
|
// src/server/routes/agents.ts
|
|
7148
7932
|
init_db();
|
|
7149
7933
|
import { Hono as Hono2 } from "hono";
|
|
7150
7934
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
7151
|
-
import { z as
|
|
7935
|
+
import { z as z16 } from "zod";
|
|
7152
7936
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
7153
|
-
import { join as
|
|
7937
|
+
import { join as join8 } from "path";
|
|
7154
7938
|
init_config();
|
|
7155
7939
|
|
|
7156
7940
|
// src/server/resumable-stream.ts
|
|
@@ -7226,7 +8010,11 @@ var streamContext = createResumableStreamContext({
|
|
|
7226
8010
|
});
|
|
7227
8011
|
|
|
7228
8012
|
// src/server/routes/agents.ts
|
|
7229
|
-
import { nanoid as
|
|
8013
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
8014
|
+
init_stream_proxy();
|
|
8015
|
+
init_recorder();
|
|
8016
|
+
init_remote();
|
|
8017
|
+
var sessionRecorders = /* @__PURE__ */ new Map();
|
|
7230
8018
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
7231
8019
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
7232
8020
|
var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
|
|
@@ -7313,35 +8101,35 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
7313
8101
|
${prompt}`;
|
|
7314
8102
|
}
|
|
7315
8103
|
var agents = new Hono2();
|
|
7316
|
-
var attachmentSchema =
|
|
7317
|
-
type:
|
|
7318
|
-
data:
|
|
8104
|
+
var attachmentSchema = z16.object({
|
|
8105
|
+
type: z16.enum(["image", "file"]),
|
|
8106
|
+
data: z16.string(),
|
|
7319
8107
|
// base64 data URL or raw base64
|
|
7320
|
-
mediaType:
|
|
7321
|
-
filename:
|
|
8108
|
+
mediaType: z16.string().optional(),
|
|
8109
|
+
filename: z16.string().optional()
|
|
7322
8110
|
});
|
|
7323
|
-
var runPromptSchema =
|
|
7324
|
-
prompt:
|
|
8111
|
+
var runPromptSchema = z16.object({
|
|
8112
|
+
prompt: z16.string(),
|
|
7325
8113
|
// Can be empty if attachments are provided
|
|
7326
|
-
attachments:
|
|
8114
|
+
attachments: z16.array(attachmentSchema).optional()
|
|
7327
8115
|
}).refine(
|
|
7328
8116
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
7329
8117
|
{ message: "Either prompt or attachments must be provided" }
|
|
7330
8118
|
);
|
|
7331
|
-
var quickStartSchema =
|
|
7332
|
-
prompt:
|
|
7333
|
-
name:
|
|
7334
|
-
workingDirectory:
|
|
7335
|
-
model:
|
|
7336
|
-
toolApprovals:
|
|
8119
|
+
var quickStartSchema = z16.object({
|
|
8120
|
+
prompt: z16.string().min(1),
|
|
8121
|
+
name: z16.string().optional(),
|
|
8122
|
+
workingDirectory: z16.string().optional(),
|
|
8123
|
+
model: z16.string().optional(),
|
|
8124
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
7337
8125
|
});
|
|
7338
|
-
var rejectSchema =
|
|
7339
|
-
reason:
|
|
8126
|
+
var rejectSchema = z16.object({
|
|
8127
|
+
reason: z16.string().optional()
|
|
7340
8128
|
}).optional();
|
|
7341
8129
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
7342
8130
|
function getAttachmentsDirectory(sessionId) {
|
|
7343
8131
|
const appDataDir = getAppDataDirectory();
|
|
7344
|
-
return
|
|
8132
|
+
return join8(appDataDir, "attachments", sessionId);
|
|
7345
8133
|
}
|
|
7346
8134
|
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
7347
8135
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
@@ -7357,7 +8145,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
7357
8145
|
if (base64Data.includes(",")) {
|
|
7358
8146
|
base64Data = base64Data.split(",")[1];
|
|
7359
8147
|
}
|
|
7360
|
-
const filePath =
|
|
8148
|
+
const filePath = join8(attachmentsDir, filename);
|
|
7361
8149
|
const buffer = Buffer.from(base64Data, "base64");
|
|
7362
8150
|
writeFileSync3(filePath, buffer);
|
|
7363
8151
|
return filePath;
|
|
@@ -7525,6 +8313,40 @@ ${prompt}` });
|
|
|
7525
8313
|
}));
|
|
7526
8314
|
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7527
8315
|
}
|
|
8316
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
8317
|
+
const browserClosed = progress.data?.browserClosed;
|
|
8318
|
+
if (progress.toolName === "bash" && browserClosed) {
|
|
8319
|
+
console.log(`[BROWSER-STREAM] agent-browser close detected, destroying proxy`);
|
|
8320
|
+
destroyProxy(sessionId);
|
|
8321
|
+
} else if (progress.toolName === "bash" && browserPort) {
|
|
8322
|
+
console.log(`[BROWSER-STREAM] agent-browser command detected, ensuring proxy on port ${browserPort}`);
|
|
8323
|
+
const proxy = getOrCreateProxy(sessionId, browserPort);
|
|
8324
|
+
if (!sessionRecorders.has(sessionId)) {
|
|
8325
|
+
const recorder = new FrameRecorder(sessionId);
|
|
8326
|
+
recorder.start();
|
|
8327
|
+
sessionRecorders.set(sessionId, recorder);
|
|
8328
|
+
}
|
|
8329
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
8330
|
+
proxy.on("frame", (frame) => {
|
|
8331
|
+
const rec = sessionRecorders.get(sessionId);
|
|
8332
|
+
rec?.addFrame(frame);
|
|
8333
|
+
writeSSE(JSON.stringify({
|
|
8334
|
+
type: "browser-frame",
|
|
8335
|
+
data: frame.data,
|
|
8336
|
+
metadata: frame.metadata
|
|
8337
|
+
})).catch(() => {
|
|
8338
|
+
});
|
|
8339
|
+
});
|
|
8340
|
+
proxy.on("status", (s) => {
|
|
8341
|
+
console.log(`[BROWSER-STREAM] Status:`, s);
|
|
8342
|
+
writeSSE(JSON.stringify({
|
|
8343
|
+
type: "browser-status",
|
|
8344
|
+
...s
|
|
8345
|
+
})).catch(() => {
|
|
8346
|
+
});
|
|
8347
|
+
});
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
7528
8350
|
},
|
|
7529
8351
|
onStepFinish: async () => {
|
|
7530
8352
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -7711,7 +8533,7 @@ ${prompt}` });
|
|
|
7711
8533
|
userMessageContent = prompt;
|
|
7712
8534
|
}
|
|
7713
8535
|
await messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
7714
|
-
const streamId = `stream_${id}_${
|
|
8536
|
+
const streamId = `stream_${id}_${nanoid6(10)}`;
|
|
7715
8537
|
await activeStreamQueries.create(id, streamId);
|
|
7716
8538
|
const stream = await streamContext.resumableStream(
|
|
7717
8539
|
streamId,
|
|
@@ -7910,7 +8732,7 @@ agents.post(
|
|
|
7910
8732
|
});
|
|
7911
8733
|
const session = agent.getSession();
|
|
7912
8734
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
7913
|
-
const streamId = `stream_${session.id}_${
|
|
8735
|
+
const streamId = `stream_${session.id}_${nanoid6(10)}`;
|
|
7914
8736
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
7915
8737
|
await activeStreamQueries.create(session.id, streamId);
|
|
7916
8738
|
const createQuickStreamProducer = () => {
|
|
@@ -7989,6 +8811,40 @@ agents.post(
|
|
|
7989
8811
|
}));
|
|
7990
8812
|
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7991
8813
|
}
|
|
8814
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
8815
|
+
const browserClosed = progress.data?.browserClosed;
|
|
8816
|
+
if (progress.toolName === "bash" && browserClosed) {
|
|
8817
|
+
console.log(`[BROWSER-STREAM] agent-browser close detected`);
|
|
8818
|
+
destroyProxy(session.id);
|
|
8819
|
+
} else if (progress.toolName === "bash" && browserPort) {
|
|
8820
|
+
console.log(`[BROWSER-STREAM] agent-browser command detected, port ${browserPort}`);
|
|
8821
|
+
const proxy = getOrCreateProxy(session.id, browserPort);
|
|
8822
|
+
if (!sessionRecorders.has(session.id)) {
|
|
8823
|
+
const recorder = new FrameRecorder(session.id);
|
|
8824
|
+
recorder.start();
|
|
8825
|
+
sessionRecorders.set(session.id, recorder);
|
|
8826
|
+
}
|
|
8827
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
8828
|
+
proxy.on("frame", (frame) => {
|
|
8829
|
+
const rec = sessionRecorders.get(session.id);
|
|
8830
|
+
rec?.addFrame(frame);
|
|
8831
|
+
writeSSE(JSON.stringify({
|
|
8832
|
+
type: "browser-frame",
|
|
8833
|
+
data: frame.data,
|
|
8834
|
+
metadata: frame.metadata
|
|
8835
|
+
})).catch(() => {
|
|
8836
|
+
});
|
|
8837
|
+
});
|
|
8838
|
+
proxy.on("status", (s) => {
|
|
8839
|
+
console.log(`[BROWSER-STREAM] Status:`, s);
|
|
8840
|
+
writeSSE(JSON.stringify({
|
|
8841
|
+
type: "browser-status",
|
|
8842
|
+
...s
|
|
8843
|
+
})).catch(() => {
|
|
8844
|
+
});
|
|
8845
|
+
});
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
7992
8848
|
},
|
|
7993
8849
|
onStepFinish: async () => {
|
|
7994
8850
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -8119,25 +8975,71 @@ agents.post(
|
|
|
8119
8975
|
});
|
|
8120
8976
|
}
|
|
8121
8977
|
);
|
|
8978
|
+
var browserInputSchema = z16.object({
|
|
8979
|
+
type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
8980
|
+
eventType: z16.string(),
|
|
8981
|
+
x: z16.number().optional(),
|
|
8982
|
+
y: z16.number().optional(),
|
|
8983
|
+
button: z16.string().optional(),
|
|
8984
|
+
clickCount: z16.number().optional(),
|
|
8985
|
+
deltaX: z16.number().optional(),
|
|
8986
|
+
deltaY: z16.number().optional(),
|
|
8987
|
+
key: z16.string().optional(),
|
|
8988
|
+
code: z16.string().optional(),
|
|
8989
|
+
text: z16.string().optional(),
|
|
8990
|
+
modifiers: z16.number().optional(),
|
|
8991
|
+
touchPoints: z16.array(z16.object({
|
|
8992
|
+
x: z16.number(),
|
|
8993
|
+
y: z16.number(),
|
|
8994
|
+
id: z16.number().optional()
|
|
8995
|
+
})).optional()
|
|
8996
|
+
});
|
|
8997
|
+
agents.post(
|
|
8998
|
+
"/:id/browser-input",
|
|
8999
|
+
zValidator2("json", browserInputSchema),
|
|
9000
|
+
async (c) => {
|
|
9001
|
+
const sessionId = c.req.param("id");
|
|
9002
|
+
const event = c.req.valid("json");
|
|
9003
|
+
const proxy = getProxy(sessionId);
|
|
9004
|
+
if (!proxy || !proxy.connected) {
|
|
9005
|
+
return c.json({ error: "No active browser stream for this session" }, 404);
|
|
9006
|
+
}
|
|
9007
|
+
proxy.injectInput(event);
|
|
9008
|
+
return c.json({ success: true });
|
|
9009
|
+
}
|
|
9010
|
+
);
|
|
9011
|
+
agents.get("/:id/browser-stream", async (c) => {
|
|
9012
|
+
const sessionId = c.req.param("id");
|
|
9013
|
+
const proxy = getProxy(sessionId);
|
|
9014
|
+
return c.json({
|
|
9015
|
+
sessionId,
|
|
9016
|
+
active: !!proxy?.connected,
|
|
9017
|
+
hasProxy: !!proxy,
|
|
9018
|
+
latestFrame: proxy?.latestFrame ? {
|
|
9019
|
+
metadata: proxy.latestFrame.metadata,
|
|
9020
|
+
timestamp: proxy.latestFrame.timestamp
|
|
9021
|
+
} : null
|
|
9022
|
+
});
|
|
9023
|
+
});
|
|
8122
9024
|
|
|
8123
9025
|
// src/server/routes/health.ts
|
|
8124
9026
|
init_config();
|
|
8125
9027
|
import { Hono as Hono3 } from "hono";
|
|
8126
9028
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
8127
|
-
import { z as
|
|
9029
|
+
import { z as z17 } from "zod";
|
|
8128
9030
|
import { readFileSync as readFileSync5 } from "fs";
|
|
8129
9031
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8130
|
-
import { dirname as dirname6, join as
|
|
9032
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
8131
9033
|
var __filename = fileURLToPath3(import.meta.url);
|
|
8132
9034
|
var __dirname = dirname6(__filename);
|
|
8133
9035
|
var possiblePaths = [
|
|
8134
|
-
|
|
9036
|
+
join9(__dirname, "../package.json"),
|
|
8135
9037
|
// From dist/server -> dist/../package.json
|
|
8136
|
-
|
|
9038
|
+
join9(__dirname, "../../package.json"),
|
|
8137
9039
|
// From dist/server (if nested differently)
|
|
8138
|
-
|
|
9040
|
+
join9(__dirname, "../../../package.json"),
|
|
8139
9041
|
// From src/server/routes (development)
|
|
8140
|
-
|
|
9042
|
+
join9(process.cwd(), "package.json")
|
|
8141
9043
|
// From current working directory
|
|
8142
9044
|
];
|
|
8143
9045
|
var currentVersion = "0.0.0";
|
|
@@ -8234,9 +9136,9 @@ health.get("/api-keys", async (c) => {
|
|
|
8234
9136
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
8235
9137
|
});
|
|
8236
9138
|
});
|
|
8237
|
-
var setApiKeySchema =
|
|
8238
|
-
provider:
|
|
8239
|
-
apiKey:
|
|
9139
|
+
var setApiKeySchema = z17.object({
|
|
9140
|
+
provider: z17.string(),
|
|
9141
|
+
apiKey: z17.string().min(1)
|
|
8240
9142
|
});
|
|
8241
9143
|
health.post(
|
|
8242
9144
|
"/api-keys",
|
|
@@ -8275,13 +9177,13 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
8275
9177
|
// src/server/routes/terminals.ts
|
|
8276
9178
|
import { Hono as Hono4 } from "hono";
|
|
8277
9179
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
8278
|
-
import { z as
|
|
9180
|
+
import { z as z18 } from "zod";
|
|
8279
9181
|
init_db();
|
|
8280
9182
|
var terminals = new Hono4();
|
|
8281
|
-
var spawnSchema =
|
|
8282
|
-
command:
|
|
8283
|
-
cwd:
|
|
8284
|
-
name:
|
|
9183
|
+
var spawnSchema = z18.object({
|
|
9184
|
+
command: z18.string(),
|
|
9185
|
+
cwd: z18.string().optional(),
|
|
9186
|
+
name: z18.string().optional()
|
|
8285
9187
|
});
|
|
8286
9188
|
terminals.post(
|
|
8287
9189
|
"/:sessionId/terminals",
|
|
@@ -8362,8 +9264,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
8362
9264
|
// We don't track exit codes in tmux mode
|
|
8363
9265
|
});
|
|
8364
9266
|
});
|
|
8365
|
-
var logsQuerySchema =
|
|
8366
|
-
tail:
|
|
9267
|
+
var logsQuerySchema = z18.object({
|
|
9268
|
+
tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
8367
9269
|
});
|
|
8368
9270
|
terminals.get(
|
|
8369
9271
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -8387,8 +9289,8 @@ terminals.get(
|
|
|
8387
9289
|
});
|
|
8388
9290
|
}
|
|
8389
9291
|
);
|
|
8390
|
-
var killSchema =
|
|
8391
|
-
signal:
|
|
9292
|
+
var killSchema = z18.object({
|
|
9293
|
+
signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
8392
9294
|
});
|
|
8393
9295
|
terminals.post(
|
|
8394
9296
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -8402,8 +9304,8 @@ terminals.post(
|
|
|
8402
9304
|
return c.json({ success: true, message: "Terminal killed" });
|
|
8403
9305
|
}
|
|
8404
9306
|
);
|
|
8405
|
-
var writeSchema =
|
|
8406
|
-
input:
|
|
9307
|
+
var writeSchema = z18.object({
|
|
9308
|
+
input: z18.string()
|
|
8407
9309
|
});
|
|
8408
9310
|
terminals.post(
|
|
8409
9311
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -8588,18 +9490,18 @@ data: ${JSON.stringify({ status: "stopped" })}
|
|
|
8588
9490
|
init_db();
|
|
8589
9491
|
import { Hono as Hono5 } from "hono";
|
|
8590
9492
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
8591
|
-
import { z as
|
|
9493
|
+
import { z as z19 } from "zod";
|
|
8592
9494
|
init_config();
|
|
8593
9495
|
var tasks = new Hono5();
|
|
8594
9496
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
8595
|
-
var createTaskSchema =
|
|
8596
|
-
prompt:
|
|
8597
|
-
outputSchema:
|
|
8598
|
-
webhookUrl:
|
|
8599
|
-
model:
|
|
8600
|
-
workingDirectory:
|
|
8601
|
-
name:
|
|
8602
|
-
maxIterations:
|
|
9497
|
+
var createTaskSchema = z19.object({
|
|
9498
|
+
prompt: z19.string().min(1),
|
|
9499
|
+
outputSchema: z19.record(z19.string(), z19.unknown()),
|
|
9500
|
+
webhookUrl: z19.string().url().optional(),
|
|
9501
|
+
model: z19.string().optional(),
|
|
9502
|
+
workingDirectory: z19.string().optional(),
|
|
9503
|
+
name: z19.string().optional(),
|
|
9504
|
+
maxIterations: z19.number().int().min(1).max(500).optional()
|
|
8603
9505
|
});
|
|
8604
9506
|
tasks.post(
|
|
8605
9507
|
"/",
|
|
@@ -8678,6 +9580,15 @@ tasks.get("/:id", async (c) => {
|
|
|
8678
9580
|
if (!task?.enabled) {
|
|
8679
9581
|
return c.json({ error: "Session is not a task" }, 400);
|
|
8680
9582
|
}
|
|
9583
|
+
let browserRecordings = [];
|
|
9584
|
+
try {
|
|
9585
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
9586
|
+
if (isRemoteConfigured2()) {
|
|
9587
|
+
const files = await storageQueries2.getSessionFiles(id);
|
|
9588
|
+
browserRecordings = files.filter((f) => f.category === "browser-recording").map((f) => ({ fileName: f.fileName, downloadUrl: f.downloadUrl, sizeBytes: f.sizeBytes }));
|
|
9589
|
+
}
|
|
9590
|
+
} catch {
|
|
9591
|
+
}
|
|
8681
9592
|
return c.json({
|
|
8682
9593
|
taskId: id,
|
|
8683
9594
|
status: task.status,
|
|
@@ -8687,7 +9598,8 @@ tasks.get("/:id", async (c) => {
|
|
|
8687
9598
|
model: session.model,
|
|
8688
9599
|
name: session.name,
|
|
8689
9600
|
createdAt: session.createdAt.toISOString(),
|
|
8690
|
-
updatedAt: session.updatedAt.toISOString()
|
|
9601
|
+
updatedAt: session.updatedAt.toISOString(),
|
|
9602
|
+
browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
|
|
8691
9603
|
});
|
|
8692
9604
|
});
|
|
8693
9605
|
tasks.post("/:id/cancel", async (c) => {
|
|
@@ -8735,10 +9647,10 @@ init_config();
|
|
|
8735
9647
|
init_db();
|
|
8736
9648
|
|
|
8737
9649
|
// src/utils/dependencies.ts
|
|
8738
|
-
import { exec as
|
|
8739
|
-
import { promisify as
|
|
9650
|
+
import { exec as exec6 } from "child_process";
|
|
9651
|
+
import { promisify as promisify6 } from "util";
|
|
8740
9652
|
import { platform as platform2 } from "os";
|
|
8741
|
-
var
|
|
9653
|
+
var execAsync6 = promisify6(exec6);
|
|
8742
9654
|
function getInstallInstructions() {
|
|
8743
9655
|
const os2 = platform2();
|
|
8744
9656
|
if (os2 === "darwin") {
|
|
@@ -8771,7 +9683,7 @@ Install tmux:
|
|
|
8771
9683
|
}
|
|
8772
9684
|
async function checkTmux() {
|
|
8773
9685
|
try {
|
|
8774
|
-
const { stdout } = await
|
|
9686
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
8775
9687
|
const version = stdout.trim();
|
|
8776
9688
|
return {
|
|
8777
9689
|
available: true,
|
|
@@ -8820,11 +9732,11 @@ function getWebDirectory() {
|
|
|
8820
9732
|
try {
|
|
8821
9733
|
const currentDir = dirname7(fileURLToPath4(import.meta.url));
|
|
8822
9734
|
const webDir = resolve10(currentDir, "..", "web");
|
|
8823
|
-
if (existsSync15(webDir) && existsSync15(
|
|
9735
|
+
if (existsSync15(webDir) && existsSync15(join10(webDir, "package.json"))) {
|
|
8824
9736
|
return webDir;
|
|
8825
9737
|
}
|
|
8826
9738
|
const altWebDir = resolve10(currentDir, "..", "..", "web");
|
|
8827
|
-
if (existsSync15(altWebDir) && existsSync15(
|
|
9739
|
+
if (existsSync15(altWebDir) && existsSync15(join10(altWebDir, "package.json"))) {
|
|
8828
9740
|
return altWebDir;
|
|
8829
9741
|
}
|
|
8830
9742
|
return null;
|
|
@@ -8882,20 +9794,20 @@ async function findWebPort(preferredPort) {
|
|
|
8882
9794
|
return { port: preferredPort, alreadyRunning: false };
|
|
8883
9795
|
}
|
|
8884
9796
|
function hasProductionBuild(webDir) {
|
|
8885
|
-
const buildIdPath =
|
|
9797
|
+
const buildIdPath = join10(webDir, ".next", "BUILD_ID");
|
|
8886
9798
|
return existsSync15(buildIdPath);
|
|
8887
9799
|
}
|
|
8888
9800
|
function hasSourceFiles(webDir) {
|
|
8889
|
-
const appDir =
|
|
8890
|
-
const pagesDir =
|
|
8891
|
-
const rootAppDir =
|
|
8892
|
-
const rootPagesDir =
|
|
9801
|
+
const appDir = join10(webDir, "src", "app");
|
|
9802
|
+
const pagesDir = join10(webDir, "src", "pages");
|
|
9803
|
+
const rootAppDir = join10(webDir, "app");
|
|
9804
|
+
const rootPagesDir = join10(webDir, "pages");
|
|
8893
9805
|
return existsSync15(appDir) || existsSync15(pagesDir) || existsSync15(rootAppDir) || existsSync15(rootPagesDir);
|
|
8894
9806
|
}
|
|
8895
9807
|
function getStandaloneServerPath(webDir) {
|
|
8896
9808
|
const possiblePaths2 = [
|
|
8897
|
-
|
|
8898
|
-
|
|
9809
|
+
join10(webDir, ".next", "standalone", "server.js"),
|
|
9810
|
+
join10(webDir, ".next", "standalone", "web", "server.js")
|
|
8899
9811
|
];
|
|
8900
9812
|
for (const serverPath of possiblePaths2) {
|
|
8901
9813
|
if (existsSync15(serverPath)) {
|
|
@@ -8938,13 +9850,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
8938
9850
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
8939
9851
|
return { process: null, port: actualPort };
|
|
8940
9852
|
}
|
|
8941
|
-
const usePnpm = existsSync15(
|
|
8942
|
-
const useNpm = !usePnpm && existsSync15(
|
|
9853
|
+
const usePnpm = existsSync15(join10(webDir, "pnpm-lock.yaml"));
|
|
9854
|
+
const useNpm = !usePnpm && existsSync15(join10(webDir, "package-lock.json"));
|
|
8943
9855
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
8944
9856
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
8945
9857
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
8946
9858
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
8947
|
-
const runtimeConfigPath =
|
|
9859
|
+
const runtimeConfigPath = join10(webDir, "runtime-config.json");
|
|
8948
9860
|
try {
|
|
8949
9861
|
writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
8950
9862
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|