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
@@ -9,6 +9,25 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/db/remote.ts
12
+ var remote_exports = {};
13
+ __export(remote_exports, {
14
+ closeRemoteDatabase: () => closeRemoteDatabase,
15
+ initRemoteDatabase: () => initRemoteDatabase,
16
+ isRemoteConfigured: () => isRemoteConfigured,
17
+ remoteActiveStreamQueries: () => remoteActiveStreamQueries,
18
+ remoteCheckpointQueries: () => remoteCheckpointQueries,
19
+ remoteFileBackupQueries: () => remoteFileBackupQueries,
20
+ remoteIndexStatusQueries: () => remoteIndexStatusQueries,
21
+ remoteIndexedChunkQueries: () => remoteIndexedChunkQueries,
22
+ remoteMessageQueries: () => remoteMessageQueries,
23
+ remoteSessionQueries: () => remoteSessionQueries,
24
+ remoteSkillQueries: () => remoteSkillQueries,
25
+ remoteSubagentQueries: () => remoteSubagentQueries,
26
+ remoteTerminalQueries: () => remoteTerminalQueries,
27
+ remoteTodoQueries: () => remoteTodoQueries,
28
+ remoteToolExecutionQueries: () => remoteToolExecutionQueries,
29
+ storageQueries: () => storageQueries
30
+ });
12
31
  function initRemoteDatabase(serverUrl, key) {
13
32
  remoteServerUrl = serverUrl.replace(/\/$/, "");
14
33
  authKey = key;
@@ -17,6 +36,9 @@ function closeRemoteDatabase() {
17
36
  remoteServerUrl = null;
18
37
  authKey = null;
19
38
  }
39
+ function isRemoteConfigured() {
40
+ return !!remoteServerUrl && !!authKey;
41
+ }
20
42
  function parseDates(obj) {
21
43
  if (obj === null || obj === void 0) return obj;
22
44
  if (Array.isArray(obj)) return obj.map(parseDates);
@@ -64,7 +86,29 @@ async function api(path, options = {}) {
64
86
  }
65
87
  return parseDates(parsed);
66
88
  }
67
- var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexStatusQueries;
89
+ async function storageApi(path, options = {}) {
90
+ if (!remoteServerUrl || !authKey) {
91
+ throw new Error("Remote database not initialized");
92
+ }
93
+ const url = `${remoteServerUrl}/storage${path}`;
94
+ const init = {
95
+ method: options.method || "GET",
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ "Authorization": `Bearer ${authKey}`
99
+ }
100
+ };
101
+ if (options.body) {
102
+ init.body = JSON.stringify(options.body);
103
+ }
104
+ const response = await fetch(url, init);
105
+ if (!response.ok) {
106
+ const errorText = await response.text().catch(() => "Unknown error");
107
+ throw new Error(`Storage API error ${response.status}: ${errorText}`);
108
+ }
109
+ return response.json();
110
+ }
111
+ var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteTerminalQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries, storageQueries;
68
112
  var init_remote = __esm({
69
113
  "src/db/remote.ts"() {
70
114
  "use strict";
@@ -203,6 +247,34 @@ var init_remote = __esm({
203
247
  return result.isLoaded;
204
248
  }
205
249
  };
250
+ remoteTerminalQueries = {
251
+ create(data) {
252
+ return api("/terminals", { method: "POST", body: data });
253
+ },
254
+ getById(id) {
255
+ return api(`/terminals/${id}`).catch(() => void 0);
256
+ },
257
+ getBySession(sessionId) {
258
+ return api(`/terminals/session/${sessionId}`);
259
+ },
260
+ getRunning(sessionId) {
261
+ return api(`/terminals/session/${sessionId}/running`);
262
+ },
263
+ updateStatus(id, status, exitCode, error) {
264
+ return api(`/terminals/${id}`, { method: "PATCH", body: { status, exitCode, error } });
265
+ },
266
+ updatePid(id, pid) {
267
+ return api(`/terminals/${id}`, { method: "PATCH", body: { pid } });
268
+ },
269
+ async delete(id) {
270
+ const result = await api(`/terminals/${id}`, { method: "DELETE" });
271
+ return result?.success ?? false;
272
+ },
273
+ async deleteBySession(sessionId) {
274
+ const result = await api(`/terminals/session/${sessionId}`, { method: "DELETE" });
275
+ return result.deleted;
276
+ }
277
+ };
206
278
  remoteActiveStreamQueries = {
207
279
  create(sessionId, streamId) {
208
280
  return api("/streams", { method: "POST", body: { sessionId, streamId } });
@@ -306,6 +378,41 @@ var init_remote = __esm({
306
378
  return result.deleted;
307
379
  }
308
380
  };
381
+ remoteIndexedChunkQueries = {
382
+ upsert(_db, data) {
383
+ return api("/indexed-chunks", { method: "POST", body: data });
384
+ },
385
+ batchUpsert(_db, chunks) {
386
+ return api("/indexed-chunks/batch", {
387
+ method: "POST",
388
+ body: { chunks }
389
+ });
390
+ },
391
+ getById(_db, id) {
392
+ return api(`/indexed-chunks/${id}`).catch(() => void 0);
393
+ },
394
+ getByNamespace(_db, namespace) {
395
+ return api(`/indexed-chunks/namespace/${namespace}`);
396
+ },
397
+ getByFilePath(_db, namespace, filePath) {
398
+ return api(`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`);
399
+ },
400
+ async deleteByNamespace(_db, namespace) {
401
+ const result = await api(`/indexed-chunks/namespace/${namespace}`, { method: "DELETE" });
402
+ return result.deleted;
403
+ },
404
+ async deleteByFilePath(_db, namespace, filePath) {
405
+ const result = await api(
406
+ `/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`,
407
+ { method: "DELETE" }
408
+ );
409
+ return result.deleted;
410
+ },
411
+ async countByNamespace(_db, namespace) {
412
+ const result = await api(`/indexed-chunks/namespace/${namespace}/count`);
413
+ return result.count;
414
+ }
415
+ };
309
416
  remoteIndexStatusQueries = {
310
417
  upsert(_db, data) {
311
418
  return api("/index-status", {
@@ -328,6 +435,27 @@ var init_remote = __esm({
328
435
  return api("/index-status");
329
436
  }
330
437
  };
438
+ storageQueries = {
439
+ async getUploadUrl(sessionId, fileName, contentType, category) {
440
+ return storageApi("/upload-url", {
441
+ method: "POST",
442
+ body: { sessionId, fileName, contentType, category }
443
+ });
444
+ },
445
+ async getSessionFiles(sessionId) {
446
+ const result = await storageApi(`/files/${sessionId}`);
447
+ return result.files;
448
+ },
449
+ async getDownloadUrl(fileId) {
450
+ return storageApi(`/download/${fileId}`);
451
+ },
452
+ async deleteFile(fileId) {
453
+ await storageApi(`/files/${fileId}`, { method: "DELETE" });
454
+ },
455
+ async updateFile(fileId, data) {
456
+ await storageApi(`/files/${fileId}`, { method: "PATCH", body: data });
457
+ }
458
+ };
331
459
  }
332
460
  });
333
461
 
@@ -1645,6 +1773,280 @@ 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/server/index.ts
1649
2051
  import "dotenv/config";
1650
2052
  import { Hono as Hono6 } from "hono";
@@ -1652,7 +2054,7 @@ import { serve } from "@hono/node-server";
1652
2054
  import { cors } from "hono/cors";
1653
2055
  import { logger } from "hono/logger";
1654
2056
  import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
1655
- import { resolve as resolve10, dirname as dirname7, join as join8 } from "path";
2057
+ import { resolve as resolve10, dirname as dirname7, join as join10 } from "path";
1656
2058
  import { spawn as spawn2 } from "child_process";
1657
2059
  import { createServer as createNetServer } from "net";
1658
2060
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -1661,17 +2063,17 @@ import { fileURLToPath as fileURLToPath4 } from "url";
1661
2063
  init_db();
1662
2064
  import { Hono } from "hono";
1663
2065
  import { zValidator } from "@hono/zod-validator";
1664
- import { z as z14 } from "zod";
2066
+ import { z as z15 } from "zod";
1665
2067
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
1666
- import { readdir as readdir5 } from "fs/promises";
1667
- import { join as join5, basename as basename4, extname as extname7, relative as relative9 } from "path";
1668
- import { nanoid as nanoid4 } from "nanoid";
2068
+ import { readdir as readdir6 } from "fs/promises";
2069
+ import { join as join7, basename as basename5, extname as extname8, relative as relative9 } from "path";
2070
+ import { nanoid as nanoid5 } from "nanoid";
1669
2071
 
1670
2072
  // src/agent/index.ts
1671
2073
  import {
1672
2074
  streamText as streamText2,
1673
2075
  generateText as generateText3,
1674
- tool as tool12,
2076
+ tool as tool13,
1675
2077
  stepCountIs as stepCountIs2
1676
2078
  } from "ai";
1677
2079
 
@@ -1852,8 +2254,8 @@ var SUBAGENT_MODELS = {
1852
2254
  // src/agent/index.ts
1853
2255
  init_db();
1854
2256
  init_config();
1855
- import { z as z13 } from "zod";
1856
- import { nanoid as nanoid3 } from "nanoid";
2257
+ import { z as z14 } from "zod";
2258
+ import { nanoid as nanoid4 } from "nanoid";
1857
2259
 
1858
2260
  // src/tools/bash.ts
1859
2261
  import { tool } from "ai";
@@ -2139,8 +2541,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
2139
2541
  const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
2140
2542
  const terminals2 = [];
2141
2543
  try {
2142
- const { readdir: readdir6 } = await import("fs/promises");
2143
- const entries = await readdir6(terminalsDir, { withFileTypes: true });
2544
+ const { readdir: readdir7 } = await import("fs/promises");
2545
+ const entries = await readdir7(terminalsDir, { withFileTypes: true });
2144
2546
  for (const entry of entries) {
2145
2547
  if (entry.isDirectory()) {
2146
2548
  const meta = await getMeta(entry.name, workingDirectory, sessionId);
@@ -2202,6 +2604,29 @@ function isBlockedCommand(command) {
2202
2604
  (blocked) => normalizedCommand.includes(blocked.toLowerCase())
2203
2605
  );
2204
2606
  }
2607
+ var BROWSER_STREAM_BASE_PORT = 9223;
2608
+ var sessionBrowserPorts = /* @__PURE__ */ new Map();
2609
+ var nextPortOffset = 0;
2610
+ function getBrowserStreamPort(sessionId) {
2611
+ let port = sessionBrowserPorts.get(sessionId);
2612
+ if (!port) {
2613
+ port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
2614
+ sessionBrowserPorts.set(sessionId, port);
2615
+ }
2616
+ return port;
2617
+ }
2618
+ function hasAgentBrowserCommand(command) {
2619
+ return /\bagent-browser\b/.test(command);
2620
+ }
2621
+ function isAgentBrowserCloseCommand(command) {
2622
+ return /\bagent-browser\s+(close|close\s+--all)\b/.test(command);
2623
+ }
2624
+ function injectBrowserStreamPort(command, port) {
2625
+ return command.replace(
2626
+ /\bagent-browser\b/g,
2627
+ `AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
2628
+ );
2629
+ }
2205
2630
  var bashInputSchema = z2.object({
2206
2631
  command: z2.string().optional().describe("The command to execute. Required for running new commands."),
2207
2632
  background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
@@ -2367,6 +2792,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
2367
2792
  exitCode: 1
2368
2793
  };
2369
2794
  }
2795
+ let actualCommand = command;
2796
+ const hasAgentBrowser = hasAgentBrowserCommand(command);
2797
+ const browserClose = isAgentBrowserCloseCommand(command);
2798
+ let browserPort;
2799
+ if (hasAgentBrowser) {
2800
+ browserPort = getBrowserStreamPort(options.sessionId);
2801
+ if (!browserClose) {
2802
+ actualCommand = injectBrowserStreamPort(command, browserPort);
2803
+ }
2804
+ }
2370
2805
  const canUseTmux = await shouldUseTmux();
2371
2806
  if (background) {
2372
2807
  if (!canUseTmux) {
@@ -2376,8 +2811,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
2376
2811
  };
2377
2812
  }
2378
2813
  const terminalId = generateTerminalId();
2379
- options.onProgress?.({ terminalId, status: "started", command });
2380
- const result = await runBackground(command, options.workingDirectory, {
2814
+ options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
2815
+ const result = await runBackground(actualCommand, options.workingDirectory, {
2381
2816
  sessionId: options.sessionId,
2382
2817
  terminalId
2383
2818
  });
@@ -2390,16 +2825,22 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
2390
2825
  }
2391
2826
  if (canUseTmux) {
2392
2827
  const terminalId = generateTerminalId();
2393
- options.onProgress?.({ terminalId, status: "started", command });
2828
+ options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
2394
2829
  try {
2395
- const result = await runSync(command, options.workingDirectory, {
2830
+ const result = await runSync(actualCommand, options.workingDirectory, {
2396
2831
  sessionId: options.sessionId,
2397
2832
  timeout: COMMAND_TIMEOUT,
2398
2833
  terminalId
2399
2834
  });
2400
2835
  const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
2401
2836
  options.onOutput?.(truncatedOutput);
2402
- options.onProgress?.({ terminalId, status: "completed", command });
2837
+ options.onProgress?.({
2838
+ terminalId,
2839
+ status: "completed",
2840
+ command,
2841
+ browserStreamPort: browserPort,
2842
+ browserClosed: browserClose || void 0
2843
+ });
2403
2844
  return {
2404
2845
  success: result.exitCode === 0,
2405
2846
  id: result.id,
@@ -2417,7 +2858,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
2417
2858
  };
2418
2859
  }
2419
2860
  } else {
2420
- const result = await execFallback(command, options.workingDirectory, options.onOutput);
2861
+ const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
2421
2862
  return {
2422
2863
  success: result.success,
2423
2864
  output: result.output,
@@ -2792,12 +3233,12 @@ function findNearestRoot(startDir, markers) {
2792
3233
  }
2793
3234
  async function commandExists(cmd) {
2794
3235
  try {
2795
- const { exec: exec6 } = await import("child_process");
2796
- const { promisify: promisify6 } = await import("util");
2797
- const execAsync6 = promisify6(exec6);
3236
+ const { exec: exec7 } = await import("child_process");
3237
+ const { promisify: promisify7 } = await import("util");
3238
+ const execAsync7 = promisify7(exec7);
2798
3239
  const isWindows = process.platform === "win32";
2799
3240
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
2800
- await execAsync6(checkCmd);
3241
+ await execAsync7(checkCmd);
2801
3242
  return true;
2802
3243
  } catch {
2803
3244
  return false;
@@ -3095,7 +3536,7 @@ async function createClient(serverId, handle, root) {
3095
3536
  const startTime = Date.now();
3096
3537
  let debounceTimer;
3097
3538
  let resolved = false;
3098
- const cleanup = () => {
3539
+ const cleanup2 = () => {
3099
3540
  if (debounceTimer) clearTimeout(debounceTimer);
3100
3541
  const listeners = diagnosticListeners.get(normalized);
3101
3542
  if (listeners) {
@@ -3109,7 +3550,7 @@ async function createClient(serverId, handle, root) {
3109
3550
  const finish = () => {
3110
3551
  if (resolved) return;
3111
3552
  resolved = true;
3112
- cleanup();
3553
+ cleanup2();
3113
3554
  resolve11(diagnostics.get(normalized) || []);
3114
3555
  };
3115
3556
  const onDiagnostic = () => {
@@ -5214,8 +5655,106 @@ function createTaskFailedTool(options) {
5214
5655
  });
5215
5656
  }
5216
5657
 
5658
+ // src/tools/upload-file.ts
5659
+ import { tool as tool12 } from "ai";
5660
+ import { z as z13 } from "zod";
5661
+ import { readFile as readFile9, stat as stat4 } from "fs/promises";
5662
+ import { join as join5, basename as basename4, extname as extname7 } from "path";
5663
+ var MIME_TYPES = {
5664
+ ".txt": "text/plain",
5665
+ ".md": "text/markdown",
5666
+ ".html": "text/html",
5667
+ ".css": "text/css",
5668
+ ".js": "application/javascript",
5669
+ ".ts": "application/typescript",
5670
+ ".json": "application/json",
5671
+ ".csv": "text/csv",
5672
+ ".xml": "application/xml",
5673
+ ".pdf": "application/pdf",
5674
+ ".png": "image/png",
5675
+ ".jpg": "image/jpeg",
5676
+ ".jpeg": "image/jpeg",
5677
+ ".gif": "image/gif",
5678
+ ".webp": "image/webp",
5679
+ ".svg": "image/svg+xml",
5680
+ ".mp4": "video/mp4",
5681
+ ".webm": "video/webm",
5682
+ ".mp3": "audio/mpeg",
5683
+ ".wav": "audio/wav",
5684
+ ".zip": "application/zip",
5685
+ ".tar": "application/x-tar",
5686
+ ".gz": "application/gzip"
5687
+ };
5688
+ function createUploadFileTool(options) {
5689
+ return tool12({
5690
+ 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.`,
5691
+ inputSchema: z13.object({
5692
+ path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
5693
+ name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
5694
+ }),
5695
+ execute: async (input) => {
5696
+ try {
5697
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
5698
+ if (!isRemoteConfigured2()) {
5699
+ return {
5700
+ success: false,
5701
+ error: "File upload is not available \u2014 remote server with GCS is not configured."
5702
+ };
5703
+ }
5704
+ const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
5705
+ try {
5706
+ await stat4(fullPath);
5707
+ } catch {
5708
+ return {
5709
+ success: false,
5710
+ error: `File not found: ${input.path}`
5711
+ };
5712
+ }
5713
+ const fileName = input.name || basename4(fullPath);
5714
+ const ext = extname7(fullPath).toLowerCase();
5715
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
5716
+ const uploadInfo = await storageQueries2.getUploadUrl(
5717
+ options.sessionId,
5718
+ fileName,
5719
+ contentType,
5720
+ "general"
5721
+ );
5722
+ const fileData = await readFile9(fullPath);
5723
+ const putRes = await fetch(uploadInfo.uploadUrl, {
5724
+ method: "PUT",
5725
+ headers: { "Content-Type": contentType },
5726
+ body: fileData
5727
+ });
5728
+ if (!putRes.ok) {
5729
+ return {
5730
+ success: false,
5731
+ error: `Upload failed: ${putRes.status} ${putRes.statusText}`
5732
+ };
5733
+ }
5734
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
5735
+ const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
5736
+ return {
5737
+ success: true,
5738
+ fileId: uploadInfo.fileId,
5739
+ fileName,
5740
+ sizeBytes: fileData.length,
5741
+ contentType,
5742
+ downloadUrl: downloadInfo.downloadUrl,
5743
+ expiresAt: downloadInfo.expiresAt
5744
+ };
5745
+ } catch (err) {
5746
+ return {
5747
+ success: false,
5748
+ error: `Upload failed: ${err.message}`
5749
+ };
5750
+ }
5751
+ }
5752
+ });
5753
+ }
5754
+
5217
5755
  // src/tools/index.ts
5218
5756
  init_semantic();
5757
+ init_remote();
5219
5758
  init_semantic_search();
5220
5759
  async function createTools(options) {
5221
5760
  const tools = {
@@ -5253,6 +5792,12 @@ async function createTools(options) {
5253
5792
  workingDirectory: options.workingDirectory
5254
5793
  })
5255
5794
  };
5795
+ if (isRemoteConfigured()) {
5796
+ tools.upload_file = createUploadFileTool({
5797
+ workingDirectory: options.workingDirectory,
5798
+ sessionId: options.sessionId
5799
+ });
5800
+ }
5256
5801
  if (options.enableSemanticSearch !== false) {
5257
5802
  try {
5258
5803
  if (isVectorGatewayConfigured()) {
@@ -5347,6 +5892,7 @@ You have access to powerful tools for:
5347
5892
  - **load_skill**: Load specialized knowledge documents for specific tasks
5348
5893
  - **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
5349
5894
  - **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
5895
+ - **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
5350
5896
 
5351
5897
 
5352
5898
  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.
@@ -5605,14 +6151,53 @@ function buildTaskPromptAddendum(outputSchema) {
5605
6151
  ## Task Mode
5606
6152
 
5607
6153
  You are running in **task mode**. You have been given a specific task to complete autonomously.
6154
+ 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.
6155
+ 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.
5608
6156
 
5609
6157
  ### Rules
5610
6158
  1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
5611
- 2. Keep working until the task is fully complete.
6159
+ 2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
5612
6160
  3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
5613
6161
  4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
5614
6162
  5. Do NOT stop without calling one of these two tools.
5615
6163
 
6164
+ ### Verification \u2014 BE EXTREMELY THOROUGH
6165
+ Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
6166
+
6167
+ **After making code changes:**
6168
+ - Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
6169
+ - **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
6170
+ - If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
6171
+ - If you created new files, verify they exist and contain what you expect.
6172
+
6173
+ **For UI / web changes:**
6174
+ - Start the dev server if it isn't already running (it might be so double check ur context)
6175
+ - **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
6176
+ - Check the dev server logs for errors or warnings.
6177
+ - If the app crashes or shows errors, fix them before completing.
6178
+
6179
+ **For backend / API changes:**
6180
+ - Test the endpoint with curl or a quick script to confirm it works as expected.
6181
+ - Check server logs for errors.
6182
+
6183
+ **For search and exploration tasks:**
6184
+ - Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
6185
+ - Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
6186
+ - Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
6187
+ - Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
6188
+
6189
+ **General verification checklist:**
6190
+ - Re-read the original task prompt and confirm every requirement has been addressed.
6191
+ - If the task asked for multiple things, verify EACH one individually.
6192
+ - If something doesn't look right, fix it \u2014 don't complete with known issues.
6193
+
6194
+ ### Use All Available Tools
6195
+ - **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
6196
+ - **explore_agent**: Use for codebase exploration and understanding before making changes.
6197
+ - **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
6198
+ - **todo**: Track your progress on multi-step tasks so you don't miss steps.
6199
+ - **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
6200
+
5616
6201
  ### Output Schema
5617
6202
  The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
5618
6203
  \`\`\`json
@@ -5620,7 +6205,7 @@ ${JSON.stringify(outputSchema, null, 2)}
5620
6205
  \`\`\`
5621
6206
 
5622
6207
  ### Completion Tools
5623
- - **\`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.
6208
+ - **\`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.
5624
6209
  - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
5625
6210
  `;
5626
6211
  }
@@ -6128,11 +6713,29 @@ ${prompt}` });
6128
6713
  const onComplete = (signal) => {
6129
6714
  completion.signal = signal;
6130
6715
  };
6716
+ let taskRecorder = null;
6717
+ const sessionId = this.session.id;
6718
+ const bashProgressHandler = (progress) => {
6719
+ options.onToolProgress?.({ toolName: "bash", data: progress });
6720
+ const port = progress.browserStreamPort;
6721
+ if (port && progress.status === "started") {
6722
+ Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
6723
+ const proxy = getOrCreateProxy2(sessionId, port);
6724
+ if (!taskRecorder) {
6725
+ Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
6726
+ taskRecorder = new FrameRecorder2(sessionId);
6727
+ taskRecorder.start();
6728
+ proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
6729
+ });
6730
+ }
6731
+ });
6732
+ }
6733
+ };
6131
6734
  const taskTools = await createTools({
6132
6735
  sessionId: this.session.id,
6133
6736
  workingDirectory: this.session.workingDirectory,
6134
6737
  skillsDirectories: config.resolvedSkillsDirectories,
6135
- onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
6738
+ onBashProgress: bashProgressHandler,
6136
6739
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
6137
6740
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
6138
6741
  taskTools: {
@@ -6203,12 +6806,24 @@ ${taskAddendum}`;
6203
6806
  if (completion.signal) {
6204
6807
  const sig = completion.signal;
6205
6808
  const finalStatus = sig.status;
6809
+ let fileUrls;
6810
+ if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
6811
+ const resultObj = sig.result;
6812
+ const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
6813
+ if (filePaths.length > 0) {
6814
+ fileUrls = await this.uploadTaskFiles(filePaths);
6815
+ }
6816
+ }
6817
+ const recordingUrls = await this.finishTaskRecording(taskRecorder);
6818
+ const allFileUrls = [...fileUrls || [], ...recordingUrls];
6206
6819
  const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
6207
6820
  fireWebhook(eventType, {
6208
6821
  status: finalStatus,
6209
6822
  result: sig.result,
6210
6823
  error: sig.error,
6211
- iterations: iteration
6824
+ iterations: iteration,
6825
+ fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
6826
+ browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
6212
6827
  });
6213
6828
  const updatedTask2 = {
6214
6829
  ...options.taskConfig,
@@ -6228,11 +6843,17 @@ ${taskAddendum}`;
6228
6843
  };
6229
6844
  }
6230
6845
  await this.context.addUserMessage(
6231
- "Continue working on the task. When done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
6846
+ "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."
6232
6847
  );
6233
6848
  }
6234
6849
  const timeoutError = `Task did not complete within ${maxIterations} iterations`;
6235
- fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
6850
+ const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
6851
+ fireWebhook("task.failed", {
6852
+ status: "failed",
6853
+ error: timeoutError,
6854
+ iterations: iteration,
6855
+ browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
6856
+ });
6236
6857
  const updatedTask = {
6237
6858
  ...options.taskConfig,
6238
6859
  status: "failed",
@@ -6244,6 +6865,113 @@ ${taskAddendum}`;
6244
6865
  });
6245
6866
  return { status: "failed", error: timeoutError, iterations: iteration };
6246
6867
  }
6868
+ /**
6869
+ * Stop a task-mode browser recording, encode to MP4, upload to GCS.
6870
+ * Returns download URLs for any recordings produced.
6871
+ */
6872
+ async finishTaskRecording(recorder) {
6873
+ try {
6874
+ const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
6875
+ destroyProxy2(this.session.id);
6876
+ } catch {
6877
+ }
6878
+ if (!recorder || recorder.frameCount === 0) {
6879
+ recorder?.clear();
6880
+ return [];
6881
+ }
6882
+ recorder.stop();
6883
+ try {
6884
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
6885
+ if (!isRemoteConfigured2()) {
6886
+ recorder.clear();
6887
+ return [];
6888
+ }
6889
+ const result = await recorder.encode();
6890
+ recorder.clear();
6891
+ if (!result) return [];
6892
+ const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
6893
+ const uploadInfo = await storageQueries2.getUploadUrl(
6894
+ this.session.id,
6895
+ `browser-recording-${Date.now()}.mp4`,
6896
+ "video/mp4",
6897
+ "browser-recording"
6898
+ );
6899
+ const fileData = await readFile11(result.path);
6900
+ await fetch(uploadInfo.uploadUrl, {
6901
+ method: "PUT",
6902
+ headers: { "Content-Type": "video/mp4" },
6903
+ body: fileData
6904
+ });
6905
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
6906
+ const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
6907
+ await unlink3(result.path).catch(() => {
6908
+ });
6909
+ console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
6910
+ return [dlInfo.downloadUrl];
6911
+ } catch (err) {
6912
+ console.error("[TASK] Failed to upload browser recording:", err.message);
6913
+ recorder.clear();
6914
+ return [];
6915
+ }
6916
+ }
6917
+ /**
6918
+ * Upload task output files to GCS via the remote server.
6919
+ * Returns an array of download URLs for successfully uploaded files.
6920
+ */
6921
+ async uploadTaskFiles(filePaths) {
6922
+ try {
6923
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
6924
+ if (!isRemoteConfigured2()) return [];
6925
+ const { readFile: readFile11 } = await import("fs/promises");
6926
+ const { join: join11, basename: basename6 } = await import("path");
6927
+ const urls = [];
6928
+ for (const filePath of filePaths) {
6929
+ try {
6930
+ const fullPath = filePath.startsWith("/") ? filePath : join11(this.session.workingDirectory, filePath);
6931
+ const fileName = basename6(fullPath);
6932
+ const ext = fileName.split(".").pop()?.toLowerCase() || "";
6933
+ const mimeMap = {
6934
+ pdf: "application/pdf",
6935
+ json: "application/json",
6936
+ csv: "text/csv",
6937
+ txt: "text/plain",
6938
+ md: "text/markdown",
6939
+ html: "text/html",
6940
+ png: "image/png",
6941
+ jpg: "image/jpeg",
6942
+ jpeg: "image/jpeg",
6943
+ gif: "image/gif",
6944
+ svg: "image/svg+xml",
6945
+ mp4: "video/mp4",
6946
+ zip: "application/zip"
6947
+ };
6948
+ const contentType = mimeMap[ext] || "application/octet-stream";
6949
+ const uploadInfo = await storageQueries2.getUploadUrl(
6950
+ this.session.id,
6951
+ fileName,
6952
+ contentType,
6953
+ "task-output"
6954
+ );
6955
+ const fileData = await readFile11(fullPath);
6956
+ await fetch(uploadInfo.uploadUrl, {
6957
+ method: "PUT",
6958
+ headers: { "Content-Type": contentType },
6959
+ body: fileData
6960
+ });
6961
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
6962
+ const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
6963
+ urls.push(downloadInfo.downloadUrl);
6964
+ console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
6965
+ } catch (err) {
6966
+ console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
6967
+ }
6968
+ }
6969
+ return urls;
6970
+ } catch (err) {
6971
+ console.error("[TASK] File upload failed:", err.message);
6972
+ return [];
6973
+ }
6974
+ }
6247
6975
  /**
6248
6976
  * Wrap tools to add approval checking
6249
6977
  */
@@ -6257,11 +6985,11 @@ ${taskAddendum}`;
6257
6985
  wrappedTools[name] = originalTool;
6258
6986
  continue;
6259
6987
  }
6260
- wrappedTools[name] = tool12({
6988
+ wrappedTools[name] = tool13({
6261
6989
  description: originalTool.description || "",
6262
- inputSchema: originalTool.inputSchema || z13.object({}),
6990
+ inputSchema: originalTool.inputSchema || z14.object({}),
6263
6991
  execute: async (input, toolOptions) => {
6264
- const toolCallId = toolOptions.toolCallId || nanoid3();
6992
+ const toolCallId = toolOptions.toolCallId || nanoid4();
6265
6993
  const execution = toolExecutionQueries.create({
6266
6994
  sessionId: this.session.id,
6267
6995
  toolName: name,
@@ -6279,10 +7007,10 @@ ${taskAddendum}`;
6279
7007
  const resolverData = approvalResolvers.get(toolCallId);
6280
7008
  approvalResolvers.delete(toolCallId);
6281
7009
  this.pendingApprovals.delete(toolCallId);
6282
- const exec6 = await execution;
7010
+ const exec7 = await execution;
6283
7011
  if (!approved) {
6284
7012
  const reason = resolverData?.reason || "User rejected the tool execution";
6285
- await toolExecutionQueries.reject(exec6.id);
7013
+ await toolExecutionQueries.reject(exec7.id);
6286
7014
  await sessionQueries.updateStatus(this.session.id, "active");
6287
7015
  return {
6288
7016
  status: "rejected",
@@ -6292,14 +7020,14 @@ ${taskAddendum}`;
6292
7020
  message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
6293
7021
  };
6294
7022
  }
6295
- await toolExecutionQueries.approve(exec6.id);
7023
+ await toolExecutionQueries.approve(exec7.id);
6296
7024
  await sessionQueries.updateStatus(this.session.id, "active");
6297
7025
  try {
6298
7026
  const result = await originalTool.execute(input, toolOptions);
6299
- await toolExecutionQueries.complete(exec6.id, result);
7027
+ await toolExecutionQueries.complete(exec7.id, result);
6300
7028
  return result;
6301
7029
  } catch (error) {
6302
- await toolExecutionQueries.complete(exec6.id, null, error.message);
7030
+ await toolExecutionQueries.complete(exec7.id, null, error.message);
6303
7031
  throw error;
6304
7032
  }
6305
7033
  }
@@ -6400,18 +7128,18 @@ function cleanupPendingInputs() {
6400
7128
  }
6401
7129
  }
6402
7130
  }
6403
- var createSessionSchema = z14.object({
6404
- name: z14.string().optional(),
6405
- workingDirectory: z14.string().optional(),
6406
- model: z14.string().optional(),
6407
- toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
7131
+ var createSessionSchema = z15.object({
7132
+ name: z15.string().optional(),
7133
+ workingDirectory: z15.string().optional(),
7134
+ model: z15.string().optional(),
7135
+ toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
6408
7136
  });
6409
- var paginationQuerySchema = z14.object({
6410
- limit: z14.string().optional(),
6411
- offset: z14.string().optional()
7137
+ var paginationQuerySchema = z15.object({
7138
+ limit: z15.string().optional(),
7139
+ offset: z15.string().optional()
6412
7140
  });
6413
- var messagesQuerySchema = z14.object({
6414
- limit: z14.string().optional()
7141
+ var messagesQuerySchema = z15.object({
7142
+ limit: z15.string().optional()
6415
7143
  });
6416
7144
  sessions.get(
6417
7145
  "/",
@@ -6550,10 +7278,10 @@ sessions.get("/:id/tools", async (c) => {
6550
7278
  count: executions.length
6551
7279
  });
6552
7280
  });
6553
- var updateSessionSchema = z14.object({
6554
- model: z14.string().optional(),
6555
- name: z14.string().optional(),
6556
- toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
7281
+ var updateSessionSchema = z15.object({
7282
+ model: z15.string().optional(),
7283
+ name: z15.string().optional(),
7284
+ toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
6557
7285
  });
6558
7286
  sessions.patch(
6559
7287
  "/:id",
@@ -6623,8 +7351,8 @@ sessions.post("/:id/clear", async (c) => {
6623
7351
  await agent.clearContext();
6624
7352
  return c.json({ success: true, sessionId: id });
6625
7353
  });
6626
- var pendingInputSchema = z14.object({
6627
- text: z14.string()
7354
+ var pendingInputSchema = z15.object({
7355
+ text: z15.string()
6628
7356
  });
6629
7357
  sessions.post(
6630
7358
  "/:id/pending-input",
@@ -6655,13 +7383,13 @@ sessions.get("/:id/pending-input", async (c) => {
6655
7383
  createdAt: pending.createdAt.toISOString()
6656
7384
  });
6657
7385
  });
6658
- var devtoolsContextSchema = z14.object({
6659
- url: z14.string(),
6660
- path: z14.string(),
6661
- pageName: z14.string().optional(),
6662
- screenWidth: z14.number().optional(),
6663
- screenHeight: z14.number().optional(),
6664
- devicePixelRatio: z14.number().optional()
7386
+ var devtoolsContextSchema = z15.object({
7387
+ url: z15.string(),
7388
+ path: z15.string(),
7389
+ pageName: z15.string().optional(),
7390
+ screenWidth: z15.number().optional(),
7391
+ screenHeight: z15.number().optional(),
7392
+ devicePixelRatio: z15.number().optional()
6665
7393
  });
6666
7394
  sessions.post(
6667
7395
  "/:id/devtools-context",
@@ -6829,7 +7557,7 @@ sessions.get("/:id/diff/:filePath", async (c) => {
6829
7557
  });
6830
7558
  function getAttachmentsDir(sessionId) {
6831
7559
  const appDataDir = getAppDataDirectory();
6832
- return join5(appDataDir, "attachments", sessionId);
7560
+ return join7(appDataDir, "attachments", sessionId);
6833
7561
  }
6834
7562
  function ensureAttachmentsDir(sessionId) {
6835
7563
  const dir = getAttachmentsDir(sessionId);
@@ -6850,7 +7578,7 @@ sessions.get("/:id/attachments", async (c) => {
6850
7578
  }
6851
7579
  const files = readdirSync(dir);
6852
7580
  const attachments = files.map((filename) => {
6853
- const filePath = join5(dir, filename);
7581
+ const filePath = join7(dir, filename);
6854
7582
  const stats = statSync2(filePath);
6855
7583
  return {
6856
7584
  id: filename.split("_")[0],
@@ -6882,10 +7610,10 @@ sessions.post("/:id/attachments", async (c) => {
6882
7610
  return c.json({ error: "No file provided" }, 400);
6883
7611
  }
6884
7612
  const dir = ensureAttachmentsDir(sessionId);
6885
- const id = nanoid4(10);
6886
- const ext = extname7(file.name) || "";
6887
- const safeFilename = `${id}_${basename4(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
6888
- const filePath = join5(dir, safeFilename);
7613
+ const id = nanoid5(10);
7614
+ const ext = extname8(file.name) || "";
7615
+ const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7616
+ const filePath = join7(dir, safeFilename);
6889
7617
  const arrayBuffer = await file.arrayBuffer();
6890
7618
  writeFileSync2(filePath, Buffer.from(arrayBuffer));
6891
7619
  return c.json({
@@ -6908,10 +7636,10 @@ sessions.post("/:id/attachments", async (c) => {
6908
7636
  return c.json({ error: "Missing filename or data" }, 400);
6909
7637
  }
6910
7638
  const dir = ensureAttachmentsDir(sessionId);
6911
- const id = nanoid4(10);
6912
- const ext = extname7(body.filename) || "";
6913
- const safeFilename = `${id}_${basename4(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
6914
- const filePath = join5(dir, safeFilename);
7639
+ const id = nanoid5(10);
7640
+ const ext = extname8(body.filename) || "";
7641
+ const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7642
+ const filePath = join7(dir, safeFilename);
6915
7643
  let base64Data = body.data;
6916
7644
  if (base64Data.includes(",")) {
6917
7645
  base64Data = base64Data.split(",")[1];
@@ -6948,14 +7676,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
6948
7676
  if (!file) {
6949
7677
  return c.json({ error: "Attachment not found" }, 404);
6950
7678
  }
6951
- const filePath = join5(dir, file);
7679
+ const filePath = join7(dir, file);
6952
7680
  unlinkSync(filePath);
6953
7681
  return c.json({ success: true, id: attachmentId });
6954
7682
  });
6955
- var filesQuerySchema = z14.object({
6956
- query: z14.string().optional(),
7683
+ var filesQuerySchema = z15.object({
7684
+ query: z15.string().optional(),
6957
7685
  // Filter query (e.g., "src/com" to match "src/components")
6958
- limit: z14.string().optional()
7686
+ limit: z15.string().optional()
6959
7687
  // Max results (default 50)
6960
7688
  });
6961
7689
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -7028,10 +7756,10 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
7028
7756
  return results;
7029
7757
  }
7030
7758
  try {
7031
- const entries = await readdir5(currentDir, { withFileTypes: true });
7759
+ const entries = await readdir6(currentDir, { withFileTypes: true });
7032
7760
  for (const entry of entries) {
7033
7761
  if (results.length >= limit * 2) break;
7034
- const fullPath = join5(currentDir, entry.name);
7762
+ const fullPath = join7(currentDir, entry.name);
7035
7763
  const relativePath = relative9(baseDir, fullPath);
7036
7764
  if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
7037
7765
  continue;
@@ -7039,7 +7767,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
7039
7767
  if (entry.name.startsWith(".")) {
7040
7768
  continue;
7041
7769
  }
7042
- const ext = extname7(entry.name).toLowerCase();
7770
+ const ext = extname8(entry.name).toLowerCase();
7043
7771
  if (IGNORED_EXTENSIONS.has(ext)) {
7044
7772
  continue;
7045
7773
  }
@@ -7128,14 +7856,70 @@ sessions.get(
7128
7856
  }
7129
7857
  }
7130
7858
  );
7859
+ sessions.get("/:id/session-files", async (c) => {
7860
+ const sessionId = c.req.param("id");
7861
+ try {
7862
+ const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7863
+ if (!isRemoteConfigured2()) {
7864
+ return c.json({ files: [] });
7865
+ }
7866
+ const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7867
+ const files = await storageQueries2.getSessionFiles(sessionId);
7868
+ return c.json({ sessionId, files });
7869
+ } catch (err) {
7870
+ console.error("Failed to get session files:", err.message);
7871
+ return c.json({ sessionId, files: [] });
7872
+ }
7873
+ });
7874
+ sessions.get("/files/:fileId/download", async (c) => {
7875
+ const fileId = c.req.param("fileId");
7876
+ try {
7877
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7878
+ if (!isRemoteConfigured2()) {
7879
+ return c.json({ error: "Remote server not configured" }, 503);
7880
+ }
7881
+ const result = await storageQueries2.getDownloadUrl(fileId);
7882
+ return c.json(result);
7883
+ } catch (err) {
7884
+ return c.json({ error: err.message }, 500);
7885
+ }
7886
+ });
7887
+ sessions.get("/:id/browser-recording", async (c) => {
7888
+ const sessionId = c.req.param("id");
7889
+ try {
7890
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7891
+ if (!isRemoteConfigured2()) {
7892
+ return c.json({ sessionId, recordings: [] });
7893
+ }
7894
+ const files = await storageQueries2.getSessionFiles(sessionId);
7895
+ const recordings = files.filter((f) => f.category === "browser-recording");
7896
+ if (recordings.length === 0) {
7897
+ return c.json({ sessionId, recordings: [], message: "No browser recordings for this session" });
7898
+ }
7899
+ return c.json({
7900
+ sessionId,
7901
+ recordings: recordings.map((r) => ({
7902
+ id: r.id,
7903
+ fileName: r.fileName,
7904
+ sizeBytes: r.sizeBytes,
7905
+ createdAt: r.createdAt,
7906
+ downloadUrl: r.downloadUrl,
7907
+ expiresAt: r.downloadUrlExpiresAt
7908
+ }))
7909
+ });
7910
+ } catch (err) {
7911
+ console.error("Failed to get browser recordings:", err.message);
7912
+ return c.json({ sessionId, recordings: [], error: err.message });
7913
+ }
7914
+ });
7131
7915
 
7132
7916
  // src/server/routes/agents.ts
7133
7917
  init_db();
7134
7918
  import { Hono as Hono2 } from "hono";
7135
7919
  import { zValidator as zValidator2 } from "@hono/zod-validator";
7136
- import { z as z15 } from "zod";
7920
+ import { z as z16 } from "zod";
7137
7921
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
7138
- import { join as join6 } from "path";
7922
+ import { join as join8 } from "path";
7139
7923
  init_config();
7140
7924
 
7141
7925
  // src/server/resumable-stream.ts
@@ -7211,7 +7995,11 @@ var streamContext = createResumableStreamContext({
7211
7995
  });
7212
7996
 
7213
7997
  // src/server/routes/agents.ts
7214
- import { nanoid as nanoid5 } from "nanoid";
7998
+ import { nanoid as nanoid6 } from "nanoid";
7999
+ init_stream_proxy();
8000
+ init_recorder();
8001
+ init_remote();
8002
+ var sessionRecorders = /* @__PURE__ */ new Map();
7215
8003
  var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
7216
8004
  var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
7217
8005
  var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
@@ -7298,35 +8086,35 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
7298
8086
  ${prompt}`;
7299
8087
  }
7300
8088
  var agents = new Hono2();
7301
- var attachmentSchema = z15.object({
7302
- type: z15.enum(["image", "file"]),
7303
- data: z15.string(),
8089
+ var attachmentSchema = z16.object({
8090
+ type: z16.enum(["image", "file"]),
8091
+ data: z16.string(),
7304
8092
  // base64 data URL or raw base64
7305
- mediaType: z15.string().optional(),
7306
- filename: z15.string().optional()
8093
+ mediaType: z16.string().optional(),
8094
+ filename: z16.string().optional()
7307
8095
  });
7308
- var runPromptSchema = z15.object({
7309
- prompt: z15.string(),
8096
+ var runPromptSchema = z16.object({
8097
+ prompt: z16.string(),
7310
8098
  // Can be empty if attachments are provided
7311
- attachments: z15.array(attachmentSchema).optional()
8099
+ attachments: z16.array(attachmentSchema).optional()
7312
8100
  }).refine(
7313
8101
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
7314
8102
  { message: "Either prompt or attachments must be provided" }
7315
8103
  );
7316
- var quickStartSchema = z15.object({
7317
- prompt: z15.string().min(1),
7318
- name: z15.string().optional(),
7319
- workingDirectory: z15.string().optional(),
7320
- model: z15.string().optional(),
7321
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
8104
+ var quickStartSchema = z16.object({
8105
+ prompt: z16.string().min(1),
8106
+ name: z16.string().optional(),
8107
+ workingDirectory: z16.string().optional(),
8108
+ model: z16.string().optional(),
8109
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
7322
8110
  });
7323
- var rejectSchema = z15.object({
7324
- reason: z15.string().optional()
8111
+ var rejectSchema = z16.object({
8112
+ reason: z16.string().optional()
7325
8113
  }).optional();
7326
8114
  var streamAbortControllers = /* @__PURE__ */ new Map();
7327
8115
  function getAttachmentsDirectory(sessionId) {
7328
8116
  const appDataDir = getAppDataDirectory();
7329
- return join6(appDataDir, "attachments", sessionId);
8117
+ return join8(appDataDir, "attachments", sessionId);
7330
8118
  }
7331
8119
  function saveAttachmentToDisk(sessionId, attachment, index) {
7332
8120
  const attachmentsDir = getAttachmentsDirectory(sessionId);
@@ -7342,7 +8130,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
7342
8130
  if (base64Data.includes(",")) {
7343
8131
  base64Data = base64Data.split(",")[1];
7344
8132
  }
7345
- const filePath = join6(attachmentsDir, filename);
8133
+ const filePath = join8(attachmentsDir, filename);
7346
8134
  const buffer = Buffer.from(base64Data, "base64");
7347
8135
  writeFileSync3(filePath, buffer);
7348
8136
  return filePath;
@@ -7510,6 +8298,40 @@ ${prompt}` });
7510
8298
  }));
7511
8299
  await new Promise((resolve11) => setTimeout(resolve11, 0));
7512
8300
  }
8301
+ const browserPort = progress.data?.browserStreamPort;
8302
+ const browserClosed = progress.data?.browserClosed;
8303
+ if (progress.toolName === "bash" && browserClosed) {
8304
+ console.log(`[BROWSER-STREAM] agent-browser close detected, destroying proxy`);
8305
+ destroyProxy(sessionId);
8306
+ } else if (progress.toolName === "bash" && browserPort) {
8307
+ console.log(`[BROWSER-STREAM] agent-browser command detected, ensuring proxy on port ${browserPort}`);
8308
+ const proxy = getOrCreateProxy(sessionId, browserPort);
8309
+ if (!sessionRecorders.has(sessionId)) {
8310
+ const recorder = new FrameRecorder(sessionId);
8311
+ recorder.start();
8312
+ sessionRecorders.set(sessionId, recorder);
8313
+ }
8314
+ if (proxy.listenerCount("frame") === 0) {
8315
+ proxy.on("frame", (frame) => {
8316
+ const rec = sessionRecorders.get(sessionId);
8317
+ rec?.addFrame(frame);
8318
+ writeSSE(JSON.stringify({
8319
+ type: "browser-frame",
8320
+ data: frame.data,
8321
+ metadata: frame.metadata
8322
+ })).catch(() => {
8323
+ });
8324
+ });
8325
+ proxy.on("status", (s) => {
8326
+ console.log(`[BROWSER-STREAM] Status:`, s);
8327
+ writeSSE(JSON.stringify({
8328
+ type: "browser-status",
8329
+ ...s
8330
+ })).catch(() => {
8331
+ });
8332
+ });
8333
+ }
8334
+ }
7513
8335
  },
7514
8336
  onStepFinish: async () => {
7515
8337
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -7696,7 +8518,7 @@ ${prompt}` });
7696
8518
  userMessageContent = prompt;
7697
8519
  }
7698
8520
  await messageQueries.create(id, { role: "user", content: userMessageContent });
7699
- const streamId = `stream_${id}_${nanoid5(10)}`;
8521
+ const streamId = `stream_${id}_${nanoid6(10)}`;
7700
8522
  await activeStreamQueries.create(id, streamId);
7701
8523
  const stream = await streamContext.resumableStream(
7702
8524
  streamId,
@@ -7895,7 +8717,7 @@ agents.post(
7895
8717
  });
7896
8718
  const session = agent.getSession();
7897
8719
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
7898
- const streamId = `stream_${session.id}_${nanoid5(10)}`;
8720
+ const streamId = `stream_${session.id}_${nanoid6(10)}`;
7899
8721
  await createCheckpoint(session.id, session.workingDirectory, 0);
7900
8722
  await activeStreamQueries.create(session.id, streamId);
7901
8723
  const createQuickStreamProducer = () => {
@@ -7974,6 +8796,40 @@ agents.post(
7974
8796
  }));
7975
8797
  await new Promise((resolve11) => setTimeout(resolve11, 0));
7976
8798
  }
8799
+ const browserPort = progress.data?.browserStreamPort;
8800
+ const browserClosed = progress.data?.browserClosed;
8801
+ if (progress.toolName === "bash" && browserClosed) {
8802
+ console.log(`[BROWSER-STREAM] agent-browser close detected`);
8803
+ destroyProxy(session.id);
8804
+ } else if (progress.toolName === "bash" && browserPort) {
8805
+ console.log(`[BROWSER-STREAM] agent-browser command detected, port ${browserPort}`);
8806
+ const proxy = getOrCreateProxy(session.id, browserPort);
8807
+ if (!sessionRecorders.has(session.id)) {
8808
+ const recorder = new FrameRecorder(session.id);
8809
+ recorder.start();
8810
+ sessionRecorders.set(session.id, recorder);
8811
+ }
8812
+ if (proxy.listenerCount("frame") === 0) {
8813
+ proxy.on("frame", (frame) => {
8814
+ const rec = sessionRecorders.get(session.id);
8815
+ rec?.addFrame(frame);
8816
+ writeSSE(JSON.stringify({
8817
+ type: "browser-frame",
8818
+ data: frame.data,
8819
+ metadata: frame.metadata
8820
+ })).catch(() => {
8821
+ });
8822
+ });
8823
+ proxy.on("status", (s) => {
8824
+ console.log(`[BROWSER-STREAM] Status:`, s);
8825
+ writeSSE(JSON.stringify({
8826
+ type: "browser-status",
8827
+ ...s
8828
+ })).catch(() => {
8829
+ });
8830
+ });
8831
+ }
8832
+ }
7977
8833
  },
7978
8834
  onStepFinish: async () => {
7979
8835
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -8104,25 +8960,71 @@ agents.post(
8104
8960
  });
8105
8961
  }
8106
8962
  );
8963
+ var browserInputSchema = z16.object({
8964
+ type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
8965
+ eventType: z16.string(),
8966
+ x: z16.number().optional(),
8967
+ y: z16.number().optional(),
8968
+ button: z16.string().optional(),
8969
+ clickCount: z16.number().optional(),
8970
+ deltaX: z16.number().optional(),
8971
+ deltaY: z16.number().optional(),
8972
+ key: z16.string().optional(),
8973
+ code: z16.string().optional(),
8974
+ text: z16.string().optional(),
8975
+ modifiers: z16.number().optional(),
8976
+ touchPoints: z16.array(z16.object({
8977
+ x: z16.number(),
8978
+ y: z16.number(),
8979
+ id: z16.number().optional()
8980
+ })).optional()
8981
+ });
8982
+ agents.post(
8983
+ "/:id/browser-input",
8984
+ zValidator2("json", browserInputSchema),
8985
+ async (c) => {
8986
+ const sessionId = c.req.param("id");
8987
+ const event = c.req.valid("json");
8988
+ const proxy = getProxy(sessionId);
8989
+ if (!proxy || !proxy.connected) {
8990
+ return c.json({ error: "No active browser stream for this session" }, 404);
8991
+ }
8992
+ proxy.injectInput(event);
8993
+ return c.json({ success: true });
8994
+ }
8995
+ );
8996
+ agents.get("/:id/browser-stream", async (c) => {
8997
+ const sessionId = c.req.param("id");
8998
+ const proxy = getProxy(sessionId);
8999
+ return c.json({
9000
+ sessionId,
9001
+ active: !!proxy?.connected,
9002
+ hasProxy: !!proxy,
9003
+ latestFrame: proxy?.latestFrame ? {
9004
+ metadata: proxy.latestFrame.metadata,
9005
+ timestamp: proxy.latestFrame.timestamp
9006
+ } : null
9007
+ });
9008
+ });
8107
9009
 
8108
9010
  // src/server/routes/health.ts
8109
9011
  init_config();
8110
9012
  import { Hono as Hono3 } from "hono";
8111
9013
  import { zValidator as zValidator3 } from "@hono/zod-validator";
8112
- import { z as z16 } from "zod";
9014
+ import { z as z17 } from "zod";
8113
9015
  import { readFileSync as readFileSync5 } from "fs";
8114
9016
  import { fileURLToPath as fileURLToPath3 } from "url";
8115
- import { dirname as dirname6, join as join7 } from "path";
9017
+ import { dirname as dirname6, join as join9 } from "path";
8116
9018
  var __filename = fileURLToPath3(import.meta.url);
8117
9019
  var __dirname = dirname6(__filename);
8118
9020
  var possiblePaths = [
8119
- join7(__dirname, "../package.json"),
9021
+ join9(__dirname, "../package.json"),
8120
9022
  // From dist/server -> dist/../package.json
8121
- join7(__dirname, "../../package.json"),
9023
+ join9(__dirname, "../../package.json"),
8122
9024
  // From dist/server (if nested differently)
8123
- join7(__dirname, "../../../package.json"),
9025
+ join9(__dirname, "../../../package.json"),
8124
9026
  // From src/server/routes (development)
8125
- join7(process.cwd(), "package.json")
9027
+ join9(process.cwd(), "package.json")
8126
9028
  // From current working directory
8127
9029
  ];
8128
9030
  var currentVersion = "0.0.0";
@@ -8219,9 +9121,9 @@ health.get("/api-keys", async (c) => {
8219
9121
  supportedProviders: SUPPORTED_PROVIDERS
8220
9122
  });
8221
9123
  });
8222
- var setApiKeySchema = z16.object({
8223
- provider: z16.string(),
8224
- apiKey: z16.string().min(1)
9124
+ var setApiKeySchema = z17.object({
9125
+ provider: z17.string(),
9126
+ apiKey: z17.string().min(1)
8225
9127
  });
8226
9128
  health.post(
8227
9129
  "/api-keys",
@@ -8260,13 +9162,13 @@ health.delete("/api-keys/:provider", async (c) => {
8260
9162
  // src/server/routes/terminals.ts
8261
9163
  import { Hono as Hono4 } from "hono";
8262
9164
  import { zValidator as zValidator4 } from "@hono/zod-validator";
8263
- import { z as z17 } from "zod";
9165
+ import { z as z18 } from "zod";
8264
9166
  init_db();
8265
9167
  var terminals = new Hono4();
8266
- var spawnSchema = z17.object({
8267
- command: z17.string(),
8268
- cwd: z17.string().optional(),
8269
- name: z17.string().optional()
9168
+ var spawnSchema = z18.object({
9169
+ command: z18.string(),
9170
+ cwd: z18.string().optional(),
9171
+ name: z18.string().optional()
8270
9172
  });
8271
9173
  terminals.post(
8272
9174
  "/:sessionId/terminals",
@@ -8347,8 +9249,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
8347
9249
  // We don't track exit codes in tmux mode
8348
9250
  });
8349
9251
  });
8350
- var logsQuerySchema = z17.object({
8351
- tail: z17.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
9252
+ var logsQuerySchema = z18.object({
9253
+ tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
8352
9254
  });
8353
9255
  terminals.get(
8354
9256
  "/:sessionId/terminals/:terminalId/logs",
@@ -8372,8 +9274,8 @@ terminals.get(
8372
9274
  });
8373
9275
  }
8374
9276
  );
8375
- var killSchema = z17.object({
8376
- signal: z17.enum(["SIGTERM", "SIGKILL"]).optional()
9277
+ var killSchema = z18.object({
9278
+ signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
8377
9279
  });
8378
9280
  terminals.post(
8379
9281
  "/:sessionId/terminals/:terminalId/kill",
@@ -8387,8 +9289,8 @@ terminals.post(
8387
9289
  return c.json({ success: true, message: "Terminal killed" });
8388
9290
  }
8389
9291
  );
8390
- var writeSchema = z17.object({
8391
- input: z17.string()
9292
+ var writeSchema = z18.object({
9293
+ input: z18.string()
8392
9294
  });
8393
9295
  terminals.post(
8394
9296
  "/:sessionId/terminals/:terminalId/write",
@@ -8573,18 +9475,18 @@ data: ${JSON.stringify({ status: "stopped" })}
8573
9475
  init_db();
8574
9476
  import { Hono as Hono5 } from "hono";
8575
9477
  import { zValidator as zValidator5 } from "@hono/zod-validator";
8576
- import { z as z18 } from "zod";
9478
+ import { z as z19 } from "zod";
8577
9479
  init_config();
8578
9480
  var tasks = new Hono5();
8579
9481
  var taskAbortControllers = /* @__PURE__ */ new Map();
8580
- var createTaskSchema = z18.object({
8581
- prompt: z18.string().min(1),
8582
- outputSchema: z18.record(z18.string(), z18.unknown()),
8583
- webhookUrl: z18.string().url().optional(),
8584
- model: z18.string().optional(),
8585
- workingDirectory: z18.string().optional(),
8586
- name: z18.string().optional(),
8587
- maxIterations: z18.number().int().min(1).max(500).optional()
9482
+ var createTaskSchema = z19.object({
9483
+ prompt: z19.string().min(1),
9484
+ outputSchema: z19.record(z19.string(), z19.unknown()),
9485
+ webhookUrl: z19.string().url().optional(),
9486
+ model: z19.string().optional(),
9487
+ workingDirectory: z19.string().optional(),
9488
+ name: z19.string().optional(),
9489
+ maxIterations: z19.number().int().min(1).max(500).optional()
8588
9490
  });
8589
9491
  tasks.post(
8590
9492
  "/",
@@ -8663,6 +9565,15 @@ tasks.get("/:id", async (c) => {
8663
9565
  if (!task?.enabled) {
8664
9566
  return c.json({ error: "Session is not a task" }, 400);
8665
9567
  }
9568
+ let browserRecordings = [];
9569
+ try {
9570
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
9571
+ if (isRemoteConfigured2()) {
9572
+ const files = await storageQueries2.getSessionFiles(id);
9573
+ browserRecordings = files.filter((f) => f.category === "browser-recording").map((f) => ({ fileName: f.fileName, downloadUrl: f.downloadUrl, sizeBytes: f.sizeBytes }));
9574
+ }
9575
+ } catch {
9576
+ }
8666
9577
  return c.json({
8667
9578
  taskId: id,
8668
9579
  status: task.status,
@@ -8672,7 +9583,8 @@ tasks.get("/:id", async (c) => {
8672
9583
  model: session.model,
8673
9584
  name: session.name,
8674
9585
  createdAt: session.createdAt.toISOString(),
8675
- updatedAt: session.updatedAt.toISOString()
9586
+ updatedAt: session.updatedAt.toISOString(),
9587
+ browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
8676
9588
  });
8677
9589
  });
8678
9590
  tasks.post("/:id/cancel", async (c) => {
@@ -8720,10 +9632,10 @@ init_config();
8720
9632
  init_db();
8721
9633
 
8722
9634
  // src/utils/dependencies.ts
8723
- import { exec as exec5 } from "child_process";
8724
- import { promisify as promisify5 } from "util";
9635
+ import { exec as exec6 } from "child_process";
9636
+ import { promisify as promisify6 } from "util";
8725
9637
  import { platform as platform2 } from "os";
8726
- var execAsync5 = promisify5(exec5);
9638
+ var execAsync6 = promisify6(exec6);
8727
9639
  function getInstallInstructions() {
8728
9640
  const os2 = platform2();
8729
9641
  if (os2 === "darwin") {
@@ -8756,7 +9668,7 @@ Install tmux:
8756
9668
  }
8757
9669
  async function checkTmux() {
8758
9670
  try {
8759
- const { stdout } = await execAsync5("tmux -V", { timeout: 5e3 });
9671
+ const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
8760
9672
  const version = stdout.trim();
8761
9673
  return {
8762
9674
  available: true,
@@ -8805,11 +9717,11 @@ function getWebDirectory() {
8805
9717
  try {
8806
9718
  const currentDir = dirname7(fileURLToPath4(import.meta.url));
8807
9719
  const webDir = resolve10(currentDir, "..", "web");
8808
- if (existsSync15(webDir) && existsSync15(join8(webDir, "package.json"))) {
9720
+ if (existsSync15(webDir) && existsSync15(join10(webDir, "package.json"))) {
8809
9721
  return webDir;
8810
9722
  }
8811
9723
  const altWebDir = resolve10(currentDir, "..", "..", "web");
8812
- if (existsSync15(altWebDir) && existsSync15(join8(altWebDir, "package.json"))) {
9724
+ if (existsSync15(altWebDir) && existsSync15(join10(altWebDir, "package.json"))) {
8813
9725
  return altWebDir;
8814
9726
  }
8815
9727
  return null;
@@ -8867,20 +9779,20 @@ async function findWebPort(preferredPort) {
8867
9779
  return { port: preferredPort, alreadyRunning: false };
8868
9780
  }
8869
9781
  function hasProductionBuild(webDir) {
8870
- const buildIdPath = join8(webDir, ".next", "BUILD_ID");
9782
+ const buildIdPath = join10(webDir, ".next", "BUILD_ID");
8871
9783
  return existsSync15(buildIdPath);
8872
9784
  }
8873
9785
  function hasSourceFiles(webDir) {
8874
- const appDir = join8(webDir, "src", "app");
8875
- const pagesDir = join8(webDir, "src", "pages");
8876
- const rootAppDir = join8(webDir, "app");
8877
- const rootPagesDir = join8(webDir, "pages");
9786
+ const appDir = join10(webDir, "src", "app");
9787
+ const pagesDir = join10(webDir, "src", "pages");
9788
+ const rootAppDir = join10(webDir, "app");
9789
+ const rootPagesDir = join10(webDir, "pages");
8878
9790
  return existsSync15(appDir) || existsSync15(pagesDir) || existsSync15(rootAppDir) || existsSync15(rootPagesDir);
8879
9791
  }
8880
9792
  function getStandaloneServerPath(webDir) {
8881
9793
  const possiblePaths2 = [
8882
- join8(webDir, ".next", "standalone", "server.js"),
8883
- join8(webDir, ".next", "standalone", "web", "server.js")
9794
+ join10(webDir, ".next", "standalone", "server.js"),
9795
+ join10(webDir, ".next", "standalone", "web", "server.js")
8884
9796
  ];
8885
9797
  for (const serverPath of possiblePaths2) {
8886
9798
  if (existsSync15(serverPath)) {
@@ -8923,13 +9835,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
8923
9835
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
8924
9836
  return { process: null, port: actualPort };
8925
9837
  }
8926
- const usePnpm = existsSync15(join8(webDir, "pnpm-lock.yaml"));
8927
- const useNpm = !usePnpm && existsSync15(join8(webDir, "package-lock.json"));
9838
+ const usePnpm = existsSync15(join10(webDir, "pnpm-lock.yaml"));
9839
+ const useNpm = !usePnpm && existsSync15(join10(webDir, "package-lock.json"));
8928
9840
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
8929
9841
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
8930
9842
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
8931
9843
  const runtimeConfig = { apiBaseUrl: apiUrl };
8932
- const runtimeConfigPath = join8(webDir, "runtime-config.json");
9844
+ const runtimeConfigPath = join10(webDir, "runtime-config.json");
8933
9845
  try {
8934
9846
  writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
8935
9847
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);