sparkecoder 0.1.64 → 0.1.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +815 -30
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +1042 -165
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/db/index.js.map +1 -1
  8. package/dist/{index-Dn-eCGLe.d.ts → index-Db23cukG.d.ts} +35 -25
  9. package/dist/index.d.ts +5 -5
  10. package/dist/index.js +1058 -146
  11. package/dist/index.js.map +1 -1
  12. package/dist/{schema-XcP0dedO.d.ts → schema-C7Mm4Ykn.d.ts} +3 -3
  13. package/dist/{search-DINnDTgj.d.ts → search-CVVfuBPZ.d.ts} +6 -4
  14. package/dist/server/index.js +1058 -146
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/skills/default/qa.md +317 -0
  17. package/dist/tools/index.d.ts +31 -4
  18. package/dist/tools/index.js +433 -7
  19. package/dist/tools/index.js.map +1 -1
  20. package/package.json +3 -1
  21. package/src/skills/default/qa.md +317 -0
  22. package/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  24. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  25. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  26. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  27. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  28. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  29. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  31. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  43. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  46. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  47. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  80. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  84. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  85. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  88. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  89. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  90. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  91. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  92. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  93. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  94. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  95. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  96. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  97. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5c78460e._.js → 2374f_02a118f9._.js} +1 -1
  98. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bfc8ef7d._.js → 2374f_0ed477f8._.js} +1 -1
  99. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_04a544c8._.js → 2374f_12bad06e._.js} +1 -1
  100. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_e366206f._.js → 2374f_2526ca80._.js} +1 -1
  101. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_45534372._.js → 2374f_3b51a934._.js} +1 -1
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d5f5b9ba._.js → 2374f_3e519469._.js} +1 -1
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5e9eb6da._.js → 2374f_5ebfcf1a._.js} +1 -1
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_db790cfe._.js → 2374f_a0f483d1._.js} +1 -1
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c61a33b3._.js → 2374f_acf3dfe4._.js} +1 -1
  106. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ab5b97d8._.js → 2374f_ad08e83a._.js} +1 -1
  107. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_68abddfe._.js → 2374f_c1d54c16._.js} +1 -1
  108. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_de60e6ea._.js → 2374f_db3e363b._.js} +1 -1
  109. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_72fb9db7._.js → 2374f_f0d7e130._.js} +1 -1
  110. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5d0b3394._.js → 2374f_fc992d90._.js} +1 -1
  111. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__aa788b85._.js → [root-of-the-server]__06818a54._.js} +2 -2
  112. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__d04c460d._.js → [root-of-the-server]__c71f29f9._.js} +4 -4
  113. package/web/.next/standalone/web/.next/server/chunks/ssr/web_2b3a5919._.js +1 -1
  114. package/web/.next/standalone/web/.next/server/chunks/ssr/web_38156da8._.js +1 -1
  115. package/web/.next/standalone/web/.next/server/chunks/ssr/web_5cca707f._.js +7 -0
  116. package/web/.next/standalone/web/.next/server/chunks/ssr/web_935e81f5._.js +7 -0
  117. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_6fb589ac._.js → web_cc5f7515._.js} +2 -2
  118. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  119. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  120. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  121. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  122. package/web/.next/standalone/web/.next/static/chunks/{eea48be65cdb3f4b.js → 31208ade542a0fcb.js} +3 -3
  123. package/web/.next/{static/chunks/054deec0c7b19894.js → standalone/web/.next/static/chunks/4e673433173ad456.js} +3 -3
  124. package/web/.next/standalone/web/.next/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
  125. package/web/.next/standalone/web/.next/static/chunks/fd39dd62879495e1.css +1 -0
  126. package/web/.next/{static/chunks/eea48be65cdb3f4b.js → standalone/web/.next/static/static/chunks/31208ade542a0fcb.js} +3 -3
  127. package/web/.next/standalone/web/.next/static/static/chunks/{054deec0c7b19894.js → 4e673433173ad456.js} +3 -3
  128. package/web/.next/standalone/web/.next/static/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
  129. package/web/.next/standalone/web/.next/static/static/chunks/fd39dd62879495e1.css +1 -0
  130. package/web/.next/standalone/web/src/components/ai-elements/browser-recording.tsx +100 -0
  131. package/web/.next/standalone/web/src/components/browser-preview.tsx +196 -0
  132. package/web/.next/standalone/web/src/components/chat-interface.tsx +188 -4
  133. package/web/.next/standalone/web/src/lib/api.ts +119 -0
  134. package/web/.next/{standalone/web/.next/static/static/chunks/eea48be65cdb3f4b.js → static/chunks/31208ade542a0fcb.js} +3 -3
  135. package/web/.next/{standalone/web/.next/static/chunks/054deec0c7b19894.js → static/chunks/4e673433173ad456.js} +3 -3
  136. package/web/.next/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
  137. package/web/.next/static/chunks/fd39dd62879495e1.css +1 -0
  138. package/web/.next/standalone/web/.next/server/chunks/ssr/web_08bbd8c8._.js +0 -7
  139. package/web/.next/standalone/web/.next/server/chunks/ssr/web_c729ad51._.js +0 -7
  140. package/web/.next/standalone/web/.next/static/chunks/1f42a42914068041.css +0 -1
  141. package/web/.next/standalone/web/.next/static/static/chunks/1f42a42914068041.css +0 -1
  142. package/web/.next/static/chunks/1f42a42914068041.css +0 -1
  143. /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
  144. /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
  145. /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
  146. /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
  147. /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
  148. /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
  149. /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
  150. /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
  151. /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -10,6 +10,25 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/db/remote.ts
