sparkecoder 0.1.9 → 0.1.11

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 (180) hide show
  1. package/dist/agent/index.js +768 -34
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +850 -120
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +831 -106
  7. package/dist/index.js.map +1 -1
  8. package/dist/server/index.js +831 -106
  9. package/dist/server/index.js.map +1 -1
  10. package/dist/tools/index.d.ts +56 -34
  11. package/dist/tools/index.js +866 -127
  12. package/dist/tools/index.js.map +1 -1
  13. package/package.json +2 -1
  14. package/web/.next/BUILD_ID +1 -1
  15. package/web/.next/build-manifest.json +2 -2
  16. package/web/.next/cache/.previewinfo +1 -1
  17. package/web/.next/cache/.rscinfo +1 -1
  18. package/web/.next/cache/.tsbuildinfo +1 -1
  19. package/web/.next/cache/config.json +3 -3
  20. package/web/.next/dev/cache/.rscinfo +1 -1
  21. package/web/.next/dev/cache/config.json +3 -3
  22. package/web/.next/dev/cache/turbopack/731ace46/00000001.sst +0 -0
  23. package/web/.next/dev/cache/turbopack/731ace46/00000002.sst +0 -0
  24. package/web/.next/dev/cache/turbopack/731ace46/00000004.sst +0 -0
  25. package/web/.next/dev/cache/turbopack/731ace46/00000005.sst +0 -0
  26. package/web/.next/dev/cache/turbopack/731ace46/00000007.meta +0 -0
  27. package/web/.next/dev/cache/turbopack/731ace46/00000008.meta +0 -0
  28. package/web/.next/dev/cache/turbopack/731ace46/00000010.meta +0 -0
  29. package/web/.next/dev/cache/turbopack/731ace46/00000011.sst +0 -0
  30. package/web/.next/dev/cache/turbopack/731ace46/00000012.sst +0 -0
  31. package/web/.next/dev/cache/turbopack/731ace46/00000014.sst +0 -0
  32. package/web/.next/dev/cache/turbopack/731ace46/00000015.sst +0 -0
  33. package/web/.next/dev/cache/turbopack/731ace46/00000017.meta +0 -0
  34. package/web/.next/dev/cache/turbopack/731ace46/00000018.meta +0 -0
  35. package/web/.next/dev/cache/turbopack/731ace46/00000019.meta +0 -0
  36. package/web/.next/dev/cache/turbopack/731ace46/00000020.meta +0 -0
  37. package/web/.next/dev/cache/turbopack/731ace46/00000021.sst +0 -0
  38. package/web/.next/dev/cache/turbopack/731ace46/00000022.sst +0 -0
  39. package/web/.next/dev/cache/turbopack/731ace46/00000023.sst +0 -0
  40. package/web/.next/dev/cache/turbopack/731ace46/{00000029.meta → 00000025.meta} +0 -0
  41. package/web/.next/dev/cache/turbopack/731ace46/00000026.meta +0 -0
  42. package/web/.next/dev/cache/turbopack/731ace46/00000027.sst +0 -0
  43. package/web/.next/dev/cache/turbopack/731ace46/00000028.sst +0 -0
  44. package/web/.next/dev/cache/turbopack/731ace46/00000029.sst +0 -0
  45. package/web/.next/dev/cache/turbopack/731ace46/00000030.meta +0 -0
  46. package/web/.next/dev/cache/turbopack/731ace46/{00000039.meta → 00000031.meta} +0 -0
  47. package/web/.next/dev/cache/turbopack/731ace46/{00000040.meta → 00000032.meta} +0 -0
  48. package/web/.next/dev/cache/turbopack/731ace46/00000033.sst +0 -0
  49. package/web/.next/dev/cache/turbopack/731ace46/00000034.sst +0 -0
  50. package/web/.next/dev/cache/turbopack/731ace46/00000035.sst +0 -0
  51. package/web/.next/dev/cache/turbopack/731ace46/00000036.meta +0 -0
  52. package/web/.next/dev/cache/turbopack/731ace46/00000037.meta +0 -0
  53. package/web/.next/dev/cache/turbopack/731ace46/00000038.meta +0 -0
  54. package/web/.next/dev/cache/turbopack/731ace46/00000039.sst +0 -0
  55. package/web/.next/dev/cache/turbopack/731ace46/00000040.sst +0 -0
  56. package/web/.next/dev/cache/turbopack/731ace46/00000041.sst +0 -0
  57. package/web/.next/dev/cache/turbopack/731ace46/00000042.meta +0 -0
  58. package/web/.next/dev/cache/turbopack/731ace46/{00000045.meta → 00000043.meta} +0 -0
  59. package/web/.next/dev/cache/turbopack/731ace46/00000044.meta +0 -0
  60. package/web/.next/dev/cache/turbopack/731ace46/00000045.sst +0 -0
  61. package/web/.next/dev/cache/turbopack/731ace46/00000046.sst +0 -0
  62. package/web/.next/dev/cache/turbopack/731ace46/00000047.sst +0 -0
  63. package/web/.next/dev/cache/turbopack/731ace46/00000048.meta +0 -0
  64. package/web/.next/dev/cache/turbopack/731ace46/00000049.meta +0 -0
  65. package/web/.next/dev/cache/turbopack/731ace46/00000050.meta +0 -0
  66. package/web/.next/dev/cache/turbopack/731ace46/00000051.sst +0 -0
  67. package/web/.next/dev/cache/turbopack/731ace46/00000052.sst +0 -0
  68. package/web/.next/dev/cache/turbopack/731ace46/00000053.sst +0 -0
  69. package/web/.next/dev/cache/turbopack/731ace46/00000054.meta +0 -0
  70. package/web/.next/dev/cache/turbopack/731ace46/00000055.meta +0 -0
  71. package/web/.next/dev/cache/turbopack/731ace46/00000056.meta +0 -0
  72. package/web/.next/dev/cache/turbopack/731ace46/00000057.sst +0 -0
  73. package/web/.next/dev/cache/turbopack/731ace46/00000058.sst +0 -0
  74. package/web/.next/dev/cache/turbopack/731ace46/00000059.sst +0 -0
  75. package/web/.next/dev/cache/turbopack/731ace46/00000060.meta +0 -0
  76. package/web/.next/dev/cache/turbopack/731ace46/00000061.meta +0 -0
  77. package/web/.next/dev/cache/turbopack/731ace46/00000062.meta +0 -0
  78. package/web/.next/dev/cache/turbopack/731ace46/00000063.sst +0 -0
  79. package/web/.next/dev/cache/turbopack/731ace46/{00000042.sst → 00000064.sst} +0 -0
  80. package/web/.next/dev/cache/turbopack/731ace46/00000065.sst +0 -0
  81. package/web/.next/dev/cache/turbopack/731ace46/00000066.meta +0 -0
  82. package/web/.next/dev/cache/turbopack/731ace46/00000067.meta +0 -0
  83. package/web/.next/dev/cache/turbopack/731ace46/00000068.meta +0 -0
  84. package/web/.next/dev/cache/turbopack/731ace46/00000069.sst +0 -0
  85. package/web/.next/dev/cache/turbopack/731ace46/00000070.sst +0 -0
  86. package/web/.next/dev/cache/turbopack/731ace46/00000071.sst +0 -0
  87. package/web/.next/dev/cache/turbopack/731ace46/00000072.meta +0 -0
  88. package/web/.next/dev/cache/turbopack/731ace46/00000073.meta +0 -0
  89. package/web/.next/dev/cache/turbopack/731ace46/00000074.meta +0 -0
  90. package/web/.next/dev/cache/turbopack/731ace46/00000075.sst +0 -0
  91. package/web/.next/dev/cache/turbopack/731ace46/00000076.sst +0 -0
  92. package/web/.next/dev/cache/turbopack/731ace46/00000077.sst +0 -0
  93. package/web/.next/dev/cache/turbopack/731ace46/00000078.meta +0 -0
  94. package/web/.next/dev/cache/turbopack/731ace46/00000079.meta +0 -0
  95. package/web/.next/dev/cache/turbopack/731ace46/00000080.meta +0 -0
  96. package/web/.next/dev/cache/turbopack/731ace46/CURRENT +0 -0
  97. package/web/.next/dev/cache/turbopack/731ace46/LOG +68 -30
  98. package/web/.next/dev/prerender-manifest.json +3 -3
  99. package/web/.next/dev/server/server-reference-manifest.js +1 -1
  100. package/web/.next/dev/server/server-reference-manifest.json +1 -1
  101. package/web/.next/dev/trace +1 -1
  102. package/web/.next/fallback-build-manifest.json +2 -2
  103. package/web/.next/prerender-manifest.json +3 -3
  104. package/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  105. package/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  106. package/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  107. package/web/.next/server/app/_global-error.html +2 -2
  108. package/web/.next/server/app/_global-error.rsc +1 -1
  109. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  110. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  111. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  112. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  113. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  114. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  115. package/web/.next/server/app/_not-found.html +1 -1
  116. package/web/.next/server/app/_not-found.rsc +2 -2
  117. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  118. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  119. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  120. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  121. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  122. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  123. package/web/.next/server/app/index.html +1 -1
  124. package/web/.next/server/app/index.rsc +4 -4
  125. package/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  126. package/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  127. package/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  128. package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  129. package/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  130. package/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  131. package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js +1 -1
  132. package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js.map +1 -1
  133. package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js → 2374f__pnpm_43c403e8._.js} +1 -1
  134. package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js → 2374f__pnpm_5f7087cf._.js} +1 -1
  135. package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js → 2374f__pnpm_7cd13576._.js} +1 -1
  136. package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js → 2374f__pnpm_9dd8b322._.js} +1 -1
  137. package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js → 2374f__pnpm_ca8b3ccf._.js} +2 -2
  138. package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js → 2374f__pnpm_ce00fd2a._.js} +1 -1
  139. package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js → 2374f__pnpm_e395e0b5._.js} +1 -1
  140. package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js → 2374f__pnpm_f1702ecf._.js} +1 -1
  141. package/web/.next/server/chunks/ssr/{[root-of-the-server]__2dfcd951._.js → [root-of-the-server]__805216cc._.js} +2 -2
  142. package/web/.next/server/chunks/ssr/[root-of-the-server]__805216cc._.js.map +1 -0
  143. package/web/.next/server/chunks/ssr/web_1658d276._.js +1 -1
  144. package/web/.next/server/chunks/ssr/web_1658d276._.js.map +1 -1
  145. package/web/.next/server/chunks/ssr/web_38b8ac39._.js +1 -1
  146. package/web/.next/server/chunks/ssr/web_38b8ac39._.js.map +1 -1
  147. package/web/.next/server/chunks/ssr/web_aaaa7872._.js +1 -1
  148. package/web/.next/server/chunks/ssr/web_aaaa7872._.js.map +1 -1
  149. package/web/.next/server/pages/404.html +1 -1
  150. package/web/.next/server/pages/500.html +2 -2
  151. package/web/.next/server/server-reference-manifest.js +1 -1
  152. package/web/.next/server/server-reference-manifest.json +1 -1
  153. package/web/.next/static/chunks/33150aab64afe27a.css +1 -0
  154. package/web/.next/static/chunks/{64d14a57d96d13ab.js → 62358f9ae3ca4fc3.js} +1 -1
  155. package/web/.next/static/chunks/{f7a8e0e717eeadf2.js → 7441934619c44e53.js} +1 -1
  156. package/web/.next/static/chunks/{dd0466c07795b288.js → 75df31f521796a31.js} +1 -1
  157. package/web/.next/static/chunks/{bf853305cd7a9765.js → fa06da28948df001.js} +3 -3
  158. package/web/.next/trace +1 -1
  159. package/web/.next/trace-build +1 -1
  160. package/web/.next/dev/cache/turbopack/731ace46/00000024.sst +0 -0
  161. package/web/.next/dev/cache/turbopack/731ace46/00000025.sst +0 -0
  162. package/web/.next/dev/cache/turbopack/731ace46/00000028.meta +0 -0
  163. package/web/.next/dev/cache/turbopack/731ace46/00000031.sst +0 -0
  164. package/web/.next/dev/cache/turbopack/731ace46/00000032.sst +0 -0
  165. package/web/.next/dev/cache/turbopack/731ace46/00000043.sst +0 -0
  166. package/web/.next/dev/cache/turbopack/731ace46/00000046.meta +0 -0
  167. package/web/.next/server/chunks/ssr/[root-of-the-server]__2dfcd951._.js.map +0 -1
  168. package/web/.next/static/chunks/7b140db694582cde.css +0 -1
  169. /package/web/.next/dev/cache/turbopack/731ace46/{00000027.meta → 00000024.meta} +0 -0
  170. /package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js.map → 2374f__pnpm_43c403e8._.js.map} +0 -0
  171. /package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js.map → 2374f__pnpm_5f7087cf._.js.map} +0 -0
  172. /package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js.map → 2374f__pnpm_7cd13576._.js.map} +0 -0
  173. /package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js.map → 2374f__pnpm_9dd8b322._.js.map} +0 -0
  174. /package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js.map → 2374f__pnpm_ca8b3ccf._.js.map} +0 -0
  175. /package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js.map → 2374f__pnpm_ce00fd2a._.js.map} +0 -0
  176. /package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js.map → 2374f__pnpm_e395e0b5._.js.map} +0 -0
  177. /package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js.map → 2374f__pnpm_f1702ecf._.js.map} +0 -0
  178. /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_buildManifest.js +0 -0
  179. /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_clientMiddlewareManifest.json +0 -0
  180. /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_ssgManifest.js +0 -0
