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
@@ -194,6 +194,36 @@ var init_config = __esm({
194
194
  });
195
195
 
196
196
  // src/db/remote.ts
197
+ var remote_exports = {};
198
+ __export(remote_exports, {
199
+ closeRemoteDatabase: () => closeRemoteDatabase,
200
+ initRemoteDatabase: () => initRemoteDatabase,
201
+ isRemoteConfigured: () => isRemoteConfigured,
202
+ remoteActiveStreamQueries: () => remoteActiveStreamQueries,
203
+ remoteCheckpointQueries: () => remoteCheckpointQueries,
204
+ remoteFileBackupQueries: () => remoteFileBackupQueries,
205
+ remoteIndexStatusQueries: () => remoteIndexStatusQueries,
206
+ remoteIndexedChunkQueries: () => remoteIndexedChunkQueries,
207
+ remoteMessageQueries: () => remoteMessageQueries,
208
+ remoteSessionQueries: () => remoteSessionQueries,
209
+ remoteSkillQueries: () => remoteSkillQueries,
210
+ remoteSubagentQueries: () => remoteSubagentQueries,
211
+ remoteTerminalQueries: () => remoteTerminalQueries,
212
+ remoteTodoQueries: () => remoteTodoQueries,
213
+ remoteToolExecutionQueries: () => remoteToolExecutionQueries,
214
+ storageQueries: () => storageQueries
215
+ });
216
+ function initRemoteDatabase(serverUrl, key) {
217
+ remoteServerUrl = serverUrl.replace(/\/$/, "");
218
+ authKey = key;
219
+ }
220
+ function closeRemoteDatabase() {
221
+ remoteServerUrl = null;
222
+ authKey = null;
223
+ }
224
+ function isRemoteConfigured() {
225
+ return !!remoteServerUrl && !!authKey;
226
+ }
197
227
  function parseDates(obj) {
198
228
  if (obj === null || obj === void 0) return obj;
199
229
  if (Array.isArray(obj)) return obj.map(parseDates);
@@ -241,7 +271,29 @@ async function api(path, options = {}) {
241
271
  }
242
272
  return parseDates(parsed);
243
273
  }
