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/index.js CHANGED
@@ -8,590 +8,251 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
- // src/db/remote.ts
12
- function initRemoteDatabase(serverUrl, key) {
13
- remoteServerUrl = serverUrl.replace(/\/$/, "");
14
- authKey = key;
11
+ // src/config/types.ts
12
+ import { z } from "zod";
13
+ var ToolApprovalConfigSchema, SkillMetadataSchema, TaskConfigSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
14
+ var init_types = __esm({
15
+ "src/config/types.ts"() {
16
+ "use strict";
17
+ ToolApprovalConfigSchema = z.object({
18
+ bash: z.boolean().optional().default(true),
19
+ write_file: z.boolean().optional().default(false),
20
+ read_file: z.boolean().optional().default(false),
21
+ load_skill: z.boolean().optional().default(false),
22
+ todo: z.boolean().optional().default(false)
23
+ });
24
+ SkillMetadataSchema = z.object({
25
+ name: z.string(),
26
+ description: z.string(),
27
+ // Whether to always inject this skill into context (vs on-demand loading)
28
+ alwaysApply: z.boolean().optional().default(false),
29
+ // Glob patterns - auto-inject when working with matching files
30
+ globs: z.array(z.string()).optional().default([])
31
+ });
32
+ TaskConfigSchema = z.object({
33
+ enabled: z.boolean(),
34
+ outputSchema: z.record(z.string(), z.unknown()),
35
+ webhookUrl: z.string().url().optional(),
36
+ maxIterations: z.number().optional(),
37
+ status: z.enum(["running", "completed", "failed"]),
38
+ result: z.unknown().optional(),
39
+ error: z.string().optional(),
40
+ iterations: z.number().optional()
41
+ });
42
+ SessionConfigSchema = z.object({
43
+ toolApprovals: z.record(z.string(), z.boolean()).optional(),
44
+ approvalWebhook: z.string().url().optional(),
45
+ skillsDirectory: z.string().optional(),
46
+ maxContextChars: z.number().optional().default(2e5),
47
+ task: TaskConfigSchema.optional()
48
+ });
49
+ VectorGatewayConfigSchema = z.object({
50
+ // Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
51
+ redisUrl: z.string().optional(),
52
+ // HTTP URL for database operations (or use VECTOR_HTTP_URL env var)
53
+ httpUrl: z.string().optional(),
54
+ // Embedding model to use (default: text-embedding-3-small)
55
+ embeddingModel: z.string().default("gemini-embedding-001"),
56
+ // Custom namespace override (auto-generated from git remote if not set)
57
+ namespace: z.string().optional(),
58
+ // File patterns to include in indexing
59
+ include: z.array(z.string()).optional().default([
60
+ "**/*.ts",
61
+ "**/*.tsx",
62
+ "**/*.js",
63
+ "**/*.jsx",
64
+ "**/*.py",
65
+ "**/*.go",
66
+ "**/*.rs",
67
+ "**/*.java",
68
+ "**/*.md",
69
+ "**/*.mdx",
70
+ "**/*.txt"
71
+ ]),
72
+ // File patterns to exclude from indexing
73
+ exclude: z.array(z.string()).optional().default([
74
+ "**/node_modules/**",
75
+ "**/dist/**",
76
+ "**/build/**",
77
+ "**/.git/**",
78
+ "**/.next/**",
79
+ "**/*.min.js",
80
+ "**/*.bundle.js",
81
+ "**/pnpm-lock.yaml",
82
+ "**/package-lock.json",
83
+ "**/yarn.lock",
84
+ "**/.test-workspace/**",
85
+ "**/.semantic-test-workspace/**",
86
+ "**/.semantic-integration-test/**"
87
+ ])
88
+ }).optional();
89
+ RemoteServerConfigSchema = z.object({
90
+ // URL of the remote server (e.g., https://agent.sparkecode.com)
91
+ url: z.string().url().optional(),
92
+ // Auth key for the remote server (auto-generated on first use if not set)
93
+ // Can also be set via SPARKECODER_AUTH_KEY env var
94
+ authKey: z.string().optional()
95
+ }).optional();
96
+ SparkcoderConfigSchema = z.object({
97
+ // Default model to use (Vercel AI Gateway format)
98
+ defaultModel: z.string().default("anthropic/claude-opus-4-6"),
99
+ // Working directory for file operations
100
+ workingDirectory: z.string().optional(),
101
+ // Tool approval settings
102
+ toolApprovals: ToolApprovalConfigSchema.optional().default({}),
103
+ // Approval webhook URL (called when approval is needed)
104
+ approvalWebhook: z.string().url().optional(),
105
+ // Skills configuration
106
+ skills: z.object({
107
+ // Directory containing skill files
108
+ directory: z.string().optional().default("./skills"),
109
+ // Additional skill directories to include
110
+ additionalDirectories: z.array(z.string()).optional().default([])
111
+ }).optional().default({}),
112
+ // Context management
113
+ context: z.object({
114
+ // Maximum context size before summarization (in characters)
115
+ maxChars: z.number().optional().default(2e5),
116
+ // Enable automatic summarization
117
+ autoSummarize: z.boolean().optional().default(true),
118
+ // Number of recent messages to keep after summarization
119
+ keepRecentMessages: z.number().optional().default(10)
120
+ }).optional().default({}),
121
+ // Server configuration
122
+ server: z.object({
123
+ port: z.number().default(3141),
124
+ host: z.string().default("127.0.0.1"),
125
+ // Public URL for web UI to connect to API (for Docker/remote access)
126
+ // If not set, defaults to http://{host}:{port}
127
+ publicUrl: z.string().url().optional()
128
+ }).default({ port: 3141, host: "127.0.0.1" }),
129
+ // Database path (used for local SQLite - ignored if remoteServer is configured)
130
+ databasePath: z.string().optional().default("./sparkecoder.db"),
131
+ // Remote server configuration (for centralized storage)
132
+ // If configured, uses remote MongoDB instead of local SQLite
133
+ remoteServer: RemoteServerConfigSchema,
134
+ // Vector Gateway configuration for semantic search
135
+ vectorGateway: VectorGatewayConfigSchema
136
+ });
137
+ }
138
+ });
139
+
140
+ // src/config/index.ts
141
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
142
+ import { resolve, dirname, join } from "path";
143
+ import { homedir, platform } from "os";
144
+ function discoverSkillDirectories(workingDir) {
145
+ const alwaysLoadedDirs = [];
146
+ const onDemandDirs = [];
147
+ const allDirectories = [];
148
+ let agentsMdPath = null;
149
+ const sparkRulesDir = join(workingDir, ".sparkecoder", "rules");
150
+ if (existsSync(sparkRulesDir)) {
151
+ alwaysLoadedDirs.push({ path: sparkRulesDir, priority: 1 });
152
+ allDirectories.push(sparkRulesDir);
153
+ }
154
+ const sparkSkillsDir = join(workingDir, ".sparkecoder", "skills");
155
+ if (existsSync(sparkSkillsDir)) {
156
+ onDemandDirs.push({ path: sparkSkillsDir, priority: 2 });
157
+ allDirectories.push(sparkSkillsDir);
158
+ }
159
+ const cursorRulesDir = join(workingDir, ".cursor", "rules");
160
+ if (existsSync(cursorRulesDir)) {
161
+ onDemandDirs.push({ path: cursorRulesDir, priority: 3 });
162
+ allDirectories.push(cursorRulesDir);
163
+ }
164
+ const claudeSkillsDir = join(workingDir, ".claude", "skills");
165
+ if (existsSync(claudeSkillsDir)) {
166
+ onDemandDirs.push({ path: claudeSkillsDir, priority: 4 });
167
+ allDirectories.push(claudeSkillsDir);
168
+ }
169
+ const legacySkillsDir = join(workingDir, "skills");
170
+ if (existsSync(legacySkillsDir)) {
171
+ onDemandDirs.push({ path: legacySkillsDir, priority: 5 });
172
+ allDirectories.push(legacySkillsDir);
173
+ }
174
+ const agentsMd = join(workingDir, "AGENTS.md");
175
+ if (existsSync(agentsMd)) {
176
+ agentsMdPath = agentsMd;
177
+ }
178
+ const baseDir = dirname(import.meta.url.replace("file://", ""));
179
+ const builtInCandidates = [
180
+ resolve(baseDir, "../skills/default"),
181
+ // dev: src/config → src/skills/default
182
+ resolve(baseDir, "./skills/default")
183
+ // prod: dist/ → dist/skills/default
184
+ ];
185
+ const builtInSkillsDir = builtInCandidates.find((p) => existsSync(p));
186
+ if (builtInSkillsDir) {
187
+ onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
188
+ allDirectories.push(builtInSkillsDir);
189
+ }
190
+ return {
191
+ alwaysLoadedDirs,
192
+ onDemandDirs,
193
+ agentsMdPath,
194
+ allDirectories
195
+ };
15
196
  }
16
- function closeRemoteDatabase() {
17
- remoteServerUrl = null;
18
- authKey = null;
197
+ function getAppDataDirectory() {
198
+ const appName = "sparkecoder";
199
+ switch (platform()) {
200
+ case "darwin":
201
+ return join(homedir(), "Library", "Application Support", appName);
202
+ case "win32":
203
+ return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), appName);
204
+ default:
205
+ return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
206
+ }
19
207
  }