13
+ var remote_exports = {};
14
+ __export(remote_exports, {
15
+ closeRemoteDatabase: () => closeRemoteDatabase,
16
+ initRemoteDatabase: () => initRemoteDatabase,
17
+ isRemoteConfigured: () => isRemoteConfigured,
18
+ remoteActiveStreamQueries: () => remoteActiveStreamQueries,
19
+ remoteCheckpointQueries: () => remoteCheckpointQueries,
20
+ remoteFileBackupQueries: () => remoteFileBackupQueries,
21
+ remoteIndexStatusQueries: () => remoteIndexStatusQueries,
22
+ remoteIndexedChunkQueries: () => remoteIndexedChunkQueries,
23
+ remoteMessageQueries: () => remoteMessageQueries,
24
+ remoteSessionQueries: () => remoteSessionQueries,
25
+ remoteSkillQueries: () => remoteSkillQueries,
26
+ remoteSubagentQueries: () => remoteSubagentQueries,
27
+ remoteTerminalQueries: () => remoteTerminalQueries,
28
+ remoteTodoQueries: () => remoteTodoQueries,
29
+ remoteToolExecutionQueries: () => remoteToolExecutionQueries,
30
+ storageQueries: () => storageQueries
31
+ });
13
32
  function initRemoteDatabase(serverUrl, key) {
14
33
  remoteServerUrl = serverUrl.replace(/\/$/, "");
15
34
  authKey = key;
@@ -18,6 +37,9 @@ function closeRemoteDatabase() {
18
37
  remoteServerUrl = null;
19
38
  authKey = null;
20
39
  }
40
+ function isRemoteConfigured() {
41
+ return !!remoteServerUrl && !!authKey;
42
+ }
21
43
  function parseDates(obj) {
22
44
  if (obj === null || obj === void 0) return obj;
23
45
  if (Array.isArray(obj)) return obj.map(parseDates);
@@ -65,7 +87,29 @@ async function api(path, options = {}) {
65
87
  }
66
88
  return parseDates(parsed);
67
89
  }
68
- var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries;
90
+ async function storageApi(path, options = {}) {
91
+ if (!remoteServerUrl || !authKey) {
92
+ throw new Error("Remote database not initialized");
93
+ }
94
+ const url = `${remoteServerUrl}/storage${path}`;
95
+ const init = {
96
+ method: options.method || "GET",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ "Authorization": `Bearer ${authKey}`
100
+ }
101
+ };
102
+ if (options.body) {
103
+ init.body = JSON.stringify(options.body);
104
+ }
105
+ const response = await fetch(url, init);
106
+ if (!response.ok) {
107
+ const errorText = await response.text().catch(() => "Unknown error");
108
+ throw new Error(`Storage API error ${response.status}: ${errorText}`);
109
+ }
110
+ return response.json();
111
+ }
112
+ var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteTerminalQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries, storageQueries;
69
113
  var init_remote = __esm({
70
114
  "src/db/remote.ts"() {
71
115
  "use strict";
@@ -204,6 +248,34 @@ var init_remote = __esm({
204
248
  return result.isLoaded;
205
249
  }
206
250
  };
251
+ remoteTerminalQueries = {
252
+ create(data) {
253
+ return api("/terminals", { method: "POST", body: data });
254
+ },
255
+ getById(id) {
256
+ return api(`/terminals/${id}`).catch(() => void 0);
257
+ },
258
+ getBySession(sessionId) {
259
+ return api(`/terminals/session/${sessionId}`);
260
+ },
261
+ getRunning(sessionId) {
262
+ return api(`/terminals/session/${sessionId}/running`);
263
+ },
264
+ updateStatus(id, status, exitCode, error) {
265
+ return api(`/terminals/${id}`, { method: "PATCH", body: { status, exitCode, error } });
266
+ },
267
+ updatePid(id, pid) {
268
+ return api(`/terminals/${id}`, { method: "PATCH", body: { pid } });
269
+ },
270
+ async delete(id) {
271
+ const result = await api(`/terminals/${id}`, { method: "DELETE" });
272
+ return result?.success ?? false;
273
+ },
274
+ async deleteBySession(sessionId) {
275
+ const result = await api(`/terminals/session/${sessionId}`, { method: "DELETE" });
276
+ return result.deleted;
277
+ }
278
+ };
207
279
  remoteActiveStreamQueries = {
208
280
  create(sessionId, streamId) {
209
281
  return api("/streams", { method: "POST", body: { sessionId, streamId } });
@@ -364,6 +436,27 @@ var init_remote = __esm({
364
436
  return api("/index-status");
365
437
  }
366
438
  };
439
+ storageQueries = {
440
+ async getUploadUrl(sessionId, fileName, contentType, category) {
441
+ return storageApi("/upload-url", {
442
+ method: "POST",
443
+ body: { sessionId, fileName, contentType, category }
444
+ });
445
+ },
446
+ async getSessionFiles(sessionId) {
447
+ const result = await storageApi(`/files/${sessionId}`);
448
+ return result.files;
449
+ },
450
+ async getDownloadUrl(fileId) {
451
+ return storageApi(`/download/${fileId}`);
452
+ },
453
+ async deleteFile(fileId) {
454
+ await storageApi(`/files/${fileId}`, { method: "DELETE" });
455
+ },
456
+ async updateFile(fileId, data) {
457
+ await storageApi(`/files/${fileId}`, { method: "PATCH", body: data });
458
+ }
459
+ };
367
460
  }
368
461
  });
369
462
 
@@ -1911,7 +2004,7 @@ function isPathExcluded(relativePath, exclude) {
1911
2004
  }
1912
2005
  async function walkDirectory(dir, include, exclude, baseDir) {
1913
2006
  const { readdirSync: readdirSync2 } = await import("fs");
1914
- const { join: join10, relative: relative10 } = await import("path");
2007
+ const { join: join12, relative: relative10 } = await import("path");
1915
2008
  const files = [];
1916
2009
  function walk(currentDir) {
1917
2010
  let entries;
@@ -1921,7 +2014,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
1921
2014
  return;
1922
2015
  }
1923
2016
  for (const entry of entries) {
1924
- const fullPath = join10(currentDir, entry.name);
2017
+ const fullPath = join12(currentDir, entry.name);
1925
2018
  const relativePath = relative10(baseDir, fullPath);
1926
2019
  if (isPathExcluded(relativePath, exclude)) {
1927
2020
  continue;
@@ -2451,6 +2544,280 @@ var init_webhook = __esm({
2451
2544
  }
2452
2545
  });
2453
2546
 
2547
+ // src/browser/stream-proxy.ts
2548
+ var stream_proxy_exports = {};
2549
+ __export(stream_proxy_exports, {
2550
+ BrowserStreamProxy: () => BrowserStreamProxy,
2551
+ destroyProxy: () => destroyProxy,
2552
+ getOrCreateProxy: () => getOrCreateProxy,
2553
+ getProxy: () => getProxy
2554
+ });
2555
+ import WebSocket from "ws";
2556
+ import { EventEmitter } from "events";
2557
+ function getOrCreateProxy(sessionId, port) {
2558
+ const existing = activeProxies.get(sessionId);
2559
+ if (existing) return existing;
2560
+ const proxy = new BrowserStreamProxy(port);
2561
+ activeProxies.set(sessionId, proxy);
2562
+ proxy.on("close", () => {
2563
+ activeProxies.delete(sessionId);
2564
+ });
2565
+ proxy.connect();
2566
+ return proxy;
2567
+ }
2568
+ function getProxy(sessionId) {
2569
+ return activeProxies.get(sessionId);
2570
+ }
2571
+ function destroyProxy(sessionId) {
2572
+ const proxy = activeProxies.get(sessionId);
2573
+ if (proxy) {
2574
+ proxy.destroy();
2575
+ activeProxies.delete(sessionId);
2576
+ }
2577
+ }
2578
+ var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
2579
+ var init_stream_proxy = __esm({
2580
+ "src/browser/stream-proxy.ts"() {
2581
+ "use strict";
2582
+ RECONNECT_DELAY_MS = 1e3;
2583
+ MAX_RECONNECT_ATTEMPTS = 20;
2584
+ FRAME_THROTTLE_MS = 100;
2585
+ BrowserStreamProxy = class extends EventEmitter {
2586
+ ws = null;
2587
+ port;
2588
+ reconnectAttempts = 0;
2589
+ reconnectTimer = null;
2590
+ destroyed = false;
2591
+ lastFrameTime = 0;
2592
+ _latestFrame = null;
2593
+ _connected = false;
2594
+ constructor(port) {
2595
+ super();
2596
+ this.port = port;
2597
+ }
2598
+ get connected() {
2599
+ return this._connected;
2600
+ }
2601
+ get latestFrame() {
2602
+ return this._latestFrame;
2603
+ }
2604
+ connect() {
2605
+ if (this.destroyed) return;
2606
+ this.doConnect();
2607
+ }
2608
+ doConnect() {
2609
+ if (this.destroyed) return;
2610
+ const url = `ws://localhost:${this.port}`;
2611
+ try {
2612
+ this.ws = new WebSocket(url);
2613
+ } catch {
2614
+ this.scheduleReconnect();
2615
+ return;
2616
+ }
2617
+ this.ws.on("open", () => {
2618
+ this.reconnectAttempts = 0;
2619
+ this._connected = true;
2620
+ this.emit("status", {
2621
+ connected: true,
2622
+ screencasting: true
2623
+ });
2624
+ });
2625
+ this.ws.on("message", (raw) => {
2626
+ try {
2627
+ const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
2628
+ this.handleMessage(msg);
2629
+ } catch {
2630
+ }
2631
+ });
2632
+ this.ws.on("close", () => {
2633
+ const wasConnected = this._connected;
2634
+ this._connected = false;
2635
+ if (wasConnected) {
2636
+ this.emit("status", { connected: false, screencasting: false });
2637
+ }
2638
+ if (!this.destroyed) {
2639
+ this.scheduleReconnect();
2640
+ }
2641
+ });
2642
+ this.ws.on("error", () => {
2643
+ });
2644
+ }
2645
+ handleMessage(msg) {
2646
+ if (msg.type === "frame") {
2647
+ const now = Date.now();
2648
+ if (now - this.lastFrameTime < FRAME_THROTTLE_MS) return;
2649
+ this.lastFrameTime = now;
2650
+ const frame = {
2651
+ data: msg.data,
2652
+ metadata: msg.metadata ?? {
2653
+ deviceWidth: 1280,
2654
+ deviceHeight: 720,
2655
+ pageScaleFactor: 1,
2656
+ offsetTop: 0,
2657
+ scrollOffsetX: 0,
2658
+ scrollOffsetY: 0
2659
+ },
2660
+ timestamp: now
2661
+ };
2662
+ this._latestFrame = frame;
2663
+ this.emit("frame", frame);
2664
+ } else if (msg.type === "status") {
2665
+ this.emit("status", {
2666
+ connected: msg.connected ?? true,
2667
+ screencasting: msg.screencasting ?? true,
2668
+ viewportWidth: msg.viewportWidth,
2669
+ viewportHeight: msg.viewportHeight
2670
+ });
2671
+ }
2672
+ }
2673
+ scheduleReconnect() {
2674
+ if (this.destroyed || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
2675
+ this.emit("close");
2676
+ return;
2677
+ }
2678
+ this.reconnectAttempts++;
2679
+ const delay = this.reconnectAttempts <= 5 ? RECONNECT_DELAY_MS : RECONNECT_DELAY_MS * (this.reconnectAttempts - 4);
2680
+ this.reconnectTimer = setTimeout(() => this.doConnect(), delay);
2681
+ }
2682
+ /**
2683
+ * Send an input event to the browser for pair-browsing.
2684
+ */
2685
+ injectInput(event) {
2686
+ if (this.ws?.readyState === WebSocket.OPEN) {
2687
+ this.ws.send(JSON.stringify(event));
2688
+ }
2689
+ }
2690
+ destroy() {
2691
+ this.destroyed = true;
2692
+ if (this.reconnectTimer) {
2693
+ clearTimeout(this.reconnectTimer);
2694
+ this.reconnectTimer = null;
2695
+ }
2696
+ if (this.ws) {
2697
+ this.ws.removeAllListeners();
2698
+ this.ws.close();
2699
+ this.ws = null;
2700
+ }
2701
+ this._connected = false;
2702
+ this.removeAllListeners();
2703
+ }
2704
+ };
2705
+ activeProxies = /* @__PURE__ */ new Map();
2706
+ }
2707
+ });
2708
+
2709
+ // src/browser/recorder.ts
2710
+ var recorder_exports = {};
2711
+ __export(recorder_exports, {
2712
+ FrameRecorder: () => FrameRecorder
2713
+ });
2714
+ import { exec as exec5 } from "child_process";
2715
+ import { promisify as promisify5 } from "util";
2716
+ import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
2717
+ import { join as join6 } from "path";
2718
+ import { tmpdir } from "os";
2719
+ import { nanoid as nanoid3 } from "nanoid";
2720
+ async function checkFfmpeg() {
2721
+ try {
2722
+ await execAsync5("ffmpeg -version", { timeout: 5e3 });
2723
+ return true;
2724
+ } catch {
2725
+ return false;
2726
+ }
2727
+ }
2728
+ async function cleanup(dir) {
2729
+ try {
2730
+ await rm(dir, { recursive: true, force: true });
2731
+ } catch {
2732
+ }
2733
+ }
2734
+ var execAsync5, FrameRecorder;
2735
+ var init_recorder = __esm({
2736
+ "src/browser/recorder.ts"() {
2737
+ "use strict";
2738
+ execAsync5 = promisify5(exec5);
2739
+ FrameRecorder = class {
2740
+ frames = [];
2741
+ startTime = null;
2742
+ recording = false;
2743
+ sessionId;
2744
+ constructor(sessionId) {
2745
+ this.sessionId = sessionId;
2746
+ }
2747
+ get isRecording() {
2748
+ return this.recording;
2749
+ }
2750
+ get frameCount() {
2751
+ return this.frames.length;
2752
+ }
2753
+ start() {
2754
+ this.frames = [];
2755
+ this.startTime = Date.now();
2756
+ this.recording = true;
2757
+ }
2758
+ addFrame(frame) {
2759
+ if (!this.recording) return;
2760
+ this.frames.push({
2761
+ data: Buffer.from(frame.data, "base64"),
2762
+ timestamp: frame.timestamp
2763
+ });
2764
+ }
2765
+ stop() {
2766
+ this.recording = false;
2767
+ }
2768
+ /**
2769
+ * Encode recorded frames into an MP4 using ffmpeg.
2770
+ * Returns the file path to the generated MP4, or null if encoding fails.
2771
+ */
2772
+ async encode() {
2773
+ if (this.frames.length === 0) return null;
2774
+ const workDir = join6(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
2775
+ await mkdir4(workDir, { recursive: true });
2776
+ try {
2777
+ for (let i = 0; i < this.frames.length; i++) {
2778
+ const framePath = join6(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
2779
+ await writeFile4(framePath, this.frames[i].data);
2780
+ }
2781
+ const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
2782
+ const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
2783
+ const clampedFps = Math.max(1, Math.min(fps, 30));
2784
+ const outputPath = join6(workDir, `recording_${this.sessionId}.mp4`);
2785
+ const hasFfmpeg = await checkFfmpeg();
2786
+ if (hasFfmpeg) {
2787
+ await execAsync5(
2788
+ `ffmpeg -y -framerate ${clampedFps} -i "${join6(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
2789
+ { timeout: 12e4 }
2790
+ );
2791
+ } else {
2792
+ console.warn("[RECORDER] ffmpeg not available, cannot encode recording");
2793
+ await cleanup(workDir);
2794
+ return null;
2795
+ }
2796
+ const outputBuf = await readFile10(outputPath);
2797
+ const files = await readdir5(workDir);
2798
+ for (const f of files) {
2799
+ if (f.startsWith("frame_")) {
2800
+ await unlink2(join6(workDir, f)).catch(() => {
2801
+ });
2802
+ }
2803
+ }
2804
+ return { path: outputPath, sizeBytes: outputBuf.length };
2805
+ } catch (error) {
2806
+ console.error("[RECORDER] Failed to encode recording:", error);
2807
+ await cleanup(workDir);
2808
+ return null;
2809
+ }
2810
+ }
2811
+ /** Discard all frames and free memory */
2812
+ clear() {
2813
+ this.frames = [];
2814
+ this.startTime = null;
2815
+ this.recording = false;
2816
+ }
2817
+ };
2818
+ }
2819
+ });
2820
+
2454
2821
  // src/cli.ts
2455
2822
  import { Command } from "commander";
2456
2823
  import chalk from "chalk";
@@ -2465,7 +2832,7 @@ import { serve } from "@hono/node-server";
2465
2832
  import { cors } from "hono/cors";
2466
2833
  import { logger } from "hono/logger";
2467
2834
  import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
2468
- import { resolve as resolve10, dirname as dirname7, join as join8 } from "path";
2835
+ import { resolve as resolve10, dirname as dirname7, join as join10 } from "path";
2469
2836
  import { spawn as spawn2 } from "child_process";
2470
2837
  import { createServer as createNetServer } from "net";
2471
2838
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -2474,17 +2841,17 @@ import { fileURLToPath as fileURLToPath4 } from "url";
2474
2841
  init_db();
2475
2842
  import { Hono } from "hono";
2476
2843
  import { zValidator } from "@hono/zod-validator";
2477
- import { z as z14 } from "zod";
2844
+ import { z as z15 } from "zod";
2478
2845
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
2479
- import { readdir as readdir5 } from "fs/promises";
2480
- import { join as join5, basename as basename4, extname as extname7, relative as relative9 } from "path";
2481
- import { nanoid as nanoid4 } from "nanoid";
2846
+ import { readdir as readdir6 } from "fs/promises";
2847
+ import { join as join7, basename as basename5, extname as extname8, relative as relative9 } from "path";
2848
+ import { nanoid as nanoid5 } from "nanoid";
2482
2849
 
2483
2850
  // src/agent/index.ts
2484
2851
  import {
2485
2852
  streamText as streamText2,
2486
2853
  generateText as generateText3,
2487
- tool as tool12,
2854
+ tool as tool13,
2488
2855
  stepCountIs as stepCountIs2
2489
2856
  } from "ai";
2490
2857
 
@@ -2665,8 +3032,8 @@ var SUBAGENT_MODELS = {
2665
3032
  // src/agent/index.ts
2666
3033
  init_db();
2667
3034
  init_config();
2668
- import { z as z13 } from "zod";
2669
- import { nanoid as nanoid3 } from "nanoid";
3035
+ import { z as z14 } from "zod";
3036
+ import { nanoid as nanoid4 } from "nanoid";
2670
3037
 
2671
3038
  // src/tools/bash.ts
2672
3039
  import { tool } from "ai";
@@ -2952,8 +3319,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
2952
3319
  const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
2953
3320
  const terminals2 = [];
2954
3321
  try {
2955
- const { readdir: readdir6 } = await import("fs/promises");
2956
- const entries = await readdir6(terminalsDir, { withFileTypes: true });
3322
+ const { readdir: readdir7 } = await import("fs/promises");
3323
+ const entries = await readdir7(terminalsDir, { withFileTypes: true });
2957
3324
  for (const entry of entries) {
2958
3325
  if (entry.isDirectory()) {
2959
3326
  const meta = await getMeta(entry.name, workingDirectory, sessionId);
@@ -3015,6 +3382,29 @@ function isBlockedCommand(command) {
3015
3382
  (blocked) => normalizedCommand.includes(blocked.toLowerCase())
3016
3383
  );
3017
3384
  }
3385
+ var BROWSER_STREAM_BASE_PORT = 9223;
3386
+ var sessionBrowserPorts = /* @__PURE__ */ new Map();
3387
+ var nextPortOffset = 0;
3388
+ function getBrowserStreamPort(sessionId) {
3389
+ let port = sessionBrowserPorts.get(sessionId);
3390
+ if (!port) {
3391
+ port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
3392
+ sessionBrowserPorts.set(sessionId, port);
3393
+ }
3394
+ return port;
3395
+ }
3396
+ function hasAgentBrowserCommand(command) {
3397
+ return /\bagent-browser\b/.test(command);
3398
+ }
3399
+ function isAgentBrowserCloseCommand(command) {
3400
+ return /\bagent-browser\s+(close|close\s+--all)\b/.test(command);
3401
+ }
3402
+ function injectBrowserStreamPort(command, port) {
3403
+ return command.replace(
3404
+ /\bagent-browser\b/g,
3405
+ `AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
3406
+ );
3407
+ }
3018
3408
  var bashInputSchema = z2.object({
3019
3409
  command: z2.string().optional().describe("The command to execute. Required for running new commands."),
3020
3410
  background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
@@ -3180,6 +3570,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3180
3570
  exitCode: 1
3181
3571
  };
3182
3572
  }
3573
+ let actualCommand = command;
3574
+ const hasAgentBrowser = hasAgentBrowserCommand(command);
3575
+ const browserClose = isAgentBrowserCloseCommand(command);
3576
+ let browserPort;
3577
+ if (hasAgentBrowser) {
3578
+ browserPort = getBrowserStreamPort(options.sessionId);
3579
+ if (!browserClose) {
3580
+ actualCommand = injectBrowserStreamPort(command, browserPort);
3581
+ }
3582
+ }
3183
3583
  const canUseTmux = await shouldUseTmux();
3184
3584
  if (background) {
3185
3585
  if (!canUseTmux) {
@@ -3189,8 +3589,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3189
3589
  };
3190
3590
  }
3191
3591
  const terminalId = generateTerminalId();
3192
- options.onProgress?.({ terminalId, status: "started", command });
3193
- const result = await runBackground(command, options.workingDirectory, {
3592
+ options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
3593
+ const result = await runBackground(actualCommand, options.workingDirectory, {
3194
3594
  sessionId: options.sessionId,
3195
3595
  terminalId
3196
3596
  });
@@ -3203,16 +3603,22 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3203
3603
  }
3204
3604
  if (canUseTmux) {
3205
3605
  const terminalId = generateTerminalId();
3206
- options.onProgress?.({ terminalId, status: "started", command });
3606
+ options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
3207
3607
  try {
3208
- const result = await runSync(command, options.workingDirectory, {
3608
+ const result = await runSync(actualCommand, options.workingDirectory, {
3209
3609
  sessionId: options.sessionId,
3210
3610
  timeout: COMMAND_TIMEOUT,
3211
3611
  terminalId
3212
3612
  });
3213
3613
  const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
3214
3614
  options.onOutput?.(truncatedOutput);
3215
- options.onProgress?.({ terminalId, status: "completed", command });
3615
+ options.onProgress?.({
3616
+ terminalId,
3617
+ status: "completed",
3618
+ command,
3619
+ browserStreamPort: browserPort,
3620
+ browserClosed: browserClose || void 0
3621
+ });
3216
3622
  return {
3217
3623
  success: result.exitCode === 0,
3218
3624
  id: result.id,
@@ -3230,7 +3636,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3230
3636
  };
3231
3637
  }
3232
3638
  } else {
3233
- const result = await execFallback(command, options.workingDirectory, options.onOutput);
3639
+ const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
3234
3640
  return {
3235
3641
  success: result.success,
3236
3642
  output: result.output,
@@ -3605,12 +4011,12 @@ function findNearestRoot(startDir, markers) {
3605
4011
  }
3606
4012
  async function commandExists(cmd) {
3607
4013
  try {
3608
- const { exec: exec6 } = await import("child_process");
3609
- const { promisify: promisify6 } = await import("util");
3610
- const execAsync6 = promisify6(exec6);
4014
+ const { exec: exec7 } = await import("child_process");
4015
+ const { promisify: promisify7 } = await import("util");
4016
+ const execAsync7 = promisify7(exec7);
3611
4017
  const isWindows = process.platform === "win32";
3612
4018
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
3613
- await execAsync6(checkCmd);
4019
+ await execAsync7(checkCmd);
3614
4020
  return true;
3615
4021
  } catch {
3616
4022
  return false;
@@ -3908,7 +4314,7 @@ async function createClient(serverId, handle, root) {
3908
4314
  const startTime = Date.now();
3909
4315
  let debounceTimer;
3910
4316
  let resolved = false;
3911
- const cleanup = () => {
4317
+ const cleanup2 = () => {
3912
4318
  if (debounceTimer) clearTimeout(debounceTimer);
3913
4319
  const listeners = diagnosticListeners.get(normalized);
3914
4320
  if (listeners) {
@@ -3922,7 +4328,7 @@ async function createClient(serverId, handle, root) {
3922
4328
  const finish = () => {
3923
4329
  if (resolved) return;
3924
4330
  resolved = true;
3925
- cleanup();
4331
+ cleanup2();
3926
4332
  resolve12(diagnostics.get(normalized) || []);
3927
4333
  };
3928
4334
  const onDiagnostic = () => {
@@ -6027,8 +6433,106 @@ function createTaskFailedTool(options) {
6027
6433
  });
6028
6434
  }
6029
6435
 
6436
+ // src/tools/upload-file.ts
6437
+ import { tool as tool12 } from "ai";
6438
+ import { z as z13 } from "zod";
6439
+ import { readFile as readFile9, stat as stat4 } from "fs/promises";
6440
+ import { join as join5, basename as basename4, extname as extname7 } from "path";
6441
+ var MIME_TYPES = {
6442
+ ".txt": "text/plain",
6443
+ ".md": "text/markdown",
6444
+ ".html": "text/html",
6445
+ ".css": "text/css",
6446
+ ".js": "application/javascript",
6447
+ ".ts": "application/typescript",
6448
+ ".json": "application/json",
6449
+ ".csv": "text/csv",
6450
+ ".xml": "application/xml",
6451
+ ".pdf": "application/pdf",
6452
+ ".png": "image/png",
6453
+ ".jpg": "image/jpeg",
6454
+ ".jpeg": "image/jpeg",
6455
+ ".gif": "image/gif",
6456
+ ".webp": "image/webp",
6457
+ ".svg": "image/svg+xml",
6458
+ ".mp4": "video/mp4",
6459
+ ".webm": "video/webm",
6460
+ ".mp3": "audio/mpeg",
6461
+ ".wav": "audio/wav",
6462
+ ".zip": "application/zip",
6463
+ ".tar": "application/x-tar",
6464
+ ".gz": "application/gzip"
6465
+ };
6466
+ function createUploadFileTool(options) {
6467
+ return tool12({
6468
+ 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.`,
6469
+ inputSchema: z13.object({
6470
+ path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
6471
+ name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
6472
+ }),
6473
+ execute: async (input) => {
6474
+ try {
6475
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
6476
+ if (!isRemoteConfigured2()) {
6477
+ return {
6478
+ success: false,
6479
+ error: "File upload is not available \u2014 remote server with GCS is not configured."
6480
+ };
6481
+ }
6482
+ const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
6483
+ try {
6484
+ await stat4(fullPath);
6485
+ } catch {
6486
+ return {
6487
+ success: false,
6488
+ error: `File not found: ${input.path}`
6489
+ };
6490
+ }
6491
+ const fileName = input.name || basename4(fullPath);
6492
+ const ext = extname7(fullPath).toLowerCase();
6493
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
6494
+ const uploadInfo = await storageQueries2.getUploadUrl(
6495
+ options.sessionId,
6496
+ fileName,
6497
+ contentType,
6498
+ "general"
6499
+ );
6500
+ const fileData = await readFile9(fullPath);
6501
+ const putRes = await fetch(uploadInfo.uploadUrl, {
6502
+ method: "PUT",
6503
+ headers: { "Content-Type": contentType },
6504
+ body: fileData
6505
+ });
6506
+ if (!putRes.ok) {
6507
+ return {
6508
+ success: false,
6509
+ error: `Upload failed: ${putRes.status} ${putRes.statusText}`
6510
+ };
6511
+ }
6512
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
6513
+ const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
6514
+ return {
6515
+ success: true,
6516
+ fileId: uploadInfo.fileId,
6517
+ fileName,
6518
+ sizeBytes: fileData.length,
6519
+ contentType,
6520
+ downloadUrl: downloadInfo.downloadUrl,
6521
+ expiresAt: downloadInfo.expiresAt
6522
+ };
6523
+ } catch (err) {
6524
+ return {
6525
+ success: false,
6526
+ error: `Upload failed: ${err.message}`
6527
+ };
6528
+ }
6529
+ }
6530
+ });
6531
+ }
6532
+
6030
6533
  // src/tools/index.ts
6031
6534
  init_semantic();
6535
+ init_remote();
6032
6536
  init_semantic_search();
6033
6537
  async function createTools(options) {
6034
6538
  const tools = {
@@ -6066,6 +6570,12 @@ async function createTools(options) {
6066
6570
  workingDirectory: options.workingDirectory
6067
6571
  })
6068
6572
  };
6573
+ if (isRemoteConfigured()) {
6574
+ tools.upload_file = createUploadFileTool({
6575
+ workingDirectory: options.workingDirectory,
6576
+ sessionId: options.sessionId
6577
+ });
6578
+ }
6069
6579
  if (options.enableSemanticSearch !== false) {
6070
6580
  try {
6071
6581
  if (isVectorGatewayConfigured()) {
@@ -6160,6 +6670,7 @@ You have access to powerful tools for:
6160
6670
  - **load_skill**: Load specialized knowledge documents for specific tasks
6161
6671
  - **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
6162
6672
  - **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
6673
+ - **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
6163
6674
 
6164
6675
 
6165
6676
  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.
@@ -6418,14 +6929,53 @@ function buildTaskPromptAddendum(outputSchema) {
6418
6929
  ## Task Mode
6419
6930
 
6420
6931
  You are running in **task mode**. You have been given a specific task to complete autonomously.
6932
+ 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.
6933
+ 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.
6421
6934
 
6422
6935
  ### Rules
6423
6936
  1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
6424
- 2. Keep working until the task is fully complete.
6937
+ 2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
6425
6938
  3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
6426
6939
  4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
6427
6940
  5. Do NOT stop without calling one of these two tools.
6428
6941
 
6942
+ ### Verification \u2014 BE EXTREMELY THOROUGH
6943
+ Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
6944
+
6945
+ **After making code changes:**
6946
+ - Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
6947
+ - **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
6948
+ - If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
6949
+ - If you created new files, verify they exist and contain what you expect.
6950
+
6951
+ **For UI / web changes:**
6952
+ - Start the dev server if it isn't already running (it might be so double check ur context)
6953
+ - **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
6954
+ - Check the dev server logs for errors or warnings.
6955
+ - If the app crashes or shows errors, fix them before completing.
6956
+
6957
+ **For backend / API changes:**
6958
+ - Test the endpoint with curl or a quick script to confirm it works as expected.
6959
+ - Check server logs for errors.
6960
+
6961
+ **For search and exploration tasks:**
6962
+ - Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
6963
+ - Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
6964
+ - Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
6965
+ - Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
6966
+
6967
+ **General verification checklist:**
6968
+ - Re-read the original task prompt and confirm every requirement has been addressed.
6969
+ - If the task asked for multiple things, verify EACH one individually.
6970
+ - If something doesn't look right, fix it \u2014 don't complete with known issues.
6971
+
6972
+ ### Use All Available Tools
6973
+ - **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
6974
+ - **explore_agent**: Use for codebase exploration and understanding before making changes.
6975
+ - **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
6976
+ - **todo**: Track your progress on multi-step tasks so you don't miss steps.
6977
+ - **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
6978
+
6429
6979
  ### Output Schema
6430
6980
  The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
6431
6981
  \`\`\`json
@@ -6433,7 +6983,7 @@ ${JSON.stringify(outputSchema, null, 2)}
6433
6983
  \`\`\`
6434
6984
 
6435
6985
  ### Completion Tools
6436
- - **\`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.
6986
+ - **\`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.
6437
6987
  - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
6438
6988
  `;
6439
6989
  }
@@ -6941,11 +7491,29 @@ ${prompt}` });
6941
7491
  const onComplete = (signal) => {
6942
7492
  completion.signal = signal;
6943
7493
  };
7494
+ let taskRecorder = null;
7495
+ const sessionId = this.session.id;
7496
+ const bashProgressHandler = (progress) => {
7497
+ options.onToolProgress?.({ toolName: "bash", data: progress });
7498
+ const port = progress.browserStreamPort;
7499
+ if (port && progress.status === "started") {
7500
+ Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
7501
+ const proxy = getOrCreateProxy2(sessionId, port);
7502
+ if (!taskRecorder) {
7503
+ Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
7504
+ taskRecorder = new FrameRecorder2(sessionId);
7505
+ taskRecorder.start();
7506
+ proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
7507
+ });
7508
+ }
7509
+ });
7510
+ }
7511
+ };
6944
7512
  const taskTools = await createTools({
6945
7513
  sessionId: this.session.id,
6946
7514
  workingDirectory: this.session.workingDirectory,
6947
7515
  skillsDirectories: config.resolvedSkillsDirectories,
6948
- onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
7516
+ onBashProgress: bashProgressHandler,
6949
7517
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
6950
7518
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
6951
7519
  taskTools: {
@@ -7016,12 +7584,24 @@ ${taskAddendum}`;
7016
7584
  if (completion.signal) {
7017
7585
  const sig = completion.signal;
7018
7586
  const finalStatus = sig.status;
7587
+ let fileUrls;
7588
+ if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
7589
+ const resultObj = sig.result;
7590
+ const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
7591
+ if (filePaths.length > 0) {
7592
+ fileUrls = await this.uploadTaskFiles(filePaths);
7593
+ }
7594
+ }
7595
+ const recordingUrls = await this.finishTaskRecording(taskRecorder);
7596
+ const allFileUrls = [...fileUrls || [], ...recordingUrls];
7019
7597
  const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
7020
7598
  fireWebhook(eventType, {
7021
7599
  status: finalStatus,
7022
7600
  result: sig.result,
7023
7601
  error: sig.error,
7024
- iterations: iteration
7602
+ iterations: iteration,
7603
+ fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
7604
+ browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
7025
7605
  });
7026
7606
  const updatedTask2 = {
7027
7607
  ...options.taskConfig,
@@ -7041,11 +7621,17 @@ ${taskAddendum}`;
7041
7621
  };
7042
7622
  }
7043
7623
  await this.context.addUserMessage(
7044
- "Continue working on the task. When done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
7624
+ "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."
7045
7625
  );
7046
7626
  }
7047
7627
  const timeoutError = `Task did not complete within ${maxIterations} iterations`;
7048
- fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
7628
+ const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
7629
+ fireWebhook("task.failed", {
7630
+ status: "failed",
7631
+ error: timeoutError,
7632
+ iterations: iteration,
7633
+ browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
7634
+ });
7049
7635
  const updatedTask = {
7050
7636
  ...options.taskConfig,
7051
7637
  status: "failed",
@@ -7057,6 +7643,113 @@ ${taskAddendum}`;
7057
7643
  });
7058
7644
  return { status: "failed", error: timeoutError, iterations: iteration };
7059
7645
  }
7646
+ /**
7647
+ * Stop a task-mode browser recording, encode to MP4, upload to GCS.
7648
+ * Returns download URLs for any recordings produced.
7649
+ */
7650
+ async finishTaskRecording(recorder) {
7651
+ try {
7652
+ const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
7653
+ destroyProxy2(this.session.id);
7654
+ } catch {
7655
+ }
7656
+ if (!recorder || recorder.frameCount === 0) {
7657
+ recorder?.clear();
7658
+ return [];
7659
+ }
7660
+ recorder.stop();
7661
+ try {
7662
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7663
+ if (!isRemoteConfigured2()) {
7664
+ recorder.clear();
7665
+ return [];
7666
+ }
7667
+ const result = await recorder.encode();
7668
+ recorder.clear();
7669
+ if (!result) return [];
7670
+ const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
7671
+ const uploadInfo = await storageQueries2.getUploadUrl(
7672
+ this.session.id,
7673
+ `browser-recording-${Date.now()}.mp4`,
7674
+ "video/mp4",
7675
+ "browser-recording"
7676
+ );
7677
+ const fileData = await readFile11(result.path);
7678
+ await fetch(uploadInfo.uploadUrl, {
7679
+ method: "PUT",
7680
+ headers: { "Content-Type": "video/mp4" },
7681
+ body: fileData
7682
+ });
7683
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
7684
+ const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
7685
+ await unlink3(result.path).catch(() => {
7686
+ });
7687
+ console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
7688
+ return [dlInfo.downloadUrl];
7689
+ } catch (err) {
7690
+ console.error("[TASK] Failed to upload browser recording:", err.message);
7691
+ recorder.clear();
7692
+ return [];
7693
+ }
7694
+ }
7695
+ /**
7696
+ * Upload task output files to GCS via the remote server.
7697
+ * Returns an array of download URLs for successfully uploaded files.
7698
+ */
7699
+ async uploadTaskFiles(filePaths) {
7700
+ try {
7701
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7702
+ if (!isRemoteConfigured2()) return [];
7703
+ const { readFile: readFile11 } = await import("fs/promises");
7704
+ const { join: join12, basename: basename6 } = await import("path");
7705
+ const urls = [];
7706
+ for (const filePath of filePaths) {
7707
+ try {
7708
+ const fullPath = filePath.startsWith("/") ? filePath : join12(this.session.workingDirectory, filePath);
7709
+ const fileName = basename6(fullPath);
7710
+ const ext = fileName.split(".").pop()?.toLowerCase() || "";
7711
+ const mimeMap = {
7712
+ pdf: "application/pdf",
7713
+ json: "application/json",
7714
+ csv: "text/csv",
7715
+ txt: "text/plain",
7716
+ md: "text/markdown",
7717
+ html: "text/html",
7718
+ png: "image/png",
7719
+ jpg: "image/jpeg",
7720
+ jpeg: "image/jpeg",
7721
+ gif: "image/gif",
7722
+ svg: "image/svg+xml",
7723
+ mp4: "video/mp4",
7724
+ zip: "application/zip"
7725
+ };
7726
+ const contentType = mimeMap[ext] || "application/octet-stream";
7727
+ const uploadInfo = await storageQueries2.getUploadUrl(
7728
+ this.session.id,
7729
+ fileName,
7730
+ contentType,
7731
+ "task-output"
7732
+ );
7733
+ const fileData = await readFile11(fullPath);
7734
+ await fetch(uploadInfo.uploadUrl, {
7735
+ method: "PUT",
7736
+ headers: { "Content-Type": contentType },
7737
+ body: fileData
7738
+ });
7739
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
7740
+ const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
7741
+ urls.push(downloadInfo.downloadUrl);
7742
+ console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
7743
+ } catch (err) {
7744
+ console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
7745
+ }
7746
+ }
7747
+ return urls;
7748
+ } catch (err) {
7749
+ console.error("[TASK] File upload failed:", err.message);
7750
+ return [];
7751
+ }
7752
+ }
7060
7753
  /**
7061
7754
  * Wrap tools to add approval checking
7062
7755
  */
@@ -7070,11 +7763,11 @@ ${taskAddendum}`;
7070
7763
  wrappedTools[name] = originalTool;
7071
7764
  continue;
7072
7765
  }
7073
- wrappedTools[name] = tool12({
7766
+ wrappedTools[name] = tool13({
7074
7767
  description: originalTool.description || "",
7075
- inputSchema: originalTool.inputSchema || z13.object({}),
7768
+ inputSchema: originalTool.inputSchema || z14.object({}),
7076
7769
  execute: async (input, toolOptions) => {
7077
- const toolCallId = toolOptions.toolCallId || nanoid3();
7770
+ const toolCallId = toolOptions.toolCallId || nanoid4();
7078
7771
  const execution = toolExecutionQueries.create({
7079
7772
  sessionId: this.session.id,
7080
7773
  toolName: name,
@@ -7092,10 +7785,10 @@ ${taskAddendum}`;
7092
7785
  const resolverData = approvalResolvers.get(toolCallId);
7093
7786
  approvalResolvers.delete(toolCallId);
7094
7787
  this.pendingApprovals.delete(toolCallId);
7095
- const exec6 = await execution;
7788
+ const exec7 = await execution;
7096
7789
  if (!approved) {
7097
7790
  const reason = resolverData?.reason || "User rejected the tool execution";
7098
- await toolExecutionQueries.reject(exec6.id);
7791
+ await toolExecutionQueries.reject(exec7.id);
7099
7792
  await sessionQueries.updateStatus(this.session.id, "active");
7100
7793
  return {
7101
7794
  status: "rejected",
@@ -7105,14 +7798,14 @@ ${taskAddendum}`;
7105
7798
  message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
7106
7799
  };
7107
7800
  }
7108
- await toolExecutionQueries.approve(exec6.id);
7801
+ await toolExecutionQueries.approve(exec7.id);
7109
7802
  await sessionQueries.updateStatus(this.session.id, "active");
7110
7803
  try {
7111
7804
  const result = await originalTool.execute(input, toolOptions);
7112
- await toolExecutionQueries.complete(exec6.id, result);
7805
+ await toolExecutionQueries.complete(exec7.id, result);
7113
7806
  return result;
7114
7807
  } catch (error) {
7115
- await toolExecutionQueries.complete(exec6.id, null, error.message);
7808
+ await toolExecutionQueries.complete(exec7.id, null, error.message);
7116
7809
  throw error;
7117
7810
  }
7118
7811
  }
@@ -7213,18 +7906,18 @@ function cleanupPendingInputs() {
7213
7906
  }
7214
7907
  }
7215
7908
  }
7216
- var createSessionSchema = z14.object({
7217
- name: z14.string().optional(),
7218
- workingDirectory: z14.string().optional(),
7219
- model: z14.string().optional(),
7220
- toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
7909
+ var createSessionSchema = z15.object({
7910
+ name: z15.string().optional(),
7911
+ workingDirectory: z15.string().optional(),
7912
+ model: z15.string().optional(),
7913
+ toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
7221
7914
  });
7222
- var paginationQuerySchema = z14.object({
7223
- limit: z14.string().optional(),
7224
- offset: z14.string().optional()
7915
+ var paginationQuerySchema = z15.object({
7916
+ limit: z15.string().optional(),
7917
+ offset: z15.string().optional()
7225
7918
  });
7226
- var messagesQuerySchema = z14.object({
7227
- limit: z14.string().optional()
7919
+ var messagesQuerySchema = z15.object({
7920
+ limit: z15.string().optional()
7228
7921
  });
7229
7922
  sessions.get(
7230
7923
  "/",
@@ -7363,10 +8056,10 @@ sessions.get("/:id/tools", async (c) => {
7363
8056
  count: executions.length
7364
8057
  });
7365
8058
  });
7366
- var updateSessionSchema = z14.object({
7367
- model: z14.string().optional(),
7368
- name: z14.string().optional(),
7369
- toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
8059
+ var updateSessionSchema = z15.object({
8060
+ model: z15.string().optional(),
8061
+ name: z15.string().optional(),
8062
+ toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
7370
8063
  });
7371
8064
  sessions.patch(
7372
8065
  "/:id",
@@ -7436,8 +8129,8 @@ sessions.post("/:id/clear", async (c) => {
7436
8129
  await agent.clearContext();
7437
8130
  return c.json({ success: true, sessionId: id });
7438
8131
  });
7439
- var pendingInputSchema = z14.object({
7440
- text: z14.string()
8132
+ var pendingInputSchema = z15.object({
8133
+ text: z15.string()
7441
8134
  });
7442
8135
  sessions.post(
7443
8136
  "/:id/pending-input",
@@ -7468,13 +8161,13 @@ sessions.get("/:id/pending-input", async (c) => {
7468
8161
  createdAt: pending.createdAt.toISOString()
7469
8162
  });
7470
8163
  });
7471
- var devtoolsContextSchema = z14.object({
7472
- url: z14.string(),
7473
- path: z14.string(),
7474
- pageName: z14.string().optional(),
7475
- screenWidth: z14.number().optional(),
7476
- screenHeight: z14.number().optional(),
7477
- devicePixelRatio: z14.number().optional()
8164
+ var devtoolsContextSchema = z15.object({
8165
+ url: z15.string(),
8166
+ path: z15.string(),
8167
+ pageName: z15.string().optional(),
8168
+ screenWidth: z15.number().optional(),
8169
+ screenHeight: z15.number().optional(),
8170
+ devicePixelRatio: z15.number().optional()
7478
8171
  });
7479
8172
  sessions.post(
7480
8173
  "/:id/devtools-context",
@@ -7642,7 +8335,7 @@ sessions.get("/:id/diff/:filePath", async (c) => {
7642
8335
  });
7643
8336
  function getAttachmentsDir(sessionId) {
7644
8337
  const appDataDir = getAppDataDirectory();
7645
- return join5(appDataDir, "attachments", sessionId);
8338
+ return join7(appDataDir, "attachments", sessionId);
7646
8339
  }
7647
8340
  function ensureAttachmentsDir(sessionId) {
7648
8341
  const dir = getAttachmentsDir(sessionId);
@@ -7663,7 +8356,7 @@ sessions.get("/:id/attachments", async (c) => {
7663
8356
  }
7664
8357
  const files = readdirSync(dir);
7665
8358
  const attachments = files.map((filename) => {
7666
- const filePath = join5(dir, filename);
8359
+ const filePath = join7(dir, filename);
7667
8360
  const stats = statSync2(filePath);
7668
8361
  return {
7669
8362
  id: filename.split("_")[0],
@@ -7695,10 +8388,10 @@ sessions.post("/:id/attachments", async (c) => {
7695
8388
  return c.json({ error: "No file provided" }, 400);
7696
8389
  }
7697
8390
  const dir = ensureAttachmentsDir(sessionId);
7698
- const id = nanoid4(10);
7699
- const ext = extname7(file.name) || "";
7700
- const safeFilename = `${id}_${basename4(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7701
- const filePath = join5(dir, safeFilename);
8391
+ const id = nanoid5(10);
8392
+ const ext = extname8(file.name) || "";
8393
+ const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8394
+ const filePath = join7(dir, safeFilename);
7702
8395
  const arrayBuffer = await file.arrayBuffer();
7703
8396
  writeFileSync2(filePath, Buffer.from(arrayBuffer));
7704
8397
  return c.json({
@@ -7721,10 +8414,10 @@ sessions.post("/:id/attachments", async (c) => {
7721
8414
  return c.json({ error: "Missing filename or data" }, 400);
7722
8415
  }
7723
8416
  const dir = ensureAttachmentsDir(sessionId);
7724
- const id = nanoid4(10);
7725
- const ext = extname7(body.filename) || "";
7726
- const safeFilename = `${id}_${basename4(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7727
- const filePath = join5(dir, safeFilename);
8417
+ const id = nanoid5(10);
8418
+ const ext = extname8(body.filename) || "";
8419
+ const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8420
+ const filePath = join7(dir, safeFilename);
7728
8421
  let base64Data = body.data;
7729
8422
  if (base64Data.includes(",")) {
7730
8423
  base64Data = base64Data.split(",")[1];
@@ -7761,14 +8454,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
7761
8454
  if (!file) {
7762
8455
  return c.json({ error: "Attachment not found" }, 404);
7763
8456
  }
7764
- const filePath = join5(dir, file);
8457
+ const filePath = join7(dir, file);
7765
8458
  unlinkSync(filePath);
7766
8459
  return c.json({ success: true, id: attachmentId });
7767
8460
  });
7768
- var filesQuerySchema = z14.object({
7769
- query: z14.string().optional(),
8461
+ var filesQuerySchema = z15.object({
8462
+ query: z15.string().optional(),
7770
8463
  // Filter query (e.g., "src/com" to match "src/components")
7771
- limit: z14.string().optional()
8464
+ limit: z15.string().optional()
7772
8465
  // Max results (default 50)
7773
8466
  });
7774
8467
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -7841,10 +8534,10 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
7841
8534
  return results;
7842
8535
  }
7843
8536
  try {
7844
- const entries = await readdir5(currentDir, { withFileTypes: true });
8537
+ const entries = await readdir6(currentDir, { withFileTypes: true });
7845
8538
  for (const entry of entries) {
7846
8539
  if (results.length >= limit * 2) break;
7847
- const fullPath = join5(currentDir, entry.name);
8540
+ const fullPath = join7(currentDir, entry.name);
7848
8541
  const relativePath = relative9(baseDir, fullPath);
7849
8542
  if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
7850
8543
  continue;
@@ -7852,7 +8545,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
7852
8545
  if (entry.name.startsWith(".")) {
7853
8546
  continue;
7854
8547
  }
7855
- const ext = extname7(entry.name).toLowerCase();
8548
+ const ext = extname8(entry.name).toLowerCase();
7856
8549
  if (IGNORED_EXTENSIONS.has(ext)) {
7857
8550
  continue;
7858
8551
  }
@@ -7941,14 +8634,70 @@ sessions.get(
7941
8634
  }
7942
8635
  }
7943
8636
  );
8637
+ sessions.get("/:id/session-files", async (c) => {
8638
+ const sessionId = c.req.param("id");
8639
+ try {
8640
+ const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
8641
+ if (!isRemoteConfigured2()) {
8642
+ return c.json({ files: [] });
8643
+ }
8644
+ const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
8645
+ const files = await storageQueries2.getSessionFiles(sessionId);
8646
+ return c.json({ sessionId, files });
8647
+ } catch (err) {
8648
+ console.error("Failed to get session files:", err.message);
8649
+ return c.json({ sessionId, files: [] });
8650
+ }
8651
+ });
8652
+ sessions.get("/files/:fileId/download", async (c) => {
8653
+ const fileId = c.req.param("fileId");
8654
+ try {
8655
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
8656
+ if (!isRemoteConfigured2()) {
8657
+ return c.json({ error: "Remote server not configured" }, 503);
8658
+ }
8659
+ const result = await storageQueries2.getDownloadUrl(fileId);
8660
+ return c.json(result);
8661
+ } catch (err) {
8662
+ return c.json({ error: err.message }, 500);
8663
+ }
8664
+ });
8665
+ sessions.get("/:id/browser-recording", async (c) => {
8666
+ const sessionId = c.req.param("id");
8667
+ try {
8668
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
8669
+ if (!isRemoteConfigured2()) {
8670
+ return c.json({ sessionId, recordings: [] });
8671
+ }
8672
+ const files = await storageQueries2.getSessionFiles(sessionId);
8673
+ const recordings = files.filter((f) => f.category === "browser-recording");
8674
+ if (recordings.length === 0) {
8675
+ return c.json({ sessionId, recordings: [], message: "No browser recordings for this session" });
8676
+ }
8677
+ return c.json({
8678
+ sessionId,
8679
+ recordings: recordings.map((r) => ({
8680
+ id: r.id,
8681
+ fileName: r.fileName,
8682
+ sizeBytes: r.sizeBytes,
8683
+ createdAt: r.createdAt,
8684
+ downloadUrl: r.downloadUrl,
8685
+ expiresAt: r.downloadUrlExpiresAt
8686
+ }))
8687
+ });
8688
+ } catch (err) {
8689
+ console.error("Failed to get browser recordings:", err.message);
8690
+ return c.json({ sessionId, recordings: [], error: err.message });
8691
+ }
8692
+ });
7944
8693
 
7945
8694
  // src/server/routes/agents.ts
7946
8695
  init_db();
7947
8696
  import { Hono as Hono2 } from "hono";
7948
8697
  import { zValidator as zValidator2 } from "@hono/zod-validator";
7949
- import { z as z15 } from "zod";
8698
+ import { z as z16 } from "zod";
7950
8699
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
7951
- import { join as join6 } from "path";
8700
+ import { join as join8 } from "path";
7952
8701
  init_config();
7953
8702
 
7954
8703
  // src/server/resumable-stream.ts
@@ -8024,7 +8773,11 @@ var streamContext = createResumableStreamContext({
8024
8773
  });
8025
8774
 
8026
8775
  // src/server/routes/agents.ts
8027
- import { nanoid as nanoid5 } from "nanoid";
8776
+ import { nanoid as nanoid6 } from "nanoid";
8777
+ init_stream_proxy();
8778
+ init_recorder();
8779
+ init_remote();
8780
+ var sessionRecorders = /* @__PURE__ */ new Map();
8028
8781
  var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
8029
8782
  var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
8030
8783
  var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
@@ -8111,35 +8864,35 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
8111
8864
  ${prompt}`;
8112
8865
  }
8113
8866
  var agents = new Hono2();
8114
- var attachmentSchema = z15.object({
8115
- type: z15.enum(["image", "file"]),
8116
- data: z15.string(),
8867
+ var attachmentSchema = z16.object({
8868
+ type: z16.enum(["image", "file"]),
8869
+ data: z16.string(),
8117
8870
  // base64 data URL or raw base64
8118
- mediaType: z15.string().optional(),
8119
- filename: z15.string().optional()
8871
+ mediaType: z16.string().optional(),
8872
+ filename: z16.string().optional()
8120
8873
  });
8121
- var runPromptSchema = z15.object({
8122
- prompt: z15.string(),
8874
+ var runPromptSchema = z16.object({
8875
+ prompt: z16.string(),
8123
8876
  // Can be empty if attachments are provided
8124
- attachments: z15.array(attachmentSchema).optional()
8877
+ attachments: z16.array(attachmentSchema).optional()
8125
8878
  }).refine(
8126
8879
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
8127
8880
  { message: "Either prompt or attachments must be provided" }
8128
8881
  );
8129
- var quickStartSchema = z15.object({
8130
- prompt: z15.string().min(1),
8131
- name: z15.string().optional(),
8132
- workingDirectory: z15.string().optional(),
8133
- model: z15.string().optional(),
8134
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
8882
+ var quickStartSchema = z16.object({
8883
+ prompt: z16.string().min(1),
8884
+ name: z16.string().optional(),
8885
+ workingDirectory: z16.string().optional(),
8886
+ model: z16.string().optional(),
8887
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
8135
8888
  });
8136
- var rejectSchema = z15.object({
8137
- reason: z15.string().optional()
8889
+ var rejectSchema = z16.object({
8890
+ reason: z16.string().optional()
8138
8891
  }).optional();
8139
8892
  var streamAbortControllers = /* @__PURE__ */ new Map();
8140
8893
  function getAttachmentsDirectory(sessionId) {
8141
8894
  const appDataDir = getAppDataDirectory();
8142
- return join6(appDataDir, "attachments", sessionId);
8895
+ return join8(appDataDir, "attachments", sessionId);
8143
8896
  }
8144
8897
  function saveAttachmentToDisk(sessionId, attachment, index) {
8145
8898
  const attachmentsDir = getAttachmentsDirectory(sessionId);
@@ -8155,7 +8908,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
8155
8908
  if (base64Data.includes(",")) {
8156
8909
  base64Data = base64Data.split(",")[1];
8157
8910
  }
8158
- const filePath = join6(attachmentsDir, filename);
8911
+ const filePath = join8(attachmentsDir, filename);
8159
8912
  const buffer = Buffer.from(base64Data, "base64");
8160
8913
  writeFileSync3(filePath, buffer);
8161
8914
  return filePath;
@@ -8323,6 +9076,40 @@ ${prompt}` });
8323
9076
  }));
8324
9077
  await new Promise((resolve12) => setTimeout(resolve12, 0));
8325
9078
  }
9079
+ const browserPort = progress.data?.browserStreamPort;
9080
+ const browserClosed = progress.data?.browserClosed;
9081
+ if (progress.toolName === "bash" && browserClosed) {
9082
+ console.log(`[BROWSER-STREAM] agent-browser close detected, destroying proxy`);
9083
+ destroyProxy(sessionId);
9084
+ } else if (progress.toolName === "bash" && browserPort) {
9085
+ console.log(`[BROWSER-STREAM] agent-browser command detected, ensuring proxy on port ${browserPort}`);
9086
+ const proxy = getOrCreateProxy(sessionId, browserPort);
9087
+ if (!sessionRecorders.has(sessionId)) {
9088
+ const recorder = new FrameRecorder(sessionId);
9089
+ recorder.start();
9090
+ sessionRecorders.set(sessionId, recorder);
9091
+ }
9092
+ if (proxy.listenerCount("frame") === 0) {
9093
+ proxy.on("frame", (frame) => {
9094
+ const rec = sessionRecorders.get(sessionId);
9095
+ rec?.addFrame(frame);
9096
+ writeSSE(JSON.stringify({
9097
+ type: "browser-frame",
9098
+ data: frame.data,
9099
+ metadata: frame.metadata
9100
+ })).catch(() => {
9101
+ });
9102
+ });
9103
+ proxy.on("status", (s) => {
9104
+ console.log(`[BROWSER-STREAM] Status:`, s);
9105
+ writeSSE(JSON.stringify({
9106
+ type: "browser-status",
9107
+ ...s
9108
+ })).catch(() => {
9109
+ });
9110
+ });
9111
+ }
9112
+ }
8326
9113
  },
8327
9114
  onStepFinish: async () => {
8328
9115
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -8509,7 +9296,7 @@ ${prompt}` });
8509
9296
  userMessageContent = prompt;
8510
9297
  }
8511
9298
  await messageQueries.create(id, { role: "user", content: userMessageContent });
8512
- const streamId = `stream_${id}_${nanoid5(10)}`;
9299
+ const streamId = `stream_${id}_${nanoid6(10)}`;
8513
9300
  await activeStreamQueries.create(id, streamId);
8514
9301
  const stream = await streamContext.resumableStream(
8515
9302
  streamId,
@@ -8708,7 +9495,7 @@ agents.post(
8708
9495
  });
8709
9496
  const session = agent.getSession();
8710
9497
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
8711
- const streamId = `stream_${session.id}_${nanoid5(10)}`;
9498
+ const streamId = `stream_${session.id}_${nanoid6(10)}`;
8712
9499
  await createCheckpoint(session.id, session.workingDirectory, 0);
8713
9500
  await activeStreamQueries.create(session.id, streamId);
8714
9501
  const createQuickStreamProducer = () => {
@@ -8787,6 +9574,40 @@ agents.post(
8787
9574
  }));
8788
9575
  await new Promise((resolve12) => setTimeout(resolve12, 0));
8789
9576
  }
9577
+ const browserPort = progress.data?.browserStreamPort;
9578
+ const browserClosed = progress.data?.browserClosed;
9579
+ if (progress.toolName === "bash" && browserClosed) {
9580
+ console.log(`[BROWSER-STREAM] agent-browser close detected`);
9581
+ destroyProxy(session.id);
9582
+ } else if (progress.toolName === "bash" && browserPort) {
9583
+ console.log(`[BROWSER-STREAM] agent-browser command detected, port ${browserPort}`);
9584
+ const proxy = getOrCreateProxy(session.id, browserPort);
9585
+ if (!sessionRecorders.has(session.id)) {
9586
+ const recorder = new FrameRecorder(session.id);
9587
+ recorder.start();
9588
+ sessionRecorders.set(session.id, recorder);
9589
+ }
9590
+ if (proxy.listenerCount("frame") === 0) {
9591
+ proxy.on("frame", (frame) => {
9592
+ const rec = sessionRecorders.get(session.id);
9593
+ rec?.addFrame(frame);
9594
+ writeSSE(JSON.stringify({
9595
+ type: "browser-frame",
9596
+ data: frame.data,
9597
+ metadata: frame.metadata
9598
+ })).catch(() => {
9599
+ });
9600
+ });
9601
+ proxy.on("status", (s) => {
9602
+ console.log(`[BROWSER-STREAM] Status:`, s);
9603
+ writeSSE(JSON.stringify({
9604
+ type: "browser-status",
9605
+ ...s
9606
+ })).catch(() => {
9607
+ });
9608
+ });
9609
+ }
9610
+ }
8790
9611
  },
8791
9612
  onStepFinish: async () => {
8792
9613
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -8917,25 +9738,71 @@ agents.post(
8917
9738
  });
8918
9739
  }
8919
9740
  );
9741
+ var browserInputSchema = z16.object({
9742
+ type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
9743
+ eventType: z16.string(),
9744
+ x: z16.number().optional(),
9745
+ y: z16.number().optional(),
9746
+ button: z16.string().optional(),
9747
+ clickCount: z16.number().optional(),
9748
+ deltaX: z16.number().optional(),
9749
+ deltaY: z16.number().optional(),
9750
+ key: z16.string().optional(),
9751
+ code: z16.string().optional(),
9752
+ text: z16.string().optional(),
9753
+ modifiers: z16.number().optional(),
9754
+ touchPoints: z16.array(z16.object({
9755
+ x: z16.number(),
9756
+ y: z16.number(),
9757
+ id: z16.number().optional()
9758
+ })).optional()
9759
+ });
9760
+ agents.post(
9761
+ "/:id/browser-input",
9762
+ zValidator2("json", browserInputSchema),
9763
+ async (c) => {
9764
+ const sessionId = c.req.param("id");
9765
+ const event = c.req.valid("json");
9766
+ const proxy = getProxy(sessionId);
9767
+ if (!proxy || !proxy.connected) {
9768
+ return c.json({ error: "No active browser stream for this session" }, 404);
9769
+ }
9770
+ proxy.injectInput(event);
9771
+ return c.json({ success: true });
9772
+ }
9773
+ );
9774
+ agents.get("/:id/browser-stream", async (c) => {
9775
+ const sessionId = c.req.param("id");
9776
+ const proxy = getProxy(sessionId);
9777
+ return c.json({
9778
+ sessionId,
9779
+ active: !!proxy?.connected,
9780
+ hasProxy: !!proxy,
9781
+ latestFrame: proxy?.latestFrame ? {
9782
+ metadata: proxy.latestFrame.metadata,
9783
+ timestamp: proxy.latestFrame.timestamp
9784
+ } : null
9785
+ });
9786
+ });
8920
9787
 
8921
9788
  // src/server/routes/health.ts
8922
9789
  init_config();
8923
9790
  import { Hono as Hono3 } from "hono";
8924
9791
  import { zValidator as zValidator3 } from "@hono/zod-validator";
8925
- import { z as z16 } from "zod";
9792
+ import { z as z17 } from "zod";
8926
9793
  import { readFileSync as readFileSync5 } from "fs";
8927
9794
  import { fileURLToPath as fileURLToPath3 } from "url";
8928
- import { dirname as dirname6, join as join7 } from "path";
9795
+ import { dirname as dirname6, join as join9 } from "path";
8929
9796
  var __filename = fileURLToPath3(import.meta.url);
8930
9797
  var __dirname = dirname6(__filename);
8931
9798
  var possiblePaths = [
8932
- join7(__dirname, "../package.json"),
9799
+ join9(__dirname, "../package.json"),
8933
9800
  // From dist/server -> dist/../package.json
8934
- join7(__dirname, "../../package.json"),
9801
+ join9(__dirname, "../../package.json"),
8935
9802
  // From dist/server (if nested differently)
8936
- join7(__dirname, "../../../package.json"),
9803
+ join9(__dirname, "../../../package.json"),
8937
9804
  // From src/server/routes (development)
8938
- join7(process.cwd(), "package.json")
9805
+ join9(process.cwd(), "package.json")
8939
9806
  // From current working directory
8940
9807
  ];
8941
9808
  var currentVersion = "0.0.0";
@@ -9032,9 +9899,9 @@ health.get("/api-keys", async (c) => {
9032
9899
  supportedProviders: SUPPORTED_PROVIDERS
9033
9900
  });
9034
9901
  });
9035
- var setApiKeySchema = z16.object({
9036
- provider: z16.string(),
9037
- apiKey: z16.string().min(1)
9902
+ var setApiKeySchema = z17.object({
9903
+ provider: z17.string(),
9904
+ apiKey: z17.string().min(1)
9038
9905
  });
9039
9906
  health.post(
9040
9907
  "/api-keys",
@@ -9073,13 +9940,13 @@ health.delete("/api-keys/:provider", async (c) => {
9073
9940
  // src/server/routes/terminals.ts
9074
9941
  import { Hono as Hono4 } from "hono";
9075
9942
  import { zValidator as zValidator4 } from "@hono/zod-validator";
9076
- import { z as z17 } from "zod";
9943
+ import { z as z18 } from "zod";
9077
9944
  init_db();
9078
9945
  var terminals = new Hono4();
9079
- var spawnSchema = z17.object({
9080
- command: z17.string(),
9081
- cwd: z17.string().optional(),
9082
- name: z17.string().optional()
9946
+ var spawnSchema = z18.object({
9947
+ command: z18.string(),
9948
+ cwd: z18.string().optional(),
9949
+ name: z18.string().optional()
9083
9950
  });
9084
9951
  terminals.post(
9085
9952
  "/:sessionId/terminals",
@@ -9160,8 +10027,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
9160
10027
  // We don't track exit codes in tmux mode
9161
10028
  });
9162
10029
  });
9163
- var logsQuerySchema = z17.object({
9164
- tail: z17.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
10030
+ var logsQuerySchema = z18.object({
10031
+ tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
9165
10032
  });
9166
10033
  terminals.get(
9167
10034
  "/:sessionId/terminals/:terminalId/logs",
@@ -9185,8 +10052,8 @@ terminals.get(
9185
10052
  });
9186
10053
  }
9187
10054
  );
9188
- var killSchema = z17.object({
9189
- signal: z17.enum(["SIGTERM", "SIGKILL"]).optional()
10055
+ var killSchema = z18.object({
10056
+ signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
9190
10057
  });
9191
10058
  terminals.post(
9192
10059
  "/:sessionId/terminals/:terminalId/kill",
@@ -9200,8 +10067,8 @@ terminals.post(
9200
10067
  return c.json({ success: true, message: "Terminal killed" });
9201
10068
  }
9202
10069
  );
9203
- var writeSchema = z17.object({
9204
- input: z17.string()
10070
+ var writeSchema = z18.object({
10071
+ input: z18.string()
9205
10072
  });
9206
10073
  terminals.post(
9207
10074
  "/:sessionId/terminals/:terminalId/write",
@@ -9386,18 +10253,18 @@ data: ${JSON.stringify({ status: "stopped" })}
9386
10253
  init_db();
9387
10254
  import { Hono as Hono5 } from "hono";
9388
10255
  import { zValidator as zValidator5 } from "@hono/zod-validator";
9389
- import { z as z18 } from "zod";
10256
+ import { z as z19 } from "zod";
9390
10257
  init_config();
9391
10258
  var tasks = new Hono5();
9392
10259
  var taskAbortControllers = /* @__PURE__ */ new Map();
9393
- var createTaskSchema = z18.object({
9394
- prompt: z18.string().min(1),
9395
- outputSchema: z18.record(z18.string(), z18.unknown()),
9396
- webhookUrl: z18.string().url().optional(),
9397
- model: z18.string().optional(),
9398
- workingDirectory: z18.string().optional(),
9399
- name: z18.string().optional(),
9400
- maxIterations: z18.number().int().min(1).max(500).optional()
10260
+ var createTaskSchema = z19.object({
10261
+ prompt: z19.string().min(1),
10262
+ outputSchema: z19.record(z19.string(), z19.unknown()),
10263
+ webhookUrl: z19.string().url().optional(),
10264
+ model: z19.string().optional(),
10265
+ workingDirectory: z19.string().optional(),
10266
+ name: z19.string().optional(),
10267
+ maxIterations: z19.number().int().min(1).max(500).optional()
9401
10268
  });
9402
10269
  tasks.post(
9403
10270
  "/",
@@ -9476,6 +10343,15 @@ tasks.get("/:id", async (c) => {
9476
10343
  if (!task?.enabled) {
9477
10344
  return c.json({ error: "Session is not a task" }, 400);
9478
10345
  }
10346
+ let browserRecordings = [];
10347
+ try {
10348
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
10349
+ if (isRemoteConfigured2()) {
10350
+ const files = await storageQueries2.getSessionFiles(id);
10351
+ browserRecordings = files.filter((f) => f.category === "browser-recording").map((f) => ({ fileName: f.fileName, downloadUrl: f.downloadUrl, sizeBytes: f.sizeBytes }));
10352
+ }
10353
+ } catch {
10354
+ }
9479
10355
  return c.json({
9480
10356
  taskId: id,
9481
10357
  status: task.status,
@@ -9485,7 +10361,8 @@ tasks.get("/:id", async (c) => {
9485
10361
  model: session.model,
9486
10362
  name: session.name,
9487
10363
  createdAt: session.createdAt.toISOString(),
9488
- updatedAt: session.updatedAt.toISOString()
10364
+ updatedAt: session.updatedAt.toISOString(),
10365
+ browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
9489
10366
  });
9490
10367
  });
9491
10368
  tasks.post("/:id/cancel", async (c) => {
@@ -9533,10 +10410,10 @@ init_config();
9533
10410
  init_db();
9534
10411
 
9535
10412
  // src/utils/dependencies.ts
9536
- import { exec as exec5 } from "child_process";
9537
- import { promisify as promisify5 } from "util";
10413
+ import { exec as exec6 } from "child_process";
10414
+ import { promisify as promisify6 } from "util";
9538
10415
  import { platform as platform2 } from "os";
9539
- var execAsync5 = promisify5(exec5);
10416
+ var execAsync6 = promisify6(exec6);
9540
10417
  function getInstallInstructions() {
9541
10418
  const os2 = platform2();
9542
10419
  if (os2 === "darwin") {
@@ -9569,7 +10446,7 @@ Install tmux:
9569
10446
  }
9570
10447
  async function checkTmux() {
9571
10448
  try {
9572
- const { stdout } = await execAsync5("tmux -V", { timeout: 5e3 });
10449
+ const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
9573
10450
  const version = stdout.trim();
9574
10451
  return {
9575
10452
  available: true,
@@ -9610,7 +10487,7 @@ async function checkDependencies(options = {}) {
9610
10487
  }
9611
10488
  async function checkAgentBrowser() {
9612
10489
  try {
9613
- const { stdout } = await execAsync5("agent-browser --version", { timeout: 1e4 });
10490
+ const { stdout } = await execAsync6("agent-browser --version", { timeout: 1e4 });
9614
10491
  const version = stdout.trim();
9615
10492
  return { available: true, version };
9616
10493
  } catch {
@@ -9626,12 +10503,12 @@ async function tryInstallAgentBrowser(options = {}) {
9626
10503
  if (!options.quiet) {
9627
10504
  console.log("\u{1F4E6} Installing agent-browser globally...");
9628
10505
  }
9629
- await execAsync5("npm install -g agent-browser", { timeout: 12e4 });
10506
+ await execAsync6("npm install -g agent-browser", { timeout: 12e4 });
9630
10507
  try {
9631
10508
  if (!options.quiet) {
9632
10509
  console.log("\u{1F4E6} Installing Chromium for browser automation...");
9633
10510
  }
9634
- await execAsync5("agent-browser install", { timeout: 12e4 });
10511
+ await execAsync6("agent-browser install", { timeout: 12e4 });
9635
10512
  } catch {
9636
10513
  }
9637
10514
  if (!options.quiet) {
@@ -9650,21 +10527,21 @@ async function tryAutoInstallTmux() {
9650
10527
  try {
9651
10528
  if (os2 === "darwin") {
9652
10529
  try {
9653
- await execAsync5("which brew", { timeout: 5e3 });
10530
+ await execAsync6("which brew", { timeout: 5e3 });
9654
10531
  } catch {
9655
10532
  return false;
9656
10533
  }
9657
10534
  console.log("\u{1F4E6} Installing tmux via Homebrew...");
9658
- await execAsync5("brew install tmux", { timeout: 3e5 });
10535
+ await execAsync6("brew install tmux", { timeout: 3e5 });
9659
10536
  console.log("\u2705 tmux installed successfully");
9660
10537
  return true;
9661
10538
  }
9662
10539
  if (os2 === "linux") {
9663
10540
  try {
9664
- await execAsync5("which apt-get", { timeout: 5e3 });
10541
+ await execAsync6("which apt-get", { timeout: 5e3 });
9665
10542
  console.log("\u{1F4E6} Installing tmux via apt-get...");
9666
10543
  console.log(" (This may require sudo password)");
9667
- await execAsync5("sudo apt-get update && sudo apt-get install -y tmux", {
10544
+ await execAsync6("sudo apt-get update && sudo apt-get install -y tmux", {
9668
10545
  timeout: 3e5
9669
10546
  });
9670
10547
  console.log("\u2705 tmux installed successfully");
@@ -9672,9 +10549,9 @@ async function tryAutoInstallTmux() {
9672
10549
  } catch {
9673
10550
  }
9674
10551
  try {
9675
- await execAsync5("which dnf", { timeout: 5e3 });
10552
+ await execAsync6("which dnf", { timeout: 5e3 });
9676
10553
  console.log("\u{1F4E6} Installing tmux via dnf...");
9677
- await execAsync5("sudo dnf install -y tmux", { timeout: 3e5 });
10554
+ await execAsync6("sudo dnf install -y tmux", { timeout: 3e5 });
9678
10555
  console.log("\u2705 tmux installed successfully");
9679
10556
  return true;
9680
10557
  } catch {
@@ -9714,11 +10591,11 @@ function getWebDirectory() {
9714
10591
  try {
9715
10592
  const currentDir = dirname7(fileURLToPath4(import.meta.url));
9716
10593
  const webDir = resolve10(currentDir, "..", "web");
9717
- if (existsSync15(webDir) && existsSync15(join8(webDir, "package.json"))) {
10594
+ if (existsSync15(webDir) && existsSync15(join10(webDir, "package.json"))) {
9718
10595
  return webDir;
9719
10596
  }
9720
10597
  const altWebDir = resolve10(currentDir, "..", "..", "web");
9721
- if (existsSync15(altWebDir) && existsSync15(join8(altWebDir, "package.json"))) {
10598
+ if (existsSync15(altWebDir) && existsSync15(join10(altWebDir, "package.json"))) {
9722
10599
  return altWebDir;
9723
10600
  }
9724
10601
  return null;
@@ -9776,20 +10653,20 @@ async function findWebPort(preferredPort) {
9776
10653
  return { port: preferredPort, alreadyRunning: false };
9777
10654
  }
9778
10655
  function hasProductionBuild(webDir) {
9779
- const buildIdPath = join8(webDir, ".next", "BUILD_ID");
10656
+ const buildIdPath = join10(webDir, ".next", "BUILD_ID");
9780
10657
  return existsSync15(buildIdPath);
9781
10658
  }
9782
10659
  function hasSourceFiles(webDir) {
9783
- const appDir = join8(webDir, "src", "app");
9784
- const pagesDir = join8(webDir, "src", "pages");
9785
- const rootAppDir = join8(webDir, "app");
9786
- const rootPagesDir = join8(webDir, "pages");
10660
+ const appDir = join10(webDir, "src", "app");
10661
+ const pagesDir = join10(webDir, "src", "pages");
10662
+ const rootAppDir = join10(webDir, "app");
10663
+ const rootPagesDir = join10(webDir, "pages");
9787
10664
  return existsSync15(appDir) || existsSync15(pagesDir) || existsSync15(rootAppDir) || existsSync15(rootPagesDir);
9788
10665
  }
9789
10666
  function getStandaloneServerPath(webDir) {
9790
10667
  const possiblePaths2 = [
9791
- join8(webDir, ".next", "standalone", "server.js"),
9792
- join8(webDir, ".next", "standalone", "web", "server.js")
10668
+ join10(webDir, ".next", "standalone", "server.js"),
10669
+ join10(webDir, ".next", "standalone", "web", "server.js")
9793
10670
  ];
9794
10671
  for (const serverPath of possiblePaths2) {
9795
10672
  if (existsSync15(serverPath)) {
@@ -9832,13 +10709,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
9832
10709
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
9833
10710
  return { process: null, port: actualPort };
9834
10711
  }
9835
- const usePnpm = existsSync15(join8(webDir, "pnpm-lock.yaml"));
9836
- const useNpm = !usePnpm && existsSync15(join8(webDir, "package-lock.json"));
10712
+ const usePnpm = existsSync15(join10(webDir, "pnpm-lock.yaml"));
10713
+ const useNpm = !usePnpm && existsSync15(join10(webDir, "package-lock.json"));
9837
10714
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
9838
10715
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
9839
10716
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
9840
10717
  const runtimeConfig = { apiBaseUrl: apiUrl };
9841
- const runtimeConfigPath = join8(webDir, "runtime-config.json");
10718
+ const runtimeConfigPath = join10(webDir, "runtime-config.json");
9842
10719
  try {
9843
10720
  writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
9844
10721
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -10549,7 +11426,7 @@ init_config();
10549
11426
  init_semantic();
10550
11427
  init_db();
10551
11428
  import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync16 } from "fs";
10552
- import { resolve as resolve11, join as join9 } from "path";
11429
+ import { resolve as resolve11, join as join11 } from "path";
10553
11430
  async function apiRequest(baseUrl, path, options = {}) {
10554
11431
  const url = `${baseUrl}${path}`;
10555
11432
  const init = {
@@ -10769,14 +11646,14 @@ async function runChat(options) {
10769
11646
  } else {
10770
11647
  spinner.succeed(`Web UI: ${chalk.cyan(webUrl)}`);
10771
11648
  }
10772
- const cleanup = () => {
11649
+ const cleanup2 = () => {
10773
11650
  if (serverStartedByUs) {
10774
11651
  stopServer();
10775
11652
  }
10776
11653
  process.exit(0);
10777
11654
  };
10778
- process.on("SIGINT", cleanup);
10779
- process.on("SIGTERM", cleanup);
11655
+ process.on("SIGINT", cleanup2);
11656
+ process.on("SIGTERM", cleanup2);
10780
11657
  } catch (err) {
10781
11658
  spinner.fail(chalk.red(`Failed to start server: ${err.message}`));
10782
11659
  process.exit(1);
@@ -11140,13 +12017,13 @@ program.command("server").description("Start the SparkECoder server (API + Web U
11140
12017
  console.log(chalk.dim(` Swagger: http://${host}:${port}/swagger`));
11141
12018
  console.log("");
11142
12019
  console.log(chalk.dim("Press Ctrl+C to stop"));
11143
- const cleanup = () => {
12020
+ const cleanup2 = () => {
11144
12021
  console.log(chalk.dim("\nShutting down..."));
11145
12022
  stopServer();
11146
12023
  process.exit(0);
11147
12024
  };
11148
- process.on("SIGINT", cleanup);
11149
- process.on("SIGTERM", cleanup);
12025
+ process.on("SIGINT", cleanup2);
12026
+ process.on("SIGTERM", cleanup2);
11150
12027
  } catch (error) {
11151
12028
  spinner.fail(chalk.red(`Failed to start server: ${error.message}`));
11152
12029
  process.exit(1);
@@ -11251,7 +12128,7 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
11251
12128
  let configLocation;
11252
12129
  if (options.global) {
11253
12130
  const appDataDir = ensureAppDataDirectory();
11254
- configPath = join9(appDataDir, "sparkecoder.config.json");
12131
+ configPath = join11(appDataDir, "sparkecoder.config.json");
11255
12132
  configLocation = "global";
11256
12133
  } else {
11257
12134
  configPath = resolve11(process.cwd(), "sparkecoder.config.json");