sparkecoder 0.1.60 → 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 (171) hide show
  1. package/dist/agent/index.d.ts +4 -3
  2. package/dist/agent/index.js +588 -176
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +818 -95
  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 +1202 -665
  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 +605 -68
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/tools/index.d.ts +38 -3
  17. package/dist/tools/index.js +230 -2
  18. package/dist/tools/index.js.map +1 -1
  19. package/package.json +5 -3
  20. package/web/.next/BUILD_ID +1 -1
  21. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  22. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  23. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  24. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  25. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  26. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  27. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  29. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  45. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  83. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  86. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  88. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  89. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  91. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  92. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  94. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  95. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_19289e11._.js → 2374f_03d22c16._.js} +1 -1
  96. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b4b86c1f._.js → 2374f_0d0b1fb9._.js} +1 -1
  97. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4858a1ea._.js → 2374f_3f7d6d28._.js} +1 -1
  98. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_51385fed._.js → 2374f_6166d14d._.js} +1 -1
  99. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_40e35a02._.js → 2374f_67b9525f._.js} +1 -1
  100. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fb95e3c9._.js → 2374f_6b436efc._.js} +1 -1
  101. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2f0d9f6f._.js → 2374f_784d851c._.js} +1 -1
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_35475cbe._.js → 2374f_976b2ded._.js} +1 -1
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_7db22cde._.js → 2374f_98eba491._.js} +1 -1
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_90b8e4fb._.js → 2374f_99b7b533._.js} +1 -1
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4666c827._.js → 2374f_a1a36483._.js} +1 -1
  106. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_076f03ec._.js → 2374f_b98835eb._.js} +1 -1
  107. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b17fce11._.js → 2374f_e19eaecc._.js} +1 -1
  108. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d8b9ce38._.js → 2374f_e3aec189._.js} +1 -1
  109. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__7775f784._.js → [root-of-the-server]__4f176a16._.js} +2 -2
  110. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d59f831d._.js +15 -0
  111. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_645f4b90._.js → web_693f514e._.js} +2 -2
  112. package/web/.next/standalone/web/.next/server/chunks/ssr/web_a42a2651._.js +7 -0
  113. package/web/.next/standalone/web/.next/server/chunks/ssr/web_dc6ce793._.js +7 -0
  114. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
  115. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  116. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  117. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  118. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  119. package/web/.next/standalone/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
  120. package/web/.next/standalone/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
  121. package/web/.next/standalone/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  122. package/web/.next/standalone/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
  123. package/web/.next/standalone/web/.next/static/chunks/95436454a7559b0d.js +7 -0
  124. package/web/.next/standalone/web/.next/static/chunks/a751ca474cc46212.js +5 -0
  125. package/web/.next/standalone/web/.next/static/static/chunks/0358a0e7a40cfb93.css +1 -0
  126. package/web/.next/standalone/web/.next/static/static/chunks/1ecde4c0d426a635.js +1 -0
  127. package/web/.next/standalone/web/.next/static/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  128. package/web/.next/standalone/web/.next/static/static/chunks/36688a049d72e8ab.js +5 -0
  129. package/web/.next/standalone/web/.next/static/static/chunks/95436454a7559b0d.js +7 -0
  130. package/web/.next/standalone/web/.next/static/static/chunks/a751ca474cc46212.js +5 -0
  131. package/web/.next/standalone/web/src/components/ai-elements/complete-task-tool.tsx +126 -0
  132. package/web/.next/standalone/web/src/components/chat-interface.tsx +68 -3
  133. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +18 -1
  134. package/web/.next/standalone/web/src/lib/api.ts +12 -0
  135. package/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
  136. package/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
  137. package/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  138. package/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
  139. package/web/.next/static/chunks/95436454a7559b0d.js +7 -0
  140. package/web/.next/static/chunks/a751ca474cc46212.js +5 -0
  141. package/dist/schema-NcQknWCg.d.ts +0 -295
  142. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__bd396152._.js +0 -15
  143. package/web/.next/standalone/web/.next/server/chunks/ssr/web_9c9f0e3b._.js +0 -7
  144. package/web/.next/standalone/web/.next/server/chunks/ssr/web_d08270f7._.js +0 -7
  145. package/web/.next/standalone/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
  146. package/web/.next/standalone/web/.next/static/chunks/3f295b6960943c38.js +0 -1
  147. package/web/.next/standalone/web/.next/static/chunks/631b023d37a08635.js +0 -13
  148. package/web/.next/standalone/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
  149. package/web/.next/standalone/web/.next/static/chunks/e97212fcc8221479.js +0 -5
  150. package/web/.next/standalone/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
  151. package/web/.next/standalone/web/.next/static/static/chunks/2868b007ce5163fc.css +0 -1
  152. package/web/.next/standalone/web/.next/static/static/chunks/3f295b6960943c38.js +0 -1
  153. package/web/.next/standalone/web/.next/static/static/chunks/631b023d37a08635.js +0 -13
  154. package/web/.next/standalone/web/.next/static/static/chunks/a2b4737b190d1b54.js +0 -5
  155. package/web/.next/standalone/web/.next/static/static/chunks/e97212fcc8221479.js +0 -5
  156. package/web/.next/standalone/web/.next/static/static/chunks/f6e47c8a9766ce91.js +0 -7
  157. package/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
  158. package/web/.next/static/chunks/3f295b6960943c38.js +0 -1
  159. package/web/.next/static/chunks/631b023d37a08635.js +0 -13
  160. package/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
  161. package/web/.next/static/chunks/e97212fcc8221479.js +0 -5
  162. package/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
  163. /package/web/.next/standalone/web/.next/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  164. /package/web/.next/standalone/web/.next/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  165. /package/web/.next/standalone/web/.next/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
  166. /package/web/.next/standalone/web/.next/static/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  167. /package/web/.next/standalone/web/.next/static/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  168. /package/web/.next/standalone/web/.next/static/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
  169. /package/web/.next/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  170. /package/web/.next/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  171. /package/web/.next/static/{R5xiWSOp_Nqqe_js-LROo → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -8,575 +8,229 @@ 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;
15
- }
16
- function closeRemoteDatabase() {
17
- remoteServerUrl = null;
18
- authKey = null;
19
- }
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;
28
- }
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]);
33
- }
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
+ });
34
137
  }