20
- function parseDates(obj) {
21
- if (obj === null || obj === void 0) return obj;
22
- if (Array.isArray(obj)) return obj.map(parseDates);
23
- if (typeof obj !== "object" || obj instanceof Date) return obj;
24
- const result = { ...obj };
25
- for (const key of Object.keys(result)) {
26
- if (MODEL_MESSAGE_FIELDS.includes(key)) {
27
- continue;
208
+ function ensureAppDataDirectory() {
209
+ const dir = getAppDataDirectory();
210
+ if (!existsSync(dir)) {
211
+ mkdirSync(dir, { recursive: true });
212
+ }
213
+ return dir;
214
+ }
215
+ function findConfigFile(startDir) {
216
+ let currentDir = startDir;
217
+ while (currentDir !== dirname(currentDir)) {
218
+ for (const fileName of CONFIG_FILE_NAMES) {
219
+ const configPath = resolve(currentDir, fileName);
220
+ if (existsSync(configPath)) {
221
+ return configPath;
222
+ }
28
223
  }
29
- if (DATE_FIELDS.includes(key) && typeof result[key] === "string") {
30
- result[key] = new Date(result[key]);
31
- } else if (typeof result[key] === "object") {
32
- result[key] = parseDates(result[key]);
224
+ currentDir = dirname(currentDir);
225
+ }
226
+ const appDataDir = getAppDataDirectory();
227
+ for (const fileName of CONFIG_FILE_NAMES) {
228
+ const configPath = join(appDataDir, fileName);
229
+ if (existsSync(configPath)) {
230
+ return configPath;
33
231
  }
34
232
  }
35
- return result;
233
+ return null;
36
234
  }
37
- async function api(path, options = {}) {
38
- if (!remoteServerUrl || !authKey) {
39
- throw new Error("Remote database not initialized");
40
- }
41
- const url = `${remoteServerUrl}/db${path}`;
42
- const init = {
43
- method: options.method || "GET",
44
- headers: {
45
- "Content-Type": "application/json",
46
- "Authorization": `Bearer ${authKey}`
235
+ function loadConfig(configPath, workingDirectory) {
236
+ const cwd = workingDirectory || process.cwd();
237
+ let rawConfig = {};
238
+ let configDir = cwd;
239
+ if (configPath) {
240
+ if (!existsSync(configPath)) {
241
+ throw new Error(`Config file not found: ${configPath}`);
242
+ }
243
+ const content = readFileSync(configPath, "utf-8");
244
+ rawConfig = JSON.parse(content);
245
+ configDir = dirname(resolve(configPath));
246
+ } else {
247
+ const foundPath = findConfigFile(cwd);
248
+ if (foundPath) {
249
+ const content = readFileSync(foundPath, "utf-8");
250
+ rawConfig = JSON.parse(content);
251
+ configDir = dirname(foundPath);
47
252
  }
48
- };
49
- if (options.body) {
50
- init.body = JSON.stringify(options.body);
51
253
  }
52
- const response = await fetch(url, init);
53
- if (!response.ok) {
54
- const error = await response.json().catch(() => ({ error: "Unknown error" }));
55
- throw new Error(error.error || `HTTP ${response.status}`);
56
- }
57
- const text = await response.text();
58
- if (!text || text === "null") {
59
- return null;
60
- }
61
- const parsed = JSON.parse(text);
62
- if (options.skipParseDates) {
63
- return parsed;
64
- }
65
- return parseDates(parsed);
66
- }
67
- var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexStatusQueries;
68
- var init_remote = __esm({
69
- "src/db/remote.ts"() {
70
- "use strict";
71
- remoteServerUrl = null;
72
- authKey = null;
73
- DATE_FIELDS = ["createdAt", "updatedAt", "startedAt", "completedAt", "stoppedAt", "finishedAt", "loadedAt", "indexedAt", "lastFullIndex", "lastIncrementalIndex"];
74
- MODEL_MESSAGE_FIELDS = ["modelMessage", "modelMessages"];
75
- remoteSessionQueries = {
76
- create(data) {
77
- return api("/sessions", { method: "POST", body: data });
78
- },
79
- getById(id) {
80
- return api(`/sessions/${id}`).catch(() => void 0);
81
- },
82
- list(limit = 50, offset = 0) {
83
- return api(`/sessions?limit=${limit}&offset=${offset}`);
84
- },
85
- updateStatus(id, status) {
86
- return api(`/sessions/${id}`, { method: "PATCH", body: { status } });
87
- },
88
- updateModel(id, model) {
89
- return api(`/sessions/${id}`, { method: "PATCH", body: { model } });
90
- },
91
- update(id, updates) {
92
- return api(`/sessions/${id}`, { method: "PATCH", body: updates });
93
- },
94
- delete(id) {
95
- return api(`/sessions/${id}`, { method: "DELETE" }).then((r) => r?.success ?? false);
96
- }
97
- };
98
- remoteMessageQueries = {
99
- async getNextSequence(sessionId) {
100
- const result = await api(`/messages/session/${sessionId}/next-sequence`);
101
- return result.nextSequence;
102
- },
103
- create(sessionId, modelMessage) {
104
- return api("/messages", { method: "POST", body: { sessionId, modelMessage } });
105
- },
106
- addMany(sessionId, modelMessages) {
107
- return api("/messages/batch", { method: "POST", body: { sessionId, modelMessages } });
108
- },
109
- getBySession(sessionId) {
110
- return api(`/messages/session/${sessionId}`);
111
- },
112
- getModelMessages(sessionId) {
113
- return api(`/messages/session/${sessionId}/model-messages`, { skipParseDates: true });
114
- },
115
- async getRecentBySession(sessionId, limit = 50) {
116
- const messages = await api(`/messages/session/${sessionId}`);
117
- return messages.slice(-limit);
118
- },
119
- async countBySession(sessionId) {
120
- const result = await api(`/messages/session/${sessionId}/count`);
121
- return result.count;
122
- },
123
- async deleteBySession(sessionId) {
124
- const result = await api(`/messages/session/${sessionId}`, { method: "DELETE" });
125
- return result.deleted;
126
- },
127
- async deleteFromSequence(sessionId, fromSequence) {
128
- const result = await api(
129
- `/messages/session/${sessionId}/from-sequence/${fromSequence}`,
130
- { method: "DELETE" }
131
- );
132
- return result.deleted;
133
- }
134
- };
135
- remoteToolExecutionQueries = {
136
- create(data) {
137
- return api("/tool-executions", { method: "POST", body: data });
138
- },
139
- getById(id) {
140
- return api(`/tool-executions/${id}`).catch(() => void 0);
141
- },
142
- getByToolCallId(toolCallId) {
143
- return api(`/tool-executions/by-tool-call-id/${toolCallId}`).catch(() => void 0);
144
- },
145
- getPendingApprovals(sessionId) {
146
- return api(`/tool-executions/session/${sessionId}/pending`);
147
- },
148
- approve(id) {
149
- return api(`/tool-executions/${id}`, { method: "PATCH", body: { status: "approved" } });
150
- },
151
- reject(id) {
152
- return api(`/tool-executions/${id}`, { method: "PATCH", body: { status: "rejected" } });
153
- },
154
- complete(id, output, error) {
155
- return api(`/tool-executions/${id}`, {
156
- method: "PATCH",
157
- body: { status: error ? "error" : "completed", output, error }
158
- });
159
- },
160
- getBySession(sessionId) {
161
- return api(`/tool-executions/session/${sessionId}`);
162
- },
163
- async deleteAfterTime(sessionId, afterTime) {
164
- const timestamp = afterTime instanceof Date ? afterTime.getTime() : new Date(afterTime).getTime();
165
- const result = await api(
166
- `/tool-executions/session/${sessionId}/after/${timestamp}`,
167
- { method: "DELETE" }
168
- );
169
- return result.deleted;
170
- }
171
- };
172
- remoteTodoQueries = {
173
- create(data) {
174
- return api("/todos", { method: "POST", body: data });
175
- },
176
- createMany(sessionId, items) {
177
- return api("/todos/batch", { method: "POST", body: { sessionId, items } });
178
- },
179
- getBySession(sessionId) {
180
- return api(`/todos/session/${sessionId}`);
181
- },
182
- updateStatus(id, status) {
183
- return api(`/todos/${id}`, { method: "PATCH", body: { status } });
184
- },
185
- async delete(id) {
186
- const result = await api(`/todos/${id}`, { method: "DELETE" });
187
- return result?.success ?? false;
188
- },
189
- async clearSession(sessionId) {
190
- const result = await api(`/todos/session/${sessionId}`, { method: "DELETE" });
191
- return result.deleted;
192
- }
193
- };
194
- remoteSkillQueries = {
195
- load(sessionId, skillName) {
196
- return api("/skills", { method: "POST", body: { sessionId, skillName } });
197
- },
198
- getBySession(sessionId) {
199
- return api(`/skills/session/${sessionId}`);
200
- },
201
- async isLoaded(sessionId, skillName) {
202
- const result = await api(`/skills/session/${sessionId}/is-loaded/${skillName}`);
203
- return result.isLoaded;
204
- }
205
- };
206
- remoteActiveStreamQueries = {
207
- create(sessionId, streamId) {
208
- return api("/streams", { method: "POST", body: { sessionId, streamId } });
209
- },
210
- getBySessionId(sessionId) {
211
- return api(`/streams/session/${sessionId}`).then((r) => r ?? void 0);
212
- },
213
- getByStreamId(streamId) {
214
- return api(`/streams/by-stream-id/${streamId}`).catch(() => void 0);
215
- },
216
- finish(streamId) {
217
- return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "finished" } });
218
- },
219
- markError(streamId) {
220
- return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "error" } });
221
- },
222
- async deleteBySession(sessionId) {
223
- const result = await api(`/streams/session/${sessionId}`, { method: "DELETE" });
224
- return result.deleted;
225
- }
226
- };
227
- remoteCheckpointQueries = {
228
- create(data) {
229
- return api("/checkpoints", { method: "POST", body: data });
230
- },
231
- getById(id) {
232
- return api(`/checkpoints/${id}`).catch(() => void 0);
233
- },
234
- getBySession(sessionId) {
235
- return api(`/checkpoints/session/${sessionId}`);
236
- },
237
- getByMessageSequence(sessionId, messageSequence) {
238
- return api(`/checkpoints/session/${sessionId}/by-sequence/${messageSequence}`).then((r) => r ?? void 0);
239
- },
240
- getLatest(sessionId) {
241
- return api(`/checkpoints/session/${sessionId}/latest`).then((r) => r ?? void 0);
242
- },
243
- async deleteAfterSequence(sessionId, messageSequence) {
244
- const result = await api(
245
- `/checkpoints/session/${sessionId}/after-sequence/${messageSequence}`,
246
- { method: "DELETE" }
247
- );
248
- return result.deleted;
249
- },
250
- async deleteBySession(sessionId) {
251
- const result = await api(`/checkpoints/session/${sessionId}`, { method: "DELETE" });
252
- return result.deleted;
253
- }
254
- };
255
- remoteFileBackupQueries = {
256
- create(data) {
257
- return api("/file-backups", { method: "POST", body: data });
258
- },
259
- getByCheckpoint(checkpointId) {
260
- return api(`/file-backups/checkpoint/${checkpointId}`);
261
- },
262
- getBySession(sessionId) {
263
- return api(`/file-backups/session/${sessionId}`);
264
- },
265
- getFromSequence(sessionId, messageSequence) {
266
- return api(`/file-backups/session/${sessionId}/from-sequence/${messageSequence}`);
267
- },
268
- async hasBackup(checkpointId, filePath) {
269
- const result = await api(
270
- `/file-backups/checkpoint/${checkpointId}/has-backup/${encodeURIComponent(filePath)}`
271
- );
272
- return result.hasBackup;
273
- },
274
- async deleteBySession(sessionId) {
275
- const result = await api(`/file-backups/session/${sessionId}`, { method: "DELETE" });
276
- return result.deleted;
277
- }
278
- };
279
- remoteSubagentQueries = {
280
- create(data) {
281
- return api("/subagents", { method: "POST", body: data });
282
- },
283
- getById(id) {
284
- return api(`/subagents/${id}`).catch(() => void 0);
285
- },
286
- getByToolCallId(toolCallId) {
287
- return api(`/subagents/by-tool-call-id/${toolCallId}`).catch(() => void 0);
288
- },
289
- getBySession(sessionId) {
290
- return api(`/subagents/session/${sessionId}`);
291
- },
292
- addStep(id, step) {
293
- return api(`/subagents/${id}/add-step`, { method: "POST", body: { step } }).catch(() => void 0);
294
- },
295
- complete(id, result) {
296
- return api(`/subagents/${id}`, { method: "PATCH", body: { status: "completed", result } }).catch(() => void 0);
297
- },
298
- markError(id, error) {
299
- return api(`/subagents/${id}`, { method: "PATCH", body: { status: "error", error } }).catch(() => void 0);
300
- },
301
- cancel(id) {
302
- return api(`/subagents/${id}`, { method: "PATCH", body: { status: "cancelled" } }).catch(() => void 0);
303
- },
304
- async deleteBySession(sessionId) {
305
- const result = await api(`/subagents/session/${sessionId}`, { method: "DELETE" });
306
- return result.deleted;
307
- }
308
- };
309
- remoteIndexStatusQueries = {
310
- upsert(_db, data) {
311
- return api("/index-status", {
312
- method: "POST",
313
- body: {
314
- ...data,
315
- lastFullIndex: data.lastFullIndex?.toISOString(),
316
- lastIncrementalIndex: data.lastIncrementalIndex?.toISOString()
317
- }
318
- });
319
- },
320
- get(_db, namespace) {
321
- return api(`/index-status/namespace/${namespace}`).then((r) => r ?? void 0);
322
- },
323
- async delete(_db, namespace) {
324
- const result = await api(`/index-status/namespace/${namespace}`, { method: "DELETE" });
325
- return result?.success ?? false;
326
- },
327
- list(_db) {
328
- return api("/index-status");
329
- }
330
- };
331
- }
332
- });
333
-
334
- // src/db/index.ts
335
- function initDatabase(config) {
336
- initRemoteDatabase(config.url, config.authKey);
337
- initialized = true;
338
- }
339
- function getDb() {
340
- if (!initialized) {
341
- throw new Error("Database not initialized. Call initDatabase first.");
342
- }
343
- return {};
344
- }
345
- function closeDatabase() {
346
- closeRemoteDatabase();
347
- initialized = false;
348
- }
349
- var initialized, sessionQueries, messageQueries, toolExecutionQueries, todoQueries, skillQueries, activeStreamQueries, checkpointQueries, fileBackupQueries, subagentQueries, indexStatusQueries;
350
- var init_db = __esm({
351
- "src/db/index.ts"() {
352
- "use strict";
353
- init_remote();
354
- initialized = false;
355
- sessionQueries = remoteSessionQueries;
356
- messageQueries = remoteMessageQueries;
357
- toolExecutionQueries = remoteToolExecutionQueries;
358
- todoQueries = remoteTodoQueries;
359
- skillQueries = remoteSkillQueries;
360
- activeStreamQueries = remoteActiveStreamQueries;
361
- checkpointQueries = remoteCheckpointQueries;
362
- fileBackupQueries = remoteFileBackupQueries;
363
- subagentQueries = remoteSubagentQueries;
364
- indexStatusQueries = remoteIndexStatusQueries;
365
- }
366
- });
367
-
368
- // src/config/types.ts
369
- import { z } from "zod";
370
- var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
371
- var init_types = __esm({
372
- "src/config/types.ts"() {
373
- "use strict";
374
- ToolApprovalConfigSchema = z.object({
375
- bash: z.boolean().optional().default(true),
376
- write_file: z.boolean().optional().default(false),
377
- read_file: z.boolean().optional().default(false),
378
- load_skill: z.boolean().optional().default(false),
379
- todo: z.boolean().optional().default(false)
380
- });
381
- SkillMetadataSchema = z.object({
382
- name: z.string(),
383
- description: z.string(),
384
- // Whether to always inject this skill into context (vs on-demand loading)
385
- alwaysApply: z.boolean().optional().default(false),
386
- // Glob patterns - auto-inject when working with matching files
387
- globs: z.array(z.string()).optional().default([])
388
- });
389
- SessionConfigSchema = z.object({
390
- toolApprovals: z.record(z.string(), z.boolean()).optional(),
391
- approvalWebhook: z.string().url().optional(),
392
- skillsDirectory: z.string().optional(),
393
- maxContextChars: z.number().optional().default(2e5)
394
- });
395
- VectorGatewayConfigSchema = z.object({
396
- // Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
397
- redisUrl: z.string().optional(),
398
- // HTTP URL for database operations (or use VECTOR_HTTP_URL env var)
399
- httpUrl: z.string().optional(),
400
- // Embedding model to use (default: text-embedding-3-small)
401
- embeddingModel: z.string().default("gemini-embedding-001"),
402
- // Custom namespace override (auto-generated from git remote if not set)
403
- namespace: z.string().optional(),
404
- // File patterns to include in indexing
405
- include: z.array(z.string()).optional().default([
406
- "**/*.ts",
407
- "**/*.tsx",
408
- "**/*.js",
409
- "**/*.jsx",
410
- "**/*.py",
411
- "**/*.go",
412
- "**/*.rs",
413
- "**/*.java",
414
- "**/*.md",
415
- "**/*.mdx",
416
- "**/*.txt"
417
- ]),
418
- // File patterns to exclude from indexing
419
- exclude: z.array(z.string()).optional().default([
420
- "**/node_modules/**",
421
- "**/dist/**",
422
- "**/build/**",
423
- "**/.git/**",
424
- "**/.next/**",
425
- "**/*.min.js",
426
- "**/*.bundle.js",
427
- "**/pnpm-lock.yaml",
428
- "**/package-lock.json",
429
- "**/yarn.lock",
430
- "**/.test-workspace/**",
431
- "**/.semantic-test-workspace/**",
432
- "**/.semantic-integration-test/**"
433
- ])
434
- }).optional();
435
- RemoteServerConfigSchema = z.object({
436
- // URL of the remote server (e.g., https://agent.sparkecode.com)
437
- url: z.string().url().optional(),
438
- // Auth key for the remote server (auto-generated on first use if not set)
439
- // Can also be set via SPARKECODER_AUTH_KEY env var
440
- authKey: z.string().optional()
441
- }).optional();
442
- SparkcoderConfigSchema = z.object({
443
- // Default model to use (Vercel AI Gateway format)
444
- defaultModel: z.string().default("anthropic/claude-opus-4-6"),
445
- // Working directory for file operations
446
- workingDirectory: z.string().optional(),
447
- // Tool approval settings
448
- toolApprovals: ToolApprovalConfigSchema.optional().default({}),
449
- // Approval webhook URL (called when approval is needed)
450
- approvalWebhook: z.string().url().optional(),
451
- // Skills configuration
452
- skills: z.object({
453
- // Directory containing skill files
454
- directory: z.string().optional().default("./skills"),
455
- // Additional skill directories to include
456
- additionalDirectories: z.array(z.string()).optional().default([])
457
- }).optional().default({}),
458
- // Context management
459
- context: z.object({
460
- // Maximum context size before summarization (in characters)
461
- maxChars: z.number().optional().default(2e5),
462
- // Enable automatic summarization
463
- autoSummarize: z.boolean().optional().default(true),
464
- // Number of recent messages to keep after summarization
465
- keepRecentMessages: z.number().optional().default(10)
466
- }).optional().default({}),
467
- // Server configuration
468
- server: z.object({
469
- port: z.number().default(3141),
470
- host: z.string().default("127.0.0.1"),
471
- // Public URL for web UI to connect to API (for Docker/remote access)
472
- // If not set, defaults to http://{host}:{port}
473
- publicUrl: z.string().url().optional()
474
- }).default({ port: 3141, host: "127.0.0.1" }),
475
- // Database path (used for local SQLite - ignored if remoteServer is configured)
476
- databasePath: z.string().optional().default("./sparkecoder.db"),
477
- // Remote server configuration (for centralized storage)
478
- // If configured, uses remote MongoDB instead of local SQLite
479
- remoteServer: RemoteServerConfigSchema,
480
- // Vector Gateway configuration for semantic search
481
- vectorGateway: VectorGatewayConfigSchema
482
- });
483
- }
484
- });
485
-
486
- // src/config/index.ts
487
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
488
- import { resolve, dirname, join } from "path";
489
- import { homedir, platform } from "os";
490
- function discoverSkillDirectories(workingDir) {
491
- const alwaysLoadedDirs = [];
492
- const onDemandDirs = [];
493
- const allDirectories = [];
494
- let agentsMdPath = null;
495
- const sparkRulesDir = join(workingDir, ".sparkecoder", "rules");
496
- if (existsSync(sparkRulesDir)) {
497
- alwaysLoadedDirs.push({ path: sparkRulesDir, priority: 1 });
498
- allDirectories.push(sparkRulesDir);
499
- }
500
- const sparkSkillsDir = join(workingDir, ".sparkecoder", "skills");
501
- if (existsSync(sparkSkillsDir)) {
502
- onDemandDirs.push({ path: sparkSkillsDir, priority: 2 });
503
- allDirectories.push(sparkSkillsDir);
504
- }
505
- const cursorRulesDir = join(workingDir, ".cursor", "rules");
506
- if (existsSync(cursorRulesDir)) {
507
- onDemandDirs.push({ path: cursorRulesDir, priority: 3 });
508
- allDirectories.push(cursorRulesDir);
509
- }
510
- const claudeSkillsDir = join(workingDir, ".claude", "skills");
511
- if (existsSync(claudeSkillsDir)) {
512
- onDemandDirs.push({ path: claudeSkillsDir, priority: 4 });
513
- allDirectories.push(claudeSkillsDir);
514
- }
515
- const legacySkillsDir = join(workingDir, "skills");
516
- if (existsSync(legacySkillsDir)) {
517
- onDemandDirs.push({ path: legacySkillsDir, priority: 5 });
518
- allDirectories.push(legacySkillsDir);
519
- }
520
- const agentsMd = join(workingDir, "AGENTS.md");
521
- if (existsSync(agentsMd)) {
522
- agentsMdPath = agentsMd;
523
- }
524
- const builtInSkillsDir = resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default");
525
- if (existsSync(builtInSkillsDir)) {
526
- onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
527
- allDirectories.push(builtInSkillsDir);
528
- }
529
- return {
530
- alwaysLoadedDirs,
531
- onDemandDirs,
532
- agentsMdPath,
533
- allDirectories
534
- };
535
- }
536
- function getAppDataDirectory() {
537
- const appName = "sparkecoder";
538
- switch (platform()) {
539
- case "darwin":
540
- return join(homedir(), "Library", "Application Support", appName);
541
- case "win32":
542
- return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), appName);
543
- default:
544
- return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
545
- }
546
- }
547
- function ensureAppDataDirectory() {
548
- const dir = getAppDataDirectory();
549
- if (!existsSync(dir)) {
550
- mkdirSync(dir, { recursive: true });
551
- }
552
- return dir;
553
- }
554
- function findConfigFile(startDir) {
555
- let currentDir = startDir;
556
- while (currentDir !== dirname(currentDir)) {
557
- for (const fileName of CONFIG_FILE_NAMES) {
558
- const configPath = resolve(currentDir, fileName);
559
- if (existsSync(configPath)) {
560
- return configPath;
561
- }
562
- }
563
- currentDir = dirname(currentDir);
564
- }
565
- const appDataDir = getAppDataDirectory();
566
- for (const fileName of CONFIG_FILE_NAMES) {
567
- const configPath = join(appDataDir, fileName);
568
- if (existsSync(configPath)) {
569
- return configPath;
570
- }
571
- }
572
- return null;
573
- }
574
- function loadConfig(configPath, workingDirectory) {
575
- const cwd = workingDirectory || process.cwd();
576
- let rawConfig = {};
577
- let configDir = cwd;
578
- if (configPath) {
579
- if (!existsSync(configPath)) {
580
- throw new Error(`Config file not found: ${configPath}`);
581
- }
582
- const content = readFileSync(configPath, "utf-8");
583
- rawConfig = JSON.parse(content);
584
- configDir = dirname(resolve(configPath));
585
- } else {
586
- const foundPath = findConfigFile(cwd);
587
- if (foundPath) {
588
- const content = readFileSync(foundPath, "utf-8");
589
- rawConfig = JSON.parse(content);
590
- configDir = dirname(foundPath);
591
- }
592
- }
593
- if (process.env.SPARKECODER_MODEL) {
594
- rawConfig.defaultModel = process.env.SPARKECODER_MODEL;
254
+ if (process.env.SPARKECODER_MODEL) {
255
+ rawConfig.defaultModel = process.env.SPARKECODER_MODEL;
595
256
  }
596
257
  if (process.env.SPARKECODER_PORT) {
597
258
  rawConfig.server = {
@@ -691,6 +352,9 @@ function getConfig() {
691
352
  }
692
353
  function requiresApproval(toolName, sessionConfig) {
693
354
  const config = getConfig();
355
+ if (sessionConfig?.toolApprovals?.["*"] !== void 0) {
356
+ return sessionConfig.toolApprovals["*"];
357
+ }
694
358
  if (sessionConfig?.toolApprovals?.[toolName] !== void 0) {
695
359
  return sessionConfig.toolApprovals[toolName];
696
360
  }
@@ -814,48 +478,405 @@ function getApiKeyStatus() {
814
478
  } else {
815
479
  source = "env";
816
480
  }
817
- value = envValue;
818
- } else if (storedValue) {
819
- source = "storage";
820
- value = storedValue;
821
- }
822
- return {
823
- provider,
824
- envVar,
825
- configured: !!value,
826
- source,
827
- maskedKey: value ? maskApiKey(value) : null
481
+ value = envValue;
482
+ } else if (storedValue) {
483
+ source = "storage";
484
+ value = storedValue;
485
+ }
486
+ return {
487
+ provider,
488
+ envVar,
489
+ configured: !!value,
490
+ source,
491
+ maskedKey: value ? maskApiKey(value) : null
492
+ };
493
+ });
494
+ }
495
+ function maskApiKey(key) {
496
+ if (key.length <= 12) {
497
+ return "****" + key.slice(-4);
498
+ }
499
+ return key.slice(0, 4) + "..." + key.slice(-4);
500
+ }
501
+ var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
502
+ var init_config = __esm({
503
+ "src/config/index.ts"() {
504
+ "use strict";
505
+ init_types();
506
+ init_types();
507
+ CONFIG_FILE_NAMES = [
508
+ "sparkecoder.config.json",
509
+ "sparkecoder.json",
510
+ ".sparkecoder.json"
511
+ ];
512
+ cachedConfig = null;
513
+ AUTH_KEY_FILE = "auth-key.json";
514
+ API_KEYS_FILE = "api-keys.json";
515
+ PROVIDER_ENV_MAP = {
516
+ anthropic: "ANTHROPIC_API_KEY",
517
+ openai: "OPENAI_API_KEY",
518
+ google: "GOOGLE_GENERATIVE_AI_API_KEY",
519
+ xai: "XAI_API_KEY",
520
+ "ai-gateway": "AI_GATEWAY_API_KEY"
521
+ };
522
+ SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
523
+ }
524
+ });
525
+
526
+ // src/db/remote.ts
527
+ function initRemoteDatabase(serverUrl, key) {
528
+ remoteServerUrl = serverUrl.replace(/\/$/, "");
529
+ authKey = key;
530
+ }
531
+ function closeRemoteDatabase() {
532
+ remoteServerUrl = null;
533
+ authKey = null;
534
+ }
535
+ function parseDates(obj) {
536
+ if (obj === null || obj === void 0) return obj;
537
+ if (Array.isArray(obj)) return obj.map(parseDates);
538
+ if (typeof obj !== "object" || obj instanceof Date) return obj;
539
+ const result = { ...obj };
540
+ for (const key of Object.keys(result)) {
541
+ if (MODEL_MESSAGE_FIELDS.includes(key)) {
542
+ continue;
543
+ }
544
+ if (DATE_FIELDS.includes(key) && typeof result[key] === "string") {
545
+ result[key] = new Date(result[key]);
546
+ } else if (typeof result[key] === "object") {
547
+ result[key] = parseDates(result[key]);
548
+ }
549
+ }
550
+ return result;
551
+ }
552
+ async function api(path, options = {}) {
553
+ if (!remoteServerUrl || !authKey) {
554
+ throw new Error("Remote database not initialized");
555
+ }
556
+ const url = `${remoteServerUrl}/db${path}`;
557
+ const init = {
558
+ method: options.method || "GET",
559
+ headers: {
560
+ "Content-Type": "application/json",
561
+ "Authorization": `Bearer ${authKey}`
562
+ }
563
+ };
564
+ if (options.body) {
565
+ init.body = JSON.stringify(options.body);
566
+ }
567
+ const response = await fetch(url, init);
568
+ if (!response.ok) {
569
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
570
+ throw new Error(error.error || `HTTP ${response.status}`);
571
+ }
572
+ const text = await response.text();
573
+ if (!text || text === "null") {
574
+ return null;
575
+ }
576
+ const parsed = JSON.parse(text);
577
+ if (options.skipParseDates) {
578
+ return parsed;
579
+ }
580
+ return parseDates(parsed);
581
+ }
582
+ var remoteServerUrl, authKey, DATE_FIELDS, MODEL_MESSAGE_FIELDS, remoteSessionQueries, remoteMessageQueries, remoteToolExecutionQueries, remoteTodoQueries, remoteSkillQueries, remoteActiveStreamQueries, remoteCheckpointQueries, remoteFileBackupQueries, remoteSubagentQueries, remoteIndexStatusQueries;
583
+ var init_remote = __esm({
584
+ "src/db/remote.ts"() {
585
+ "use strict";
586
+ remoteServerUrl = null;
587
+ authKey = null;
588
+ DATE_FIELDS = ["createdAt", "updatedAt", "startedAt", "completedAt", "stoppedAt", "finishedAt", "loadedAt", "indexedAt", "lastFullIndex", "lastIncrementalIndex"];
589
+ MODEL_MESSAGE_FIELDS = ["modelMessage", "modelMessages"];
590
+ remoteSessionQueries = {
591
+ create(data) {
592
+ return api("/sessions", { method: "POST", body: data });
593
+ },
594
+ getById(id) {
595
+ return api(`/sessions/${id}`).catch(() => void 0);
596
+ },
597
+ list(limit = 50, offset = 0) {
598
+ return api(`/sessions?limit=${limit}&offset=${offset}`);
599
+ },
600
+ updateStatus(id, status) {
601
+ return api(`/sessions/${id}`, { method: "PATCH", body: { status } });
602
+ },
603
+ updateModel(id, model) {
604
+ return api(`/sessions/${id}`, { method: "PATCH", body: { model } });
605
+ },
606
+ update(id, updates) {
607
+ return api(`/sessions/${id}`, { method: "PATCH", body: updates });
608
+ },
609
+ delete(id) {
610
+ return api(`/sessions/${id}`, { method: "DELETE" }).then((r) => r?.success ?? false);
611
+ }
612
+ };
613
+ remoteMessageQueries = {
614
+ async getNextSequence(sessionId) {
615
+ const result = await api(`/messages/session/${sessionId}/next-sequence`);
616
+ return result.nextSequence;
617
+ },
618
+ create(sessionId, modelMessage) {
619
+ return api("/messages", { method: "POST", body: { sessionId, modelMessage } });
620
+ },
621
+ addMany(sessionId, modelMessages) {
622
+ return api("/messages/batch", { method: "POST", body: { sessionId, modelMessages } });
623
+ },
624
+ getBySession(sessionId) {
625
+ return api(`/messages/session/${sessionId}`);
626
+ },
627
+ getModelMessages(sessionId) {
628
+ return api(`/messages/session/${sessionId}/model-messages`, { skipParseDates: true });
629
+ },
630
+ async getRecentBySession(sessionId, limit = 50) {
631
+ const messages = await api(`/messages/session/${sessionId}`);
632
+ return messages.slice(-limit);
633
+ },
634
+ async countBySession(sessionId) {
635
+ const result = await api(`/messages/session/${sessionId}/count`);
636
+ return result.count;
637
+ },
638
+ async deleteBySession(sessionId) {
639
+ const result = await api(`/messages/session/${sessionId}`, { method: "DELETE" });
640
+ return result.deleted;
641
+ },
642
+ async deleteFromSequence(sessionId, fromSequence) {
643
+ const result = await api(
644
+ `/messages/session/${sessionId}/from-sequence/${fromSequence}`,
645
+ { method: "DELETE" }
646
+ );
647
+ return result.deleted;
648
+ }
828
649
  };
829
- });
650
+ remoteToolExecutionQueries = {
651
+ create(data) {
652
+ return api("/tool-executions", { method: "POST", body: data });
653
+ },
654
+ getById(id) {
655
+ return api(`/tool-executions/${id}`).catch(() => void 0);
656
+ },
657
+ getByToolCallId(toolCallId) {
658
+ return api(`/tool-executions/by-tool-call-id/${toolCallId}`).catch(() => void 0);
659
+ },
660
+ getPendingApprovals(sessionId) {
661
+ return api(`/tool-executions/session/${sessionId}/pending`);
662
+ },
663
+ approve(id) {
664
+ return api(`/tool-executions/${id}`, { method: "PATCH", body: { status: "approved" } });
665
+ },
666
+ reject(id) {
667
+ return api(`/tool-executions/${id}`, { method: "PATCH", body: { status: "rejected" } });
668
+ },
669
+ complete(id, output, error) {
670
+ return api(`/tool-executions/${id}`, {
671
+ method: "PATCH",
672
+ body: { status: error ? "error" : "completed", output, error }
673
+ });
674
+ },
675
+ getBySession(sessionId) {
676
+ return api(`/tool-executions/session/${sessionId}`);
677
+ },
678
+ async deleteAfterTime(sessionId, afterTime) {
679
+ const timestamp = afterTime instanceof Date ? afterTime.getTime() : new Date(afterTime).getTime();
680
+ const result = await api(
681
+ `/tool-executions/session/${sessionId}/after/${timestamp}`,
682
+ { method: "DELETE" }
683
+ );
684
+ return result.deleted;
685
+ }
686
+ };
687
+ remoteTodoQueries = {
688
+ create(data) {
689
+ return api("/todos", { method: "POST", body: data });
690
+ },
691
+ createMany(sessionId, items) {
692
+ return api("/todos/batch", { method: "POST", body: { sessionId, items } });
693
+ },
694
+ getBySession(sessionId) {
695
+ return api(`/todos/session/${sessionId}`);
696
+ },
697
+ updateStatus(id, status) {
698
+ return api(`/todos/${id}`, { method: "PATCH", body: { status } });
699
+ },
700
+ async delete(id) {
701
+ const result = await api(`/todos/${id}`, { method: "DELETE" });
702
+ return result?.success ?? false;
703
+ },
704
+ async clearSession(sessionId) {
705
+ const result = await api(`/todos/session/${sessionId}`, { method: "DELETE" });
706
+ return result.deleted;
707
+ }
708
+ };
709
+ remoteSkillQueries = {
710
+ load(sessionId, skillName) {
711
+ return api("/skills", { method: "POST", body: { sessionId, skillName } });
712
+ },
713
+ getBySession(sessionId) {
714
+ return api(`/skills/session/${sessionId}`);
715
+ },
716
+ async isLoaded(sessionId, skillName) {
717
+ const result = await api(`/skills/session/${sessionId}/is-loaded/${skillName}`);
718
+ return result.isLoaded;
719
+ }
720
+ };
721
+ remoteActiveStreamQueries = {
722
+ create(sessionId, streamId) {
723
+ return api("/streams", { method: "POST", body: { sessionId, streamId } });
724
+ },
725
+ getBySessionId(sessionId) {
726
+ return api(`/streams/session/${sessionId}`).then((r) => r ?? void 0);
727
+ },
728
+ getByStreamId(streamId) {
729
+ return api(`/streams/by-stream-id/${streamId}`).catch(() => void 0);
730
+ },
731
+ finish(streamId) {
732
+ return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "finished" } });
733
+ },
734
+ markError(streamId) {
735
+ return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "error" } });
736
+ },
737
+ async deleteBySession(sessionId) {
738
+ const result = await api(`/streams/session/${sessionId}`, { method: "DELETE" });
739
+ return result.deleted;
740
+ }
741
+ };
742
+ remoteCheckpointQueries = {
743
+ create(data) {
744
+ return api("/checkpoints", { method: "POST", body: data });
745
+ },
746
+ getById(id) {
747
+ return api(`/checkpoints/${id}`).catch(() => void 0);
748
+ },
749
+ getBySession(sessionId) {
750
+ return api(`/checkpoints/session/${sessionId}`);
751
+ },
752
+ getByMessageSequence(sessionId, messageSequence) {
753
+ return api(`/checkpoints/session/${sessionId}/by-sequence/${messageSequence}`).then((r) => r ?? void 0);
754
+ },
755
+ getLatest(sessionId) {
756
+ return api(`/checkpoints/session/${sessionId}/latest`).then((r) => r ?? void 0);
757
+ },
758
+ async deleteAfterSequence(sessionId, messageSequence) {
759
+ const result = await api(
760
+ `/checkpoints/session/${sessionId}/after-sequence/${messageSequence}`,
761
+ { method: "DELETE" }
762
+ );
763
+ return result.deleted;
764
+ },
765
+ async deleteBySession(sessionId) {
766
+ const result = await api(`/checkpoints/session/${sessionId}`, { method: "DELETE" });
767
+ return result.deleted;
768
+ }
769
+ };
770
+ remoteFileBackupQueries = {
771
+ create(data) {
772
+ return api("/file-backups", { method: "POST", body: data });
773
+ },
774
+ getByCheckpoint(checkpointId) {
775
+ return api(`/file-backups/checkpoint/${checkpointId}`);
776
+ },
777
+ getBySession(sessionId) {
778
+ return api(`/file-backups/session/${sessionId}`);
779
+ },
780
+ getFromSequence(sessionId, messageSequence) {
781
+ return api(`/file-backups/session/${sessionId}/from-sequence/${messageSequence}`);
782
+ },
783
+ async hasBackup(checkpointId, filePath) {
784
+ const result = await api(
785
+ `/file-backups/checkpoint/${checkpointId}/has-backup/${encodeURIComponent(filePath)}`
786
+ );
787
+ return result.hasBackup;
788
+ },
789
+ async deleteBySession(sessionId) {
790
+ const result = await api(`/file-backups/session/${sessionId}`, { method: "DELETE" });
791
+ return result.deleted;
792
+ }
793
+ };
794
+ remoteSubagentQueries = {
795
+ create(data) {
796
+ return api("/subagents", { method: "POST", body: data });
797
+ },
798
+ getById(id) {
799
+ return api(`/subagents/${id}`).catch(() => void 0);
800
+ },
801
+ getByToolCallId(toolCallId) {
802
+ return api(`/subagents/by-tool-call-id/${toolCallId}`).catch(() => void 0);
803
+ },
804
+ getBySession(sessionId) {
805
+ return api(`/subagents/session/${sessionId}`);
806
+ },
807
+ addStep(id, step) {
808
+ return api(`/subagents/${id}/add-step`, { method: "POST", body: { step } }).catch(() => void 0);
809
+ },
810
+ complete(id, result) {
811
+ return api(`/subagents/${id}`, { method: "PATCH", body: { status: "completed", result } }).catch(() => void 0);
812
+ },
813
+ markError(id, error) {
814
+ return api(`/subagents/${id}`, { method: "PATCH", body: { status: "error", error } }).catch(() => void 0);
815
+ },
816
+ cancel(id) {
817
+ return api(`/subagents/${id}`, { method: "PATCH", body: { status: "cancelled" } }).catch(() => void 0);
818
+ },
819
+ async deleteBySession(sessionId) {
820
+ const result = await api(`/subagents/session/${sessionId}`, { method: "DELETE" });
821
+ return result.deleted;
822
+ }
823
+ };
824
+ remoteIndexStatusQueries = {
825
+ upsert(_db, data) {
826
+ return api("/index-status", {
827
+ method: "POST",
828
+ body: {
829
+ ...data,
830
+ lastFullIndex: data.lastFullIndex?.toISOString(),
831
+ lastIncrementalIndex: data.lastIncrementalIndex?.toISOString()
832
+ }
833
+ });
834
+ },
835
+ get(_db, namespace) {
836
+ return api(`/index-status/namespace/${namespace}`).then((r) => r ?? void 0);
837
+ },
838
+ async delete(_db, namespace) {
839
+ const result = await api(`/index-status/namespace/${namespace}`, { method: "DELETE" });
840
+ return result?.success ?? false;
841
+ },
842
+ list(_db) {
843
+ return api("/index-status");
844
+ }
845
+ };
846
+ }
847
+ });
848
+
849
+ // src/db/index.ts
850
+ function initDatabase(config) {
851
+ initRemoteDatabase(config.url, config.authKey);
852
+ initialized = true;
830
853
  }
