@mrclrchtr/supi-lsp 1.3.0 → 1.4.0
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/README.md +58 -39
- package/node_modules/@mrclrchtr/supi-core/README.md +52 -41
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +13 -13
- package/node_modules/@mrclrchtr/supi-core/src/{config-settings.ts → config/config-settings.ts} +2 -2
- package/node_modules/@mrclrchtr/supi-core/src/{context-provider-registry.ts → context/context-provider-registry.ts} +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +13 -13
- package/node_modules/@mrclrchtr/supi-core/src/{settings-registry.ts → settings/settings-registry.ts} +1 -1
- package/package.json +5 -3
- package/src/api.ts +16 -3
- package/src/client/client-refresh.ts +1 -1
- package/src/client/client.ts +27 -3
- package/src/client/transport.ts +61 -5
- package/src/config/tsconfig-scope.ts +244 -0
- package/src/{types.ts → config/types.ts} +4 -2
- package/src/coordinates.ts +11 -0
- package/src/diagnostics/diagnostic-augmentation.ts +5 -5
- package/src/diagnostics/diagnostic-context.ts +115 -0
- package/src/diagnostics/diagnostic-display.ts +1 -1
- package/src/diagnostics/diagnostic-summary.ts +3 -2
- package/src/diagnostics/diagnostics.ts +1 -1
- package/src/diagnostics/stale-diagnostics.ts +1 -1
- package/src/diagnostics/suppression-diagnostics.ts +1 -1
- package/src/{workspace-sentinels.ts → diagnostics/workspace-sentinels.ts} +2 -2
- package/src/format.ts +2 -23
- package/src/index.ts +18 -5
- package/src/lsp.ts +72 -120
- package/src/manager/manager-diagnostics.ts +1 -1
- package/src/manager/manager-helpers.ts +4 -2
- package/src/manager/manager-project-info.ts +10 -7
- package/src/manager/manager-workspace-recovery.ts +1 -1
- package/src/manager/manager-workspace-symbol.ts +158 -6
- package/src/manager/manager.ts +202 -43
- package/src/{lsp-state.ts → session/lsp-state.ts} +22 -11
- package/src/{scanner.ts → session/scanner.ts} +3 -3
- package/src/{service-registry.ts → session/service-registry.ts} +104 -12
- package/src/{settings-registration.ts → session/settings-registration.ts} +1 -1
- package/src/session/tree-persist.ts +75 -0
- package/src/summary.ts +1 -1
- package/src/tool/guidance.ts +138 -0
- package/src/tool/names.ts +19 -0
- package/src/{overrides.ts → tool/overrides.ts} +55 -24
- package/src/tool/register-tools.ts +224 -0
- package/src/tool/service-actions.ts +258 -0
- package/src/{ui.ts → ui/ui.ts} +4 -4
- package/src/utils.ts +11 -0
- package/src/guidance.ts +0 -163
- package/src/search-fallback.ts +0 -98
- package/src/tool-actions.ts +0 -430
- package/src/tree-persist.ts +0 -48
- package/src/tsconfig-scope.ts +0 -156
- /package/node_modules/@mrclrchtr/supi-core/src/{config.ts → config/config.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{context-messages.ts → context/context-messages.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{context-tag.ts → context/context-tag.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{settings-command.ts → settings/settings-command.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{settings-ui.ts → settings/settings-ui.ts} +0 -0
- /package/src/{capabilities.ts → config/capabilities.ts} +0 -0
- /package/src/{config.ts → config/config.ts} +0 -0
- /package/src/{defaults.json → config/defaults.json} +0 -0
- /package/src/{renderer.ts → ui/renderer.ts} +0 -0
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
loadSupiConfigForScope,
|
|
9
9
|
registerConfigSettings,
|
|
10
10
|
} from "@mrclrchtr/supi-core/api";
|
|
11
|
-
import { loadConfig } from "
|
|
11
|
+
import { loadConfig } from "../config/config.ts";
|
|
12
12
|
|
|
13
13
|
// ── Types ────────────────────────────────────────────────────
|
|
14
14
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// LSP tree navigation persistence — restores tool activation state across /tree navigation.
|
|
2
|
+
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { LspManager } from "../manager/manager.ts";
|
|
5
|
+
import { LSP_TOOL_NAMES } from "../tool/names.ts";
|
|
6
|
+
import { SessionLspService, setSessionLspServiceState } from "./service-registry.ts";
|
|
7
|
+
|
|
8
|
+
/** Shape of the entry persisted via `pi.appendEntry()`. */
|
|
9
|
+
export interface LspStateEntry {
|
|
10
|
+
active: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Restore LSP activation state from the current branch after /tree navigation. */
|
|
14
|
+
export function registerTreePersistHandlers(
|
|
15
|
+
pi: ExtensionAPI,
|
|
16
|
+
state: { lspActive: boolean; manager?: LspManager | null },
|
|
17
|
+
): void {
|
|
18
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
19
|
+
const branch = ctx.sessionManager.getBranch();
|
|
20
|
+
const isActive = findLastLspState(branch)?.active === true;
|
|
21
|
+
|
|
22
|
+
syncBranchToolActivation(pi, isActive);
|
|
23
|
+
syncBranchServiceState(state.manager, isActive);
|
|
24
|
+
state.lspActive = isActive;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findLastLspState(branch: Array<{ type: string; customType?: string; data?: unknown }>) {
|
|
29
|
+
let lastEntry: LspStateEntry | undefined;
|
|
30
|
+
for (const entry of branch) {
|
|
31
|
+
if (entry.type === "custom" && entry.customType === "lsp-active") {
|
|
32
|
+
lastEntry = entry.data as LspStateEntry | undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return lastEntry;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function syncBranchToolActivation(pi: ExtensionAPI, isActive: boolean): void {
|
|
39
|
+
const activeTools = pi.getActiveTools();
|
|
40
|
+
if (isActive) {
|
|
41
|
+
const missing = LSP_TOOL_NAMES.filter((toolName) => !activeTools.includes(toolName));
|
|
42
|
+
if (missing.length > 0) {
|
|
43
|
+
pi.setActiveTools([...activeTools, ...missing]);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const nextTools = activeTools.filter(
|
|
49
|
+
(toolName) => !LSP_TOOL_NAMES.includes(toolName as (typeof LSP_TOOL_NAMES)[number]),
|
|
50
|
+
);
|
|
51
|
+
if (nextTools.length !== activeTools.length) {
|
|
52
|
+
pi.setActiveTools(nextTools);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function syncBranchServiceState(manager: LspManager | null | undefined, isActive: boolean): void {
|
|
57
|
+
if (!manager) return;
|
|
58
|
+
|
|
59
|
+
setSessionLspServiceState(manager.getCwd(), {
|
|
60
|
+
kind: isActive ? "ready" : "inactive",
|
|
61
|
+
service: new SessionLspService(manager),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Persist that LSP is active in the session tree. */
|
|
66
|
+
export function persistLspActiveState(pi: ExtensionAPI, state: { lspActive: boolean }): void {
|
|
67
|
+
state.lspActive = true;
|
|
68
|
+
pi.appendEntry<LspStateEntry>("lsp-active", { active: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Persist that LSP is inactive in the session tree. */
|
|
72
|
+
export function persistLspInactiveState(pi: ExtensionAPI, state: { lspActive: boolean }): void {
|
|
73
|
+
state.lspActive = false;
|
|
74
|
+
pi.appendEntry<LspStateEntry>("lsp-active", { active: false });
|
|
75
|
+
}
|
package/src/summary.ts
CHANGED
|
@@ -84,7 +84,7 @@ export function isPathRelevant(filePath: string, relevantPaths: string[], cwd: s
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
import { isFileExcludedByTsconfig } from "./tsconfig-scope.ts";
|
|
87
|
+
import { isFileExcludedByTsconfig } from "./config/tsconfig-scope.ts";
|
|
88
88
|
|
|
89
89
|
/** Check whether a file path is inside the project tree (within cwd, not node_modules/.pnpm/out-of-tree).
|
|
90
90
|
* Does NOT check tsconfig exclusion — use `shouldIgnoreLspPath` for diagnostics/guidance filtering. */
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Prompt guidance and tool descriptions for the expert LSP toolset.
|
|
2
|
+
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { ProjectServerInfo } from "../config/types.ts";
|
|
5
|
+
import {
|
|
6
|
+
LSP_DIAGNOSTICS_TOOL,
|
|
7
|
+
LSP_DOCUMENT_SYMBOLS_TOOL,
|
|
8
|
+
LSP_LOOKUP_TOOL,
|
|
9
|
+
LSP_RECOVER_TOOL,
|
|
10
|
+
LSP_REFACTOR_TOOL,
|
|
11
|
+
LSP_WORKSPACE_SYMBOLS_TOOL,
|
|
12
|
+
type LspToolName,
|
|
13
|
+
} from "./names.ts";
|
|
14
|
+
|
|
15
|
+
export interface LspToolPromptSurface {
|
|
16
|
+
description: string;
|
|
17
|
+
promptSnippet: string;
|
|
18
|
+
promptGuidelines: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type LspToolPromptSurfaceMap = Record<LspToolName, LspToolPromptSurface>;
|
|
22
|
+
|
|
23
|
+
const LOOKUP_GUIDELINES = [
|
|
24
|
+
'Use lsp_lookup with `kind: "hover"` for semantic type or symbol information at a known `file`, `line`, and `character`.',
|
|
25
|
+
'Use lsp_lookup with `kind: "definition"`, `"references"`, or `"implementation"` for semantic navigation at a known position.',
|
|
26
|
+
"Use lsp_lookup after code_intel or tree_sitter has already narrowed the target file and position.",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const DOCUMENT_SYMBOL_GUIDELINES = [
|
|
30
|
+
"Use lsp_document_symbols(file) for semantic declarations in one supported file.",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const WORKSPACE_SYMBOL_GUIDELINES = [
|
|
34
|
+
"Use lsp_workspace_symbols(query) for semantic symbol-name lookup across the current project.",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const DIAGNOSTICS_GUIDELINES = [
|
|
38
|
+
"Use lsp_diagnostics(file?) when you need current diagnostics for one file or a workspace-level summary.",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const REFACTOR_GUIDELINES = [
|
|
42
|
+
'Use lsp_refactor with `kind: "rename"` for semantic rename planning at a known `file`, `line`, and `character`.',
|
|
43
|
+
'Use lsp_refactor with `kind: "code_actions"` for semantic fixes or refactors at a known position.',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const RECOVER_GUIDELINES = [
|
|
47
|
+
"Use lsp_recover() when diagnostics look stale after workspace-level changes or generated-file updates.",
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const defaultLspToolPromptSurfaces = buildLspToolPromptSurfaces([], ".");
|
|
51
|
+
|
|
52
|
+
export function buildLspToolPromptSurfaces(
|
|
53
|
+
servers: ProjectServerInfo[],
|
|
54
|
+
cwd: string,
|
|
55
|
+
): LspToolPromptSurfaceMap {
|
|
56
|
+
const coverageGuidelines = buildCoverageGuidelines(servers, cwd);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
[LSP_LOOKUP_TOOL]: {
|
|
60
|
+
description:
|
|
61
|
+
"Language Server Protocol lookup tool — semantic hover, definition, references, and implementation for supported files. Use lsp_lookup when you know the file and 1-based line/character position and need semantic drill-down rather than text search.",
|
|
62
|
+
promptSnippet:
|
|
63
|
+
"lsp_lookup — semantic hover/definition/references/implementation at a known file position",
|
|
64
|
+
promptGuidelines: [...LOOKUP_GUIDELINES, ...coverageGuidelines],
|
|
65
|
+
},
|
|
66
|
+
[LSP_DOCUMENT_SYMBOLS_TOOL]: {
|
|
67
|
+
description:
|
|
68
|
+
"Language Server Protocol document symbols tool — list semantic declarations in one supported file. Use lsp_document_symbols when you need a symbol-aware outline rather than raw text structure.",
|
|
69
|
+
promptSnippet: "lsp_document_symbols — semantic declarations for one supported file",
|
|
70
|
+
promptGuidelines: DOCUMENT_SYMBOL_GUIDELINES,
|
|
71
|
+
},
|
|
72
|
+
[LSP_WORKSPACE_SYMBOLS_TOOL]: {
|
|
73
|
+
description:
|
|
74
|
+
"Language Server Protocol workspace symbols tool — semantic symbol-name lookup across the current project. Use lsp_workspace_symbols to find declarations by name before opening a specific file.",
|
|
75
|
+
promptSnippet: "lsp_workspace_symbols — semantic symbol-name lookup across the project",
|
|
76
|
+
promptGuidelines: WORKSPACE_SYMBOL_GUIDELINES,
|
|
77
|
+
},
|
|
78
|
+
[LSP_DIAGNOSTICS_TOOL]: {
|
|
79
|
+
description:
|
|
80
|
+
"Language Server Protocol diagnostics tool — current diagnostics for one file or a workspace summary. Use lsp_diagnostics for semantic compiler or language-server issues instead of guessing from text alone.",
|
|
81
|
+
promptSnippet: "lsp_diagnostics — current diagnostics for one file or the workspace",
|
|
82
|
+
promptGuidelines: DIAGNOSTICS_GUIDELINES,
|
|
83
|
+
},
|
|
84
|
+
[LSP_REFACTOR_TOOL]: {
|
|
85
|
+
description:
|
|
86
|
+
"Language Server Protocol refactor tool — semantic rename planning and code actions at a known file position. Use lsp_refactor when you need language-server-backed edits or quick-fix suggestions.",
|
|
87
|
+
promptSnippet: "lsp_refactor — semantic rename planning and code actions at a known position",
|
|
88
|
+
promptGuidelines: REFACTOR_GUIDELINES,
|
|
89
|
+
},
|
|
90
|
+
[LSP_RECOVER_TOOL]: {
|
|
91
|
+
description:
|
|
92
|
+
"Language Server Protocol recover tool — refresh diagnostics after workspace changes and stale language-server state. Use lsp_recover when new files, generated types, or config updates leave diagnostics out of sync.",
|
|
93
|
+
promptSnippet: "lsp_recover — refresh stale diagnostics after workspace changes",
|
|
94
|
+
promptGuidelines: RECOVER_GUIDELINES,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildCoverageGuidelines(servers: ProjectServerInfo[], cwd: string): string[] {
|
|
100
|
+
const active = servers
|
|
101
|
+
.filter((server) => server.status === "running")
|
|
102
|
+
.map((server) => {
|
|
103
|
+
const root = displayRoot(server.root, cwd);
|
|
104
|
+
const fileTypes = server.fileTypes.map((entry) => `.${entry}`).join(",");
|
|
105
|
+
const actions = server.supportedActions.join(",");
|
|
106
|
+
const actionText = actions.length > 0 ? ` | actions: ${actions}` : "";
|
|
107
|
+
return `lsp server coverage: ${server.name} | root: ${root} | files: ${fileTypes}${actionText}`;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const unavailable = servers
|
|
111
|
+
.filter((server) => server.status !== "running")
|
|
112
|
+
.map((server) => server.name);
|
|
113
|
+
|
|
114
|
+
const dynamic = [...active];
|
|
115
|
+
if (unavailable.length > 0) {
|
|
116
|
+
dynamic.push(
|
|
117
|
+
`lsp server unavailable: ${unavailable.join(",")} — install or enable to extend semantic coverage`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return dynamic;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function displayRoot(root: string, cwd: string): string {
|
|
125
|
+
const relative = path.relative(cwd, root);
|
|
126
|
+
if (relative === "") return ".";
|
|
127
|
+
if (relative.startsWith(`..${path.sep}`) || relative === "..") return root;
|
|
128
|
+
return relative.replaceAll(path.sep, "/");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Compatibility exports for older internal tests and helper imports.
|
|
132
|
+
export const toolDescription = defaultLspToolPromptSurfaces[LSP_LOOKUP_TOOL].description;
|
|
133
|
+
export const promptSnippet = defaultLspToolPromptSurfaces[LSP_LOOKUP_TOOL].promptSnippet;
|
|
134
|
+
export const promptGuidelines = defaultLspToolPromptSurfaces[LSP_LOOKUP_TOOL].promptGuidelines;
|
|
135
|
+
|
|
136
|
+
export function buildProjectGuidelines(servers: ProjectServerInfo[], cwd: string): string[] {
|
|
137
|
+
return buildLspToolPromptSurfaces(servers, cwd)[LSP_LOOKUP_TOOL].promptGuidelines;
|
|
138
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Stable LSP tool names shared across registration, guidance, and runtime state.
|
|
2
|
+
|
|
3
|
+
export const LSP_LOOKUP_TOOL = "lsp_lookup";
|
|
4
|
+
export const LSP_DOCUMENT_SYMBOLS_TOOL = "lsp_document_symbols";
|
|
5
|
+
export const LSP_WORKSPACE_SYMBOLS_TOOL = "lsp_workspace_symbols";
|
|
6
|
+
export const LSP_DIAGNOSTICS_TOOL = "lsp_diagnostics";
|
|
7
|
+
export const LSP_REFACTOR_TOOL = "lsp_refactor";
|
|
8
|
+
export const LSP_RECOVER_TOOL = "lsp_recover";
|
|
9
|
+
|
|
10
|
+
export const LSP_TOOL_NAMES = [
|
|
11
|
+
LSP_LOOKUP_TOOL,
|
|
12
|
+
LSP_DOCUMENT_SYMBOLS_TOOL,
|
|
13
|
+
LSP_WORKSPACE_SYMBOLS_TOOL,
|
|
14
|
+
LSP_DIAGNOSTICS_TOOL,
|
|
15
|
+
LSP_REFACTOR_TOOL,
|
|
16
|
+
LSP_RECOVER_TOOL,
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export type LspToolName = (typeof LSP_TOOL_NAMES)[number];
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AgentToolUpdateCallback,
|
|
3
|
+
EditToolInput,
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionContext,
|
|
6
|
+
ReadToolInput,
|
|
7
|
+
WriteToolInput,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
2
9
|
import { createEditTool, createReadTool, createWriteTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import type {
|
|
10
|
+
import type { Diagnostic } from "../config/types.ts";
|
|
11
|
+
import { augmentDiagnostics } from "../diagnostics/diagnostic-augmentation.ts";
|
|
12
|
+
import { formatGroupedDiagnostics } from "../diagnostics/diagnostics.ts";
|
|
13
|
+
import { splitSuppressionDiagnostics } from "../diagnostics/suppression-diagnostics.ts";
|
|
14
|
+
import type { LspManager } from "../manager/manager.ts";
|
|
15
|
+
import { resolveSessionPath } from "../utils.ts";
|
|
8
16
|
|
|
9
17
|
interface LspOverrideState {
|
|
10
18
|
getInlineSeverity(): number;
|
|
11
19
|
getManager(): LspManager | null;
|
|
12
|
-
|
|
20
|
+
isActive(): boolean;
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
export function registerLspAwareToolOverrides(pi: ExtensionAPI, state: LspOverrideState): void {
|
|
@@ -20,11 +28,17 @@ export function registerLspAwareToolOverrides(pi: ExtensionAPI, state: LspOverri
|
|
|
20
28
|
pi.registerTool({
|
|
21
29
|
...readMeta,
|
|
22
30
|
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
23
|
-
async execute(
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
async execute(
|
|
32
|
+
toolCallId: string,
|
|
33
|
+
params: ReadToolInput,
|
|
34
|
+
signal: AbortSignal | undefined,
|
|
35
|
+
onUpdate: AgentToolUpdateCallback | undefined,
|
|
36
|
+
ctx: ExtensionContext,
|
|
37
|
+
) {
|
|
38
|
+
const originalRead = createReadTool(ctx.cwd);
|
|
26
39
|
const result = await originalRead.execute(toolCallId, params, signal, onUpdate);
|
|
27
|
-
|
|
40
|
+
if (!state.isActive()) return result;
|
|
41
|
+
await ensureFileOpen(state.getManager(), ctx.cwd, params.path);
|
|
28
42
|
return result;
|
|
29
43
|
},
|
|
30
44
|
});
|
|
@@ -32,15 +46,21 @@ export function registerLspAwareToolOverrides(pi: ExtensionAPI, state: LspOverri
|
|
|
32
46
|
pi.registerTool({
|
|
33
47
|
...writeMeta,
|
|
34
48
|
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
35
|
-
async execute(
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
async execute(
|
|
50
|
+
toolCallId: string,
|
|
51
|
+
params: WriteToolInput,
|
|
52
|
+
signal: AbortSignal | undefined,
|
|
53
|
+
onUpdate: AgentToolUpdateCallback | undefined,
|
|
54
|
+
ctx: ExtensionContext,
|
|
55
|
+
) {
|
|
56
|
+
const originalWrite = createWriteTool(ctx.cwd);
|
|
38
57
|
const result = await originalWrite.execute(toolCallId, params, signal, onUpdate);
|
|
58
|
+
if (!state.isActive()) return result;
|
|
39
59
|
return appendInlineDiagnostics({
|
|
40
60
|
manager: state.getManager(),
|
|
41
61
|
filePath: params.path,
|
|
42
62
|
inlineSeverity: state.getInlineSeverity(),
|
|
43
|
-
cwd,
|
|
63
|
+
cwd: ctx.cwd,
|
|
44
64
|
result,
|
|
45
65
|
});
|
|
46
66
|
},
|
|
@@ -49,15 +69,21 @@ export function registerLspAwareToolOverrides(pi: ExtensionAPI, state: LspOverri
|
|
|
49
69
|
pi.registerTool({
|
|
50
70
|
...editMeta,
|
|
51
71
|
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
52
|
-
async execute(
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
async execute(
|
|
73
|
+
toolCallId: string,
|
|
74
|
+
params: EditToolInput,
|
|
75
|
+
signal: AbortSignal | undefined,
|
|
76
|
+
onUpdate: AgentToolUpdateCallback | undefined,
|
|
77
|
+
ctx: ExtensionContext,
|
|
78
|
+
) {
|
|
79
|
+
const originalEdit = createEditTool(ctx.cwd);
|
|
55
80
|
const result = await originalEdit.execute(toolCallId, params, signal, onUpdate);
|
|
81
|
+
if (!state.isActive()) return result;
|
|
56
82
|
return appendInlineDiagnostics({
|
|
57
83
|
manager: state.getManager(),
|
|
58
84
|
filePath: params.path,
|
|
59
85
|
inlineSeverity: state.getInlineSeverity(),
|
|
60
|
-
cwd,
|
|
86
|
+
cwd: ctx.cwd,
|
|
61
87
|
result,
|
|
62
88
|
});
|
|
63
89
|
},
|
|
@@ -78,17 +104,18 @@ async function appendInlineDiagnostics<T extends { content: unknown[]; details:
|
|
|
78
104
|
if (!options.manager) return options.result;
|
|
79
105
|
|
|
80
106
|
try {
|
|
107
|
+
const resolvedFilePath = resolveSessionPath(options.cwd, options.filePath);
|
|
81
108
|
const effectiveSeverity = Math.max(options.inlineSeverity, 2);
|
|
82
109
|
const entries = await options.manager.syncFileAndGetCascadingDiagnostics(
|
|
83
|
-
|
|
110
|
+
resolvedFilePath,
|
|
84
111
|
effectiveSeverity,
|
|
85
112
|
);
|
|
86
113
|
if (entries.length === 0) return options.result;
|
|
87
114
|
|
|
88
115
|
const primaryDiagnostics =
|
|
89
|
-
entries.find((entry) => entry.file ===
|
|
116
|
+
entries.find((entry) => entry.file === resolvedFilePath)?.diagnostics ?? [];
|
|
90
117
|
const augmentation = await augmentDiagnostics(
|
|
91
|
-
|
|
118
|
+
resolvedFilePath,
|
|
92
119
|
splitSuppressionDiagnostics(primaryDiagnostics, options.inlineSeverity).regular,
|
|
93
120
|
options.manager,
|
|
94
121
|
options.cwd,
|
|
@@ -162,11 +189,15 @@ export function buildInlineDiagnosticsMessage(
|
|
|
162
189
|
return sections.join("\n\n");
|
|
163
190
|
}
|
|
164
191
|
|
|
165
|
-
async function ensureFileOpen(
|
|
192
|
+
async function ensureFileOpen(
|
|
193
|
+
manager: LspManager | null,
|
|
194
|
+
cwd: string,
|
|
195
|
+
filePath: string,
|
|
196
|
+
): Promise<void> {
|
|
166
197
|
if (!manager) return;
|
|
167
198
|
|
|
168
199
|
try {
|
|
169
|
-
await manager.ensureFileOpen(filePath);
|
|
200
|
+
await manager.ensureFileOpen(resolveSessionPath(cwd, filePath));
|
|
170
201
|
} catch {
|
|
171
202
|
// Never block the agent on LSP errors
|
|
172
203
|
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { getSessionLspService } from "../session/service-registry.ts";
|
|
5
|
+
import type { LspToolPromptSurfaceMap } from "./guidance.ts";
|
|
6
|
+
import {
|
|
7
|
+
LSP_DIAGNOSTICS_TOOL,
|
|
8
|
+
LSP_DOCUMENT_SYMBOLS_TOOL,
|
|
9
|
+
LSP_LOOKUP_TOOL,
|
|
10
|
+
LSP_RECOVER_TOOL,
|
|
11
|
+
LSP_REFACTOR_TOOL,
|
|
12
|
+
LSP_WORKSPACE_SYMBOLS_TOOL,
|
|
13
|
+
} from "./names.ts";
|
|
14
|
+
import {
|
|
15
|
+
executeDiagnostics,
|
|
16
|
+
executeDocumentSymbols,
|
|
17
|
+
executeLookup,
|
|
18
|
+
executeRecover,
|
|
19
|
+
executeRefactor,
|
|
20
|
+
executeWorkspaceSymbols,
|
|
21
|
+
} from "./service-actions.ts";
|
|
22
|
+
|
|
23
|
+
const FileParam = Type.String({ description: "File path (relative or absolute)" });
|
|
24
|
+
const LineParam = Type.Number({ description: "1-based line number", minimum: 1 });
|
|
25
|
+
const CharacterParam = Type.Number({ description: "1-based column number", minimum: 1 });
|
|
26
|
+
const QueryParam = Type.String({ description: "Symbol query string" });
|
|
27
|
+
const NewNameParam = Type.String({ description: "New name for rename" });
|
|
28
|
+
|
|
29
|
+
const LookupKindEnum = StringEnum(["hover", "definition", "references", "implementation"] as const);
|
|
30
|
+
|
|
31
|
+
const RefactorKindEnum = StringEnum(["rename", "code_actions"] as const);
|
|
32
|
+
|
|
33
|
+
const LookupParameters = Type.Object(
|
|
34
|
+
{
|
|
35
|
+
kind: LookupKindEnum,
|
|
36
|
+
file: FileParam,
|
|
37
|
+
line: LineParam,
|
|
38
|
+
character: CharacterParam,
|
|
39
|
+
},
|
|
40
|
+
{ additionalProperties: false },
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const DocumentSymbolsParameters = Type.Object(
|
|
44
|
+
{
|
|
45
|
+
file: FileParam,
|
|
46
|
+
},
|
|
47
|
+
{ additionalProperties: false },
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const WorkspaceSymbolsParameters = Type.Object(
|
|
51
|
+
{
|
|
52
|
+
query: QueryParam,
|
|
53
|
+
},
|
|
54
|
+
{ additionalProperties: false },
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const DiagnosticsParameters = Type.Object(
|
|
58
|
+
{
|
|
59
|
+
file: Type.Optional(FileParam),
|
|
60
|
+
},
|
|
61
|
+
{ additionalProperties: false },
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const RefactorParameters = Type.Object(
|
|
65
|
+
{
|
|
66
|
+
kind: RefactorKindEnum,
|
|
67
|
+
file: FileParam,
|
|
68
|
+
line: LineParam,
|
|
69
|
+
character: CharacterParam,
|
|
70
|
+
newName: Type.Optional(NewNameParam),
|
|
71
|
+
},
|
|
72
|
+
{ additionalProperties: false },
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const RecoverParameters = Type.Object({}, { additionalProperties: false });
|
|
76
|
+
|
|
77
|
+
/** Register the expert LSP toolset. Tools are re-registered on session_start to refresh guidance. */
|
|
78
|
+
export function registerLspTools(pi: ExtensionAPI, promptSurfaces: LspToolPromptSurfaceMap): void {
|
|
79
|
+
const lookupSurface = promptSurfaces[LSP_LOOKUP_TOOL];
|
|
80
|
+
pi.registerTool({
|
|
81
|
+
name: LSP_LOOKUP_TOOL,
|
|
82
|
+
label: "LSP Lookup",
|
|
83
|
+
description: lookupSurface.description,
|
|
84
|
+
promptSnippet: lookupSurface.promptSnippet,
|
|
85
|
+
promptGuidelines: lookupSurface.promptGuidelines,
|
|
86
|
+
parameters: LookupParameters,
|
|
87
|
+
execute: createToolExecutor((service, cwd, params) =>
|
|
88
|
+
executeLookup(service, cwd, params as Parameters<typeof executeLookup>[2]),
|
|
89
|
+
),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const documentSymbolsSurface = promptSurfaces[LSP_DOCUMENT_SYMBOLS_TOOL];
|
|
93
|
+
pi.registerTool({
|
|
94
|
+
name: LSP_DOCUMENT_SYMBOLS_TOOL,
|
|
95
|
+
label: "LSP Document Symbols",
|
|
96
|
+
description: documentSymbolsSurface.description,
|
|
97
|
+
promptSnippet: documentSymbolsSurface.promptSnippet,
|
|
98
|
+
promptGuidelines: documentSymbolsSurface.promptGuidelines,
|
|
99
|
+
parameters: DocumentSymbolsParameters,
|
|
100
|
+
execute: createToolExecutor((service, cwd, params) =>
|
|
101
|
+
executeDocumentSymbols(service, cwd, params as Parameters<typeof executeDocumentSymbols>[2]),
|
|
102
|
+
),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const workspaceSymbolsSurface = promptSurfaces[LSP_WORKSPACE_SYMBOLS_TOOL];
|
|
106
|
+
pi.registerTool({
|
|
107
|
+
name: LSP_WORKSPACE_SYMBOLS_TOOL,
|
|
108
|
+
label: "LSP Workspace Symbols",
|
|
109
|
+
description: workspaceSymbolsSurface.description,
|
|
110
|
+
promptSnippet: workspaceSymbolsSurface.promptSnippet,
|
|
111
|
+
promptGuidelines: workspaceSymbolsSurface.promptGuidelines,
|
|
112
|
+
parameters: WorkspaceSymbolsParameters,
|
|
113
|
+
execute: createToolExecutor((service, cwd, params) =>
|
|
114
|
+
executeWorkspaceSymbols(
|
|
115
|
+
service,
|
|
116
|
+
cwd,
|
|
117
|
+
params as Parameters<typeof executeWorkspaceSymbols>[2],
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const diagnosticsSurface = promptSurfaces[LSP_DIAGNOSTICS_TOOL];
|
|
123
|
+
pi.registerTool({
|
|
124
|
+
name: LSP_DIAGNOSTICS_TOOL,
|
|
125
|
+
label: "LSP Diagnostics",
|
|
126
|
+
description: diagnosticsSurface.description,
|
|
127
|
+
promptSnippet: diagnosticsSurface.promptSnippet,
|
|
128
|
+
promptGuidelines: diagnosticsSurface.promptGuidelines,
|
|
129
|
+
parameters: DiagnosticsParameters,
|
|
130
|
+
execute: createToolExecutor((service, cwd, params) =>
|
|
131
|
+
executeDiagnostics(service, cwd, params as Parameters<typeof executeDiagnostics>[2]),
|
|
132
|
+
),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const refactorSurface = promptSurfaces[LSP_REFACTOR_TOOL];
|
|
136
|
+
pi.registerTool({
|
|
137
|
+
name: LSP_REFACTOR_TOOL,
|
|
138
|
+
label: "LSP Refactor",
|
|
139
|
+
description: refactorSurface.description,
|
|
140
|
+
promptSnippet: refactorSurface.promptSnippet,
|
|
141
|
+
promptGuidelines: refactorSurface.promptGuidelines,
|
|
142
|
+
parameters: RefactorParameters,
|
|
143
|
+
execute: createToolExecutor((service, cwd, params) =>
|
|
144
|
+
executeRefactor(service, cwd, params as Parameters<typeof executeRefactor>[2]),
|
|
145
|
+
),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const recoverSurface = promptSurfaces[LSP_RECOVER_TOOL];
|
|
149
|
+
pi.registerTool({
|
|
150
|
+
name: LSP_RECOVER_TOOL,
|
|
151
|
+
label: "LSP Recover",
|
|
152
|
+
description: recoverSurface.description,
|
|
153
|
+
promptSnippet: recoverSurface.promptSnippet,
|
|
154
|
+
promptGuidelines: recoverSurface.promptGuidelines,
|
|
155
|
+
parameters: RecoverParameters,
|
|
156
|
+
execute: createRecoverToolExecutor(),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getReadyService(cwd: string) {
|
|
161
|
+
const state = getSessionLspService(cwd);
|
|
162
|
+
return state.kind === "ready" ? state.service : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function describeUnavailableService(cwd: string): string {
|
|
166
|
+
const state = getSessionLspService(cwd);
|
|
167
|
+
switch (state.kind) {
|
|
168
|
+
case "pending":
|
|
169
|
+
return "LSP is still starting for this workspace. Retry in a moment.";
|
|
170
|
+
case "inactive":
|
|
171
|
+
return `LSP is inactive on the current session branch for ${cwd}.`;
|
|
172
|
+
case "disabled":
|
|
173
|
+
return `LSP is disabled for ${cwd}.`;
|
|
174
|
+
case "unavailable":
|
|
175
|
+
return state.reason;
|
|
176
|
+
default:
|
|
177
|
+
return "LSP not initialized. Start a new session first.";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function createToolExecutor(
|
|
182
|
+
run: (
|
|
183
|
+
service: NonNullable<ReturnType<typeof getReadyService>>,
|
|
184
|
+
cwd: string,
|
|
185
|
+
params: unknown,
|
|
186
|
+
) => Promise<string>,
|
|
187
|
+
) {
|
|
188
|
+
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
189
|
+
return async (
|
|
190
|
+
_toolCallId: string,
|
|
191
|
+
params: unknown,
|
|
192
|
+
_signal: AbortSignal | undefined,
|
|
193
|
+
_onUpdate: unknown,
|
|
194
|
+
ctx: ExtensionContext,
|
|
195
|
+
) => {
|
|
196
|
+
const service = getReadyService(ctx.cwd);
|
|
197
|
+
const text = service
|
|
198
|
+
? await run(service, ctx.cwd, params)
|
|
199
|
+
: describeUnavailableService(ctx.cwd);
|
|
200
|
+
return makeTextResult(text);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function createRecoverToolExecutor() {
|
|
205
|
+
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
206
|
+
return async (
|
|
207
|
+
_toolCallId: string,
|
|
208
|
+
_params: unknown,
|
|
209
|
+
_signal: AbortSignal | undefined,
|
|
210
|
+
_onUpdate: unknown,
|
|
211
|
+
ctx: ExtensionContext,
|
|
212
|
+
) => {
|
|
213
|
+
const service = getReadyService(ctx.cwd);
|
|
214
|
+
const text = service ? await executeRecover(service) : describeUnavailableService(ctx.cwd);
|
|
215
|
+
return makeTextResult(text);
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function makeTextResult(text: string) {
|
|
220
|
+
return {
|
|
221
|
+
content: [{ type: "text" as const, text }],
|
|
222
|
+
details: {},
|
|
223
|
+
};
|
|
224
|
+
}
|