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.
Files changed (144) hide show
  1. package/dist/agent/index.d.ts +2 -2
  2. package/dist/agent/index.js +812 -30
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +1064 -165
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.js.map +1 -1
  7. package/dist/{index-Dn-eCGLe.d.ts → index-BhKQYdOx.d.ts} +11 -1
  8. package/dist/index.d.ts +3 -3
  9. package/dist/index.js +1080 -146
  10. package/dist/index.js.map +1 -1
  11. package/dist/{search-DINnDTgj.d.ts → search-DkNNtC5A.d.ts} +1 -0
  12. package/dist/server/index.js +1080 -146
  13. package/dist/server/index.js.map +1 -1
  14. package/dist/skills/default/qa.md +317 -0
  15. package/dist/tools/index.d.ts +30 -3
  16. package/dist/tools/index.js +428 -7
  17. package/dist/tools/index.js.map +1 -1
  18. package/package.json +3 -1
  19. package/src/skills/default/qa.md +317 -0
  20. package/web/.next/BUILD_ID +1 -1
  21. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  22. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  23. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  24. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  25. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  26. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  28. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  43. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  44. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  85. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  86. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  88. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  89. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  91. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  93. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  94. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bfc8ef7d._.js → 2374f_1225fb9d._.js} +1 -1
  95. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c61a33b3._.js → 2374f_2775224f._.js} +1 -1
  96. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_de60e6ea._.js → 2374f_2eac907f._.js} +1 -1
  97. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ab5b97d8._.js → 2374f_32fd5733._.js} +1 -1
  98. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_e366206f._.js → 2374f_3db08933._.js} +1 -1
  99. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_68abddfe._.js → 2374f_51500fd2._.js} +1 -1
  100. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5d0b3394._.js → 2374f_6f1ea3a7._.js} +1 -1
  101. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5c78460e._.js → 2374f_708e1a2e._.js} +1 -1
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_04a544c8._.js → 2374f_7d957e38._.js} +1 -1
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_72fb9db7._.js → 2374f_8385a57b._.js} +1 -1
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d5f5b9ba._.js → 2374f_95e53328._.js} +1 -1
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5e9eb6da._.js → 2374f_a19628a3._.js} +1 -1
  106. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_db790cfe._.js → 2374f_a90b70ee._.js} +1 -1
  107. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_45534372._.js → 2374f_f1f229dd._.js} +1 -1
  108. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__d04c460d._.js → [root-of-the-server]__4085bf35._.js} +4 -4
  109. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__aa788b85._.js → [root-of-the-server]__5bde4c40._.js} +2 -2
  110. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_6fb589ac._.js → web_14735895._.js} +2 -2
  111. package/web/.next/standalone/web/.next/server/chunks/ssr/web_2b3a5919._.js +1 -1
  112. package/web/.next/standalone/web/.next/server/chunks/ssr/web_38156da8._.js +1 -1
  113. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  114. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  115. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  116. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  117. package/web/.next/standalone/web/.next/static/chunks/{054deec0c7b19894.js → 4a490bc538aa0940.js} +3 -3
  118. package/web/.next/{static/chunks/eea48be65cdb3f4b.js → standalone/web/.next/static/chunks/4b6a9454bc6460c5.js} +3 -3
  119. package/web/.next/standalone/web/.next/static/chunks/8b53c110c5a6f9a0.css +1 -0
  120. package/web/.next/standalone/web/.next/static/chunks/{7fb141141caa4fac.js → c2dccd8f56d8eb5a.js} +4 -4
  121. package/web/.next/{static/chunks/054deec0c7b19894.js → standalone/web/.next/static/static/chunks/4a490bc538aa0940.js} +3 -3
  122. package/web/.next/standalone/web/.next/static/static/chunks/{eea48be65cdb3f4b.js → 4b6a9454bc6460c5.js} +3 -3
  123. package/web/.next/standalone/web/.next/static/static/chunks/8b53c110c5a6f9a0.css +1 -0
  124. package/web/.next/standalone/web/.next/static/static/chunks/{7fb141141caa4fac.js → c2dccd8f56d8eb5a.js} +4 -4
  125. package/web/.next/standalone/web/src/components/ai-elements/browser-recording.tsx +100 -0
  126. package/web/.next/standalone/web/src/components/browser-preview.tsx +196 -0
  127. package/web/.next/standalone/web/src/components/chat-interface.tsx +92 -3
  128. package/web/.next/standalone/web/src/lib/api.ts +95 -0
  129. package/web/.next/{standalone/web/.next/static/static/chunks/054deec0c7b19894.js → static/chunks/4a490bc538aa0940.js} +3 -3
  130. package/web/.next/{standalone/web/.next/static/chunks/eea48be65cdb3f4b.js → static/chunks/4b6a9454bc6460c5.js} +3 -3
  131. package/web/.next/static/chunks/8b53c110c5a6f9a0.css +1 -0
  132. package/web/.next/static/chunks/{7fb141141caa4fac.js → c2dccd8f56d8eb5a.js} +4 -4
  133. package/web/.next/standalone/web/.next/static/chunks/1f42a42914068041.css +0 -1
  134. package/web/.next/standalone/web/.next/static/static/chunks/1f42a42914068041.css +0 -1
  135. package/web/.next/static/chunks/1f42a42914068041.css +0 -1
  136. /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_buildManifest.js +0 -0
  137. /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_clientMiddlewareManifest.json +0 -0
  138. /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_ssgManifest.js +0 -0
  139. /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_buildManifest.js +0 -0
  140. /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_clientMiddlewareManifest.json +0 -0
  141. /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_ssgManifest.js +0 -0
  142. /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_buildManifest.js +0 -0
  143. /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → NuySHGSR0CchKU9zo42LK}/_clientMiddlewareManifest.json +0 -0
  144. /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