@@ -10,16 +10,16 @@ import { Hono as Hono5 } from "hono";
10
10
  import { serve } from "@hono/node-server";
11
11
  import { cors } from "hono/cors";
12
12
  import { logger } from "hono/logger";
13
- import { existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
14
- import { resolve as resolve6, dirname as dirname4, join as join3 } from "path";
15
- import { spawn } from "child_process";
13
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "fs";
14
+ import { resolve as resolve8, dirname as dirname6, join as join3 } from "path";
15
+ import { spawn as spawn2 } from "child_process";
16
16
  import { createServer as createNetServer } from "net";
17
- import { fileURLToPath } from "url";
17
+ import { fileURLToPath as fileURLToPath2 } from "url";
18
18
 
19
19
  // src/server/routes/sessions.ts
20
20
  import { Hono } from "hono";
21
21
  import { zValidator } from "@hono/zod-validator";
22
- import { z as z8 } from "zod";
22
+ import { z as z9 } from "zod";
23
23
 
24
24
  // src/db/index.ts
25
25
  import Database from "better-sqlite3";
@@ -650,11 +650,11 @@ var fileBackupQueries = {
650
650
  import {
651
651
  streamText,
652
652
  generateText as generateText2,
653
- tool as tool6,
653
+ tool as tool7,
654
654
  stepCountIs
655
655
  } from "ai";
656
656
  import { gateway as gateway2 } from "@ai-sdk/gateway";
657
- import { z as z7 } from "zod";
657
+ import { z as z8 } from "zod";
658
658
  import { nanoid as nanoid3 } from "nanoid";
659
659
 
660
660
  // src/config/index.ts
@@ -978,12 +978,12 @@ function calculateContextSize(messages2) {
978
978
  import { exec } from "child_process";
979
979
  import { promisify } from "util";
980
980
  import { mkdir, writeFile, readFile } from "fs/promises";
981
- import { existsSync as existsSync2 } from "fs";
981
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
982
982
  import { join as join2 } from "path";
983
983
  import { nanoid as nanoid2 } from "nanoid";
984
984
  var execAsync = promisify(exec);
985
985
  var SESSION_PREFIX = "spark_";
986
- var LOG_BASE_DIR = ".sparkecoder/sessions";
986
+ var LOG_BASE_DIR = "sessions";
987
987
  var tmuxAvailableCache = null;
988
988
  async function isTmuxAvailable() {
989
989
  if (tmuxAvailableCache !== null) {
@@ -1005,11 +1005,19 @@ function generateTerminalId() {
1005
1005
  function getSessionName(terminalId) {
1006
1006
  return `${SESSION_PREFIX}${terminalId}`;
1007
1007
  }
1008
- function getLogDir(terminalId, workingDirectory, sessionId) {
1008
+ function getTerminalDataDir() {
1009
+ const appDataDir = getAppDataDirectory();
1010
+ if (!existsSync2(appDataDir)) {
1011
+ mkdirSync2(appDataDir, { recursive: true });
1012
+ }
1013
+ return appDataDir;
1014
+ }
1015
+ function getLogDir(terminalId, _workingDirectory, sessionId) {
1016
+ const baseDir = getTerminalDataDir();
1009
1017
  if (sessionId) {
1010
- return join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals", terminalId);
1018
+ return join2(baseDir, LOG_BASE_DIR, sessionId, "terminals", terminalId);
1011
1019
  }
1012
- return join2(workingDirectory, ".sparkecoder/terminals", terminalId);
1020
+ return join2(baseDir, "terminals", terminalId);
1013
1021
  }
1014
1022
  function shellEscape(str) {
1015
1023
  return `'${str.replace(/'/g, "'\\''")}'`;
@@ -1222,8 +1230,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
1222
1230
  const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
1223
1231
  const terminals3 = [];
1224
1232
  try {
1225
- const { readdir: readdir2 } = await import("fs/promises");
1226
- const entries = await readdir2(terminalsDir, { withFileTypes: true });
1233
+ const { readdir: readdir3 } = await import("fs/promises");
1234
+ const entries = await readdir3(terminalsDir, { withFileTypes: true });
1227
1235
  for (const entry of entries) {
1228
1236
  if (entry.isDirectory()) {
1229
1237
  const meta = await getMeta(entry.name, workingDirectory, sessionId);
@@ -1373,7 +1381,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
1373
1381
  - For npm: add --yes or -y to skip confirmation
1374
1382
  - If prompts are unavoidable, run in background mode and use input/key to respond
1375
1383
 
1376
- Logs are saved to .sparkecoder/terminals/{id}/output.log`,
1384
+ Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
1377
1385
  inputSchema: bashInputSchema,
1378
1386
  execute: async (inputArgs) => {
1379
1387
  const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
@@ -1612,9 +1620,9 @@ Use this to understand existing code, check file contents, or gather context.`,
1612
1620
  // src/tools/write-file.ts
1613
1621
  import { tool as tool3 } from "ai";
1614
1622
  import { z as z4 } from "zod";
1615
- import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1616
- import { resolve as resolve4, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname3 } from "path";
1617
- import { existsSync as existsSync5 } from "fs";
1623
+ import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1624
+ import { resolve as resolve5, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname5 } from "path";
1625
+ import { existsSync as existsSync7 } from "fs";
1618
1626
 
1619
1627
  // src/checkpoints/index.ts
1620
1628
  import { readFile as readFile3, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
@@ -1803,6 +1811,501 @@ function clearCheckpointManager(sessionId) {
1803
1811
  activeManagers.delete(sessionId);
1804
1812
  }
1805
1813
 
1814
+ // src/lsp/index.ts
1815
+ import { extname as extname2, dirname as dirname4 } from "path";
1816
+
1817
+ // src/lsp/servers.ts
1818
+ import { spawn } from "child_process";
1819
+ import { existsSync as existsSync5 } from "fs";
1820
+ import { resolve as resolve4, dirname as dirname3 } from "path";
1821
+ function findNearestRoot(startDir, markers) {
1822
+ let dir = startDir;
1823
+ const root = "/";
1824
+ while (dir !== root) {
1825
+ for (const marker of markers) {
1826
+ if (existsSync5(resolve4(dir, marker))) {
1827
+ return dir;
1828
+ }
1829
+ }
1830
+ const parent = dirname3(dir);
1831
+ if (parent === dir) break;
1832
+ dir = parent;
1833
+ }
1834
+ return null;
1835
+ }
1836
+ async function commandExists(cmd) {
1837
+ try {
1838
+ const { exec: exec5 } = await import("child_process");
1839
+ const { promisify: promisify5 } = await import("util");
1840
+ const execAsync5 = promisify5(exec5);
1841
+ const isWindows = process.platform === "win32";
1842
+ const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
1843
+ await execAsync5(checkCmd);
1844
+ return true;
1845
+ } catch {
1846
+ return false;
1847
+ }
1848
+ }
1849
+ var TypeScriptServer = {
1850
+ id: "typescript",
1851
+ name: "TypeScript Language Server",
1852
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
1853
+ async spawn(root) {
1854
+ const projectRoot = findNearestRoot(root, [
1855
+ "package-lock.json",
1856
+ "pnpm-lock.yaml",
1857
+ "yarn.lock",
1858
+ "bun.lockb",
1859
+ "bun.lock"
1860
+ ]) || root;
1861
+ const hasNpx = await commandExists("npx");
1862
+ const hasBunx = await commandExists("bunx");
1863
+ const hasPnpx = await commandExists("pnpx");
1864
+ let cmd;
1865
+ if (hasPnpx) {
1866
+ cmd = ["pnpx", "typescript-language-server", "--stdio"];
1867
+ } else if (hasBunx) {
1868
+ cmd = ["bunx", "typescript-language-server", "--stdio"];
1869
+ } else if (hasNpx) {
1870
+ cmd = ["npx", "typescript-language-server", "--stdio"];
1871
+ } else {
1872
+ console.warn("[lsp] No package runner (npx/bunx/pnpx) found for typescript-language-server");
1873
+ return null;
1874
+ }
1875
+ try {
1876
+ const proc = spawn(cmd[0], cmd.slice(1), {
1877
+ cwd: projectRoot,
1878
+ stdio: ["pipe", "pipe", "pipe"],
1879
+ env: {
1880
+ ...process.env,
1881
+ // Suppress some noisy output
1882
+ TSS_LOG: "-level none"
1883
+ }
1884
+ });
1885
+ proc.stderr?.on("data", (data) => {
1886
+ const msg = data.toString().trim();
1887
+ if (msg && !msg.includes("deprecated")) {
1888
+ console.debug("[lsp:typescript:stderr]", msg);
1889
+ }
1890
+ });
1891
+ return {
1892
+ process: proc,
1893
+ initialization: {
1894
+ // TypeScript-specific initialization options
1895
+ preferences: {
1896
+ includeInlayParameterNameHints: "none",
1897
+ includeInlayPropertyDeclarationTypeHints: false,
1898
+ includeInlayFunctionLikeReturnTypeHints: false
1899
+ }
1900
+ }
1901
+ };
1902
+ } catch (error) {
1903
+ console.error("[lsp] Failed to spawn typescript-language-server:", error);
1904
+ return null;
1905
+ }
1906
+ }
1907
+ };
1908
+ var servers = [
1909
+ TypeScriptServer
1910
+ ];
1911
+ function getServerForExtension(ext) {
1912
+ for (const server of servers) {
1913
+ if (server.extensions.includes(ext)) {
1914
+ return server;
1915
+ }
1916
+ }
1917
+ return null;
1918
+ }
1919
+ function getSupportedExtensions() {
1920
+ const extensions = /* @__PURE__ */ new Set();
1921
+ for (const server of servers) {
1922
+ for (const ext of server.extensions) {
1923
+ extensions.add(ext);
1924
+ }
1925
+ }
1926
+ return Array.from(extensions);
1927
+ }
1928
+
1929
+ // src/lsp/client.ts
1930
+ import {
1931
+ createMessageConnection,
1932
+ StreamMessageReader,
1933
+ StreamMessageWriter
1934
+ } from "vscode-jsonrpc/node";
1935
+ import { pathToFileURL, fileURLToPath } from "url";
1936
+ import { readFile as readFile4 } from "fs/promises";
1937
+ import { existsSync as existsSync6 } from "fs";
1938
+ import { extname, normalize } from "path";
1939
+ function getLanguageId(filePath) {
1940
+ const ext = extname(filePath).toLowerCase();
1941
+ const map = {
1942
+ ".ts": "typescript",
1943
+ ".tsx": "typescriptreact",
1944
+ ".js": "javascript",
1945
+ ".jsx": "javascriptreact",
1946
+ ".mjs": "javascript",
1947
+ ".cjs": "javascript",
1948
+ ".mts": "typescript",
1949
+ ".cts": "typescript",
1950
+ ".json": "json",
1951
+ ".jsonc": "jsonc"
1952
+ };
1953
+ return map[ext] || "plaintext";
1954
+ }
1955
+ function normalizePath(filePath) {
1956
+ return normalize(filePath);
1957
+ }
1958
+ async function createClient(serverId, handle, root) {
1959
+ const { process: proc } = handle;
1960
+ if (!proc.stdout || !proc.stdin) {
1961
+ throw new Error("LSP server process has no stdout/stdin");
1962
+ }
1963
+ const connection = createMessageConnection(
1964
+ new StreamMessageReader(proc.stdout),
1965
+ new StreamMessageWriter(proc.stdin)
1966
+ );
1967
+ const diagnostics = /* @__PURE__ */ new Map();
1968
+ const fileVersions = /* @__PURE__ */ new Map();
1969
+ const diagnosticListeners = /* @__PURE__ */ new Map();
1970
+ connection.onNotification("textDocument/publishDiagnostics", (params) => {
1971
+ const filePath = normalizePath(fileURLToPath(params.uri));
1972
+ diagnostics.set(filePath, params.diagnostics || []);
1973
+ const listeners = diagnosticListeners.get(filePath);
1974
+ if (listeners) {
1975
+ for (const listener of listeners) {
1976
+ listener();
1977
+ }
1978
+ }
1979
+ });
1980
+ connection.onRequest("workspace/configuration", async (params) => {
1981
+ return params.items.map(() => handle.initialization || {});
1982
+ });
1983
+ connection.onRequest("client/registerCapability", async () => {
1984
+ return null;
1985
+ });
1986
+ connection.onRequest("window/workDoneProgress/create", async () => {
1987
+ return null;
1988
+ });
1989
+ connection.onNotification("window/logMessage", (params) => {
1990
+ if (params.type <= 2) {
1991
+ console.debug(`[lsp:${serverId}]`, params.message);
1992
+ }
1993
+ });
1994
+ connection.listen();
1995
+ const initResult = await connection.sendRequest("initialize", {
1996
+ processId: process.pid,
1997
+ rootUri: pathToFileURL(root).href,
1998
+ rootPath: root,
1999
+ workspaceFolders: [
2000
+ {
2001
+ name: "workspace",
2002
+ uri: pathToFileURL(root).href
2003
+ }
2004
+ ],
2005
+ capabilities: {
2006
+ textDocument: {
2007
+ synchronization: {
2008
+ dynamicRegistration: true,
2009
+ willSave: false,
2010
+ willSaveWaitUntil: false,
2011
+ didSave: true
2012
+ },
2013
+ publishDiagnostics: {
2014
+ relatedInformation: true,
2015
+ versionSupport: true,
2016
+ codeDescriptionSupport: true
2017
+ },
2018
+ completion: {
2019
+ dynamicRegistration: true,
2020
+ completionItem: {
2021
+ snippetSupport: true,
2022
+ documentationFormat: ["markdown", "plaintext"]
2023
+ }
2024
+ },
2025
+ hover: {
2026
+ dynamicRegistration: true,
2027
+ contentFormat: ["markdown", "plaintext"]
2028
+ },
2029
+ definition: {
2030
+ dynamicRegistration: true
2031
+ },
2032
+ references: {
2033
+ dynamicRegistration: true
2034
+ },
2035
+ documentSymbol: {
2036
+ dynamicRegistration: true
2037
+ }
2038
+ },
2039
+ workspace: {
2040
+ configuration: true,
2041
+ didChangeConfiguration: {
2042
+ dynamicRegistration: true
2043
+ },
2044
+ didChangeWatchedFiles: {
2045
+ dynamicRegistration: true
2046
+ },
2047
+ workspaceFolders: true
2048
+ }
2049
+ },
2050
+ initializationOptions: handle.initialization
2051
+ });
2052
+ await connection.sendNotification("initialized", {});
2053
+ const client = {
2054
+ serverId,
2055
+ root,
2056
+ diagnostics,
2057
+ async notifyOpen(filePath) {
2058
+ const normalized = normalizePath(filePath);
2059
+ if (!existsSync6(normalized)) {
2060
+ return;
2061
+ }
2062
+ try {
2063
+ const content = await readFile4(normalized, "utf-8");
2064
+ const version = (fileVersions.get(normalized) ?? -1) + 1;
2065
+ fileVersions.set(normalized, version);
2066
+ if (version === 0) {
2067
+ await connection.sendNotification("textDocument/didOpen", {
2068
+ textDocument: {
2069
+ uri: pathToFileURL(normalized).href,
2070
+ languageId: getLanguageId(normalized),
2071
+ version,
2072
+ text: content
2073
+ }
2074
+ });
2075
+ } else {
2076
+ await connection.sendNotification("textDocument/didChange", {
2077
+ textDocument: {
2078
+ uri: pathToFileURL(normalized).href,
2079
+ version
2080
+ },
2081
+ contentChanges: [{ text: content }]
2082
+ });
2083
+ }
2084
+ } catch (error) {
2085
+ console.error("[lsp] Error notifying open:", error);
2086
+ }
2087
+ },
2088
+ async notifyChange(filePath) {
2089
+ const normalized = normalizePath(filePath);
2090
+ if (!existsSync6(normalized)) {
2091
+ return;
2092
+ }
2093
+ try {
2094
+ const content = await readFile4(normalized, "utf-8");
2095
+ const version = (fileVersions.get(normalized) ?? 0) + 1;
2096
+ fileVersions.set(normalized, version);
2097
+ await connection.sendNotification("textDocument/didChange", {
2098
+ textDocument: {
2099
+ uri: pathToFileURL(normalized).href,
2100
+ version
2101
+ },
2102
+ contentChanges: [{ text: content }]
2103
+ });
2104
+ } catch (error) {
2105
+ console.error("[lsp] Error notifying change:", error);
2106
+ }
2107
+ },
2108
+ async notifyClose(filePath) {
2109
+ const normalized = normalizePath(filePath);
2110
+ fileVersions.delete(normalized);
2111
+ diagnostics.delete(normalized);
2112
+ try {
2113
+ await connection.sendNotification("textDocument/didClose", {
2114
+ textDocument: {
2115
+ uri: pathToFileURL(normalized).href
2116
+ }
2117
+ });
2118
+ } catch (error) {
2119
+ console.error("[lsp] Error notifying close:", error);
2120
+ }
2121
+ },
2122
+ async notifyWatchedFilesChanged(changes) {
2123
+ try {
2124
+ await connection.sendNotification("workspace/didChangeWatchedFiles", {
2125
+ changes
2126
+ });
2127
+ } catch (error) {
2128
+ console.error("[lsp] Error notifying watched files:", error);
2129
+ }
2130
+ },
2131
+ async waitForDiagnostics(filePath, timeoutMs = 5e3) {
2132
+ const normalized = normalizePath(filePath);
2133
+ return new Promise((resolve9) => {
2134
+ const startTime = Date.now();
2135
+ let debounceTimer;
2136
+ let resolved = false;
2137
+ const cleanup = () => {
2138
+ if (debounceTimer) clearTimeout(debounceTimer);
2139
+ const listeners = diagnosticListeners.get(normalized);
2140
+ if (listeners) {
2141
+ const idx = listeners.indexOf(onDiagnostic);
2142
+ if (idx >= 0) listeners.splice(idx, 1);
2143
+ if (listeners.length === 0) {
2144
+ diagnosticListeners.delete(normalized);
2145
+ }
2146
+ }
2147
+ };
2148
+ const finish = () => {
2149
+ if (resolved) return;
2150
+ resolved = true;
2151
+ cleanup();
2152
+ resolve9(diagnostics.get(normalized) || []);
2153
+ };
2154
+ const onDiagnostic = () => {
2155
+ if (debounceTimer) clearTimeout(debounceTimer);
2156
+ debounceTimer = setTimeout(finish, 150);
2157
+ };
2158
+ if (!diagnosticListeners.has(normalized)) {
2159
+ diagnosticListeners.set(normalized, []);
2160
+ }
2161
+ diagnosticListeners.get(normalized).push(onDiagnostic);
2162
+ setTimeout(() => {
2163
+ if (!resolved) {
2164
+ finish();
2165
+ }
2166
+ }, timeoutMs);
2167
+ if (diagnostics.has(normalized)) {
2168
+ onDiagnostic();
2169
+ }
2170
+ });
2171
+ },
2172
+ getDiagnostics(filePath) {
2173
+ return diagnostics.get(normalizePath(filePath)) || [];
2174
+ },
2175
+ getAllDiagnostics() {
2176
+ return new Map(diagnostics);
2177
+ },
2178
+ async shutdown() {
2179
+ try {
2180
+ await connection.sendRequest("shutdown");
2181
+ await connection.sendNotification("exit");
2182
+ connection.end();
2183
+ connection.dispose();
2184
+ proc.kill();
2185
+ } catch (error) {
2186
+ proc.kill("SIGKILL");
2187
+ }
2188
+ }
2189
+ };
2190
+ return client;
2191
+ }
2192
+
2193
+ // src/lsp/types.ts
2194
+ function formatDiagnostic(diagnostic) {
2195
+ const severity = {
2196
+ [1 /* Error */]: "ERROR",
2197
+ [2 /* Warning */]: "WARN",
2198
+ [3 /* Information */]: "INFO",
2199
+ [4 /* Hint */]: "HINT"
2200
+ }[diagnostic.severity ?? 1 /* Error */];
2201
+ const line = diagnostic.range.start.line + 1;
2202
+ const col = diagnostic.range.start.character + 1;
2203
+ const source = diagnostic.source ? ` [${diagnostic.source}]` : "";
2204
+ return `${severity} [${line}:${col}]${source} ${diagnostic.message}`;
2205
+ }
2206
+ function formatDiagnosticsForAgent(filePath, diagnostics, options = {}) {
2207
+ const { maxDiagnostics = 20, errorsOnly = true } = options;
2208
+ const filtered = errorsOnly ? diagnostics.filter((d) => d.severity === 1 /* Error */) : diagnostics;
2209
+ if (filtered.length === 0) return "";
2210
+ const limited = filtered.slice(0, maxDiagnostics);
2211
+ const suffix = filtered.length > maxDiagnostics ? `
2212
+ ... and ${filtered.length - maxDiagnostics} more` : "";
2213
+ const formatted = limited.map(formatDiagnostic).join("\n");
2214
+ return `
2215
+
2216
+ LSP errors detected in this file, please fix:
2217
+ <diagnostics file="${filePath}">
2218
+ ${formatted}${suffix}
2219
+ </diagnostics>`;
2220
+ }
2221
+
2222
+ // src/lsp/index.ts
2223
+ var state = {
2224
+ clients: /* @__PURE__ */ new Map(),
2225
+ broken: /* @__PURE__ */ new Set(),
2226
+ initialized: false
2227
+ };
2228
+ async function getClientForFile(filePath) {
2229
+ const normalized = normalizePath(filePath);
2230
+ const ext = extname2(normalized);
2231
+ const serverDef = getServerForExtension(ext);
2232
+ if (!serverDef) {
2233
+ return null;
2234
+ }
2235
+ const root = dirname4(normalized);
2236
+ const key = `${serverDef.id}:${root}`;
2237
+ const existing = state.clients.get(key);
2238
+ if (existing) {
2239
+ return existing;
2240
+ }
2241
+ if (state.broken.has(key)) {
2242
+ return null;
2243
+ }
2244
+ try {
2245
+ const handle = await serverDef.spawn(root);
2246
+ if (!handle) {
2247
+ state.broken.add(key);
2248
+ return null;
2249
+ }
2250
+ console.log(`[lsp] Started ${serverDef.name} for ${root}`);
2251
+ const client = await createClient(serverDef.id, handle, root);
2252
+ state.clients.set(key, client);
2253
+ handle.process.on("exit", (code) => {
2254
+ console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
2255
+ state.clients.delete(key);
2256
+ });
2257
+ return client;
2258
+ } catch (error) {
2259
+ console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
2260
+ state.broken.add(key);
2261
+ return null;
2262
+ }
2263
+ }
2264
+ async function getClientsForFile(filePath) {
2265
+ const client = await getClientForFile(filePath);
2266
+ return client ? [client] : [];
2267
+ }
2268
+ async function touchFile(filePath, waitForDiagnostics = false) {
2269
+ const clients = await getClientsForFile(filePath);
2270
+ if (clients.length === 0) {
2271
+ return;
2272
+ }
2273
+ await Promise.all(clients.map((client) => client.notifyOpen(filePath)));
2274
+ if (waitForDiagnostics) {
2275
+ await Promise.all(clients.map((client) => client.waitForDiagnostics(filePath)));
2276
+ }
2277
+ }
2278
+ async function getDiagnostics(filePath) {
2279
+ const normalized = normalizePath(filePath);
2280
+ const clients = await getClientsForFile(normalized);
2281
+ const allDiagnostics = [];
2282
+ for (const client of clients) {
2283
+ const diags = client.getDiagnostics(normalized);
2284
+ allDiagnostics.push(...diags);
2285
+ }
2286
+ return allDiagnostics;
2287
+ }
2288
+ async function getAllDiagnostics() {
2289
+ const results = {};
2290
+ for (const client of state.clients.values()) {
2291
+ const clientDiags = client.getAllDiagnostics();
2292
+ for (const [path, diagnostics] of clientDiags.entries()) {
2293
+ const existing = results[path] || [];
2294
+ existing.push(...diagnostics);
2295
+ results[path] = existing;
2296
+ }
2297
+ }
2298
+ return results;
2299
+ }
2300
+ async function formatDiagnosticsOutput(filePath, options = {}) {
2301
+ const diagnostics = await getDiagnostics(filePath);
2302
+ return formatDiagnosticsForAgent(filePath, diagnostics, options);
2303
+ }
2304
+ function isSupported(filePath) {
2305
+ const ext = extname2(filePath);
2306
+ return getServerForExtension(ext) !== null;
2307
+ }
2308
+
1806
2309
  // src/tools/write-file.ts
1807
2310
  var writeFileInputSchema = z4.object({
1808
2311
  path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
@@ -1832,7 +2335,7 @@ Working directory: ${options.workingDirectory}`,
1832
2335
  inputSchema: writeFileInputSchema,
1833
2336
  execute: async ({ path, mode, content, old_string, new_string }) => {
1834
2337
  try {
1835
- const absolutePath = isAbsolute2(path) ? path : resolve4(options.workingDirectory, path);
2338
+ const absolutePath = isAbsolute2(path) ? path : resolve5(options.workingDirectory, path);
1836
2339
  const relativePath = relative3(options.workingDirectory, absolutePath);
1837
2340
  if (relativePath.startsWith("..") && !isAbsolute2(path)) {
1838
2341
  return {
@@ -1848,12 +2351,17 @@ Working directory: ${options.workingDirectory}`,
1848
2351
  };
1849
2352
  }
1850
2353
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1851
- const dir = dirname3(absolutePath);
1852
- if (!existsSync5(dir)) {
2354
+ const dir = dirname5(absolutePath);
2355
+ if (!existsSync7(dir)) {
1853
2356
  await mkdir3(dir, { recursive: true });
1854
2357
  }
1855
- const existed = existsSync5(absolutePath);
2358
+ const existed = existsSync7(absolutePath);
1856
2359
  await writeFile3(absolutePath, content, "utf-8");
2360
+ let diagnosticsOutput = "";
2361
+ if (options.enableLSP !== false && isSupported(absolutePath)) {
2362
+ await touchFile(absolutePath, true);
2363
+ diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2364
+ }
1857
2365
  return {
1858
2366
  success: true,
1859
2367
  path: absolutePath,
@@ -1861,7 +2369,8 @@ Working directory: ${options.workingDirectory}`,
1861
2369
  mode: "full",
1862
2370
  action: existed ? "replaced" : "created",
1863
2371
  bytesWritten: Buffer.byteLength(content, "utf-8"),
1864
- lineCount: content.split("\n").length
2372
+ lineCount: content.split("\n").length,
2373
+ ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
1865
2374
  };
1866
2375
  } else if (mode === "str_replace") {
1867
2376
  if (old_string === void 0 || new_string === void 0) {
@@ -1870,14 +2379,14 @@ Working directory: ${options.workingDirectory}`,
1870
2379
  error: 'Both old_string and new_string are required for "str_replace" mode'
1871
2380
  };
1872
2381
  }
1873
- if (!existsSync5(absolutePath)) {
2382
+ if (!existsSync7(absolutePath)) {
1874
2383
  return {
1875
2384
  success: false,
1876
2385
  error: `File not found: ${path}. Use "full" mode to create new files.`
1877
2386
  };
1878
2387
  }
1879
2388
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1880
- const currentContent = await readFile4(absolutePath, "utf-8");
2389
+ const currentContent = await readFile5(absolutePath, "utf-8");
1881
2390
  if (!currentContent.includes(old_string)) {
1882
2391
  const lines = currentContent.split("\n");
1883
2392
  const preview = lines.slice(0, 20).join("\n");
@@ -1901,6 +2410,11 @@ Working directory: ${options.workingDirectory}`,
1901
2410
  await writeFile3(absolutePath, newContent, "utf-8");
1902
2411
  const oldLines = old_string.split("\n").length;
1903
2412
  const newLines = new_string.split("\n").length;
2413
+ let diagnosticsOutput = "";
2414
+ if (options.enableLSP !== false && isSupported(absolutePath)) {
2415
+ await touchFile(absolutePath, true);
2416
+ diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2417
+ }
1904
2418
  return {
1905
2419
  success: true,
1906
2420
  path: absolutePath,
@@ -1908,7 +2422,8 @@ Working directory: ${options.workingDirectory}`,
1908
2422
  mode: "str_replace",
1909
2423
  linesRemoved: oldLines,
1910
2424
  linesAdded: newLines,
1911
- lineDelta: newLines - oldLines
2425
+ lineDelta: newLines - oldLines,
2426
+ ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
1912
2427
  };
1913
2428
  }
1914
2429
  return {
@@ -2055,9 +2570,9 @@ import { tool as tool5 } from "ai";
2055
2570
  import { z as z6 } from "zod";
2056
2571
 
2057
2572
  // src/skills/index.ts
2058
- import { readFile as readFile5, readdir } from "fs/promises";
2059
- import { resolve as resolve5, basename, extname } from "path";
2060
- import { existsSync as existsSync6 } from "fs";
2573
+ import { readFile as readFile6, readdir } from "fs/promises";
2574
+ import { resolve as resolve6, basename, extname as extname3 } from "path";
2575
+ import { existsSync as existsSync8 } from "fs";
2061
2576
  function parseSkillFrontmatter(content) {
2062
2577
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
2063
2578
  if (!frontmatterMatch) {
@@ -2085,18 +2600,18 @@ function parseSkillFrontmatter(content) {
2085
2600
  }
2086
2601
  }
2087
2602
  function getSkillNameFromPath(filePath) {
2088
- return basename(filePath, extname(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2603
+ return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2089
2604
  }
2090
2605
  async function loadSkillsFromDirectory(directory) {
2091
- if (!existsSync6(directory)) {
2606
+ if (!existsSync8(directory)) {
2092
2607
  return [];
2093
2608
  }
2094
2609
  const skills = [];
2095
2610
  const files = await readdir(directory);
2096
2611
  for (const file of files) {
2097
2612
  if (!file.endsWith(".md")) continue;
2098
- const filePath = resolve5(directory, file);
2099
- const content = await readFile5(filePath, "utf-8");
2613
+ const filePath = resolve6(directory, file);
2614
+ const content = await readFile6(filePath, "utf-8");
2100
2615
  const parsed = parseSkillFrontmatter(content);
2101
2616
  if (parsed) {
2102
2617
  skills.push({
@@ -2138,7 +2653,7 @@ async function loadSkillContent(skillName, directories) {
2138
2653
  if (!skill) {
2139
2654
  return null;
2140
2655
  }
2141
- const content = await readFile5(skill.filePath, "utf-8");
2656
+ const content = await readFile6(skill.filePath, "utf-8");
2142
2657
  const parsed = parseSkillFrontmatter(content);
2143
2658
  return {
2144
2659
  ...skill,
@@ -2236,6 +2751,199 @@ Once loaded, a skill's content will be available in the conversation context.`,
2236
2751
  });
2237
2752
  }
2238
2753
 
2754
+ // src/tools/linter.ts
2755
+ import { tool as tool6 } from "ai";
2756
+ import { z as z7 } from "zod";
2757
+ import { resolve as resolve7, relative as relative4, isAbsolute as isAbsolute3, extname as extname4 } from "path";
2758
+ import { existsSync as existsSync9 } from "fs";
2759
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
2760
+ var linterInputSchema = z7.object({
2761
+ paths: z7.array(z7.string()).optional().describe("File or directory paths to check for lint errors. If not provided, returns diagnostics for all recently touched files."),
2762
+ fix: z7.boolean().optional().default(false).describe("Reserved for future use: auto-fix lint errors (not yet implemented)")
2763
+ });
2764
+ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
2765
+ const files = [];
2766
+ const supportedExtensions = getSupportedExtensions();
2767
+ async function walk(currentDir) {
2768
+ if (files.length >= maxFiles) return;
2769
+ try {
2770
+ const entries = await readdir2(currentDir, { withFileTypes: true });
2771
+ for (const entry of entries) {
2772
+ if (files.length >= maxFiles) break;
2773
+ const fullPath = resolve7(currentDir, entry.name);
2774
+ if (entry.isDirectory()) {
2775
+ if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
2776
+ continue;
2777
+ }
2778
+ await walk(fullPath);
2779
+ } else if (entry.isFile()) {
2780
+ const ext = extname4(entry.name);
2781
+ if (supportedExtensions.includes(ext)) {
2782
+ files.push(fullPath);
2783
+ }
2784
+ }
2785
+ }
2786
+ } catch {
2787
+ }
2788
+ }
2789
+ await walk(dir);
2790
+ return files;
2791
+ }
2792
+ function createLinterTool(options) {
2793
+ return tool6({
2794
+ description: `Check files for linting and type errors using the Language Server Protocol (LSP).
2795
+
2796
+ Supports TypeScript, JavaScript, TSX, JSX files.
2797
+
2798
+ Usage:
2799
+ - \`linter({})\` - Get diagnostics for all recently edited files
2800
+ - \`linter({ paths: ["src/app.ts"] })\` - Check specific files
2801
+ - \`linter({ paths: ["src/"] })\` - Check all supported files in a directory
2802
+
2803
+ Returns detailed error information including line numbers, error messages, and severity.
2804
+ Use this after making changes to verify your code is correct, or proactively to find issues.
2805
+
2806
+ Working directory: ${options.workingDirectory}`,
2807
+ inputSchema: linterInputSchema,
2808
+ execute: async ({ paths }) => {
2809
+ try {
2810
+ if (!paths || paths.length === 0) {
2811
+ const allDiagnostics = await getAllDiagnostics();
2812
+ if (Object.keys(allDiagnostics).length === 0) {
2813
+ return {
2814
+ success: true,
2815
+ message: "No lint errors found. No files have been analyzed yet - specify paths to check specific files.",
2816
+ files: [],
2817
+ totalErrors: 0,
2818
+ totalWarnings: 0
2819
+ };
2820
+ }
2821
+ return formatDiagnosticsResult(allDiagnostics, options.workingDirectory);
2822
+ }
2823
+ const filesToCheck = [];
2824
+ for (const path of paths) {
2825
+ const absolutePath = isAbsolute3(path) ? path : resolve7(options.workingDirectory, path);
2826
+ if (!existsSync9(absolutePath)) {
2827
+ continue;
2828
+ }
2829
+ const stats = await stat2(absolutePath);
2830
+ if (stats.isDirectory()) {
2831
+ const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);
2832
+ filesToCheck.push(...dirFiles);
2833
+ } else if (stats.isFile()) {
2834
+ if (isSupported(absolutePath)) {
2835
+ filesToCheck.push(absolutePath);
2836
+ }
2837
+ }
2838
+ }
2839
+ if (filesToCheck.length === 0) {
2840
+ return {
2841
+ success: true,
2842
+ message: "No supported files found to check. Supported extensions: " + getSupportedExtensions().join(", "),
2843
+ files: [],
2844
+ totalErrors: 0,
2845
+ totalWarnings: 0
2846
+ };
2847
+ }
2848
+ await Promise.all(
2849
+ filesToCheck.map((file) => touchFile(file, true))
2850
+ );
2851
+ const diagnosticsMap = {};
2852
+ for (const file of filesToCheck) {
2853
+ const diagnostics = await getDiagnostics(file);
2854
+ if (diagnostics.length > 0) {
2855
+ diagnosticsMap[file] = diagnostics;
2856
+ }
2857
+ }
2858
+ return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);
2859
+ } catch (error) {
2860
+ return {
2861
+ success: false,
2862
+ error: error.message
2863
+ };
2864
+ }
2865
+ }
2866
+ });
2867
+ }
2868
+ function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
2869
+ let totalErrors = 0;
2870
+ let totalWarnings = 0;
2871
+ let totalInfo = 0;
2872
+ const files = [];
2873
+ for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
2874
+ const relativePath = relative4(workingDirectory, filePath);
2875
+ let fileErrors = 0;
2876
+ let fileWarnings = 0;
2877
+ const formattedDiagnostics = diagnostics.map((d) => {
2878
+ const severity = getSeverityString(d.severity);
2879
+ if (d.severity === 1 /* Error */) {
2880
+ fileErrors++;
2881
+ totalErrors++;
2882
+ } else if (d.severity === 2 /* Warning */) {
2883
+ fileWarnings++;
2884
+ totalWarnings++;
2885
+ } else {
2886
+ totalInfo++;
2887
+ }
2888
+ return {
2889
+ severity,
2890
+ line: d.range.start.line + 1,
2891
+ column: d.range.start.character + 1,
2892
+ message: d.message,
2893
+ source: d.source,
2894
+ code: d.code
2895
+ };
2896
+ });
2897
+ files.push({
2898
+ path: filePath,
2899
+ relativePath,
2900
+ errors: fileErrors,
2901
+ warnings: fileWarnings,
2902
+ diagnostics: formattedDiagnostics
2903
+ });
2904
+ }
2905
+ files.sort((a, b) => b.errors - a.errors);
2906
+ const hasIssues = totalErrors > 0 || totalWarnings > 0;
2907
+ return {
2908
+ success: true,
2909
+ message: hasIssues ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).` : `No lint errors found in ${Object.keys(diagnosticsMap).length || "any"} file(s).`,
2910
+ files,
2911
+ totalErrors,
2912
+ totalWarnings,
2913
+ totalInfo,
2914
+ summary: hasIssues ? formatSummary(files) : void 0
2915
+ };
2916
+ }
2917
+ function getSeverityString(severity) {
2918
+ switch (severity) {
2919
+ case 1 /* Error */:
2920
+ return "error";
2921
+ case 2 /* Warning */:
2922
+ return "warning";
2923
+ case 3 /* Information */:
2924
+ return "info";
2925
+ case 4 /* Hint */:
2926
+ return "hint";
2927
+ default:
2928
+ return "error";
2929
+ }
2930
+ }
2931
+ function formatSummary(files) {
2932
+ const lines = [];
2933
+ for (const file of files) {
2934
+ lines.push(`
2935
+ ${file.relativePath}:`);
2936
+ for (const d of file.diagnostics.slice(0, 10)) {
2937
+ const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
2938
+ lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
2939
+ }
2940
+ if (file.diagnostics.length > 10) {
2941
+ lines.push(` ... and ${file.diagnostics.length - 10} more`);
2942
+ }
2943
+ }
2944
+ return lines.join("\n");
2945
+ }
2946
+
2239
2947
  // src/tools/index.ts
2240
2948
  function createTools(options) {
2241
2949
  return {
@@ -2250,7 +2958,8 @@ function createTools(options) {
2250
2958
  }),
2251
2959
  write_file: createWriteFileTool({
2252
2960
  workingDirectory: options.workingDirectory,
2253
- sessionId: options.sessionId
2961
+ sessionId: options.sessionId,
2962
+ enableLSP: options.enableLSP ?? true
2254
2963
  }),
2255
2964
  todo: createTodoTool({
2256
2965
  sessionId: options.sessionId
@@ -2258,6 +2967,9 @@ function createTools(options) {
2258
2967
  load_skill: createLoadSkillTool({
2259
2968
  sessionId: options.sessionId,
2260
2969
  skillsDirectories: options.skillsDirectories
2970
+ }),
2971
+ linter: createLinterTool({
2972
+ workingDirectory: options.workingDirectory
2261
2973
  })
2262
2974
  };
2263
2975
  }
@@ -2305,6 +3017,7 @@ You have access to powerful tools for:
2305
3017
  - **bash**: Execute commands in the terminal (see below for details)
2306
3018
  - **read_file**: Read file contents to understand code and context
2307
3019
  - **write_file**: Create new files or edit existing ones (supports targeted string replacement)
3020
+ - **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
2308
3021
  - **todo**: Manage your task list to track progress on complex operations
2309
3022
  - **load_skill**: Load specialized knowledge documents for specific tasks
2310
3023
 
@@ -2361,7 +3074,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
2361
3074
  - Use \`key: "y"\` or \`key: "n"\` for yes/no prompts
2362
3075
  - Use \`input: "text"\` for text input prompts
2363
3076
 
2364
- Logs are saved to \`.sparkecoder/terminals/{id}/output.log\` and can be read with \`read_file\` if needed.
3077
+ Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.
2365
3078
 
2366
3079
  ## Guidelines
2367
3080
 
@@ -2381,7 +3094,17 @@ Logs are saved to \`.sparkecoder/terminals/{id}/output.log\` and can be read wit
2381
3094
  - Use \`read_file\` to understand code before modifying
2382
3095
  - Use \`write_file\` with mode "str_replace" for targeted edits to existing files
2383
3096
  - Use \`write_file\` with mode "full" only for new files or complete rewrites
2384
- - Always verify changes by reading files after modifications
3097
+ - After making changes, use the \`linter\` tool to check for type errors and lint issues
3098
+ - The \`write_file\` tool automatically shows lint errors in its output for TypeScript/JavaScript files
3099
+
3100
+ ### Linter Tool
3101
+ The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
3102
+ \`\`\`
3103
+ linter({}) // Check all recently edited files
3104
+ linter({ paths: ["src/app.ts"] }) // Check specific files
3105
+ linter({ paths: ["src/"] }) // Check all files in a directory
3106
+ \`\`\`
3107
+ Use this proactively after making code changes to catch errors early.
2385
3108
 
2386
3109
  ### Searching and Exploration
2387
3110
  ${searchInstructions}
@@ -2731,9 +3454,9 @@ var Agent = class _Agent {
2731
3454
  wrappedTools[name] = originalTool;
2732
3455
  continue;
2733
3456
  }
2734
- wrappedTools[name] = tool6({
3457
+ wrappedTools[name] = tool7({
2735
3458
  description: originalTool.description || "",
2736
- inputSchema: originalTool.inputSchema || z7.object({}),
3459
+ inputSchema: originalTool.inputSchema || z8.object({}),
2737
3460
  execute: async (input, toolOptions) => {
2738
3461
  const toolCallId = toolOptions.toolCallId || nanoid3();
2739
3462
  const execution = toolExecutionQueries.create({
@@ -2747,8 +3470,8 @@ var Agent = class _Agent {
2747
3470
  this.pendingApprovals.set(toolCallId, execution);
2748
3471
  options.onApprovalRequired?.(execution);
2749
3472
  sessionQueries.updateStatus(this.session.id, "waiting");
2750
- const approved = await new Promise((resolve7) => {
2751
- approvalResolvers.set(toolCallId, { resolve: resolve7, sessionId: this.session.id });
3473
+ const approved = await new Promise((resolve9) => {
3474
+ approvalResolvers.set(toolCallId, { resolve: resolve9, sessionId: this.session.id });
2752
3475
  });
2753
3476
  const resolverData = approvalResolvers.get(toolCallId);
2754
3477
  approvalResolvers.delete(toolCallId);
@@ -2843,18 +3566,18 @@ var Agent = class _Agent {
2843
3566
 
2844
3567
  // src/server/routes/sessions.ts
2845
3568
  var sessions2 = new Hono();
2846
- var createSessionSchema = z8.object({
2847
- name: z8.string().optional(),
2848
- workingDirectory: z8.string().optional(),
2849
- model: z8.string().optional(),
2850
- toolApprovals: z8.record(z8.string(), z8.boolean()).optional()
3569
+ var createSessionSchema = z9.object({
3570
+ name: z9.string().optional(),
3571
+ workingDirectory: z9.string().optional(),
3572
+ model: z9.string().optional(),
3573
+ toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
2851
3574
  });
2852
- var paginationQuerySchema = z8.object({
2853
- limit: z8.string().optional(),
2854
- offset: z8.string().optional()
3575
+ var paginationQuerySchema = z9.object({
3576
+ limit: z9.string().optional(),
3577
+ offset: z9.string().optional()
2855
3578
  });
2856
- var messagesQuerySchema = z8.object({
2857
- limit: z8.string().optional()
3579
+ var messagesQuerySchema = z9.object({
3580
+ limit: z9.string().optional()
2858
3581
  });
2859
3582
  sessions2.get(
2860
3583
  "/",
@@ -2993,10 +3716,10 @@ sessions2.get("/:id/tools", async (c) => {
2993
3716
  count: executions.length
2994
3717
  });
2995
3718
  });
2996
- var updateSessionSchema = z8.object({
2997
- model: z8.string().optional(),
2998
- name: z8.string().optional(),
2999
- toolApprovals: z8.record(z8.string(), z8.boolean()).optional()
3719
+ var updateSessionSchema = z9.object({
3720
+ model: z9.string().optional(),
3721
+ name: z9.string().optional(),
3722
+ toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
3000
3723
  });
3001
3724
  sessions2.patch(
3002
3725
  "/:id",
@@ -3197,7 +3920,7 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
3197
3920
  // src/server/routes/agents.ts
3198
3921
  import { Hono as Hono2 } from "hono";
3199
3922
  import { zValidator as zValidator2 } from "@hono/zod-validator";
3200
- import { z as z9 } from "zod";
3923
+ import { z as z10 } from "zod";
3201
3924
 
3202
3925
  // src/server/resumable-stream.ts
3203
3926
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -3274,18 +3997,18 @@ var streamContext = createResumableStreamContext({
3274
3997
  // src/server/routes/agents.ts
3275
3998
  import { nanoid as nanoid4 } from "nanoid";
3276
3999
  var agents = new Hono2();
3277
- var runPromptSchema = z9.object({
3278
- prompt: z9.string().min(1)
4000
+ var runPromptSchema = z10.object({
4001
+ prompt: z10.string().min(1)
3279
4002
  });
3280
- var quickStartSchema = z9.object({
3281
- prompt: z9.string().min(1),
3282
- name: z9.string().optional(),
3283
- workingDirectory: z9.string().optional(),
3284
- model: z9.string().optional(),
3285
- toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
4003
+ var quickStartSchema = z10.object({
4004
+ prompt: z10.string().min(1),
4005
+ name: z10.string().optional(),
4006
+ workingDirectory: z10.string().optional(),
4007
+ model: z10.string().optional(),
4008
+ toolApprovals: z10.record(z10.string(), z10.boolean()).optional()
3286
4009
  });
3287
- var rejectSchema = z9.object({
3288
- reason: z9.string().optional()
4010
+ var rejectSchema = z10.object({
4011
+ reason: z10.string().optional()
3289
4012
  }).optional();
3290
4013
  var streamAbortControllers = /* @__PURE__ */ new Map();
3291
4014
  function createAgentStreamProducer(sessionId, prompt, streamId) {
@@ -3846,7 +4569,7 @@ agents.post(
3846
4569
  // src/server/routes/health.ts
3847
4570
  import { Hono as Hono3 } from "hono";
3848
4571
  import { zValidator as zValidator3 } from "@hono/zod-validator";
3849
- import { z as z10 } from "zod";
4572
+ import { z as z11 } from "zod";
3850
4573
  var health = new Hono3();
3851
4574
  health.get("/", async (c) => {
3852
4575
  const config = getConfig();
@@ -3892,9 +4615,9 @@ health.get("/api-keys", async (c) => {
3892
4615
  supportedProviders: SUPPORTED_PROVIDERS
3893
4616
  });
3894
4617
  });
3895
- var setApiKeySchema = z10.object({
3896
- provider: z10.string(),
3897
- apiKey: z10.string().min(1)
4618
+ var setApiKeySchema = z11.object({
4619
+ provider: z11.string(),
4620
+ apiKey: z11.string().min(1)
3898
4621
  });
3899
4622
  health.post(
3900
4623
  "/api-keys",
@@ -3933,12 +4656,12 @@ health.delete("/api-keys/:provider", async (c) => {
3933
4656
  // src/server/routes/terminals.ts
3934
4657
  import { Hono as Hono4 } from "hono";
3935
4658
  import { zValidator as zValidator4 } from "@hono/zod-validator";
3936
- import { z as z11 } from "zod";
4659
+ import { z as z12 } from "zod";
3937
4660
  var terminals2 = new Hono4();
3938
- var spawnSchema = z11.object({
3939
- command: z11.string(),
3940
- cwd: z11.string().optional(),
3941
- name: z11.string().optional()
4661
+ var spawnSchema = z12.object({
4662
+ command: z12.string(),
4663
+ cwd: z12.string().optional(),
4664
+ name: z12.string().optional()
3942
4665
  });
3943
4666
  terminals2.post(
3944
4667
  "/:sessionId/terminals",
@@ -4019,8 +4742,8 @@ terminals2.get("/:sessionId/terminals/:terminalId", async (c) => {
4019
4742
  // We don't track exit codes in tmux mode
4020
4743
  });
4021
4744
  });
4022
- var logsQuerySchema = z11.object({
4023
- tail: z11.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
4745
+ var logsQuerySchema = z12.object({
4746
+ tail: z12.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
4024
4747
  });
4025
4748
  terminals2.get(
4026
4749
  "/:sessionId/terminals/:terminalId/logs",
@@ -4044,8 +4767,8 @@ terminals2.get(
4044
4767
  });
4045
4768
  }
4046
4769
  );
4047
- var killSchema = z11.object({
4048
- signal: z11.enum(["SIGTERM", "SIGKILL"]).optional()
4770
+ var killSchema = z12.object({
4771
+ signal: z12.enum(["SIGTERM", "SIGKILL"]).optional()
4049
4772
  });
4050
4773
  terminals2.post(
4051
4774
  "/:sessionId/terminals/:terminalId/kill",
@@ -4059,8 +4782,8 @@ terminals2.post(
4059
4782
  return c.json({ success: true, message: "Terminal killed" });
4060
4783
  }
4061
4784
  );
4062
- var writeSchema = z11.object({
4063
- input: z11.string()
4785
+ var writeSchema = z12.object({
4786
+ input: z12.string()
4064
4787
  });
4065
4788
  terminals2.post(
4066
4789
  "/:sessionId/terminals/:terminalId/write",
@@ -4325,13 +5048,13 @@ var DEFAULT_WEB_PORT = 6969;
4325
5048
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
4326
5049
  function getWebDirectory() {
4327
5050
  try {
4328
- const currentDir = dirname4(fileURLToPath(import.meta.url));
4329
- const webDir = resolve6(currentDir, "..", "web");
4330
- if (existsSync7(webDir) && existsSync7(join3(webDir, "package.json"))) {
5051
+ const currentDir = dirname6(fileURLToPath2(import.meta.url));
5052
+ const webDir = resolve8(currentDir, "..", "web");
5053
+ if (existsSync10(webDir) && existsSync10(join3(webDir, "package.json"))) {
4331
5054
  return webDir;
4332
5055
  }
4333
- const altWebDir = resolve6(currentDir, "..", "..", "web");
4334
- if (existsSync7(altWebDir) && existsSync7(join3(altWebDir, "package.json"))) {
5056
+ const altWebDir = resolve8(currentDir, "..", "..", "web");
5057
+ if (existsSync10(altWebDir) && existsSync10(join3(altWebDir, "package.json"))) {
4335
5058
  return altWebDir;
4336
5059
  }
4337
5060
  return null;
@@ -4354,18 +5077,18 @@ async function isSparkcoderWebRunning(port) {
4354
5077
  }
4355
5078
  }
4356
5079
  function isPortInUse(port) {
4357
- return new Promise((resolve7) => {
5080
+ return new Promise((resolve9) => {
4358
5081
  const server = createNetServer();
4359
5082
  server.once("error", (err) => {
4360
5083
  if (err.code === "EADDRINUSE") {
4361
- resolve7(true);
5084
+ resolve9(true);
4362
5085
  } else {
4363
- resolve7(false);
5086
+ resolve9(false);
4364
5087
  }
4365
5088
  });
4366
5089
  server.once("listening", () => {
4367
5090
  server.close();
4368
- resolve7(false);
5091
+ resolve9(false);
4369
5092
  });
4370
5093
  server.listen(port, "0.0.0.0");
4371
5094
  });
@@ -4390,11 +5113,11 @@ async function findWebPort(preferredPort) {
4390
5113
  }
4391
5114
  function hasProductionBuild(webDir) {
4392
5115
  const buildIdPath = join3(webDir, ".next", "BUILD_ID");
4393
- return existsSync7(buildIdPath);
5116
+ return existsSync10(buildIdPath);
4394
5117
  }
4395
5118
  function runCommand(command, args, cwd, env) {
4396
- return new Promise((resolve7) => {
4397
- const child = spawn(command, args, {
5119
+ return new Promise((resolve9) => {
5120
+ const child = spawn2(command, args, {
4398
5121
  cwd,
4399
5122
  stdio: ["ignore", "pipe", "pipe"],
4400
5123
  env,
@@ -4408,10 +5131,10 @@ function runCommand(command, args, cwd, env) {
4408
5131
  output += data.toString();
4409
5132
  });
4410
5133
  child.on("close", (code) => {
4411
- resolve7({ success: code === 0, output });
5134
+ resolve9({ success: code === 0, output });
4412
5135
  });
4413
5136
  child.on("error", (err) => {
4414
- resolve7({ success: false, output: err.message });
5137
+ resolve9({ success: false, output: err.message });
4415
5138
  });
4416
5139
  });
4417
5140
  }
@@ -4426,13 +5149,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4426
5149
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
4427
5150
  return { process: null, port: actualPort };
4428
5151
  }
4429
- const usePnpm = existsSync7(join3(webDir, "pnpm-lock.yaml"));
4430
- const useNpm = !usePnpm && existsSync7(join3(webDir, "package-lock.json"));
5152
+ const usePnpm = existsSync10(join3(webDir, "pnpm-lock.yaml"));
5153
+ const useNpm = !usePnpm && existsSync10(join3(webDir, "package-lock.json"));
4431
5154
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
4432
5155
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
4433
5156
  const webEnv = {
4434
5157
  ...cleanEnv,
4435
- NEXT_PUBLIC_API_URL: `http://127.0.0.1:${apiPort}`
5158
+ NEXT_PUBLIC_API_URL: `http://127.0.0.1:${apiPort}`,
5159
+ PORT: String(actualPort)
5160
+ // Next.js respects PORT env var
4436
5161
  };
4437
5162
  const isProduction = process.env.NODE_ENV === "production";
4438
5163
  let command;
@@ -4454,7 +5179,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4454
5179
  command = pkgManager;
4455
5180
  args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev", "-p", String(actualPort)];
4456
5181
  }
4457
- const child = spawn(command, args, {
5182
+ const child = spawn2(command, args, {
4458
5183
  cwd: webDir,
4459
5184
  stdio: ["ignore", "pipe", "pipe"],
4460
5185
  env: webEnv,
@@ -4465,10 +5190,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4465
5190
  let started = false;
4466
5191
  let exited = false;
4467
5192
  let exitCode = null;
4468
- const startedPromise = new Promise((resolve7) => {
5193
+ const startedPromise = new Promise((resolve9) => {
4469
5194
  const timeout = setTimeout(() => {
4470
5195
  if (!started && !exited) {
4471
- resolve7(false);
5196
+ resolve9(false);
4472
5197
  }
4473
5198
  }, startupTimeout);
4474
5199
  child.stdout?.on("data", (data) => {
@@ -4476,7 +5201,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4476
5201
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
4477
5202
  started = true;
4478
5203
  clearTimeout(timeout);
4479
- resolve7(true);
5204
+ resolve9(true);
4480
5205
  }
4481
5206
  });
4482
5207
  child.stderr?.on("data", (data) => {
@@ -4488,14 +5213,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
4488
5213
  child.on("error", (err) => {
4489
5214
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
4490
5215
  clearTimeout(timeout);
4491
- resolve7(false);
5216
+ resolve9(false);
4492
5217
  });
4493
5218
  child.on("exit", (code) => {
4494
5219
  exited = true;
4495
5220
  exitCode = code;
4496
5221
  if (!started) {
4497
5222
  clearTimeout(timeout);
4498
- resolve7(false);
5223
+ resolve9(false);
4499
5224
  }
4500
5225
  webUIProcess = null;
4501
5226
  });
@@ -4580,8 +5305,8 @@ async function startServer(options = {}) {
4580
5305
  if (options.workingDirectory) {
4581
5306
  config.resolvedWorkingDirectory = options.workingDirectory;
4582
5307
  }
4583
- if (!existsSync7(config.resolvedWorkingDirectory)) {
4584
- mkdirSync2(config.resolvedWorkingDirectory, { recursive: true });
5308
+ if (!existsSync10(config.resolvedWorkingDirectory)) {
5309
+ mkdirSync3(config.resolvedWorkingDirectory, { recursive: true });
4585
5310
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
4586
5311
  }
4587
5312
  initDatabase(config.resolvedDatabasePath);