sparkecoder 0.1.59 → 0.1.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/agent/index.d.ts +4 -3
  2. package/dist/agent/index.js +662 -203
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +906 -129
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +3 -2
  7. package/dist/db/index.js.map +1 -1
  8. package/dist/{index-Csad1Nx4.d.ts → index-DuGtMaAJ.d.ts} +85 -4
  9. package/dist/index.d.ts +6 -5
  10. package/dist/index.js +1302 -711
  11. package/dist/index.js.map +1 -1
  12. package/dist/schema-C7Mm4Ykn.d.ts +1410 -0
  13. package/dist/{search-BETuS1vh.d.ts → search-C_IFImt1.d.ts} +3 -3
  14. package/dist/server/index.js +692 -101
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/skills/default/browser.md +143 -0
  17. package/dist/skills/default/code-review.md +122 -0
  18. package/dist/skills/default/debugging.md +105 -0
  19. package/dist/skills/default/refactoring.md +197 -0
  20. package/dist/tools/index.d.ts +54 -4
  21. package/dist/tools/index.js +304 -29
  22. package/dist/tools/index.js.map +1 -1
  23. package/package.json +6 -1
  24. package/src/skills/default/browser.md +143 -0
  25. package/src/skills/default/code-review.md +122 -0
  26. package/src/skills/default/debugging.md +105 -0
  27. package/src/skills/default/refactoring.md +197 -0
  28. package/web/.next/BUILD_ID +1 -1
  29. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  30. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  31. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  32. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  33. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  34. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  35. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  37. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  44. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  45. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  46. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  49. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  53. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  56. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  57. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  60. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  61. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  72. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  79. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  86. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  88. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  91. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  94. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  95. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  96. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  97. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  98. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  99. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  100. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  101. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  102. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_19289e11._.js → 2374f_03d22c16._.js} +1 -1
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b4b86c1f._.js → 2374f_0d0b1fb9._.js} +1 -1
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4858a1ea._.js → 2374f_3f7d6d28._.js} +1 -1
  106. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_51385fed._.js → 2374f_6166d14d._.js} +1 -1
  107. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_40e35a02._.js → 2374f_67b9525f._.js} +1 -1
  108. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fb95e3c9._.js → 2374f_6b436efc._.js} +1 -1
  109. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2f0d9f6f._.js → 2374f_784d851c._.js} +1 -1
  110. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_35475cbe._.js → 2374f_976b2ded._.js} +1 -1
  111. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_7db22cde._.js → 2374f_98eba491._.js} +1 -1
  112. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_90b8e4fb._.js → 2374f_99b7b533._.js} +1 -1
  113. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4666c827._.js → 2374f_a1a36483._.js} +1 -1
  114. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_076f03ec._.js → 2374f_b98835eb._.js} +1 -1
  115. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b17fce11._.js → 2374f_e19eaecc._.js} +1 -1
  116. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d8b9ce38._.js → 2374f_e3aec189._.js} +1 -1
  117. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__7775f784._.js → [root-of-the-server]__4f176a16._.js} +2 -2
  118. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d59f831d._.js +15 -0
  119. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_645f4b90._.js → web_693f514e._.js} +2 -2
  120. package/web/.next/standalone/web/.next/server/chunks/ssr/web_a42a2651._.js +7 -0
  121. package/web/.next/standalone/web/.next/server/chunks/ssr/web_dc6ce793._.js +7 -0
  122. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
  123. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  124. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  125. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  126. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  127. package/web/.next/standalone/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
  128. package/web/.next/standalone/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
  129. package/web/.next/standalone/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  130. package/web/.next/standalone/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
  131. package/web/.next/standalone/web/.next/static/chunks/95436454a7559b0d.js +7 -0
  132. package/web/.next/standalone/web/.next/static/chunks/a751ca474cc46212.js +5 -0
  133. package/web/.next/standalone/web/.next/static/static/chunks/0358a0e7a40cfb93.css +1 -0
  134. package/web/.next/standalone/web/.next/static/static/chunks/1ecde4c0d426a635.js +1 -0
  135. package/web/.next/standalone/web/.next/static/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  136. package/web/.next/standalone/web/.next/static/static/chunks/36688a049d72e8ab.js +5 -0
  137. package/web/.next/standalone/web/.next/static/static/chunks/95436454a7559b0d.js +7 -0
  138. package/web/.next/standalone/web/.next/static/static/chunks/a751ca474cc46212.js +5 -0
  139. package/web/.next/standalone/web/src/components/ai-elements/complete-task-tool.tsx +126 -0
  140. package/web/.next/standalone/web/src/components/chat-interface.tsx +68 -3
  141. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +18 -1
  142. package/web/.next/standalone/web/src/lib/api.ts +12 -0
  143. package/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
  144. package/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
  145. package/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
  146. package/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
  147. package/web/.next/static/chunks/95436454a7559b0d.js +7 -0
  148. package/web/.next/static/chunks/a751ca474cc46212.js +5 -0
  149. package/dist/schema-NcQknWCg.d.ts +0 -295
  150. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__bd396152._.js +0 -15
  151. package/web/.next/standalone/web/.next/server/chunks/ssr/web_9c9f0e3b._.js +0 -7
  152. package/web/.next/standalone/web/.next/server/chunks/ssr/web_d08270f7._.js +0 -7
  153. package/web/.next/standalone/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
  154. package/web/.next/standalone/web/.next/static/chunks/3f295b6960943c38.js +0 -1
  155. package/web/.next/standalone/web/.next/static/chunks/631b023d37a08635.js +0 -13
  156. package/web/.next/standalone/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
  157. package/web/.next/standalone/web/.next/static/chunks/e97212fcc8221479.js +0 -5
  158. package/web/.next/standalone/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
  159. package/web/.next/standalone/web/.next/static/static/chunks/2868b007ce5163fc.css +0 -1
  160. package/web/.next/standalone/web/.next/static/static/chunks/3f295b6960943c38.js +0 -1
  161. package/web/.next/standalone/web/.next/static/static/chunks/631b023d37a08635.js +0 -13
  162. package/web/.next/standalone/web/.next/static/static/chunks/a2b4737b190d1b54.js +0 -5
  163. package/web/.next/standalone/web/.next/static/static/chunks/e97212fcc8221479.js +0 -5
  164. package/web/.next/standalone/web/.next/static/static/chunks/f6e47c8a9766ce91.js +0 -7
  165. package/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
  166. package/web/.next/static/chunks/3f295b6960943c38.js +0 -1
  167. package/web/.next/static/chunks/631b023d37a08635.js +0 -13
  168. package/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
  169. package/web/.next/static/chunks/e97212fcc8221479.js +0 -5
  170. package/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
  171. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  172. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  173. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
  174. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  175. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  176. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
  177. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
  178. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
  179. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
