sparkecoder 0.1.56 → 0.1.58
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.d.ts +2 -2
- package/dist/agent/index.js +706 -68
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +824 -186
- package/dist/cli.js.map +1 -1
- package/dist/{index-CqKMYoJv.d.ts → index-DqLDYWhb.d.ts} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +811 -173
- package/dist/index.js.map +1 -1
- package/dist/{search-CZsTAZ90.d.ts → search-DINnDTgj.d.ts} +1 -0
- package/dist/server/index.js +811 -173
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +49 -3
- package/dist/tools/index.js +659 -60
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f661d73d._.js → 2374f_076f03ec._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_e35df3d7._.js → 2374f_19289e11._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_6ced4caf._.js → 2374f_2f0d9f6f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_aade2a6d._.js → 2374f_35475cbe._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_562d2f4f._.js → 2374f_40e35a02._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_05c00b91._.js → 2374f_4666c827._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ac284eb3._.js → 2374f_4858a1ea._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_39f76acc._.js → 2374f_51385fed._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ef435bac._.js → 2374f_7db22cde._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_800db4e3._.js → 2374f_90b8e4fb._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1fa4a79a._.js → 2374f_b17fce11._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d118a207._.js → 2374f_b4b86c1f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_934cb548._.js → 2374f_d8b9ce38._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c272cbb4._.js → 2374f_fb95e3c9._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f44222d9._.js → [root-of-the-server]__7775f784._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__8cdc7b0b._.js → [root-of-the-server]__bd396152._.js} +4 -4
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_0a13adb9._.js → web_645f4b90._.js} +2 -2
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/2868b007ce5163fc.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/{ca717ddd7c8f37e8.js → 631b023d37a08635.js} +6 -6
- package/web/.next/standalone/web/.next/static/static/chunks/2868b007ce5163fc.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/{ca717ddd7c8f37e8.js → 631b023d37a08635.js} +6 -6
- package/web/.next/standalone/web/src/components/ai-elements/code-graph-tool.tsx +202 -0
- package/web/.next/standalone/web/src/components/chat-interface.tsx +49 -0
- package/web/.next/static/chunks/2868b007ce5163fc.css +1 -0
- package/web/.next/static/chunks/{ca717ddd7c8f37e8.js → 631b023d37a08635.js} +6 -6
- package/web/.next/standalone/web/.next/static/chunks/7228b2394d1fb347.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/7228b2394d1fb347.css +0 -1
- package/web/.next/static/chunks/7228b2394d1fb347.css +0 -1
- /package/web/.next/standalone/web/.next/static/{static/yfBGAcI8gI55lwQllPu-z → TuFKgULSvgTGHxXzZoeMo}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/yfBGAcI8gI55lwQllPu-z → TuFKgULSvgTGHxXzZoeMo}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/yfBGAcI8gI55lwQllPu-z → TuFKgULSvgTGHxXzZoeMo}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{yfBGAcI8gI55lwQllPu-z → static/TuFKgULSvgTGHxXzZoeMo}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{yfBGAcI8gI55lwQllPu-z → static/TuFKgULSvgTGHxXzZoeMo}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{yfBGAcI8gI55lwQllPu-z → static/TuFKgULSvgTGHxXzZoeMo}/_ssgManifest.js +0 -0
- /package/web/.next/static/{yfBGAcI8gI55lwQllPu-z → TuFKgULSvgTGHxXzZoeMo}/_buildManifest.js +0 -0
- /package/web/.next/static/{yfBGAcI8gI55lwQllPu-z → TuFKgULSvgTGHxXzZoeMo}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{yfBGAcI8gI55lwQllPu-z → TuFKgULSvgTGHxXzZoeMo}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -1461,13 +1461,13 @@ var semantic_search_exports = {};
|
|
|
1461
1461
|
__export(semantic_search_exports, {
|
|
1462
1462
|
createSemanticSearchTool: () => createSemanticSearchTool
|
|
1463
1463
|
});
|
|
1464
|
-
import { tool as
|
|
1465
|
-
import { z as
|
|
1466
|
-
import { existsSync as
|
|
1464
|
+
import { tool as tool8 } from "ai";
|
|
1465
|
+
import { z as z9 } from "zod";
|
|
1466
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
|
|
1467
1467
|
import { join as join4 } from "path";
|
|
1468
1468
|
import { minimatch as minimatch3 } from "minimatch";
|
|
1469
1469
|
function createSemanticSearchTool(options) {
|
|
1470
|
-
return
|
|
1470
|
+
return tool8({
|
|
1471
1471
|
description: `Search the codebase using semantic similarity. This tool finds code by understanding its meaning, not just matching text.
|
|
1472
1472
|
|
|
1473
1473
|
Use this tool when:
|
|
@@ -1532,7 +1532,7 @@ Returns matching code snippets with file paths, line numbers, and relevance scor
|
|
|
1532
1532
|
continue;
|
|
1533
1533
|
}
|
|
1534
1534
|
const fullPath = join4(options.workingDirectory, filePath);
|
|
1535
|
-
if (!
|
|
1535
|
+
if (!existsSync11(fullPath)) {
|
|
1536
1536
|
continue;
|
|
1537
1537
|
}
|
|
1538
1538
|
let snippet = "";
|
|
@@ -1587,11 +1587,11 @@ var init_semantic_search = __esm({
|
|
|
1587
1587
|
"use strict";
|
|
1588
1588
|
init_semantic();
|
|
1589
1589
|
init_config();
|
|
1590
|
-
semanticSearchInputSchema =
|
|
1591
|
-
query:
|
|
1592
|
-
topK:
|
|
1593
|
-
filePattern:
|
|
1594
|
-
language:
|
|
1590
|
+
semanticSearchInputSchema = z9.object({
|
|
1591
|
+
query: z9.string().describe("Natural language search query describing what you want to find"),
|
|
1592
|
+
topK: z9.number().optional().default(10).describe("Number of results to return (default: 10, max: 50)"),
|
|
1593
|
+
filePattern: z9.string().optional().describe('Filter results by file glob pattern (e.g., "*.ts", "src/**/*.py")'),
|
|
1594
|
+
language: z9.string().optional().describe('Filter by programming language (e.g., "typescript", "python")')
|
|
1595
1595
|
});
|
|
1596
1596
|
}
|
|
1597
1597
|
});
|
|
@@ -1600,7 +1600,7 @@ var init_semantic_search = __esm({
|
|
|
1600
1600
|
import {
|
|
1601
1601
|
streamText as streamText2,
|
|
1602
1602
|
generateText as generateText3,
|
|
1603
|
-
tool as
|
|
1603
|
+
tool as tool11,
|
|
1604
1604
|
stepCountIs as stepCountIs2
|
|
1605
1605
|
} from "ai";
|
|
1606
1606
|
|
|
@@ -1623,7 +1623,7 @@ var SUBAGENT_MODELS = {
|
|
|
1623
1623
|
// src/agent/index.ts
|
|
1624
1624
|
init_db();
|
|
1625
1625
|
init_config();
|
|
1626
|
-
import { z as
|
|
1626
|
+
import { z as z12 } from "zod";
|
|
1627
1627
|
import { nanoid as nanoid3 } from "nanoid";
|
|
1628
1628
|
|
|
1629
1629
|
// src/tools/bash.ts
|
|
@@ -1927,8 +1927,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
|
|
|
1927
1927
|
const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
|
|
1928
1928
|
const terminals2 = [];
|
|
1929
1929
|
try {
|
|
1930
|
-
const { readdir:
|
|
1931
|
-
const entries = await
|
|
1930
|
+
const { readdir: readdir6 } = await import("fs/promises");
|
|
1931
|
+
const entries = await readdir6(terminalsDir, { withFileTypes: true });
|
|
1932
1932
|
for (const entry of entries) {
|
|
1933
1933
|
if (entry.isDirectory()) {
|
|
1934
1934
|
const meta = await getMeta(entry.name, workingDirectory, sessionId);
|
|
@@ -2731,7 +2731,11 @@ async function createClient(serverId, handle, root) {
|
|
|
2731
2731
|
dynamicRegistration: true
|
|
2732
2732
|
},
|
|
2733
2733
|
documentSymbol: {
|
|
2734
|
-
dynamicRegistration: true
|
|
2734
|
+
dynamicRegistration: true,
|
|
2735
|
+
hierarchicalDocumentSymbolSupport: true,
|
|
2736
|
+
symbolKind: {
|
|
2737
|
+
valueSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
|
|
2738
|
+
}
|
|
2735
2739
|
}
|
|
2736
2740
|
},
|
|
2737
2741
|
workspace: {
|
|
@@ -2828,7 +2832,7 @@ async function createClient(serverId, handle, root) {
|
|
|
2828
2832
|
},
|
|
2829
2833
|
async waitForDiagnostics(filePath, timeoutMs = 5e3) {
|
|
2830
2834
|
const normalized = normalizePath(filePath);
|
|
2831
|
-
return new Promise((
|
|
2835
|
+
return new Promise((resolve11) => {
|
|
2832
2836
|
const startTime = Date.now();
|
|
2833
2837
|
let debounceTimer;
|
|
2834
2838
|
let resolved = false;
|
|
@@ -2847,7 +2851,7 @@ async function createClient(serverId, handle, root) {
|
|
|
2847
2851
|
if (resolved) return;
|
|
2848
2852
|
resolved = true;
|
|
2849
2853
|
cleanup();
|
|
2850
|
-
|
|
2854
|
+
resolve11(diagnostics.get(normalized) || []);
|
|
2851
2855
|
};
|
|
2852
2856
|
const onDiagnostic = () => {
|
|
2853
2857
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -2873,6 +2877,100 @@ async function createClient(serverId, handle, root) {
|
|
|
2873
2877
|
getAllDiagnostics() {
|
|
2874
2878
|
return new Map(diagnostics);
|
|
2875
2879
|
},
|
|
2880
|
+
async getDefinition(filePath, line, character) {
|
|
2881
|
+
const normalized = normalizePath(filePath);
|
|
2882
|
+
if (!fileVersions.has(normalized)) {
|
|
2883
|
+
await client.notifyOpen(normalized);
|
|
2884
|
+
}
|
|
2885
|
+
try {
|
|
2886
|
+
const result = await connection.sendRequest("textDocument/definition", {
|
|
2887
|
+
textDocument: { uri: pathToFileURL(normalized).href },
|
|
2888
|
+
position: { line, character }
|
|
2889
|
+
});
|
|
2890
|
+
if (!result) return [];
|
|
2891
|
+
const items = Array.isArray(result) ? result : [result];
|
|
2892
|
+
return items.map((r) => ({
|
|
2893
|
+
uri: r.targetUri || r.uri,
|
|
2894
|
+
range: r.targetRange || r.range
|
|
2895
|
+
}));
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
console.error("[lsp] Error getting definition:", error);
|
|
2898
|
+
return [];
|
|
2899
|
+
}
|
|
2900
|
+
},
|
|
2901
|
+
async getReferences(filePath, line, character, includeDeclaration = false) {
|
|
2902
|
+
const normalized = normalizePath(filePath);
|
|
2903
|
+
if (!fileVersions.has(normalized)) {
|
|
2904
|
+
await client.notifyOpen(normalized);
|
|
2905
|
+
}
|
|
2906
|
+
try {
|
|
2907
|
+
const result = await connection.sendRequest("textDocument/references", {
|
|
2908
|
+
textDocument: { uri: pathToFileURL(normalized).href },
|
|
2909
|
+
position: { line, character },
|
|
2910
|
+
context: { includeDeclaration }
|
|
2911
|
+
});
|
|
2912
|
+
return result || [];
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
console.error("[lsp] Error getting references:", error);
|
|
2915
|
+
return [];
|
|
2916
|
+
}
|
|
2917
|
+
},
|
|
2918
|
+
async getHover(filePath, line, character) {
|
|
2919
|
+
const normalized = normalizePath(filePath);
|
|
2920
|
+
if (!fileVersions.has(normalized)) {
|
|
2921
|
+
await client.notifyOpen(normalized);
|
|
2922
|
+
}
|
|
2923
|
+
try {
|
|
2924
|
+
const result = await connection.sendRequest("textDocument/hover", {
|
|
2925
|
+
textDocument: { uri: pathToFileURL(normalized).href },
|
|
2926
|
+
position: { line, character }
|
|
2927
|
+
});
|
|
2928
|
+
if (!result || !result.contents) return null;
|
|
2929
|
+
if (typeof result.contents === "string") return result.contents;
|
|
2930
|
+
if (result.contents.value) return result.contents.value;
|
|
2931
|
+
if (Array.isArray(result.contents)) {
|
|
2932
|
+
return result.contents.map((c) => typeof c === "string" ? c : c.value).join("\n");
|
|
2933
|
+
}
|
|
2934
|
+
return null;
|
|
2935
|
+
} catch (error) {
|
|
2936
|
+
console.error("[lsp] Error getting hover:", error);
|
|
2937
|
+
return null;
|
|
2938
|
+
}
|
|
2939
|
+
},
|
|
2940
|
+
async getDocumentSymbols(filePath) {
|
|
2941
|
+
const normalized = normalizePath(filePath);
|
|
2942
|
+
if (!fileVersions.has(normalized)) {
|
|
2943
|
+
await client.notifyOpen(normalized);
|
|
2944
|
+
}
|
|
2945
|
+
try {
|
|
2946
|
+
const result = await connection.sendRequest("textDocument/documentSymbol", {
|
|
2947
|
+
textDocument: { uri: pathToFileURL(normalized).href }
|
|
2948
|
+
});
|
|
2949
|
+
if (!result || result.length === 0) return [];
|
|
2950
|
+
if (result[0].range) {
|
|
2951
|
+
return result;
|
|
2952
|
+
}
|
|
2953
|
+
return result.map((si) => ({
|
|
2954
|
+
name: si.name,
|
|
2955
|
+
kind: si.kind,
|
|
2956
|
+
range: si.location?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
|
|
2957
|
+
selectionRange: si.location?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
|
|
2958
|
+
detail: si.containerName
|
|
2959
|
+
}));
|
|
2960
|
+
} catch (error) {
|
|
2961
|
+
console.error("[lsp] Error getting document symbols:", error);
|
|
2962
|
+
return [];
|
|
2963
|
+
}
|
|
2964
|
+
},
|
|
2965
|
+
async findWorkspaceSymbols(query) {
|
|
2966
|
+
try {
|
|
2967
|
+
const result = await connection.sendRequest("workspace/symbol", { query });
|
|
2968
|
+
return result || [];
|
|
2969
|
+
} catch (error) {
|
|
2970
|
+
console.error("[lsp] Error finding workspace symbols:", error);
|
|
2971
|
+
return [];
|
|
2972
|
+
}
|
|
2973
|
+
},
|
|
2876
2974
|
async shutdown() {
|
|
2877
2975
|
try {
|
|
2878
2976
|
await connection.sendRequest("shutdown");
|
|
@@ -2995,6 +3093,24 @@ async function getAllDiagnostics() {
|
|
|
2995
3093
|
}
|
|
2996
3094
|
return results;
|
|
2997
3095
|
}
|
|
3096
|
+
async function getReferences(filePath, line, character, includeDeclaration = false) {
|
|
3097
|
+
const normalized = normalizePath(filePath);
|
|
3098
|
+
const client = await getClientForFile(normalized);
|
|
3099
|
+
if (!client) return [];
|
|
3100
|
+
return client.getReferences(normalized, line, character, includeDeclaration);
|
|
3101
|
+
}
|
|
3102
|
+
async function getHover(filePath, line, character) {
|
|
3103
|
+
const normalized = normalizePath(filePath);
|
|
3104
|
+
const client = await getClientForFile(normalized);
|
|
3105
|
+
if (!client) return null;
|
|
3106
|
+
return client.getHover(normalized, line, character);
|
|
3107
|
+
}
|
|
3108
|
+
async function getDocumentSymbols(filePath) {
|
|
3109
|
+
const normalized = normalizePath(filePath);
|
|
3110
|
+
const client = await getClientForFile(normalized);
|
|
3111
|
+
if (!client) return [];
|
|
3112
|
+
return client.getDocumentSymbols(normalized);
|
|
3113
|
+
}
|
|
2998
3114
|
async function formatDiagnosticsOutput(filePath, options = {}) {
|
|
2999
3115
|
const diagnostics = await getDiagnostics(filePath);
|
|
3000
3116
|
return formatDiagnosticsForAgent(filePath, diagnostics, options);
|
|
@@ -3090,7 +3206,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3090
3206
|
isChunked: true
|
|
3091
3207
|
});
|
|
3092
3208
|
if (chunkCount > 1) {
|
|
3093
|
-
await new Promise((
|
|
3209
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
3094
3210
|
}
|
|
3095
3211
|
}
|
|
3096
3212
|
}
|
|
@@ -3618,8 +3734,8 @@ ${file.relativePath}:`);
|
|
|
3618
3734
|
}
|
|
3619
3735
|
|
|
3620
3736
|
// src/tools/search.ts
|
|
3621
|
-
import { tool as
|
|
3622
|
-
import { z as
|
|
3737
|
+
import { tool as tool10 } from "ai";
|
|
3738
|
+
import { z as z11 } from "zod";
|
|
3623
3739
|
|
|
3624
3740
|
// src/agent/subagent.ts
|
|
3625
3741
|
import {
|
|
@@ -3791,8 +3907,8 @@ var Subagent = class {
|
|
|
3791
3907
|
if (eventQueue.length > 0) {
|
|
3792
3908
|
yield eventQueue.shift();
|
|
3793
3909
|
} else if (!done) {
|
|
3794
|
-
const event = await new Promise((
|
|
3795
|
-
resolveNext =
|
|
3910
|
+
const event = await new Promise((resolve11) => {
|
|
3911
|
+
resolveNext = resolve11;
|
|
3796
3912
|
});
|
|
3797
3913
|
if (event) {
|
|
3798
3914
|
yield event;
|
|
@@ -3804,14 +3920,466 @@ var Subagent = class {
|
|
|
3804
3920
|
};
|
|
3805
3921
|
|
|
3806
3922
|
// src/agent/subagents/search.ts
|
|
3807
|
-
import { tool as
|
|
3808
|
-
import { z as
|
|
3923
|
+
import { tool as tool9 } from "ai";
|
|
3924
|
+
import { z as z10 } from "zod";
|
|
3809
3925
|
import { exec as exec4 } from "child_process";
|
|
3810
3926
|
import { promisify as promisify4 } from "util";
|
|
3811
|
-
import { readFile as
|
|
3812
|
-
import { resolve as
|
|
3813
|
-
import { existsSync as
|
|
3927
|
+
import { readFile as readFile8, stat as stat3, readdir as readdir4 } from "fs/promises";
|
|
3928
|
+
import { resolve as resolve9, relative as relative8, isAbsolute as isAbsolute5 } from "path";
|
|
3929
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3814
3930
|
init_semantic();
|
|
3931
|
+
|
|
3932
|
+
// src/tools/code-graph.ts
|
|
3933
|
+
import { tool as tool7 } from "ai";
|
|
3934
|
+
import { z as z8 } from "zod";
|
|
3935
|
+
import { resolve as resolve8, relative as relative7, isAbsolute as isAbsolute4, basename as basename3 } from "path";
|
|
3936
|
+
import { readFile as readFile7, readdir as readdir3 } from "fs/promises";
|
|
3937
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3938
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3939
|
+
import { execFileSync } from "child_process";
|
|
3940
|
+
var codeGraphInputSchema = z8.object({
|
|
3941
|
+
symbol: z8.string().describe(
|
|
3942
|
+
"The symbol name to inspect (function, component, class, type, variable, etc.)"
|
|
3943
|
+
),
|
|
3944
|
+
filePath: z8.string().optional().describe(
|
|
3945
|
+
"File path where the symbol is defined. If omitted, searches the workspace via grep."
|
|
3946
|
+
),
|
|
3947
|
+
depth: z8.number().optional().default(2).describe(
|
|
3948
|
+
"How many levels of references to traverse upward (default: 2, max: 3). Level 1 = direct usages, level 2 = usages of those usages."
|
|
3949
|
+
)
|
|
3950
|
+
});
|
|
3951
|
+
function isPageFile(filePath) {
|
|
3952
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
3953
|
+
if (/\/app\/(.+\/)?(page|layout|loading|error|not-found)\.(tsx?|jsx?)$/.test(normalized)) return true;
|
|
3954
|
+
if (/\/pages\/(?!_|api\/).+\.(tsx?|jsx?)$/.test(normalized)) return true;
|
|
3955
|
+
return false;
|
|
3956
|
+
}
|
|
3957
|
+
function extractRoutePath(filePath, workingDirectory) {
|
|
3958
|
+
const rel = relative7(workingDirectory, filePath).replace(/\\/g, "/");
|
|
3959
|
+
const appMatch = rel.match(/(?:src\/)?app((?:\/[^/]+)*?)\/(?:page|layout|loading|error|not-found)\.\w+$/);
|
|
3960
|
+
if (appMatch) return appMatch[1] || "/";
|
|
3961
|
+
const pagesMatch = rel.match(/(?:src\/)?pages(\/.*?)(?:\/index)?\.\w+$/);
|
|
3962
|
+
if (pagesMatch) return pagesMatch[1] || "/";
|
|
3963
|
+
return void 0;
|
|
3964
|
+
}
|
|
3965
|
+
function symbolKindName(kind) {
|
|
3966
|
+
const names = {
|
|
3967
|
+
[5 /* Class */]: "class",
|
|
3968
|
+
[12 /* Function */]: "function",
|
|
3969
|
+
[6 /* Method */]: "method",
|
|
3970
|
+
[7 /* Property */]: "property",
|
|
3971
|
+
[13 /* Variable */]: "variable",
|
|
3972
|
+
[11 /* Interface */]: "interface",
|
|
3973
|
+
[10 /* Enum */]: "enum",
|
|
3974
|
+
[14 /* Constant */]: "constant",
|
|
3975
|
+
[9 /* Constructor */]: "constructor",
|
|
3976
|
+
[2 /* Module */]: "module",
|
|
3977
|
+
[3 /* Namespace */]: "namespace",
|
|
3978
|
+
[26 /* TypeParameter */]: "type_param",
|
|
3979
|
+
[8 /* Field */]: "field",
|
|
3980
|
+
[22 /* EnumMember */]: "enum_member",
|
|
3981
|
+
[19 /* Object */]: "object"
|
|
3982
|
+
};
|
|
3983
|
+
return names[kind] || "symbol";
|
|
3984
|
+
}
|
|
3985
|
+
function findContainingSymbol(symbols, line, character) {
|
|
3986
|
+
for (const sym of symbols) {
|
|
3987
|
+
if (!sym.range) continue;
|
|
3988
|
+
const { start, end } = sym.range;
|
|
3989
|
+
const afterStart = line > start.line || line === start.line && character >= start.character;
|
|
3990
|
+
const beforeEnd = line < end.line || line === end.line && character < end.character;
|
|
3991
|
+
if (afterStart && beforeEnd) {
|
|
3992
|
+
if (sym.children?.length) {
|
|
3993
|
+
const child = findContainingSymbol(sym.children, line, character);
|
|
3994
|
+
if (child) return child;
|
|
3995
|
+
}
|
|
3996
|
+
return sym;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
return null;
|
|
4000
|
+
}
|
|
4001
|
+
function findSymbolByName(symbols, name) {
|
|
4002
|
+
for (const sym of symbols) {
|
|
4003
|
+
if (sym.name === name && sym.selectionRange) return sym;
|
|
4004
|
+
if (sym.children) {
|
|
4005
|
+
const found = findSymbolByName(sym.children, name);
|
|
4006
|
+
if (found) return found;
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
return null;
|
|
4010
|
+
}
|
|
4011
|
+
function cleanHoverText(text) {
|
|
4012
|
+
return text.replace(/```\w*\n?/g, "").replace(/\n```/g, "").trim();
|
|
4013
|
+
}
|
|
4014
|
+
async function grepForSymbol(symbol, workingDirectory) {
|
|
4015
|
+
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4016
|
+
const rgPatterns = [
|
|
4017
|
+
`(export\\s+)?(default\\s+)?(function|const|let|var|class|interface|type|enum)\\s+${escaped}\\b`,
|
|
4018
|
+
`(export\\s+)?(default\\s+)?\\b${escaped}\\s*[=:(]`
|
|
4019
|
+
];
|
|
4020
|
+
for (const pattern of rgPatterns) {
|
|
4021
|
+
try {
|
|
4022
|
+
const result = execFileSync("rg", [
|
|
4023
|
+
"-n",
|
|
4024
|
+
"--no-heading",
|
|
4025
|
+
"-e",
|
|
4026
|
+
pattern,
|
|
4027
|
+
"--glob",
|
|
4028
|
+
"*.{ts,tsx,js,jsx}",
|
|
4029
|
+
"-m",
|
|
4030
|
+
"5"
|
|
4031
|
+
], {
|
|
4032
|
+
cwd: workingDirectory,
|
|
4033
|
+
encoding: "utf-8",
|
|
4034
|
+
timeout: 5e3,
|
|
4035
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4036
|
+
}).trim();
|
|
4037
|
+
if (result) {
|
|
4038
|
+
const firstLine = result.split("\n")[0];
|
|
4039
|
+
const match = firstLine.match(/^(.+?):(\d+):(.*)/);
|
|
4040
|
+
if (match) {
|
|
4041
|
+
const col = match[3].indexOf(symbol);
|
|
4042
|
+
return {
|
|
4043
|
+
filePath: resolve8(workingDirectory, match[1]),
|
|
4044
|
+
line: parseInt(match[2]) - 1,
|
|
4045
|
+
char: col >= 0 ? col : 0
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
} catch {
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
const defPattern = new RegExp(
|
|
4053
|
+
`(export|function|const|let|var|class|interface|type|enum)\\s+.*\\b${escaped}\\b`
|
|
4054
|
+
);
|
|
4055
|
+
const SUPPORTED_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
4056
|
+
const IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
|
|
4057
|
+
async function search(dir, maxFiles) {
|
|
4058
|
+
if (maxFiles <= 0) return null;
|
|
4059
|
+
let remaining = maxFiles;
|
|
4060
|
+
try {
|
|
4061
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
4062
|
+
for (const entry of entries) {
|
|
4063
|
+
if (remaining <= 0) return null;
|
|
4064
|
+
const fullPath = resolve8(dir, entry.name);
|
|
4065
|
+
if (entry.isDirectory()) {
|
|
4066
|
+
if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
4067
|
+
const found = await search(fullPath, remaining);
|
|
4068
|
+
if (found) return found;
|
|
4069
|
+
remaining -= 10;
|
|
4070
|
+
} else if (entry.isFile()) {
|
|
4071
|
+
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
4072
|
+
if (!SUPPORTED_EXTS.has(ext)) continue;
|
|
4073
|
+
remaining--;
|
|
4074
|
+
const content = await readFile7(fullPath, "utf-8");
|
|
4075
|
+
const lines = content.split("\n");
|
|
4076
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4077
|
+
if (defPattern.test(lines[i])) {
|
|
4078
|
+
const col = lines[i].indexOf(symbol);
|
|
4079
|
+
if (col >= 0) {
|
|
4080
|
+
return { filePath: fullPath, line: i, char: col };
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
} catch {
|
|
4087
|
+
}
|
|
4088
|
+
return null;
|
|
4089
|
+
}
|
|
4090
|
+
return search(workingDirectory, 200);
|
|
4091
|
+
}
|
|
4092
|
+
var MAX_REF_FILES = 15;
|
|
4093
|
+
var MAX_LEVEL2_PARENTS = 8;
|
|
4094
|
+
var MAX_LEVEL2_SYMBOLS_PER_PARENT = 3;
|
|
4095
|
+
function createCodeGraphTool(options) {
|
|
4096
|
+
return tool7({
|
|
4097
|
+
description: `Inspect a symbol's type information and usage graph using the TypeScript language server.
|
|
4098
|
+
|
|
4099
|
+
Given a symbol name (function, component, class, type, etc.), this tool will:
|
|
4100
|
+
1. Find its definition and full type signature (parameters, return type)
|
|
4101
|
+
2. Find all references \u2014 what components/functions/files use this symbol
|
|
4102
|
+
3. Identify which pages/routes contain it in their component tree
|
|
4103
|
+
4. Show the file's symbol structure for surrounding context
|
|
4104
|
+
|
|
4105
|
+
Use this to understand:
|
|
4106
|
+
- Component hierarchies (what renders what, which pages are affected)
|
|
4107
|
+
- Type signatures and parameter/return types before making changes
|
|
4108
|
+
- How deeply a symbol is used across the codebase
|
|
4109
|
+
- What will break if you change something
|
|
4110
|
+
|
|
4111
|
+
Supports TypeScript, JavaScript, TSX, JSX files.
|
|
4112
|
+
Working directory: ${options.workingDirectory}`,
|
|
4113
|
+
inputSchema: codeGraphInputSchema,
|
|
4114
|
+
execute: async ({ symbol, filePath, depth }) => {
|
|
4115
|
+
const maxDepth = Math.min(depth ?? 2, 3);
|
|
4116
|
+
try {
|
|
4117
|
+
let defFilePath;
|
|
4118
|
+
let defLine = 0;
|
|
4119
|
+
let defChar = 0;
|
|
4120
|
+
let defSymbol = null;
|
|
4121
|
+
if (filePath) {
|
|
4122
|
+
const absPath = isAbsolute4(filePath) ? filePath : resolve8(options.workingDirectory, filePath);
|
|
4123
|
+
if (!existsSync10(absPath)) {
|
|
4124
|
+
return { success: false, error: `File not found: ${filePath}` };
|
|
4125
|
+
}
|
|
4126
|
+
if (!isSupported(absPath)) {
|
|
4127
|
+
return { success: false, error: `File type not supported. Supports: ${getSupportedExtensions().join(", ")}` };
|
|
4128
|
+
}
|
|
4129
|
+
await touchFile(absPath, true);
|
|
4130
|
+
const symbols = await getDocumentSymbols(absPath);
|
|
4131
|
+
defSymbol = findSymbolByName(symbols, symbol);
|
|
4132
|
+
if (defSymbol) {
|
|
4133
|
+
defFilePath = absPath;
|
|
4134
|
+
defLine = defSymbol.selectionRange.start.line;
|
|
4135
|
+
defChar = defSymbol.selectionRange.start.character;
|
|
4136
|
+
} else {
|
|
4137
|
+
const content = await readFile7(absPath, "utf-8");
|
|
4138
|
+
const lines2 = content.split("\n");
|
|
4139
|
+
const defPattern = new RegExp(
|
|
4140
|
+
`(export|function|const|let|var|class|interface|type|enum)\\s+.*\\b${symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
|
|
4141
|
+
);
|
|
4142
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
4143
|
+
if (defPattern.test(lines2[i])) {
|
|
4144
|
+
const col = lines2[i].indexOf(symbol);
|
|
4145
|
+
if (col !== -1) {
|
|
4146
|
+
defFilePath = absPath;
|
|
4147
|
+
defLine = i;
|
|
4148
|
+
defChar = col;
|
|
4149
|
+
break;
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
if (!defFilePath) {
|
|
4154
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
4155
|
+
const col = lines2[i].indexOf(symbol);
|
|
4156
|
+
if (col !== -1) {
|
|
4157
|
+
defFilePath = absPath;
|
|
4158
|
+
defLine = i;
|
|
4159
|
+
defChar = col;
|
|
4160
|
+
break;
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
} else {
|
|
4166
|
+
const found = await grepForSymbol(symbol, options.workingDirectory);
|
|
4167
|
+
if (found) {
|
|
4168
|
+
defFilePath = found.filePath;
|
|
4169
|
+
defLine = found.line;
|
|
4170
|
+
defChar = found.char;
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
if (!defFilePath) {
|
|
4174
|
+
return {
|
|
4175
|
+
success: false,
|
|
4176
|
+
error: `Could not find symbol "${symbol}" in the codebase. Try providing a filePath.`
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
await touchFile(defFilePath, true);
|
|
4180
|
+
const rawHover = await getHover(defFilePath, defLine, defChar);
|
|
4181
|
+
const typeInfo = rawHover ? cleanHoverText(rawHover) : null;
|
|
4182
|
+
const fileSymbols = await getDocumentSymbols(defFilePath);
|
|
4183
|
+
if (!defSymbol && fileSymbols.length > 0) {
|
|
4184
|
+
defSymbol = findSymbolByName(fileSymbols, symbol);
|
|
4185
|
+
}
|
|
4186
|
+
const references = await getReferences(defFilePath, defLine, defChar, false);
|
|
4187
|
+
const refsByFile = /* @__PURE__ */ new Map();
|
|
4188
|
+
for (const ref of references) {
|
|
4189
|
+
const refPath = fileURLToPath2(ref.uri);
|
|
4190
|
+
if (!refsByFile.has(refPath)) {
|
|
4191
|
+
refsByFile.set(refPath, []);
|
|
4192
|
+
}
|
|
4193
|
+
refsByFile.get(refPath).push(ref);
|
|
4194
|
+
}
|
|
4195
|
+
const refFileInfos = [];
|
|
4196
|
+
let processed = 0;
|
|
4197
|
+
for (const [refPath, locs] of refsByFile) {
|
|
4198
|
+
if (processed >= MAX_REF_FILES) break;
|
|
4199
|
+
if (refPath === defFilePath) continue;
|
|
4200
|
+
processed++;
|
|
4201
|
+
const relPath = relative7(options.workingDirectory, refPath);
|
|
4202
|
+
const pageFile = isPageFile(refPath);
|
|
4203
|
+
const routePath = pageFile ? extractRoutePath(refPath, options.workingDirectory) : void 0;
|
|
4204
|
+
await touchFile(refPath, false);
|
|
4205
|
+
const refFileSymbols = await getDocumentSymbols(refPath);
|
|
4206
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4207
|
+
for (const loc of locs) {
|
|
4208
|
+
const container = findContainingSymbol(
|
|
4209
|
+
refFileSymbols,
|
|
4210
|
+
loc.range.start.line,
|
|
4211
|
+
loc.range.start.character
|
|
4212
|
+
);
|
|
4213
|
+
if (container && !seen.has(container.name)) {
|
|
4214
|
+
let containerHover = null;
|
|
4215
|
+
try {
|
|
4216
|
+
const raw = await getHover(
|
|
4217
|
+
refPath,
|
|
4218
|
+
container.selectionRange.start.line,
|
|
4219
|
+
container.selectionRange.start.character
|
|
4220
|
+
);
|
|
4221
|
+
if (raw) containerHover = cleanHoverText(raw).split("\n")[0];
|
|
4222
|
+
} catch {
|
|
4223
|
+
}
|
|
4224
|
+
seen.set(container.name, {
|
|
4225
|
+
name: container.name,
|
|
4226
|
+
kind: symbolKindName(container.kind),
|
|
4227
|
+
line: container.selectionRange.start.line + 1,
|
|
4228
|
+
char: container.selectionRange.start.character,
|
|
4229
|
+
typeInfo: containerHover || void 0
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
refFileInfos.push({
|
|
4234
|
+
filePath: refPath,
|
|
4235
|
+
relativePath: relPath,
|
|
4236
|
+
isPage: pageFile,
|
|
4237
|
+
routePath,
|
|
4238
|
+
containingSymbols: Array.from(seen.values())
|
|
4239
|
+
});
|
|
4240
|
+
}
|
|
4241
|
+
const level2Refs = [];
|
|
4242
|
+
if (maxDepth >= 2) {
|
|
4243
|
+
for (const refFile of refFileInfos.slice(0, MAX_LEVEL2_PARENTS)) {
|
|
4244
|
+
for (const sym of refFile.containingSymbols.slice(0, MAX_LEVEL2_SYMBOLS_PER_PARENT)) {
|
|
4245
|
+
try {
|
|
4246
|
+
const symLineIdx = sym.line - 1;
|
|
4247
|
+
const symChar = sym.char;
|
|
4248
|
+
const l2Locations = await getReferences(
|
|
4249
|
+
refFile.filePath,
|
|
4250
|
+
symLineIdx,
|
|
4251
|
+
symChar,
|
|
4252
|
+
false
|
|
4253
|
+
);
|
|
4254
|
+
const l2Nodes = [];
|
|
4255
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
4256
|
+
for (const loc of l2Locations.slice(0, 10)) {
|
|
4257
|
+
const l2Path = fileURLToPath2(loc.uri);
|
|
4258
|
+
if (l2Path === refFile.filePath || l2Path === defFilePath) continue;
|
|
4259
|
+
if (seenPaths.has(l2Path)) continue;
|
|
4260
|
+
seenPaths.add(l2Path);
|
|
4261
|
+
const l2Rel = relative7(options.workingDirectory, l2Path);
|
|
4262
|
+
const l2Page = isPageFile(l2Path);
|
|
4263
|
+
const l2Route = l2Page ? extractRoutePath(l2Path, options.workingDirectory) : void 0;
|
|
4264
|
+
let containerName;
|
|
4265
|
+
try {
|
|
4266
|
+
await touchFile(l2Path, false);
|
|
4267
|
+
const l2Symbols = await getDocumentSymbols(l2Path);
|
|
4268
|
+
const container = findContainingSymbol(l2Symbols, loc.range.start.line, loc.range.start.character);
|
|
4269
|
+
if (container) containerName = container.name;
|
|
4270
|
+
} catch {
|
|
4271
|
+
}
|
|
4272
|
+
l2Nodes.push({
|
|
4273
|
+
relativePath: l2Rel,
|
|
4274
|
+
isPage: l2Page,
|
|
4275
|
+
routePath: l2Route,
|
|
4276
|
+
containingSymbol: containerName
|
|
4277
|
+
});
|
|
4278
|
+
}
|
|
4279
|
+
if (l2Nodes.length > 0) {
|
|
4280
|
+
level2Refs.push({
|
|
4281
|
+
parentSymbol: sym.name,
|
|
4282
|
+
parentFile: refFile.relativePath,
|
|
4283
|
+
refs: l2Nodes
|
|
4284
|
+
});
|
|
4285
|
+
}
|
|
4286
|
+
} catch {
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
const relDefPath = relative7(options.workingDirectory, defFilePath);
|
|
4292
|
+
const lines = [];
|
|
4293
|
+
lines.push(`=== ${symbol} ===`);
|
|
4294
|
+
lines.push(`File: ${relDefPath}:${defLine + 1}`);
|
|
4295
|
+
if (defSymbol) lines.push(`Kind: ${symbolKindName(defSymbol.kind)}`);
|
|
4296
|
+
if (typeInfo) lines.push(`Type: ${typeInfo}`);
|
|
4297
|
+
const externalRefCount = references.filter((r) => fileURLToPath2(r.uri) !== defFilePath).length;
|
|
4298
|
+
const externalFileCount = refsByFile.size - (refsByFile.has(defFilePath) ? 1 : 0);
|
|
4299
|
+
if (refFileInfos.length > 0) {
|
|
4300
|
+
lines.push("");
|
|
4301
|
+
lines.push(`=== Referenced by (${externalRefCount} usages across ${externalFileCount} files) ===`);
|
|
4302
|
+
const pages = refFileInfos.filter((r) => r.isPage);
|
|
4303
|
+
const nonPages = refFileInfos.filter((r) => !r.isPage);
|
|
4304
|
+
if (pages.length > 0) {
|
|
4305
|
+
lines.push("");
|
|
4306
|
+
lines.push("Pages/Routes:");
|
|
4307
|
+
for (const page of pages) {
|
|
4308
|
+
lines.push(` ${page.relativePath}${page.routePath ? ` \u2192 ${page.routePath}` : ""}`);
|
|
4309
|
+
for (const s of page.containingSymbols) {
|
|
4310
|
+
lines.push(` \u2514\u2500\u2500 ${s.name} (${s.kind}:${s.line})${s.typeInfo ? ` \u2014 ${s.typeInfo}` : ""}`);
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
if (nonPages.length > 0) {
|
|
4315
|
+
lines.push("");
|
|
4316
|
+
lines.push("Components/Functions:");
|
|
4317
|
+
for (const ref of nonPages) {
|
|
4318
|
+
lines.push(` ${ref.relativePath}`);
|
|
4319
|
+
for (const s of ref.containingSymbols) {
|
|
4320
|
+
const typePart = s.typeInfo && s.typeInfo.length < 120 ? ` \u2014 ${s.typeInfo}` : "";
|
|
4321
|
+
lines.push(` \u2514\u2500\u2500 ${s.name} (${s.kind}:${s.line})${typePart}`);
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
} else {
|
|
4326
|
+
lines.push("");
|
|
4327
|
+
lines.push("No external references found (symbol may be unused or only used within the same file).");
|
|
4328
|
+
}
|
|
4329
|
+
if (level2Refs.length > 0) {
|
|
4330
|
+
lines.push("");
|
|
4331
|
+
lines.push("=== Extended tree (level 2) ===");
|
|
4332
|
+
for (const l2 of level2Refs) {
|
|
4333
|
+
lines.push("");
|
|
4334
|
+
lines.push(`${l2.parentSymbol} (${l2.parentFile}) is used by:`);
|
|
4335
|
+
for (const ref of l2.refs) {
|
|
4336
|
+
const tag = ref.isPage ? " [PAGE]" : "";
|
|
4337
|
+
const route = ref.routePath ? ` \u2192 ${ref.routePath}` : "";
|
|
4338
|
+
const container = ref.containingSymbol ? ` in ${ref.containingSymbol}` : "";
|
|
4339
|
+
lines.push(` \u2514\u2500\u2500 ${ref.relativePath}${tag}${route}${container}`);
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
if (fileSymbols.length > 0) {
|
|
4344
|
+
lines.push("");
|
|
4345
|
+
lines.push(`=== File structure (${basename3(defFilePath)}) ===`);
|
|
4346
|
+
for (const sym of fileSymbols) {
|
|
4347
|
+
const marker = sym.name === symbol ? " \u2190 target" : "";
|
|
4348
|
+
lines.push(` ${sym.name} (${symbolKindName(sym.kind)}:${sym.selectionRange.start.line + 1})${marker}`);
|
|
4349
|
+
if (sym.children) {
|
|
4350
|
+
for (const child of sym.children.slice(0, 10)) {
|
|
4351
|
+
lines.push(` \u2514\u2500\u2500 ${child.name} (${symbolKindName(child.kind)}:${child.selectionRange.start.line + 1})`);
|
|
4352
|
+
}
|
|
4353
|
+
if (sym.children.length > 10) {
|
|
4354
|
+
lines.push(` ... and ${sym.children.length - 10} more`);
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
const formattedResult = lines.join("\n");
|
|
4360
|
+
return {
|
|
4361
|
+
success: true,
|
|
4362
|
+
symbol,
|
|
4363
|
+
filePath: relDefPath,
|
|
4364
|
+
line: defLine + 1,
|
|
4365
|
+
kind: defSymbol ? symbolKindName(defSymbol.kind) : void 0,
|
|
4366
|
+
typeInfo: typeInfo || void 0,
|
|
4367
|
+
referenceCount: externalRefCount,
|
|
4368
|
+
referenceFiles: externalFileCount,
|
|
4369
|
+
pages: refFileInfos.filter((r) => r.isPage).map((r) => ({ path: r.relativePath, route: r.routePath })),
|
|
4370
|
+
formattedResult
|
|
4371
|
+
};
|
|
4372
|
+
} catch (error) {
|
|
4373
|
+
return {
|
|
4374
|
+
success: false,
|
|
4375
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4376
|
+
};
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
// src/agent/subagents/search.ts
|
|
3815
4383
|
var execAsync4 = promisify4(exec4);
|
|
3816
4384
|
var MAX_OUTPUT_CHARS4 = 1e4;
|
|
3817
4385
|
var MAX_FILE_SIZE3 = 1 * 1024 * 1024;
|
|
@@ -3841,17 +4409,20 @@ ${contextBlock}
|
|
|
3841
4409
|
- **glob**: Find files matching a name pattern. Best for file discovery.
|
|
3842
4410
|
- **read_file**: Read contents of a specific file. Use to examine code found in searches.
|
|
3843
4411
|
- **list_dir**: List directory contents. Use to understand project structure.
|
|
4412
|
+
- **code_graph**: Inspect a symbol's type hierarchy, references, and usage graph via the TypeScript language server. Returns type signatures, all files that reference the symbol, and which pages/routes contain it. Best for understanding component/function relationships and impact analysis.
|
|
3844
4413
|
|
|
3845
4414
|
## Search Strategy
|
|
3846
4415
|
|
|
3847
4416
|
1. **Start with semantic_search** if available - it finds code by meaning, which is the fastest way to explore
|
|
3848
4417
|
2. **Use grep** for exact symbol/string matches (function names, class names, imports)
|
|
3849
|
-
3. **Use
|
|
3850
|
-
4. **
|
|
3851
|
-
5. **
|
|
4418
|
+
3. **Use code_graph** when you need to understand a symbol's type signature, what depends on it, or which pages use it. It's much more precise than grep for understanding relationships.
|
|
4419
|
+
4. **Use glob** for file discovery by name patterns
|
|
4420
|
+
5. **Read key files** to get actual code content and understand context
|
|
4421
|
+
6. **Run searches in PARALLEL** - make multiple tool calls at once to cover different angles simultaneously. This is critical for speed.
|
|
3852
4422
|
|
|
3853
4423
|
### Tool Selection Guide
|
|
3854
4424
|
- Know the exact name? Use **grep** (e.g. \`getUserById\`, \`class AuthService\`)
|
|
4425
|
+
- Need type info, references, or impact analysis? Use **code_graph** (e.g. \`code_graph({ symbol: "UserCard" })\`)
|
|
3855
4426
|
- Exploring a concept? Use **semantic_search** (e.g. "how does authentication work")
|
|
3856
4427
|
- Looking for files? Use **glob** (e.g. \`**/*.config.ts\`, \`**/auth/**\`)
|
|
3857
4428
|
- Need file content? Use **read_file** with optional line ranges for large files
|
|
@@ -3888,17 +4459,17 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
3888
4459
|
async getToolsAsync(options) {
|
|
3889
4460
|
const workingDirectory = options.workingDirectory;
|
|
3890
4461
|
const tools = {
|
|
3891
|
-
grep:
|
|
4462
|
+
grep: tool9({
|
|
3892
4463
|
description: "Search for patterns in files using ripgrep. Returns matching lines with file paths and line numbers.",
|
|
3893
|
-
inputSchema:
|
|
3894
|
-
pattern:
|
|
3895
|
-
path:
|
|
3896
|
-
fileType:
|
|
3897
|
-
maxResults:
|
|
4464
|
+
inputSchema: z10.object({
|
|
4465
|
+
pattern: z10.string().describe("The regex pattern to search for"),
|
|
4466
|
+
path: z10.string().optional().describe("Subdirectory or file to search in (relative to working directory)"),
|
|
4467
|
+
fileType: z10.string().optional().describe('File type to filter (e.g., "ts", "js", "py")'),
|
|
4468
|
+
maxResults: z10.number().optional().default(50).describe("Maximum number of results to return")
|
|
3898
4469
|
}),
|
|
3899
4470
|
execute: async ({ pattern, path, fileType, maxResults }) => {
|
|
3900
4471
|
try {
|
|
3901
|
-
const searchPath = path ?
|
|
4472
|
+
const searchPath = path ? resolve9(workingDirectory, path) : workingDirectory;
|
|
3902
4473
|
let args = ["rg", "--line-number", "--no-heading"];
|
|
3903
4474
|
if (fileType) {
|
|
3904
4475
|
args.push("--type", fileType);
|
|
@@ -3935,11 +4506,11 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
3935
4506
|
}
|
|
3936
4507
|
}
|
|
3937
4508
|
}),
|
|
3938
|
-
glob:
|
|
4509
|
+
glob: tool9({
|
|
3939
4510
|
description: "Find files matching a glob pattern. Returns list of matching file paths.",
|
|
3940
|
-
inputSchema:
|
|
3941
|
-
pattern:
|
|
3942
|
-
maxResults:
|
|
4511
|
+
inputSchema: z10.object({
|
|
4512
|
+
pattern: z10.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json")'),
|
|
4513
|
+
maxResults: z10.number().optional().default(100).describe("Maximum number of files to return")
|
|
3943
4514
|
}),
|
|
3944
4515
|
execute: async ({ pattern, maxResults }) => {
|
|
3945
4516
|
try {
|
|
@@ -3966,17 +4537,17 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
3966
4537
|
}
|
|
3967
4538
|
}
|
|
3968
4539
|
}),
|
|
3969
|
-
read_file:
|
|
4540
|
+
read_file: tool9({
|
|
3970
4541
|
description: "Read the contents of a file. Use this to examine specific files found in search.",
|
|
3971
|
-
inputSchema:
|
|
3972
|
-
path:
|
|
3973
|
-
startLine:
|
|
3974
|
-
endLine:
|
|
4542
|
+
inputSchema: z10.object({
|
|
4543
|
+
path: z10.string().describe("Path to the file (relative to working directory or absolute)"),
|
|
4544
|
+
startLine: z10.number().optional().describe("Start reading from this line (1-indexed)"),
|
|
4545
|
+
endLine: z10.number().optional().describe("Stop reading at this line (1-indexed, inclusive)")
|
|
3975
4546
|
}),
|
|
3976
4547
|
execute: async ({ path, startLine, endLine }) => {
|
|
3977
4548
|
try {
|
|
3978
|
-
const absolutePath =
|
|
3979
|
-
if (!
|
|
4549
|
+
const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
|
|
4550
|
+
if (!existsSync12(absolutePath)) {
|
|
3980
4551
|
return {
|
|
3981
4552
|
success: false,
|
|
3982
4553
|
error: `File not found: ${path}`
|
|
@@ -3989,7 +4560,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
3989
4560
|
error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
|
|
3990
4561
|
};
|
|
3991
4562
|
}
|
|
3992
|
-
let content = await
|
|
4563
|
+
let content = await readFile8(absolutePath, "utf-8");
|
|
3993
4564
|
if (startLine !== void 0 || endLine !== void 0) {
|
|
3994
4565
|
const lines = content.split("\n");
|
|
3995
4566
|
const start = (startLine ?? 1) - 1;
|
|
@@ -3998,7 +4569,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
3998
4569
|
}
|
|
3999
4570
|
return {
|
|
4000
4571
|
success: true,
|
|
4001
|
-
path:
|
|
4572
|
+
path: relative8(workingDirectory, absolutePath),
|
|
4002
4573
|
content: truncateOutput(content, MAX_OUTPUT_CHARS4),
|
|
4003
4574
|
lineCount: content.split("\n").length
|
|
4004
4575
|
};
|
|
@@ -4010,17 +4581,17 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
4010
4581
|
}
|
|
4011
4582
|
}
|
|
4012
4583
|
}),
|
|
4013
|
-
list_dir:
|
|
4584
|
+
list_dir: tool9({
|
|
4014
4585
|
description: "List contents of a directory. Shows files and subdirectories.",
|
|
4015
|
-
inputSchema:
|
|
4016
|
-
path:
|
|
4017
|
-
recursive:
|
|
4018
|
-
maxDepth:
|
|
4586
|
+
inputSchema: z10.object({
|
|
4587
|
+
path: z10.string().optional().default(".").describe("Directory path (relative to working directory)"),
|
|
4588
|
+
recursive: z10.boolean().optional().default(false).describe("List recursively (be careful with large directories)"),
|
|
4589
|
+
maxDepth: z10.number().optional().default(2).describe("Maximum depth for recursive listing")
|
|
4019
4590
|
}),
|
|
4020
4591
|
execute: async ({ path, recursive, maxDepth }) => {
|
|
4021
4592
|
try {
|
|
4022
|
-
const absolutePath =
|
|
4023
|
-
if (!
|
|
4593
|
+
const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
|
|
4594
|
+
if (!existsSync12(absolutePath)) {
|
|
4024
4595
|
return {
|
|
4025
4596
|
success: false,
|
|
4026
4597
|
error: `Directory not found: ${path}`
|
|
@@ -4044,20 +4615,20 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
4044
4615
|
const files = stdout.trim().split("\n").filter(Boolean);
|
|
4045
4616
|
return {
|
|
4046
4617
|
success: true,
|
|
4047
|
-
path:
|
|
4618
|
+
path: relative8(workingDirectory, absolutePath) || ".",
|
|
4048
4619
|
files,
|
|
4049
4620
|
count: files.length,
|
|
4050
4621
|
recursive: true
|
|
4051
4622
|
};
|
|
4052
4623
|
} else {
|
|
4053
|
-
const entries = await
|
|
4624
|
+
const entries = await readdir4(absolutePath, { withFileTypes: true });
|
|
4054
4625
|
const items = entries.slice(0, 200).map((e) => ({
|
|
4055
4626
|
name: e.name,
|
|
4056
4627
|
type: e.isDirectory() ? "directory" : "file"
|
|
4057
4628
|
}));
|
|
4058
4629
|
return {
|
|
4059
4630
|
success: true,
|
|
4060
|
-
path:
|
|
4631
|
+
path: relative8(workingDirectory, absolutePath) || ".",
|
|
4061
4632
|
items,
|
|
4062
4633
|
count: items.length
|
|
4063
4634
|
};
|
|
@@ -4069,6 +4640,9 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
4069
4640
|
};
|
|
4070
4641
|
}
|
|
4071
4642
|
}
|
|
4643
|
+
}),
|
|
4644
|
+
code_graph: createCodeGraphTool({
|
|
4645
|
+
workingDirectory
|
|
4072
4646
|
})
|
|
4073
4647
|
};
|
|
4074
4648
|
try {
|
|
@@ -4162,6 +4736,26 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
4162
4736
|
context: m.symbolName || m.language
|
|
4163
4737
|
});
|
|
4164
4738
|
}
|
|
4739
|
+
} else if (step.toolName === "code_graph" && output.success) {
|
|
4740
|
+
matchCount += output.referenceCount || 0;
|
|
4741
|
+
if (output.filePath) {
|
|
4742
|
+
findings.push({
|
|
4743
|
+
type: "file",
|
|
4744
|
+
path: output.filePath,
|
|
4745
|
+
lineNumber: output.line,
|
|
4746
|
+
content: output.typeInfo ? truncateOutput(output.typeInfo, 300) : void 0,
|
|
4747
|
+
relevance: "high",
|
|
4748
|
+
context: `${output.kind || "symbol"}${output.referenceCount ? `, ${output.referenceCount} refs` : ""}`
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
for (const page of (output.pages || []).slice(0, 10)) {
|
|
4752
|
+
findings.push({
|
|
4753
|
+
type: "file",
|
|
4754
|
+
path: page.path,
|
|
4755
|
+
relevance: "high",
|
|
4756
|
+
context: page.route ? `route: ${page.route}` : "page"
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4165
4759
|
}
|
|
4166
4760
|
}
|
|
4167
4761
|
}
|
|
@@ -4183,7 +4777,7 @@ function createSearchSubagent(model) {
|
|
|
4183
4777
|
// src/tools/search.ts
|
|
4184
4778
|
var MAX_RESULT_CHARS = 1e4;
|
|
4185
4779
|
function createSearchTool(options) {
|
|
4186
|
-
return
|
|
4780
|
+
return tool10({
|
|
4187
4781
|
description: `Delegate an explore task to the explore_agent tool. Use this when you need to:
|
|
4188
4782
|
- Find files or code matching a pattern
|
|
4189
4783
|
- Explore the codebase structure
|
|
@@ -4193,11 +4787,12 @@ function createSearchTool(options) {
|
|
|
4193
4787
|
The Explore agent will explore the codebase and return a summary of findings.
|
|
4194
4788
|
This is more thorough than a simple grep because it can follow references and understand context.
|
|
4195
4789
|
It also has access to semantic search to find code by meaning, not just text.
|
|
4790
|
+
It can also use code_graph to inspect a symbol's type hierarchy, references, and which pages/routes use it.
|
|
4196
4791
|
|
|
4197
4792
|
CRITICAL: The explore agent has ZERO context. It cannot see the conversation, the user's message, devtools data, or any prior context. You MUST pass ALL relevant context via the "context" parameter. If the user selected a component (component name, file path, HTML, component stack) or there is a <devtools-context> block, you MUST copy that information into the "context" field verbatim. Without it the explore agent is searching blind.`,
|
|
4198
|
-
inputSchema:
|
|
4199
|
-
query:
|
|
4200
|
-
context:
|
|
4793
|
+
inputSchema: z11.object({
|
|
4794
|
+
query: z11.string().describe("What to search for. Be specific about what you're looking for."),
|
|
4795
|
+
context: z11.string().describe("ALL context the explore agent needs. It has ZERO context on its own - no conversation history, no devtools data, nothing. You MUST include: any selected component info (name, file path, HTML, component stack), any <devtools-context> block (page URL, path, viewport), and any other relevant details from the user message. The explore agent literally only sees the query and this context field.")
|
|
4201
4796
|
}),
|
|
4202
4797
|
execute: async ({ query, context }, toolOptions) => {
|
|
4203
4798
|
const toolCallId = toolOptions.toolCallId || `explore_agent_${Date.now()}`;
|
|
@@ -4341,6 +4936,9 @@ async function createTools(options) {
|
|
|
4341
4936
|
sessionId: options.sessionId,
|
|
4342
4937
|
workingDirectory: options.workingDirectory,
|
|
4343
4938
|
onProgress: options.onSearchProgress
|
|
4939
|
+
}),
|
|
4940
|
+
code_graph: createCodeGraphTool({
|
|
4941
|
+
workingDirectory: options.workingDirectory
|
|
4344
4942
|
})
|
|
4345
4943
|
};
|
|
4346
4944
|
if (options.enableSemanticSearch !== false) {
|
|
@@ -4432,6 +5030,7 @@ You have access to powerful tools for:
|
|
|
4432
5030
|
- **todo**: Manage your task list to track progress on complex operations
|
|
4433
5031
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
4434
5032
|
- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
|
|
5033
|
+
- **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
|
|
4435
5034
|
|
|
4436
5035
|
|
|
4437
5036
|
IMPORTANT: If you have zero context of where you are working, always explore it first to understand the structure before doing things for the user.
|
|
@@ -4521,6 +5120,33 @@ linter({ paths: ["src/"] }) // Check all files in a directory
|
|
|
4521
5120
|
\`\`\`
|
|
4522
5121
|
Use this proactively after making code changes to catch errors early.
|
|
4523
5122
|
|
|
5123
|
+
### Code Graph Tool
|
|
5124
|
+
The code_graph tool uses the TypeScript language server to inspect a symbol's type hierarchy and usage graph:
|
|
5125
|
+
\`\`\`
|
|
5126
|
+
code_graph({ symbol: "UserCard" }) // Search workspace for symbol
|
|
5127
|
+
code_graph({ symbol: "UserCard", filePath: "src/components.tsx" }) // Look up in a specific file
|
|
5128
|
+
code_graph({ symbol: "formatUser", filePath: "utils.ts", depth: 2 }) // Traverse 2 levels up the reference tree
|
|
5129
|
+
\`\`\`
|
|
5130
|
+
|
|
5131
|
+
**What it returns:**
|
|
5132
|
+
- The symbol's full type signature (parameters, return type)
|
|
5133
|
+
- All files/functions/components that reference it (grouped into pages vs components)
|
|
5134
|
+
- Which Next.js pages/routes contain it in their component tree
|
|
5135
|
+
- Level-2 transitive usages (who uses the things that use this symbol)
|
|
5136
|
+
- The file's symbol structure for surrounding context
|
|
5137
|
+
|
|
5138
|
+
**When to use code_graph:**
|
|
5139
|
+
- **To locate a component/function by name** when you don't have the file path \u2014 e.g. a user mentions a component from devtools but the path is missing or mangled. Just pass the symbol name and it will find the definition.
|
|
5140
|
+
- **Before making changes** to a function/component \u2014 understand what depends on it and what will break
|
|
5141
|
+
- **To understand component hierarchies** \u2014 what renders what, which pages are affected across the *entire* codebase (not just the current page)
|
|
5142
|
+
- **To get type signatures** (props, params, return types) without reading entire files
|
|
5143
|
+
- **After a devtools selection** when the task involves refactoring, changing props, or anything that could impact other consumers
|
|
5144
|
+
|
|
5145
|
+
**When NOT to use code_graph:**
|
|
5146
|
+
- For exploratory "how does X work?" questions \u2014 use \`explore_agent\` instead
|
|
5147
|
+
- For exact string searches \u2014 use grep/rg directly
|
|
5148
|
+
- For non-TypeScript/JavaScript files \u2014 code_graph only supports TS/JS/TSX/JSX
|
|
5149
|
+
|
|
4524
5150
|
### Searching and Exploration
|
|
4525
5151
|
|
|
4526
5152
|
**Choose the right search approach:**
|
|
@@ -4528,7 +5154,8 @@ Use this proactively after making code changes to catch errors early.
|
|
|
4528
5154
|
0. **Use paths to your advantage \u2014 skip searching if you already have what you need.**
|
|
4529
5155
|
- If the user selected a component via devtools and you can see the component name, file path, and/or line number, you ALREADY know where the code is. Just use \`read_file\` to read that file directly \u2014 do NOT call \`explore_agent\` to "find" something you already have the location of.
|
|
4530
5156
|
- If you received a **page path** (e.g. \`/dashboard\`, \`/settings/profile\`), map it to the corresponding file in the project structure. In Next.js this means \`app/dashboard/page.tsx\`, \`app/settings/profile/page.tsx\`, etc. In other frameworks, check the routing convention (e.g. \`pages/\`, \`src/routes/\`). Use \`read_file\` on the mapped path directly.
|
|
4531
|
-
- If the file path doesn't exist
|
|
5157
|
+
- **If the file path is missing, truncated, or doesn't exist** (common with devtools \u2014 webpack paths can be mangled), use \`code_graph({ symbol: "ComponentName" })\` to locate the component. This searches the workspace for the symbol definition AND returns its type info, references, and page locations in one call \u2014 much better than raw grep for components.
|
|
5158
|
+
- **After reading a devtools-selected component**, if the task involves changes that could affect other consumers (refactoring, changing props, renaming), use \`code_graph\` to see ALL files and pages that depend on it \u2014 the devtools component stack only shows the current page's hierarchy, not the full picture.
|
|
4532
5159
|
- Read up and down component trees when you have the file path or page path to find what you're looking for.
|
|
4533
5160
|
1. **Use the \`explore_agent\` tool (Explore agent)** for:
|
|
4534
5161
|
- Semantic/exploratory questions: "How does authentication work?", "Where is user data processed?"
|
|
@@ -4546,7 +5173,14 @@ Use this proactively after making code changes to catch errors early.
|
|
|
4546
5173
|
- If you skip the \`context\` field, the explore agent is searching completely blind and will waste time guessing.
|
|
4547
5174
|
- NEVER call \`explore_agent\` with only a \`query\` and no \`context\` when the user's message contains devtools or component information.
|
|
4548
5175
|
|
|
4549
|
-
2. **Use
|
|
5176
|
+
2. **Use the \`code_graph\` tool** for:
|
|
5177
|
+
- Understanding what depends on a specific symbol before changing it
|
|
5178
|
+
- Tracing component/function usage up to page-level routes
|
|
5179
|
+
- Getting type signatures (params, return types) without reading full files
|
|
5180
|
+
- Finding exact components usages in the codebase
|
|
5181
|
+
- Answering "what will break if I change this?" or "which pages use this component?"
|
|
5182
|
+
|
|
5183
|
+
3. **Use direct commands (grep/rg, find)** for:
|
|
4550
5184
|
- Exact string matches: \`rg "functionName"\`, \`rg "class MyClass"\`
|
|
4551
5185
|
- Finding files by name: \`find . -name "*.config.ts"\`
|
|
4552
5186
|
- Simple pattern matching when you know exactly what you're looking for
|
|
@@ -4554,7 +5188,11 @@ Use this proactively after making code changes to catch errors early.
|
|
|
4554
5188
|
|
|
4555
5189
|
**Examples:**
|
|
4556
5190
|
- User selected \`<LandingButton>\` at \`src/components/LandingButton.tsx:12\` \u2192 Just \`read_file("src/components/LandingButton.tsx")\`. Do NOT call explore_agent.
|
|
5191
|
+
- User selected \`<PricingCard>\` but no file path in the component stack \u2192 Use \`code_graph({ symbol: "PricingCard" })\` to find its definition, type info, and all usages at once.
|
|
5192
|
+
- User selected \`<UserCard>\` and says "refactor the props" \u2192 First \`read_file\` the component, then \`code_graph({ symbol: "UserCard" })\` to see every file/page that depends on it before changing the interface.
|
|
4557
5193
|
- "Where is the API authentication handled?" (no file path given) \u2192 Use \`explore_agent\` tool
|
|
5194
|
+
- "What pages use the UserCard component?" \u2192 Use \`code_graph({ symbol: "UserCard" })\`
|
|
5195
|
+
- "What's the type signature of formatUser?" \u2192 Use \`code_graph({ symbol: "formatUser", filePath: "utils.ts" })\`
|
|
4558
5196
|
- "Find all usages of getUserById" \u2192 Use \`rg "getUserById"\`
|
|
4559
5197
|
- "How does the payment flow work?" \u2192 Use \`explore_agent\` tool
|
|
4560
5198
|
- "Find files named config" \u2192 Use \`find . -name "*config*"\`
|
|
@@ -5126,9 +5764,9 @@ ${prompt}` });
|
|
|
5126
5764
|
wrappedTools[name] = originalTool;
|
|
5127
5765
|
continue;
|
|
5128
5766
|
}
|
|
5129
|
-
wrappedTools[name] =
|
|
5767
|
+
wrappedTools[name] = tool11({
|
|
5130
5768
|
description: originalTool.description || "",
|
|
5131
|
-
inputSchema: originalTool.inputSchema ||
|
|
5769
|
+
inputSchema: originalTool.inputSchema || z12.object({}),
|
|
5132
5770
|
execute: async (input, toolOptions) => {
|
|
5133
5771
|
const toolCallId = toolOptions.toolCallId || nanoid3();
|
|
5134
5772
|
const execution = toolExecutionQueries.create({
|
|
@@ -5142,8 +5780,8 @@ ${prompt}` });
|
|
|
5142
5780
|
this.pendingApprovals.set(toolCallId, await execution);
|
|
5143
5781
|
options.onApprovalRequired?.(await execution);
|
|
5144
5782
|
await sessionQueries.updateStatus(this.session.id, "waiting");
|
|
5145
|
-
const approved = await new Promise((
|
|
5146
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
5783
|
+
const approved = await new Promise((resolve11) => {
|
|
5784
|
+
approvalResolvers.set(toolCallId, { resolve: resolve11, sessionId: this.session.id });
|
|
5147
5785
|
});
|
|
5148
5786
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
5149
5787
|
approvalResolvers.delete(toolCallId);
|
|
@@ -5243,20 +5881,20 @@ import { Hono as Hono5 } from "hono";
|
|
|
5243
5881
|
import { serve } from "@hono/node-server";
|
|
5244
5882
|
import { cors } from "hono/cors";
|
|
5245
5883
|
import { logger } from "hono/logger";
|
|
5246
|
-
import { existsSync as
|
|
5247
|
-
import { resolve as
|
|
5884
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5885
|
+
import { resolve as resolve10, dirname as dirname7, join as join8 } from "path";
|
|
5248
5886
|
import { spawn as spawn2 } from "child_process";
|
|
5249
5887
|
import { createServer as createNetServer } from "net";
|
|
5250
|
-
import { fileURLToPath as
|
|
5888
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5251
5889
|
|
|
5252
5890
|
// src/server/routes/sessions.ts
|
|
5253
5891
|
init_db();
|
|
5254
5892
|
import { Hono } from "hono";
|
|
5255
5893
|
import { zValidator } from "@hono/zod-validator";
|
|
5256
|
-
import { z as
|
|
5257
|
-
import { existsSync as
|
|
5258
|
-
import { readdir as
|
|
5259
|
-
import { join as join5, basename as
|
|
5894
|
+
import { z as z13 } from "zod";
|
|
5895
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
|
|
5896
|
+
import { readdir as readdir5 } from "fs/promises";
|
|
5897
|
+
import { join as join5, basename as basename4, extname as extname6, relative as relative9 } from "path";
|
|
5260
5898
|
import { nanoid as nanoid4 } from "nanoid";
|
|
5261
5899
|
init_config();
|
|
5262
5900
|
|
|
@@ -5289,18 +5927,18 @@ function cleanupPendingInputs() {
|
|
|
5289
5927
|
}
|
|
5290
5928
|
}
|
|
5291
5929
|
}
|
|
5292
|
-
var createSessionSchema =
|
|
5293
|
-
name:
|
|
5294
|
-
workingDirectory:
|
|
5295
|
-
model:
|
|
5296
|
-
toolApprovals:
|
|
5930
|
+
var createSessionSchema = z13.object({
|
|
5931
|
+
name: z13.string().optional(),
|
|
5932
|
+
workingDirectory: z13.string().optional(),
|
|
5933
|
+
model: z13.string().optional(),
|
|
5934
|
+
toolApprovals: z13.record(z13.string(), z13.boolean()).optional()
|
|
5297
5935
|
});
|
|
5298
|
-
var paginationQuerySchema =
|
|
5299
|
-
limit:
|
|
5300
|
-
offset:
|
|
5936
|
+
var paginationQuerySchema = z13.object({
|
|
5937
|
+
limit: z13.string().optional(),
|
|
5938
|
+
offset: z13.string().optional()
|
|
5301
5939
|
});
|
|
5302
|
-
var messagesQuerySchema =
|
|
5303
|
-
limit:
|
|
5940
|
+
var messagesQuerySchema = z13.object({
|
|
5941
|
+
limit: z13.string().optional()
|
|
5304
5942
|
});
|
|
5305
5943
|
sessions.get(
|
|
5306
5944
|
"/",
|
|
@@ -5439,10 +6077,10 @@ sessions.get("/:id/tools", async (c) => {
|
|
|
5439
6077
|
count: executions.length
|
|
5440
6078
|
});
|
|
5441
6079
|
});
|
|
5442
|
-
var updateSessionSchema =
|
|
5443
|
-
model:
|
|
5444
|
-
name:
|
|
5445
|
-
toolApprovals:
|
|
6080
|
+
var updateSessionSchema = z13.object({
|
|
6081
|
+
model: z13.string().optional(),
|
|
6082
|
+
name: z13.string().optional(),
|
|
6083
|
+
toolApprovals: z13.record(z13.string(), z13.boolean()).optional()
|
|
5446
6084
|
});
|
|
5447
6085
|
sessions.patch(
|
|
5448
6086
|
"/:id",
|
|
@@ -5512,8 +6150,8 @@ sessions.post("/:id/clear", async (c) => {
|
|
|
5512
6150
|
await agent.clearContext();
|
|
5513
6151
|
return c.json({ success: true, sessionId: id });
|
|
5514
6152
|
});
|
|
5515
|
-
var pendingInputSchema =
|
|
5516
|
-
text:
|
|
6153
|
+
var pendingInputSchema = z13.object({
|
|
6154
|
+
text: z13.string()
|
|
5517
6155
|
});
|
|
5518
6156
|
sessions.post(
|
|
5519
6157
|
"/:id/pending-input",
|
|
@@ -5544,13 +6182,13 @@ sessions.get("/:id/pending-input", async (c) => {
|
|
|
5544
6182
|
createdAt: pending.createdAt.toISOString()
|
|
5545
6183
|
});
|
|
5546
6184
|
});
|
|
5547
|
-
var devtoolsContextSchema =
|
|
5548
|
-
url:
|
|
5549
|
-
path:
|
|
5550
|
-
pageName:
|
|
5551
|
-
screenWidth:
|
|
5552
|
-
screenHeight:
|
|
5553
|
-
devicePixelRatio:
|
|
6185
|
+
var devtoolsContextSchema = z13.object({
|
|
6186
|
+
url: z13.string(),
|
|
6187
|
+
path: z13.string(),
|
|
6188
|
+
pageName: z13.string().optional(),
|
|
6189
|
+
screenWidth: z13.number().optional(),
|
|
6190
|
+
screenHeight: z13.number().optional(),
|
|
6191
|
+
devicePixelRatio: z13.number().optional()
|
|
5554
6192
|
});
|
|
5555
6193
|
sessions.post(
|
|
5556
6194
|
"/:id/devtools-context",
|
|
@@ -5722,7 +6360,7 @@ function getAttachmentsDir(sessionId) {
|
|
|
5722
6360
|
}
|
|
5723
6361
|
function ensureAttachmentsDir(sessionId) {
|
|
5724
6362
|
const dir = getAttachmentsDir(sessionId);
|
|
5725
|
-
if (!
|
|
6363
|
+
if (!existsSync13(dir)) {
|
|
5726
6364
|
mkdirSync3(dir, { recursive: true });
|
|
5727
6365
|
}
|
|
5728
6366
|
return dir;
|
|
@@ -5734,7 +6372,7 @@ sessions.get("/:id/attachments", async (c) => {
|
|
|
5734
6372
|
return c.json({ error: "Session not found" }, 404);
|
|
5735
6373
|
}
|
|
5736
6374
|
const dir = getAttachmentsDir(sessionId);
|
|
5737
|
-
if (!
|
|
6375
|
+
if (!existsSync13(dir)) {
|
|
5738
6376
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
5739
6377
|
}
|
|
5740
6378
|
const files = readdirSync(dir);
|
|
@@ -5773,7 +6411,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
5773
6411
|
const dir = ensureAttachmentsDir(sessionId);
|
|
5774
6412
|
const id = nanoid4(10);
|
|
5775
6413
|
const ext = extname6(file.name) || "";
|
|
5776
|
-
const safeFilename = `${id}_${
|
|
6414
|
+
const safeFilename = `${id}_${basename4(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
5777
6415
|
const filePath = join5(dir, safeFilename);
|
|
5778
6416
|
const arrayBuffer = await file.arrayBuffer();
|
|
5779
6417
|
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
@@ -5799,7 +6437,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
5799
6437
|
const dir = ensureAttachmentsDir(sessionId);
|
|
5800
6438
|
const id = nanoid4(10);
|
|
5801
6439
|
const ext = extname6(body.filename) || "";
|
|
5802
|
-
const safeFilename = `${id}_${
|
|
6440
|
+
const safeFilename = `${id}_${basename4(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
5803
6441
|
const filePath = join5(dir, safeFilename);
|
|
5804
6442
|
let base64Data = body.data;
|
|
5805
6443
|
if (base64Data.includes(",")) {
|
|
@@ -5829,7 +6467,7 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
5829
6467
|
return c.json({ error: "Session not found" }, 404);
|
|
5830
6468
|
}
|
|
5831
6469
|
const dir = getAttachmentsDir(sessionId);
|
|
5832
|
-
if (!
|
|
6470
|
+
if (!existsSync13(dir)) {
|
|
5833
6471
|
return c.json({ error: "Attachment not found" }, 404);
|
|
5834
6472
|
}
|
|
5835
6473
|
const files = readdirSync(dir);
|
|
@@ -5841,10 +6479,10 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
5841
6479
|
unlinkSync(filePath);
|
|
5842
6480
|
return c.json({ success: true, id: attachmentId });
|
|
5843
6481
|
});
|
|
5844
|
-
var filesQuerySchema =
|
|
5845
|
-
query:
|
|
6482
|
+
var filesQuerySchema = z13.object({
|
|
6483
|
+
query: z13.string().optional(),
|
|
5846
6484
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
5847
|
-
limit:
|
|
6485
|
+
limit: z13.string().optional()
|
|
5848
6486
|
// Max results (default 50)
|
|
5849
6487
|
});
|
|
5850
6488
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -5917,11 +6555,11 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
5917
6555
|
return results;
|
|
5918
6556
|
}
|
|
5919
6557
|
try {
|
|
5920
|
-
const entries = await
|
|
6558
|
+
const entries = await readdir5(currentDir, { withFileTypes: true });
|
|
5921
6559
|
for (const entry of entries) {
|
|
5922
6560
|
if (results.length >= limit * 2) break;
|
|
5923
6561
|
const fullPath = join5(currentDir, entry.name);
|
|
5924
|
-
const relativePath =
|
|
6562
|
+
const relativePath = relative9(baseDir, fullPath);
|
|
5925
6563
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
5926
6564
|
continue;
|
|
5927
6565
|
}
|
|
@@ -5968,7 +6606,7 @@ sessions.get(
|
|
|
5968
6606
|
return c.json({ error: "Session not found" }, 404);
|
|
5969
6607
|
}
|
|
5970
6608
|
const workingDirectory = session.workingDirectory;
|
|
5971
|
-
if (!
|
|
6609
|
+
if (!existsSync13(workingDirectory)) {
|
|
5972
6610
|
return c.json({
|
|
5973
6611
|
sessionId,
|
|
5974
6612
|
workingDirectory,
|
|
@@ -6022,8 +6660,8 @@ sessions.get(
|
|
|
6022
6660
|
init_db();
|
|
6023
6661
|
import { Hono as Hono2 } from "hono";
|
|
6024
6662
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
6025
|
-
import { z as
|
|
6026
|
-
import { existsSync as
|
|
6663
|
+
import { z as z14 } from "zod";
|
|
6664
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
6027
6665
|
import { join as join6 } from "path";
|
|
6028
6666
|
init_config();
|
|
6029
6667
|
|
|
@@ -6164,7 +6802,7 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
|
|
|
6164
6802
|
toolCallId,
|
|
6165
6803
|
argsTextDelta: chunk
|
|
6166
6804
|
}));
|
|
6167
|
-
await new Promise((
|
|
6805
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
6168
6806
|
}
|
|
6169
6807
|
}
|
|
6170
6808
|
function buildDevtoolsContextXml(sessionId) {
|
|
@@ -6187,30 +6825,30 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
6187
6825
|
${prompt}`;
|
|
6188
6826
|
}
|
|
6189
6827
|
var agents = new Hono2();
|
|
6190
|
-
var attachmentSchema =
|
|
6191
|
-
type:
|
|
6192
|
-
data:
|
|
6828
|
+
var attachmentSchema = z14.object({
|
|
6829
|
+
type: z14.enum(["image", "file"]),
|
|
6830
|
+
data: z14.string(),
|
|
6193
6831
|
// base64 data URL or raw base64
|
|
6194
|
-
mediaType:
|
|
6195
|
-
filename:
|
|
6832
|
+
mediaType: z14.string().optional(),
|
|
6833
|
+
filename: z14.string().optional()
|
|
6196
6834
|
});
|
|
6197
|
-
var runPromptSchema =
|
|
6198
|
-
prompt:
|
|
6835
|
+
var runPromptSchema = z14.object({
|
|
6836
|
+
prompt: z14.string(),
|
|
6199
6837
|
// Can be empty if attachments are provided
|
|
6200
|
-
attachments:
|
|
6838
|
+
attachments: z14.array(attachmentSchema).optional()
|
|
6201
6839
|
}).refine(
|
|
6202
6840
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
6203
6841
|
{ message: "Either prompt or attachments must be provided" }
|
|
6204
6842
|
);
|
|
6205
|
-
var quickStartSchema =
|
|
6206
|
-
prompt:
|
|
6207
|
-
name:
|
|
6208
|
-
workingDirectory:
|
|
6209
|
-
model:
|
|
6210
|
-
toolApprovals:
|
|
6843
|
+
var quickStartSchema = z14.object({
|
|
6844
|
+
prompt: z14.string().min(1),
|
|
6845
|
+
name: z14.string().optional(),
|
|
6846
|
+
workingDirectory: z14.string().optional(),
|
|
6847
|
+
model: z14.string().optional(),
|
|
6848
|
+
toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
|
|
6211
6849
|
});
|
|
6212
|
-
var rejectSchema =
|
|
6213
|
-
reason:
|
|
6850
|
+
var rejectSchema = z14.object({
|
|
6851
|
+
reason: z14.string().optional()
|
|
6214
6852
|
}).optional();
|
|
6215
6853
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
6216
6854
|
function getAttachmentsDirectory(sessionId) {
|
|
@@ -6219,7 +6857,7 @@ function getAttachmentsDirectory(sessionId) {
|
|
|
6219
6857
|
}
|
|
6220
6858
|
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
6221
6859
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
6222
|
-
if (!
|
|
6860
|
+
if (!existsSync14(attachmentsDir)) {
|
|
6223
6861
|
mkdirSync4(attachmentsDir, { recursive: true });
|
|
6224
6862
|
}
|
|
6225
6863
|
let filename = attachment.filename;
|
|
@@ -6397,7 +7035,7 @@ ${prompt}` });
|
|
|
6397
7035
|
chunkIndex,
|
|
6398
7036
|
chunkCount
|
|
6399
7037
|
}));
|
|
6400
|
-
await new Promise((
|
|
7038
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
6401
7039
|
}
|
|
6402
7040
|
},
|
|
6403
7041
|
onStepFinish: async () => {
|
|
@@ -6861,7 +7499,7 @@ agents.post(
|
|
|
6861
7499
|
chunkIndex,
|
|
6862
7500
|
chunkCount
|
|
6863
7501
|
}));
|
|
6864
|
-
await new Promise((
|
|
7502
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
6865
7503
|
}
|
|
6866
7504
|
},
|
|
6867
7505
|
onStepFinish: async () => {
|
|
@@ -6998,11 +7636,11 @@ agents.post(
|
|
|
6998
7636
|
init_config();
|
|
6999
7637
|
import { Hono as Hono3 } from "hono";
|
|
7000
7638
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
7001
|
-
import { z as
|
|
7639
|
+
import { z as z15 } from "zod";
|
|
7002
7640
|
import { readFileSync as readFileSync5 } from "fs";
|
|
7003
|
-
import { fileURLToPath as
|
|
7641
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7004
7642
|
import { dirname as dirname6, join as join7 } from "path";
|
|
7005
|
-
var __filename =
|
|
7643
|
+
var __filename = fileURLToPath3(import.meta.url);
|
|
7006
7644
|
var __dirname = dirname6(__filename);
|
|
7007
7645
|
var possiblePaths = [
|
|
7008
7646
|
join7(__dirname, "../package.json"),
|
|
@@ -7108,9 +7746,9 @@ health.get("/api-keys", async (c) => {
|
|
|
7108
7746
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
7109
7747
|
});
|
|
7110
7748
|
});
|
|
7111
|
-
var setApiKeySchema =
|
|
7112
|
-
provider:
|
|
7113
|
-
apiKey:
|
|
7749
|
+
var setApiKeySchema = z15.object({
|
|
7750
|
+
provider: z15.string(),
|
|
7751
|
+
apiKey: z15.string().min(1)
|
|
7114
7752
|
});
|
|
7115
7753
|
health.post(
|
|
7116
7754
|
"/api-keys",
|
|
@@ -7149,13 +7787,13 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
7149
7787
|
// src/server/routes/terminals.ts
|
|
7150
7788
|
import { Hono as Hono4 } from "hono";
|
|
7151
7789
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
7152
|
-
import { z as
|
|
7790
|
+
import { z as z16 } from "zod";
|
|
7153
7791
|
init_db();
|
|
7154
7792
|
var terminals = new Hono4();
|
|
7155
|
-
var spawnSchema =
|
|
7156
|
-
command:
|
|
7157
|
-
cwd:
|
|
7158
|
-
name:
|
|
7793
|
+
var spawnSchema = z16.object({
|
|
7794
|
+
command: z16.string(),
|
|
7795
|
+
cwd: z16.string().optional(),
|
|
7796
|
+
name: z16.string().optional()
|
|
7159
7797
|
});
|
|
7160
7798
|
terminals.post(
|
|
7161
7799
|
"/:sessionId/terminals",
|
|
@@ -7236,8 +7874,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
7236
7874
|
// We don't track exit codes in tmux mode
|
|
7237
7875
|
});
|
|
7238
7876
|
});
|
|
7239
|
-
var logsQuerySchema =
|
|
7240
|
-
tail:
|
|
7877
|
+
var logsQuerySchema = z16.object({
|
|
7878
|
+
tail: z16.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
7241
7879
|
});
|
|
7242
7880
|
terminals.get(
|
|
7243
7881
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -7261,8 +7899,8 @@ terminals.get(
|
|
|
7261
7899
|
});
|
|
7262
7900
|
}
|
|
7263
7901
|
);
|
|
7264
|
-
var killSchema =
|
|
7265
|
-
signal:
|
|
7902
|
+
var killSchema = z16.object({
|
|
7903
|
+
signal: z16.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
7266
7904
|
});
|
|
7267
7905
|
terminals.post(
|
|
7268
7906
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -7276,8 +7914,8 @@ terminals.post(
|
|
|
7276
7914
|
return c.json({ success: true, message: "Terminal killed" });
|
|
7277
7915
|
}
|
|
7278
7916
|
);
|
|
7279
|
-
var writeSchema =
|
|
7280
|
-
input:
|
|
7917
|
+
var writeSchema = z16.object({
|
|
7918
|
+
input: z16.string()
|
|
7281
7919
|
});
|
|
7282
7920
|
terminals.post(
|
|
7283
7921
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -7546,13 +8184,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
7546
8184
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
7547
8185
|
function getWebDirectory() {
|
|
7548
8186
|
try {
|
|
7549
|
-
const currentDir = dirname7(
|
|
7550
|
-
const webDir =
|
|
7551
|
-
if (
|
|
8187
|
+
const currentDir = dirname7(fileURLToPath4(import.meta.url));
|
|
8188
|
+
const webDir = resolve10(currentDir, "..", "web");
|
|
8189
|
+
if (existsSync15(webDir) && existsSync15(join8(webDir, "package.json"))) {
|
|
7552
8190
|
return webDir;
|
|
7553
8191
|
}
|
|
7554
|
-
const altWebDir =
|
|
7555
|
-
if (
|
|
8192
|
+
const altWebDir = resolve10(currentDir, "..", "..", "web");
|
|
8193
|
+
if (existsSync15(altWebDir) && existsSync15(join8(altWebDir, "package.json"))) {
|
|
7556
8194
|
return altWebDir;
|
|
7557
8195
|
}
|
|
7558
8196
|
return null;
|
|
@@ -7575,18 +8213,18 @@ async function isSparkcoderWebRunning(port) {
|
|
|
7575
8213
|
}
|
|
7576
8214
|
}
|
|
7577
8215
|
function isPortInUse(port) {
|
|
7578
|
-
return new Promise((
|
|
8216
|
+
return new Promise((resolve11) => {
|
|
7579
8217
|
const server = createNetServer();
|
|
7580
8218
|
server.once("error", (err) => {
|
|
7581
8219
|
if (err.code === "EADDRINUSE") {
|
|
7582
|
-
|
|
8220
|
+
resolve11(true);
|
|
7583
8221
|
} else {
|
|
7584
|
-
|
|
8222
|
+
resolve11(false);
|
|
7585
8223
|
}
|
|
7586
8224
|
});
|
|
7587
8225
|
server.once("listening", () => {
|
|
7588
8226
|
server.close();
|
|
7589
|
-
|
|
8227
|
+
resolve11(false);
|
|
7590
8228
|
});
|
|
7591
8229
|
server.listen(port, "0.0.0.0");
|
|
7592
8230
|
});
|
|
@@ -7611,14 +8249,14 @@ async function findWebPort(preferredPort) {
|
|
|
7611
8249
|
}
|
|
7612
8250
|
function hasProductionBuild(webDir) {
|
|
7613
8251
|
const buildIdPath = join8(webDir, ".next", "BUILD_ID");
|
|
7614
|
-
return
|
|
8252
|
+
return existsSync15(buildIdPath);
|
|
7615
8253
|
}
|
|
7616
8254
|
function hasSourceFiles(webDir) {
|
|
7617
8255
|
const appDir = join8(webDir, "src", "app");
|
|
7618
8256
|
const pagesDir = join8(webDir, "src", "pages");
|
|
7619
8257
|
const rootAppDir = join8(webDir, "app");
|
|
7620
8258
|
const rootPagesDir = join8(webDir, "pages");
|
|
7621
|
-
return
|
|
8259
|
+
return existsSync15(appDir) || existsSync15(pagesDir) || existsSync15(rootAppDir) || existsSync15(rootPagesDir);
|
|
7622
8260
|
}
|
|
7623
8261
|
function getStandaloneServerPath(webDir) {
|
|
7624
8262
|
const possiblePaths2 = [
|
|
@@ -7626,14 +8264,14 @@ function getStandaloneServerPath(webDir) {
|
|
|
7626
8264
|
join8(webDir, ".next", "standalone", "web", "server.js")
|
|
7627
8265
|
];
|
|
7628
8266
|
for (const serverPath of possiblePaths2) {
|
|
7629
|
-
if (
|
|
8267
|
+
if (existsSync15(serverPath)) {
|
|
7630
8268
|
return serverPath;
|
|
7631
8269
|
}
|
|
7632
8270
|
}
|
|
7633
8271
|
return null;
|
|
7634
8272
|
}
|
|
7635
8273
|
function runCommand(command, args, cwd, env) {
|
|
7636
|
-
return new Promise((
|
|
8274
|
+
return new Promise((resolve11) => {
|
|
7637
8275
|
const child = spawn2(command, args, {
|
|
7638
8276
|
cwd,
|
|
7639
8277
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -7648,10 +8286,10 @@ function runCommand(command, args, cwd, env) {
|
|
|
7648
8286
|
output += data.toString();
|
|
7649
8287
|
});
|
|
7650
8288
|
child.on("close", (code) => {
|
|
7651
|
-
|
|
8289
|
+
resolve11({ success: code === 0, output });
|
|
7652
8290
|
});
|
|
7653
8291
|
child.on("error", (err) => {
|
|
7654
|
-
|
|
8292
|
+
resolve11({ success: false, output: err.message });
|
|
7655
8293
|
});
|
|
7656
8294
|
});
|
|
7657
8295
|
}
|
|
@@ -7666,8 +8304,8 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
7666
8304
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
7667
8305
|
return { process: null, port: actualPort };
|
|
7668
8306
|
}
|
|
7669
|
-
const usePnpm =
|
|
7670
|
-
const useNpm = !usePnpm &&
|
|
8307
|
+
const usePnpm = existsSync15(join8(webDir, "pnpm-lock.yaml"));
|
|
8308
|
+
const useNpm = !usePnpm && existsSync15(join8(webDir, "package-lock.json"));
|
|
7671
8309
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
7672
8310
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
7673
8311
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
@@ -7735,10 +8373,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
7735
8373
|
let started = false;
|
|
7736
8374
|
let exited = false;
|
|
7737
8375
|
let exitCode = null;
|
|
7738
|
-
const startedPromise = new Promise((
|
|
8376
|
+
const startedPromise = new Promise((resolve11) => {
|
|
7739
8377
|
const timeout = setTimeout(() => {
|
|
7740
8378
|
if (!started && !exited) {
|
|
7741
|
-
|
|
8379
|
+
resolve11(false);
|
|
7742
8380
|
}
|
|
7743
8381
|
}, startupTimeout);
|
|
7744
8382
|
child.stdout?.on("data", (data) => {
|
|
@@ -7752,7 +8390,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
7752
8390
|
if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
7753
8391
|
started = true;
|
|
7754
8392
|
clearTimeout(timeout);
|
|
7755
|
-
|
|
8393
|
+
resolve11(true);
|
|
7756
8394
|
}
|
|
7757
8395
|
});
|
|
7758
8396
|
child.stderr?.on("data", (data) => {
|
|
@@ -7764,14 +8402,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
7764
8402
|
child.on("error", (err) => {
|
|
7765
8403
|
if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
|
|
7766
8404
|
clearTimeout(timeout);
|
|
7767
|
-
|
|
8405
|
+
resolve11(false);
|
|
7768
8406
|
});
|
|
7769
8407
|
child.on("exit", (code) => {
|
|
7770
8408
|
exited = true;
|
|
7771
8409
|
exitCode = code;
|
|
7772
8410
|
if (!started) {
|
|
7773
8411
|
clearTimeout(timeout);
|
|
7774
|
-
|
|
8412
|
+
resolve11(false);
|
|
7775
8413
|
}
|
|
7776
8414
|
webUIProcess = null;
|
|
7777
8415
|
});
|
|
@@ -7864,7 +8502,7 @@ async function startServer(options = {}) {
|
|
|
7864
8502
|
if (options.workingDirectory) {
|
|
7865
8503
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
7866
8504
|
}
|
|
7867
|
-
if (!
|
|
8505
|
+
if (!existsSync15(config.resolvedWorkingDirectory)) {
|
|
7868
8506
|
mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
|
|
7869
8507
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
7870
8508
|
}
|