35
- return result;
36
- }
37
- async function api(path, options = {}) {
38
- if (!remoteServerUrl || !authKey) {
39
- throw new Error("Remote database not initialized");
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);
40
153
  }
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}`
47
- }
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
48
195
  };
49
- if (options.body) {
50
- init.body = JSON.stringify(options.body);
196
+ }
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);
51
206
  }
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}`);
207
+ }
208
+ function ensureAppDataDirectory() {
209
+ const dir = getAppDataDirectory();
210
+ if (!existsSync(dir)) {
211
+ mkdirSync(dir, { recursive: true });
56
212
  }
57
- const text = await response.text();
58
- if (!text || text === "null") {
59
- return null;
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
+ }
223
+ }
224
+ currentDir = dirname(currentDir);
60
225
  }
61
- const parsed = JSON.parse(text);
62
- if (options.skipParseDates) {
63
- return parsed;
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;
231
+ }
64
232
  }
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 baseDir = dirname(import.meta.url.replace("file://", ""));
525
- const builtInCandidates = [
526
- resolve(baseDir, "../skills/default"),
527
- // dev: src/config → src/skills/default
528
- resolve(baseDir, "./skills/default")
529
- // prod: dist/ → dist/skills/default
530
- ];
531
- const builtInSkillsDir = builtInCandidates.find((p) => existsSync(p));
532
- if (builtInSkillsDir) {
533
- onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
534
- allDirectories.push(builtInSkillsDir);
535
- }
536
- return {
537
- alwaysLoadedDirs,
538
- onDemandDirs,
539
- agentsMdPath,
540
- allDirectories
541
- };
542
- }
543
- function getAppDataDirectory() {
544
- const appName = "sparkecoder";
545
- switch (platform()) {
546
- case "darwin":
547
- return join(homedir(), "Library", "Application Support", appName);
548
- case "win32":
549
- return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), appName);
550
- default:
551
- return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
552
- }
553
- }
554
- function ensureAppDataDirectory() {
555
- const dir = getAppDataDirectory();
556
- if (!existsSync(dir)) {
557
- mkdirSync(dir, { recursive: true });
558
- }
559
- return dir;
560
- }
561
- function findConfigFile(startDir) {
562
- let currentDir = startDir;
563
- while (currentDir !== dirname(currentDir)) {
564
- for (const fileName of CONFIG_FILE_NAMES) {
565
- const configPath = resolve(currentDir, fileName);
566
- if (existsSync(configPath)) {
567
- return configPath;
568
- }
569
- }
570
- currentDir = dirname(currentDir);
571
- }
572
- const appDataDir = getAppDataDirectory();
573
- for (const fileName of CONFIG_FILE_NAMES) {
574
- const configPath = join(appDataDir, fileName);
575
- if (existsSync(configPath)) {
576
- return configPath;
577
- }
578
- }
579
- return null;
233
+ return null;
580
234
  }