@@ -8,6 +8,191 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/config/types.ts
12
+ import { z } from "zod";
13
+ var ToolApprovalConfigSchema, SkillMetadataSchema, TaskConfigSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
14
+ var init_types = __esm({
15
+ "src/config/types.ts"() {
16
+ "use strict";
17
+ ToolApprovalConfigSchema = z.object({
18
+ bash: z.boolean().optional().default(true),
19
+ write_file: z.boolean().optional().default(false),
20
+ read_file: z.boolean().optional().default(false),
21
+ load_skill: z.boolean().optional().default(false),
22
+ todo: z.boolean().optional().default(false)
23
+ });
24
+ SkillMetadataSchema = z.object({
25
+ name: z.string(),
26
+ description: z.string(),
27
+ // Whether to always inject this skill into context (vs on-demand loading)
28
+ alwaysApply: z.boolean().optional().default(false),
29
+ // Glob patterns - auto-inject when working with matching files
30
+ globs: z.array(z.string()).optional().default([])
31
+ });
32
+ TaskConfigSchema = z.object({
33
+ enabled: z.boolean(),
34
+ outputSchema: z.record(z.string(), z.unknown()),
35
+ webhookUrl: z.string().url().optional(),
36
+ maxIterations: z.number().optional(),
37
+ status: z.enum(["running", "completed", "failed"]),
38
+ result: z.unknown().optional(),
39
+ error: z.string().optional(),
40
+ iterations: z.number().optional()
41
+ });
42
+ SessionConfigSchema = z.object({
43
+ toolApprovals: z.record(z.string(), z.boolean()).optional(),
44
+ approvalWebhook: z.string().url().optional(),
45
+ skillsDirectory: z.string().optional(),
46
+ maxContextChars: z.number().optional().default(2e5),
47
+ task: TaskConfigSchema.optional()
48
+ });
49
+ VectorGatewayConfigSchema = z.object({
50
+ // Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
51
+ redisUrl: z.string().optional(),
52
+ // HTTP URL for database operations (or use VECTOR_HTTP_URL env var)
53
+ httpUrl: z.string().optional(),
54
+ // Embedding model to use (default: text-embedding-3-small)
55
+ embeddingModel: z.string().default("gemini-embedding-001"),
56
+ // Custom namespace override (auto-generated from git remote if not set)
57
+ namespace: z.string().optional(),
58
+ // File patterns to include in indexing
59
+ include: z.array(z.string()).optional().default([
60
+ "**/*.ts",
61
+ "**/*.tsx",
62
+ "**/*.js",
63
+ "**/*.jsx",
64
+ "**/*.py",
65
+ "**/*.go",
66
+ "**/*.rs",
67
+ "**/*.java",
68
+ "**/*.md",
69
+ "**/*.mdx",
70
+ "**/*.txt"
71
+ ]),
72
+ // File patterns to exclude from indexing
73
+ exclude: z.array(z.string()).optional().default([
74
+ "**/node_modules/**",
75
+ "**/dist/**",
76
+ "**/build/**",
77
+ "**/.git/**",
78
+ "**/.next/**",
79
+ "**/*.min.js",
80
+ "**/*.bundle.js",
81
+ "**/pnpm-lock.yaml",
82
+ "**/package-lock.json",
83
+ "**/yarn.lock",
84
+ "**/.test-workspace/**",
85
+ "**/.semantic-test-workspace/**",
86
+ "**/.semantic-integration-test/**"
87
+ ])
88
+ }).optional();
89
+ RemoteServerConfigSchema = z.object({
90
+ // URL of the remote server (e.g., https://agent.sparkecode.com)
91
+ url: z.string().url().optional(),
92
+ // Auth key for the remote server (auto-generated on first use if not set)
93
+ // Can also be set via SPARKECODER_AUTH_KEY env var
94
+ authKey: z.string().optional()
95
+ }).optional();
96
+ SparkcoderConfigSchema = z.object({
97
+ // Default model to use (Vercel AI Gateway format)
98
+ defaultModel: z.string().default("anthropic/claude-opus-4-6"),
99
+ // Working directory for file operations
100
+ workingDirectory: z.string().optional(),
101
+ // Tool approval settings
102
+ toolApprovals: ToolApprovalConfigSchema.optional().default({}),
103
+ // Approval webhook URL (called when approval is needed)
104
+ approvalWebhook: z.string().url().optional(),
105
+ // Skills configuration
106
+ skills: z.object({
107
+ // Directory containing skill files
108
+ directory: z.string().optional().default("./skills"),
109
+ // Additional skill directories to include
110
+ additionalDirectories: z.array(z.string()).optional().default([])
111
+ }).optional().default({}),
112
+ // Context management
113
+ context: z.object({
114
+ // Maximum context size before summarization (in characters)
115
+ maxChars: z.number().optional().default(2e5),
116
+ // Enable automatic summarization
117
+ autoSummarize: z.boolean().optional().default(true),
118
+ // Number of recent messages to keep after summarization
119
+ keepRecentMessages: z.number().optional().default(10)
120
+ }).optional().default({}),
121
+ // Server configuration
122
+ server: z.object({
123
+ port: z.number().default(3141),
124
+ host: z.string().default("127.0.0.1"),
125
+ // Public URL for web UI to connect to API (for Docker/remote access)
126
+ // If not set, defaults to http://{host}:{port}
127
+ publicUrl: z.string().url().optional()
128
+ }).default({ port: 3141, host: "127.0.0.1" }),
129
+ // Database path (used for local SQLite - ignored if remoteServer is configured)
130
+ databasePath: z.string().optional().default("./sparkecoder.db"),
131
+ // Remote server configuration (for centralized storage)
132
+ // If configured, uses remote MongoDB instead of local SQLite
133
+ remoteServer: RemoteServerConfigSchema,
134
+ // Vector Gateway configuration for semantic search
135
+ vectorGateway: VectorGatewayConfigSchema
136
+ });
137
+ }
138
+ });
139
+
140
+ // src/config/index.ts
141
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
142
+ import { resolve, dirname, join } from "path";
143
+ import { homedir, platform } from "os";
144
+ function getAppDataDirectory() {
145
+ const appName = "sparkecoder";
146
+ switch (platform()) {
147
+ case "darwin":
148
+ return join(homedir(), "Library", "Application Support", appName);
149
+ case "win32":
150
+ return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), appName);
151
+ default:
152
+ return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
153
+ }
154
+ }
155
+ function getConfig() {
156
+ if (!cachedConfig) {
157
+ throw new Error("Config not loaded. Call loadConfig first.");
158
+ }
159
+ return cachedConfig;
160
+ }
161
+ function requiresApproval(toolName, sessionConfig) {
162
+ const config = getConfig();
163
+ if (sessionConfig?.toolApprovals?.["*"] !== void 0) {
164
+ return sessionConfig.toolApprovals["*"];
165
+ }
166
+ if (sessionConfig?.toolApprovals?.[toolName] !== void 0) {
167
+ return sessionConfig.toolApprovals[toolName];
168
+ }
169
+ const globalApprovals = config.toolApprovals;
170
+ if (globalApprovals[toolName] !== void 0) {
171
+ return globalApprovals[toolName];
172
+ }
173
+ if (toolName === "bash") {
174
+ return true;
175
+ }
176
+ return false;
177
+ }
178
+ var cachedConfig, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
179
+ var init_config = __esm({
180
+ "src/config/index.ts"() {
181
+ "use strict";
182
+ init_types();
183
+ init_types();
184
+ cachedConfig = null;
185
+ PROVIDER_ENV_MAP = {
186
+ anthropic: "ANTHROPIC_API_KEY",
187
+ openai: "OPENAI_API_KEY",
188
+ google: "GOOGLE_GENERATIVE_AI_API_KEY",
189
+ xai: "XAI_API_KEY",
190
+ "ai-gateway": "AI_GATEWAY_API_KEY"
191
+ };
192
+ SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
193
+ }
194
+ });
195
+
11
196
  // src/db/remote.ts
