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.
- package/dist/agent/index.js +768 -34
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +850 -120
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +831 -106
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +831 -106
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +56 -34
- package/dist/tools/index.js +866 -127
- package/dist/tools/index.js.map +1 -1
- package/package.json +2 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/dev/cache/.rscinfo +1 -1
- package/web/.next/dev/cache/config.json +3 -3
- package/web/.next/dev/cache/turbopack/731ace46/00000001.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000002.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000004.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000005.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000007.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000008.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000010.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000011.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000012.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000014.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000015.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000017.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000018.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000019.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000020.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000021.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000022.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000023.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000029.meta → 00000025.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000026.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000027.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000028.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000029.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000030.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000039.meta → 00000031.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000040.meta → 00000032.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000033.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000034.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000035.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000036.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000037.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000038.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000039.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000040.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000041.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000042.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000045.meta → 00000043.meta} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000044.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000045.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000046.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000047.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000048.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000049.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000050.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000051.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000052.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000053.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000054.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000055.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000056.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000057.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000058.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000059.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000060.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000061.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000062.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000063.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/{00000042.sst → 00000064.sst} +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000065.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000066.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000067.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000068.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000069.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000070.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000071.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000072.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000073.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000074.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000075.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000076.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000077.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000078.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000079.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000080.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/CURRENT +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/LOG +68 -30
- package/web/.next/dev/prerender-manifest.json +3 -3
- package/web/.next/dev/server/server-reference-manifest.js +1 -1
- package/web/.next/dev/server/server-reference-manifest.json +1 -1
- package/web/.next/dev/trace +1 -1
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/index.html +1 -1
- package/web/.next/server/app/index.rsc +4 -4
- package/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__4e36aa92._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js → 2374f__pnpm_43c403e8._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js → 2374f__pnpm_5f7087cf._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js → 2374f__pnpm_7cd13576._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js → 2374f__pnpm_9dd8b322._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js → 2374f__pnpm_ca8b3ccf._.js} +2 -2
- package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js → 2374f__pnpm_ce00fd2a._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js → 2374f__pnpm_e395e0b5._.js} +1 -1
- package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js → 2374f__pnpm_f1702ecf._.js} +1 -1
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__2dfcd951._.js → [root-of-the-server]__805216cc._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__805216cc._.js.map +1 -0
- package/web/.next/server/chunks/ssr/web_1658d276._.js +1 -1
- package/web/.next/server/chunks/ssr/web_1658d276._.js.map +1 -1
- package/web/.next/server/chunks/ssr/web_38b8ac39._.js +1 -1
- package/web/.next/server/chunks/ssr/web_38b8ac39._.js.map +1 -1
- package/web/.next/server/chunks/ssr/web_aaaa7872._.js +1 -1
- package/web/.next/server/chunks/ssr/web_aaaa7872._.js.map +1 -1
- package/web/.next/server/pages/404.html +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/chunks/33150aab64afe27a.css +1 -0
- package/web/.next/static/chunks/{64d14a57d96d13ab.js → 62358f9ae3ca4fc3.js} +1 -1
- package/web/.next/static/chunks/{f7a8e0e717eeadf2.js → 7441934619c44e53.js} +1 -1
- package/web/.next/static/chunks/{dd0466c07795b288.js → 75df31f521796a31.js} +1 -1
- package/web/.next/static/chunks/{bf853305cd7a9765.js → fa06da28948df001.js} +3 -3
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/dev/cache/turbopack/731ace46/00000024.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000025.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000028.meta +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000031.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000032.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000043.sst +0 -0
- package/web/.next/dev/cache/turbopack/731ace46/00000046.meta +0 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2dfcd951._.js.map +0 -1
- package/web/.next/static/chunks/7b140db694582cde.css +0 -1
- /package/web/.next/dev/cache/turbopack/731ace46/{00000027.meta → 00000024.meta} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_aa2ce93b._.js.map → 2374f__pnpm_43c403e8._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_2f0d7c45._.js.map → 2374f__pnpm_5f7087cf._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_f4abb025._.js.map → 2374f__pnpm_7cd13576._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_c7feb274._.js.map → 2374f__pnpm_9dd8b322._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_eb87394d._.js.map → 2374f__pnpm_ca8b3ccf._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_e04ff5e3._.js.map → 2374f__pnpm_ce00fd2a._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_d7f11ceb._.js.map → 2374f__pnpm_e395e0b5._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{2374f__pnpm_c03e1359._.js.map → 2374f__pnpm_f1702ecf._.js.map} +0 -0
- /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_buildManifest.js +0 -0
- /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{O9K30qQQXDEvnesMUBQxo → szyAyOMgs48yag2cwDbXh}/_ssgManifest.js +0 -0
package/dist/agent/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
streamText,
|
|
4
4
|
generateText as generateText2,
|
|
5
|
-
tool as
|
|
5
|
+
tool as tool7,
|
|
6
6
|
stepCountIs
|
|
7
7
|
} from "ai";
|
|
8
8
|
import { gateway as gateway2 } from "@ai-sdk/gateway";
|
|
9
|
-
import { z as
|
|
9
|
+
import { z as z8 } from "zod";
|
|
10
10
|
import { nanoid as nanoid3 } from "nanoid";
|
|
11
11
|
|
|
12
12
|
// src/db/index.ts
|
|
@@ -480,6 +480,17 @@ var SparkcoderConfigSchema = z.object({
|
|
|
480
480
|
});
|
|
481
481
|
|
|
482
482
|
// src/config/index.ts
|
|
483
|
+
function getAppDataDirectory() {
|
|
484
|
+
const appName = "sparkecoder";
|
|
485
|
+
switch (platform()) {
|
|
486
|
+
case "darwin":
|
|
487
|
+
return join(homedir(), "Library", "Application Support", appName);
|
|
488
|
+
case "win32":
|
|
489
|
+
return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), appName);
|
|
490
|
+
default:
|
|
491
|
+
return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
483
494
|
var cachedConfig = null;
|
|
484
495
|
function getConfig() {
|
|
485
496
|
if (!cachedConfig) {
|
|
@@ -541,12 +552,12 @@ function calculateContextSize(messages2) {
|
|
|
541
552
|
import { exec } from "child_process";
|
|
542
553
|
import { promisify } from "util";
|
|
543
554
|
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
544
|
-
import { existsSync as existsSync2 } from "fs";
|
|
555
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
545
556
|
import { join as join2 } from "path";
|
|
546
557
|
import { nanoid as nanoid2 } from "nanoid";
|
|
547
558
|
var execAsync = promisify(exec);
|
|
548
559
|
var SESSION_PREFIX = "spark_";
|
|
549
|
-
var LOG_BASE_DIR = "
|
|
560
|
+
var LOG_BASE_DIR = "sessions";
|
|
550
561
|
var tmuxAvailableCache = null;
|
|
551
562
|
async function isTmuxAvailable() {
|
|
552
563
|
if (tmuxAvailableCache !== null) {
|
|
@@ -568,11 +579,19 @@ function generateTerminalId() {
|
|
|
568
579
|
function getSessionName(terminalId) {
|
|
569
580
|
return `${SESSION_PREFIX}${terminalId}`;
|
|
570
581
|
}
|
|
571
|
-
function
|
|
582
|
+
function getTerminalDataDir() {
|
|
583
|
+
const appDataDir = getAppDataDirectory();
|
|
584
|
+
if (!existsSync2(appDataDir)) {
|
|
585
|
+
mkdirSync2(appDataDir, { recursive: true });
|
|
586
|
+
}
|
|
587
|
+
return appDataDir;
|
|
588
|
+
}
|
|
589
|
+
function getLogDir(terminalId, _workingDirectory, sessionId) {
|
|
590
|
+
const baseDir = getTerminalDataDir();
|
|
572
591
|
if (sessionId) {
|
|
573
|
-
return join2(
|
|
592
|
+
return join2(baseDir, LOG_BASE_DIR, sessionId, "terminals", terminalId);
|
|
574
593
|
}
|
|
575
|
-
return join2(
|
|
594
|
+
return join2(baseDir, "terminals", terminalId);
|
|
576
595
|
}
|
|
577
596
|
function shellEscape(str) {
|
|
578
597
|
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
@@ -888,7 +907,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
|
|
|
888
907
|
- For npm: add --yes or -y to skip confirmation
|
|
889
908
|
- If prompts are unavoidable, run in background mode and use input/key to respond
|
|
890
909
|
|
|
891
|
-
|
|
910
|
+
Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
|
|
892
911
|
inputSchema: bashInputSchema,
|
|
893
912
|
execute: async (inputArgs) => {
|
|
894
913
|
const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
|
|
@@ -1127,9 +1146,9 @@ Use this to understand existing code, check file contents, or gather context.`,
|
|
|
1127
1146
|
// src/tools/write-file.ts
|
|
1128
1147
|
import { tool as tool3 } from "ai";
|
|
1129
1148
|
import { z as z4 } from "zod";
|
|
1130
|
-
import { readFile as
|
|
1131
|
-
import { resolve as
|
|
1132
|
-
import { existsSync as
|
|
1149
|
+
import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1150
|
+
import { resolve as resolve5, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname5 } from "path";
|
|
1151
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1133
1152
|
|
|
1134
1153
|
// src/checkpoints/index.ts
|
|
1135
1154
|
import { readFile as readFile3, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
|
|
@@ -1182,6 +1201,501 @@ async function backupFile(sessionId, workingDirectory, filePath) {
|
|
|
1182
1201
|
return backup;
|
|
1183
1202
|
}
|
|
1184
1203
|
|
|
1204
|
+
// src/lsp/index.ts
|
|
1205
|
+
import { extname as extname2, dirname as dirname4 } from "path";
|
|
1206
|
+
|
|
1207
|
+
// src/lsp/servers.ts
|
|
1208
|
+
import { spawn } from "child_process";
|
|
1209
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1210
|
+
import { resolve as resolve4, dirname as dirname3 } from "path";
|
|
1211
|
+
function findNearestRoot(startDir, markers) {
|
|
1212
|
+
let dir = startDir;
|
|
1213
|
+
const root = "/";
|
|
1214
|
+
while (dir !== root) {
|
|
1215
|
+
for (const marker of markers) {
|
|
1216
|
+
if (existsSync5(resolve4(dir, marker))) {
|
|
1217
|
+
return dir;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const parent = dirname3(dir);
|
|
1221
|
+
if (parent === dir) break;
|
|
1222
|
+
dir = parent;
|
|
1223
|
+
}
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
async function commandExists(cmd) {
|
|
1227
|
+
try {
|
|
1228
|
+
const { exec: exec4 } = await import("child_process");
|
|
1229
|
+
const { promisify: promisify4 } = await import("util");
|
|
1230
|
+
const execAsync4 = promisify4(exec4);
|
|
1231
|
+
const isWindows = process.platform === "win32";
|
|
1232
|
+
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
1233
|
+
await execAsync4(checkCmd);
|
|
1234
|
+
return true;
|
|
1235
|
+
} catch {
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
var TypeScriptServer = {
|
|
1240
|
+
id: "typescript",
|
|
1241
|
+
name: "TypeScript Language Server",
|
|
1242
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
|
1243
|
+
async spawn(root) {
|
|
1244
|
+
const projectRoot = findNearestRoot(root, [
|
|
1245
|
+
"package-lock.json",
|
|
1246
|
+
"pnpm-lock.yaml",
|
|
1247
|
+
"yarn.lock",
|
|
1248
|
+
"bun.lockb",
|
|
1249
|
+
"bun.lock"
|
|
1250
|
+
]) || root;
|
|
1251
|
+
const hasNpx = await commandExists("npx");
|
|
1252
|
+
const hasBunx = await commandExists("bunx");
|
|
1253
|
+
const hasPnpx = await commandExists("pnpx");
|
|
1254
|
+
let cmd;
|
|
1255
|
+
if (hasPnpx) {
|
|
1256
|
+
cmd = ["pnpx", "typescript-language-server", "--stdio"];
|
|
1257
|
+
} else if (hasBunx) {
|
|
1258
|
+
cmd = ["bunx", "typescript-language-server", "--stdio"];
|
|
1259
|
+
} else if (hasNpx) {
|
|
1260
|
+
cmd = ["npx", "typescript-language-server", "--stdio"];
|
|
1261
|
+
} else {
|
|
1262
|
+
console.warn("[lsp] No package runner (npx/bunx/pnpx) found for typescript-language-server");
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
try {
|
|
1266
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
1267
|
+
cwd: projectRoot,
|
|
1268
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1269
|
+
env: {
|
|
1270
|
+
...process.env,
|
|
1271
|
+
// Suppress some noisy output
|
|
1272
|
+
TSS_LOG: "-level none"
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
proc.stderr?.on("data", (data) => {
|
|
1276
|
+
const msg = data.toString().trim();
|
|
1277
|
+
if (msg && !msg.includes("deprecated")) {
|
|
1278
|
+
console.debug("[lsp:typescript:stderr]", msg);
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
return {
|
|
1282
|
+
process: proc,
|
|
1283
|
+
initialization: {
|
|
1284
|
+
// TypeScript-specific initialization options
|
|
1285
|
+
preferences: {
|
|
1286
|
+
includeInlayParameterNameHints: "none",
|
|
1287
|
+
includeInlayPropertyDeclarationTypeHints: false,
|
|
1288
|
+
includeInlayFunctionLikeReturnTypeHints: false
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
console.error("[lsp] Failed to spawn typescript-language-server:", error);
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
var servers = [
|
|
1299
|
+
TypeScriptServer
|
|
1300
|
+
];
|
|
1301
|
+
function getServerForExtension(ext) {
|
|
1302
|
+
for (const server of servers) {
|
|
1303
|
+
if (server.extensions.includes(ext)) {
|
|
1304
|
+
return server;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
function getSupportedExtensions() {
|
|
1310
|
+
const extensions = /* @__PURE__ */ new Set();
|
|
1311
|
+
for (const server of servers) {
|
|
1312
|
+
for (const ext of server.extensions) {
|
|
1313
|
+
extensions.add(ext);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return Array.from(extensions);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// src/lsp/client.ts
|
|
1320
|
+
import {
|
|
1321
|
+
createMessageConnection,
|
|
1322
|
+
StreamMessageReader,
|
|
1323
|
+
StreamMessageWriter
|
|
1324
|
+
} from "vscode-jsonrpc/node";
|
|
1325
|
+
import { pathToFileURL, fileURLToPath } from "url";
|
|
1326
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1327
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1328
|
+
import { extname, normalize } from "path";
|
|
1329
|
+
function getLanguageId(filePath) {
|
|
1330
|
+
const ext = extname(filePath).toLowerCase();
|
|
1331
|
+
const map = {
|
|
1332
|
+
".ts": "typescript",
|
|
1333
|
+
".tsx": "typescriptreact",
|
|
1334
|
+
".js": "javascript",
|
|
1335
|
+
".jsx": "javascriptreact",
|
|
1336
|
+
".mjs": "javascript",
|
|
1337
|
+
".cjs": "javascript",
|
|
1338
|
+
".mts": "typescript",
|
|
1339
|
+
".cts": "typescript",
|
|
1340
|
+
".json": "json",
|
|
1341
|
+
".jsonc": "jsonc"
|
|
1342
|
+
};
|
|
1343
|
+
return map[ext] || "plaintext";
|
|
1344
|
+
}
|
|
1345
|
+
function normalizePath(filePath) {
|
|
1346
|
+
return normalize(filePath);
|
|
1347
|
+
}
|
|
1348
|
+
async function createClient(serverId, handle, root) {
|
|
1349
|
+
const { process: proc } = handle;
|
|
1350
|
+
if (!proc.stdout || !proc.stdin) {
|
|
1351
|
+
throw new Error("LSP server process has no stdout/stdin");
|
|
1352
|
+
}
|
|
1353
|
+
const connection = createMessageConnection(
|
|
1354
|
+
new StreamMessageReader(proc.stdout),
|
|
1355
|
+
new StreamMessageWriter(proc.stdin)
|
|
1356
|
+
);
|
|
1357
|
+
const diagnostics = /* @__PURE__ */ new Map();
|
|
1358
|
+
const fileVersions = /* @__PURE__ */ new Map();
|
|
1359
|
+
const diagnosticListeners = /* @__PURE__ */ new Map();
|
|
1360
|
+
connection.onNotification("textDocument/publishDiagnostics", (params) => {
|
|
1361
|
+
const filePath = normalizePath(fileURLToPath(params.uri));
|
|
1362
|
+
diagnostics.set(filePath, params.diagnostics || []);
|
|
1363
|
+
const listeners = diagnosticListeners.get(filePath);
|
|
1364
|
+
if (listeners) {
|
|
1365
|
+
for (const listener of listeners) {
|
|
1366
|
+
listener();
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
connection.onRequest("workspace/configuration", async (params) => {
|
|
1371
|
+
return params.items.map(() => handle.initialization || {});
|
|
1372
|
+
});
|
|
1373
|
+
connection.onRequest("client/registerCapability", async () => {
|
|
1374
|
+
return null;
|
|
1375
|
+
});
|
|
1376
|
+
connection.onRequest("window/workDoneProgress/create", async () => {
|
|
1377
|
+
return null;
|
|
1378
|
+
});
|
|
1379
|
+
connection.onNotification("window/logMessage", (params) => {
|
|
1380
|
+
if (params.type <= 2) {
|
|
1381
|
+
console.debug(`[lsp:${serverId}]`, params.message);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
connection.listen();
|
|
1385
|
+
const initResult = await connection.sendRequest("initialize", {
|
|
1386
|
+
processId: process.pid,
|
|
1387
|
+
rootUri: pathToFileURL(root).href,
|
|
1388
|
+
rootPath: root,
|
|
1389
|
+
workspaceFolders: [
|
|
1390
|
+
{
|
|
1391
|
+
name: "workspace",
|
|
1392
|
+
uri: pathToFileURL(root).href
|
|
1393
|
+
}
|
|
1394
|
+
],
|
|
1395
|
+
capabilities: {
|
|
1396
|
+
textDocument: {
|
|
1397
|
+
synchronization: {
|
|
1398
|
+
dynamicRegistration: true,
|
|
1399
|
+
willSave: false,
|
|
1400
|
+
willSaveWaitUntil: false,
|
|
1401
|
+
didSave: true
|
|
1402
|
+
},
|
|
1403
|
+
publishDiagnostics: {
|
|
1404
|
+
relatedInformation: true,
|
|
1405
|
+
versionSupport: true,
|
|
1406
|
+
codeDescriptionSupport: true
|
|
1407
|
+
},
|
|
1408
|
+
completion: {
|
|
1409
|
+
dynamicRegistration: true,
|
|
1410
|
+
completionItem: {
|
|
1411
|
+
snippetSupport: true,
|
|
1412
|
+
documentationFormat: ["markdown", "plaintext"]
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
hover: {
|
|
1416
|
+
dynamicRegistration: true,
|
|
1417
|
+
contentFormat: ["markdown", "plaintext"]
|
|
1418
|
+
},
|
|
1419
|
+
definition: {
|
|
1420
|
+
dynamicRegistration: true
|
|
1421
|
+
},
|
|
1422
|
+
references: {
|
|
1423
|
+
dynamicRegistration: true
|
|
1424
|
+
},
|
|
1425
|
+
documentSymbol: {
|
|
1426
|
+
dynamicRegistration: true
|
|
1427
|
+
}
|
|
1428
|
+
},
|
|
1429
|
+
workspace: {
|
|
1430
|
+
configuration: true,
|
|
1431
|
+
didChangeConfiguration: {
|
|
1432
|
+
dynamicRegistration: true
|
|
1433
|
+
},
|
|
1434
|
+
didChangeWatchedFiles: {
|
|
1435
|
+
dynamicRegistration: true
|
|
1436
|
+
},
|
|
1437
|
+
workspaceFolders: true
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
initializationOptions: handle.initialization
|
|
1441
|
+
});
|
|
1442
|
+
await connection.sendNotification("initialized", {});
|
|
1443
|
+
const client = {
|
|
1444
|
+
serverId,
|
|
1445
|
+
root,
|
|
1446
|
+
diagnostics,
|
|
1447
|
+
async notifyOpen(filePath) {
|
|
1448
|
+
const normalized = normalizePath(filePath);
|
|
1449
|
+
if (!existsSync6(normalized)) {
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
try {
|
|
1453
|
+
const content = await readFile4(normalized, "utf-8");
|
|
1454
|
+
const version = (fileVersions.get(normalized) ?? -1) + 1;
|
|
1455
|
+
fileVersions.set(normalized, version);
|
|
1456
|
+
if (version === 0) {
|
|
1457
|
+
await connection.sendNotification("textDocument/didOpen", {
|
|
1458
|
+
textDocument: {
|
|
1459
|
+
uri: pathToFileURL(normalized).href,
|
|
1460
|
+
languageId: getLanguageId(normalized),
|
|
1461
|
+
version,
|
|
1462
|
+
text: content
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
} else {
|
|
1466
|
+
await connection.sendNotification("textDocument/didChange", {
|
|
1467
|
+
textDocument: {
|
|
1468
|
+
uri: pathToFileURL(normalized).href,
|
|
1469
|
+
version
|
|
1470
|
+
},
|
|
1471
|
+
contentChanges: [{ text: content }]
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
console.error("[lsp] Error notifying open:", error);
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
async notifyChange(filePath) {
|
|
1479
|
+
const normalized = normalizePath(filePath);
|
|
1480
|
+
if (!existsSync6(normalized)) {
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
try {
|
|
1484
|
+
const content = await readFile4(normalized, "utf-8");
|
|
1485
|
+
const version = (fileVersions.get(normalized) ?? 0) + 1;
|
|
1486
|
+
fileVersions.set(normalized, version);
|
|
1487
|
+
await connection.sendNotification("textDocument/didChange", {
|
|
1488
|
+
textDocument: {
|
|
1489
|
+
uri: pathToFileURL(normalized).href,
|
|
1490
|
+
version
|
|
1491
|
+
},
|
|
1492
|
+
contentChanges: [{ text: content }]
|
|
1493
|
+
});
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
console.error("[lsp] Error notifying change:", error);
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
async notifyClose(filePath) {
|
|
1499
|
+
const normalized = normalizePath(filePath);
|
|
1500
|
+
fileVersions.delete(normalized);
|
|
1501
|
+
diagnostics.delete(normalized);
|
|
1502
|
+
try {
|
|
1503
|
+
await connection.sendNotification("textDocument/didClose", {
|
|
1504
|
+
textDocument: {
|
|
1505
|
+
uri: pathToFileURL(normalized).href
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
console.error("[lsp] Error notifying close:", error);
|
|
1510
|
+
}
|
|
1511
|
+
},
|
|
1512
|
+
async notifyWatchedFilesChanged(changes) {
|
|
1513
|
+
try {
|
|
1514
|
+
await connection.sendNotification("workspace/didChangeWatchedFiles", {
|
|
1515
|
+
changes
|
|
1516
|
+
});
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
console.error("[lsp] Error notifying watched files:", error);
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
async waitForDiagnostics(filePath, timeoutMs = 5e3) {
|
|
1522
|
+
const normalized = normalizePath(filePath);
|
|
1523
|
+
return new Promise((resolve8) => {
|
|
1524
|
+
const startTime = Date.now();
|
|
1525
|
+
let debounceTimer;
|
|
1526
|
+
let resolved = false;
|
|
1527
|
+
const cleanup = () => {
|
|
1528
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1529
|
+
const listeners = diagnosticListeners.get(normalized);
|
|
1530
|
+
if (listeners) {
|
|
1531
|
+
const idx = listeners.indexOf(onDiagnostic);
|
|
1532
|
+
if (idx >= 0) listeners.splice(idx, 1);
|
|
1533
|
+
if (listeners.length === 0) {
|
|
1534
|
+
diagnosticListeners.delete(normalized);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
const finish = () => {
|
|
1539
|
+
if (resolved) return;
|
|
1540
|
+
resolved = true;
|
|
1541
|
+
cleanup();
|
|
1542
|
+
resolve8(diagnostics.get(normalized) || []);
|
|
1543
|
+
};
|
|
1544
|
+
const onDiagnostic = () => {
|
|
1545
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1546
|
+
debounceTimer = setTimeout(finish, 150);
|
|
1547
|
+
};
|
|
1548
|
+
if (!diagnosticListeners.has(normalized)) {
|
|
1549
|
+
diagnosticListeners.set(normalized, []);
|
|
1550
|
+
}
|
|
1551
|
+
diagnosticListeners.get(normalized).push(onDiagnostic);
|
|
1552
|
+
setTimeout(() => {
|
|
1553
|
+
if (!resolved) {
|
|
1554
|
+
finish();
|
|
1555
|
+
}
|
|
1556
|
+
}, timeoutMs);
|
|
1557
|
+
if (diagnostics.has(normalized)) {
|
|
1558
|
+
onDiagnostic();
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
},
|
|
1562
|
+
getDiagnostics(filePath) {
|
|
1563
|
+
return diagnostics.get(normalizePath(filePath)) || [];
|
|
1564
|
+
},
|
|
1565
|
+
getAllDiagnostics() {
|
|
1566
|
+
return new Map(diagnostics);
|
|
1567
|
+
},
|
|
1568
|
+
async shutdown() {
|
|
1569
|
+
try {
|
|
1570
|
+
await connection.sendRequest("shutdown");
|
|
1571
|
+
await connection.sendNotification("exit");
|
|
1572
|
+
connection.end();
|
|
1573
|
+
connection.dispose();
|
|
1574
|
+
proc.kill();
|
|
1575
|
+
} catch (error) {
|
|
1576
|
+
proc.kill("SIGKILL");
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
return client;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/lsp/types.ts
|
|
1584
|
+
function formatDiagnostic(diagnostic) {
|
|
1585
|
+
const severity = {
|
|
1586
|
+
[1 /* Error */]: "ERROR",
|
|
1587
|
+
[2 /* Warning */]: "WARN",
|
|
1588
|
+
[3 /* Information */]: "INFO",
|
|
1589
|
+
[4 /* Hint */]: "HINT"
|
|
1590
|
+
}[diagnostic.severity ?? 1 /* Error */];
|
|
1591
|
+
const line = diagnostic.range.start.line + 1;
|
|
1592
|
+
const col = diagnostic.range.start.character + 1;
|
|
1593
|
+
const source = diagnostic.source ? ` [${diagnostic.source}]` : "";
|
|
1594
|
+
return `${severity} [${line}:${col}]${source} ${diagnostic.message}`;
|
|
1595
|
+
}
|
|
1596
|
+
function formatDiagnosticsForAgent(filePath, diagnostics, options = {}) {
|
|
1597
|
+
const { maxDiagnostics = 20, errorsOnly = true } = options;
|
|
1598
|
+
const filtered = errorsOnly ? diagnostics.filter((d) => d.severity === 1 /* Error */) : diagnostics;
|
|
1599
|
+
if (filtered.length === 0) return "";
|
|
1600
|
+
const limited = filtered.slice(0, maxDiagnostics);
|
|
1601
|
+
const suffix = filtered.length > maxDiagnostics ? `
|
|
1602
|
+
... and ${filtered.length - maxDiagnostics} more` : "";
|
|
1603
|
+
const formatted = limited.map(formatDiagnostic).join("\n");
|
|
1604
|
+
return `
|
|
1605
|
+
|
|
1606
|
+
LSP errors detected in this file, please fix:
|
|
1607
|
+
<diagnostics file="${filePath}">
|
|
1608
|
+
${formatted}${suffix}
|
|
1609
|
+
</diagnostics>`;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// src/lsp/index.ts
|
|
1613
|
+
var state = {
|
|
1614
|
+
clients: /* @__PURE__ */ new Map(),
|
|
1615
|
+
broken: /* @__PURE__ */ new Set(),
|
|
1616
|
+
initialized: false
|
|
1617
|
+
};
|
|
1618
|
+
async function getClientForFile(filePath) {
|
|
1619
|
+
const normalized = normalizePath(filePath);
|
|
1620
|
+
const ext = extname2(normalized);
|
|
1621
|
+
const serverDef = getServerForExtension(ext);
|
|
1622
|
+
if (!serverDef) {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
const root = dirname4(normalized);
|
|
1626
|
+
const key = `${serverDef.id}:${root}`;
|
|
1627
|
+
const existing = state.clients.get(key);
|
|
1628
|
+
if (existing) {
|
|
1629
|
+
return existing;
|
|
1630
|
+
}
|
|
1631
|
+
if (state.broken.has(key)) {
|
|
1632
|
+
return null;
|
|
1633
|
+
}
|
|
1634
|
+
try {
|
|
1635
|
+
const handle = await serverDef.spawn(root);
|
|
1636
|
+
if (!handle) {
|
|
1637
|
+
state.broken.add(key);
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
console.log(`[lsp] Started ${serverDef.name} for ${root}`);
|
|
1641
|
+
const client = await createClient(serverDef.id, handle, root);
|
|
1642
|
+
state.clients.set(key, client);
|
|
1643
|
+
handle.process.on("exit", (code) => {
|
|
1644
|
+
console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
|
|
1645
|
+
state.clients.delete(key);
|
|
1646
|
+
});
|
|
1647
|
+
return client;
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
|
|
1650
|
+
state.broken.add(key);
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
async function getClientsForFile(filePath) {
|
|
1655
|
+
const client = await getClientForFile(filePath);
|
|
1656
|
+
return client ? [client] : [];
|
|
1657
|
+
}
|
|
1658
|
+
async function touchFile(filePath, waitForDiagnostics = false) {
|
|
1659
|
+
const clients = await getClientsForFile(filePath);
|
|
1660
|
+
if (clients.length === 0) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
await Promise.all(clients.map((client) => client.notifyOpen(filePath)));
|
|
1664
|
+
if (waitForDiagnostics) {
|
|
1665
|
+
await Promise.all(clients.map((client) => client.waitForDiagnostics(filePath)));
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
async function getDiagnostics(filePath) {
|
|
1669
|
+
const normalized = normalizePath(filePath);
|
|
1670
|
+
const clients = await getClientsForFile(normalized);
|
|
1671
|
+
const allDiagnostics = [];
|
|
1672
|
+
for (const client of clients) {
|
|
1673
|
+
const diags = client.getDiagnostics(normalized);
|
|
1674
|
+
allDiagnostics.push(...diags);
|
|
1675
|
+
}
|
|
1676
|
+
return allDiagnostics;
|
|
1677
|
+
}
|
|
1678
|
+
async function getAllDiagnostics() {
|
|
1679
|
+
const results = {};
|
|
1680
|
+
for (const client of state.clients.values()) {
|
|
1681
|
+
const clientDiags = client.getAllDiagnostics();
|
|
1682
|
+
for (const [path, diagnostics] of clientDiags.entries()) {
|
|
1683
|
+
const existing = results[path] || [];
|
|
1684
|
+
existing.push(...diagnostics);
|
|
1685
|
+
results[path] = existing;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
return results;
|
|
1689
|
+
}
|
|
1690
|
+
async function formatDiagnosticsOutput(filePath, options = {}) {
|
|
1691
|
+
const diagnostics = await getDiagnostics(filePath);
|
|
1692
|
+
return formatDiagnosticsForAgent(filePath, diagnostics, options);
|
|
1693
|
+
}
|
|
1694
|
+
function isSupported(filePath) {
|
|
1695
|
+
const ext = extname2(filePath);
|
|
1696
|
+
return getServerForExtension(ext) !== null;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1185
1699
|
// src/tools/write-file.ts
|
|
1186
1700
|
var writeFileInputSchema = z4.object({
|
|
1187
1701
|
path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
|
|
@@ -1211,7 +1725,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1211
1725
|
inputSchema: writeFileInputSchema,
|
|
1212
1726
|
execute: async ({ path, mode, content, old_string, new_string }) => {
|
|
1213
1727
|
try {
|
|
1214
|
-
const absolutePath = isAbsolute2(path) ? path :
|
|
1728
|
+
const absolutePath = isAbsolute2(path) ? path : resolve5(options.workingDirectory, path);
|
|
1215
1729
|
const relativePath = relative3(options.workingDirectory, absolutePath);
|
|
1216
1730
|
if (relativePath.startsWith("..") && !isAbsolute2(path)) {
|
|
1217
1731
|
return {
|
|
@@ -1227,12 +1741,17 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1227
1741
|
};
|
|
1228
1742
|
}
|
|
1229
1743
|
await backupFile(options.sessionId, options.workingDirectory, absolutePath);
|
|
1230
|
-
const dir =
|
|
1231
|
-
if (!
|
|
1744
|
+
const dir = dirname5(absolutePath);
|
|
1745
|
+
if (!existsSync7(dir)) {
|
|
1232
1746
|
await mkdir3(dir, { recursive: true });
|
|
1233
1747
|
}
|
|
1234
|
-
const existed =
|
|
1748
|
+
const existed = existsSync7(absolutePath);
|
|
1235
1749
|
await writeFile3(absolutePath, content, "utf-8");
|
|
1750
|
+
let diagnosticsOutput = "";
|
|
1751
|
+
if (options.enableLSP !== false && isSupported(absolutePath)) {
|
|
1752
|
+
await touchFile(absolutePath, true);
|
|
1753
|
+
diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
|
|
1754
|
+
}
|
|
1236
1755
|
return {
|
|
1237
1756
|
success: true,
|
|
1238
1757
|
path: absolutePath,
|
|
@@ -1240,7 +1759,8 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1240
1759
|
mode: "full",
|
|
1241
1760
|
action: existed ? "replaced" : "created",
|
|
1242
1761
|
bytesWritten: Buffer.byteLength(content, "utf-8"),
|
|
1243
|
-
lineCount: content.split("\n").length
|
|
1762
|
+
lineCount: content.split("\n").length,
|
|
1763
|
+
...diagnosticsOutput && { diagnostics: diagnosticsOutput }
|
|
1244
1764
|
};
|
|
1245
1765
|
} else if (mode === "str_replace") {
|
|
1246
1766
|
if (old_string === void 0 || new_string === void 0) {
|
|
@@ -1249,14 +1769,14 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1249
1769
|
error: 'Both old_string and new_string are required for "str_replace" mode'
|
|
1250
1770
|
};
|
|
1251
1771
|
}
|
|
1252
|
-
if (!
|
|
1772
|
+
if (!existsSync7(absolutePath)) {
|
|
1253
1773
|
return {
|
|
1254
1774
|
success: false,
|
|
1255
1775
|
error: `File not found: ${path}. Use "full" mode to create new files.`
|
|
1256
1776
|
};
|
|
1257
1777
|
}
|
|
1258
1778
|
await backupFile(options.sessionId, options.workingDirectory, absolutePath);
|
|
1259
|
-
const currentContent = await
|
|
1779
|
+
const currentContent = await readFile5(absolutePath, "utf-8");
|
|
1260
1780
|
if (!currentContent.includes(old_string)) {
|
|
1261
1781
|
const lines = currentContent.split("\n");
|
|
1262
1782
|
const preview = lines.slice(0, 20).join("\n");
|
|
@@ -1280,6 +1800,11 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1280
1800
|
await writeFile3(absolutePath, newContent, "utf-8");
|
|
1281
1801
|
const oldLines = old_string.split("\n").length;
|
|
1282
1802
|
const newLines = new_string.split("\n").length;
|
|
1803
|
+
let diagnosticsOutput = "";
|
|
1804
|
+
if (options.enableLSP !== false && isSupported(absolutePath)) {
|
|
1805
|
+
await touchFile(absolutePath, true);
|
|
1806
|
+
diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
|
|
1807
|
+
}
|
|
1283
1808
|
return {
|
|
1284
1809
|
success: true,
|
|
1285
1810
|
path: absolutePath,
|
|
@@ -1287,7 +1812,8 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1287
1812
|
mode: "str_replace",
|
|
1288
1813
|
linesRemoved: oldLines,
|
|
1289
1814
|
linesAdded: newLines,
|
|
1290
|
-
lineDelta: newLines - oldLines
|
|
1815
|
+
lineDelta: newLines - oldLines,
|
|
1816
|
+
...diagnosticsOutput && { diagnostics: diagnosticsOutput }
|
|
1291
1817
|
};
|
|
1292
1818
|
}
|
|
1293
1819
|
return {
|
|
@@ -1434,9 +1960,9 @@ import { tool as tool5 } from "ai";
|
|
|
1434
1960
|
import { z as z6 } from "zod";
|
|
1435
1961
|
|
|
1436
1962
|
// src/skills/index.ts
|
|
1437
|
-
import { readFile as
|
|
1438
|
-
import { resolve as
|
|
1439
|
-
import { existsSync as
|
|
1963
|
+
import { readFile as readFile6, readdir } from "fs/promises";
|
|
1964
|
+
import { resolve as resolve6, basename, extname as extname3 } from "path";
|
|
1965
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1440
1966
|
function parseSkillFrontmatter(content) {
|
|
1441
1967
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
1442
1968
|
if (!frontmatterMatch) {
|
|
@@ -1464,18 +1990,18 @@ function parseSkillFrontmatter(content) {
|
|
|
1464
1990
|
}
|
|
1465
1991
|
}
|
|
1466
1992
|
function getSkillNameFromPath(filePath) {
|
|
1467
|
-
return basename(filePath,
|
|
1993
|
+
return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1468
1994
|
}
|
|
1469
1995
|
async function loadSkillsFromDirectory(directory) {
|
|
1470
|
-
if (!
|
|
1996
|
+
if (!existsSync8(directory)) {
|
|
1471
1997
|
return [];
|
|
1472
1998
|
}
|
|
1473
1999
|
const skills = [];
|
|
1474
2000
|
const files = await readdir(directory);
|
|
1475
2001
|
for (const file of files) {
|
|
1476
2002
|
if (!file.endsWith(".md")) continue;
|
|
1477
|
-
const filePath =
|
|
1478
|
-
const content = await
|
|
2003
|
+
const filePath = resolve6(directory, file);
|
|
2004
|
+
const content = await readFile6(filePath, "utf-8");
|
|
1479
2005
|
const parsed = parseSkillFrontmatter(content);
|
|
1480
2006
|
if (parsed) {
|
|
1481
2007
|
skills.push({
|
|
@@ -1517,7 +2043,7 @@ async function loadSkillContent(skillName, directories) {
|
|
|
1517
2043
|
if (!skill) {
|
|
1518
2044
|
return null;
|
|
1519
2045
|
}
|
|
1520
|
-
const content = await
|
|
2046
|
+
const content = await readFile6(skill.filePath, "utf-8");
|
|
1521
2047
|
const parsed = parseSkillFrontmatter(content);
|
|
1522
2048
|
return {
|
|
1523
2049
|
...skill,
|
|
@@ -1615,6 +2141,199 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
1615
2141
|
});
|
|
1616
2142
|
}
|
|
1617
2143
|
|
|
2144
|
+
// src/tools/linter.ts
|
|
2145
|
+
import { tool as tool6 } from "ai";
|
|
2146
|
+
import { z as z7 } from "zod";
|
|
2147
|
+
import { resolve as resolve7, relative as relative4, isAbsolute as isAbsolute3, extname as extname4 } from "path";
|
|
2148
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2149
|
+
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
2150
|
+
var linterInputSchema = z7.object({
|
|
2151
|
+
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."),
|
|
2152
|
+
fix: z7.boolean().optional().default(false).describe("Reserved for future use: auto-fix lint errors (not yet implemented)")
|
|
2153
|
+
});
|
|
2154
|
+
async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
|
|
2155
|
+
const files = [];
|
|
2156
|
+
const supportedExtensions = getSupportedExtensions();
|
|
2157
|
+
async function walk(currentDir) {
|
|
2158
|
+
if (files.length >= maxFiles) return;
|
|
2159
|
+
try {
|
|
2160
|
+
const entries = await readdir2(currentDir, { withFileTypes: true });
|
|
2161
|
+
for (const entry of entries) {
|
|
2162
|
+
if (files.length >= maxFiles) break;
|
|
2163
|
+
const fullPath = resolve7(currentDir, entry.name);
|
|
2164
|
+
if (entry.isDirectory()) {
|
|
2165
|
+
if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
|
|
2166
|
+
continue;
|
|
2167
|
+
}
|
|
2168
|
+
await walk(fullPath);
|
|
2169
|
+
} else if (entry.isFile()) {
|
|
2170
|
+
const ext = extname4(entry.name);
|
|
2171
|
+
if (supportedExtensions.includes(ext)) {
|
|
2172
|
+
files.push(fullPath);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
} catch {
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
await walk(dir);
|
|
2180
|
+
return files;
|
|
2181
|
+
}
|
|
2182
|
+
function createLinterTool(options) {
|
|
2183
|
+
return tool6({
|
|
2184
|
+
description: `Check files for linting and type errors using the Language Server Protocol (LSP).
|
|
2185
|
+
|
|
2186
|
+
Supports TypeScript, JavaScript, TSX, JSX files.
|
|
2187
|
+
|
|
2188
|
+
Usage:
|
|
2189
|
+
- \`linter({})\` - Get diagnostics for all recently edited files
|
|
2190
|
+
- \`linter({ paths: ["src/app.ts"] })\` - Check specific files
|
|
2191
|
+
- \`linter({ paths: ["src/"] })\` - Check all supported files in a directory
|
|
2192
|
+
|
|
2193
|
+
Returns detailed error information including line numbers, error messages, and severity.
|
|
2194
|
+
Use this after making changes to verify your code is correct, or proactively to find issues.
|
|
2195
|
+
|
|
2196
|
+
Working directory: ${options.workingDirectory}`,
|
|
2197
|
+
inputSchema: linterInputSchema,
|
|
2198
|
+
execute: async ({ paths }) => {
|
|
2199
|
+
try {
|
|
2200
|
+
if (!paths || paths.length === 0) {
|
|
2201
|
+
const allDiagnostics = await getAllDiagnostics();
|
|
2202
|
+
if (Object.keys(allDiagnostics).length === 0) {
|
|
2203
|
+
return {
|
|
2204
|
+
success: true,
|
|
2205
|
+
message: "No lint errors found. No files have been analyzed yet - specify paths to check specific files.",
|
|
2206
|
+
files: [],
|
|
2207
|
+
totalErrors: 0,
|
|
2208
|
+
totalWarnings: 0
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
return formatDiagnosticsResult(allDiagnostics, options.workingDirectory);
|
|
2212
|
+
}
|
|
2213
|
+
const filesToCheck = [];
|
|
2214
|
+
for (const path of paths) {
|
|
2215
|
+
const absolutePath = isAbsolute3(path) ? path : resolve7(options.workingDirectory, path);
|
|
2216
|
+
if (!existsSync9(absolutePath)) {
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
const stats = await stat2(absolutePath);
|
|
2220
|
+
if (stats.isDirectory()) {
|
|
2221
|
+
const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);
|
|
2222
|
+
filesToCheck.push(...dirFiles);
|
|
2223
|
+
} else if (stats.isFile()) {
|
|
2224
|
+
if (isSupported(absolutePath)) {
|
|
2225
|
+
filesToCheck.push(absolutePath);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
if (filesToCheck.length === 0) {
|
|
2230
|
+
return {
|
|
2231
|
+
success: true,
|
|
2232
|
+
message: "No supported files found to check. Supported extensions: " + getSupportedExtensions().join(", "),
|
|
2233
|
+
files: [],
|
|
2234
|
+
totalErrors: 0,
|
|
2235
|
+
totalWarnings: 0
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
await Promise.all(
|
|
2239
|
+
filesToCheck.map((file) => touchFile(file, true))
|
|
2240
|
+
);
|
|
2241
|
+
const diagnosticsMap = {};
|
|
2242
|
+
for (const file of filesToCheck) {
|
|
2243
|
+
const diagnostics = await getDiagnostics(file);
|
|
2244
|
+
if (diagnostics.length > 0) {
|
|
2245
|
+
diagnosticsMap[file] = diagnostics;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);
|
|
2249
|
+
} catch (error) {
|
|
2250
|
+
return {
|
|
2251
|
+
success: false,
|
|
2252
|
+
error: error.message
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
|
|
2259
|
+
let totalErrors = 0;
|
|
2260
|
+
let totalWarnings = 0;
|
|
2261
|
+
let totalInfo = 0;
|
|
2262
|
+
const files = [];
|
|
2263
|
+
for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
|
|
2264
|
+
const relativePath = relative4(workingDirectory, filePath);
|
|
2265
|
+
let fileErrors = 0;
|
|
2266
|
+
let fileWarnings = 0;
|
|
2267
|
+
const formattedDiagnostics = diagnostics.map((d) => {
|
|
2268
|
+
const severity = getSeverityString(d.severity);
|
|
2269
|
+
if (d.severity === 1 /* Error */) {
|
|
2270
|
+
fileErrors++;
|
|
2271
|
+
totalErrors++;
|
|
2272
|
+
} else if (d.severity === 2 /* Warning */) {
|
|
2273
|
+
fileWarnings++;
|
|
2274
|
+
totalWarnings++;
|
|
2275
|
+
} else {
|
|
2276
|
+
totalInfo++;
|
|
2277
|
+
}
|
|
2278
|
+
return {
|
|
2279
|
+
severity,
|
|
2280
|
+
line: d.range.start.line + 1,
|
|
2281
|
+
column: d.range.start.character + 1,
|
|
2282
|
+
message: d.message,
|
|
2283
|
+
source: d.source,
|
|
2284
|
+
code: d.code
|
|
2285
|
+
};
|
|
2286
|
+
});
|
|
2287
|
+
files.push({
|
|
2288
|
+
path: filePath,
|
|
2289
|
+
relativePath,
|
|
2290
|
+
errors: fileErrors,
|
|
2291
|
+
warnings: fileWarnings,
|
|
2292
|
+
diagnostics: formattedDiagnostics
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
files.sort((a, b) => b.errors - a.errors);
|
|
2296
|
+
const hasIssues = totalErrors > 0 || totalWarnings > 0;
|
|
2297
|
+
return {
|
|
2298
|
+
success: true,
|
|
2299
|
+
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).`,
|
|
2300
|
+
files,
|
|
2301
|
+
totalErrors,
|
|
2302
|
+
totalWarnings,
|
|
2303
|
+
totalInfo,
|
|
2304
|
+
summary: hasIssues ? formatSummary(files) : void 0
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
function getSeverityString(severity) {
|
|
2308
|
+
switch (severity) {
|
|
2309
|
+
case 1 /* Error */:
|
|
2310
|
+
return "error";
|
|
2311
|
+
case 2 /* Warning */:
|
|
2312
|
+
return "warning";
|
|
2313
|
+
case 3 /* Information */:
|
|
2314
|
+
return "info";
|
|
2315
|
+
case 4 /* Hint */:
|
|
2316
|
+
return "hint";
|
|
2317
|
+
default:
|
|
2318
|
+
return "error";
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
function formatSummary(files) {
|
|
2322
|
+
const lines = [];
|
|
2323
|
+
for (const file of files) {
|
|
2324
|
+
lines.push(`
|
|
2325
|
+
${file.relativePath}:`);
|
|
2326
|
+
for (const d of file.diagnostics.slice(0, 10)) {
|
|
2327
|
+
const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
2328
|
+
lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
|
|
2329
|
+
}
|
|
2330
|
+
if (file.diagnostics.length > 10) {
|
|
2331
|
+
lines.push(` ... and ${file.diagnostics.length - 10} more`);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return lines.join("\n");
|
|
2335
|
+
}
|
|
2336
|
+
|
|
1618
2337
|
// src/tools/index.ts
|
|
1619
2338
|
function createTools(options) {
|
|
1620
2339
|
return {
|
|
@@ -1629,7 +2348,8 @@ function createTools(options) {
|
|
|
1629
2348
|
}),
|
|
1630
2349
|
write_file: createWriteFileTool({
|
|
1631
2350
|
workingDirectory: options.workingDirectory,
|
|
1632
|
-
sessionId: options.sessionId
|
|
2351
|
+
sessionId: options.sessionId,
|
|
2352
|
+
enableLSP: options.enableLSP ?? true
|
|
1633
2353
|
}),
|
|
1634
2354
|
todo: createTodoTool({
|
|
1635
2355
|
sessionId: options.sessionId
|
|
@@ -1637,6 +2357,9 @@ function createTools(options) {
|
|
|
1637
2357
|
load_skill: createLoadSkillTool({
|
|
1638
2358
|
sessionId: options.sessionId,
|
|
1639
2359
|
skillsDirectories: options.skillsDirectories
|
|
2360
|
+
}),
|
|
2361
|
+
linter: createLinterTool({
|
|
2362
|
+
workingDirectory: options.workingDirectory
|
|
1640
2363
|
})
|
|
1641
2364
|
};
|
|
1642
2365
|
}
|
|
@@ -1684,6 +2407,7 @@ You have access to powerful tools for:
|
|
|
1684
2407
|
- **bash**: Execute commands in the terminal (see below for details)
|
|
1685
2408
|
- **read_file**: Read file contents to understand code and context
|
|
1686
2409
|
- **write_file**: Create new files or edit existing ones (supports targeted string replacement)
|
|
2410
|
+
- **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
|
|
1687
2411
|
- **todo**: Manage your task list to track progress on complex operations
|
|
1688
2412
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
1689
2413
|
|
|
@@ -1740,7 +2464,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
|
|
|
1740
2464
|
- Use \`key: "y"\` or \`key: "n"\` for yes/no prompts
|
|
1741
2465
|
- Use \`input: "text"\` for text input prompts
|
|
1742
2466
|
|
|
1743
|
-
|
|
2467
|
+
Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.
|
|
1744
2468
|
|
|
1745
2469
|
## Guidelines
|
|
1746
2470
|
|
|
@@ -1760,7 +2484,17 @@ Logs are saved to \`.sparkecoder/terminals/{id}/output.log\` and can be read wit
|
|
|
1760
2484
|
- Use \`read_file\` to understand code before modifying
|
|
1761
2485
|
- Use \`write_file\` with mode "str_replace" for targeted edits to existing files
|
|
1762
2486
|
- Use \`write_file\` with mode "full" only for new files or complete rewrites
|
|
1763
|
-
-
|
|
2487
|
+
- After making changes, use the \`linter\` tool to check for type errors and lint issues
|
|
2488
|
+
- The \`write_file\` tool automatically shows lint errors in its output for TypeScript/JavaScript files
|
|
2489
|
+
|
|
2490
|
+
### Linter Tool
|
|
2491
|
+
The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
|
|
2492
|
+
\`\`\`
|
|
2493
|
+
linter({}) // Check all recently edited files
|
|
2494
|
+
linter({ paths: ["src/app.ts"] }) // Check specific files
|
|
2495
|
+
linter({ paths: ["src/"] }) // Check all files in a directory
|
|
2496
|
+
\`\`\`
|
|
2497
|
+
Use this proactively after making code changes to catch errors early.
|
|
1764
2498
|
|
|
1765
2499
|
### Searching and Exploration
|
|
1766
2500
|
${searchInstructions}
|
|
@@ -2110,9 +2844,9 @@ var Agent = class _Agent {
|
|
|
2110
2844
|
wrappedTools[name] = originalTool;
|
|
2111
2845
|
continue;
|
|
2112
2846
|
}
|
|
2113
|
-
wrappedTools[name] =
|
|
2847
|
+
wrappedTools[name] = tool7({
|
|
2114
2848
|
description: originalTool.description || "",
|
|
2115
|
-
inputSchema: originalTool.inputSchema ||
|
|
2849
|
+
inputSchema: originalTool.inputSchema || z8.object({}),
|
|
2116
2850
|
execute: async (input, toolOptions) => {
|
|
2117
2851
|
const toolCallId = toolOptions.toolCallId || nanoid3();
|
|
2118
2852
|
const execution = toolExecutionQueries.create({
|
|
@@ -2126,8 +2860,8 @@ var Agent = class _Agent {
|
|
|
2126
2860
|
this.pendingApprovals.set(toolCallId, execution);
|
|
2127
2861
|
options.onApprovalRequired?.(execution);
|
|
2128
2862
|
sessionQueries.updateStatus(this.session.id, "waiting");
|
|
2129
|
-
const approved = await new Promise((
|
|
2130
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
2863
|
+
const approved = await new Promise((resolve8) => {
|
|
2864
|
+
approvalResolvers.set(toolCallId, { resolve: resolve8, sessionId: this.session.id });
|
|
2131
2865
|
});
|
|
2132
2866
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
2133
2867
|
approvalResolvers.delete(toolCallId);
|