sparkecoder 0.1.64 → 0.1.65
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 +2 -2
- package/dist/agent/index.js +812 -30
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +1064 -165
- package/dist/cli.js.map +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/{index-Dn-eCGLe.d.ts → index-BhKQYdOx.d.ts} +11 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1080 -146
- package/dist/index.js.map +1 -1
- package/dist/{search-DINnDTgj.d.ts → search-DkNNtC5A.d.ts} +1 -0
- package/dist/server/index.js +1080 -146
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/qa.md +317 -0
- package/dist/tools/index.d.ts +30 -3
- package/dist/tools/index.js +428 -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_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_bfc8ef7d._.js → 2374f_1225fb9d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c61a33b3._.js → 2374f_2775224f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_de60e6ea._.js → 2374f_2eac907f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ab5b97d8._.js → 2374f_32fd5733._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_e366206f._.js → 2374f_3db08933._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_68abddfe._.js → 2374f_51500fd2._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5d0b3394._.js → 2374f_6f1ea3a7._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5c78460e._.js → 2374f_708e1a2e._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_04a544c8._.js → 2374f_7d957e38._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_72fb9db7._.js → 2374f_8385a57b._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d5f5b9ba._.js → 2374f_95e53328._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5e9eb6da._.js → 2374f_a19628a3._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_db790cfe._.js → 2374f_a90b70ee._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_45534372._.js → 2374f_f1f229dd._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__d04c460d._.js → [root-of-the-server]__4085bf35._.js} +4 -4
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__aa788b85._.js → [root-of-the-server]__5bde4c40._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_6fb589ac._.js → web_14735895._.js} +2 -2
- 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/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/{054deec0c7b19894.js → 4a490bc538aa0940.js} +3 -3
- package/web/.next/{static/chunks/eea48be65cdb3f4b.js → standalone/web/.next/static/chunks/4b6a9454bc6460c5.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/8b53c110c5a6f9a0.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/{7fb141141caa4fac.js → c2dccd8f56d8eb5a.js} +4 -4
- package/web/.next/{static/chunks/054deec0c7b19894.js → standalone/web/.next/static/static/chunks/4a490bc538aa0940.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/{eea48be65cdb3f4b.js → 4b6a9454bc6460c5.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/8b53c110c5a6f9a0.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/{7fb141141caa4fac.js → c2dccd8f56d8eb5a.js} +4 -4
- 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 +92 -3
- package/web/.next/standalone/web/src/lib/api.ts +95 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/054deec0c7b19894.js → static/chunks/4a490bc538aa0940.js} +3 -3
- package/web/.next/{standalone/web/.next/static/chunks/eea48be65cdb3f4b.js → static/chunks/4b6a9454bc6460c5.js} +3 -3
- package/web/.next/static/chunks/8b53c110c5a6f9a0.css +1 -0
- package/web/.next/static/chunks/{7fb141141caa4fac.js → c2dccd8f56d8eb5a.js} +4 -4
- 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 → NuySHGSR0CchKU9zo42LK}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_ssgManifest.js +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_buildManifest.js +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_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,287 @@ 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
|
+
let proxy = activeProxies.get(sessionId);
|
|
1788
|
+
if (proxy && !proxy.connected) {
|
|
1789
|
+
proxy.destroy();
|
|
1790
|
+
proxy = void 0;
|
|
1791
|
+
}
|
|
1792
|
+
if (!proxy) {
|
|
1793
|
+
proxy = new BrowserStreamProxy(port);
|
|
1794
|
+
activeProxies.set(sessionId, proxy);
|
|
1795
|
+
proxy.on("close", () => {
|
|
1796
|
+
activeProxies.delete(sessionId);
|
|
1797
|
+
});
|
|
1798
|
+
proxy.connect();
|
|
1799
|
+
}
|
|
1800
|
+
return proxy;
|
|
1801
|
+
}
|
|
1802
|
+
function getProxy(sessionId) {
|
|
1803
|
+
return activeProxies.get(sessionId);
|
|
1804
|
+
}
|
|
1805
|
+
function destroyProxy(sessionId) {
|
|
1806
|
+
const proxy = activeProxies.get(sessionId);
|
|
1807
|
+
if (proxy) {
|
|
1808
|
+
proxy.destroy();
|
|
1809
|
+
activeProxies.delete(sessionId);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
|
|
1813
|
+
var init_stream_proxy = __esm({
|
|
1814
|
+
"src/browser/stream-proxy.ts"() {
|
|
1815
|
+
"use strict";
|
|
1816
|
+
RECONNECT_DELAY_MS = 500;
|
|
1817
|
+
MAX_RECONNECT_ATTEMPTS = 10;
|
|
1818
|
+
FRAME_THROTTLE_MS = 100;
|
|
1819
|
+
BrowserStreamProxy = class extends EventEmitter {
|
|
1820
|
+
ws = null;
|
|
1821
|
+
port;
|
|
1822
|
+
reconnectAttempts = 0;
|
|
1823
|
+
reconnectTimer = null;
|
|
1824
|
+
destroyed = false;
|
|
1825
|
+
lastFrameTime = 0;
|
|
1826
|
+
_latestFrame = null;
|
|
1827
|
+
_connected = false;
|
|
1828
|
+
constructor(port) {
|
|
1829
|
+
super();
|
|
1830
|
+
this.port = port;
|
|
1831
|
+
}
|
|
1832
|
+
get connected() {
|
|
1833
|
+
return this._connected;
|
|
1834
|
+
}
|
|
1835
|
+
get latestFrame() {
|
|
1836
|
+
return this._latestFrame;
|
|
1837
|
+
}
|
|
1838
|
+
connect() {
|
|
1839
|
+
if (this.destroyed) return;
|
|
1840
|
+
this.doConnect();
|
|
1841
|
+
}
|
|
1842
|
+
doConnect() {
|
|
1843
|
+
if (this.destroyed) return;
|
|
1844
|
+
const url = `ws://localhost:${this.port}`;
|
|
1845
|
+
try {
|
|
1846
|
+
this.ws = new WebSocket(url);
|
|
1847
|
+
} catch {
|
|
1848
|
+
this.scheduleReconnect();
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
this.ws.on("open", () => {
|
|
1852
|
+
this.reconnectAttempts = 0;
|
|
1853
|
+
this._connected = true;
|
|
1854
|
+
this.emit("status", {
|
|
1855
|
+
connected: true,
|
|
1856
|
+
screencasting: true
|
|
1857
|
+
});
|
|
1858
|
+
});
|
|
1859
|
+
this.ws.on("message", (raw) => {
|
|
1860
|
+
try {
|
|
1861
|
+
const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
1862
|
+
this.handleMessage(msg);
|
|
1863
|
+
} catch {
|
|
1864
|
+
}
|
|
1865
|
+
});
|
|
1866
|
+
this.ws.on("close", () => {
|
|
1867
|
+
this._connected = false;
|
|
1868
|
+
this.emit("status", { connected: false, screencasting: false });
|
|
1869
|
+
if (!this.destroyed) {
|
|
1870
|
+
this.scheduleReconnect();
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
this.ws.on("error", () => {
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
handleMessage(msg) {
|
|
1877
|
+
if (msg.type === "frame") {
|
|
1878
|
+
const now = Date.now();
|
|
1879
|
+
if (now - this.lastFrameTime < FRAME_THROTTLE_MS) return;
|
|
1880
|
+
this.lastFrameTime = now;
|
|
1881
|
+
const frame = {
|
|
1882
|
+
data: msg.data,
|
|
1883
|
+
metadata: msg.metadata ?? {
|
|
1884
|
+
deviceWidth: 1280,
|
|
1885
|
+
deviceHeight: 720,
|
|
1886
|
+
pageScaleFactor: 1,
|
|
1887
|
+
offsetTop: 0,
|
|
1888
|
+
scrollOffsetX: 0,
|
|
1889
|
+
scrollOffsetY: 0
|
|
1890
|
+
},
|
|
1891
|
+
timestamp: now
|
|
1892
|
+
};
|
|
1893
|
+
this._latestFrame = frame;
|
|
1894
|
+
this.emit("frame", frame);
|
|
1895
|
+
} else if (msg.type === "status") {
|
|
1896
|
+
this.emit("status", {
|
|
1897
|
+
connected: msg.connected ?? true,
|
|
1898
|
+
screencasting: msg.screencasting ?? true,
|
|
1899
|
+
viewportWidth: msg.viewportWidth,
|
|
1900
|
+
viewportHeight: msg.viewportHeight
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
scheduleReconnect() {
|
|
1905
|
+
if (this.destroyed || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
1906
|
+
this.emit("close");
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
this.reconnectAttempts++;
|
|
1910
|
+
const delay = RECONNECT_DELAY_MS * this.reconnectAttempts;
|
|
1911
|
+
this.reconnectTimer = setTimeout(() => this.doConnect(), delay);
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Send an input event to the browser for pair-browsing.
|
|
1915
|
+
*/
|
|
1916
|
+
injectInput(event) {
|
|
1917
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1918
|
+
this.ws.send(JSON.stringify(event));
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
destroy() {
|
|
1922
|
+
this.destroyed = true;
|
|
1923
|
+
if (this.reconnectTimer) {
|
|
1924
|
+
clearTimeout(this.reconnectTimer);
|
|
1925
|
+
this.reconnectTimer = null;
|
|
1926
|
+
}
|
|
1927
|
+
if (this.ws) {
|
|
1928
|
+
this.ws.removeAllListeners();
|
|
1929
|
+
this.ws.close();
|
|
1930
|
+
this.ws = null;
|
|
1931
|
+
}
|
|
1932
|
+
this._connected = false;
|
|
1933
|
+
this.removeAllListeners();
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
activeProxies = /* @__PURE__ */ new Map();
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
// src/browser/recorder.ts
|
|
1941
|
+
var recorder_exports = {};
|
|
1942
|
+
__export(recorder_exports, {
|
|
1943
|
+
FrameRecorder: () => FrameRecorder
|
|
1944
|
+
});
|
|
1945
|
+
import { exec as exec5 } from "child_process";
|
|
1946
|
+
import { promisify as promisify5 } from "util";
|
|
1947
|
+
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1948
|
+
import { join as join6 } from "path";
|
|
1949
|
+
import { tmpdir } from "os";
|
|
1950
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
1951
|
+
async function checkFfmpeg() {
|
|
1952
|
+
try {
|
|
1953
|
+
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
1954
|
+
return true;
|
|
1955
|
+
} catch {
|
|
1956
|
+
return false;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
async function cleanup(dir) {
|
|
1960
|
+
try {
|
|
1961
|
+
await rm(dir, { recursive: true, force: true });
|
|
1962
|
+
} catch {
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
var execAsync5, FrameRecorder;
|
|
1966
|
+
var init_recorder = __esm({
|
|
1967
|
+
"src/browser/recorder.ts"() {
|
|
1968
|
+
"use strict";
|
|
1969
|
+
execAsync5 = promisify5(exec5);
|
|
1970
|
+
FrameRecorder = class {
|
|
1971
|
+
frames = [];
|
|
1972
|
+
startTime = null;
|
|
1973
|
+
recording = false;
|
|
1974
|
+
sessionId;
|
|
1975
|
+
constructor(sessionId) {
|
|
1976
|
+
this.sessionId = sessionId;
|
|
1977
|
+
}
|
|
1978
|
+
get isRecording() {
|
|
1979
|
+
return this.recording;
|
|
1980
|
+
}
|
|
1981
|
+
get frameCount() {
|
|
1982
|
+
return this.frames.length;
|
|
1983
|
+
}
|
|
1984
|
+
start() {
|
|
1985
|
+
this.frames = [];
|
|
1986
|
+
this.startTime = Date.now();
|
|
1987
|
+
this.recording = true;
|
|
1988
|
+
}
|
|
1989
|
+
addFrame(frame) {
|
|
1990
|
+
if (!this.recording) return;
|
|
1991
|
+
this.frames.push({
|
|
1992
|
+
data: Buffer.from(frame.data, "base64"),
|
|
1993
|
+
timestamp: frame.timestamp
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
stop() {
|
|
1997
|
+
this.recording = false;
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Encode recorded frames into an MP4 using ffmpeg.
|
|
2001
|
+
* Returns the file path to the generated MP4, or null if encoding fails.
|
|
2002
|
+
*/
|
|
2003
|
+
async encode() {
|
|
2004
|
+
if (this.frames.length === 0) return null;
|
|
2005
|
+
const workDir = join6(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
|
|
2006
|
+
await mkdir4(workDir, { recursive: true });
|
|
2007
|
+
try {
|
|
2008
|
+
for (let i = 0; i < this.frames.length; i++) {
|
|
2009
|
+
const framePath = join6(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
2010
|
+
await writeFile4(framePath, this.frames[i].data);
|
|
2011
|
+
}
|
|
2012
|
+
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
2013
|
+
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
2014
|
+
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
2015
|
+
const outputPath = join6(workDir, `recording_${this.sessionId}.mp4`);
|
|
2016
|
+
const hasFfmpeg = await checkFfmpeg();
|
|
2017
|
+
if (hasFfmpeg) {
|
|
2018
|
+
await execAsync5(
|
|
2019
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join6(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
2020
|
+
{ timeout: 12e4 }
|
|
2021
|
+
);
|
|
2022
|
+
} else {
|
|
2023
|
+
console.warn("[RECORDER] ffmpeg not available, cannot encode recording");
|
|
2024
|
+
await cleanup(workDir);
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
const outputBuf = await readFile10(outputPath);
|
|
2028
|
+
const files = await readdir5(workDir);
|
|
2029
|
+
for (const f of files) {
|
|
2030
|
+
if (f.startsWith("frame_")) {
|
|
2031
|
+
await unlink2(join6(workDir, f)).catch(() => {
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
return { path: outputPath, sizeBytes: outputBuf.length };
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
console.error("[RECORDER] Failed to encode recording:", error);
|
|
2038
|
+
await cleanup(workDir);
|
|
2039
|
+
return null;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
/** Discard all frames and free memory */
|
|
2043
|
+
clear() {
|
|
2044
|
+
this.frames = [];
|
|
2045
|
+
this.startTime = null;
|
|
2046
|
+
this.recording = false;
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
|
|
1648
2052
|
// src/agent/index.ts
|
|
1649
2053
|
import {
|
|
1650
2054
|
streamText as streamText2,
|
|
1651
2055
|
generateText as generateText3,
|
|
1652
|
-
tool as
|
|
2056
|
+
tool as tool13,
|
|
1653
2057
|
stepCountIs as stepCountIs2
|
|
1654
2058
|
} from "ai";
|
|
1655
2059
|
|
|
@@ -1830,8 +2234,8 @@ var SUBAGENT_MODELS = {
|
|
|
1830
2234
|
// src/agent/index.ts
|
|
1831
2235
|
init_db();
|
|
1832
2236
|
init_config();
|
|
1833
|
-
import { z as
|
|
1834
|
-
import { nanoid as
|
|
2237
|
+
import { z as z14 } from "zod";
|
|
2238
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1835
2239
|
|
|
1836
2240
|
// src/tools/bash.ts
|
|
1837
2241
|
import { tool } from "ai";
|
|
@@ -2134,8 +2538,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
|
|
|
2134
2538
|
const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
|
|
2135
2539
|
const terminals2 = [];
|
|
2136
2540
|
try {
|
|
2137
|
-
const { readdir:
|
|
2138
|
-
const entries = await
|
|
2541
|
+
const { readdir: readdir7 } = await import("fs/promises");
|
|
2542
|
+
const entries = await readdir7(terminalsDir, { withFileTypes: true });
|
|
2139
2543
|
for (const entry of entries) {
|
|
2140
2544
|
if (entry.isDirectory()) {
|
|
2141
2545
|
const meta = await getMeta(entry.name, workingDirectory, sessionId);
|
|
@@ -2197,6 +2601,29 @@ function isBlockedCommand(command) {
|
|
|
2197
2601
|
(blocked) => normalizedCommand.includes(blocked.toLowerCase())
|
|
2198
2602
|
);
|
|
2199
2603
|
}
|
|
2604
|
+
var BROWSER_STREAM_BASE_PORT = 9223;
|
|
2605
|
+
var sessionBrowserPorts = /* @__PURE__ */ new Map();
|
|
2606
|
+
var nextPortOffset = 0;
|
|
2607
|
+
function getBrowserStreamPort(sessionId) {
|
|
2608
|
+
let port = sessionBrowserPorts.get(sessionId);
|
|
2609
|
+
if (!port) {
|
|
2610
|
+
port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
|
|
2611
|
+
sessionBrowserPorts.set(sessionId, port);
|
|
2612
|
+
}
|
|
2613
|
+
return port;
|
|
2614
|
+
}
|
|
2615
|
+
function isAgentBrowserOpenCommand(command) {
|
|
2616
|
+
return /\bagent-browser\s+open\b/.test(command);
|
|
2617
|
+
}
|
|
2618
|
+
function isAgentBrowserCloseCommand(command) {
|
|
2619
|
+
return /\bagent-browser\s+close\b/.test(command);
|
|
2620
|
+
}
|
|
2621
|
+
function injectBrowserStreamPort(command, port) {
|
|
2622
|
+
return command.replace(
|
|
2623
|
+
/\bagent-browser\b/,
|
|
2624
|
+
`AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2200
2627
|
var bashInputSchema = z2.object({
|
|
2201
2628
|
command: z2.string().optional().describe("The command to execute. Required for running new commands."),
|
|
2202
2629
|
background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
|
|
@@ -2362,6 +2789,14 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2362
2789
|
exitCode: 1
|
|
2363
2790
|
};
|
|
2364
2791
|
}
|
|
2792
|
+
let actualCommand = command;
|
|
2793
|
+
const browserOpen = isAgentBrowserOpenCommand(command);
|
|
2794
|
+
const browserClose = isAgentBrowserCloseCommand(command);
|
|
2795
|
+
let browserPort;
|
|
2796
|
+
if (browserOpen) {
|
|
2797
|
+
browserPort = getBrowserStreamPort(options.sessionId);
|
|
2798
|
+
actualCommand = injectBrowserStreamPort(command, browserPort);
|
|
2799
|
+
}
|
|
2365
2800
|
const canUseTmux = await shouldUseTmux();
|
|
2366
2801
|
if (background) {
|
|
2367
2802
|
if (!canUseTmux) {
|
|
@@ -2371,11 +2806,14 @@ 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
|
});
|
|
2814
|
+
if (browserOpen && browserPort) {
|
|
2815
|
+
options.onProgress?.({ terminalId, status: "running", command, browserStreamPort: browserPort });
|
|
2816
|
+
}
|
|
2379
2817
|
return {
|
|
2380
2818
|
success: true,
|
|
2381
2819
|
id: result.id,
|
|
@@ -2385,16 +2823,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2385
2823
|
}
|
|
2386
2824
|
if (canUseTmux) {
|
|
2387
2825
|
const terminalId = generateTerminalId();
|
|
2388
|
-
options.onProgress?.({ terminalId, status: "started", command });
|
|
2826
|
+
options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
|
|
2389
2827
|
try {
|
|
2390
|
-
const result = await runSync(
|
|
2828
|
+
const result = await runSync(actualCommand, options.workingDirectory, {
|
|
2391
2829
|
sessionId: options.sessionId,
|
|
2392
2830
|
timeout: COMMAND_TIMEOUT,
|
|
2393
2831
|
terminalId
|
|
2394
2832
|
});
|
|
2395
2833
|
const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
|
|
2396
2834
|
options.onOutput?.(truncatedOutput);
|
|
2397
|
-
options.onProgress?.({ terminalId, status: "completed", command });
|
|
2835
|
+
options.onProgress?.({ terminalId, status: "completed", command, browserStreamPort: browserClose ? browserPort : void 0 });
|
|
2398
2836
|
return {
|
|
2399
2837
|
success: result.exitCode === 0,
|
|
2400
2838
|
id: result.id,
|
|
@@ -2412,7 +2850,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
2412
2850
|
};
|
|
2413
2851
|
}
|
|
2414
2852
|
} else {
|
|
2415
|
-
const result = await execFallback(
|
|
2853
|
+
const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
|
|
2416
2854
|
return {
|
|
2417
2855
|
success: result.success,
|
|
2418
2856
|
output: result.output,
|
|
@@ -2787,12 +3225,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
2787
3225
|
}
|
|
2788
3226
|
async function commandExists(cmd) {
|
|
2789
3227
|
try {
|
|
2790
|
-
const { exec:
|
|
2791
|
-
const { promisify:
|
|
2792
|
-
const
|
|
3228
|
+
const { exec: exec7 } = await import("child_process");
|
|
3229
|
+
const { promisify: promisify7 } = await import("util");
|
|
3230
|
+
const execAsync7 = promisify7(exec7);
|
|
2793
3231
|
const isWindows = process.platform === "win32";
|
|
2794
3232
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
2795
|
-
await
|
|
3233
|
+
await execAsync7(checkCmd);
|
|
2796
3234
|
return true;
|
|
2797
3235
|
} catch {
|
|
2798
3236
|
return false;
|
|
@@ -3090,7 +3528,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3090
3528
|
const startTime = Date.now();
|
|
3091
3529
|
let debounceTimer;
|
|
3092
3530
|
let resolved = false;
|
|
3093
|
-
const
|
|
3531
|
+
const cleanup2 = () => {
|
|
3094
3532
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
3095
3533
|
const listeners = diagnosticListeners.get(normalized);
|
|
3096
3534
|
if (listeners) {
|
|
@@ -3104,7 +3542,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3104
3542
|
const finish = () => {
|
|
3105
3543
|
if (resolved) return;
|
|
3106
3544
|
resolved = true;
|
|
3107
|
-
|
|
3545
|
+
cleanup2();
|
|
3108
3546
|
resolve11(diagnostics.get(normalized) || []);
|
|
3109
3547
|
};
|
|
3110
3548
|
const onDiagnostic = () => {
|
|
@@ -5209,8 +5647,106 @@ function createTaskFailedTool(options) {
|
|
|
5209
5647
|
});
|
|
5210
5648
|
}
|
|
5211
5649
|
|
|
5650
|
+
// src/tools/upload-file.ts
|
|
5651
|
+
import { tool as tool12 } from "ai";
|
|
5652
|
+
import { z as z13 } from "zod";
|
|
5653
|
+
import { readFile as readFile9, stat as stat4 } from "fs/promises";
|
|
5654
|
+
import { join as join5, basename as basename4, extname as extname7 } from "path";
|
|
5655
|
+
var MIME_TYPES = {
|
|
5656
|
+
".txt": "text/plain",
|
|
5657
|
+
".md": "text/markdown",
|
|
5658
|
+
".html": "text/html",
|
|
5659
|
+
".css": "text/css",
|
|
5660
|
+
".js": "application/javascript",
|
|
5661
|
+
".ts": "application/typescript",
|
|
5662
|
+
".json": "application/json",
|
|
5663
|
+
".csv": "text/csv",
|
|
5664
|
+
".xml": "application/xml",
|
|
5665
|
+
".pdf": "application/pdf",
|
|
5666
|
+
".png": "image/png",
|
|
5667
|
+
".jpg": "image/jpeg",
|
|
5668
|
+
".jpeg": "image/jpeg",
|
|
5669
|
+
".gif": "image/gif",
|
|
5670
|
+
".webp": "image/webp",
|
|
5671
|
+
".svg": "image/svg+xml",
|
|
5672
|
+
".mp4": "video/mp4",
|
|
5673
|
+
".webm": "video/webm",
|
|
5674
|
+
".mp3": "audio/mpeg",
|
|
5675
|
+
".wav": "audio/wav",
|
|
5676
|
+
".zip": "application/zip",
|
|
5677
|
+
".tar": "application/x-tar",
|
|
5678
|
+
".gz": "application/gzip"
|
|
5679
|
+
};
|
|
5680
|
+
function createUploadFileTool(options) {
|
|
5681
|
+
return tool12({
|
|
5682
|
+
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.`,
|
|
5683
|
+
inputSchema: z13.object({
|
|
5684
|
+
path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
|
|
5685
|
+
name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
|
|
5686
|
+
}),
|
|
5687
|
+
execute: async (input) => {
|
|
5688
|
+
try {
|
|
5689
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
5690
|
+
if (!isRemoteConfigured2()) {
|
|
5691
|
+
return {
|
|
5692
|
+
success: false,
|
|
5693
|
+
error: "File upload is not available \u2014 remote server with GCS is not configured."
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5696
|
+
const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
|
|
5697
|
+
try {
|
|
5698
|
+
await stat4(fullPath);
|
|
5699
|
+
} catch {
|
|
5700
|
+
return {
|
|
5701
|
+
success: false,
|
|
5702
|
+
error: `File not found: ${input.path}`
|
|
5703
|
+
};
|
|
5704
|
+
}
|
|
5705
|
+
const fileName = input.name || basename4(fullPath);
|
|
5706
|
+
const ext = extname7(fullPath).toLowerCase();
|
|
5707
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
5708
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
5709
|
+
options.sessionId,
|
|
5710
|
+
fileName,
|
|
5711
|
+
contentType,
|
|
5712
|
+
"general"
|
|
5713
|
+
);
|
|
5714
|
+
const fileData = await readFile9(fullPath);
|
|
5715
|
+
const putRes = await fetch(uploadInfo.uploadUrl, {
|
|
5716
|
+
method: "PUT",
|
|
5717
|
+
headers: { "Content-Type": contentType },
|
|
5718
|
+
body: fileData
|
|
5719
|
+
});
|
|
5720
|
+
if (!putRes.ok) {
|
|
5721
|
+
return {
|
|
5722
|
+
success: false,
|
|
5723
|
+
error: `Upload failed: ${putRes.status} ${putRes.statusText}`
|
|
5724
|
+
};
|
|
5725
|
+
}
|
|
5726
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
5727
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
5728
|
+
return {
|
|
5729
|
+
success: true,
|
|
5730
|
+
fileId: uploadInfo.fileId,
|
|
5731
|
+
fileName,
|
|
5732
|
+
sizeBytes: fileData.length,
|
|
5733
|
+
contentType,
|
|
5734
|
+
downloadUrl: downloadInfo.downloadUrl,
|
|
5735
|
+
expiresAt: downloadInfo.expiresAt
|
|
5736
|
+
};
|
|
5737
|
+
} catch (err) {
|
|
5738
|
+
return {
|
|
5739
|
+
success: false,
|
|
5740
|
+
error: `Upload failed: ${err.message}`
|
|
5741
|
+
};
|
|
5742
|
+
}
|
|
5743
|
+
}
|
|
5744
|
+
});
|
|
5745
|
+
}
|
|
5746
|
+
|
|
5212
5747
|
// src/tools/index.ts
|
|
5213
5748
|
init_semantic();
|
|
5749
|
+
init_remote();
|
|
5214
5750
|
init_semantic_search();
|
|
5215
5751
|
async function createTools(options) {
|
|
5216
5752
|
const tools = {
|
|
@@ -5248,6 +5784,12 @@ async function createTools(options) {
|
|
|
5248
5784
|
workingDirectory: options.workingDirectory
|
|
5249
5785
|
})
|
|
5250
5786
|
};
|
|
5787
|
+
if (isRemoteConfigured()) {
|
|
5788
|
+
tools.upload_file = createUploadFileTool({
|
|
5789
|
+
workingDirectory: options.workingDirectory,
|
|
5790
|
+
sessionId: options.sessionId
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5251
5793
|
if (options.enableSemanticSearch !== false) {
|
|
5252
5794
|
try {
|
|
5253
5795
|
if (isVectorGatewayConfigured()) {
|
|
@@ -5342,6 +5884,7 @@ You have access to powerful tools for:
|
|
|
5342
5884
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
5343
5885
|
- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
|
|
5344
5886
|
- **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
|
|
5887
|
+
- **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
|
|
5345
5888
|
|
|
5346
5889
|
|
|
5347
5890
|
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 +6143,53 @@ function buildTaskPromptAddendum(outputSchema) {
|
|
|
5600
6143
|
## Task Mode
|
|
5601
6144
|
|
|
5602
6145
|
You are running in **task mode**. You have been given a specific task to complete autonomously.
|
|
6146
|
+
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.
|
|
6147
|
+
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
6148
|
|
|
5604
6149
|
### Rules
|
|
5605
6150
|
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.
|
|
6151
|
+
2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
|
|
5607
6152
|
3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
5608
6153
|
4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
5609
6154
|
5. Do NOT stop without calling one of these two tools.
|
|
5610
6155
|
|
|
6156
|
+
### Verification \u2014 BE EXTREMELY THOROUGH
|
|
6157
|
+
Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
|
|
6158
|
+
|
|
6159
|
+
**After making code changes:**
|
|
6160
|
+
- Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
|
|
6161
|
+
- **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
|
|
6162
|
+
- If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
|
|
6163
|
+
- If you created new files, verify they exist and contain what you expect.
|
|
6164
|
+
|
|
6165
|
+
**For UI / web changes:**
|
|
6166
|
+
- Start the dev server if it isn't already running (it might be so double check ur context)
|
|
6167
|
+
- **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
|
|
6168
|
+
- Check the dev server logs for errors or warnings.
|
|
6169
|
+
- If the app crashes or shows errors, fix them before completing.
|
|
6170
|
+
|
|
6171
|
+
**For backend / API changes:**
|
|
6172
|
+
- Test the endpoint with curl or a quick script to confirm it works as expected.
|
|
6173
|
+
- Check server logs for errors.
|
|
6174
|
+
|
|
6175
|
+
**For search and exploration tasks:**
|
|
6176
|
+
- Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
|
|
6177
|
+
- Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
|
|
6178
|
+
- Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
|
|
6179
|
+
- Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
|
|
6180
|
+
|
|
6181
|
+
**General verification checklist:**
|
|
6182
|
+
- Re-read the original task prompt and confirm every requirement has been addressed.
|
|
6183
|
+
- If the task asked for multiple things, verify EACH one individually.
|
|
6184
|
+
- If something doesn't look right, fix it \u2014 don't complete with known issues.
|
|
6185
|
+
|
|
6186
|
+
### Use All Available Tools
|
|
6187
|
+
- **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
|
|
6188
|
+
- **explore_agent**: Use for codebase exploration and understanding before making changes.
|
|
6189
|
+
- **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
|
|
6190
|
+
- **todo**: Track your progress on multi-step tasks so you don't miss steps.
|
|
6191
|
+
- **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
|
|
6192
|
+
|
|
5611
6193
|
### Output Schema
|
|
5612
6194
|
The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
|
|
5613
6195
|
\`\`\`json
|
|
@@ -5615,7 +6197,7 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
5615
6197
|
\`\`\`
|
|
5616
6198
|
|
|
5617
6199
|
### Completion Tools
|
|
5618
|
-
- **\`complete_task({ result: ... })\`** \u2014 Call
|
|
6200
|
+
- **\`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
6201
|
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
5620
6202
|
`;
|
|
5621
6203
|
}
|
|
@@ -6123,11 +6705,29 @@ ${prompt}` });
|
|
|
6123
6705
|
const onComplete = (signal) => {
|
|
6124
6706
|
completion.signal = signal;
|
|
6125
6707
|
};
|
|
6708
|
+
let taskRecorder = null;
|
|
6709
|
+
const sessionId = this.session.id;
|
|
6710
|
+
const bashProgressHandler = (progress) => {
|
|
6711
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
6712
|
+
const port = progress.browserStreamPort;
|
|
6713
|
+
if (port && progress.status === "started") {
|
|
6714
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
6715
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
6716
|
+
if (!taskRecorder) {
|
|
6717
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
6718
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
6719
|
+
taskRecorder.start();
|
|
6720
|
+
proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
|
|
6721
|
+
});
|
|
6722
|
+
}
|
|
6723
|
+
});
|
|
6724
|
+
}
|
|
6725
|
+
};
|
|
6126
6726
|
const taskTools = await createTools({
|
|
6127
6727
|
sessionId: this.session.id,
|
|
6128
6728
|
workingDirectory: this.session.workingDirectory,
|
|
6129
6729
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
6130
|
-
onBashProgress:
|
|
6730
|
+
onBashProgress: bashProgressHandler,
|
|
6131
6731
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
6132
6732
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
|
|
6133
6733
|
taskTools: {
|
|
@@ -6198,12 +6798,24 @@ ${taskAddendum}`;
|
|
|
6198
6798
|
if (completion.signal) {
|
|
6199
6799
|
const sig = completion.signal;
|
|
6200
6800
|
const finalStatus = sig.status;
|
|
6801
|
+
let fileUrls;
|
|
6802
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
6803
|
+
const resultObj = sig.result;
|
|
6804
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
6805
|
+
if (filePaths.length > 0) {
|
|
6806
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6810
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
6201
6811
|
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
6202
6812
|
fireWebhook(eventType, {
|
|
6203
6813
|
status: finalStatus,
|
|
6204
6814
|
result: sig.result,
|
|
6205
6815
|
error: sig.error,
|
|
6206
|
-
iterations: iteration
|
|
6816
|
+
iterations: iteration,
|
|
6817
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
6818
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
6207
6819
|
});
|
|
6208
6820
|
const updatedTask2 = {
|
|
6209
6821
|
...options.taskConfig,
|
|
@@ -6223,11 +6835,17 @@ ${taskAddendum}`;
|
|
|
6223
6835
|
};
|
|
6224
6836
|
}
|
|
6225
6837
|
await this.context.addUserMessage(
|
|
6226
|
-
"Continue working on the task. When
|
|
6838
|
+
"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
6839
|
);
|
|
6228
6840
|
}
|
|
6229
6841
|
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
6230
|
-
|
|
6842
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
6843
|
+
fireWebhook("task.failed", {
|
|
6844
|
+
status: "failed",
|
|
6845
|
+
error: timeoutError,
|
|
6846
|
+
iterations: iteration,
|
|
6847
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
6848
|
+
});
|
|
6231
6849
|
const updatedTask = {
|
|
6232
6850
|
...options.taskConfig,
|
|
6233
6851
|
status: "failed",
|
|
@@ -6239,6 +6857,113 @@ ${taskAddendum}`;
|
|
|
6239
6857
|
});
|
|
6240
6858
|
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
6241
6859
|
}
|
|
6860
|
+
/**
|
|
6861
|
+
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
6862
|
+
* Returns download URLs for any recordings produced.
|
|
6863
|
+
*/
|
|
6864
|
+
async finishTaskRecording(recorder) {
|
|
6865
|
+
try {
|
|
6866
|
+
const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
|
|
6867
|
+
destroyProxy2(this.session.id);
|
|
6868
|
+
} catch {
|
|
6869
|
+
}
|
|
6870
|
+
if (!recorder || recorder.frameCount === 0) {
|
|
6871
|
+
recorder?.clear();
|
|
6872
|
+
return [];
|
|
6873
|
+
}
|
|
6874
|
+
recorder.stop();
|
|
6875
|
+
try {
|
|
6876
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6877
|
+
if (!isRemoteConfigured2()) {
|
|
6878
|
+
recorder.clear();
|
|
6879
|
+
return [];
|
|
6880
|
+
}
|
|
6881
|
+
const result = await recorder.encode();
|
|
6882
|
+
recorder.clear();
|
|
6883
|
+
if (!result) return [];
|
|
6884
|
+
const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
|
|
6885
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6886
|
+
this.session.id,
|
|
6887
|
+
`browser-recording-${Date.now()}.mp4`,
|
|
6888
|
+
"video/mp4",
|
|
6889
|
+
"browser-recording"
|
|
6890
|
+
);
|
|
6891
|
+
const fileData = await readFile11(result.path);
|
|
6892
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6893
|
+
method: "PUT",
|
|
6894
|
+
headers: { "Content-Type": "video/mp4" },
|
|
6895
|
+
body: fileData
|
|
6896
|
+
});
|
|
6897
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
6898
|
+
const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6899
|
+
await unlink3(result.path).catch(() => {
|
|
6900
|
+
});
|
|
6901
|
+
console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
|
|
6902
|
+
return [dlInfo.downloadUrl];
|
|
6903
|
+
} catch (err) {
|
|
6904
|
+
console.error("[TASK] Failed to upload browser recording:", err.message);
|
|
6905
|
+
recorder.clear();
|
|
6906
|
+
return [];
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
6909
|
+
/**
|
|
6910
|
+
* Upload task output files to GCS via the remote server.
|
|
6911
|
+
* Returns an array of download URLs for successfully uploaded files.
|
|
6912
|
+
*/
|
|
6913
|
+
async uploadTaskFiles(filePaths) {
|
|
6914
|
+
try {
|
|
6915
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
6916
|
+
if (!isRemoteConfigured2()) return [];
|
|
6917
|
+
const { readFile: readFile11 } = await import("fs/promises");
|
|
6918
|
+
const { join: join11, basename: basename6 } = await import("path");
|
|
6919
|
+
const urls = [];
|
|
6920
|
+
for (const filePath of filePaths) {
|
|
6921
|
+
try {
|
|
6922
|
+
const fullPath = filePath.startsWith("/") ? filePath : join11(this.session.workingDirectory, filePath);
|
|
6923
|
+
const fileName = basename6(fullPath);
|
|
6924
|
+
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
6925
|
+
const mimeMap = {
|
|
6926
|
+
pdf: "application/pdf",
|
|
6927
|
+
json: "application/json",
|
|
6928
|
+
csv: "text/csv",
|
|
6929
|
+
txt: "text/plain",
|
|
6930
|
+
md: "text/markdown",
|
|
6931
|
+
html: "text/html",
|
|
6932
|
+
png: "image/png",
|
|
6933
|
+
jpg: "image/jpeg",
|
|
6934
|
+
jpeg: "image/jpeg",
|
|
6935
|
+
gif: "image/gif",
|
|
6936
|
+
svg: "image/svg+xml",
|
|
6937
|
+
mp4: "video/mp4",
|
|
6938
|
+
zip: "application/zip"
|
|
6939
|
+
};
|
|
6940
|
+
const contentType = mimeMap[ext] || "application/octet-stream";
|
|
6941
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
6942
|
+
this.session.id,
|
|
6943
|
+
fileName,
|
|
6944
|
+
contentType,
|
|
6945
|
+
"task-output"
|
|
6946
|
+
);
|
|
6947
|
+
const fileData = await readFile11(fullPath);
|
|
6948
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
6949
|
+
method: "PUT",
|
|
6950
|
+
headers: { "Content-Type": contentType },
|
|
6951
|
+
body: fileData
|
|
6952
|
+
});
|
|
6953
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
|
|
6954
|
+
const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
|
|
6955
|
+
urls.push(downloadInfo.downloadUrl);
|
|
6956
|
+
console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
|
|
6957
|
+
} catch (err) {
|
|
6958
|
+
console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
|
|
6959
|
+
}
|
|
6960
|
+
}
|
|
6961
|
+
return urls;
|
|
6962
|
+
} catch (err) {
|
|
6963
|
+
console.error("[TASK] File upload failed:", err.message);
|
|
6964
|
+
return [];
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6242
6967
|
/**
|
|
6243
6968
|
* Wrap tools to add approval checking
|
|
6244
6969
|
*/
|
|
@@ -6252,11 +6977,11 @@ ${taskAddendum}`;
|
|
|
6252
6977
|
wrappedTools[name] = originalTool;
|
|
6253
6978
|
continue;
|
|
6254
6979
|
}
|
|
6255
|
-
wrappedTools[name] =
|
|
6980
|
+
wrappedTools[name] = tool13({
|
|
6256
6981
|
description: originalTool.description || "",
|
|
6257
|
-
inputSchema: originalTool.inputSchema ||
|
|
6982
|
+
inputSchema: originalTool.inputSchema || z14.object({}),
|
|
6258
6983
|
execute: async (input, toolOptions) => {
|
|
6259
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
6984
|
+
const toolCallId = toolOptions.toolCallId || nanoid4();
|
|
6260
6985
|
const execution = toolExecutionQueries.create({
|
|
6261
6986
|
sessionId: this.session.id,
|
|
6262
6987
|
toolName: name,
|
|
@@ -6274,10 +6999,10 @@ ${taskAddendum}`;
|
|
|
6274
6999
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
6275
7000
|
approvalResolvers.delete(toolCallId);
|
|
6276
7001
|
this.pendingApprovals.delete(toolCallId);
|
|
6277
|
-
const
|
|
7002
|
+
const exec7 = await execution;
|
|
6278
7003
|
if (!approved) {
|
|
6279
7004
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
6280
|
-
await toolExecutionQueries.reject(
|
|
7005
|
+
await toolExecutionQueries.reject(exec7.id);
|
|
6281
7006
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
6282
7007
|
return {
|
|
6283
7008
|
status: "rejected",
|
|
@@ -6287,14 +7012,14 @@ ${taskAddendum}`;
|
|
|
6287
7012
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
6288
7013
|
};
|
|
6289
7014
|
}
|
|
6290
|
-
await toolExecutionQueries.approve(
|
|
7015
|
+
await toolExecutionQueries.approve(exec7.id);
|
|
6291
7016
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
6292
7017
|
try {
|
|
6293
7018
|
const result = await originalTool.execute(input, toolOptions);
|
|
6294
|
-
await toolExecutionQueries.complete(
|
|
7019
|
+
await toolExecutionQueries.complete(exec7.id, result);
|
|
6295
7020
|
return result;
|
|
6296
7021
|
} catch (error) {
|
|
6297
|
-
await toolExecutionQueries.complete(
|
|
7022
|
+
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
6298
7023
|
throw error;
|
|
6299
7024
|
}
|
|
6300
7025
|
}
|
|
@@ -6370,7 +7095,7 @@ import { serve } from "@hono/node-server";
|
|
|
6370
7095
|
import { cors } from "hono/cors";
|
|
6371
7096
|
import { logger } from "hono/logger";
|
|
6372
7097
|
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
6373
|
-
import { resolve as resolve10, dirname as dirname7, join as
|
|
7098
|
+
import { resolve as resolve10, dirname as dirname7, join as join10 } from "path";
|
|
6374
7099
|
import { spawn as spawn2 } from "child_process";
|
|
6375
7100
|
import { createServer as createNetServer } from "net";
|
|
6376
7101
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -6379,11 +7104,11 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
6379
7104
|
init_db();
|
|
6380
7105
|
import { Hono } from "hono";
|
|
6381
7106
|
import { zValidator } from "@hono/zod-validator";
|
|
6382
|
-
import { z as
|
|
7107
|
+
import { z as z15 } from "zod";
|
|
6383
7108
|
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
|
|
7109
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
7110
|
+
import { join as join7, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
7111
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
6387
7112
|
init_config();
|
|
6388
7113
|
|
|
6389
7114
|
// src/server/devtools-store.ts
|
|
@@ -6415,18 +7140,18 @@ function cleanupPendingInputs() {
|
|
|
6415
7140
|
}
|
|
6416
7141
|
}
|
|
6417
7142
|
}
|
|
6418
|
-
var createSessionSchema =
|
|
6419
|
-
name:
|
|
6420
|
-
workingDirectory:
|
|
6421
|
-
model:
|
|
6422
|
-
toolApprovals:
|
|
7143
|
+
var createSessionSchema = z15.object({
|
|
7144
|
+
name: z15.string().optional(),
|
|
7145
|
+
workingDirectory: z15.string().optional(),
|
|
7146
|
+
model: z15.string().optional(),
|
|
7147
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
6423
7148
|
});
|
|
6424
|
-
var paginationQuerySchema =
|
|
6425
|
-
limit:
|
|
6426
|
-
offset:
|
|
7149
|
+
var paginationQuerySchema = z15.object({
|
|
7150
|
+
limit: z15.string().optional(),
|
|
7151
|
+
offset: z15.string().optional()
|
|
6427
7152
|
});
|
|
6428
|
-
var messagesQuerySchema =
|
|
6429
|
-
limit:
|
|
7153
|
+
var messagesQuerySchema = z15.object({
|
|
7154
|
+
limit: z15.string().optional()
|
|
6430
7155
|
});
|
|
6431
7156
|
sessions.get(
|
|
6432
7157
|
"/",
|
|
@@ -6565,10 +7290,10 @@ sessions.get("/:id/tools", async (c) => {
|
|
|
6565
7290
|
count: executions.length
|
|
6566
7291
|
});
|
|
6567
7292
|
});
|
|
6568
|
-
var updateSessionSchema =
|
|
6569
|
-
model:
|
|
6570
|
-
name:
|
|
6571
|
-
toolApprovals:
|
|
7293
|
+
var updateSessionSchema = z15.object({
|
|
7294
|
+
model: z15.string().optional(),
|
|
7295
|
+
name: z15.string().optional(),
|
|
7296
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
6572
7297
|
});
|
|
6573
7298
|
sessions.patch(
|
|
6574
7299
|
"/:id",
|
|
@@ -6638,8 +7363,8 @@ sessions.post("/:id/clear", async (c) => {
|
|
|
6638
7363
|
await agent.clearContext();
|
|
6639
7364
|
return c.json({ success: true, sessionId: id });
|
|
6640
7365
|
});
|
|
6641
|
-
var pendingInputSchema =
|
|
6642
|
-
text:
|
|
7366
|
+
var pendingInputSchema = z15.object({
|
|
7367
|
+
text: z15.string()
|
|
6643
7368
|
});
|
|
6644
7369
|
sessions.post(
|
|
6645
7370
|
"/:id/pending-input",
|
|
@@ -6670,13 +7395,13 @@ sessions.get("/:id/pending-input", async (c) => {
|
|
|
6670
7395
|
createdAt: pending.createdAt.toISOString()
|
|
6671
7396
|
});
|
|
6672
7397
|
});
|
|
6673
|
-
var devtoolsContextSchema =
|
|
6674
|
-
url:
|
|
6675
|
-
path:
|
|
6676
|
-
pageName:
|
|
6677
|
-
screenWidth:
|
|
6678
|
-
screenHeight:
|
|
6679
|
-
devicePixelRatio:
|
|
7398
|
+
var devtoolsContextSchema = z15.object({
|
|
7399
|
+
url: z15.string(),
|
|
7400
|
+
path: z15.string(),
|
|
7401
|
+
pageName: z15.string().optional(),
|
|
7402
|
+
screenWidth: z15.number().optional(),
|
|
7403
|
+
screenHeight: z15.number().optional(),
|
|
7404
|
+
devicePixelRatio: z15.number().optional()
|
|
6680
7405
|
});
|
|
6681
7406
|
sessions.post(
|
|
6682
7407
|
"/:id/devtools-context",
|
|
@@ -6844,7 +7569,7 @@ sessions.get("/:id/diff/:filePath", async (c) => {
|
|
|
6844
7569
|
});
|
|
6845
7570
|
function getAttachmentsDir(sessionId) {
|
|
6846
7571
|
const appDataDir = getAppDataDirectory();
|
|
6847
|
-
return
|
|
7572
|
+
return join7(appDataDir, "attachments", sessionId);
|
|
6848
7573
|
}
|
|
6849
7574
|
function ensureAttachmentsDir(sessionId) {
|
|
6850
7575
|
const dir = getAttachmentsDir(sessionId);
|
|
@@ -6865,7 +7590,7 @@ sessions.get("/:id/attachments", async (c) => {
|
|
|
6865
7590
|
}
|
|
6866
7591
|
const files = readdirSync(dir);
|
|
6867
7592
|
const attachments = files.map((filename) => {
|
|
6868
|
-
const filePath =
|
|
7593
|
+
const filePath = join7(dir, filename);
|
|
6869
7594
|
const stats = statSync2(filePath);
|
|
6870
7595
|
return {
|
|
6871
7596
|
id: filename.split("_")[0],
|
|
@@ -6897,10 +7622,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
6897
7622
|
return c.json({ error: "No file provided" }, 400);
|
|
6898
7623
|
}
|
|
6899
7624
|
const dir = ensureAttachmentsDir(sessionId);
|
|
6900
|
-
const id =
|
|
6901
|
-
const ext =
|
|
6902
|
-
const safeFilename = `${id}_${
|
|
6903
|
-
const filePath =
|
|
7625
|
+
const id = nanoid5(10);
|
|
7626
|
+
const ext = extname8(file.name) || "";
|
|
7627
|
+
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7628
|
+
const filePath = join7(dir, safeFilename);
|
|
6904
7629
|
const arrayBuffer = await file.arrayBuffer();
|
|
6905
7630
|
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
6906
7631
|
return c.json({
|
|
@@ -6923,10 +7648,10 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
6923
7648
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
6924
7649
|
}
|
|
6925
7650
|
const dir = ensureAttachmentsDir(sessionId);
|
|
6926
|
-
const id =
|
|
6927
|
-
const ext =
|
|
6928
|
-
const safeFilename = `${id}_${
|
|
6929
|
-
const filePath =
|
|
7651
|
+
const id = nanoid5(10);
|
|
7652
|
+
const ext = extname8(body.filename) || "";
|
|
7653
|
+
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7654
|
+
const filePath = join7(dir, safeFilename);
|
|
6930
7655
|
let base64Data = body.data;
|
|
6931
7656
|
if (base64Data.includes(",")) {
|
|
6932
7657
|
base64Data = base64Data.split(",")[1];
|
|
@@ -6963,14 +7688,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
6963
7688
|
if (!file) {
|
|
6964
7689
|
return c.json({ error: "Attachment not found" }, 404);
|
|
6965
7690
|
}
|
|
6966
|
-
const filePath =
|
|
7691
|
+
const filePath = join7(dir, file);
|
|
6967
7692
|
unlinkSync(filePath);
|
|
6968
7693
|
return c.json({ success: true, id: attachmentId });
|
|
6969
7694
|
});
|
|
6970
|
-
var filesQuerySchema =
|
|
6971
|
-
query:
|
|
7695
|
+
var filesQuerySchema = z15.object({
|
|
7696
|
+
query: z15.string().optional(),
|
|
6972
7697
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
6973
|
-
limit:
|
|
7698
|
+
limit: z15.string().optional()
|
|
6974
7699
|
// Max results (default 50)
|
|
6975
7700
|
});
|
|
6976
7701
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -7043,10 +7768,10 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7043
7768
|
return results;
|
|
7044
7769
|
}
|
|
7045
7770
|
try {
|
|
7046
|
-
const entries = await
|
|
7771
|
+
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
7047
7772
|
for (const entry of entries) {
|
|
7048
7773
|
if (results.length >= limit * 2) break;
|
|
7049
|
-
const fullPath =
|
|
7774
|
+
const fullPath = join7(currentDir, entry.name);
|
|
7050
7775
|
const relativePath = relative9(baseDir, fullPath);
|
|
7051
7776
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
7052
7777
|
continue;
|
|
@@ -7054,7 +7779,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7054
7779
|
if (entry.name.startsWith(".")) {
|
|
7055
7780
|
continue;
|
|
7056
7781
|
}
|
|
7057
|
-
const ext =
|
|
7782
|
+
const ext = extname8(entry.name).toLowerCase();
|
|
7058
7783
|
if (IGNORED_EXTENSIONS.has(ext)) {
|
|
7059
7784
|
continue;
|
|
7060
7785
|
}
|
|
@@ -7143,14 +7868,70 @@ sessions.get(
|
|
|
7143
7868
|
}
|
|
7144
7869
|
}
|
|
7145
7870
|
);
|
|
7871
|
+
sessions.get("/:id/session-files", async (c) => {
|
|
7872
|
+
const sessionId = c.req.param("id");
|
|
7873
|
+
try {
|
|
7874
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7875
|
+
if (!isRemoteConfigured2()) {
|
|
7876
|
+
return c.json({ files: [] });
|
|
7877
|
+
}
|
|
7878
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7879
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
7880
|
+
return c.json({ sessionId, files });
|
|
7881
|
+
} catch (err) {
|
|
7882
|
+
console.error("Failed to get session files:", err.message);
|
|
7883
|
+
return c.json({ sessionId, files: [] });
|
|
7884
|
+
}
|
|
7885
|
+
});
|
|
7886
|
+
sessions.get("/files/:fileId/download", async (c) => {
|
|
7887
|
+
const fileId = c.req.param("fileId");
|
|
7888
|
+
try {
|
|
7889
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7890
|
+
if (!isRemoteConfigured2()) {
|
|
7891
|
+
return c.json({ error: "Remote server not configured" }, 503);
|
|
7892
|
+
}
|
|
7893
|
+
const result = await storageQueries2.getDownloadUrl(fileId);
|
|
7894
|
+
return c.json(result);
|
|
7895
|
+
} catch (err) {
|
|
7896
|
+
return c.json({ error: err.message }, 500);
|
|
7897
|
+
}
|
|
7898
|
+
});
|
|
7899
|
+
sessions.get("/:id/browser-recording", async (c) => {
|
|
7900
|
+
const sessionId = c.req.param("id");
|
|
7901
|
+
try {
|
|
7902
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7903
|
+
if (!isRemoteConfigured2()) {
|
|
7904
|
+
return c.json({ sessionId, recordings: [] });
|
|
7905
|
+
}
|
|
7906
|
+
const files = await storageQueries2.getSessionFiles(sessionId);
|
|
7907
|
+
const recordings = files.filter((f) => f.category === "browser-recording");
|
|
7908
|
+
if (recordings.length === 0) {
|
|
7909
|
+
return c.json({ sessionId, recordings: [], message: "No browser recordings for this session" });
|
|
7910
|
+
}
|
|
7911
|
+
return c.json({
|
|
7912
|
+
sessionId,
|
|
7913
|
+
recordings: recordings.map((r) => ({
|
|
7914
|
+
id: r.id,
|
|
7915
|
+
fileName: r.fileName,
|
|
7916
|
+
sizeBytes: r.sizeBytes,
|
|
7917
|
+
createdAt: r.createdAt,
|
|
7918
|
+
downloadUrl: r.downloadUrl,
|
|
7919
|
+
expiresAt: r.downloadUrlExpiresAt
|
|
7920
|
+
}))
|
|
7921
|
+
});
|
|
7922
|
+
} catch (err) {
|
|
7923
|
+
console.error("Failed to get browser recordings:", err.message);
|
|
7924
|
+
return c.json({ sessionId, recordings: [], error: err.message });
|
|
7925
|
+
}
|
|
7926
|
+
});
|
|
7146
7927
|
|
|
7147
7928
|
// src/server/routes/agents.ts
|
|
7148
7929
|
init_db();
|
|
7149
7930
|
import { Hono as Hono2 } from "hono";
|
|
7150
7931
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
7151
|
-
import { z as
|
|
7932
|
+
import { z as z16 } from "zod";
|
|
7152
7933
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
7153
|
-
import { join as
|
|
7934
|
+
import { join as join8 } from "path";
|
|
7154
7935
|
init_config();
|
|
7155
7936
|
|
|
7156
7937
|
// src/server/resumable-stream.ts
|
|
@@ -7226,7 +8007,11 @@ var streamContext = createResumableStreamContext({
|
|
|
7226
8007
|
});
|
|
7227
8008
|
|
|
7228
8009
|
// src/server/routes/agents.ts
|
|
7229
|
-
import { nanoid as
|
|
8010
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
8011
|
+
init_stream_proxy();
|
|
8012
|
+
init_recorder();
|
|
8013
|
+
init_remote();
|
|
8014
|
+
var sessionRecorders = /* @__PURE__ */ new Map();
|
|
7230
8015
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
7231
8016
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
7232
8017
|
var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
|
|
@@ -7312,36 +8097,72 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
7312
8097
|
|
|
7313
8098
|
${prompt}`;
|
|
7314
8099
|
}
|
|
8100
|
+
async function encodAndUploadRecording(sessionId, recorder) {
|
|
8101
|
+
if (!isRemoteConfigured()) {
|
|
8102
|
+
console.log("[RECORDING] Remote server not configured, skipping upload");
|
|
8103
|
+
recorder.clear();
|
|
8104
|
+
return;
|
|
8105
|
+
}
|
|
8106
|
+
console.log(`[RECORDING] Encoding ${recorder.frameCount} frames for session ${sessionId}...`);
|
|
8107
|
+
const result = await recorder.encode();
|
|
8108
|
+
recorder.clear();
|
|
8109
|
+
if (!result) {
|
|
8110
|
+
console.log("[RECORDING] Encoding failed or produced no output");
|
|
8111
|
+
return;
|
|
8112
|
+
}
|
|
8113
|
+
try {
|
|
8114
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
8115
|
+
const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
|
|
8116
|
+
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
8117
|
+
sessionId,
|
|
8118
|
+
`browser-recording-${Date.now()}.mp4`,
|
|
8119
|
+
"video/mp4",
|
|
8120
|
+
"browser-recording"
|
|
8121
|
+
);
|
|
8122
|
+
const fileData = await readFile11(result.path);
|
|
8123
|
+
await fetch(uploadInfo.uploadUrl, {
|
|
8124
|
+
method: "PUT",
|
|
8125
|
+
headers: { "Content-Type": "video/mp4" },
|
|
8126
|
+
body: fileData
|
|
8127
|
+
});
|
|
8128
|
+
await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
|
|
8129
|
+
console.log(`[RECORDING] Uploaded recording for session ${sessionId} (${result.sizeBytes} bytes)`);
|
|
8130
|
+
await unlink3(result.path).catch(() => {
|
|
8131
|
+
});
|
|
8132
|
+
} catch (err) {
|
|
8133
|
+
console.error("[RECORDING] Upload failed:", err.message);
|
|
8134
|
+
}
|
|
8135
|
+
}
|
|
7315
8136
|
var agents = new Hono2();
|
|
7316
|
-
var attachmentSchema =
|
|
7317
|
-
type:
|
|
7318
|
-
data:
|
|
8137
|
+
var attachmentSchema = z16.object({
|
|
8138
|
+
type: z16.enum(["image", "file"]),
|
|
8139
|
+
data: z16.string(),
|
|
7319
8140
|
// base64 data URL or raw base64
|
|
7320
|
-
mediaType:
|
|
7321
|
-
filename:
|
|
8141
|
+
mediaType: z16.string().optional(),
|
|
8142
|
+
filename: z16.string().optional()
|
|
7322
8143
|
});
|
|
7323
|
-
var runPromptSchema =
|
|
7324
|
-
prompt:
|
|
8144
|
+
var runPromptSchema = z16.object({
|
|
8145
|
+
prompt: z16.string(),
|
|
7325
8146
|
// Can be empty if attachments are provided
|
|
7326
|
-
attachments:
|
|
8147
|
+
attachments: z16.array(attachmentSchema).optional()
|
|
7327
8148
|
}).refine(
|
|
7328
8149
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
7329
8150
|
{ message: "Either prompt or attachments must be provided" }
|
|
7330
8151
|
);
|
|
7331
|
-
var quickStartSchema =
|
|
7332
|
-
prompt:
|
|
7333
|
-
name:
|
|
7334
|
-
workingDirectory:
|
|
7335
|
-
model:
|
|
7336
|
-
toolApprovals:
|
|
8152
|
+
var quickStartSchema = z16.object({
|
|
8153
|
+
prompt: z16.string().min(1),
|
|
8154
|
+
name: z16.string().optional(),
|
|
8155
|
+
workingDirectory: z16.string().optional(),
|
|
8156
|
+
model: z16.string().optional(),
|
|
8157
|
+
toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
|
|
7337
8158
|
});
|
|
7338
|
-
var rejectSchema =
|
|
7339
|
-
reason:
|
|
8159
|
+
var rejectSchema = z16.object({
|
|
8160
|
+
reason: z16.string().optional()
|
|
7340
8161
|
}).optional();
|
|
7341
8162
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
7342
8163
|
function getAttachmentsDirectory(sessionId) {
|
|
7343
8164
|
const appDataDir = getAppDataDirectory();
|
|
7344
|
-
return
|
|
8165
|
+
return join8(appDataDir, "attachments", sessionId);
|
|
7345
8166
|
}
|
|
7346
8167
|
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
7347
8168
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
@@ -7357,7 +8178,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
7357
8178
|
if (base64Data.includes(",")) {
|
|
7358
8179
|
base64Data = base64Data.split(",")[1];
|
|
7359
8180
|
}
|
|
7360
|
-
const filePath =
|
|
8181
|
+
const filePath = join8(attachmentsDir, filename);
|
|
7361
8182
|
const buffer = Buffer.from(base64Data, "base64");
|
|
7362
8183
|
writeFileSync3(filePath, buffer);
|
|
7363
8184
|
return filePath;
|
|
@@ -7525,6 +8346,32 @@ ${prompt}` });
|
|
|
7525
8346
|
}));
|
|
7526
8347
|
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7527
8348
|
}
|
|
8349
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
8350
|
+
if (progress.toolName === "bash" && browserPort && status === "started") {
|
|
8351
|
+
const proxy = getOrCreateProxy(sessionId, browserPort);
|
|
8352
|
+
let recorder = sessionRecorders.get(sessionId);
|
|
8353
|
+
if (!recorder) {
|
|
8354
|
+
recorder = new FrameRecorder(sessionId);
|
|
8355
|
+
recorder.start();
|
|
8356
|
+
sessionRecorders.set(sessionId, recorder);
|
|
8357
|
+
}
|
|
8358
|
+
proxy.on("frame", (frame) => {
|
|
8359
|
+
recorder.addFrame(frame);
|
|
8360
|
+
writeSSE(JSON.stringify({
|
|
8361
|
+
type: "browser-frame",
|
|
8362
|
+
data: frame.data,
|
|
8363
|
+
metadata: frame.metadata
|
|
8364
|
+
})).catch(() => {
|
|
8365
|
+
});
|
|
8366
|
+
});
|
|
8367
|
+
proxy.on("status", (s) => {
|
|
8368
|
+
writeSSE(JSON.stringify({
|
|
8369
|
+
type: "browser-status",
|
|
8370
|
+
...s
|
|
8371
|
+
})).catch(() => {
|
|
8372
|
+
});
|
|
8373
|
+
});
|
|
8374
|
+
}
|
|
7528
8375
|
},
|
|
7529
8376
|
onStepFinish: async () => {
|
|
7530
8377
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -7635,6 +8482,15 @@ ${prompt}` });
|
|
|
7635
8482
|
}
|
|
7636
8483
|
} finally {
|
|
7637
8484
|
cleanupAbortController();
|
|
8485
|
+
destroyProxy(sessionId);
|
|
8486
|
+
const recorder = sessionRecorders.get(sessionId);
|
|
8487
|
+
if (recorder && recorder.frameCount > 0) {
|
|
8488
|
+
sessionRecorders.delete(sessionId);
|
|
8489
|
+
recorder.stop();
|
|
8490
|
+
encodAndUploadRecording(sessionId, recorder).catch((err) => {
|
|
8491
|
+
console.error("[RECORDING] Failed to encode/upload:", err.message);
|
|
8492
|
+
});
|
|
8493
|
+
}
|
|
7638
8494
|
await writeSSE("[DONE]");
|
|
7639
8495
|
await safeClose();
|
|
7640
8496
|
}
|
|
@@ -7711,7 +8567,7 @@ ${prompt}` });
|
|
|
7711
8567
|
userMessageContent = prompt;
|
|
7712
8568
|
}
|
|
7713
8569
|
await messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
7714
|
-
const streamId = `stream_${id}_${
|
|
8570
|
+
const streamId = `stream_${id}_${nanoid6(10)}`;
|
|
7715
8571
|
await activeStreamQueries.create(id, streamId);
|
|
7716
8572
|
const stream = await streamContext.resumableStream(
|
|
7717
8573
|
streamId,
|
|
@@ -7910,7 +8766,7 @@ agents.post(
|
|
|
7910
8766
|
});
|
|
7911
8767
|
const session = agent.getSession();
|
|
7912
8768
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
7913
|
-
const streamId = `stream_${session.id}_${
|
|
8769
|
+
const streamId = `stream_${session.id}_${nanoid6(10)}`;
|
|
7914
8770
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
7915
8771
|
await activeStreamQueries.create(session.id, streamId);
|
|
7916
8772
|
const createQuickStreamProducer = () => {
|
|
@@ -7989,6 +8845,32 @@ agents.post(
|
|
|
7989
8845
|
}));
|
|
7990
8846
|
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7991
8847
|
}
|
|
8848
|
+
const browserPort = progress.data?.browserStreamPort;
|
|
8849
|
+
if (progress.toolName === "bash" && browserPort && status === "started") {
|
|
8850
|
+
const proxy = getOrCreateProxy(session.id, browserPort);
|
|
8851
|
+
let recorder = sessionRecorders.get(session.id);
|
|
8852
|
+
if (!recorder) {
|
|
8853
|
+
recorder = new FrameRecorder(session.id);
|
|
8854
|
+
recorder.start();
|
|
8855
|
+
sessionRecorders.set(session.id, recorder);
|
|
8856
|
+
}
|
|
8857
|
+
proxy.on("frame", (frame) => {
|
|
8858
|
+
recorder.addFrame(frame);
|
|
8859
|
+
writeSSE(JSON.stringify({
|
|
8860
|
+
type: "browser-frame",
|
|
8861
|
+
data: frame.data,
|
|
8862
|
+
metadata: frame.metadata
|
|
8863
|
+
})).catch(() => {
|
|
8864
|
+
});
|
|
8865
|
+
});
|
|
8866
|
+
proxy.on("status", (s) => {
|
|
8867
|
+
writeSSE(JSON.stringify({
|
|
8868
|
+
type: "browser-status",
|
|
8869
|
+
...s
|
|
8870
|
+
})).catch(() => {
|
|
8871
|
+
});
|
|
8872
|
+
});
|
|
8873
|
+
}
|
|
7992
8874
|
},
|
|
7993
8875
|
onStepFinish: async () => {
|
|
7994
8876
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -8093,6 +8975,15 @@ agents.post(
|
|
|
8093
8975
|
}
|
|
8094
8976
|
} finally {
|
|
8095
8977
|
cleanupAbortController();
|
|
8978
|
+
destroyProxy(session.id);
|
|
8979
|
+
const recorder = sessionRecorders.get(session.id);
|
|
8980
|
+
if (recorder && recorder.frameCount > 0) {
|
|
8981
|
+
sessionRecorders.delete(session.id);
|
|
8982
|
+
recorder.stop();
|
|
8983
|
+
encodAndUploadRecording(session.id, recorder).catch((err) => {
|
|
8984
|
+
console.error("[RECORDING] Failed to encode/upload:", err.message);
|
|
8985
|
+
});
|
|
8986
|
+
}
|
|
8096
8987
|
await writeSSE("[DONE]");
|
|
8097
8988
|
await safeClose();
|
|
8098
8989
|
}
|
|
@@ -8119,25 +9010,58 @@ agents.post(
|
|
|
8119
9010
|
});
|
|
8120
9011
|
}
|
|
8121
9012
|
);
|
|
9013
|
+
var browserInputSchema = z16.object({
|
|
9014
|
+
type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
|
|
9015
|
+
eventType: z16.string(),
|
|
9016
|
+
x: z16.number().optional(),
|
|
9017
|
+
y: z16.number().optional(),
|
|
9018
|
+
button: z16.string().optional(),
|
|
9019
|
+
clickCount: z16.number().optional(),
|
|
9020
|
+
deltaX: z16.number().optional(),
|
|
9021
|
+
deltaY: z16.number().optional(),
|
|
9022
|
+
key: z16.string().optional(),
|
|
9023
|
+
code: z16.string().optional(),
|
|
9024
|
+
text: z16.string().optional(),
|
|
9025
|
+
modifiers: z16.number().optional(),
|
|
9026
|
+
touchPoints: z16.array(z16.object({
|
|
9027
|
+
x: z16.number(),
|
|
9028
|
+
y: z16.number(),
|
|
9029
|
+
id: z16.number().optional()
|
|
9030
|
+
})).optional()
|
|
9031
|
+
});
|
|
9032
|
+
agents.post(
|
|
9033
|
+
"/:id/browser-input",
|
|
9034
|
+
zValidator2("json", browserInputSchema),
|
|
9035
|
+
async (c) => {
|
|
9036
|
+
const sessionId = c.req.param("id");
|
|
9037
|
+
const event = c.req.valid("json");
|
|
9038
|
+
const proxy = getProxy(sessionId);
|
|
9039
|
+
if (!proxy || !proxy.connected) {
|
|
9040
|
+
return c.json({ error: "No active browser stream for this session" }, 404);
|
|
9041
|
+
}
|
|
9042
|
+
proxy.injectInput(event);
|
|
9043
|
+
return c.json({ success: true });
|
|
9044
|
+
}
|
|
9045
|
+
);
|
|
8122
9046
|
|
|
8123
9047
|
// src/server/routes/health.ts
|
|
8124
9048
|
init_config();
|
|
8125
9049
|
import { Hono as Hono3 } from "hono";
|
|
8126
9050
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
8127
|
-
import { z as
|
|
9051
|
+
import { z as z17 } from "zod";
|
|
8128
9052
|
import { readFileSync as readFileSync5 } from "fs";
|
|
8129
9053
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8130
|
-
import { dirname as dirname6, join as
|
|
9054
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
8131
9055
|
var __filename = fileURLToPath3(import.meta.url);
|
|
8132
9056
|
var __dirname = dirname6(__filename);
|
|
8133
9057
|
var possiblePaths = [
|
|
8134
|
-
|
|
9058
|
+
join9(__dirname, "../package.json"),
|
|
8135
9059
|
// From dist/server -> dist/../package.json
|
|
8136
|
-
|
|
9060
|
+
join9(__dirname, "../../package.json"),
|
|
8137
9061
|
// From dist/server (if nested differently)
|
|
8138
|
-
|
|
9062
|
+
join9(__dirname, "../../../package.json"),
|
|
8139
9063
|
// From src/server/routes (development)
|
|
8140
|
-
|
|
9064
|
+
join9(process.cwd(), "package.json")
|
|
8141
9065
|
// From current working directory
|
|
8142
9066
|
];
|
|
8143
9067
|
var currentVersion = "0.0.0";
|
|
@@ -8234,9 +9158,9 @@ health.get("/api-keys", async (c) => {
|
|
|
8234
9158
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
8235
9159
|
});
|
|
8236
9160
|
});
|
|
8237
|
-
var setApiKeySchema =
|
|
8238
|
-
provider:
|
|
8239
|
-
apiKey:
|
|
9161
|
+
var setApiKeySchema = z17.object({
|
|
9162
|
+
provider: z17.string(),
|
|
9163
|
+
apiKey: z17.string().min(1)
|
|
8240
9164
|
});
|
|
8241
9165
|
health.post(
|
|
8242
9166
|
"/api-keys",
|
|
@@ -8275,13 +9199,13 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
8275
9199
|
// src/server/routes/terminals.ts
|
|
8276
9200
|
import { Hono as Hono4 } from "hono";
|
|
8277
9201
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
8278
|
-
import { z as
|
|
9202
|
+
import { z as z18 } from "zod";
|
|
8279
9203
|
init_db();
|
|
8280
9204
|
var terminals = new Hono4();
|
|
8281
|
-
var spawnSchema =
|
|
8282
|
-
command:
|
|
8283
|
-
cwd:
|
|
8284
|
-
name:
|
|
9205
|
+
var spawnSchema = z18.object({
|
|
9206
|
+
command: z18.string(),
|
|
9207
|
+
cwd: z18.string().optional(),
|
|
9208
|
+
name: z18.string().optional()
|
|
8285
9209
|
});
|
|
8286
9210
|
terminals.post(
|
|
8287
9211
|
"/:sessionId/terminals",
|
|
@@ -8362,8 +9286,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
8362
9286
|
// We don't track exit codes in tmux mode
|
|
8363
9287
|
});
|
|
8364
9288
|
});
|
|
8365
|
-
var logsQuerySchema =
|
|
8366
|
-
tail:
|
|
9289
|
+
var logsQuerySchema = z18.object({
|
|
9290
|
+
tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
8367
9291
|
});
|
|
8368
9292
|
terminals.get(
|
|
8369
9293
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -8387,8 +9311,8 @@ terminals.get(
|
|
|
8387
9311
|
});
|
|
8388
9312
|
}
|
|
8389
9313
|
);
|
|
8390
|
-
var killSchema =
|
|
8391
|
-
signal:
|
|
9314
|
+
var killSchema = z18.object({
|
|
9315
|
+
signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
8392
9316
|
});
|
|
8393
9317
|
terminals.post(
|
|
8394
9318
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -8402,8 +9326,8 @@ terminals.post(
|
|
|
8402
9326
|
return c.json({ success: true, message: "Terminal killed" });
|
|
8403
9327
|
}
|
|
8404
9328
|
);
|
|
8405
|
-
var writeSchema =
|
|
8406
|
-
input:
|
|
9329
|
+
var writeSchema = z18.object({
|
|
9330
|
+
input: z18.string()
|
|
8407
9331
|
});
|
|
8408
9332
|
terminals.post(
|
|
8409
9333
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -8588,18 +9512,18 @@ data: ${JSON.stringify({ status: "stopped" })}
|
|
|
8588
9512
|
init_db();
|
|
8589
9513
|
import { Hono as Hono5 } from "hono";
|
|
8590
9514
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
8591
|
-
import { z as
|
|
9515
|
+
import { z as z19 } from "zod";
|
|
8592
9516
|
init_config();
|
|
8593
9517
|
var tasks = new Hono5();
|
|
8594
9518
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
8595
|
-
var createTaskSchema =
|
|
8596
|
-
prompt:
|
|
8597
|
-
outputSchema:
|
|
8598
|
-
webhookUrl:
|
|
8599
|
-
model:
|
|
8600
|
-
workingDirectory:
|
|
8601
|
-
name:
|
|
8602
|
-
maxIterations:
|
|
9519
|
+
var createTaskSchema = z19.object({
|
|
9520
|
+
prompt: z19.string().min(1),
|
|
9521
|
+
outputSchema: z19.record(z19.string(), z19.unknown()),
|
|
9522
|
+
webhookUrl: z19.string().url().optional(),
|
|
9523
|
+
model: z19.string().optional(),
|
|
9524
|
+
workingDirectory: z19.string().optional(),
|
|
9525
|
+
name: z19.string().optional(),
|
|
9526
|
+
maxIterations: z19.number().int().min(1).max(500).optional()
|
|
8603
9527
|
});
|
|
8604
9528
|
tasks.post(
|
|
8605
9529
|
"/",
|
|
@@ -8678,6 +9602,15 @@ tasks.get("/:id", async (c) => {
|
|
|
8678
9602
|
if (!task?.enabled) {
|
|
8679
9603
|
return c.json({ error: "Session is not a task" }, 400);
|
|
8680
9604
|
}
|
|
9605
|
+
let browserRecordings = [];
|
|
9606
|
+
try {
|
|
9607
|
+
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
9608
|
+
if (isRemoteConfigured2()) {
|
|
9609
|
+
const files = await storageQueries2.getSessionFiles(id);
|
|
9610
|
+
browserRecordings = files.filter((f) => f.category === "browser-recording").map((f) => ({ fileName: f.fileName, downloadUrl: f.downloadUrl, sizeBytes: f.sizeBytes }));
|
|
9611
|
+
}
|
|
9612
|
+
} catch {
|
|
9613
|
+
}
|
|
8681
9614
|
return c.json({
|
|
8682
9615
|
taskId: id,
|
|
8683
9616
|
status: task.status,
|
|
@@ -8687,7 +9620,8 @@ tasks.get("/:id", async (c) => {
|
|
|
8687
9620
|
model: session.model,
|
|
8688
9621
|
name: session.name,
|
|
8689
9622
|
createdAt: session.createdAt.toISOString(),
|
|
8690
|
-
updatedAt: session.updatedAt.toISOString()
|
|
9623
|
+
updatedAt: session.updatedAt.toISOString(),
|
|
9624
|
+
browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
|
|
8691
9625
|
});
|
|
8692
9626
|
});
|
|
8693
9627
|
tasks.post("/:id/cancel", async (c) => {
|
|
@@ -8735,10 +9669,10 @@ init_config();
|
|
|
8735
9669
|
init_db();
|
|
8736
9670
|
|
|
8737
9671
|
// src/utils/dependencies.ts
|
|
8738
|
-
import { exec as
|
|
8739
|
-
import { promisify as
|
|
9672
|
+
import { exec as exec6 } from "child_process";
|
|
9673
|
+
import { promisify as promisify6 } from "util";
|
|
8740
9674
|
import { platform as platform2 } from "os";
|
|
8741
|
-
var
|
|
9675
|
+
var execAsync6 = promisify6(exec6);
|
|
8742
9676
|
function getInstallInstructions() {
|
|
8743
9677
|
const os2 = platform2();
|
|
8744
9678
|
if (os2 === "darwin") {
|
|
@@ -8771,7 +9705,7 @@ Install tmux:
|
|
|
8771
9705
|
}
|
|
8772
9706
|
async function checkTmux() {
|
|
8773
9707
|
try {
|
|
8774
|
-
const { stdout } = await
|
|
9708
|
+
const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
|
|
8775
9709
|
const version = stdout.trim();
|
|
8776
9710
|
return {
|
|
8777
9711
|
available: true,
|
|
@@ -8820,11 +9754,11 @@ function getWebDirectory() {
|
|
|
8820
9754
|
try {
|
|
8821
9755
|
const currentDir = dirname7(fileURLToPath4(import.meta.url));
|
|
8822
9756
|
const webDir = resolve10(currentDir, "..", "web");
|
|
8823
|
-
if (existsSync15(webDir) && existsSync15(
|
|
9757
|
+
if (existsSync15(webDir) && existsSync15(join10(webDir, "package.json"))) {
|
|
8824
9758
|
return webDir;
|
|
8825
9759
|
}
|
|
8826
9760
|
const altWebDir = resolve10(currentDir, "..", "..", "web");
|
|
8827
|
-
if (existsSync15(altWebDir) && existsSync15(
|
|
9761
|
+
if (existsSync15(altWebDir) && existsSync15(join10(altWebDir, "package.json"))) {
|
|
8828
9762
|
return altWebDir;
|
|
8829
9763
|
}
|
|
8830
9764
|
return null;
|
|
@@ -8882,20 +9816,20 @@ async function findWebPort(preferredPort) {
|
|
|
8882
9816
|
return { port: preferredPort, alreadyRunning: false };
|
|
8883
9817
|
}
|
|
8884
9818
|
function hasProductionBuild(webDir) {
|
|
8885
|
-
const buildIdPath =
|
|
9819
|
+
const buildIdPath = join10(webDir, ".next", "BUILD_ID");
|
|
8886
9820
|
return existsSync15(buildIdPath);
|
|
8887
9821
|
}
|
|
8888
9822
|
function hasSourceFiles(webDir) {
|
|
8889
|
-
const appDir =
|
|
8890
|
-
const pagesDir =
|
|
8891
|
-
const rootAppDir =
|
|
8892
|
-
const rootPagesDir =
|
|
9823
|
+
const appDir = join10(webDir, "src", "app");
|
|
9824
|
+
const pagesDir = join10(webDir, "src", "pages");
|
|
9825
|
+
const rootAppDir = join10(webDir, "app");
|
|
9826
|
+
const rootPagesDir = join10(webDir, "pages");
|
|
8893
9827
|
return existsSync15(appDir) || existsSync15(pagesDir) || existsSync15(rootAppDir) || existsSync15(rootPagesDir);
|
|
8894
9828
|
}
|
|
8895
9829
|
function getStandaloneServerPath(webDir) {
|
|
8896
9830
|
const possiblePaths2 = [
|
|
8897
|
-
|
|
8898
|
-
|
|
9831
|
+
join10(webDir, ".next", "standalone", "server.js"),
|
|
9832
|
+
join10(webDir, ".next", "standalone", "web", "server.js")
|
|
8899
9833
|
];
|
|
8900
9834
|
for (const serverPath of possiblePaths2) {
|
|
8901
9835
|
if (existsSync15(serverPath)) {
|
|
@@ -8938,13 +9872,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
8938
9872
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
8939
9873
|
return { process: null, port: actualPort };
|
|
8940
9874
|
}
|
|
8941
|
-
const usePnpm = existsSync15(
|
|
8942
|
-
const useNpm = !usePnpm && existsSync15(
|
|
9875
|
+
const usePnpm = existsSync15(join10(webDir, "pnpm-lock.yaml"));
|
|
9876
|
+
const useNpm = !usePnpm && existsSync15(join10(webDir, "package-lock.json"));
|
|
8943
9877
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
8944
9878
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
8945
9879
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
8946
9880
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
8947
|
-
const runtimeConfigPath =
|
|
9881
|
+
const runtimeConfigPath = join10(webDir, "runtime-config.json");
|
|
8948
9882
|
try {
|
|
8949
9883
|
writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
8950
9884
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|