- 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,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 tool12,
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 z13 } from "zod";
1834
- import { nanoid as nanoid3 } from "nanoid";
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: readdir6 } = await import("fs/promises");
2138
- const entries = await readdir6(terminalsDir, { withFileTypes: true });
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(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
  });
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(command, options.workingDirectory, {
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(command, options.workingDirectory, options.onOutput);
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: exec6 } = await import("child_process");
2791
- const { promisify: promisify6 } = await import("util");
2792
- const execAsync6 = promisify6(exec6);
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 execAsync6(checkCmd);
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 cleanup = () => {
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
- cleanup();
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 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.
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: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
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 done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
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
- fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
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] = tool12({
6980
+ wrappedTools[name] = tool13({
6256
6981
  description: originalTool.description || "",
6257
- inputSchema: originalTool.inputSchema || z13.object({}),
6982
+ inputSchema: originalTool.inputSchema || z14.object({}),
6258
6983
  execute: async (input, toolOptions) => {
6259
- const toolCallId = toolOptions.toolCallId || nanoid3();
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 exec6 = await execution;
7002
+ const exec7 = await execution;
6278
7003
  if (!approved) {
6279
7004
  const reason = resolverData?.reason || "User rejected the tool execution";
6280
- await toolExecutionQueries.reject(exec6.id);
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(exec6.id);
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(exec6.id, result);
7019
+ await toolExecutionQueries.complete(exec7.id, result);
6295
7020
  return result;
6296
7021
  } catch (error) {
6297
- await toolExecutionQueries.complete(exec6.id, null, error.message);
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 join8 } from "path";
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 z14 } from "zod";
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 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";
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 = 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()
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 = z14.object({
6425
- limit: z14.string().optional(),
6426
- offset: z14.string().optional()
7149
+ var paginationQuerySchema = z15.object({
7150
+ limit: z15.string().optional(),
7151
+ offset: z15.string().optional()
6427
7152
  });
6428
- var messagesQuerySchema = z14.object({
6429
- limit: z14.string().optional()
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 = z14.object({
6569
- model: z14.string().optional(),
6570
- name: z14.string().optional(),
6571
- toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
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 = z14.object({
6642
- text: z14.string()
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 = 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()
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 join5(appDataDir, "attachments", sessionId);
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 = join5(dir, filename);
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 = 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);
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 = 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);
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 = join5(dir, file);
7691
+ const filePath = join7(dir, file);
6967
7692
  unlinkSync(filePath);
6968
7693
  return c.json({ success: true, id: attachmentId });
6969
7694
  });
6970
- var filesQuerySchema = z14.object({
6971
- query: z14.string().optional(),
7695
+ var filesQuerySchema = z15.object({
7696
+ query: z15.string().optional(),
6972
7697
  // Filter query (e.g., "src/com" to match "src/components")
6973
- limit: z14.string().optional()
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 readdir5(currentDir, { withFileTypes: true });
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 = join5(currentDir, entry.name);
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 = extname7(entry.name).toLowerCase();
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 z15 } from "zod";
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 join6 } from "path";
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 nanoid5 } from "nanoid";
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 = z15.object({
7317
- type: z15.enum(["image", "file"]),
7318
- data: z15.string(),
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: z15.string().optional(),
7321
- filename: z15.string().optional()
8141
+ mediaType: z16.string().optional(),
8142
+ filename: z16.string().optional()
7322
8143
  });
7323
- var runPromptSchema = z15.object({
7324
- prompt: z15.string(),
8144
+ var runPromptSchema = z16.object({
8145
+ prompt: z16.string(),
7325
8146
  // Can be empty if attachments are provided
7326
- attachments: z15.array(attachmentSchema).optional()
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 = 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()
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 = z15.object({
7339
- reason: z15.string().optional()
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 join6(appDataDir, "attachments", sessionId);
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 = join6(attachmentsDir, filename);
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}_${nanoid5(10)}`;
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}_${nanoid5(10)}`;
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 z16 } from "zod";
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 join7 } from "path";
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
- join7(__dirname, "../package.json"),
9058
+ join9(__dirname, "../package.json"),
8135
9059
  // From dist/server -> dist/../package.json
8136
- join7(__dirname, "../../package.json"),
9060
+ join9(__dirname, "../../package.json"),
8137
9061
  // From dist/server (if nested differently)
8138
- join7(__dirname, "../../../package.json"),
9062
+ join9(__dirname, "../../../package.json"),
8139
9063
  // From src/server/routes (development)
8140
- join7(process.cwd(), "package.json")
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 = z16.object({
8238
- provider: z16.string(),
8239
- apiKey: z16.string().min(1)
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 z17 } from "zod";
9202
+ import { z as z18 } from "zod";
8279
9203
  init_db();
8280
9204
  var terminals = new Hono4();
8281
- var spawnSchema = z17.object({
8282
- command: z17.string(),
8283
- cwd: z17.string().optional(),
8284
- name: z17.string().optional()
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 = z17.object({
8366
- tail: z17.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
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 = z17.object({
8391
- signal: z17.enum(["SIGTERM", "SIGKILL"]).optional()
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 = z17.object({
8406
- input: z17.string()
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 z18 } from "zod";
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 = 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()
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 exec5 } from "child_process";
8739
- import { promisify as promisify5 } from "util";
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 execAsync5 = promisify5(exec5);
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 execAsync5("tmux -V", { timeout: 5e3 });
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(join8(webDir, "package.json"))) {
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(join8(altWebDir, "package.json"))) {
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 = join8(webDir, ".next", "BUILD_ID");
9819
+ const buildIdPath = join10(webDir, ".next", "BUILD_ID");
8886
9820
  return existsSync15(buildIdPath);
8887
9821
  }
8888
9822
  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");
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
- join8(webDir, ".next", "standalone", "server.js"),
8898
- join8(webDir, ".next", "standalone", "web", "server.js")
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(join8(webDir, "pnpm-lock.yaml"));
8942
- const useNpm = !usePnpm && existsSync15(join8(webDir, "package-lock.json"));
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 = join8(webDir, "runtime-config.json");
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}`);