sparkecoder 0.1.64 → 0.1.66

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