12
197
  function parseDates(obj) {
13
198
  if (obj === null || obj === void 0) return obj;
@@ -298,177 +483,6 @@ var init_db = __esm({
298
483
  }
299
484
  });
300
485
 
301
- // src/config/types.ts
302
- import { z } from "zod";
303
- var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
304
- var init_types = __esm({
305
- "src/config/types.ts"() {
306
- "use strict";
307
- ToolApprovalConfigSchema = z.object({
308
- bash: z.boolean().optional().default(true),
309
- write_file: z.boolean().optional().default(false),
310
- read_file: z.boolean().optional().default(false),
311
- load_skill: z.boolean().optional().default(false),
312
- todo: z.boolean().optional().default(false)
313
- });
314
- SkillMetadataSchema = z.object({
315
- name: z.string(),
316
- description: z.string(),
317
- // Whether to always inject this skill into context (vs on-demand loading)
318
- alwaysApply: z.boolean().optional().default(false),
319
- // Glob patterns - auto-inject when working with matching files
320
- globs: z.array(z.string()).optional().default([])
321
- });
322
- SessionConfigSchema = z.object({
323
- toolApprovals: z.record(z.string(), z.boolean()).optional(),
324
- approvalWebhook: z.string().url().optional(),
325
- skillsDirectory: z.string().optional(),
326
- maxContextChars: z.number().optional().default(2e5)
327
- });
328
- VectorGatewayConfigSchema = z.object({
329
- // Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
330
- redisUrl: z.string().optional(),
331
- // HTTP URL for database operations (or use VECTOR_HTTP_URL env var)
332
- httpUrl: z.string().optional(),
333
- // Embedding model to use (default: text-embedding-3-small)
334
- embeddingModel: z.string().default("gemini-embedding-001"),
335
- // Custom namespace override (auto-generated from git remote if not set)
336
- namespace: z.string().optional(),
337
- // File patterns to include in indexing
338
- include: z.array(z.string()).optional().default([
339
- "**/*.ts",
340
- "**/*.tsx",
341
- "**/*.js",
342
- "**/*.jsx",
343
- "**/*.py",
344
- "**/*.go",
345
- "**/*.rs",
346
- "**/*.java",
347
- "**/*.md",
348
- "**/*.mdx",
349
- "**/*.txt"
350
- ]),
351
- // File patterns to exclude from indexing
352
- exclude: z.array(z.string()).optional().default([
353
- "**/node_modules/**",
354
- "**/dist/**",
355
- "**/build/**",
356
- "**/.git/**",
357
- "**/.next/**",
358
- "**/*.min.js",
359
- "**/*.bundle.js",
360
- "**/pnpm-lock.yaml",
361
- "**/package-lock.json",
362
- "**/yarn.lock",
363
- "**/.test-workspace/**",
364
- "**/.semantic-test-workspace/**",
365
- "**/.semantic-integration-test/**"
366
- ])
367
- }).optional();
368
- RemoteServerConfigSchema = z.object({
369
- // URL of the remote server (e.g., https://agent.sparkecode.com)
370
- url: z.string().url().optional(),
371
- // Auth key for the remote server (auto-generated on first use if not set)
372
- // Can also be set via SPARKECODER_AUTH_KEY env var
373
- authKey: z.string().optional()
374
- }).optional();
375
- SparkcoderConfigSchema = z.object({
376
- // Default model to use (Vercel AI Gateway format)
377
- defaultModel: z.string().default("anthropic/claude-opus-4-6"),
378
- // Working directory for file operations
379
- workingDirectory: z.string().optional(),
380
- // Tool approval settings
381
- toolApprovals: ToolApprovalConfigSchema.optional().default({}),
382
- // Approval webhook URL (called when approval is needed)
383
- approvalWebhook: z.string().url().optional(),
384
- // Skills configuration
385
- skills: z.object({
386
- // Directory containing skill files
387
- directory: z.string().optional().default("./skills"),
388
- // Additional skill directories to include
389
- additionalDirectories: z.array(z.string()).optional().default([])
390
- }).optional().default({}),
391
- // Context management
392
- context: z.object({
393
- // Maximum context size before summarization (in characters)
394
- maxChars: z.number().optional().default(2e5),
395
- // Enable automatic summarization
396
- autoSummarize: z.boolean().optional().default(true),
397
- // Number of recent messages to keep after summarization
398
- keepRecentMessages: z.number().optional().default(10)
399
- }).optional().default({}),
400
- // Server configuration
401
- server: z.object({
402
- port: z.number().default(3141),
403
- host: z.string().default("127.0.0.1"),
404
- // Public URL for web UI to connect to API (for Docker/remote access)
405
- // If not set, defaults to http://{host}:{port}
406
- publicUrl: z.string().url().optional()
407
- }).default({ port: 3141, host: "127.0.0.1" }),
408
- // Database path (used for local SQLite - ignored if remoteServer is configured)
409
- databasePath: z.string().optional().default("./sparkecoder.db"),
410
- // Remote server configuration (for centralized storage)
411
- // If configured, uses remote MongoDB instead of local SQLite
412
- remoteServer: RemoteServerConfigSchema,
413
- // Vector Gateway configuration for semantic search
414
- vectorGateway: VectorGatewayConfigSchema
415
- });
416
- }
417
- });
418
-
419
- // src/config/index.ts
420
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
421
- import { resolve, dirname, join } from "path";
422
- import { homedir, platform } from "os";
423
- function getAppDataDirectory() {
424
- const appName = "sparkecoder";
425
- switch (platform()) {
426
- case "darwin":
427
- return join(homedir(), "Library", "Application Support", appName);
428
- case "win32":
429
- return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), appName);
430
- default:
431
- return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
432
- }
433
- }
434
- function getConfig() {
435
- if (!cachedConfig) {
436
- throw new Error("Config not loaded. Call loadConfig first.");
437
- }
438
- return cachedConfig;
439
- }
440
- function requiresApproval(toolName, sessionConfig) {
441
- const config = getConfig();
442
- if (sessionConfig?.toolApprovals?.[toolName] !== void 0) {
443
- return sessionConfig.toolApprovals[toolName];
444
- }
445
- const globalApprovals = config.toolApprovals;
446
- if (globalApprovals[toolName] !== void 0) {
447
- return globalApprovals[toolName];
448
- }
449
- if (toolName === "bash") {
450
- return true;
451
- }
452
- return false;
453
- }
454
- var cachedConfig, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
455
- var init_config = __esm({
456
- "src/config/index.ts"() {
457
- "use strict";
458
- init_types();
459
- init_types();
460
- cachedConfig = null;
461
- PROVIDER_ENV_MAP = {
462
- anthropic: "ANTHROPIC_API_KEY",
463
- openai: "OPENAI_API_KEY",
464
- google: "GOOGLE_GENERATIVE_AI_API_KEY",
465
- xai: "XAI_API_KEY",
466
- "ai-gateway": "AI_GATEWAY_API_KEY"
467
- };
468
- SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
469
- }
470
- });
471
-
472
486
  // src/skills/index.ts
