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/server/index.js
CHANGED
|
@@ -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
|
|
14
|
-
import { resolve as
|
|
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
|
|
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
|
|
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
|
|
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 = "
|
|
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
|
|
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(
|
|
1018
|
+
return join2(baseDir, LOG_BASE_DIR, sessionId, "terminals", terminalId);
|
|
1011
1019
|
}
|
|
1012
|
-
return join2(
|
|
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:
|
|
1226
|
-
const entries = await
|
|
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
|
-
|
|
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
|
|
1616
|
-
import { resolve as
|
|
1617
|
-
import { existsSync as
|
|
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 :
|
|
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 =
|
|
1852
|
-
if (!
|
|
2354
|
+
const dir = dirname5(absolutePath);
|
|
2355
|
+
if (!existsSync7(dir)) {
|
|
1853
2356
|
await mkdir3(dir, { recursive: true });
|
|
1854
2357
|
}
|
|
1855
|
-
const existed =
|
|
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 (!
|
|
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
|
|
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
|
|
2059
|
-
import { resolve as
|
|
2060
|
-
import { existsSync as
|
|
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,
|
|
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 (!
|
|
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 =
|
|
2099
|
-
const content = await
|
|
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
|
|
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
|
-
|
|
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
|
-
-
|
|
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] =
|
|
3457
|
+
wrappedTools[name] = tool7({
|
|
2735
3458
|
description: originalTool.description || "",
|
|
2736
|
-
inputSchema: originalTool.inputSchema ||
|
|
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((
|
|
2751
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
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 =
|
|
2847
|
-
name:
|
|
2848
|
-
workingDirectory:
|
|
2849
|
-
model:
|
|
2850
|
-
toolApprovals:
|
|
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 =
|
|
2853
|
-
limit:
|
|
2854
|
-
offset:
|
|
3575
|
+
var paginationQuerySchema = z9.object({
|
|
3576
|
+
limit: z9.string().optional(),
|
|
3577
|
+
offset: z9.string().optional()
|
|
2855
3578
|
});
|
|
2856
|
-
var messagesQuerySchema =
|
|
2857
|
-
limit:
|
|
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 =
|
|
2997
|
-
model:
|
|
2998
|
-
name:
|
|
2999
|
-
toolApprovals:
|
|
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
|
|
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 =
|
|
3278
|
-
prompt:
|
|
4000
|
+
var runPromptSchema = z10.object({
|
|
4001
|
+
prompt: z10.string().min(1)
|
|
3279
4002
|
});
|
|
3280
|
-
var quickStartSchema =
|
|
3281
|
-
prompt:
|
|
3282
|
-
name:
|
|
3283
|
-
workingDirectory:
|
|
3284
|
-
model:
|
|
3285
|
-
toolApprovals:
|
|
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 =
|
|
3288
|
-
reason:
|
|
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
|
|
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 =
|
|
3896
|
-
provider:
|
|
3897
|
-
apiKey:
|
|
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
|
|
4659
|
+
import { z as z12 } from "zod";
|
|
3937
4660
|
var terminals2 = new Hono4();
|
|
3938
|
-
var spawnSchema =
|
|
3939
|
-
command:
|
|
3940
|
-
cwd:
|
|
3941
|
-
name:
|
|
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 =
|
|
4023
|
-
tail:
|
|
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 =
|
|
4048
|
-
signal:
|
|
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 =
|
|
4063
|
-
input:
|
|
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 =
|
|
4329
|
-
const webDir =
|
|
4330
|
-
if (
|
|
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 =
|
|
4334
|
-
if (
|
|
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((
|
|
5080
|
+
return new Promise((resolve9) => {
|
|
4358
5081
|
const server = createNetServer();
|
|
4359
5082
|
server.once("error", (err) => {
|
|
4360
5083
|
if (err.code === "EADDRINUSE") {
|
|
4361
|
-
|
|
5084
|
+
resolve9(true);
|
|
4362
5085
|
} else {
|
|
4363
|
-
|
|
5086
|
+
resolve9(false);
|
|
4364
5087
|
}
|
|
4365
5088
|
});
|
|
4366
5089
|
server.once("listening", () => {
|
|
4367
5090
|
server.close();
|
|
4368
|
-
|
|
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
|
|
5116
|
+
return existsSync10(buildIdPath);
|
|
4394
5117
|
}
|
|
4395
5118
|
function runCommand(command, args, cwd, env) {
|
|
4396
|
-
return new Promise((
|
|
4397
|
-
const child =
|
|
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
|
-
|
|
5134
|
+
resolve9({ success: code === 0, output });
|
|
4412
5135
|
});
|
|
4413
5136
|
child.on("error", (err) => {
|
|
4414
|
-
|
|
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 =
|
|
4430
|
-
const useNpm = !usePnpm &&
|
|
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 =
|
|
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((
|
|
5193
|
+
const startedPromise = new Promise((resolve9) => {
|
|
4469
5194
|
const timeout = setTimeout(() => {
|
|
4470
5195
|
if (!started && !exited) {
|
|
4471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
4584
|
-
|
|
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);
|