581
235
  function loadConfig(configPath, workingDirectory) {
582
236
  const cwd = workingDirectory || process.cwd();
@@ -698,6 +352,9 @@ function getConfig() {
698
352
  }
699
353
  function requiresApproval(toolName, sessionConfig) {
700
354
  const config = getConfig();
355
+ if (sessionConfig?.toolApprovals?.["*"] !== void 0) {
356
+ return sessionConfig.toolApprovals["*"];
357
+ }
701
358
  if (sessionConfig?.toolApprovals?.[toolName] !== void 0) {
702
359
  return sessionConfig.toolApprovals[toolName];
703
360
  }
@@ -821,48 +478,405 @@ function getApiKeyStatus() {
821
478
  } else {
822
479
  source = "env";
823
480
  }
824
- value = envValue;
825
- } else if (storedValue) {
826
- source = "storage";
827
- value = storedValue;
828
- }
829
- return {
830
- provider,
831
- envVar,
832
- configured: !!value,
833
- source,
834
- 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
+ }
835
649
  };
836
- });
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;
837
853
  }
838
- function maskApiKey(key) {
839
- if (key.length <= 12) {
840
- return "****" + key.slice(-4);
854
+ function getDb() {
855
+ if (!initialized) {
856
+ throw new Error("Database not initialized. Call initDatabase first.");
841
857
  }
842
- return key.slice(0, 4) + "..." + key.slice(-4);
858
+ return {};
843
859
  }
844
- var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
845
- var init_config = __esm({
846
- "src/config/index.ts"() {
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"() {
847
867
  "use strict";
848
- init_types();
849
- init_types();
850
- CONFIG_FILE_NAMES = [
851
- "sparkecoder.config.json",
852
- "sparkecoder.json",
853
- ".sparkecoder.json"
854
- ];
855
- cachedConfig = null;
856
- AUTH_KEY_FILE = "auth-key.json";
857
- API_KEYS_FILE = "api-keys.json";
858
- PROVIDER_ENV_MAP = {
859
- anthropic: "ANTHROPIC_API_KEY",
860
- openai: "OPENAI_API_KEY",
861
- google: "GOOGLE_GENERATIVE_AI_API_KEY",
862
- xai: "XAI_API_KEY",
863
- "ai-gateway": "AI_GATEWAY_API_KEY"
864
- };
865
- SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
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;
866
880
  }
867
881
  });
868
882
 
@@ -1607,18 +1621,176 @@ var init_semantic_search = __esm({
1607
1621
  import {
1608
1622
  streamText as streamText2,
1609
1623
  generateText as generateText3,
1610
- tool as tool11,
1624
+ tool as tool12,
1611
1625
  stepCountIs as stepCountIs2
1612
1626
  } from "ai";
1613
1627
 
1614
1628
  // src/agent/model.ts
1615
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();
1616
1778
  var ANTHROPIC_PREFIX = "anthropic/";
1617
1779
  function isAnthropicModel(modelId) {
1618
1780
  const normalized = modelId.trim().toLowerCase();
1619
1781
  return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
1620
1782
  }
1621
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
+ }
1622
1794
  return gateway(modelId.trim());
1623
1795
  }
1624
1796
  var SUBAGENT_MODELS = {
@@ -1630,7 +1802,7 @@ var SUBAGENT_MODELS = {
1630
1802
  // src/agent/index.ts
1631
1803
  init_db();
1632
1804
  init_config();
1633
- import { z as z12 } from "zod";
1805
+ import { z as z13 } from "zod";
1634
1806
  import { nanoid as nanoid3 } from "nanoid";
1635
1807
 
1636
1808
  // src/tools/bash.ts
@@ -4957,6 +5129,59 @@ Context: ${context}` : query;
4957
5129
 
4958
5130
  // src/tools/index.ts
4959
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
4960
5185
  init_semantic();
4961
5186
  init_semantic_search();
4962
5187
  async function createTools(options) {
@@ -5008,6 +5233,10 @@ async function createTools(options) {
5008
5233
  } catch {
5009
5234
  }
5010
5235
  }
5236
+ if (options.taskTools) {
5237
+ tools.complete_task = createCompleteTaskTool(options.taskTools);
5238
+ tools.task_failed = createTaskFailedTool(options.taskTools);
5239
+ }
5011
5240
  return tools;
5012
5241
  }
5013
5242
 
@@ -5325,6 +5554,30 @@ function formatTodosForContext(todos) {
5325
5554
  }
5326
5555
  return lines.join("\n");
5327
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
+ }
5328
5581
  function createSummaryPrompt(conversationHistory) {
5329
5582
  return `Please provide a concise summary of the following conversation history. Focus on:
5330
5583
  1. The main task or goal being worked on
@@ -5586,6 +5839,25 @@ ${this.summary}`
5586
5839
  }
5587
5840
  };
5588
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
+
5589
5861
  // src/agent/index.ts
5590
5862
  var approvalResolvers = /* @__PURE__ */ new Map();
5591
5863
  var Agent = class _Agent {
@@ -5805,6 +6077,145 @@ ${prompt}` });
5805
6077
  steps: result.steps
5806
6078
  };
5807
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
+ }
5808
6219
  /**
5809
6220
  * Wrap tools to add approval checking
5810
6221
  */
