sparkecoder 0.1.59 → 0.1.61

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 (179) hide show
  1. package/dist/agent/index.d.ts +4 -3
  2. package/dist/agent/index.js +662 -203
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +906 -129
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +3 -2
  7. package/dist/db/index.js.map +1 -1
  8. package/dist/{index-Csad1Nx4.d.ts → index-DuGtMaAJ.d.ts} +85 -4
  9. package/dist/index.d.ts +6 -5
  10. package/dist/index.js +1302 -711
  11. package/dist/index.js.map +1 -1
  12. package/dist/schema-C7Mm4Ykn.d.ts +1410 -0
  13. package/dist/{search-BETuS1vh.d.ts → search-C_IFImt1.d.ts} +3 -3
  14. package/dist/server/index.js +692 -101
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/skills/default/browser.md +143 -0
  17. package/dist/skills/default/code-review.md +122 -0
  18. package/dist/skills/default/debugging.md +105 -0
  19. package/dist/skills/default/refactoring.md +197 -0
  20. package/dist/tools/index.d.ts +54 -4
  21. package/dist/tools/index.js +304 -29
  22. package/dist/tools/index.js.map +1 -1
  23. package/package.json +6 -1
  24. package/src/skills/default/browser.md +143 -0
  25. package/src/skills/default/code-review.md +122 -0
  26. package/src/skills/default/debugging.md +105 -0
  27. package/src/skills/default/refactoring.md +197 -0
  28. package/web/.next/BUILD_ID +1 -1
  29. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  30. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  31. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  32. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  33. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  34. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  35. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  37. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  44. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  45. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  46. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  49. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  53. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  56. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  72. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  79. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  86. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  88. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  91. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  94. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  95. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  96. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  97. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  98. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  99. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  100. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  101. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  102. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_19289e11._.js → 2374f_03d22c16._.js} +1 -1
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b4b86c1f._.js → 2374f_0d0b1fb9._.js} +1 -1
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4858a1ea._.js → 2374f_3f7d6d28._.js} +1 -1
  106. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_51385fed._.js → 2374f_6166d14d._.js} +1 -1
  107. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_40e35a02._.js → 2374f_67b9525f._.js} +1 -1
  108. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fb95e3c9._.js → 2374f_6b436efc._.js} +1 -1
  109. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2f0d9f6f._.js → 2374f_784d851c._.js} +1 -1
  110. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_35475cbe._.js → 2374f_976b2ded._.js} +1 -1
  111. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_7db22cde._.js → 2374f_98eba491._.js} +1 -1
  112. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_90b8e4fb._.js → 2374f_99b7b533._.js} +1 -1
  113. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4666c827._.js → 2374f_a1a36483._.js} +1 -1
  114. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_076f03ec._.js → 2374f_b98835eb._.js} +1 -1
  115. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b17fce11._.js → 2374f_e19eaecc._.js} +1 -1
  116. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d8b9ce38._.js → 2374f_e3aec189._.js} +1 -1
  117. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__7775f784._.js → [root-of-the-server]__4f176a16._.js} +2 -2
  118. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d59f831d._.js +15 -0
  119. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_645f4b90._.js → web_693f514e._.js} +2 -2
  120. package/web/.next/standalone/web/.next/server/chunks/ssr/web_a42a2651._.js +7 -0
  121. package/web/.next/standalone/web/.next/server/chunks/ssr/web_dc6ce793._.js +7 -0
  122. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
  123. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  124. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  125. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  126. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  127. package/web/.next/standalone/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
  128. package/web/.next/standalone/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
  129. package/web/.next/standalone/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  130. package/web/.next/standalone/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
  131. package/web/.next/standalone/web/.next/static/chunks/95436454a7559b0d.js +7 -0
  132. package/web/.next/standalone/web/.next/static/chunks/a751ca474cc46212.js +5 -0
  133. package/web/.next/standalone/web/.next/static/static/chunks/0358a0e7a40cfb93.css +1 -0
  134. package/web/.next/standalone/web/.next/static/static/chunks/1ecde4c0d426a635.js +1 -0
  135. package/web/.next/standalone/web/.next/static/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  136. package/web/.next/standalone/web/.next/static/static/chunks/36688a049d72e8ab.js +5 -0
  137. package/web/.next/standalone/web/.next/static/static/chunks/95436454a7559b0d.js +7 -0
  138. package/web/.next/standalone/web/.next/static/static/chunks/a751ca474cc46212.js +5 -0
  139. package/web/.next/standalone/web/src/components/ai-elements/complete-task-tool.tsx +126 -0
  140. package/web/.next/standalone/web/src/components/chat-interface.tsx +68 -3
  141. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +18 -1
  142. package/web/.next/standalone/web/src/lib/api.ts +12 -0
  143. package/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
  144. package/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
  145. package/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  146. package/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
  147. package/web/.next/static/chunks/95436454a7559b0d.js +7 -0
  148. package/web/.next/static/chunks/a751ca474cc46212.js +5 -0
  149. package/dist/schema-NcQknWCg.d.ts +0 -295
  150. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__bd396152._.js +0 -15
  151. package/web/.next/standalone/web/.next/server/chunks/ssr/web_9c9f0e3b._.js +0 -7
  152. package/web/.next/standalone/web/.next/server/chunks/ssr/web_d08270f7._.js +0 -7
  153. package/web/.next/standalone/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
  154. package/web/.next/standalone/web/.next/static/chunks/3f295b6960943c38.js +0 -1
  155. package/web/.next/standalone/web/.next/static/chunks/631b023d37a08635.js +0 -13
  156. package/web/.next/standalone/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
  157. package/web/.next/standalone/web/.next/static/chunks/e97212fcc8221479.js +0 -5
  158. package/web/.next/standalone/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
  159. package/web/.next/standalone/web/.next/static/static/chunks/2868b007ce5163fc.css +0 -1
  160. package/web/.next/standalone/web/.next/static/static/chunks/3f295b6960943c38.js +0 -1
  161. package/web/.next/standalone/web/.next/static/static/chunks/631b023d37a08635.js +0 -13
  162. package/web/.next/standalone/web/.next/static/static/chunks/a2b4737b190d1b54.js +0 -5
  163. package/web/.next/standalone/web/.next/static/static/chunks/e97212fcc8221479.js +0 -5
  164. package/web/.next/standalone/web/.next/static/static/chunks/f6e47c8a9766ce91.js +0 -7
  165. package/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
  166. package/web/.next/static/chunks/3f295b6960943c38.js +0 -1
  167. package/web/.next/static/chunks/631b023d37a08635.js +0 -13
  168. package/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
  169. package/web/.next/static/chunks/e97212fcc8221479.js +0 -5
  170. package/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
  171. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  172. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  173. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
  174. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  175. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  176. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
  177. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  178. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  179. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -404,7 +404,7 @@ var init_db = __esm({
404
404
 
405
405
  // src/config/types.ts
406
406
  import { z } from "zod";
407
- var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
407
+ var ToolApprovalConfigSchema, SkillMetadataSchema, TaskConfigSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
408
408
  var init_types = __esm({
409
409
  "src/config/types.ts"() {
410
410
  "use strict";
@@ -423,11 +423,22 @@ var init_types = __esm({
423
423
  // Glob patterns - auto-inject when working with matching files
424
424
  globs: z.array(z.string()).optional().default([])
425
425
  });
426
+ TaskConfigSchema = z.object({
427
+ enabled: z.boolean(),
428
+ outputSchema: z.record(z.string(), z.unknown()),
429
+ webhookUrl: z.string().url().optional(),
430
+ maxIterations: z.number().optional(),
431
+ status: z.enum(["running", "completed", "failed"]),
432
+ result: z.unknown().optional(),
433
+ error: z.string().optional(),
434
+ iterations: z.number().optional()
435
+ });
426
436
  SessionConfigSchema = z.object({
427
437
  toolApprovals: z.record(z.string(), z.boolean()).optional(),
428
438
  approvalWebhook: z.string().url().optional(),
429
439
  skillsDirectory: z.string().optional(),
430
- maxContextChars: z.number().optional().default(2e5)
440
+ maxContextChars: z.number().optional().default(2e5),
441
+ task: TaskConfigSchema.optional()
431
442
  });
432
443
  VectorGatewayConfigSchema = z.object({
433
444
  // Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
@@ -558,8 +569,15 @@ function discoverSkillDirectories(workingDir) {
558
569
  if (existsSync(agentsMd)) {
559
570
  agentsMdPath = agentsMd;
560
571
  }
561
- const builtInSkillsDir = resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default");
562
- if (existsSync(builtInSkillsDir)) {
572
+ const baseDir = dirname(import.meta.url.replace("file://", ""));
573
+ const builtInCandidates = [
574
+ resolve(baseDir, "../skills/default"),
575
+ // dev: src/config → src/skills/default
576
+ resolve(baseDir, "./skills/default")
577
+ // prod: dist/ → dist/skills/default
578
+ ];
579
+ const builtInSkillsDir = builtInCandidates.find((p) => existsSync(p));
580
+ if (builtInSkillsDir) {
563
581
  onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
564
582
  allDirectories.push(builtInSkillsDir);
565
583
  }
@@ -728,6 +746,9 @@ function getConfig() {
728
746
  }
729
747
  function requiresApproval(toolName, sessionConfig) {
730
748
  const config = getConfig();
749
+ if (sessionConfig?.toolApprovals?.["*"] !== void 0) {
750
+ return sessionConfig.toolApprovals["*"];
751
+ }
731
752
  if (sessionConfig?.toolApprovals?.[toolName] !== void 0) {
732
753
  return sessionConfig.toolApprovals[toolName];
733
754
  }
@@ -938,7 +959,7 @@ __export(skills_exports, {
938
959
  loadSkillsFromDirectory: () => loadSkillsFromDirectory
939
960
  });
940
961
  import { readFile as readFile6, readdir } from "fs/promises";
941
- import { resolve as resolve6, basename, extname as extname3, relative as relative4 } from "path";
962
+ import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
942
963
  import { existsSync as existsSync8 } from "fs";
943
964
  import { minimatch } from "minimatch";
944
965
  function parseSkillFrontmatter(content) {
@@ -1009,7 +1030,7 @@ function parseSkillFrontmatter(content) {
1009
1030
  }
1010
1031
  }
1011
1032
  function getSkillNameFromPath(filePath) {
1012
- return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1033
+ return basename(filePath, extname4(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1013
1034
  }
1014
1035
  async function loadSkillsFromDirectory(directory, options = {}) {
1015
1036
  const {
@@ -1334,9 +1355,9 @@ var init_hasher = __esm({
1334
1355
  });
1335
1356
 
1336
1357
  // src/semantic/chunker.ts
1337
- import { extname as extname5, basename as basename2 } from "path";
1358
+ import { extname as extname6, basename as basename2 } from "path";
1338
1359
  function detectLanguage(filePath) {
1339
- const ext = extname5(filePath).toLowerCase();
1360
+ const ext = extname6(filePath).toLowerCase();
1340
1361
  return LANGUAGE_MAP[ext] || "unknown";
1341
1362
  }
1342
1363
  function supportsSemanticChunking(language) {
@@ -2411,7 +2432,7 @@ import { createInterface } from "readline";
2411
2432
 
2412
2433
  // src/server/index.ts
2413
2434
  import "dotenv/config";
2414
- import { Hono as Hono5 } from "hono";
2435
+ import { Hono as Hono6 } from "hono";
2415
2436
  import { serve } from "@hono/node-server";
2416
2437
  import { cors } from "hono/cors";
2417
2438
  import { logger } from "hono/logger";
@@ -2425,28 +2446,186 @@ import { fileURLToPath as fileURLToPath4 } from "url";
2425
2446
  init_db();
2426
2447
  import { Hono } from "hono";
2427
2448
  import { zValidator } from "@hono/zod-validator";
2428
- import { z as z13 } from "zod";
2449
+ import { z as z14 } from "zod";
2429
2450
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
2430
2451
  import { readdir as readdir5 } from "fs/promises";
2431
- import { join as join5, basename as basename4, extname as extname6, relative as relative9 } from "path";
2452
+ import { join as join5, basename as basename4, extname as extname7, relative as relative9 } from "path";
2432
2453
  import { nanoid as nanoid4 } from "nanoid";
2433
2454
 
2434
2455
  // src/agent/index.ts
2435
2456
  import {
2436
2457
  streamText as streamText2,
2437
2458
  generateText as generateText3,
2438
- tool as tool11,
2459
+ tool as tool12,
2439
2460
  stepCountIs as stepCountIs2
2440
2461
  } from "ai";
2441
2462
 
2442
2463
  // src/agent/model.ts
2443
2464
  import { gateway } from "@ai-sdk/gateway";
2465
+
2466
+ // src/agent/remote-model.ts
2467
+ function serializePrompt(prompt) {
2468
+ return prompt.map((msg) => {
2469
+ if (!Array.isArray(msg.content)) return msg;
2470
+ return {
2471
+ ...msg,
2472
+ content: msg.content.map((part) => {
2473
+ if (part.type === "file" && part.data instanceof Uint8Array) {
2474
+ return {
2475
+ ...part,
2476
+ data: Buffer.from(part.data).toString("base64"),
2477
+ _base64: true
2478
+ };
2479
+ }
2480
+ return part;
2481
+ })
2482
+ };
2483
+ });
2484
+ }
2485
+ function deserializeValue(value) {
2486
+ if (value && typeof value === "object") {
2487
+ if (value.__uint8array && typeof value.data === "string") {
2488
+ return Buffer.from(value.data, "base64");
2489
+ }
2490
+ if (Array.isArray(value)) {
2491
+ return value.map(deserializeValue);
2492
+ }
2493
+ const result = {};
2494
+ for (const [k, v] of Object.entries(value)) {
2495
+ result[k] = deserializeValue(v);
2496
+ }
2497
+ return result;
2498
+ }
2499
+ return value;
2500
+ }
2501
+ function prepareOptions(options) {
2502
+ const { abortSignal, ...rest } = options;
2503
+ return {
2504
+ ...rest,
2505
+ prompt: serializePrompt(options.prompt)
2506
+ };
2507
+ }
2508
+ function createRemoteModel(modelId, config) {
2509
+ const baseUrl = config.url.replace(/\/$/, "");
2510
+ const headers = {
2511
+ "Content-Type": "application/json",
2512
+ "Authorization": `Bearer ${config.authKey}`
2513
+ };
2514
+ return {
2515
+ specificationVersion: "v3",
2516
+ provider: "remote-proxy",
2517
+ modelId,
2518
+ supportedUrls: {},
2519
+ async doGenerate(options) {
2520
+ const res = await fetch(`${baseUrl}/inference/generate`, {
2521
+ method: "POST",
2522
+ headers,
2523
+ body: JSON.stringify({
2524
+ modelId,
2525
+ options: prepareOptions(options)
2526
+ }),
2527
+ signal: options.abortSignal
2528
+ });
2529
+ if (!res.ok) {
2530
+ const err = await res.json().catch(() => ({}));
2531
+ throw new Error(
2532
+ `Remote inference failed (${res.status}): ${err.error || res.statusText}`
2533
+ );
2534
+ }
2535
+ const result = await res.json();
2536
+ return deserializeValue(result);
2537
+ },
2538
+ async doStream(options) {
2539
+ const res = await fetch(`${baseUrl}/inference/stream`, {
2540
+ method: "POST",
2541
+ headers,
2542
+ body: JSON.stringify({
2543
+ modelId,
2544
+ options: prepareOptions(options)
2545
+ }),
2546
+ signal: options.abortSignal
2547
+ });
2548
+ if (!res.ok) {
2549
+ const err = await res.json().catch(() => ({}));
2550
+ throw new Error(
2551
+ `Remote inference failed (${res.status}): ${err.error || res.statusText}`
2552
+ );
2553
+ }
2554
+ const reader = res.body.getReader();
2555
+ const decoder = new TextDecoder();
2556
+ let buffer = "";
2557
+ const stream = new ReadableStream({
2558
+ async pull(controller) {
2559
+ while (true) {
2560
+ const { done, value } = await reader.read();
2561
+ if (done) {
2562
+ if (buffer.trim()) {
2563
+ try {
2564
+ const parsed = deserializeValue(JSON.parse(buffer.trim()));
2565
+ if (parsed.type === "error") {
2566
+ controller.error(new Error(parsed.error));
2567
+ } else {
2568
+ controller.enqueue(parsed);
2569
+ }
2570
+ } catch {
2571
+ }
2572
+ }
2573
+ controller.close();
2574
+ return;
2575
+ }
2576
+ buffer += decoder.decode(value, { stream: true });
2577
+ const lines = buffer.split("\n");
2578
+ buffer = lines.pop() || "";
2579
+ for (const line of lines) {
2580
+ if (!line.trim()) continue;
2581
+ try {
2582
+ const parsed = deserializeValue(JSON.parse(line));
2583
+ if (parsed.type === "error") {
2584
+ controller.error(new Error(parsed.error));
2585
+ return;
2586
+ }
2587
+ controller.enqueue(parsed);
2588
+ } catch {
2589
+ }
2590
+ }
2591
+ }
2592
+ },
2593
+ cancel() {
2594
+ reader.cancel();
2595
+ }
2596
+ });
2597
+ const responseHeaders = {};
2598
+ res.headers.forEach((v, k) => {
2599
+ if (k.startsWith("x-upstream-")) {
2600
+ responseHeaders[k.replace("x-upstream-", "")] = v;
2601
+ }
2602
+ });
2603
+ return {
2604
+ stream,
2605
+ response: Object.keys(responseHeaders).length > 0 ? { headers: responseHeaders } : void 0
2606
+ };
2607
+ }
2608
+ };
2609
+ }
2610
+
2611
+ // src/agent/model.ts
2612
+ init_config();
2444
2613
  var ANTHROPIC_PREFIX = "anthropic/";
2445
2614
  function isAnthropicModel(modelId) {
2446
2615
  const normalized = modelId.trim().toLowerCase();
2447
2616
  return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
2448
2617
  }
2449
2618
  function resolveModel(modelId) {
2619
+ try {
2620
+ const config = getConfig();
2621
+ if (config.resolvedRemoteServer.isConfigured) {
2622
+ return createRemoteModel(modelId.trim(), {
2623
+ url: config.resolvedRemoteServer.url,
2624
+ authKey: config.resolvedRemoteServer.authKey
2625
+ });
2626
+ }
2627
+ } catch {
2628
+ }
2450
2629
  return gateway(modelId.trim());
2451
2630
  }
2452
2631
  var SUBAGENT_MODELS = {
@@ -2458,7 +2637,7 @@ var SUBAGENT_MODELS = {
2458
2637
  // src/agent/index.ts
2459
2638
  init_db();
2460
2639
  init_config();
2461
- import { z as z12 } from "zod";
2640
+ import { z as z13 } from "zod";
2462
2641
  import { nanoid as nanoid3 } from "nanoid";
2463
2642
 
2464
2643
  // src/tools/bash.ts
@@ -3039,26 +3218,41 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3039
3218
  import { tool as tool2 } from "ai";
3040
3219
  import { z as z3 } from "zod";
3041
3220
  import { readFile as readFile2, stat } from "fs/promises";
3042
- import { resolve as resolve2, relative, isAbsolute } from "path";
3221
+ import { resolve as resolve2, relative, isAbsolute, extname } from "path";
3043
3222
  import { existsSync as existsSync3 } from "fs";
3044
3223
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
3224
+ var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
3045
3225
  var MAX_OUTPUT_CHARS3 = 5e4;
3226
+ var IMAGE_EXTENSIONS = {
3227
+ ".png": "image/png",
3228
+ ".jpg": "image/jpeg",
3229
+ ".jpeg": "image/jpeg",
3230
+ ".gif": "image/gif",
3231
+ ".webp": "image/webp"
3232
+ };
3233
+ function isImageFile(filePath) {
3234
+ return extname(filePath).toLowerCase() in IMAGE_EXTENSIONS;
3235
+ }
3236
+ function getImageMediaType(filePath) {
3237
+ return IMAGE_EXTENSIONS[extname(filePath).toLowerCase()] || "image/png";
3238
+ }
3046
3239
  var readFileInputSchema = z3.object({
3047
- path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute."),
3048
- startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed)"),
3049
- endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive)")
3240
+ path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute. Supports text files and images (png, jpg, jpeg, gif, webp)."),
3241
+ startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed). Only for text files."),
3242
+ endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive). Only for text files.")
3050
3243
  });
3051
3244
  function createReadFileTool(options) {
3052
3245
  return tool2({
3053
3246
  description: `Read the contents of a file. Provide a path relative to the working directory (${options.workingDirectory}) or an absolute path.
3054
- Large files will be automatically truncated. Binary files are not supported.
3055
- Use this to understand existing code, check file contents, or gather context.`,
3247
+ Supports text files (automatically truncated if large) and image files (png, jpg, jpeg, gif, webp).
3248
+ For images, the file contents are returned as visual data you can see and analyze.
3249
+ Use this to understand existing code, check file contents, view screenshots, or gather context.`,
3056
3250
  inputSchema: readFileInputSchema,
3057
- execute: async ({ path, startLine, endLine }) => {
3251
+ execute: async ({ path: filePath, startLine, endLine }) => {
3058
3252
  try {
3059
- const absolutePath = isAbsolute(path) ? path : resolve2(options.workingDirectory, path);
3253
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve2(options.workingDirectory, filePath);
3060
3254
  const relativePath = relative(options.workingDirectory, absolutePath);
3061
- if (relativePath.startsWith("..") && !isAbsolute(path)) {
3255
+ if (relativePath.startsWith("..") && !isAbsolute(filePath)) {
3062
3256
  return {
3063
3257
  success: false,
3064
3258
  error: "Path escapes the working directory. Use an absolute path if intentional.",
@@ -3068,22 +3262,43 @@ Use this to understand existing code, check file contents, or gather context.`,
3068
3262
  if (!existsSync3(absolutePath)) {
3069
3263
  return {
3070
3264
  success: false,
3071
- error: `File not found: ${path}`,
3265
+ error: `File not found: ${filePath}`,
3072
3266
  content: null
3073
3267
  };
3074
3268
  }
3075
3269
  const stats = await stat(absolutePath);
3076
- if (stats.size > MAX_FILE_SIZE) {
3270
+ if (stats.isDirectory()) {
3077
3271
  return {
3078
3272
  success: false,
3079
- error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
3273
+ error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
3080
3274
  content: null
3081
3275
  };
3082
3276
  }
3083
- if (stats.isDirectory()) {
3277
+ if (isImageFile(absolutePath)) {
3278
+ if (stats.size > MAX_IMAGE_SIZE) {
3279
+ return {
3280
+ success: false,
3281
+ error: `Image is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_IMAGE_SIZE / 1024 / 1024}MB.`,
3282
+ content: null
3283
+ };
3284
+ }
3285
+ const buffer = await readFile2(absolutePath);
3286
+ const base64 = buffer.toString("base64");
3287
+ const mediaType = getImageMediaType(absolutePath);
3288
+ return {
3289
+ success: true,
3290
+ path: absolutePath,
3291
+ relativePath: relative(options.workingDirectory, absolutePath),
3292
+ content: `[Image: ${relativePath} (${mediaType}, ${(stats.size / 1024).toFixed(1)}KB)]`,
3293
+ mediaType,
3294
+ imageData: base64,
3295
+ sizeBytes: stats.size
3296
+ };
3297
+ }
3298
+ if (stats.size > MAX_FILE_SIZE) {
3084
3299
  return {
3085
3300
  success: false,
3086
- error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
3301
+ error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
3087
3302
  content: null
3088
3303
  };
3089
3304
  }
@@ -3099,9 +3314,7 @@ Use this to understand existing code, check file contents, or gather context.`,
3099
3314
  content: null
3100
3315
  };
3101
3316
  }
3102
- content = lines.slice(start, end).join("\n");
3103
- const lineNumbers = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
3104
- content = lineNumbers;
3317
+ content = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
3105
3318
  }
3106
3319
  const truncatedContent = truncateOutput(content, MAX_OUTPUT_CHARS3);
3107
3320
  const wasTruncated = truncatedContent.length < content.length;
@@ -3128,6 +3341,19 @@ Use this to understand existing code, check file contents, or gather context.`,
3128
3341
  content: null
3129
3342
  };
3130
3343
  }
3344
+ },
3345
+ toModelOutput: ({ output }) => {
3346
+ if (output && typeof output === "object" && "imageData" in output && output.imageData) {
3347
+ const result = output;
3348
+ return {
3349
+ type: "content",
3350
+ value: [
3351
+ { type: "text", text: result.content },
3352
+ { type: "image-data", data: result.imageData, mediaType: result.mediaType }
3353
+ ]
3354
+ };
3355
+ }
3356
+ return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output };
3131
3357
  }
3132
3358
  });
3133
3359
  }
@@ -3328,7 +3554,7 @@ function clearCheckpointManager(sessionId) {
3328
3554
  }
3329
3555
 
3330
3556
  // src/lsp/index.ts
3331
- import { extname as extname2, dirname as dirname4 } from "path";
3557
+ import { extname as extname3, dirname as dirname4 } from "path";
3332
3558
 
3333
3559
  // src/lsp/servers.ts
3334
3560
  import { spawn } from "child_process";
@@ -3451,9 +3677,9 @@ import {
3451
3677
  import { pathToFileURL, fileURLToPath } from "url";
3452
3678
  import { readFile as readFile4 } from "fs/promises";
3453
3679
  import { existsSync as existsSync6 } from "fs";
3454
- import { extname, normalize } from "path";
3680
+ import { extname as extname2, normalize } from "path";
3455
3681
  function getLanguageId(filePath) {
3456
- const ext = extname(filePath).toLowerCase();
3682
+ const ext = extname2(filePath).toLowerCase();
3457
3683
  const map = {
3458
3684
  ".ts": "typescript",
3459
3685
  ".tsx": "typescriptreact",
@@ -3841,7 +4067,7 @@ var state = {
3841
4067
  };
3842
4068
  async function getClientForFile(filePath) {
3843
4069
  const normalized = normalizePath(filePath);
3844
- const ext = extname2(normalized);
4070
+ const ext = extname3(normalized);
3845
4071
  const serverDef = getServerForExtension(ext);
3846
4072
  if (!serverDef) {
3847
4073
  return null;
@@ -3934,7 +4160,7 @@ async function formatDiagnosticsOutput(filePath, options = {}) {
3934
4160
  return formatDiagnosticsForAgent(filePath, diagnostics, options);
3935
4161
  }
3936
4162
  function isSupported(filePath) {
3937
- const ext = extname2(filePath);
4163
+ const ext = extname3(filePath);
3938
4164
  return getServerForExtension(ext) !== null;
3939
4165
  }
3940
4166
 
@@ -4361,7 +4587,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
4361
4587
  // src/tools/linter.ts
4362
4588
  import { tool as tool6 } from "ai";
4363
4589
  import { z as z7 } from "zod";
4364
- import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname4 } from "path";
4590
+ import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
4365
4591
  import { existsSync as existsSync9 } from "fs";
4366
4592
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
4367
4593
  var linterInputSchema = z7.object({
@@ -4384,7 +4610,7 @@ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
4384
4610
  }
4385
4611
  await walk(fullPath);
4386
4612
  } else if (entry.isFile()) {
4387
- const ext = extname4(entry.name);
4613
+ const ext = extname5(entry.name);
4388
4614
  if (supportedExtensions.includes(ext)) {
4389
4615
  files.push(fullPath);
4390
4616
  }
@@ -5721,6 +5947,59 @@ Context: ${context}` : query;
5721
5947
 
5722
5948
  // src/tools/index.ts
5723
5949
  init_semantic_search();
5950
+
5951
+ // src/tools/task.ts
5952
+ import { tool as tool11 } from "ai";
5953
+ import { z as z12 } from "zod";
5954
+ import Ajv from "ajv";
5955
+ var ajv = new Ajv({ allErrors: true });
5956
+ function createCompleteTaskTool(options) {
5957
+ const validate = ajv.compile(options.outputSchema);
5958
+ return tool11({
5959
+ description: "Call this tool when you have completed the task. Pass the result as a JSON object matching the required output schema. If the result does not match the schema, you will receive validation errors and should fix and retry.",
5960
+ inputSchema: z12.object({
5961
+ result: z12.record(z12.string(), z12.unknown()).describe("The task result as a JSON object matching the output schema")
5962
+ }),
5963
+ execute: async (input) => {
5964
+ const valid = validate(input.result);
5965
+ if (!valid) {
5966
+ const errors = validate.errors?.map((e) => ({
5967
+ path: e.instancePath || "/",
5968
+ message: e.message,
5969
+ params: e.params
5970
+ }));
5971
+ return {
5972
+ status: "validation_error",
5973
+ message: "The result does not match the required output schema. Fix the errors and call complete_task again.",
5974
+ errors,
5975
+ expectedSchema: options.outputSchema
5976
+ };
5977
+ }
5978
+ options.onComplete({ status: "completed", result: input.result });
5979
+ return {
5980
+ status: "completed",
5981
+ message: "Task completed successfully."
5982
+ };
5983
+ }
5984
+ });
5985
+ }
5986
+ function createTaskFailedTool(options) {
5987
+ return tool11({
5988
+ description: "Call this tool if you are unable to complete the task. Provide a clear reason explaining why the task cannot be completed.",
5989
+ inputSchema: z12.object({
5990
+ reason: z12.string().describe("Explanation of why the task cannot be completed")
5991
+ }),
5992
+ execute: async (input) => {
5993
+ options.onComplete({ status: "failed", error: input.reason });
5994
+ return {
5995
+ status: "failed",
5996
+ message: `Task marked as failed: ${input.reason}`
5997
+ };
5998
+ }
5999
+ });
6000
+ }
6001
+
6002
+ // src/tools/index.ts
5724
6003
  init_semantic();
5725
6004
  init_semantic_search();
5726
6005
  async function createTools(options) {
@@ -5772,6 +6051,10 @@ async function createTools(options) {
5772
6051
  } catch {
5773
6052
  }
5774
6053
  }
6054
+ if (options.taskTools) {
6055
+ tools.complete_task = createCompleteTaskTool(options.taskTools);
6056
+ tools.task_failed = createTaskFailedTool(options.taskTools);
6057
+ }
5775
6058
  return tools;
5776
6059
  }
5777
6060
 
@@ -6089,6 +6372,30 @@ function formatTodosForContext(todos) {
6089
6372
  }
6090
6373
  return lines.join("\n");
6091
6374
  }
6375
+ function buildTaskPromptAddendum(outputSchema) {
6376
+ return `
6377
+ ## Task Mode
6378
+
6379
+ You are running in **task mode**. You have been given a specific task to complete autonomously.
6380
+
6381
+ ### Rules
6382
+ 1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
6383
+ 2. Keep working until the task is fully complete.
6384
+ 3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
6385
+ 4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
6386
+ 5. Do NOT stop without calling one of these two tools.
6387
+
6388
+ ### Output Schema
6389
+ The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
6390
+ \`\`\`json
6391
+ ${JSON.stringify(outputSchema, null, 2)}
6392
+ \`\`\`
6393
+
6394
+ ### Completion Tools
6395
+ - **\`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.
6396
+ - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
6397
+ `;
6398
+ }
6092
6399
  function createSummaryPrompt(conversationHistory) {
6093
6400
  return `Please provide a concise summary of the following conversation history. Focus on:
6094
6401
  1. The main task or goal being worked on
@@ -6350,6 +6657,25 @@ ${this.summary}`
6350
6657
  }
6351
6658
  };
6352
6659
 
6660
+ // src/utils/webhook.ts
6661
+ async function sendWebhook(url, event) {
6662
+ try {
6663
+ const controller = new AbortController();
6664
+ const timeout = setTimeout(() => controller.abort(), 5e3);
6665
+ await fetch(url, {
6666
+ method: "POST",
6667
+ headers: {
6668
+ "Content-Type": "application/json",
6669
+ "X-SparkECoder-Event": event.type
6670
+ },
6671
+ body: JSON.stringify(event),
6672
+ signal: controller.signal
6673
+ });
6674
+ clearTimeout(timeout);
6675
+ } catch {
6676
+ }
6677
+ }
6678
+
6353
6679
  // src/agent/index.ts
6354
6680
  var approvalResolvers = /* @__PURE__ */ new Map();
6355
6681
  var Agent = class _Agent {
@@ -6569,6 +6895,145 @@ ${prompt}` });
6569
6895
  steps: result.steps
6570
6896
  };
6571
6897
  }
6898
+ /**
6899
+ * Run the agent in task mode — loops autonomously until the agent calls
6900
+ * complete_task or task_failed (or hits maxIterations).
6901
+ * All tools run without approval. Webhook events are fired throughout.
6902
+ */
6903
+ async runTask(options) {
6904
+ const config = getConfig();
6905
+ const maxIterations = options.taskConfig.maxIterations ?? 50;
6906
+ const webhookUrl = options.taskConfig.webhookUrl;
6907
+ const fireWebhook = (type, data) => {
6908
+ if (!webhookUrl) return;
6909
+ sendWebhook(webhookUrl, {
6910
+ type,
6911
+ taskId: this.session.id,
6912
+ sessionId: this.session.id,
6913
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6914
+ data
6915
+ });
6916
+ };
6917
+ const completion = { signal: null };
6918
+ const onComplete = (signal) => {
6919
+ completion.signal = signal;
6920
+ };
6921
+ const taskTools = await createTools({
6922
+ sessionId: this.session.id,
6923
+ workingDirectory: this.session.workingDirectory,
6924
+ skillsDirectories: config.resolvedSkillsDirectories,
6925
+ onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
6926
+ onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
6927
+ onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
6928
+ taskTools: {
6929
+ outputSchema: options.taskConfig.outputSchema,
6930
+ onComplete
6931
+ }
6932
+ });
6933
+ const baseSystemPrompt = await buildSystemPrompt({
6934
+ workingDirectory: this.session.workingDirectory,
6935
+ skillsDirectories: config.resolvedSkillsDirectories,
6936
+ sessionId: this.session.id,
6937
+ discoveredSkills: config.discoveredSkills,
6938
+ activeFiles: []
6939
+ });
6940
+ const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
6941
+ const systemPrompt = `${baseSystemPrompt}
6942
+
6943
+ ${taskAddendum}`;
6944
+ fireWebhook("task.started", { prompt: options.prompt });
6945
+ this.context.addUserMessage(options.prompt);
6946
+ let iteration = 0;
6947
+ while (iteration < maxIterations) {
6948
+ iteration++;
6949
+ if (options.abortSignal?.aborted) {
6950
+ const cancelError = "Task was cancelled";
6951
+ fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
6952
+ return { status: "failed", error: cancelError, iterations: iteration };
6953
+ }
6954
+ const messages = await this.context.getMessages();
6955
+ const useAnthropic = isAnthropicModel(this.session.model);
6956
+ const result = await generateText3({
6957
+ model: resolveModel(this.session.model),
6958
+ system: systemPrompt,
6959
+ messages,
6960
+ tools: taskTools,
6961
+ stopWhen: stepCountIs2(500),
6962
+ abortSignal: options.abortSignal,
6963
+ providerOptions: useAnthropic ? {
6964
+ anthropic: {
6965
+ thinking: { type: "enabled", budgetTokens: 1e4 }
6966
+ }
6967
+ } : void 0,
6968
+ onStepFinish: (step) => {
6969
+ options.onStepFinish?.(step);
6970
+ fireWebhook("task.step_finished", { iteration, text: step.text });
6971
+ }
6972
+ });
6973
+ const responseMessages = result.response.messages;
6974
+ this.context.addResponseMessages(responseMessages);
6975
+ if (result.text) {
6976
+ options.onText?.(result.text);
6977
+ fireWebhook("task.message", { iteration, text: result.text });
6978
+ }
6979
+ for (const step of result.steps) {
6980
+ if (step.toolCalls) {
6981
+ for (const tc of step.toolCalls) {
6982
+ options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.args });
6983
+ fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.args });
6984
+ }
6985
+ }
6986
+ if (step.toolResults) {
6987
+ for (const tr of step.toolResults) {
6988
+ options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.result });
6989
+ fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.result });
6990
+ }
6991
+ }
6992
+ }
6993
+ if (completion.signal) {
6994
+ const sig = completion.signal;
6995
+ const finalStatus = sig.status;
6996
+ const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
6997
+ fireWebhook(eventType, {
6998
+ status: finalStatus,
6999
+ result: sig.result,
7000
+ error: sig.error,
7001
+ iterations: iteration
7002
+ });
7003
+ const updatedTask2 = {
7004
+ ...options.taskConfig,
7005
+ status: finalStatus,
7006
+ result: sig.result,
7007
+ error: sig.error,
7008
+ iterations: iteration
7009
+ };
7010
+ await sessionQueries.update(this.session.id, {
7011
+ config: { ...this.session.config, task: updatedTask2 }
7012
+ });
7013
+ return {
7014
+ status: finalStatus,
7015
+ result: sig.result,
7016
+ error: sig.error,
7017
+ iterations: iteration
7018
+ };
7019
+ }
7020
+ this.context.addUserMessage(
7021
+ "Continue working on the task. When done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
7022
+ );
7023
+ }
7024
+ const timeoutError = `Task did not complete within ${maxIterations} iterations`;
7025
+ fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
7026
+ const updatedTask = {
7027
+ ...options.taskConfig,
7028
+ status: "failed",
7029
+ error: timeoutError,
7030
+ iterations: iteration
7031
+ };
7032
+ await sessionQueries.update(this.session.id, {
7033
+ config: { ...this.session.config, task: updatedTask }
7034
+ });
7035
+ return { status: "failed", error: timeoutError, iterations: iteration };
7036
+ }
6572
7037
  /**
6573
7038
  * Wrap tools to add approval checking
6574
7039
  */
@@ -6582,9 +7047,9 @@ ${prompt}` });
6582
7047
  wrappedTools[name] = originalTool;
6583
7048
  continue;
6584
7049
  }
6585
- wrappedTools[name] = tool11({
7050
+ wrappedTools[name] = tool12({
6586
7051
  description: originalTool.description || "",
6587
- inputSchema: originalTool.inputSchema || z12.object({}),
7052
+ inputSchema: originalTool.inputSchema || z13.object({}),
6588
7053
  execute: async (input, toolOptions) => {
6589
7054
  const toolCallId = toolOptions.toolCallId || nanoid3();
6590
7055
  const execution = toolExecutionQueries.create({
@@ -6725,18 +7190,18 @@ function cleanupPendingInputs() {
6725
7190
  }
6726
7191
  }
6727
7192
  }
6728
- var createSessionSchema = z13.object({
6729
- name: z13.string().optional(),
6730
- workingDirectory: z13.string().optional(),
6731
- model: z13.string().optional(),
6732
- toolApprovals: z13.record(z13.string(), z13.boolean()).optional()
7193
+ var createSessionSchema = z14.object({
7194
+ name: z14.string().optional(),
7195
+ workingDirectory: z14.string().optional(),
7196
+ model: z14.string().optional(),
7197
+ toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
6733
7198
  });
6734
- var paginationQuerySchema = z13.object({
6735
- limit: z13.string().optional(),
6736
- offset: z13.string().optional()
7199
+ var paginationQuerySchema = z14.object({
7200
+ limit: z14.string().optional(),
7201
+ offset: z14.string().optional()
6737
7202
  });
6738
- var messagesQuerySchema = z13.object({
6739
- limit: z13.string().optional()
7203
+ var messagesQuerySchema = z14.object({
7204
+ limit: z14.string().optional()
6740
7205
  });
6741
7206
  sessions.get(
6742
7207
  "/",
@@ -6875,10 +7340,10 @@ sessions.get("/:id/tools", async (c) => {
6875
7340
  count: executions.length
6876
7341
  });
6877
7342
  });
6878
- var updateSessionSchema = z13.object({
6879
- model: z13.string().optional(),
6880
- name: z13.string().optional(),
6881
- toolApprovals: z13.record(z13.string(), z13.boolean()).optional()
7343
+ var updateSessionSchema = z14.object({
7344
+ model: z14.string().optional(),
7345
+ name: z14.string().optional(),
7346
+ toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
6882
7347
  });
6883
7348
  sessions.patch(
6884
7349
  "/:id",
@@ -6948,8 +7413,8 @@ sessions.post("/:id/clear", async (c) => {
6948
7413
  await agent.clearContext();
6949
7414
  return c.json({ success: true, sessionId: id });
6950
7415
  });
6951
- var pendingInputSchema = z13.object({
6952
- text: z13.string()
7416
+ var pendingInputSchema = z14.object({
7417
+ text: z14.string()
6953
7418
  });
6954
7419
  sessions.post(
6955
7420
  "/:id/pending-input",
@@ -6980,13 +7445,13 @@ sessions.get("/:id/pending-input", async (c) => {
6980
7445
  createdAt: pending.createdAt.toISOString()
6981
7446
  });
6982
7447
  });
6983
- var devtoolsContextSchema = z13.object({
6984
- url: z13.string(),
6985
- path: z13.string(),
6986
- pageName: z13.string().optional(),
6987
- screenWidth: z13.number().optional(),
6988
- screenHeight: z13.number().optional(),
6989
- devicePixelRatio: z13.number().optional()
7448
+ var devtoolsContextSchema = z14.object({
7449
+ url: z14.string(),
7450
+ path: z14.string(),
7451
+ pageName: z14.string().optional(),
7452
+ screenWidth: z14.number().optional(),
7453
+ screenHeight: z14.number().optional(),
7454
+ devicePixelRatio: z14.number().optional()
6990
7455
  });
6991
7456
  sessions.post(
6992
7457
  "/:id/devtools-context",
@@ -7208,7 +7673,7 @@ sessions.post("/:id/attachments", async (c) => {
7208
7673
  }
7209
7674
  const dir = ensureAttachmentsDir(sessionId);
7210
7675
  const id = nanoid4(10);
7211
- const ext = extname6(file.name) || "";
7676
+ const ext = extname7(file.name) || "";
7212
7677
  const safeFilename = `${id}_${basename4(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7213
7678
  const filePath = join5(dir, safeFilename);
7214
7679
  const arrayBuffer = await file.arrayBuffer();
@@ -7234,7 +7699,7 @@ sessions.post("/:id/attachments", async (c) => {
7234
7699
  }
7235
7700
  const dir = ensureAttachmentsDir(sessionId);
7236
7701
  const id = nanoid4(10);
7237
- const ext = extname6(body.filename) || "";
7702
+ const ext = extname7(body.filename) || "";
7238
7703
  const safeFilename = `${id}_${basename4(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7239
7704
  const filePath = join5(dir, safeFilename);
7240
7705
  let base64Data = body.data;
@@ -7277,10 +7742,10 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
7277
7742
  unlinkSync(filePath);
7278
7743
  return c.json({ success: true, id: attachmentId });
7279
7744
  });
7280
- var filesQuerySchema = z13.object({
7281
- query: z13.string().optional(),
7745
+ var filesQuerySchema = z14.object({
7746
+ query: z14.string().optional(),
7282
7747
  // Filter query (e.g., "src/com" to match "src/components")
7283
- limit: z13.string().optional()
7748
+ limit: z14.string().optional()
7284
7749
  // Max results (default 50)
7285
7750
  });
7286
7751
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -7364,7 +7829,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
7364
7829
  if (entry.name.startsWith(".")) {
7365
7830
  continue;
7366
7831
  }
7367
- const ext = extname6(entry.name).toLowerCase();
7832
+ const ext = extname7(entry.name).toLowerCase();
7368
7833
  if (IGNORED_EXTENSIONS.has(ext)) {
7369
7834
  continue;
7370
7835
  }
@@ -7458,7 +7923,7 @@ sessions.get(
7458
7923
  init_db();
7459
7924
  import { Hono as Hono2 } from "hono";
7460
7925
  import { zValidator as zValidator2 } from "@hono/zod-validator";
7461
- import { z as z14 } from "zod";
7926
+ import { z as z15 } from "zod";
7462
7927
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
7463
7928
  import { join as join6 } from "path";
7464
7929
  init_config();
@@ -7623,30 +8088,30 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
7623
8088
  ${prompt}`;
7624
8089
  }
7625
8090
  var agents = new Hono2();
7626
- var attachmentSchema = z14.object({
7627
- type: z14.enum(["image", "file"]),
7628
- data: z14.string(),
8091
+ var attachmentSchema = z15.object({
8092
+ type: z15.enum(["image", "file"]),
8093
+ data: z15.string(),
7629
8094
  // base64 data URL or raw base64
7630
- mediaType: z14.string().optional(),
7631
- filename: z14.string().optional()
8095
+ mediaType: z15.string().optional(),
8096
+ filename: z15.string().optional()
7632
8097
  });
7633
- var runPromptSchema = z14.object({
7634
- prompt: z14.string(),
8098
+ var runPromptSchema = z15.object({
8099
+ prompt: z15.string(),
7635
8100
  // Can be empty if attachments are provided
7636
- attachments: z14.array(attachmentSchema).optional()
8101
+ attachments: z15.array(attachmentSchema).optional()
7637
8102
  }).refine(
7638
8103
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
7639
8104
  { message: "Either prompt or attachments must be provided" }
7640
8105
  );
7641
- var quickStartSchema = z14.object({
7642
- prompt: z14.string().min(1),
7643
- name: z14.string().optional(),
7644
- workingDirectory: z14.string().optional(),
7645
- model: z14.string().optional(),
7646
- toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
8106
+ var quickStartSchema = z15.object({
8107
+ prompt: z15.string().min(1),
8108
+ name: z15.string().optional(),
8109
+ workingDirectory: z15.string().optional(),
8110
+ model: z15.string().optional(),
8111
+ toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
7647
8112
  });
7648
- var rejectSchema = z14.object({
7649
- reason: z14.string().optional()
8113
+ var rejectSchema = z15.object({
8114
+ reason: z15.string().optional()
7650
8115
  }).optional();
7651
8116
  var streamAbortControllers = /* @__PURE__ */ new Map();
7652
8117
  function getAttachmentsDirectory(sessionId) {
@@ -8076,7 +8541,7 @@ agents.get("/:id/watch", async (c) => {
8076
8541
  "Cache-Control": "no-cache",
8077
8542
  "Connection": "keep-alive",
8078
8543
  "x-vercel-ai-ui-message-stream": "v1",
8079
- "x-stream-id": streamId
8544
+ "x-stream-id": streamId ?? ""
8080
8545
  }
8081
8546
  });
8082
8547
  });
@@ -8434,7 +8899,7 @@ agents.post(
8434
8899
  init_config();
8435
8900
  import { Hono as Hono3 } from "hono";
8436
8901
  import { zValidator as zValidator3 } from "@hono/zod-validator";
8437
- import { z as z15 } from "zod";
8902
+ import { z as z16 } from "zod";
8438
8903
  import { readFileSync as readFileSync5 } from "fs";
8439
8904
  import { fileURLToPath as fileURLToPath3 } from "url";
8440
8905
  import { dirname as dirname6, join as join7 } from "path";
@@ -8544,9 +9009,9 @@ health.get("/api-keys", async (c) => {
8544
9009
  supportedProviders: SUPPORTED_PROVIDERS
8545
9010
  });
8546
9011
  });
8547
- var setApiKeySchema = z15.object({
8548
- provider: z15.string(),
8549
- apiKey: z15.string().min(1)
9012
+ var setApiKeySchema = z16.object({
9013
+ provider: z16.string(),
9014
+ apiKey: z16.string().min(1)
8550
9015
  });
8551
9016
  health.post(
8552
9017
  "/api-keys",
@@ -8585,13 +9050,13 @@ health.delete("/api-keys/:provider", async (c) => {
8585
9050
  // src/server/routes/terminals.ts
8586
9051
  import { Hono as Hono4 } from "hono";
8587
9052
  import { zValidator as zValidator4 } from "@hono/zod-validator";
8588
- import { z as z16 } from "zod";
9053
+ import { z as z17 } from "zod";
8589
9054
  init_db();
8590
9055
  var terminals = new Hono4();
8591
- var spawnSchema = z16.object({
8592
- command: z16.string(),
8593
- cwd: z16.string().optional(),
8594
- name: z16.string().optional()
9056
+ var spawnSchema = z17.object({
9057
+ command: z17.string(),
9058
+ cwd: z17.string().optional(),
9059
+ name: z17.string().optional()
8595
9060
  });
8596
9061
  terminals.post(
8597
9062
  "/:sessionId/terminals",
@@ -8672,8 +9137,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
8672
9137
  // We don't track exit codes in tmux mode
8673
9138
  });
8674
9139
  });
8675
- var logsQuerySchema = z16.object({
8676
- tail: z16.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
9140
+ var logsQuerySchema = z17.object({
9141
+ tail: z17.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
8677
9142
  });
8678
9143
  terminals.get(
8679
9144
  "/:sessionId/terminals/:terminalId/logs",
@@ -8697,8 +9162,8 @@ terminals.get(
8697
9162
  });
8698
9163
  }
8699
9164
  );
8700
- var killSchema = z16.object({
8701
- signal: z16.enum(["SIGTERM", "SIGKILL"]).optional()
9165
+ var killSchema = z17.object({
9166
+ signal: z17.enum(["SIGTERM", "SIGKILL"]).optional()
8702
9167
  });
8703
9168
  terminals.post(
8704
9169
  "/:sessionId/terminals/:terminalId/kill",
@@ -8712,8 +9177,8 @@ terminals.post(
8712
9177
  return c.json({ success: true, message: "Terminal killed" });
8713
9178
  }
8714
9179
  );
8715
- var writeSchema = z16.object({
8716
- input: z16.string()
9180
+ var writeSchema = z17.object({
9181
+ input: z17.string()
8717
9182
  });
8718
9183
  terminals.post(
8719
9184
  "/:sessionId/terminals/:terminalId/write",
@@ -8894,6 +9359,131 @@ data: ${JSON.stringify({ status: "stopped" })}
8894
9359
  );
8895
9360
  });
8896
9361
 
9362
+ // src/server/routes/tasks.ts
9363
+ init_db();
9364
+ import { Hono as Hono5 } from "hono";
9365
+ import { zValidator as zValidator5 } from "@hono/zod-validator";
9366
+ import { z as z18 } from "zod";
9367
+ init_config();
9368
+ var tasks = new Hono5();
9369
+ var taskAbortControllers = /* @__PURE__ */ new Map();
9370
+ var createTaskSchema = z18.object({
9371
+ prompt: z18.string().min(1),
9372
+ outputSchema: z18.record(z18.string(), z18.unknown()),
9373
+ webhookUrl: z18.string().url().optional(),
9374
+ model: z18.string().optional(),
9375
+ workingDirectory: z18.string().optional(),
9376
+ name: z18.string().optional(),
9377
+ maxIterations: z18.number().int().min(1).max(500).optional()
9378
+ });
9379
+ tasks.post(
9380
+ "/",
9381
+ zValidator5("json", createTaskSchema),
9382
+ async (c) => {
9383
+ const body = c.req.valid("json");
9384
+ const config = getConfig();
9385
+ const taskConfig = {
9386
+ enabled: true,
9387
+ outputSchema: body.outputSchema,
9388
+ webhookUrl: body.webhookUrl,
9389
+ maxIterations: body.maxIterations ?? 50,
9390
+ status: "running"
9391
+ };
9392
+ const agent = await Agent.create({
9393
+ name: body.name || "Task",
9394
+ workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
9395
+ model: body.model || config.defaultModel,
9396
+ sessionConfig: {
9397
+ toolApprovals: { bash: false, write_file: false, read_file: false },
9398
+ task: taskConfig
9399
+ }
9400
+ });
9401
+ const taskId = agent.sessionId;
9402
+ const abortController = new AbortController();
9403
+ taskAbortControllers.set(taskId, abortController);
9404
+ (async () => {
9405
+ try {
9406
+ await agent.runTask({
9407
+ prompt: body.prompt,
9408
+ taskConfig,
9409
+ abortSignal: abortController.signal
9410
+ });
9411
+ } catch (err) {
9412
+ if (err.name === "AbortError" || abortController.signal.aborted) {
9413
+ console.log(`[TASK] Task ${taskId} was cancelled`);
9414
+ } else {
9415
+ console.error(`[TASK] Error in task ${taskId}:`, err.message);
9416
+ const failedTask = {
9417
+ ...taskConfig,
9418
+ status: "failed",
9419
+ error: err.message || "Unknown error"
9420
+ };
9421
+ await sessionQueries.update(taskId, {
9422
+ config: {
9423
+ toolApprovals: { bash: false, write_file: false, read_file: false },
9424
+ task: failedTask
9425
+ }
9426
+ });
9427
+ }
9428
+ } finally {
9429
+ taskAbortControllers.delete(taskId);
9430
+ }
9431
+ })();
9432
+ return c.json({ taskId, status: "running" }, 201);
9433
+ }
9434
+ );
9435
+ tasks.get("/:id", async (c) => {
9436
+ const id = c.req.param("id");
9437
+ const session = await sessionQueries.getById(id);
9438
+ if (!session) {
9439
+ return c.json({ error: "Task not found" }, 404);
9440
+ }
9441
+ const task = session.config?.task;
9442
+ if (!task?.enabled) {
9443
+ return c.json({ error: "Session is not a task" }, 400);
9444
+ }
9445
+ return c.json({
9446
+ taskId: id,
9447
+ status: task.status,
9448
+ result: task.result,
9449
+ error: task.error,
9450
+ iterations: task.iterations,
9451
+ model: session.model,
9452
+ name: session.name,
9453
+ createdAt: session.createdAt.toISOString(),
9454
+ updatedAt: session.updatedAt.toISOString()
9455
+ });
9456
+ });
9457
+ tasks.post("/:id/cancel", async (c) => {
9458
+ const id = c.req.param("id");
9459
+ const session = await sessionQueries.getById(id);
9460
+ if (!session) {
9461
+ return c.json({ error: "Task not found" }, 404);
9462
+ }
9463
+ const task = session.config?.task;
9464
+ if (!task?.enabled) {
9465
+ return c.json({ error: "Session is not a task" }, 400);
9466
+ }
9467
+ if (task.status !== "running") {
9468
+ return c.json({ error: `Task is already ${task.status}` }, 400);
9469
+ }
9470
+ const abortController = taskAbortControllers.get(id);
9471
+ if (abortController) {
9472
+ abortController.abort();
9473
+ taskAbortControllers.delete(id);
9474
+ }
9475
+ const cancelledTask = {
9476
+ ...task,
9477
+ status: "failed",
9478
+ error: "Task cancelled by user"
9479
+ };
9480
+ await sessionQueries.update(id, {
9481
+ config: { ...session.config, task: cancelledTask }
9482
+ });
9483
+ return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
9484
+ });
9485
+ var tasks_default = tasks;
9486
+
8897
9487
  // src/server/index.ts
8898
9488
  init_config();
8899
9489
  init_db();
@@ -8974,6 +9564,43 @@ async function checkDependencies(options = {}) {
8974
9564
  }
8975
9565
  return true;
8976
9566
  }
9567
+ async function checkAgentBrowser() {
9568
+ try {
9569
+ const { stdout } = await execAsync5("agent-browser --version", { timeout: 1e4 });
9570
+ const version = stdout.trim();
9571
+ return { available: true, version };
9572
+ } catch {
9573
+ return {
9574
+ available: false,
9575
+ error: "agent-browser is not installed globally",
9576
+ installInstructions: "Install agent-browser globally:\n npm install -g agent-browser\n agent-browser install"
9577
+ };
9578
+ }
9579
+ }
9580
+ async function tryInstallAgentBrowser(options = {}) {
9581
+ try {
9582
+ if (!options.quiet) {
9583
+ console.log("\u{1F4E6} Installing agent-browser globally...");
9584
+ }
9585
+ await execAsync5("npm install -g agent-browser", { timeout: 12e4 });
9586
+ try {
9587
+ if (!options.quiet) {
9588
+ console.log("\u{1F4E6} Installing Chromium for browser automation...");
9589
+ }
9590
+ await execAsync5("agent-browser install", { timeout: 12e4 });
9591
+ } catch {
9592
+ }
9593
+ if (!options.quiet) {
9594
+ console.log("\u2705 agent-browser installed successfully");
9595
+ }
9596
+ return true;
9597
+ } catch (error) {
9598
+ if (!options.quiet) {
9599
+ console.error(`Failed to install agent-browser: ${error.message}`);
9600
+ }
9601
+ return false;
9602
+ }
9603
+ }
8977
9604
  async function tryAutoInstallTmux() {
8978
9605
  const os2 = platform2();
8979
9606
  try {
@@ -9018,16 +9645,20 @@ async function tryAutoInstallTmux() {
9018
9645
  async function ensureDependencies(options = {}) {
9019
9646
  const { autoInstall = false, quiet = false } = options;
9020
9647
  const tmuxCheck = await checkTmux();
9021
- if (tmuxCheck.available) {
9022
- return;
9023
- }
9024
- if (autoInstall) {
9025
- const installed = await tryAutoInstallTmux();
9026
- if (installed) {
9027
- return;
9648
+ if (!tmuxCheck.available) {
9649
+ if (autoInstall) {
9650
+ const installed = await tryAutoInstallTmux();
9651
+ if (!installed) {
9652
+ await checkDependencies({ quiet, exitOnFailure: true });
9653
+ }
9654
+ } else {
9655
+ await checkDependencies({ quiet, exitOnFailure: true });
9028
9656
  }
9029
9657
  }
9030
- await checkDependencies({ quiet, exitOnFailure: true });
9658
+ const browserCheck = await checkAgentBrowser();
9659
+ if (!browserCheck.available) {
9660
+ await tryInstallAgentBrowser({ quiet });
9661
+ }
9031
9662
  }
9032
9663
 
9033
9664
  // src/server/index.ts
@@ -9285,7 +9916,7 @@ function stopWebUI() {
9285
9916
  }
9286
9917
  }
9287
9918
  async function createApp(options = {}) {
9288
- const app = new Hono5();
9919
+ const app = new Hono6();
9289
9920
  app.use("*", cors({
9290
9921
  origin: "*",
9291
9922
  // Allow all origins
@@ -9303,6 +9934,7 @@ async function createApp(options = {}) {
9303
9934
  app.route("/agents", agents);
9304
9935
  app.route("/sessions", terminals);
9305
9936
  app.route("/terminals", terminals);
9937
+ app.route("/tasks", tasks_default);
9306
9938
  app.get("/openapi.json", async (c) => {
9307
9939
  return c.json(generateOpenAPISpec());
9308
9940
  });
@@ -9872,7 +10504,7 @@ function generateOpenAPISpec() {
9872
10504
  init_config();
9873
10505
  init_semantic();
9874
10506
  init_db();
9875
- import { writeFileSync as writeFileSync5, existsSync as existsSync16 } from "fs";
10507
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync16 } from "fs";
9876
10508
  import { resolve as resolve11, join as join9 } from "path";
9877
10509
  async function apiRequest(baseUrl, path, options = {}) {
9878
10510
  const url = `${baseUrl}${path}`;
@@ -9912,9 +10544,15 @@ function promptApproval(rl, toolName, input) {
9912
10544
  const inputStr = JSON.stringify(input);
9913
10545
  const truncatedInput = inputStr.length > 100 ? inputStr.slice(0, 100) + "..." : inputStr;
9914
10546
  console.log(chalk.dim(` Command: ${truncatedInput}`));
9915
- rl.question(chalk.yellow(` Approve? [y/n]: `), (answer) => {
9916
- const approved = answer.toLowerCase().startsWith("y");
9917
- resolve12(approved);
10547
+ rl.question(chalk.yellow(` Approve? [y/n/a(lways)]: `), (answer) => {
10548
+ const lower = answer.toLowerCase().trim();
10549
+ if (lower === "a" || lower === "always") {
10550
+ resolve12("always");
10551
+ } else if (lower.startsWith("y")) {
10552
+ resolve12("approve");
10553
+ } else {
10554
+ resolve12("reject");
10555
+ }
9918
10556
  });
9919
10557
  });
9920
10558
  }
@@ -9976,14 +10614,27 @@ async function consumeSSEStream(response, options = {}) {
9976
10614
  }
9977
10615
  if (event.type === "data-approval-required") {
9978
10616
  const approval = event.data;
9979
- console.log(chalk.yellow(`
10617
+ if (options.skipApprovals && options.baseUrl && options.sessionId) {
10618
+ console.log(chalk.dim(` \u2713 Auto-approved: ${approval.toolName}`));
10619
+ await apiRequest(options.baseUrl, `/agents/${options.sessionId}/approve/${approval.toolCallId}`, { method: "POST" });
10620
+ } else if (options.interactive && options.baseUrl && options.sessionId && options.readline) {
10621
+ console.log(chalk.yellow(`
9980
10622
  \u26A0\uFE0F Approval required for: ${approval.toolName}`));
9981
- if (options.interactive && options.baseUrl && options.sessionId && options.readline) {
9982
- const approved = await promptApproval(options.readline, approval.toolName, approval.input);
9983
- const endpoint = approved ? "approve" : "reject";
10623
+ const result = await promptApproval(options.readline, approval.toolName, approval.input);
10624
+ const endpoint = result === "reject" ? "reject" : "approve";
9984
10625
  const apiResponse = await apiRequest(options.baseUrl, `/agents/${options.sessionId}/${endpoint}/${approval.toolCallId}`, { method: "POST" });
9985
10626
  if (apiResponse.ok) {
9986
- console.log(approved ? chalk.green(" \u2713 Approved") : chalk.red(" \u2717 Rejected"));
10627
+ if (result === "always") {
10628
+ await apiRequest(options.baseUrl, `/sessions/${options.sessionId}`, {
10629
+ method: "PATCH",
10630
+ body: { toolApprovals: { [approval.toolName]: false } }
10631
+ });
10632
+ console.log(chalk.green(` \u2713 Always allowed \u2014 ${approval.toolName} won't ask again this session`));
10633
+ } else if (result === "approve") {
10634
+ console.log(chalk.green(" \u2713 Approved"));
10635
+ } else {
10636
+ console.log(chalk.red(" \u2717 Rejected"));
10637
+ }
9987
10638
  } else {
9988
10639
  console.log(chalk.red(` Failed to ${endpoint}: ${apiResponse.statusText}`));
9989
10640
  }
@@ -10122,6 +10773,7 @@ async function runChat(options) {
10122
10773
  output: process.stdout
10123
10774
  });
10124
10775
  let sessionId;
10776
+ const skipApprovals = !!options.dangerouslySkipApprovals;
10125
10777
  if (options.session) {
10126
10778
  const response = await apiRequest(baseUrl, `/sessions/${options.session}`);
10127
10779
  if (!response.ok) {
@@ -10131,6 +10783,12 @@ async function runChat(options) {
10131
10783
  const session = await response.json();
10132
10784
  sessionId = session.id;
10133
10785
  console.log(chalk.dim(`Resuming session: ${session.name || sessionId}`));
10786
+ if (skipApprovals) {
10787
+ await apiRequest(baseUrl, `/sessions/${sessionId}`, {
10788
+ method: "PATCH",
10789
+ body: { toolApprovals: { "*": false } }
10790
+ });
10791
+ }
10134
10792
  const streamInfo = await getActiveStream(baseUrl, sessionId);
10135
10793
  if (streamInfo.hasActiveStream && streamInfo.streamId) {
10136
10794
  console.log(chalk.cyan(`
@@ -10141,7 +10799,7 @@ async function runChat(options) {
10141
10799
  try {
10142
10800
  const watchResponse = await fetch(`${baseUrl}/agents/${sessionId}/watch?streamId=${streamInfo.streamId}`);
10143
10801
  if (watchResponse.ok) {
10144
- await consumeSSEStream(watchResponse, { interactive: true, baseUrl, sessionId, readline: rl });
10802
+ await consumeSSEStream(watchResponse, { interactive: true, skipApprovals, baseUrl, sessionId, readline: rl });
10145
10803
  }
10146
10804
  } catch (err) {
10147
10805
  console.log(chalk.dim("Stream ended or connection lost."));
@@ -10150,13 +10808,17 @@ async function runChat(options) {
10150
10808
  }
10151
10809
  } else {
10152
10810
  const config = loadConfig(options.config, options.workingDir);
10811
+ const sessionBody = {
10812
+ name: options.name || "CLI Chat",
10813
+ workingDirectory: options.workingDir || config.resolvedWorkingDirectory,
10814
+ model: options.model || config.defaultModel
10815
+ };
10816
+ if (skipApprovals) {
10817
+ sessionBody.toolApprovals = { "*": false };
10818
+ }
10153
10819
  const response = await apiRequest(baseUrl, "/sessions", {
10154
10820
  method: "POST",
10155
- body: {
10156
- name: options.name || "CLI Chat",
10157
- workingDirectory: options.workingDir || config.resolvedWorkingDirectory,
10158
- model: options.model || config.defaultModel
10159
- }
10821
+ body: sessionBody
10160
10822
  });
10161
10823
  if (!response.ok) {
10162
10824
  const error = await response.json();
@@ -10172,6 +10834,9 @@ async function runChat(options) {
10172
10834
  console.log("");
10173
10835
  console.log(chalk.bold.cyan("\u{1F436} SparkECoder"));
10174
10836
  console.log(chalk.dim(`Working directory: ${workingDir}`));
10837
+ if (skipApprovals) {
10838
+ console.log(chalk.yellow("\u26A1 All tool approvals disabled (--dangerously-skip-approvals)"));
10839
+ }
10175
10840
  console.log(chalk.dim("Commands: /quit, /clear, /session, /tools, /help"));
10176
10841
  console.log("");
10177
10842
  const prompt = () => {
@@ -10200,6 +10865,7 @@ async function runChat(options) {
10200
10865
  console.log(chalk.dim(" /approve <id> - Approve pending tool call"));
10201
10866
  console.log(chalk.dim(" /reject <id> - Reject pending tool call"));
10202
10867
  console.log(chalk.dim(" /approvals - List pending approvals"));
10868
+ console.log(chalk.dim(" /allow <tool> - Always allow a tool (skip approval)"));
10203
10869
  console.log(chalk.dim(" /help, /? - Show this help"));
10204
10870
  prompt();
10205
10871
  return;
@@ -10211,7 +10877,7 @@ async function runChat(options) {
10211
10877
  try {
10212
10878
  const watchResponse = await fetch(`${baseUrl}/agents/${sessionId}/watch?streamId=${streamInfo.streamId}`);
10213
10879
  if (watchResponse.ok) {
10214
- await consumeSSEStream(watchResponse, { interactive: true, baseUrl, sessionId, readline: rl });
10880
+ await consumeSSEStream(watchResponse, { interactive: true, skipApprovals, baseUrl, sessionId, readline: rl });
10215
10881
  } else {
10216
10882
  console.log(chalk.dim("Failed to connect to stream."));
10217
10883
  }
@@ -10277,6 +10943,26 @@ async function runChat(options) {
10277
10943
  prompt();
10278
10944
  return;
10279
10945
  }
10946
+ if (cmd.startsWith("/allow ")) {
10947
+ const toolName = trimmed.slice(7).trim();
10948
+ if (!toolName) {
10949
+ console.log(chalk.yellow("Usage: /allow <toolName> (e.g., /allow bash)"));
10950
+ prompt();
10951
+ return;
10952
+ }
10953
+ const response = await apiRequest(baseUrl, `/sessions/${sessionId}`, {
10954
+ method: "PATCH",
10955
+ body: { toolApprovals: { [toolName]: false } }
10956
+ });
10957
+ if (response.ok) {
10958
+ console.log(chalk.green(`\u2713 ${toolName} will no longer require approval this session`));
10959
+ } else {
10960
+ const error = await response.json();
10961
+ console.log(chalk.red(`Failed: ${error.error || "Unknown error"}`));
10962
+ }
10963
+ prompt();
10964
+ return;
10965
+ }
10280
10966
  if (cmd === "/clear") {
10281
10967
  const response = await apiRequest(baseUrl, `/sessions/${sessionId}/clear`, { method: "POST" });
10282
10968
  if (response.ok) {
@@ -10353,7 +11039,7 @@ async function runChat(options) {
10353
11039
  const error = await response.json();
10354
11040
  throw new Error(error.error || `HTTP ${response.status}`);
10355
11041
  }
10356
- await consumeSSEStream(response, { interactive: true, baseUrl, sessionId, readline: rl });
11042
+ await consumeSSEStream(response, { interactive: true, skipApprovals, baseUrl, sessionId, readline: rl });
10357
11043
  } catch (error) {
10358
11044
  process.stdout.write("\r" + " ".repeat(20) + "\r");
10359
11045
  console.log(chalk.red(`
@@ -10382,7 +11068,7 @@ Unexpected error: ${outerError.message}`));
10382
11068
  }
10383
11069
  }
10384
11070
  var program = new Command();
10385
- program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version("0.1.0").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
11071
+ program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version("0.1.0").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
10386
11072
  await runChat(options);
10387
11073
  });
10388
11074
  program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
@@ -10422,9 +11108,100 @@ program.command("server").description("Start the SparkECoder server (API + Web U
10422
11108
  process.exit(1);
10423
11109
  }
10424
11110
  });
10425
- program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
11111
+ program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
10426
11112
  await runChat(options);
10427
11113
  });
11114
+ program.command("task").description("Run an autonomous task that completes without human interaction").requiredOption("--prompt <prompt>", "Task prompt describing what to do").requiredOption("--schema <schema>", "JSON Schema for the output (file path or inline JSON string)").option("--webhook <url>", "Webhook URL to receive task events").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-n, --name <name>", "Name for the task").option("--max-iterations <n>", "Maximum agent loop iterations", "50").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("--no-auto-start", "Do not auto-start server if not running").option("--wait", "Block and poll until task completes").option("-v, --verbose", "Enable verbose logging").action(async (options) => {
11115
+ await ensureDependencies({ quiet: true });
11116
+ loadApiKeysIntoEnv();
11117
+ const baseUrl = `http://${options.host}:${options.port}`;
11118
+ const running = await isServerRunning(baseUrl);
11119
+ if (!running) {
11120
+ if (options.autoStart === false) {
11121
+ console.error(chalk.red(`Server not running at ${baseUrl}`));
11122
+ process.exit(1);
11123
+ }
11124
+ const spinner2 = ora("Starting server...").start();
11125
+ try {
11126
+ await startServer({
11127
+ port: parseInt(options.port),
11128
+ host: options.host,
11129
+ configPath: options.config,
11130
+ workingDirectory: options.workingDir,
11131
+ quiet: true,
11132
+ webUI: false
11133
+ });
11134
+ spinner2.succeed("Server started");
11135
+ } catch (err) {
11136
+ spinner2.fail(chalk.red(`Failed to start server: ${err.message}`));
11137
+ process.exit(1);
11138
+ }
11139
+ }
11140
+ let outputSchema;
11141
+ try {
11142
+ const schemaStr = options.schema;
11143
+ if (existsSync16(schemaStr)) {
11144
+ outputSchema = JSON.parse(readFileSync6(schemaStr, "utf-8"));
11145
+ } else {
11146
+ outputSchema = JSON.parse(schemaStr);
11147
+ }
11148
+ } catch (err) {
11149
+ console.error(chalk.red(`Invalid schema: ${err.message}`));
11150
+ console.error(chalk.dim("Provide a path to a JSON Schema file or an inline JSON string"));
11151
+ process.exit(1);
11152
+ }
11153
+ const body = {
11154
+ prompt: options.prompt,
11155
+ outputSchema,
11156
+ maxIterations: parseInt(options.maxIterations)
11157
+ };
11158
+ if (options.webhook) body.webhookUrl = options.webhook;
11159
+ if (options.model) body.model = options.model;
11160
+ if (options.workingDir) body.workingDirectory = options.workingDir;
11161
+ if (options.name) body.name = options.name;
11162
+ const spinner = ora("Creating task...").start();
11163
+ const response = await apiRequest(baseUrl, "/tasks", {
11164
+ method: "POST",
11165
+ body
11166
+ });
11167
+ if (!response.ok) {
11168
+ const err = await response.json();
11169
+ spinner.fail(chalk.red(`Failed to create task: ${err.error || "Unknown error"}`));
11170
+ process.exit(1);
11171
+ }
11172
+ const { taskId } = await response.json();
11173
+ spinner.succeed(`Task created: ${chalk.cyan(taskId)}`);
11174
+ if (!options.wait) {
11175
+ console.log("");
11176
+ console.log(chalk.dim("Task is running in the background."));
11177
+ console.log(chalk.dim(`Check status: sparkecoder task-status ${taskId} --port ${options.port}`));
11178
+ console.log(chalk.dim(`Cancel: curl -X POST ${baseUrl}/tasks/${taskId}/cancel`));
11179
+ process.exit(0);
11180
+ }
11181
+ console.log(chalk.dim("\nWaiting for task to complete..."));
11182
+ const pollInterval = 3e3;
11183
+ while (true) {
11184
+ await new Promise((r) => setTimeout(r, pollInterval));
11185
+ const statusRes = await apiRequest(baseUrl, `/tasks/${taskId}`);
11186
+ if (!statusRes.ok) {
11187
+ console.error(chalk.red("Failed to check task status"));
11188
+ process.exit(1);
11189
+ }
11190
+ const status = await statusRes.json();
11191
+ if (status.status === "completed") {
11192
+ console.log(chalk.green(`
11193
+ Task completed in ${status.iterations} iteration(s).`));
11194
+ console.log(chalk.bold("Result:"));
11195
+ console.log(JSON.stringify(status.result, null, 2));
11196
+ process.exit(0);
11197
+ } else if (status.status === "failed") {
11198
+ console.error(chalk.red(`
11199
+ Task failed: ${status.error}`));
11200
+ process.exit(1);
11201
+ }
11202
+ process.stdout.write(chalk.dim("."));
11203
+ }
11204
+ });
10428
11205
  program.command("init").description("Create a sparkecoder.config.json file").option("-f, --force", "Overwrite existing config").option("-g, --global", "Create global config in app data directory").action((options) => {
10429
11206
  let configPath;
10430
11207
  let configLocation;