831
- function maskApiKey(key) {
832
- if (key.length <= 12) {
833
- return "****" + key.slice(-4);
854
+ function getDb() {
855
+ if (!initialized) {
856
+ throw new Error("Database not initialized. Call initDatabase first.");
834
857
  }
835
- return key.slice(0, 4) + "..." + key.slice(-4);
858
+ return {};
836
859
  }
837
- var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
838
- var init_config = __esm({
839
- "src/config/index.ts"() {
840
- "use strict";
841
- init_types();
842
- init_types();
843
- CONFIG_FILE_NAMES = [
844
- "sparkecoder.config.json",
845
- "sparkecoder.json",
846
- ".sparkecoder.json"
847
- ];
848
- cachedConfig = null;
849
- AUTH_KEY_FILE = "auth-key.json";
850
- API_KEYS_FILE = "api-keys.json";
851
- PROVIDER_ENV_MAP = {
852
- anthropic: "ANTHROPIC_API_KEY",
853
- openai: "OPENAI_API_KEY",
854
- google: "GOOGLE_GENERATIVE_AI_API_KEY",
855
- xai: "XAI_API_KEY",
856
- "ai-gateway": "AI_GATEWAY_API_KEY"
857
- };
858
- SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
860
+ function closeDatabase() {
861
+ closeRemoteDatabase();
862
+ initialized = false;
863
+ }
864
+ var initialized, sessionQueries, messageQueries, toolExecutionQueries, todoQueries, skillQueries, activeStreamQueries, checkpointQueries, fileBackupQueries, subagentQueries, indexStatusQueries;
865
+ var init_db = __esm({
866
+ "src/db/index.ts"() {
867
+ "use strict";
868
+ init_remote();
869
+ initialized = false;
870
+ sessionQueries = remoteSessionQueries;
871
+ messageQueries = remoteMessageQueries;
872
+ toolExecutionQueries = remoteToolExecutionQueries;
873
+ todoQueries = remoteTodoQueries;
874
+ skillQueries = remoteSkillQueries;
875
+ activeStreamQueries = remoteActiveStreamQueries;
876
+ checkpointQueries = remoteCheckpointQueries;
877
+ fileBackupQueries = remoteFileBackupQueries;
878
+ subagentQueries = remoteSubagentQueries;
879
+ indexStatusQueries = remoteIndexStatusQueries;
859
880
  }
860
881
  });
861
882
 
@@ -874,7 +895,7 @@ __export(skills_exports, {
874
895
  loadSkillsFromDirectory: () => loadSkillsFromDirectory
875
896
  });
876
897
  import { readFile as readFile6, readdir } from "fs/promises";
877
- import { resolve as resolve6, basename, extname as extname3, relative as relative4 } from "path";
898
+ import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
878
899
  import { existsSync as existsSync8 } from "fs";
879
900
  import { minimatch } from "minimatch";
880
901
  function parseSkillFrontmatter(content) {
@@ -945,7 +966,7 @@ function parseSkillFrontmatter(content) {
945
966
  }
946
967
  }
947
968
  function getSkillNameFromPath(filePath) {
948
- return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
969
+ return basename(filePath, extname4(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
949
970
  }
950
971
  async function loadSkillsFromDirectory(directory, options = {}) {
951
972
  const {
@@ -1250,7 +1271,7 @@ var init_hasher = __esm({
1250
1271
  });
1251
1272
 
1252
1273
  // src/semantic/chunker.ts
1253
- import { extname as extname5, basename as basename2 } from "path";
1274
+ import { extname as extname6, basename as basename2 } from "path";
1254
1275
  var init_chunker = __esm({
1255
1276
  "src/semantic/chunker.ts"() {
1256
1277
  "use strict";
@@ -1600,18 +1621,176 @@ var init_semantic_search = __esm({
1600
1621
  import {
1601
1622
  streamText as streamText2,
1602
1623
  generateText as generateText3,
1603
- tool as tool11,
1624
+ tool as tool12,
1604
1625
  stepCountIs as stepCountIs2
1605
1626
  } from "ai";
1606
1627
 
1607
1628
  // src/agent/model.ts
1608
1629
  import { gateway } from "@ai-sdk/gateway";
1630
+
1631
+ // src/agent/remote-model.ts
1632
+ function serializePrompt(prompt) {
1633
+ return prompt.map((msg) => {
1634
+ if (!Array.isArray(msg.content)) return msg;
1635
+ return {
1636
+ ...msg,
1637
+ content: msg.content.map((part) => {
1638
+ if (part.type === "file" && part.data instanceof Uint8Array) {
1639
+ return {
1640
+ ...part,
1641
+ data: Buffer.from(part.data).toString("base64"),
1642
+ _base64: true
1643
+ };
1644
+ }
1645
+ return part;
1646
+ })
1647
+ };
1648
+ });
1649
+ }
1650
+ function deserializeValue(value) {
1651
+ if (value && typeof value === "object") {
1652
+ if (value.__uint8array && typeof value.data === "string") {
1653
+ return Buffer.from(value.data, "base64");
1654
+ }
1655
+ if (Array.isArray(value)) {
1656
+ return value.map(deserializeValue);
1657
+ }
1658
+ const result = {};
1659
+ for (const [k, v] of Object.entries(value)) {
1660
+ result[k] = deserializeValue(v);
1661
+ }
1662
+ return result;
1663
+ }
1664
+ return value;
1665
+ }
1666
+ function prepareOptions(options) {
1667
+ const { abortSignal, ...rest } = options;
1668
+ return {
1669
+ ...rest,
1670
+ prompt: serializePrompt(options.prompt)
1671
+ };
1672
+ }
1673
+ function createRemoteModel(modelId, config) {
1674
+ const baseUrl = config.url.replace(/\/$/, "");
1675
+ const headers = {
1676
+ "Content-Type": "application/json",
1677
+ "Authorization": `Bearer ${config.authKey}`
1678
+ };
1679
+ return {
1680
+ specificationVersion: "v3",
1681
+ provider: "remote-proxy",
1682
+ modelId,
1683
+ supportedUrls: {},
1684
+ async doGenerate(options) {
1685
+ const res = await fetch(`${baseUrl}/inference/generate`, {
1686
+ method: "POST",
1687
+ headers,
1688
+ body: JSON.stringify({
1689
+ modelId,
1690
+ options: prepareOptions(options)
1691
+ }),
1692
+ signal: options.abortSignal
1693
+ });
1694
+ if (!res.ok) {
1695
+ const err = await res.json().catch(() => ({}));
1696
+ throw new Error(
1697
+ `Remote inference failed (${res.status}): ${err.error || res.statusText}`
1698
+ );
1699
+ }
1700
+ const result = await res.json();
1701
+ return deserializeValue(result);
1702
+ },
1703
+ async doStream(options) {
1704
+ const res = await fetch(`${baseUrl}/inference/stream`, {
1705
+ method: "POST",
1706
+ headers,
1707
+ body: JSON.stringify({
1708
+ modelId,
1709
+ options: prepareOptions(options)
1710
+ }),
1711
+ signal: options.abortSignal
1712
+ });
1713
+ if (!res.ok) {
1714
+ const err = await res.json().catch(() => ({}));
1715
+ throw new Error(
1716
+ `Remote inference failed (${res.status}): ${err.error || res.statusText}`
1717
+ );
1718
+ }
1719
+ const reader = res.body.getReader();
1720
+ const decoder = new TextDecoder();
1721
+ let buffer = "";
1722
+ const stream = new ReadableStream({
1723
+ async pull(controller) {
1724
+ while (true) {
1725
+ const { done, value } = await reader.read();
1726
+ if (done) {
1727
+ if (buffer.trim()) {
1728
+ try {
1729
+ const parsed = deserializeValue(JSON.parse(buffer.trim()));
1730
+ if (parsed.type === "error") {
1731
+ controller.error(new Error(parsed.error));
1732
+ } else {
1733
+ controller.enqueue(parsed);
1734
+ }
1735
+ } catch {
1736
+ }
1737
+ }
1738
+ controller.close();
1739
+ return;
1740
+ }
1741
+ buffer += decoder.decode(value, { stream: true });
1742
+ const lines = buffer.split("\n");
1743
+ buffer = lines.pop() || "";
1744
+ for (const line of lines) {
1745
+ if (!line.trim()) continue;
1746
+ try {
1747
+ const parsed = deserializeValue(JSON.parse(line));
1748
+ if (parsed.type === "error") {
1749
+ controller.error(new Error(parsed.error));
1750
+ return;
1751
+ }
1752
+ controller.enqueue(parsed);
1753
+ } catch {
1754
+ }
1755
+ }
1756
+ }
1757
+ },
1758
+ cancel() {
1759
+ reader.cancel();
1760
+ }
1761
+ });
1762
+ const responseHeaders = {};
1763
+ res.headers.forEach((v, k) => {
1764
+ if (k.startsWith("x-upstream-")) {
1765
+ responseHeaders[k.replace("x-upstream-", "")] = v;
1766
+ }
1767
+ });
1768
+ return {
1769
+ stream,
1770
+ response: Object.keys(responseHeaders).length > 0 ? { headers: responseHeaders } : void 0
1771
+ };
1772
+ }
1773
+ };
1774
+ }
1775
+
1776
+ // src/agent/model.ts
1777
+ init_config();
1609
1778
  var ANTHROPIC_PREFIX = "anthropic/";
1610
1779
  function isAnthropicModel(modelId) {
1611
1780
  const normalized = modelId.trim().toLowerCase();
1612
1781
  return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
1613
1782
  }
1614
1783
  function resolveModel(modelId) {
1784
+ try {
1785
+ const config = getConfig();
1786
+ if (config.resolvedRemoteServer.isConfigured) {
1787
+ return createRemoteModel(modelId.trim(), {
1788
+ url: config.resolvedRemoteServer.url,
1789
+ authKey: config.resolvedRemoteServer.authKey
1790
+ });
1791
+ }
1792
+ } catch {
1793
+ }
1615
1794
  return gateway(modelId.trim());
1616
1795
  }
1617
1796
  var SUBAGENT_MODELS = {
@@ -1623,7 +1802,7 @@ var SUBAGENT_MODELS = {
1623
1802
  // src/agent/index.ts
1624
1803
  init_db();
1625
1804
  init_config();
1626
- import { z as z12 } from "zod";
1805
+ import { z as z13 } from "zod";
1627
1806
  import { nanoid as nanoid3 } from "nanoid";
1628
1807
 
1629
1808
  // src/tools/bash.ts
@@ -2221,26 +2400,41 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
2221
2400
  import { tool as tool2 } from "ai";
2222
2401
  import { z as z3 } from "zod";
2223
2402
  import { readFile as readFile2, stat } from "fs/promises";
2224
- import { resolve as resolve2, relative, isAbsolute } from "path";
2403
+ import { resolve as resolve2, relative, isAbsolute, extname } from "path";
2225
2404
  import { existsSync as existsSync3 } from "fs";
2226
2405
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
2406
+ var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
2227
2407
  var MAX_OUTPUT_CHARS3 = 5e4;
2408
+ var IMAGE_EXTENSIONS = {
2409
+ ".png": "image/png",
2410
+ ".jpg": "image/jpeg",
2411
+ ".jpeg": "image/jpeg",
2412
+ ".gif": "image/gif",
2413
+ ".webp": "image/webp"
2414
+ };
2415
+ function isImageFile(filePath) {
2416
+ return extname(filePath).toLowerCase() in IMAGE_EXTENSIONS;
2417
+ }
2418
+ function getImageMediaType(filePath) {
2419
+ return IMAGE_EXTENSIONS[extname(filePath).toLowerCase()] || "image/png";
2420
+ }
2228
2421
  var readFileInputSchema = z3.object({
2229
- path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute."),
2230
- startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed)"),
2231
- endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive)")
2422
+ 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)."),
2423
+ startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed). Only for text files."),
2424
+ endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive). Only for text files.")
2232
2425
  });
2233
2426
  function createReadFileTool(options) {
2234
2427
  return tool2({
2235
2428
  description: `Read the contents of a file. Provide a path relative to the working directory (${options.workingDirectory}) or an absolute path.
2236
- Large files will be automatically truncated. Binary files are not supported.
2237
- Use this to understand existing code, check file contents, or gather context.`,
2429
+ Supports text files (automatically truncated if large) and image files (png, jpg, jpeg, gif, webp).
2430
+ For images, the file contents are returned as visual data you can see and analyze.
2431
+ Use this to understand existing code, check file contents, view screenshots, or gather context.`,
2238
2432
  inputSchema: readFileInputSchema,
2239
- execute: async ({ path, startLine, endLine }) => {
2433
+ execute: async ({ path: filePath, startLine, endLine }) => {
2240
2434
  try {
2241
- const absolutePath = isAbsolute(path) ? path : resolve2(options.workingDirectory, path);
2435
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve2(options.workingDirectory, filePath);
2242
2436
  const relativePath = relative(options.workingDirectory, absolutePath);
2243
- if (relativePath.startsWith("..") && !isAbsolute(path)) {
2437
+ if (relativePath.startsWith("..") && !isAbsolute(filePath)) {
2244
2438
  return {
2245
2439
  success: false,
2246
2440
  error: "Path escapes the working directory. Use an absolute path if intentional.",
@@ -2250,22 +2444,43 @@ Use this to understand existing code, check file contents, or gather context.`,
2250
2444
  if (!existsSync3(absolutePath)) {
2251
2445
  return {
2252
2446
  success: false,
2253
- error: `File not found: ${path}`,
2447
+ error: `File not found: ${filePath}`,
2254
2448
  content: null
2255
2449
  };
2256
2450
  }
2257
2451
  const stats = await stat(absolutePath);
2258
- if (stats.size > MAX_FILE_SIZE) {
2452
+ if (stats.isDirectory()) {
2259
2453
  return {
2260
2454
  success: false,
2261
- error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
2455
+ error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
2262
2456
  content: null
2263
2457
  };
2264
2458
  }
2265
- if (stats.isDirectory()) {
2459
+ if (isImageFile(absolutePath)) {
2460
+ if (stats.size > MAX_IMAGE_SIZE) {
2461
+ return {
2462
+ success: false,
2463
+ error: `Image is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_IMAGE_SIZE / 1024 / 1024}MB.`,
2464
+ content: null
2465
+ };
2466
+ }
2467
+ const buffer = await readFile2(absolutePath);
2468
+ const base64 = buffer.toString("base64");
2469
+ const mediaType = getImageMediaType(absolutePath);
2470
+ return {
2471
+ success: true,
2472
+ path: absolutePath,
2473
+ relativePath: relative(options.workingDirectory, absolutePath),
2474
+ content: `[Image: ${relativePath} (${mediaType}, ${(stats.size / 1024).toFixed(1)}KB)]`,
2475
+ mediaType,
2476
+ imageData: base64,
2477
+ sizeBytes: stats.size
2478
+ };
2479
+ }
2480
+ if (stats.size > MAX_FILE_SIZE) {
2266
2481
  return {
2267
2482
  success: false,
2268
- error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
2483
+ error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
2269
2484
  content: null
2270
2485
  };
2271
2486
  }
@@ -2281,9 +2496,7 @@ Use this to understand existing code, check file contents, or gather context.`,
2281
2496
  content: null
2282
2497
  };
2283
2498
  }
2284
- content = lines.slice(start, end).join("\n");
2285
- const lineNumbers = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
2286
- content = lineNumbers;
2499
+ content = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
2287
2500
  }
2288
2501
  const truncatedContent = truncateOutput(content, MAX_OUTPUT_CHARS3);
2289
2502
  const wasTruncated = truncatedContent.length < content.length;
@@ -2310,6 +2523,19 @@ Use this to understand existing code, check file contents, or gather context.`,
2310
2523
  content: null
2311
2524
  };
2312
2525
  }
2526
+ },
2527
+ toModelOutput: ({ output }) => {
2528
+ if (output && typeof output === "object" && "imageData" in output && output.imageData) {
2529
+ const result = output;
2530
+ return {
2531
+ type: "content",
2532
+ value: [
2533
+ { type: "text", text: result.content },
2534
+ { type: "image-data", data: result.imageData, mediaType: result.mediaType }
2535
+ ]
2536
+ };
2537
+ }
2538
+ return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output };
2313
2539
  }
2314
2540
  });
2315
2541
  }
@@ -2510,7 +2736,7 @@ function clearCheckpointManager(sessionId) {
2510
2736
  }
2511
2737
 
2512
2738
  // src/lsp/index.ts
2513
- import { extname as extname2, dirname as dirname4 } from "path";
2739
+ import { extname as extname3, dirname as dirname4 } from "path";
2514
2740
 
2515
2741
  // src/lsp/servers.ts
2516
2742
  import { spawn } from "child_process";
@@ -2633,9 +2859,9 @@ import {
2633
2859
  import { pathToFileURL, fileURLToPath } from "url";
2634
2860
  import { readFile as readFile4 } from "fs/promises";
2635
2861
  import { existsSync as existsSync6 } from "fs";
2636
- import { extname, normalize } from "path";
2862
+ import { extname as extname2, normalize } from "path";
2637
2863
  function getLanguageId(filePath) {
2638
- const ext = extname(filePath).toLowerCase();
2864
+ const ext = extname2(filePath).toLowerCase();
2639
2865
  const map = {
2640
2866
  ".ts": "typescript",
2641
2867
  ".tsx": "typescriptreact",
@@ -3023,7 +3249,7 @@ var state = {
3023
3249
  };
3024
3250
  async function getClientForFile(filePath) {
3025
3251
  const normalized = normalizePath(filePath);
3026
- const ext = extname2(normalized);
3252
+ const ext = extname3(normalized);
3027
3253
  const serverDef = getServerForExtension(ext);
3028
3254
  if (!serverDef) {
3029
3255
  return null;
@@ -3116,7 +3342,7 @@ async function formatDiagnosticsOutput(filePath, options = {}) {
3116
3342
  return formatDiagnosticsForAgent(filePath, diagnostics, options);
3117
3343
  }
3118
3344
  function isSupported(filePath) {
3119
- const ext = extname2(filePath);
3345
+ const ext = extname3(filePath);
3120
3346
  return getServerForExtension(ext) !== null;
3121
3347
  }
3122
3348
 
@@ -3543,7 +3769,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
3543
3769
  // src/tools/linter.ts
3544
3770
  import { tool as tool6 } from "ai";
3545
3771
  import { z as z7 } from "zod";
3546
- import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname4 } from "path";
3772
+ import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
3547
3773
  import { existsSync as existsSync9 } from "fs";
3548
3774
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
3549
3775
  var linterInputSchema = z7.object({
@@ -3566,7 +3792,7 @@ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
3566
3792
  }
3567
3793
  await walk(fullPath);
3568
3794
  } else if (entry.isFile()) {
3569
- const ext = extname4(entry.name);
3795
+ const ext = extname5(entry.name);
3570
3796
  if (supportedExtensions.includes(ext)) {
3571
3797
  files.push(fullPath);
3572
3798
  }
@@ -4903,6 +5129,59 @@ Context: ${context}` : query;
4903
5129
 
4904
5130
  // src/tools/index.ts
4905
5131
  init_semantic_search();
5132
+
5133
+ // src/tools/task.ts
5134
+ import { tool as tool11 } from "ai";
5135
+ import { z as z12 } from "zod";
5136
+ import Ajv from "ajv";
5137
+ var ajv = new Ajv({ allErrors: true });
5138
+ function createCompleteTaskTool(options) {
5139
+ const validate = ajv.compile(options.outputSchema);
5140
+ return tool11({
5141
+ 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.",
5142
+ inputSchema: z12.object({
5143
+ result: z12.record(z12.string(), z12.unknown()).describe("The task result as a JSON object matching the output schema")
5144
+ }),
5145
+ execute: async (input) => {
5146
+ const valid = validate(input.result);
5147
+ if (!valid) {
5148
+ const errors = validate.errors?.map((e) => ({
5149
+ path: e.instancePath || "/",
5150
+ message: e.message,
5151
+ params: e.params
5152
+ }));
5153
+ return {
5154
+ status: "validation_error",
5155
+ message: "The result does not match the required output schema. Fix the errors and call complete_task again.",
5156
+ errors,
5157
+ expectedSchema: options.outputSchema
5158
+ };
5159
+ }
5160
+ options.onComplete({ status: "completed", result: input.result });
5161
+ return {
5162
+ status: "completed",
5163
+ message: "Task completed successfully."
5164
+ };
5165
+ }
5166
+ });
5167
+ }
5168
+ function createTaskFailedTool(options) {
5169
+ return tool11({
5170
+ description: "Call this tool if you are unable to complete the task. Provide a clear reason explaining why the task cannot be completed.",
5171
+ inputSchema: z12.object({
5172
+ reason: z12.string().describe("Explanation of why the task cannot be completed")
5173
+ }),
5174
+ execute: async (input) => {
5175
+ options.onComplete({ status: "failed", error: input.reason });
5176
+ return {
5177
+ status: "failed",
5178
+ message: `Task marked as failed: ${input.reason}`
5179
+ };
5180
+ }
5181
+ });
5182
+ }
5183
+
5184
+ // src/tools/index.ts
4906
5185
  init_semantic();
4907
5186
  init_semantic_search();
4908
5187
  async function createTools(options) {
@@ -4954,6 +5233,10 @@ async function createTools(options) {
4954
5233
  } catch {
4955
5234
  }
4956
5235
  }
5236
+ if (options.taskTools) {
5237
+ tools.complete_task = createCompleteTaskTool(options.taskTools);
5238
+ tools.task_failed = createTaskFailedTool(options.taskTools);
5239
+ }
4957
5240
  return tools;
4958
5241
  }
4959
5242
 
@@ -5271,6 +5554,30 @@ function formatTodosForContext(todos) {
5271
5554
  }
5272
5555
  return lines.join("\n");
5273
5556
  }
5557
+ function buildTaskPromptAddendum(outputSchema) {
5558
+ return `
5559
+ ## Task Mode
5560
+
5561
+ You are running in **task mode**. You have been given a specific task to complete autonomously.
5562
+
5563
+ ### Rules
5564
+ 1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
5565
+ 2. Keep working until the task is fully complete.
5566
+ 3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
5567
+ 4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
5568
+ 5. Do NOT stop without calling one of these two tools.
5569
+
5570
+ ### Output Schema
5571
+ The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
5572
+ \`\`\`json
5573
+ ${JSON.stringify(outputSchema, null, 2)}
5574
+ \`\`\`
5575
+
5576
+ ### Completion Tools
5577
+ - **\`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.
5578
+ - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
5579
+ `;
5580
+ }
5274
5581
  function createSummaryPrompt(conversationHistory) {
5275
5582
  return `Please provide a concise summary of the following conversation history. Focus on:
5276
5583
  1. The main task or goal being worked on
@@ -5532,6 +5839,25 @@ ${this.summary}`
5532
5839
  }
5533
5840
  };
5534
5841
 
5842
+ // src/utils/webhook.ts
5843
+ async function sendWebhook(url, event) {
5844
+ try {
5845
+ const controller = new AbortController();
5846
+ const timeout = setTimeout(() => controller.abort(), 5e3);
5847
+ await fetch(url, {
5848
+ method: "POST",
5849
+ headers: {
5850
+ "Content-Type": "application/json",
5851
+ "X-SparkECoder-Event": event.type
5852
+ },
5853
+ body: JSON.stringify(event),
5854
+ signal: controller.signal
5855
+ });
5856
+ clearTimeout(timeout);
5857
+ } catch {
5858
+ }
5859
+ }
5860
+
5535
5861
  // src/agent/index.ts
5536
5862
  var approvalResolvers = /* @__PURE__ */ new Map();
5537
5863
  var Agent = class _Agent {
@@ -5751,6 +6077,145 @@ ${prompt}` });
5751
6077
  steps: result.steps
5752
6078
  };
5753
6079
  }
6080
+ /**
6081
+ * Run the agent in task mode — loops autonomously until the agent calls
6082
+ * complete_task or task_failed (or hits maxIterations).
6083
+ * All tools run without approval. Webhook events are fired throughout.
6084
+ */
6085
+ async runTask(options) {
6086
+ const config = getConfig();
6087
+ const maxIterations = options.taskConfig.maxIterations ?? 50;
6088
+ const webhookUrl = options.taskConfig.webhookUrl;
6089
+ const fireWebhook = (type, data) => {
6090
+ if (!webhookUrl) return;
6091
+ sendWebhook(webhookUrl, {
6092
+ type,
6093
+ taskId: this.session.id,
6094
+ sessionId: this.session.id,
6095
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6096
+ data
6097
+ });
6098
+ };
6099
+ const completion = { signal: null };
6100
+ const onComplete = (signal) => {
6101
+ completion.signal = signal;
6102
+ };
6103
+ const taskTools = await createTools({
6104
+ sessionId: this.session.id,
6105
+ workingDirectory: this.session.workingDirectory,
6106
+ skillsDirectories: config.resolvedSkillsDirectories,
6107
+ onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
6108
+ onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
6109
+ onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
6110
+ taskTools: {
6111
+ outputSchema: options.taskConfig.outputSchema,
6112
+ onComplete
6113
+ }
6114
+ });
6115
+ const baseSystemPrompt = await buildSystemPrompt({
6116
+ workingDirectory: this.session.workingDirectory,
6117
+ skillsDirectories: config.resolvedSkillsDirectories,
6118
+ sessionId: this.session.id,
6119
+ discoveredSkills: config.discoveredSkills,
6120
+ activeFiles: []
6121
+ });
6122
+ const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
6123
+ const systemPrompt = `${baseSystemPrompt}
6124
+
6125
+ ${taskAddendum}`;
6126
+ fireWebhook("task.started", { prompt: options.prompt });
6127
+ this.context.addUserMessage(options.prompt);
6128
+ let iteration = 0;
6129
+ while (iteration < maxIterations) {
6130
+ iteration++;
6131
+ if (options.abortSignal?.aborted) {
6132
+ const cancelError = "Task was cancelled";
6133
+ fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
6134
+ return { status: "failed", error: cancelError, iterations: iteration };
6135
+ }
6136
+ const messages = await this.context.getMessages();
6137
+ const useAnthropic = isAnthropicModel(this.session.model);
6138
+ const result = await generateText3({
6139
+ model: resolveModel(this.session.model),
6140
+ system: systemPrompt,
6141
+ messages,
6142
+ tools: taskTools,
6143
+ stopWhen: stepCountIs2(500),
6144
+ abortSignal: options.abortSignal,
6145
+ providerOptions: useAnthropic ? {
6146
+ anthropic: {
6147
+ thinking: { type: "enabled", budgetTokens: 1e4 }
6148
+ }
6149
+ } : void 0,
6150
+ onStepFinish: (step) => {
6151
+ options.onStepFinish?.(step);
6152
+ fireWebhook("task.step_finished", { iteration, text: step.text });
6153
+ }
6154
+ });
6155
+ const responseMessages = result.response.messages;
6156
+ this.context.addResponseMessages(responseMessages);
6157
+ if (result.text) {
6158
+ options.onText?.(result.text);
6159
+ fireWebhook("task.message", { iteration, text: result.text });
6160
+ }
6161
+ for (const step of result.steps) {
6162
+ if (step.toolCalls) {
6163
+ for (const tc of step.toolCalls) {
6164
+ options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.args });
6165
+ fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.args });
6166
+ }
6167
+ }
6168
+ if (step.toolResults) {
6169
+ for (const tr of step.toolResults) {
6170
+ options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.result });
6171
+ fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.result });
6172
+ }
6173
+ }
6174
+ }
6175
+ if (completion.signal) {
6176
+ const sig = completion.signal;
6177
+ const finalStatus = sig.status;
6178
+ const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
6179
+ fireWebhook(eventType, {
6180
+ status: finalStatus,
6181
+ result: sig.result,
6182
+ error: sig.error,
6183
+ iterations: iteration
6184
+ });
6185
+ const updatedTask2 = {
6186
+ ...options.taskConfig,
6187
+ status: finalStatus,
6188
+ result: sig.result,
6189
+ error: sig.error,
6190
+ iterations: iteration
6191
+ };
6192
+ await sessionQueries.update(this.session.id, {
6193
+ config: { ...this.session.config, task: updatedTask2 }
6194
+ });
6195
+ return {
6196
+ status: finalStatus,
6197
+ result: sig.result,
6198
+ error: sig.error,
6199
+ iterations: iteration
6200
+ };
6201
+ }
6202
+ this.context.addUserMessage(
6203
+ "Continue working on the task. When done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
6204
+ );
6205
+ }
6206
+ const timeoutError = `Task did not complete within ${maxIterations} iterations`;
6207
+ fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
6208
+ const updatedTask = {
6209
+ ...options.taskConfig,
6210
+ status: "failed",
6211
+ error: timeoutError,
6212
+ iterations: iteration
6213
+ };
6214
+ await sessionQueries.update(this.session.id, {
6215
+ config: { ...this.session.config, task: updatedTask }
6216
+ });
6217
+ return { status: "failed", error: timeoutError, iterations: iteration };
6218
+ }
5754
6219
  /**
5755
6220
  * Wrap tools to add approval checking
5756
6221
  */
@@ -5764,9 +6229,9 @@ ${prompt}` });
5764
6229
  wrappedTools[name] = originalTool;
5765
6230
  continue;
5766
6231
  }
5767
- wrappedTools[name] = tool11({
6232
+ wrappedTools[name] = tool12({
5768
6233
  description: originalTool.description || "",
5769
- inputSchema: originalTool.inputSchema || z12.object({}),
6234
+ inputSchema: originalTool.inputSchema || z13.object({}),
5770
6235
  execute: async (input, toolOptions) => {
5771
6236
  const toolCallId = toolOptions.toolCallId || nanoid3();
5772
6237
  const execution = toolExecutionQueries.create({
@@ -5877,7 +6342,7 @@ ${prompt}` });
5877
6342
 
5878
6343
  // src/server/index.ts
5879
6344
  import "dotenv/config";
5880
- import { Hono as Hono5 } from "hono";
6345
+ import { Hono as Hono6 } from "hono";
5881
6346
  import { serve } from "@hono/node-server";
5882
6347
  import { cors } from "hono/cors";
5883
6348
  import { logger } from "hono/logger";
@@ -5891,10 +6356,10 @@ import { fileURLToPath as fileURLToPath4 } from "url";
5891
6356
  init_db();
5892
6357
  import { Hono } from "hono";
5893
6358
  import { zValidator } from "@hono/zod-validator";
5894
- import { z as z13 } from "zod";
6359
+ import { z as z14 } from "zod";
5895
6360
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
5896
6361
  import { readdir as readdir5 } from "fs/promises";
5897
- import { join as join5, basename as basename4, extname as extname6, relative as relative9 } from "path";
6362
+ import { join as join5, basename as basename4, extname as extname7, relative as relative9 } from "path";
5898
6363
  import { nanoid as nanoid4 } from "nanoid";
5899
6364
  init_config();
5900
6365
 
@@ -5927,18 +6392,18 @@ function cleanupPendingInputs() {
5927
6392
  }
5928
6393
  }
5929
6394
  }
5930
- var createSessionSchema = z13.object({
5931
- name: z13.string().optional(),
5932
- workingDirectory: z13.string().optional(),
5933
- model: z13.string().optional(),
5934
- toolApprovals: z13.record(z13.string(), z13.boolean()).optional()
6395
+ var createSessionSchema = z14.object({
6396
+ name: z14.string().optional(),
6397
+ workingDirectory: z14.string().optional(),
6398
+ model: z14.string().optional(),
6399
+ toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
5935
6400
  });
5936
- var paginationQuerySchema = z13.object({
5937
- limit: z13.string().optional(),
5938
- offset: z13.string().optional()
6401
+ var paginationQuerySchema = z14.object({
6402
+ limit: z14.string().optional(),
6403
+ offset: z14.string().optional()
5939
6404
  });
5940
- var messagesQuerySchema = z13.object({
5941
- limit: z13.string().optional()
6405
+ var messagesQuerySchema = z14.object({
6406
+ limit: z14.string().optional()
5942
6407
  });
5943
6408
  sessions.get(
5944
6409
  "/",
@@ -6077,10 +6542,10 @@ sessions.get("/:id/tools", async (c) => {
6077
6542
  count: executions.length
6078
6543
  });
6079
6544
  });
6080
- var updateSessionSchema = z13.object({
6081
- model: z13.string().optional(),
6082
- name: z13.string().optional(),
6083
- toolApprovals: z13.record(z13.string(), z13.boolean()).optional()
6545
+ var updateSessionSchema = z14.object({
6546
+ model: z14.string().optional(),
6547
+ name: z14.string().optional(),
6548
+ toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
6084
6549
  });
6085
6550
  sessions.patch(
6086
6551
  "/:id",
@@ -6150,8 +6615,8 @@ sessions.post("/:id/clear", async (c) => {
6150
6615
  await agent.clearContext();
6151
6616
  return c.json({ success: true, sessionId: id });
6152
6617
  });
6153
- var pendingInputSchema = z13.object({
6154
- text: z13.string()
6618
+ var pendingInputSchema = z14.object({
6619
+ text: z14.string()
6155
6620
  });
6156
6621
  sessions.post(
6157
6622
  "/:id/pending-input",
@@ -6182,13 +6647,13 @@ sessions.get("/:id/pending-input", async (c) => {
6182
6647
  createdAt: pending.createdAt.toISOString()
6183
6648
  });
6184
6649
  });
6185
- var devtoolsContextSchema = z13.object({
6186
- url: z13.string(),
6187
- path: z13.string(),
6188
- pageName: z13.string().optional(),
6189
- screenWidth: z13.number().optional(),
6190
- screenHeight: z13.number().optional(),
6191
- devicePixelRatio: z13.number().optional()
6650
+ var devtoolsContextSchema = z14.object({
6651
+ url: z14.string(),
6652
+ path: z14.string(),
6653
+ pageName: z14.string().optional(),
6654
+ screenWidth: z14.number().optional(),
6655
+ screenHeight: z14.number().optional(),
6656
+ devicePixelRatio: z14.number().optional()
6192
6657
  });
6193
6658
  sessions.post(
6194
6659
  "/:id/devtools-context",
@@ -6410,7 +6875,7 @@ sessions.post("/:id/attachments", async (c) => {
6410
6875
  }
6411
6876
  const dir = ensureAttachmentsDir(sessionId);
6412
6877
  const id = nanoid4(10);
6413
- const ext = extname6(file.name) || "";
6878
+ const ext = extname7(file.name) || "";
6414
6879
  const safeFilename = `${id}_${basename4(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
6415
6880
  const filePath = join5(dir, safeFilename);
6416
6881
  const arrayBuffer = await file.arrayBuffer();
@@ -6436,7 +6901,7 @@ sessions.post("/:id/attachments", async (c) => {
6436
6901
  }
6437
6902
  const dir = ensureAttachmentsDir(sessionId);
6438
6903
  const id = nanoid4(10);
6439
- const ext = extname6(body.filename) || "";
6904
+ const ext = extname7(body.filename) || "";
6440
6905
  const safeFilename = `${id}_${basename4(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
6441
6906
  const filePath = join5(dir, safeFilename);
6442
6907
  let base64Data = body.data;
@@ -6479,10 +6944,10 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
6479
6944
  unlinkSync(filePath);
6480
6945
  return c.json({ success: true, id: attachmentId });
6481
6946
  });
6482
- var filesQuerySchema = z13.object({
6483
- query: z13.string().optional(),
6947
+ var filesQuerySchema = z14.object({
6948
+ query: z14.string().optional(),
6484
6949
  // Filter query (e.g., "src/com" to match "src/components")
6485
- limit: z13.string().optional()
6950
+ limit: z14.string().optional()
6486
6951
  // Max results (default 50)
6487
6952
  });
6488
6953
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -6566,7 +7031,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
6566
7031
  if (entry.name.startsWith(".")) {
6567
7032
  continue;
6568
7033
  }
6569
- const ext = extname6(entry.name).toLowerCase();
7034
+ const ext = extname7(entry.name).toLowerCase();
6570
7035
  if (IGNORED_EXTENSIONS.has(ext)) {
6571
7036
  continue;
6572
7037
  }
@@ -6660,7 +7125,7 @@ sessions.get(
6660
7125
  init_db();
6661
7126
  import { Hono as Hono2 } from "hono";
6662
7127
  import { zValidator as zValidator2 } from "@hono/zod-validator";
6663
- import { z as z14 } from "zod";
7128
+ import { z as z15 } from "zod";
6664
7129
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
6665
7130
  import { join as join6 } from "path";
6666
7131
  init_config();
@@ -6825,30 +7290,30 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
6825
7290
  ${prompt}`;
6826
7291
  }
6827
7292
  var agents = new Hono2();
6828
- var attachmentSchema = z14.object({
6829
- type: z14.enum(["image", "file"]),
6830
- data: z14.string(),
7293
+ var attachmentSchema = z15.object({
7294
+ type: z15.enum(["image", "file"]),
7295
+ data: z15.string(),
6831
7296
  // base64 data URL or raw base64
6832
- mediaType: z14.string().optional(),
6833
- filename: z14.string().optional()
7297
+ mediaType: z15.string().optional(),
7298
+ filename: z15.string().optional()
6834
7299
  });
6835
- var runPromptSchema = z14.object({
6836
- prompt: z14.string(),
7300
+ var runPromptSchema = z15.object({
7301
+ prompt: z15.string(),
6837
7302
  // Can be empty if attachments are provided
6838
- attachments: z14.array(attachmentSchema).optional()
7303
+ attachments: z15.array(attachmentSchema).optional()
6839
7304
  }).refine(
6840
7305
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
6841
7306
  { message: "Either prompt or attachments must be provided" }
6842
7307
  );
6843
- var quickStartSchema = z14.object({
6844
- prompt: z14.string().min(1),
6845
- name: z14.string().optional(),
6846
- workingDirectory: z14.string().optional(),
6847
- model: z14.string().optional(),
6848
- toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
7308
+ var quickStartSchema = z15.object({
7309
+ prompt: z15.string().min(1),
7310
+ name: z15.string().optional(),
7311
+ workingDirectory: z15.string().optional(),
7312
+ model: z15.string().optional(),
7313
+ toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
6849
7314
  });
6850
- var rejectSchema = z14.object({
6851
- reason: z14.string().optional()
7315
+ var rejectSchema = z15.object({
7316
+ reason: z15.string().optional()
6852
7317
  }).optional();
6853
7318
  var streamAbortControllers = /* @__PURE__ */ new Map();
6854
7319
  function getAttachmentsDirectory(sessionId) {
@@ -7278,7 +7743,7 @@ agents.get("/:id/watch", async (c) => {
7278
7743
  "Cache-Control": "no-cache",
7279
7744
  "Connection": "keep-alive",
7280
7745
  "x-vercel-ai-ui-message-stream": "v1",
7281
- "x-stream-id": streamId
7746
+ "x-stream-id": streamId ?? ""
7282
7747
  }
7283
7748
  });
7284
7749
  });
@@ -7636,7 +8101,7 @@ agents.post(
7636
8101
  init_config();
7637
8102
  import { Hono as Hono3 } from "hono";
7638
8103
  import { zValidator as zValidator3 } from "@hono/zod-validator";
7639
- import { z as z15 } from "zod";
8104
+ import { z as z16 } from "zod";
7640
8105
  import { readFileSync as readFileSync5 } from "fs";
7641
8106
  import { fileURLToPath as fileURLToPath3 } from "url";
7642
8107
  import { dirname as dirname6, join as join7 } from "path";
@@ -7746,9 +8211,9 @@ health.get("/api-keys", async (c) => {
7746
8211
  supportedProviders: SUPPORTED_PROVIDERS
7747
8212
  });
7748
8213
  });
7749
- var setApiKeySchema = z15.object({
7750
- provider: z15.string(),
7751
- apiKey: z15.string().min(1)
8214
+ var setApiKeySchema = z16.object({
8215
+ provider: z16.string(),
8216
+ apiKey: z16.string().min(1)
7752
8217
  });
7753
8218
  health.post(
7754
8219
  "/api-keys",
@@ -7787,13 +8252,13 @@ health.delete("/api-keys/:provider", async (c) => {
7787
8252
  // src/server/routes/terminals.ts
7788
8253
  import { Hono as Hono4 } from "hono";
7789
8254
  import { zValidator as zValidator4 } from "@hono/zod-validator";
7790
- import { z as z16 } from "zod";
8255
+ import { z as z17 } from "zod";
7791
8256
  init_db();
7792
8257
  var terminals = new Hono4();
7793
- var spawnSchema = z16.object({
7794
- command: z16.string(),
7795
- cwd: z16.string().optional(),
7796
- name: z16.string().optional()
8258
+ var spawnSchema = z17.object({
8259
+ command: z17.string(),
8260
+ cwd: z17.string().optional(),
8261
+ name: z17.string().optional()
7797
8262
  });
7798
8263
  terminals.post(
7799
8264
  "/:sessionId/terminals",
@@ -7874,8 +8339,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
7874
8339
  // We don't track exit codes in tmux mode
7875
8340
  });
7876
8341
  });
7877
- var logsQuerySchema = z16.object({
7878
- tail: z16.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
8342
+ var logsQuerySchema = z17.object({
8343
+ tail: z17.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
7879
8344
  });
7880
8345
  terminals.get(
7881
8346
  "/:sessionId/terminals/:terminalId/logs",
@@ -7899,8 +8364,8 @@ terminals.get(
7899
8364
  });
7900
8365
  }
7901
8366
  );
7902
- var killSchema = z16.object({
7903
- signal: z16.enum(["SIGTERM", "SIGKILL"]).optional()
8367
+ var killSchema = z17.object({
8368
+ signal: z17.enum(["SIGTERM", "SIGKILL"]).optional()
7904
8369
  });
7905
8370
  terminals.post(
7906
8371
  "/:sessionId/terminals/:terminalId/kill",
@@ -7914,8 +8379,8 @@ terminals.post(
7914
8379
  return c.json({ success: true, message: "Terminal killed" });
7915
8380
  }
7916
8381
  );
7917
- var writeSchema = z16.object({
7918
- input: z16.string()
8382
+ var writeSchema = z17.object({
8383
+ input: z17.string()
7919
8384
  });
7920
8385
  terminals.post(
7921
8386
  "/:sessionId/terminals/:terminalId/write",
@@ -8096,6 +8561,131 @@ data: ${JSON.stringify({ status: "stopped" })}
8096
8561
  );
8097
8562
  });
8098
8563
 
8564
+ // src/server/routes/tasks.ts
8565
+ init_db();
8566
+ import { Hono as Hono5 } from "hono";
8567
+ import { zValidator as zValidator5 } from "@hono/zod-validator";
8568
+ import { z as z18 } from "zod";
8569
+ init_config();
8570
+ var tasks = new Hono5();
8571
+ var taskAbortControllers = /* @__PURE__ */ new Map();
8572
+ var createTaskSchema = z18.object({
8573
+ prompt: z18.string().min(1),
8574
+ outputSchema: z18.record(z18.string(), z18.unknown()),
8575
+ webhookUrl: z18.string().url().optional(),
8576
+ model: z18.string().optional(),
8577
+ workingDirectory: z18.string().optional(),
8578
+ name: z18.string().optional(),
8579
+ maxIterations: z18.number().int().min(1).max(500).optional()
8580
+ });
8581
+ tasks.post(
8582
+ "/",
8583
+ zValidator5("json", createTaskSchema),
8584
+ async (c) => {
8585
+ const body = c.req.valid("json");
8586
+ const config = getConfig();
8587
+ const taskConfig = {
8588
+ enabled: true,
8589
+ outputSchema: body.outputSchema,
8590
+ webhookUrl: body.webhookUrl,
8591
+ maxIterations: body.maxIterations ?? 50,
8592
+ status: "running"
8593
+ };
8594
+ const agent = await Agent.create({
8595
+ name: body.name || "Task",
8596
+ workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
8597
+ model: body.model || config.defaultModel,
8598
+ sessionConfig: {
8599
+ toolApprovals: { bash: false, write_file: false, read_file: false },
8600
+ task: taskConfig
8601
+ }
8602
+ });
8603
+ const taskId = agent.sessionId;
8604
+ const abortController = new AbortController();
8605
+ taskAbortControllers.set(taskId, abortController);
8606
+ (async () => {
8607
+ try {
8608
+ await agent.runTask({
8609
+ prompt: body.prompt,
8610
+ taskConfig,
8611
+ abortSignal: abortController.signal
8612
+ });
8613
+ } catch (err) {
8614
+ if (err.name === "AbortError" || abortController.signal.aborted) {
8615
+ console.log(`[TASK] Task ${taskId} was cancelled`);
8616
+ } else {
8617
+ console.error(`[TASK] Error in task ${taskId}:`, err.message);
8618
+ const failedTask = {
8619
+ ...taskConfig,
8620
+ status: "failed",
8621
+ error: err.message || "Unknown error"
8622
+ };
8623
+ await sessionQueries.update(taskId, {
8624
+ config: {
8625
+ toolApprovals: { bash: false, write_file: false, read_file: false },
8626
+ task: failedTask
8627
+ }
8628
+ });
8629
+ }
8630
+ } finally {
8631
+ taskAbortControllers.delete(taskId);
8632
+ }
8633
+ })();
8634
+ return c.json({ taskId, status: "running" }, 201);
8635
+ }
8636
+ );
8637
+ tasks.get("/:id", async (c) => {
8638
+ const id = c.req.param("id");
8639
+ const session = await sessionQueries.getById(id);
8640
+ if (!session) {
8641
+ return c.json({ error: "Task not found" }, 404);
8642
+ }
8643
+ const task = session.config?.task;
8644
+ if (!task?.enabled) {
8645
+ return c.json({ error: "Session is not a task" }, 400);
8646
+ }
8647
+ return c.json({
8648
+ taskId: id,
8649
+ status: task.status,
8650
+ result: task.result,
8651
+ error: task.error,
8652
+ iterations: task.iterations,
8653
+ model: session.model,
8654
+ name: session.name,
8655
+ createdAt: session.createdAt.toISOString(),
8656
+ updatedAt: session.updatedAt.toISOString()
8657
+ });
8658
+ });
8659
+ tasks.post("/:id/cancel", async (c) => {
8660
+ const id = c.req.param("id");
8661
+ const session = await sessionQueries.getById(id);
8662
+ if (!session) {
8663
+ return c.json({ error: "Task not found" }, 404);
8664
+ }
8665
+ const task = session.config?.task;
8666
+ if (!task?.enabled) {
8667
+ return c.json({ error: "Session is not a task" }, 400);
8668
+ }
8669
+ if (task.status !== "running") {
8670
+ return c.json({ error: `Task is already ${task.status}` }, 400);
8671
+ }
8672
+ const abortController = taskAbortControllers.get(id);
8673
+ if (abortController) {
8674
+ abortController.abort();
8675
+ taskAbortControllers.delete(id);
8676
+ }
8677
+ const cancelledTask = {
8678
+ ...task,
8679
+ status: "failed",
8680
+ error: "Task cancelled by user"
8681
+ };
8682
+ await sessionQueries.update(id, {
8683
+ config: { ...session.config, task: cancelledTask }
8684
+ });
8685
+ return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
8686
+ });
8687
+ var tasks_default = tasks;
8688
+
8099
8689
  // src/server/index.ts
8100
8690
  init_config();
8101
8691
  init_db();
@@ -8432,7 +9022,7 @@ function stopWebUI() {
8432
9022
  }
8433
9023
  }
8434
9024
  async function createApp(options = {}) {
8435
- const app = new Hono5();
9025
+ const app = new Hono6();
8436
9026
  app.use("*", cors({
8437
9027
  origin: "*",
8438
9028
  // Allow all origins
@@ -8450,6 +9040,7 @@ async function createApp(options = {}) {
8450
9040
  app.route("/agents", agents);
8451
9041
  app.route("/sessions", terminals);
8452
9042
  app.route("/terminals", terminals);
9043
+ app.route("/tasks", tasks_default);
8453
9044
  app.get("/openapi.json", async (c) => {
8454
9045
  return c.json(generateOpenAPISpec());
8455
9046
  });