@@ -5818,9 +6229,9 @@ ${prompt}` });
5818
6229
  wrappedTools[name] = originalTool;
5819
6230
  continue;
5820
6231
  }
5821
- wrappedTools[name] = tool11({
6232
+ wrappedTools[name] = tool12({
5822
6233
  description: originalTool.description || "",
5823
- inputSchema: originalTool.inputSchema || z12.object({}),
6234
+ inputSchema: originalTool.inputSchema || z13.object({}),
5824
6235
  execute: async (input, toolOptions) => {
5825
6236
  const toolCallId = toolOptions.toolCallId || nanoid3();
5826
6237
  const execution = toolExecutionQueries.create({
@@ -5931,7 +6342,7 @@ ${prompt}` });
5931
6342
 
5932
6343
  // src/server/index.ts
5933
6344
  import "dotenv/config";
5934
- import { Hono as Hono5 } from "hono";
6345
+ import { Hono as Hono6 } from "hono";
5935
6346
  import { serve } from "@hono/node-server";
5936
6347
  import { cors } from "hono/cors";
5937
6348
  import { logger } from "hono/logger";
@@ -5945,7 +6356,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
5945
6356
  init_db();
5946
6357
  import { Hono } from "hono";
5947
6358
  import { zValidator } from "@hono/zod-validator";
5948
- import { z as z13 } from "zod";
6359
+ import { z as z14 } from "zod";
5949
6360
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
5950
6361
  import { readdir as readdir5 } from "fs/promises";
5951
6362
  import { join as join5, basename as basename4, extname as extname7, relative as relative9 } from "path";
@@ -5981,18 +6392,18 @@ function cleanupPendingInputs() {
5981
6392
  }
5982
6393
  }
5983
6394
  }
5984
- var createSessionSchema = z13.object({
5985
- name: z13.string().optional(),
5986
- workingDirectory: z13.string().optional(),
5987
- model: z13.string().optional(),
5988
- 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()
5989
6400
  });