473
487
  var skills_exports = {};
474
488
  __export(skills_exports, {
@@ -484,7 +498,7 @@ __export(skills_exports, {
484
498
  loadSkillsFromDirectory: () => loadSkillsFromDirectory
485
499
  });
486
500
  import { readFile as readFile6, readdir } from "fs/promises";
487
- import { resolve as resolve6, basename, extname as extname3, relative as relative4 } from "path";
501
+ import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
488
502
  import { existsSync as existsSync8 } from "fs";
489
503
  import { minimatch } from "minimatch";
490
504
  function parseSkillFrontmatter(content) {
@@ -555,7 +569,7 @@ function parseSkillFrontmatter(content) {
555
569
  }
556
570
  }
557
571
  function getSkillNameFromPath(filePath) {
558
- return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
572
+ return basename(filePath, extname4(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
559
573
  }
560
574
  async function loadSkillsFromDirectory(directory, options = {}) {
561
575
  const {
@@ -860,7 +874,7 @@ var init_hasher = __esm({
860
874
  });
861
875
 
862
876
  // src/semantic/chunker.ts
863
- import { extname as extname5, basename as basename2 } from "path";
877
+ import { extname as extname6, basename as basename2 } from "path";
864
878
  var init_chunker = __esm({
865
879
  "src/semantic/chunker.ts"() {
866
880
  "use strict";
@@ -1210,18 +1224,176 @@ var init_semantic_search = __esm({
1210
1224
  import {
1211
1225
  streamText as streamText2,
1212
1226
  generateText as generateText3,
1213
- tool as tool11,
1227
+ tool as tool12,
1214
1228
  stepCountIs as stepCountIs2
1215
1229
  } from "ai";
1216
1230
 
1217
1231
  // src/agent/model.ts
1218
1232
  import { gateway } from "@ai-sdk/gateway";
1233
+
1234
+ // src/agent/remote-model.ts
1235
+ function serializePrompt(prompt) {
1236
+ return prompt.map((msg) => {
1237
+ if (!Array.isArray(msg.content)) return msg;
1238
+ return {
1239
+ ...msg,
1240
+ content: msg.content.map((part) => {
1241
+ if (part.type === "file" && part.data instanceof Uint8Array) {
1242
+ return {
1243
+ ...part,
1244
+ data: Buffer.from(part.data).toString("base64"),
1245
+ _base64: true
1246
+ };
1247
+ }
1248
+ return part;
1249
+ })
1250
+ };
1251
+ });
1252
+ }
1253
+ function deserializeValue(value) {
1254
+ if (value && typeof value === "object") {
1255
+ if (value.__uint8array && typeof value.data === "string") {
1256
+ return Buffer.from(value.data, "base64");
1257
+ }
1258
+ if (Array.isArray(value)) {
1259
+ return value.map(deserializeValue);
1260
+ }
1261
+ const result = {};
1262
+ for (const [k, v] of Object.entries(value)) {
1263
+ result[k] = deserializeValue(v);
1264
+ }
1265
+ return result;
1266
+ }
1267
+ return value;
1268
+ }
1269
+ function prepareOptions(options) {
1270
+ const { abortSignal, ...rest } = options;
1271
+ return {
1272
+ ...rest,
1273
+ prompt: serializePrompt(options.prompt)
1274
+ };
1275
+ }
1276
+ function createRemoteModel(modelId, config) {
1277
+ const baseUrl = config.url.replace(/\/$/, "");
1278
+ const headers = {
1279
+ "Content-Type": "application/json",
1280
+ "Authorization": `Bearer ${config.authKey}`
1281
+ };
1282
+ return {
1283
+ specificationVersion: "v3",
1284
+ provider: "remote-proxy",
1285
+ modelId,
1286
+ supportedUrls: {},
1287
+ async doGenerate(options) {
1288
+ const res = await fetch(`${baseUrl}/inference/generate`, {
1289
+ method: "POST",
1290
+ headers,
1291
+ body: JSON.stringify({
1292
+ modelId,
1293
+ options: prepareOptions(options)
1294
+ }),
1295
+ signal: options.abortSignal
1296
+ });
1297
+ if (!res.ok) {
1298
+ const err = await res.json().catch(() => ({}));
1299
+ throw new Error(
1300
+ `Remote inference failed (${res.status}): ${err.error || res.statusText}`
1301
+ );
1302
+ }
1303
+ const result = await res.json();
1304
+ return deserializeValue(result);
1305
+ },
1306
+ async doStream(options) {
1307
+ const res = await fetch(`${baseUrl}/inference/stream`, {
1308
+ method: "POST",
1309
+ headers,
1310
+ body: JSON.stringify({
1311
+ modelId,
1312
+ options: prepareOptions(options)
1313
+ }),
1314
+ signal: options.abortSignal
1315
+ });
1316
+ if (!res.ok) {
1317
+ const err = await res.json().catch(() => ({}));
1318
+ throw new Error(
1319
+ `Remote inference failed (${res.status}): ${err.error || res.statusText}`
1320
+ );
1321
+ }
1322
+ const reader = res.body.getReader();
1323
+ const decoder = new TextDecoder();
1324
+ let buffer = "";
1325
+ const stream = new ReadableStream({
1326
+ async pull(controller) {
1327
+ while (true) {
1328
+ const { done, value } = await reader.read();
1329
+ if (done) {
1330
+ if (buffer.trim()) {
1331
+ try {
1332
+ const parsed = deserializeValue(JSON.parse(buffer.trim()));
1333
+ if (parsed.type === "error") {
1334
+ controller.error(new Error(parsed.error));
1335
+ } else {
1336
+ controller.enqueue(parsed);
1337
+ }
1338
+ } catch {
1339
+ }
1340
+ }
1341
+ controller.close();
1342
+ return;
1343
+ }
1344
+ buffer += decoder.decode(value, { stream: true });
1345
+ const lines = buffer.split("\n");
1346
+ buffer = lines.pop() || "";
1347
+ for (const line of lines) {
1348
+ if (!line.trim()) continue;
1349
+ try {
1350
+ const parsed = deserializeValue(JSON.parse(line));
1351
+ if (parsed.type === "error") {
1352
+ controller.error(new Error(parsed.error));
1353
+ return;
1354
+ }
1355
+ controller.enqueue(parsed);
1356
+ } catch {
1357
+ }
1358
+ }
1359
+ }
1360
+ },
1361
+ cancel() {
1362
+ reader.cancel();
1363
+ }
1364
+ });
1365
+ const responseHeaders = {};
1366
+ res.headers.forEach((v, k) => {
1367
+ if (k.startsWith("x-upstream-")) {
1368
+ responseHeaders[k.replace("x-upstream-", "")] = v;
1369
+ }
1370
+ });
1371
+ return {
1372
+ stream,
1373
+ response: Object.keys(responseHeaders).length > 0 ? { headers: responseHeaders } : void 0
1374
+ };
1375
+ }
1376
+ };
1377
+ }
1378
+
1379
+ // src/agent/model.ts
1380
+ init_config();
1219
1381
  var ANTHROPIC_PREFIX = "anthropic/";
1220
1382
  function isAnthropicModel(modelId) {
1221
1383
  const normalized = modelId.trim().toLowerCase();
1222
1384
  return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
1223
1385
  }
1224
1386
  function resolveModel(modelId) {
1387
+ try {
1388
+ const config = getConfig();
1389
+ if (config.resolvedRemoteServer.isConfigured) {
1390
+ return createRemoteModel(modelId.trim(), {
1391
+ url: config.resolvedRemoteServer.url,
1392
+ authKey: config.resolvedRemoteServer.authKey
1393
+ });
1394
+ }
1395
+ } catch {
1396
+ }
1225
1397
  return gateway(modelId.trim());
1226
1398
  }
1227
1399
  var SUBAGENT_MODELS = {
@@ -1233,7 +1405,7 @@ var SUBAGENT_MODELS = {
1233
1405
  // src/agent/index.ts
1234
1406
  init_db();
1235
1407
  init_config();
1236
- import { z as z12 } from "zod";
1408
+ import { z as z13 } from "zod";
1237
1409
  import { nanoid as nanoid3 } from "nanoid";
1238
1410
 
1239
1411
  // src/tools/bash.ts
@@ -1766,26 +1938,41 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
1766
1938
  import { tool as tool2 } from "ai";
1767
1939
  import { z as z3 } from "zod";
1768
1940
  import { readFile as readFile2, stat } from "fs/promises";
1769
- import { resolve as resolve2, relative, isAbsolute } from "path";
1941
+ import { resolve as resolve2, relative, isAbsolute, extname } from "path";
1770
1942
  import { existsSync as existsSync3 } from "fs";
1771
1943
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
1944
+ var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
1772
1945
  var MAX_OUTPUT_CHARS3 = 5e4;
1946
+ var IMAGE_EXTENSIONS = {
1947
+ ".png": "image/png",
1948
+ ".jpg": "image/jpeg",
1949
+ ".jpeg": "image/jpeg",
1950
+ ".gif": "image/gif",
1951
+ ".webp": "image/webp"
1952
+ };
1953
+ function isImageFile(filePath) {
1954
+ return extname(filePath).toLowerCase() in IMAGE_EXTENSIONS;
1955
+ }
1956
+ function getImageMediaType(filePath) {
1957
+ return IMAGE_EXTENSIONS[extname(filePath).toLowerCase()] || "image/png";
1958
+ }
1773
1959
  var readFileInputSchema = z3.object({
1774
- path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute."),
1775
- startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed)"),
1776
- endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive)")
1960
+ path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute. Supports text files and images (png, jpg, jpeg, gif, webp)."),
1961
+ startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed). Only for text files."),
1962
+ endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive). Only for text files.")
1777
1963
  });
1778
1964
  function createReadFileTool(options) {
1779
1965
  return tool2({
1780
1966
  description: `Read the contents of a file. Provide a path relative to the working directory (${options.workingDirectory}) or an absolute path.
1781
- Large files will be automatically truncated. Binary files are not supported.
1782
- Use this to understand existing code, check file contents, or gather context.`,
1967
+ Supports text files (automatically truncated if large) and image files (png, jpg, jpeg, gif, webp).
1968
+ For images, the file contents are returned as visual data you can see and analyze.
1969
+ Use this to understand existing code, check file contents, view screenshots, or gather context.`,
1783
1970
  inputSchema: readFileInputSchema,
1784
- execute: async ({ path, startLine, endLine }) => {
1971
+ execute: async ({ path: filePath, startLine, endLine }) => {
1785
1972
  try {
1786
- const absolutePath = isAbsolute(path) ? path : resolve2(options.workingDirectory, path);
1973
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve2(options.workingDirectory, filePath);
1787
1974
  const relativePath = relative(options.workingDirectory, absolutePath);
1788
- if (relativePath.startsWith("..") && !isAbsolute(path)) {
1975
+ if (relativePath.startsWith("..") && !isAbsolute(filePath)) {
1789
1976
  return {
1790
1977
  success: false,
1791
1978
  error: "Path escapes the working directory. Use an absolute path if intentional.",
@@ -1795,22 +1982,43 @@ Use this to understand existing code, check file contents, or gather context.`,
1795
1982
  if (!existsSync3(absolutePath)) {
1796
1983
  return {
1797
1984
  success: false,
1798
- error: `File not found: ${path}`,
1985
+ error: `File not found: ${filePath}`,
1799
1986
  content: null
1800
1987
  };
1801
1988
  }
1802
1989
  const stats = await stat(absolutePath);
1803
- if (stats.size > MAX_FILE_SIZE) {
1990
+ if (stats.isDirectory()) {
1804
1991
  return {
1805
1992
  success: false,
1806
- error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
1993
+ error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
1807
1994
  content: null
1808
1995
  };
1809
1996
  }
1810
- if (stats.isDirectory()) {
1997
+ if (isImageFile(absolutePath)) {
1998
+ if (stats.size > MAX_IMAGE_SIZE) {
1999
+ return {
2000
+ success: false,
2001
+ error: `Image is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_IMAGE_SIZE / 1024 / 1024}MB.`,
2002
+ content: null
2003
+ };
2004
+ }
2005
+ const buffer = await readFile2(absolutePath);
2006
+ const base64 = buffer.toString("base64");
2007
+ const mediaType = getImageMediaType(absolutePath);
2008
+ return {
2009
+ success: true,
2010
+ path: absolutePath,
2011
+ relativePath: relative(options.workingDirectory, absolutePath),
2012
+ content: `[Image: ${relativePath} (${mediaType}, ${(stats.size / 1024).toFixed(1)}KB)]`,
2013
+ mediaType,
2014
+ imageData: base64,
2015
+ sizeBytes: stats.size
2016
+ };
2017
+ }
2018
+ if (stats.size > MAX_FILE_SIZE) {
1811
2019
  return {
1812
2020
  success: false,
1813
- error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
2021
+ error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
1814
2022
  content: null
1815
2023
  };
1816
2024
  }
@@ -1826,9 +2034,7 @@ Use this to understand existing code, check file contents, or gather context.`,
1826
2034
  content: null
1827
2035
  };
1828
2036
  }
1829
- content = lines.slice(start, end).join("\n");
1830
- const lineNumbers = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
1831
- content = lineNumbers;
2037
+ content = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
1832
2038
  }
1833
2039
  const truncatedContent = truncateOutput(content, MAX_OUTPUT_CHARS3);
1834
2040
  const wasTruncated = truncatedContent.length < content.length;
@@ -1855,6 +2061,19 @@ Use this to understand existing code, check file contents, or gather context.`,
1855
2061
  content: null
1856
2062
  };
1857
2063
  }
2064
+ },
2065
+ toModelOutput: ({ output }) => {
2066
+ if (output && typeof output === "object" && "imageData" in output && output.imageData) {
2067
+ const result = output;
2068
+ return {
2069
+ type: "content",
2070
+ value: [
2071
+ { type: "text", text: result.content },
2072
+ { type: "image-data", data: result.imageData, mediaType: result.mediaType }
2073
+ ]
2074
+ };
2075
+ }
2076
+ return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output };
1858
2077
  }
1859
2078
  });
1860
2079
  }
@@ -1919,7 +2138,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
1919
2138
  }
1920
2139
 
1921
2140
  // src/lsp/index.ts
1922
- import { extname as extname2, dirname as dirname4 } from "path";
2141
+ import { extname as extname3, dirname as dirname4 } from "path";
1923
2142
 
1924
2143
  // src/lsp/servers.ts
1925
2144
  import { spawn } from "child_process";
@@ -2042,9 +2261,9 @@ import {
2042
2261
  import { pathToFileURL, fileURLToPath } from "url";
2043
2262
  import { readFile as readFile4 } from "fs/promises";
2044
2263
  import { existsSync as existsSync6 } from "fs";
2045
- import { extname, normalize } from "path";
2264
+ import { extname as extname2, normalize } from "path";
2046
2265
  function getLanguageId(filePath) {
2047
- const ext = extname(filePath).toLowerCase();
2266
+ const ext = extname2(filePath).toLowerCase();
2048
2267
  const map = {
2049
2268
  ".ts": "typescript",
2050
2269
  ".tsx": "typescriptreact",
@@ -2432,7 +2651,7 @@ var state = {
2432
2651
  };
2433
2652
  async function getClientForFile(filePath) {
2434
2653
  const normalized = normalizePath(filePath);
2435
- const ext = extname2(normalized);
2654
+ const ext = extname3(normalized);
2436
2655
  const serverDef = getServerForExtension(ext);
2437
2656
  if (!serverDef) {
2438
2657
  return null;
@@ -2525,7 +2744,7 @@ async function formatDiagnosticsOutput(filePath, options = {}) {
2525
2744
  return formatDiagnosticsForAgent(filePath, diagnostics, options);
2526
2745
  }
2527
2746
  function isSupported(filePath) {
2528
- const ext = extname2(filePath);
2747
+ const ext = extname3(filePath);
2529
2748
  return getServerForExtension(ext) !== null;
2530
2749
  }
2531
2750
 
@@ -2952,7 +3171,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
2952
3171
  // src/tools/linter.ts
2953
3172
  import { tool as tool6 } from "ai";
2954
3173
  import { z as z7 } from "zod";
2955
- import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname4 } from "path";
3174
+ import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
2956
3175
  import { existsSync as existsSync9 } from "fs";
2957
3176
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
2958
3177
  var linterInputSchema = z7.object({
@@ -2975,7 +3194,7 @@ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
2975
3194
  }
2976
3195
  await walk(fullPath);
2977
3196
  } else if (entry.isFile()) {
2978
- const ext = extname4(entry.name);
3197
+ const ext = extname5(entry.name);
2979
3198
  if (supportedExtensions.includes(ext)) {
2980
3199
  files.push(fullPath);
2981
3200
  }
@@ -4312,6 +4531,59 @@ Context: ${context}` : query;
4312
4531
 
4313
4532
  // src/tools/index.ts
4314
4533
  init_semantic_search();
4534
+
4535
+ // src/tools/task.ts
4536
+ import { tool as tool11 } from "ai";
4537
+ import { z as z12 } from "zod";
4538
+ import Ajv from "ajv";
4539
+ var ajv = new Ajv({ allErrors: true });
4540
+ function createCompleteTaskTool(options) {
4541
+ const validate = ajv.compile(options.outputSchema);
4542
+ return tool11({
4543
+ 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.",
4544
+ inputSchema: z12.object({
4545
+ result: z12.record(z12.string(), z12.unknown()).describe("The task result as a JSON object matching the output schema")
4546
+ }),
4547
+ execute: async (input) => {
4548
+ const valid = validate(input.result);
4549
+ if (!valid) {
4550
+ const errors = validate.errors?.map((e) => ({
4551
+ path: e.instancePath || "/",
4552
+ message: e.message,
4553
+ params: e.params
4554
+ }));
4555
+ return {
4556
+ status: "validation_error",
4557
+ message: "The result does not match the required output schema. Fix the errors and call complete_task again.",
4558
+ errors,
4559
+ expectedSchema: options.outputSchema
4560
+ };
4561
+ }
4562
+ options.onComplete({ status: "completed", result: input.result });
4563
+ return {
4564
+ status: "completed",
4565
+ message: "Task completed successfully."
4566
+ };
4567
+ }
4568
+ });
4569
+ }
4570
+ function createTaskFailedTool(options) {
4571
+ return tool11({
4572
+ description: "Call this tool if you are unable to complete the task. Provide a clear reason explaining why the task cannot be completed.",
4573
+ inputSchema: z12.object({
4574
+ reason: z12.string().describe("Explanation of why the task cannot be completed")
4575
+ }),
4576
+ execute: async (input) => {
4577
+ options.onComplete({ status: "failed", error: input.reason });
4578
+ return {
4579
+ status: "failed",
4580
+ message: `Task marked as failed: ${input.reason}`
4581
+ };
4582
+ }
4583
+ });
4584
+ }
4585
+
4586
+ // src/tools/index.ts
4315
4587
  init_semantic();
4316
4588
  init_semantic_search();
4317
4589
  async function createTools(options) {
@@ -4363,6 +4635,10 @@ async function createTools(options) {
4363
4635
  } catch {
4364
4636
  }
4365
4637
  }
4638
+ if (options.taskTools) {
4639
+ tools.complete_task = createCompleteTaskTool(options.taskTools);
4640
+ tools.task_failed = createTaskFailedTool(options.taskTools);
4641
+ }
4366
4642
  return tools;
4367
4643
  }
4368
4644
 
@@ -4680,6 +4956,30 @@ function formatTodosForContext(todos) {
4680
4956
  }
4681
4957
  return lines.join("\n");
4682
4958
  }
4959
+ function buildTaskPromptAddendum(outputSchema) {
4960
+ return `
4961
+ ## Task Mode
4962
+
4963
+ You are running in **task mode**. You have been given a specific task to complete autonomously.
4964
+
4965
+ ### Rules
4966
+ 1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
4967
+ 2. Keep working until the task is fully complete.
4968
+ 3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
4969
+ 4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
4970
+ 5. Do NOT stop without calling one of these two tools.
4971
+
4972
+ ### Output Schema
4973
+ The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
4974
+ \`\`\`json
4975
+ ${JSON.stringify(outputSchema, null, 2)}
4976
+ \`\`\`
4977
+
4978
+ ### Completion Tools
4979
+ - **\`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.
4980
+ - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
4981
+ `;
4982
+ }
4683
4983
  function createSummaryPrompt(conversationHistory) {
4684
4984
  return `Please provide a concise summary of the following conversation history. Focus on:
4685
4985
  1. The main task or goal being worked on
@@ -4941,6 +5241,25 @@ ${this.summary}`
4941
5241
  }
4942
5242
  };
4943
5243
 
5244
+ // src/utils/webhook.ts
5245
+ async function sendWebhook(url, event) {
5246
+ try {
5247
+ const controller = new AbortController();
5248
+ const timeout = setTimeout(() => controller.abort(), 5e3);
5249
+ await fetch(url, {
5250
+ method: "POST",
5251
+ headers: {
5252
+ "Content-Type": "application/json",
5253
+ "X-SparkECoder-Event": event.type
5254
+ },
5255
+ body: JSON.stringify(event),
5256
+ signal: controller.signal
5257
+ });
5258
+ clearTimeout(timeout);
5259
+ } catch {
5260
+ }
5261
+ }
5262
+
4944
5263
  // src/agent/index.ts
4945
5264
  var approvalResolvers = /* @__PURE__ */ new Map();
4946
5265
  var Agent = class _Agent {
@@ -5160,6 +5479,145 @@ ${prompt}` });
5160
5479
  steps: result.steps
5161
5480
  };
5162
5481
  }
5482
+ /**
5483
+ * Run the agent in task mode — loops autonomously until the agent calls
5484
+ * complete_task or task_failed (or hits maxIterations).
5485
+ * All tools run without approval. Webhook events are fired throughout.
5486
+ */
5487
+ async runTask(options) {
5488
+ const config = getConfig();
5489
+ const maxIterations = options.taskConfig.maxIterations ?? 50;
5490
+ const webhookUrl = options.taskConfig.webhookUrl;
5491
+ const fireWebhook = (type, data) => {
5492
+ if (!webhookUrl) return;
5493
+ sendWebhook(webhookUrl, {
5494
+ type,
5495
+ taskId: this.session.id,
5496
+ sessionId: this.session.id,
5497
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5498
+ data
5499
+ });
5500
+ };
5501
+ const completion = { signal: null };
5502
+ const onComplete = (signal) => {
5503
+ completion.signal = signal;
5504
+ };
5505
+ const taskTools = await createTools({
5506
+ sessionId: this.session.id,
5507
+ workingDirectory: this.session.workingDirectory,
5508
+ skillsDirectories: config.resolvedSkillsDirectories,
5509
+ onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
5510
+ onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
5511
+ onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
5512
+ taskTools: {
5513
+ outputSchema: options.taskConfig.outputSchema,
5514
+ onComplete
5515
+ }
5516
+ });
5517
+ const baseSystemPrompt = await buildSystemPrompt({
5518
+ workingDirectory: this.session.workingDirectory,
5519
+ skillsDirectories: config.resolvedSkillsDirectories,
5520
+ sessionId: this.session.id,
5521
+ discoveredSkills: config.discoveredSkills,
5522
+ activeFiles: []
5523
+ });
5524
+ const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
5525
+ const systemPrompt = `${baseSystemPrompt}
5526
+
5527
+ ${taskAddendum}`;
5528
+ fireWebhook("task.started", { prompt: options.prompt });
5529
+ this.context.addUserMessage(options.prompt);
5530
+ let iteration = 0;
5531
+ while (iteration < maxIterations) {
5532
+ iteration++;
5533
+ if (options.abortSignal?.aborted) {
5534
+ const cancelError = "Task was cancelled";
5535
+ fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
5536
+ return { status: "failed", error: cancelError, iterations: iteration };
5537
+ }
5538
+ const messages = await this.context.getMessages();
5539
+ const useAnthropic = isAnthropicModel(this.session.model);
5540
+ const result = await generateText3({
5541
+ model: resolveModel(this.session.model),
5542
+ system: systemPrompt,
5543
+ messages,
5544
+ tools: taskTools,
5545
+ stopWhen: stepCountIs2(500),
5546
+ abortSignal: options.abortSignal,
5547
+ providerOptions: useAnthropic ? {
5548
+ anthropic: {
5549
+ thinking: { type: "enabled", budgetTokens: 1e4 }
5550
+ }
5551
+ } : void 0,
5552
+ onStepFinish: (step) => {
5553
+ options.onStepFinish?.(step);
5554
+ fireWebhook("task.step_finished", { iteration, text: step.text });
5555
+ }
5556
+ });
5557
+ const responseMessages = result.response.messages;
5558
+ this.context.addResponseMessages(responseMessages);
5559
+ if (result.text) {
5560
+ options.onText?.(result.text);
5561
+ fireWebhook("task.message", { iteration, text: result.text });
5562
+ }
5563
+ for (const step of result.steps) {
5564
+ if (step.toolCalls) {
5565
+ for (const tc of step.toolCalls) {
5566
+ options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.args });
5567
+ fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.args });
5568
+ }
5569
+ }
5570
+ if (step.toolResults) {
5571
+ for (const tr of step.toolResults) {
5572
+ options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.result });
5573
+ fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.result });
5574
+ }
5575
+ }
5576
+ }
5577
+ if (completion.signal) {
5578
+ const sig = completion.signal;
5579
+ const finalStatus = sig.status;
5580
+ const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
5581
+ fireWebhook(eventType, {
5582
+ status: finalStatus,
5583
+ result: sig.result,
5584
+ error: sig.error,
5585
+ iterations: iteration
5586
+ });
5587
+ const updatedTask2 = {
5588
+ ...options.taskConfig,
5589
+ status: finalStatus,
5590
+ result: sig.result,
5591
+ error: sig.error,
5592
+ iterations: iteration
5593
+ };
5594
+ await sessionQueries.update(this.session.id, {
5595
+ config: { ...this.session.config, task: updatedTask2 }
5596
+ });
5597
+ return {
5598
+ status: finalStatus,
5599
+ result: sig.result,
5600
+ error: sig.error,
5601
+ iterations: iteration
5602
+ };
5603
+ }
5604
+ this.context.addUserMessage(
5605
+ "Continue working on the task. When done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
5606
+ );
5607
+ }
5608
+ const timeoutError = `Task did not complete within ${maxIterations} iterations`;
5609
+ fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
5610
+ const updatedTask = {
5611
+ ...options.taskConfig,
5612
+ status: "failed",
5613
+ error: timeoutError,
5614
+ iterations: iteration
5615
+ };
5616
+ await sessionQueries.update(this.session.id, {
5617
+ config: { ...this.session.config, task: updatedTask }
5618
+ });
5619
+ return { status: "failed", error: timeoutError, iterations: iteration };
5620
+ }
5163
5621
  /**
5164
5622
  * Wrap tools to add approval checking
5165
5623
  */
@@ -5173,9 +5631,9 @@ ${prompt}` });
5173
5631
  wrappedTools[name] = originalTool;
5174
5632
  continue;
5175
5633
  }
5176
- wrappedTools[name] = tool11({
5634
+ wrappedTools[name] = tool12({
5177
5635
  description: originalTool.description || "",
5178
- inputSchema: originalTool.inputSchema || z12.object({}),
5636
+ inputSchema: originalTool.inputSchema || z13.object({}),
5179
5637
  execute: async (input, toolOptions) => {
5180
5638
  const toolCallId = toolOptions.toolCallId || nanoid3();
5181
5639
  const execution = toolExecutionQueries.create({
@@ -5286,6 +5744,7 @@ ${prompt}` });
5286
5744
  export {
5287
5745
  Agent,
5288
5746
  ContextManager,
5289
- buildSystemPrompt
5747
+ buildSystemPrompt,
5748
+ buildTaskPromptAddendum
5290
5749
  };
5291
5750
  //# sourceMappingURL=index.js.map