244
- var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexStatusQueries;
274
+ async function storageApi(path, options = {}) {
275
+ if (!remoteServerUrl || !authKey) {
276
+ throw new Error("Remote database not initialized");
277
+ }
278
+ const url = `${remoteServerUrl}/storage${path}`;
279
+ const init = {
280
+ method: options.method || "GET",
281
+ headers: {
282
+ "Content-Type": "application/json",
283
+ "Authorization": `Bearer ${authKey}`
284
+ }
285
+ };
286
+ if (options.body) {
287
+ init.body = JSON.stringify(options.body);
288
+ }
289
+ const response = await fetch(url, init);
290
+ if (!response.ok) {
291
+ const errorText = await response.text().catch(() => "Unknown error");
292
+ throw new Error(`Storage API error ${response.status}: ${errorText}`);
293
+ }
294
+ return response.json();
295
+ }
296
+ var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteTerminalQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexedChunkQueries, remoteIndexStatusQueries, storageQueries;
245
297
  var init_remote = __esm({
246
298
  "src/db/remote.ts"() {
247
299
  "use strict";
@@ -380,6 +432,83 @@ var init_remote = __esm({
380
432
  return result.isLoaded;
381
433
  }
382
434
  };
435
+ remoteTerminalQueries = {
436
+ create(data) {
437
+ return api("/terminals", { method: "POST", body: data });
438
+ },
439
+ getById(id) {
440
+ return api(`/terminals/${id}`).catch(() => void 0);
441
+ },
442
+ getBySession(sessionId) {
443
+ return api(`/terminals/session/${sessionId}`);
444
+ },
445
+ getRunning(sessionId) {
446
+ return api(`/terminals/session/${sessionId}/running`);
447
+ },
448
+ updateStatus(id, status, exitCode, error) {
449
+ return api(`/terminals/${id}`, { method: "PATCH", body: { status, exitCode, error } });
450
+ },
451
+ updatePid(id, pid) {
452
+ return api(`/terminals/${id}`, { method: "PATCH", body: { pid } });
453
+ },
454
+ async delete(id) {
455
+ const result = await api(`/terminals/${id}`, { method: "DELETE" });
456
+ return result?.success ?? false;
457
+ },
458
+ async deleteBySession(sessionId) {
459
+ const result = await api(`/terminals/session/${sessionId}`, { method: "DELETE" });
460
+ return result.deleted;
461
+ }
462
+ };
463
+ remoteActiveStreamQueries = {
464
+ create(sessionId, streamId) {
465
+ return api("/streams", { method: "POST", body: { sessionId, streamId } });
466
+ },
467
+ getBySessionId(sessionId) {
468
+ return api(`/streams/session/${sessionId}`).then((r) => r ?? void 0);
469
+ },
470
+ getByStreamId(streamId) {
471
+ return api(`/streams/by-stream-id/${streamId}`).catch(() => void 0);
472
+ },
473
+ finish(streamId) {
474
+ return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "finished" } });
475
+ },
476
+ markError(streamId) {
477
+ return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "error" } });
478
+ },
479
+ async deleteBySession(sessionId) {
480
+ const result = await api(`/streams/session/${sessionId}`, { method: "DELETE" });
481
+ return result.deleted;
482
+ }
483
+ };
484
+ remoteCheckpointQueries = {
485
+ create(data) {
486
+ return api("/checkpoints", { method: "POST", body: data });
487
+ },
488
+ getById(id) {
489
+ return api(`/checkpoints/${id}`).catch(() => void 0);
490
+ },
491
+ getBySession(sessionId) {
492
+ return api(`/checkpoints/session/${sessionId}`);
493
+ },
494
+ getByMessageSequence(sessionId, messageSequence) {
495
+ return api(`/checkpoints/session/${sessionId}/by-sequence/${messageSequence}`).then((r) => r ?? void 0);
496
+ },
497
+ getLatest(sessionId) {
498
+ return api(`/checkpoints/session/${sessionId}/latest`).then((r) => r ?? void 0);
499
+ },
500
+ async deleteAfterSequence(sessionId, messageSequence) {
501
+ const result = await api(
502
+ `/checkpoints/session/${sessionId}/after-sequence/${messageSequence}`,
503
+ { method: "DELETE" }
504
+ );
505
+ return result.deleted;
506
+ },
507
+ async deleteBySession(sessionId) {
508
+ const result = await api(`/checkpoints/session/${sessionId}`, { method: "DELETE" });
509
+ return result.deleted;
510
+ }
511
+ };
383
512
  remoteFileBackupQueries = {
384
513
  create(data) {
385
514
  return api("/file-backups", { method: "POST", body: data });
@@ -434,6 +563,41 @@ var init_remote = __esm({
434
563
  return result.deleted;
435
564
  }
436
565
  };
566
+ remoteIndexedChunkQueries = {
567
+ upsert(_db, data) {
568
+ return api("/indexed-chunks", { method: "POST", body: data });
569
+ },
570
+ batchUpsert(_db, chunks) {
571
+ return api("/indexed-chunks/batch", {
572
+ method: "POST",
573
+ body: { chunks }
574
+ });
575
+ },
576
+ getById(_db, id) {
577
+ return api(`/indexed-chunks/${id}`).catch(() => void 0);
578
+ },
579
+ getByNamespace(_db, namespace) {
580
+ return api(`/indexed-chunks/namespace/${namespace}`);
581
+ },
582
+ getByFilePath(_db, namespace, filePath) {
583
+ return api(`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`);
584
+ },
585
+ async deleteByNamespace(_db, namespace) {
586
+ const result = await api(`/indexed-chunks/namespace/${namespace}`, { method: "DELETE" });
587
+ return result.deleted;
588
+ },
589
+ async deleteByFilePath(_db, namespace, filePath) {
590
+ const result = await api(
591
+ `/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`,
592
+ { method: "DELETE" }
593
+ );
594
+ return result.deleted;
595
+ },
596
+ async countByNamespace(_db, namespace) {
597
+ const result = await api(`/indexed-chunks/namespace/${namespace}/count`);
598
+ return result.count;
599
+ }
600
+ };
437
601
  remoteIndexStatusQueries = {
438
602
  upsert(_db, data) {
439
603
  return api("/index-status", {
@@ -456,6 +620,27 @@ var init_remote = __esm({
456
620
  return api("/index-status");
457
621
  }
458
622
  };
623
+ storageQueries = {
624
+ async getUploadUrl(sessionId, fileName, contentType, category) {
625
+ return storageApi("/upload-url", {
626
+ method: "POST",
627
+ body: { sessionId, fileName, contentType, category }
628
+ });
629
+ },
630
+ async getSessionFiles(sessionId) {
631
+ const result = await storageApi(`/files/${sessionId}`);
632
+ return result.files;
633
+ },
634
+ async getDownloadUrl(fileId) {
635
+ return storageApi(`/download/${fileId}`);
636
+ },
637
+ async deleteFile(fileId) {
638
+ await storageApi(`/files/${fileId}`, { method: "DELETE" });
639
+ },
640
+ async updateFile(fileId, data) {
641
+ await storageApi(`/files/${fileId}`, { method: "PATCH", body: data });
642
+ }
643
+ };
459
644
  }
460
645
  });
461
646
 
@@ -1220,11 +1405,285 @@ var init_semantic_search = __esm({
1220
1405
  }
1221
1406
  });
1222
1407
 
1408
+ // src/browser/stream-proxy.ts
1409
+ var stream_proxy_exports = {};
1410
+ __export(stream_proxy_exports, {
1411
+ BrowserStreamProxy: () => BrowserStreamProxy,
1412
+ destroyProxy: () => destroyProxy,
1413
+ getOrCreateProxy: () => getOrCreateProxy,
1414
+ getProxy: () => getProxy
1415
+ });
1416
+ import WebSocket from "ws";
1417
+ import { EventEmitter } from "events";
1418
+ function getOrCreateProxy(sessionId, port) {
1419
+ const existing = activeProxies.get(sessionId);
1420
+ if (existing) return existing;
1421
+ const proxy = new BrowserStreamProxy(port);
1422
+ activeProxies.set(sessionId, proxy);
1423
+ proxy.on("close", () => {
1424
+ activeProxies.delete(sessionId);
1425
+ });
1426
+ proxy.connect();
1427
+ return proxy;
1428
+ }
1429
+ function getProxy(sessionId) {
1430
+ return activeProxies.get(sessionId);
1431
+ }
1432
+ function destroyProxy(sessionId) {
1433
+ const proxy = activeProxies.get(sessionId);
1434
+ if (proxy) {
1435
+ proxy.destroy();
1436
+ activeProxies.delete(sessionId);
1437
+ }
1438
+ }
1439
+ var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
1440
+ var init_stream_proxy = __esm({
1441
+ "src/browser/stream-proxy.ts"() {
1442
+ "use strict";
1443
+ RECONNECT_DELAY_MS = 1e3;
1444
+ MAX_RECONNECT_ATTEMPTS = 20;
1445
+ FRAME_THROTTLE_MS = 100;
1446
+ BrowserStreamProxy = class extends EventEmitter {
1447
+ ws = null;
1448
+ port;
1449
+ reconnectAttempts = 0;
1450
+ reconnectTimer = null;
1451
+ destroyed = false;
1452
+ lastFrameTime = 0;
1453
+ _latestFrame = null;
1454
+ _connected = false;
1455
+ constructor(port) {
1456
+ super();
1457
+ this.port = port;
1458
+ }
1459
+ get connected() {
1460
+ return this._connected;
1461
+ }
1462
+ get latestFrame() {
1463
+ return this._latestFrame;
1464
+ }
1465
+ connect() {
1466
+ if (this.destroyed) return;
1467
+ this.doConnect();
1468
+ }
1469
+ doConnect() {
1470
+ if (this.destroyed) return;
1471
+ const url = `ws://localhost:${this.port}`;
1472
+ try {
1473
+ this.ws = new WebSocket(url);
1474
+ } catch {
1475
+ this.scheduleReconnect();
1476
+ return;
1477
+ }
1478
+ this.ws.on("open", () => {
1479
+ this.reconnectAttempts = 0;
1480
+ this._connected = true;
1481
+ this.emit("status", {
1482
+ connected: true,
1483
+ screencasting: true
1484
+ });
1485
+ });
1486
+ this.ws.on("message", (raw) => {
1487
+ try {
1488
+ const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
1489
+ this.handleMessage(msg);
1490
+ } catch {
1491
+ }
1492
+ });
1493
+ this.ws.on("close", () => {
1494
+ const wasConnected = this._connected;
1495
+ this._connected = false;
1496
+ if (wasConnected) {
1497
+ this.emit("status", { connected: false, screencasting: false });
1498
+ }
1499
+ if (!this.destroyed) {
1500
+ this.scheduleReconnect();
1501
+ }
1502
+ });
1503
+ this.ws.on("error", () => {
1504
+ });
1505
+ }
1506
+ handleMessage(msg) {
1507
+ if (msg.type === "frame") {
1508
+ const now = Date.now();
1509
+ if (now - this.lastFrameTime < FRAME_THROTTLE_MS) return;
1510
+ this.lastFrameTime = now;
1511
+ const frame = {
1512
+ data: msg.data,
1513
+ metadata: msg.metadata ?? {
1514
+ deviceWidth: 1280,
1515
+ deviceHeight: 720,
1516
+ pageScaleFactor: 1,
1517
+ offsetTop: 0,
1518
+ scrollOffsetX: 0,
1519
+ scrollOffsetY: 0
1520
+ },
1521
+ timestamp: now
1522
+ };
1523
+ this._latestFrame = frame;
1524
+ this.emit("frame", frame);
1525
+ } else if (msg.type === "status") {
1526
+ this.emit("status", {
1527
+ connected: msg.connected ?? true,
1528
+ screencasting: msg.screencasting ?? true,
1529
+ viewportWidth: msg.viewportWidth,
1530
+ viewportHeight: msg.viewportHeight
1531
+ });
1532
+ }
1533
+ }
1534
+ scheduleReconnect() {
1535
+ if (this.destroyed || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
1536
+ this.emit("close");
1537
+ return;
1538
+ }
1539
+ this.reconnectAttempts++;
1540
+ const delay = this.reconnectAttempts <= 5 ? RECONNECT_DELAY_MS : RECONNECT_DELAY_MS * (this.reconnectAttempts - 4);
1541
+ this.reconnectTimer = setTimeout(() => this.doConnect(), delay);
1542
+ }
1543
+ /**
1544
+ * Send an input event to the browser for pair-browsing.
1545
+ */
1546
+ injectInput(event) {
1547
+ if (this.ws?.readyState === WebSocket.OPEN) {
1548
+ this.ws.send(JSON.stringify(event));
1549
+ }
1550
+ }
1551
+ destroy() {
1552
+ this.destroyed = true;
1553
+ if (this.reconnectTimer) {
1554
+ clearTimeout(this.reconnectTimer);
1555
+ this.reconnectTimer = null;
1556
+ }
1557
+ if (this.ws) {
1558
+ this.ws.removeAllListeners();
1559
+ this.ws.close();
1560
+ this.ws = null;
1561
+ }
1562
+ this._connected = false;
1563
+ this.removeAllListeners();
1564
+ }
1565
+ };
1566
+ activeProxies = /* @__PURE__ */ new Map();
1567
+ }
1568
+ });
1569
+
1570
+ // src/browser/recorder.ts
1571
+ var recorder_exports = {};
1572
+ __export(recorder_exports, {
1573
+ FrameRecorder: () => FrameRecorder
1574
+ });
1575
+ import { exec as exec5 } from "child_process";
1576
+ import { promisify as promisify5 } from "util";
1577
+ import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
1578
+ import { join as join6 } from "path";
1579
+ import { tmpdir } from "os";
1580
+ import { nanoid as nanoid3 } from "nanoid";
1581
+ async function checkFfmpeg() {
1582
+ try {
1583
+ await execAsync5("ffmpeg -version", { timeout: 5e3 });
1584
+ return true;
1585
+ } catch {
1586
+ return false;
1587
+ }
1588
+ }
1589
+ async function cleanup(dir) {
1590
+ try {
1591
+ await rm(dir, { recursive: true, force: true });
1592
+ } catch {
1593
+ }
1594
+ }
1595
+ var execAsync5, FrameRecorder;
1596
+ var init_recorder = __esm({
1597
+ "src/browser/recorder.ts"() {
1598
+ "use strict";
1599
+ execAsync5 = promisify5(exec5);
1600
+ FrameRecorder = class {
1601
+ frames = [];
1602
+ startTime = null;
1603
+ recording = false;
1604
+ sessionId;
1605
+ constructor(sessionId) {
1606
+ this.sessionId = sessionId;
1607
+ }
1608
+ get isRecording() {
1609
+ return this.recording;
1610
+ }
1611
+ get frameCount() {
1612
+ return this.frames.length;
1613
+ }
1614
+ start() {
1615
+ this.frames = [];
1616
+ this.startTime = Date.now();
1617
+ this.recording = true;
1618
+ }
1619
+ addFrame(frame) {
1620
+ if (!this.recording) return;
1621
+ this.frames.push({
1622
+ data: Buffer.from(frame.data, "base64"),
1623
+ timestamp: frame.timestamp
1624
+ });
1625
+ }
1626
+ stop() {
1627
+ this.recording = false;
1628
+ }
1629
+ /**
1630
+ * Encode recorded frames into an MP4 using ffmpeg.
1631
+ * Returns the file path to the generated MP4, or null if encoding fails.
1632
+ */
1633
+ async encode() {
1634
+ if (this.frames.length === 0) return null;
1635
+ const workDir = join6(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
1636
+ await mkdir4(workDir, { recursive: true });
1637
+ try {
1638
+ for (let i = 0; i < this.frames.length; i++) {
1639
+ const framePath = join6(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
1640
+ await writeFile4(framePath, this.frames[i].data);
1641
+ }
1642
+ const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
1643
+ const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
1644
+ const clampedFps = Math.max(1, Math.min(fps, 30));
1645
+ const outputPath = join6(workDir, `recording_${this.sessionId}.mp4`);
1646
+ const hasFfmpeg = await checkFfmpeg();
1647
+ if (hasFfmpeg) {
1648
+ await execAsync5(
1649
+ `ffmpeg -y -framerate ${clampedFps} -i "${join6(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
1650
+ { timeout: 12e4 }
1651
+ );
1652
+ } else {
1653
+ console.warn("[RECORDER] ffmpeg not available, cannot encode recording");
1654
+ await cleanup(workDir);
1655
+ return null;
1656
+ }
1657
+ const outputBuf = await readFile10(outputPath);
1658
+ const files = await readdir5(workDir);
1659
+ for (const f of files) {
1660
+ if (f.startsWith("frame_")) {
1661
+ await unlink2(join6(workDir, f)).catch(() => {
1662
+ });
1663
+ }
1664
+ }
1665
+ return { path: outputPath, sizeBytes: outputBuf.length };
1666
+ } catch (error) {
1667
+ console.error("[RECORDER] Failed to encode recording:", error);
1668
+ await cleanup(workDir);
1669
+ return null;
1670
+ }
1671
+ }
1672
+ /** Discard all frames and free memory */
1673
+ clear() {
1674
+ this.frames = [];
1675
+ this.startTime = null;
1676
+ this.recording = false;
1677
+ }
1678
+ };
1679
+ }
1680
+ });
1681
+
1223
1682
  // src/agent/index.ts
1224
1683
  import {
1225
1684
  streamText as streamText2,
1226
1685
  generateText as generateText3,
1227
- tool as tool12,
1686
+ tool as tool13,
1228
1687
  stepCountIs as stepCountIs2
1229
1688
  } from "ai";
1230
1689
 
@@ -1405,8 +1864,8 @@ var SUBAGENT_MODELS = {
1405
1864
  // src/agent/index.ts
1406
1865
  init_db();
1407
1866
  init_config();
1408
- import { z as z13 } from "zod";
1409
- import { nanoid as nanoid3 } from "nanoid";
1867
+ import { z as z14 } from "zod";
1868
+ import { nanoid as nanoid4 } from "nanoid";
1410
1869
 
1411
1870
  // src/tools/bash.ts
1412
1871
  import { tool } from "ai";
@@ -1707,6 +2166,29 @@ function isBlockedCommand(command) {
1707
2166
  (blocked) => normalizedCommand.includes(blocked.toLowerCase())
1708
2167
  );
1709
2168
  }
2169
+ var BROWSER_STREAM_BASE_PORT = 9223;
2170
+ var sessionBrowserPorts = /* @__PURE__ */ new Map();
2171
+ var nextPortOffset = 0;
2172
+ function getBrowserStreamPort(sessionId) {
2173
+ let port = sessionBrowserPorts.get(sessionId);
2174
+ if (!port) {
2175
+ port = BROWSER_STREAM_BASE_PORT + nextPortOffset++;
2176
+ sessionBrowserPorts.set(sessionId, port);
2177
+ }
2178
+ return port;
2179
+ }
2180
+ function hasAgentBrowserCommand(command) {
2181
+ return /\bagent-browser\b/.test(command);
2182
+ }
2183
+ function isAgentBrowserCloseCommand(command) {
2184
+ return /\bagent-browser\s+(close|close\s+--all)\b/.test(command);
2185
+ }
2186
+ function injectBrowserStreamPort(command, port) {
2187
+ return command.replace(
2188
+ /\bagent-browser\b/g,
2189
+ `AGENT_BROWSER_STREAM_PORT=${port} agent-browser`
2190
+ );
2191
+ }
1710
2192
  var bashInputSchema = z2.object({
1711
2193
  command: z2.string().optional().describe("The command to execute. Required for running new commands."),
1712
2194
  background: z2.boolean().default(false).describe("Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID."),
@@ -1872,6 +2354,16 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
1872
2354
  exitCode: 1
1873
2355
  };
1874
2356
  }
2357
+ let actualCommand = command;
2358
+ const hasAgentBrowser = hasAgentBrowserCommand(command);
2359
+ const browserClose = isAgentBrowserCloseCommand(command);
2360
+ let browserPort;
2361
+ if (hasAgentBrowser) {
2362
+ browserPort = getBrowserStreamPort(options.sessionId);
2363
+ if (!browserClose) {
2364
+ actualCommand = injectBrowserStreamPort(command, browserPort);
2365
+ }
2366
+ }
1875
2367
  const canUseTmux = await shouldUseTmux();
1876
2368
  if (background) {
1877
2369
  if (!canUseTmux) {
@@ -1881,8 +2373,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
1881
2373
  };
1882
2374
  }
1883
2375
  const terminalId = generateTerminalId();
1884
- options.onProgress?.({ terminalId, status: "started", command });
1885
- const result = await runBackground(command, options.workingDirectory, {
2376
+ options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
2377
+ const result = await runBackground(actualCommand, options.workingDirectory, {
1886
2378
  sessionId: options.sessionId,
1887
2379
  terminalId
1888
2380
  });
@@ -1895,16 +2387,22 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
1895
2387
  }
1896
2388
  if (canUseTmux) {
1897
2389
  const terminalId = generateTerminalId();
1898
- options.onProgress?.({ terminalId, status: "started", command });
2390
+ options.onProgress?.({ terminalId, status: "started", command, browserStreamPort: browserPort });
1899
2391
  try {
1900
- const result = await runSync(command, options.workingDirectory, {
2392
+ const result = await runSync(actualCommand, options.workingDirectory, {
1901
2393
  sessionId: options.sessionId,
1902
2394
  timeout: COMMAND_TIMEOUT,
1903
2395
  terminalId
1904
2396
  });
1905
2397
  const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS2);
1906
2398
  options.onOutput?.(truncatedOutput);
1907
- options.onProgress?.({ terminalId, status: "completed", command });
2399
+ options.onProgress?.({
2400
+ terminalId,
2401
+ status: "completed",
2402
+ command,
2403
+ browserStreamPort: browserPort,
2404
+ browserClosed: browserClose || void 0
2405
+ });
1908
2406
  return {
1909
2407
  success: result.exitCode === 0,
1910
2408
  id: result.id,
@@ -1922,7 +2420,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
1922
2420
  };
1923
2421
  }
1924
2422
  } else {
1925
- const result = await execFallback(command, options.workingDirectory, options.onOutput);
2423
+ const result = await execFallback(actualCommand, options.workingDirectory, options.onOutput);
1926
2424
  return {
1927
2425
  success: result.success,
1928
2426
  output: result.output,
@@ -2161,12 +2659,12 @@ function findNearestRoot(startDir, markers) {
2161
2659
  }
2162
2660
  async function commandExists(cmd) {
2163
2661
  try {
2164
- const { exec: exec5 } = await import("child_process");
2165
- const { promisify: promisify5 } = await import("util");
2166
- const execAsync5 = promisify5(exec5);
2662
+ const { exec: exec6 } = await import("child_process");
2663
+ const { promisify: promisify6 } = await import("util");
2664
+ const execAsync6 = promisify6(exec6);
2167
2665
  const isWindows = process.platform === "win32";
2168
2666
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
2169
- await execAsync5(checkCmd);
2667
+ await execAsync6(checkCmd);
2170
2668
  return true;
2171
2669
  } catch {
2172
2670
  return false;
@@ -2464,7 +2962,7 @@ async function createClient(serverId, handle, root) {
2464
2962
  const startTime = Date.now();
2465
2963
  let debounceTimer;
2466
2964
  let resolved = false;
2467
- const cleanup = () => {
2965
+ const cleanup2 = () => {
2468
2966
  if (debounceTimer) clearTimeout(debounceTimer);
2469
2967
  const listeners = diagnosticListeners.get(normalized);
2470
2968
  if (listeners) {
@@ -2478,7 +2976,7 @@ async function createClient(serverId, handle, root) {
2478
2976
  const finish = () => {
2479
2977
  if (resolved) return;
2480
2978
  resolved = true;
2481
- cleanup();
2979
+ cleanup2();
2482
2980
  resolve10(diagnostics.get(normalized) || []);
2483
2981
  };
2484
2982
  const onDiagnostic = () => {
@@ -4583,8 +5081,106 @@ function createTaskFailedTool(options) {
4583
5081
  });
4584
5082
  }
4585
5083
 
5084
+ // src/tools/upload-file.ts
5085
+ import { tool as tool12 } from "ai";
5086
+ import { z as z13 } from "zod";
5087
+ import { readFile as readFile9, stat as stat4 } from "fs/promises";
5088
+ import { join as join5, basename as basename4, extname as extname7 } from "path";
5089
+ var MIME_TYPES = {
5090
+ ".txt": "text/plain",
5091
+ ".md": "text/markdown",
5092
+ ".html": "text/html",
5093
+ ".css": "text/css",
5094
+ ".js": "application/javascript",
5095
+ ".ts": "application/typescript",
5096
+ ".json": "application/json",
5097
+ ".csv": "text/csv",
5098
+ ".xml": "application/xml",
5099
+ ".pdf": "application/pdf",
5100
+ ".png": "image/png",
5101
+ ".jpg": "image/jpeg",
5102
+ ".jpeg": "image/jpeg",
5103
+ ".gif": "image/gif",
5104
+ ".webp": "image/webp",
5105
+ ".svg": "image/svg+xml",
5106
+ ".mp4": "video/mp4",
5107
+ ".webm": "video/webm",
5108
+ ".mp3": "audio/mpeg",
5109
+ ".wav": "audio/wav",
5110
+ ".zip": "application/zip",
5111
+ ".tar": "application/x-tar",
5112
+ ".gz": "application/gzip"
5113
+ };
5114
+ function createUploadFileTool(options) {
5115
+ return tool12({
5116
+ 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.`,
5117
+ inputSchema: z13.object({
5118
+ path: z13.string().describe("Path to the file to upload (relative to working directory or absolute)"),
5119
+ name: z13.string().optional().describe("Display name for the file (defaults to the filename)")
5120
+ }),
5121
+ execute: async (input) => {
5122
+ try {
5123
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
5124
+ if (!isRemoteConfigured2()) {
5125
+ return {
5126
+ success: false,
5127
+ error: "File upload is not available \u2014 remote server with GCS is not configured."
5128
+ };
5129
+ }
5130
+ const fullPath = input.path.startsWith("/") ? input.path : join5(options.workingDirectory, input.path);
5131
+ try {
5132
+ await stat4(fullPath);
5133
+ } catch {
5134
+ return {
5135
+ success: false,
5136
+ error: `File not found: ${input.path}`
5137
+ };
5138
+ }
5139
+ const fileName = input.name || basename4(fullPath);
5140
+ const ext = extname7(fullPath).toLowerCase();
5141
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
5142
+ const uploadInfo = await storageQueries2.getUploadUrl(
5143
+ options.sessionId,
5144
+ fileName,
5145
+ contentType,
5146
+ "general"
5147
+ );
5148
+ const fileData = await readFile9(fullPath);
5149
+ const putRes = await fetch(uploadInfo.uploadUrl, {
5150
+ method: "PUT",
5151
+ headers: { "Content-Type": contentType },
5152
+ body: fileData
5153
+ });
5154
+ if (!putRes.ok) {
5155
+ return {
5156
+ success: false,
5157
+ error: `Upload failed: ${putRes.status} ${putRes.statusText}`
5158
+ };
5159
+ }
5160
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
5161
+ const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
5162
+ return {
5163
+ success: true,
5164
+ fileId: uploadInfo.fileId,
5165
+ fileName,
5166
+ sizeBytes: fileData.length,
5167
+ contentType,
5168
+ downloadUrl: downloadInfo.downloadUrl,
5169
+ expiresAt: downloadInfo.expiresAt
5170
+ };
5171
+ } catch (err) {
5172
+ return {
5173
+ success: false,
5174
+ error: `Upload failed: ${err.message}`
5175
+ };
5176
+ }
5177
+ }
5178
+ });
5179
+ }
5180
+
4586
5181
  // src/tools/index.ts
4587
5182
  init_semantic();
5183
+ init_remote();
4588
5184
  init_semantic_search();
4589
5185
  async function createTools(options) {
4590
5186
  const tools = {
@@ -4622,6 +5218,12 @@ async function createTools(options) {
4622
5218
  workingDirectory: options.workingDirectory
4623
5219
  })
4624
5220
  };
5221
+ if (isRemoteConfigured()) {
5222
+ tools.upload_file = createUploadFileTool({
5223
+ workingDirectory: options.workingDirectory,
5224
+ sessionId: options.sessionId
5225
+ });
5226
+ }
4625
5227
  if (options.enableSemanticSearch !== false) {
4626
5228
  try {
4627
5229
  if (isVectorGatewayConfigured()) {
@@ -4716,6 +5318,7 @@ You have access to powerful tools for:
4716
5318
  - **load_skill**: Load specialized knowledge documents for specific tasks
4717
5319
  - **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
4718
5320
  - **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
5321
+ - **upload_file**: Upload a file to cloud storage and get a shareable download URL (available when remote storage is configured)
4719
5322
 
4720
5323
 
4721
5324
  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.
@@ -4974,14 +5577,53 @@ function buildTaskPromptAddendum(outputSchema) {
4974
5577
  ## Task Mode
4975
5578
 
4976
5579
  You are running in **task mode**. You have been given a specific task to complete autonomously.
5580
+ 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.
5581
+ 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.
4977
5582
 
4978
5583
  ### Rules
4979
5584
  1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
4980
- 2. Keep working until the task is fully complete.
5585
+ 2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
4981
5586
  3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
4982
5587
  4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
4983
5588
  5. Do NOT stop without calling one of these two tools.
4984
5589
 
5590
+ ### Verification \u2014 BE EXTREMELY THOROUGH
5591
+ Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
5592
+
5593
+ **After making code changes:**
5594
+ - Run the **linter** on every file you touched to catch type errors and lint issues. Fix any you introduced.
5595
+ - **Read back the files you edited** to confirm the changes are correct and complete \u2014 don't rely on memory.
5596
+ - If there are **tests**, run them (\`npm test\`, \`pytest\`, etc.) and ensure they pass.
5597
+ - If you created new files, verify they exist and contain what you expect.
5598
+
5599
+ **For UI / web changes:**
5600
+ - Start the dev server if it isn't already running (it might be so double check ur context)
5601
+ - **Open the browser** to verify the changes visually: using your agent-browser tool read the skill
5602
+ - Check the dev server logs for errors or warnings.
5603
+ - If the app crashes or shows errors, fix them before completing.
5604
+
5605
+ **For backend / API changes:**
5606
+ - Test the endpoint with curl or a quick script to confirm it works as expected.
5607
+ - Check server logs for errors.
5608
+
5609
+ **For search and exploration tasks:**
5610
+ - Actually search in the RIGHT directories \u2014 don't just search the root if the relevant code is in \`src/\`, \`app/\`, \`lib/\`, etc.
5611
+ - Use \`explore_agent\` for semantic/conceptual questions and \`grep\`/\`code_graph\` for exact lookups.
5612
+ - Cross-reference findings \u2014 if you find something in one place, verify related files are consistent.
5613
+ - Don't stop at the first match \u2014 make sure you've found ALL relevant occurrences.
5614
+
5615
+ **General verification checklist:**
5616
+ - Re-read the original task prompt and confirm every requirement has been addressed.
5617
+ - If the task asked for multiple things, verify EACH one individually.
5618
+ - If something doesn't look right, fix it \u2014 don't complete with known issues.
5619
+
5620
+ ### Use All Available Tools
5621
+ - **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
5622
+ - **explore_agent**: Use for codebase exploration and understanding before making changes.
5623
+ - **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
5624
+ - **todo**: Track your progress on multi-step tasks so you don't miss steps.
5625
+ - **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
5626
+
4985
5627
  ### Output Schema
4986
5628
  The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
4987
5629
  \`\`\`json
@@ -4989,7 +5631,7 @@ ${JSON.stringify(outputSchema, null, 2)}
4989
5631
  \`\`\`
4990
5632
 
4991
5633
  ### Completion Tools
4992
- - **\`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.
5634
+ - **\`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.
4993
5635
  - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
4994
5636
  `;
4995
5637
  }
@@ -5515,11 +6157,29 @@ ${prompt}` });
5515
6157
  const onComplete = (signal) => {
5516
6158
  completion.signal = signal;
5517
6159
  };
6160
+ let taskRecorder = null;
6161
+ const sessionId = this.session.id;
6162
+ const bashProgressHandler = (progress) => {
6163
+ options.onToolProgress?.({ toolName: "bash", data: progress });
6164
+ const port = progress.browserStreamPort;
6165
+ if (port && progress.status === "started") {
6166
+ Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
6167
+ const proxy = getOrCreateProxy2(sessionId, port);
6168
+ if (!taskRecorder) {
6169
+ Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
6170
+ taskRecorder = new FrameRecorder2(sessionId);
6171
+ taskRecorder.start();
6172
+ proxy.on("frame", (frame) => taskRecorder?.addFrame(frame));
6173
+ });
6174
+ }
6175
+ });
6176
+ }
6177
+ };
5518
6178
  const taskTools = await createTools({
5519
6179
  sessionId: this.session.id,
5520
6180
  workingDirectory: this.session.workingDirectory,
5521
6181
  skillsDirectories: config.resolvedSkillsDirectories,
5522
- onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
6182
+ onBashProgress: bashProgressHandler,
5523
6183
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
5524
6184
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
5525
6185
  taskTools: {
@@ -5590,12 +6250,24 @@ ${taskAddendum}`;
5590
6250
  if (completion.signal) {
5591
6251
  const sig = completion.signal;
5592
6252
  const finalStatus = sig.status;
6253
+ let fileUrls;
6254
+ if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
6255
+ const resultObj = sig.result;
6256
+ const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
6257
+ if (filePaths.length > 0) {
6258
+ fileUrls = await this.uploadTaskFiles(filePaths);
6259
+ }
6260
+ }
6261
+ const recordingUrls = await this.finishTaskRecording(taskRecorder);
6262
+ const allFileUrls = [...fileUrls || [], ...recordingUrls];
5593
6263
  const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
5594
6264
  fireWebhook(eventType, {
5595
6265
  status: finalStatus,
5596
6266
  result: sig.result,
5597
6267
  error: sig.error,
5598
- iterations: iteration
6268
+ iterations: iteration,
6269
+ fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
6270
+ browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
5599
6271
  });
5600
6272
  const updatedTask2 = {
5601
6273
  ...options.taskConfig,
@@ -5615,11 +6287,17 @@ ${taskAddendum}`;
5615
6287
  };
5616
6288
  }
5617
6289
  await this.context.addUserMessage(
5618
- "Continue working on the task. When done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
6290
+ "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."
5619
6291
  );
5620
6292
  }
5621
6293
  const timeoutError = `Task did not complete within ${maxIterations} iterations`;
5622
- fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
6294
+ const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
6295
+ fireWebhook("task.failed", {
6296
+ status: "failed",
6297
+ error: timeoutError,
6298
+ iterations: iteration,
6299
+ browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
6300
+ });
5623
6301
  const updatedTask = {
5624
6302
  ...options.taskConfig,
5625
6303
  status: "failed",
@@ -5631,6 +6309,113 @@ ${taskAddendum}`;
5631
6309
  });
5632
6310
  return { status: "failed", error: timeoutError, iterations: iteration };
5633
6311
  }
6312
+ /**
6313
+ * Stop a task-mode browser recording, encode to MP4, upload to GCS.
6314
+ * Returns download URLs for any recordings produced.
6315
+ */
6316
+ async finishTaskRecording(recorder) {
6317
+ try {
6318
+ const { destroyProxy: destroyProxy2 } = await Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports));
6319
+ destroyProxy2(this.session.id);
6320
+ } catch {
6321
+ }
6322
+ if (!recorder || recorder.frameCount === 0) {
6323
+ recorder?.clear();
6324
+ return [];
6325
+ }
6326
+ recorder.stop();
6327
+ try {
6328
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
6329
+ if (!isRemoteConfigured2()) {
6330
+ recorder.clear();
6331
+ return [];
6332
+ }
6333
+ const result = await recorder.encode();
6334
+ recorder.clear();
6335
+ if (!result) return [];
6336
+ const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
6337
+ const uploadInfo = await storageQueries2.getUploadUrl(
6338
+ this.session.id,
6339
+ `browser-recording-${Date.now()}.mp4`,
6340
+ "video/mp4",
6341
+ "browser-recording"
6342
+ );
6343
+ const fileData = await readFile11(result.path);
6344
+ await fetch(uploadInfo.uploadUrl, {
6345
+ method: "PUT",
6346
+ headers: { "Content-Type": "video/mp4" },
6347
+ body: fileData
6348
+ });
6349
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: result.sizeBytes });
6350
+ const dlInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
6351
+ await unlink3(result.path).catch(() => {
6352
+ });
6353
+ console.log(`[TASK] Browser recording uploaded (${result.sizeBytes} bytes)`);
6354
+ return [dlInfo.downloadUrl];
6355
+ } catch (err) {
6356
+ console.error("[TASK] Failed to upload browser recording:", err.message);
6357
+ recorder.clear();
6358
+ return [];
6359
+ }
6360
+ }
6361
+ /**
6362
+ * Upload task output files to GCS via the remote server.
6363
+ * Returns an array of download URLs for successfully uploaded files.
6364
+ */
6365
+ async uploadTaskFiles(filePaths) {
6366
+ try {
6367
+ const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
6368
+ if (!isRemoteConfigured2()) return [];
6369
+ const { readFile: readFile11 } = await import("fs/promises");
6370
+ const { join: join7, basename: basename5 } = await import("path");
6371
+ const urls = [];
6372
+ for (const filePath of filePaths) {
6373
+ try {
6374
+ const fullPath = filePath.startsWith("/") ? filePath : join7(this.session.workingDirectory, filePath);
6375
+ const fileName = basename5(fullPath);
6376
+ const ext = fileName.split(".").pop()?.toLowerCase() || "";
6377
+ const mimeMap = {
6378
+ pdf: "application/pdf",
6379
+ json: "application/json",
6380
+ csv: "text/csv",
6381
+ txt: "text/plain",
6382
+ md: "text/markdown",
6383
+ html: "text/html",
6384
+ png: "image/png",
6385
+ jpg: "image/jpeg",
6386
+ jpeg: "image/jpeg",
6387
+ gif: "image/gif",
6388
+ svg: "image/svg+xml",
6389
+ mp4: "video/mp4",
6390
+ zip: "application/zip"
6391
+ };
6392
+ const contentType = mimeMap[ext] || "application/octet-stream";
6393
+ const uploadInfo = await storageQueries2.getUploadUrl(
6394
+ this.session.id,
6395
+ fileName,
6396
+ contentType,
6397
+ "task-output"
6398
+ );
6399
+ const fileData = await readFile11(fullPath);
6400
+ await fetch(uploadInfo.uploadUrl, {
6401
+ method: "PUT",
6402
+ headers: { "Content-Type": contentType },
6403
+ body: fileData
6404
+ });
6405
+ await storageQueries2.updateFile(uploadInfo.fileId, { sizeBytes: fileData.length });
6406
+ const downloadInfo = await storageQueries2.getDownloadUrl(uploadInfo.fileId);
6407
+ urls.push(downloadInfo.downloadUrl);
6408
+ console.log(`[TASK] Uploaded file: ${fileName} (${fileData.length} bytes)`);
6409
+ } catch (err) {
6410
+ console.error(`[TASK] Failed to upload file ${filePath}:`, err.message);
6411
+ }
6412
+ }
6413
+ return urls;
6414
+ } catch (err) {
6415
+ console.error("[TASK] File upload failed:", err.message);
6416
+ return [];
6417
+ }
6418
+ }
5634
6419
  /**
5635
6420
  * Wrap tools to add approval checking
5636
6421
  */
@@ -5644,11 +6429,11 @@ ${taskAddendum}`;
5644
6429
  wrappedTools[name] = originalTool;
5645
6430
  continue;
5646
6431
  }
5647
- wrappedTools[name] = tool12({
6432
+ wrappedTools[name] = tool13({
5648
6433
  description: originalTool.description || "",
5649
- inputSchema: originalTool.inputSchema || z13.object({}),
6434
+ inputSchema: originalTool.inputSchema || z14.object({}),
5650
6435
  execute: async (input, toolOptions) => {
5651
- const toolCallId = toolOptions.toolCallId || nanoid3();
6436
+ const toolCallId = toolOptions.toolCallId || nanoid4();
5652
6437
  const execution = toolExecutionQueries.create({
5653
6438
  sessionId: this.session.id,
5654
6439
  toolName: name,
@@ -5666,10 +6451,10 @@ ${taskAddendum}`;
5666
6451
  const resolverData = approvalResolvers.get(toolCallId);
5667
6452
  approvalResolvers.delete(toolCallId);
5668
6453
  this.pendingApprovals.delete(toolCallId);
5669
- const exec5 = await execution;
6454
+ const exec6 = await execution;
5670
6455
  if (!approved) {
5671
6456
  const reason = resolverData?.reason || "User rejected the tool execution";
5672
- await toolExecutionQueries.reject(exec5.id);
6457
+ await toolExecutionQueries.reject(exec6.id);
5673
6458
  await sessionQueries.updateStatus(this.session.id, "active");
5674
6459
  return {
5675
6460
  status: "rejected",
@@ -5679,14 +6464,14 @@ ${taskAddendum}`;
5679
6464
  message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
5680
6465
  };
5681
6466
  }
5682
- await toolExecutionQueries.approve(exec5.id);
6467
+ await toolExecutionQueries.approve(exec6.id);
5683
6468
  await sessionQueries.updateStatus(this.session.id, "active");
5684
6469
  try {
5685
6470
  const result = await originalTool.execute(input, toolOptions);
5686
- await toolExecutionQueries.complete(exec5.id, result);
6471
+ await toolExecutionQueries.complete(exec6.id, result);
5687
6472
  return result;
5688
6473
  } catch (error) {
5689
- await toolExecutionQueries.complete(exec5.id, null, error.message);
6474
+ await toolExecutionQueries.complete(exec6.id, null, error.message);
5690
6475
  throw error;
5691
6476
  }
5692
6477
  }