5990
- var paginationQuerySchema = z13.object({
5991
- limit: z13.string().optional(),
5992
- offset: z13.string().optional()
6401
+ var paginationQuerySchema = z14.object({
6402
+ limit: z14.string().optional(),
6403
+ offset: z14.string().optional()
5993
6404
  });
5994
- var messagesQuerySchema = z13.object({
5995
- limit: z13.string().optional()
6405
+ var messagesQuerySchema = z14.object({
6406
+ limit: z14.string().optional()
5996
6407
  });
5997
6408
  sessions.get(
5998
6409
  "/",
@@ -6131,10 +6542,10 @@ sessions.get("/:id/tools", async (c) => {
6131
6542
  count: executions.length
6132
6543
  });
6133
6544
  });
6134
- var updateSessionSchema = z13.object({
6135
- model: z13.string().optional(),
6136
- name: z13.string().optional(),
6137
- 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()
6138
6549
  });
6139
6550
  sessions.patch(
6140
6551
  "/:id",
@@ -6204,8 +6615,8 @@ sessions.post("/:id/clear", async (c) => {
6204
6615
  await agent.clearContext();
6205
6616
  return c.json({ success: true, sessionId: id });
6206
6617
  });
6207
- var pendingInputSchema = z13.object({
6208
- text: z13.string()
6618
+ var pendingInputSchema = z14.object({
6619
+ text: z14.string()
6209
6620
  });
6210
6621
  sessions.post(
6211
6622
  "/:id/pending-input",
@@ -6236,13 +6647,13 @@ sessions.get("/:id/pending-input", async (c) => {
6236
6647
  createdAt: pending.createdAt.toISOString()
6237
6648
  });
6238
6649
  });
6239
- var devtoolsContextSchema = z13.object({
6240
- url: z13.string(),
6241
- path: z13.string(),
6242
- pageName: z13.string().optional(),
6243
- screenWidth: z13.number().optional(),
6244
- screenHeight: z13.number().optional(),
6245
- 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()
6246
6657
  });
6247
6658
  sessions.post(
6248
6659
  "/:id/devtools-context",
@@ -6533,10 +6944,10 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
6533
6944
  unlinkSync(filePath);
6534
6945
  return c.json({ success: true, id: attachmentId });
6535
6946
  });
6536
- var filesQuerySchema = z13.object({
6537
- query: z13.string().optional(),
6947
+ var filesQuerySchema = z14.object({
6948
+ query: z14.string().optional(),
6538
6949
  // Filter query (e.g., "src/com" to match "src/components")
6539
- limit: z13.string().optional()
6950
+ limit: z14.string().optional()
6540
6951
  // Max results (default 50)
6541
6952
  });
6542
6953
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -6714,7 +7125,7 @@ sessions.get(
6714
7125
  init_db();
6715
7126
  import { Hono as Hono2 } from "hono";
6716
7127
  import { zValidator as zValidator2 } from "@hono/zod-validator";
6717
- import { z as z14 } from "zod";
7128
+ import { z as z15 } from "zod";
6718
7129
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
6719
7130
  import { join as join6 } from "path";
6720
7131
  init_config();
@@ -6879,30 +7290,30 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
6879
7290
  ${prompt}`;
6880
7291
  }
6881
7292
  var agents = new Hono2();
6882
- var attachmentSchema = z14.object({
6883
- type: z14.enum(["image", "file"]),
6884
- data: z14.string(),
7293
+ var attachmentSchema = z15.object({
7294
+ type: z15.enum(["image", "file"]),
7295
+ data: z15.string(),
6885
7296
  // base64 data URL or raw base64
6886
- mediaType: z14.string().optional(),
6887
- filename: z14.string().optional()
7297
+ mediaType: z15.string().optional(),
7298
+ filename: z15.string().optional()
6888
7299
  });
6889
- var runPromptSchema = z14.object({
6890
- prompt: z14.string(),
7300
+ var runPromptSchema = z15.object({
7301
+ prompt: z15.string(),
6891
7302
  // Can be empty if attachments are provided
6892
- attachments: z14.array(attachmentSchema).optional()
7303
+ attachments: z15.array(attachmentSchema).optional()
6893
7304
  }).refine(
6894
7305
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
6895
7306
  { message: "Either prompt or attachments must be provided" }
6896
7307
  );
6897
- var quickStartSchema = z14.object({
6898
- prompt: z14.string().min(1),
6899
- name: z14.string().optional(),
6900
- workingDirectory: z14.string().optional(),
6901
- model: z14.string().optional(),
6902
- 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()
6903
7314
  });
6904
- var rejectSchema = z14.object({
6905
- reason: z14.string().optional()
7315
+ var rejectSchema = z15.object({
7316
+ reason: z15.string().optional()
6906
7317
  }).optional();
6907
7318
  var streamAbortControllers = /* @__PURE__ */ new Map();
6908
7319
  function getAttachmentsDirectory(sessionId) {
@@ -7332,7 +7743,7 @@ agents.get("/:id/watch", async (c) => {
7332
7743
  "Cache-Control": "no-cache",
7333
7744
  "Connection": "keep-alive",
7334
7745
  "x-vercel-ai-ui-message-stream": "v1",
7335
- "x-stream-id": streamId
7746
+ "x-stream-id": streamId ?? ""
7336
7747
  }
7337
7748
  });
7338
7749
  });
@@ -7690,7 +8101,7 @@ agents.post(
7690
8101
  init_config();
7691
8102
  import { Hono as Hono3 } from "hono";
7692
8103
  import { zValidator as zValidator3 } from "@hono/zod-validator";
7693
- import { z as z15 } from "zod";
8104
+ import { z as z16 } from "zod";
7694
8105
  import { readFileSync as readFileSync5 } from "fs";
7695
8106
  import { fileURLToPath as fileURLToPath3 } from "url";
7696
8107
  import { dirname as dirname6, join as join7 } from "path";
@@ -7800,9 +8211,9 @@ health.get("/api-keys", async (c) => {
7800
8211
  supportedProviders: SUPPORTED_PROVIDERS
7801
8212
  });
7802
8213
  });
7803
- var setApiKeySchema = z15.object({
7804
- provider: z15.string(),
7805
- apiKey: z15.string().min(1)
8214
+ var setApiKeySchema = z16.object({
8215
+ provider: z16.string(),
8216
+ apiKey: z16.string().min(1)
7806
8217
  });
7807
8218
  health.post(
7808
8219
  "/api-keys",
@@ -7841,13 +8252,13 @@ health.delete("/api-keys/:provider", async (c) => {
7841
8252
  // src/server/routes/terminals.ts
7842
8253
  import { Hono as Hono4 } from "hono";
7843
8254
  import { zValidator as zValidator4 } from "@hono/zod-validator";
7844
- import { z as z16 } from "zod";
8255
+ import { z as z17 } from "zod";
7845
8256
  init_db();
7846
8257
  var terminals = new Hono4();
7847
- var spawnSchema = z16.object({
7848
- command: z16.string(),
7849
- cwd: z16.string().optional(),
7850
- name: z16.string().optional()
8258
+ var spawnSchema = z17.object({
8259
+ command: z17.string(),
8260
+ cwd: z17.string().optional(),
8261
+ name: z17.string().optional()
7851
8262
  });
7852
8263
  terminals.post(
7853
8264
  "/:sessionId/terminals",
@@ -7928,8 +8339,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
7928
8339
  // We don't track exit codes in tmux mode
7929
8340
  });
7930
8341
  });
7931
- var logsQuerySchema = z16.object({
7932
- 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)
7933
8344
  });
7934
8345
  terminals.get(
7935
8346
  "/:sessionId/terminals/:terminalId/logs",
@@ -7953,8 +8364,8 @@ terminals.get(
7953
8364
  });
7954
8365
  }
7955
8366
  );
7956
- var killSchema = z16.object({
7957
- signal: z16.enum(["SIGTERM", "SIGKILL"]).optional()
8367
+ var killSchema = z17.object({
8368
+ signal: z17.enum(["SIGTERM", "SIGKILL"]).optional()
7958
8369
  });
7959
8370
  terminals.post(
7960
8371
  "/:sessionId/terminals/:terminalId/kill",
@@ -7968,8 +8379,8 @@ terminals.post(
7968
8379
  return c.json({ success: true, message: "Terminal killed" });
7969
8380
  }
7970
8381
  );
7971
- var writeSchema = z16.object({
7972
- input: z16.string()
8382
+ var writeSchema = z17.object({
8383
+ input: z17.string()
7973
8384
  });
7974
8385
  terminals.post(
7975
8386
  "/:sessionId/terminals/:terminalId/write",
@@ -8150,6 +8561,131 @@ data: ${JSON.stringify({ status: "stopped" })}
8150
8561
  );
8151
8562
  });
8152
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
+
8153
8689
  // src/server/index.ts
8154
8690
  init_config();
8155
8691
  init_db();
@@ -8486,7 +9022,7 @@ function stopWebUI() {
8486
9022
  }
8487
9023
  }
8488
9024
  async function createApp(options = {}) {
8489
- const app = new Hono5();
9025
+ const app = new Hono6();
8490
9026
  app.use("*", cors({
8491
9027
  origin: "*",
8492
9028
  // Allow all origins
@@ -8504,6 +9040,7 @@ async function createApp(options = {}) {
8504
9040
  app.route("/agents", agents);
8505
9041
  app.route("/sessions", terminals);
8506
9042
  app.route("/terminals", terminals);
9043
+ app.route("/tasks", tasks_default);
8507
9044
  app.get("/openapi.json", async (c) => {
8508
9045
  return c.json(generateOpenAPISpec());
8509
9046
  });