ashlrcode 1.0.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/LICENSE +21 -0
- package/README.md +295 -0
- package/package.json +46 -0
- package/src/__tests__/branded-types.test.ts +47 -0
- package/src/__tests__/context.test.ts +163 -0
- package/src/__tests__/cost-tracker.test.ts +274 -0
- package/src/__tests__/cron.test.ts +197 -0
- package/src/__tests__/dream.test.ts +204 -0
- package/src/__tests__/error-handler.test.ts +192 -0
- package/src/__tests__/features.test.ts +69 -0
- package/src/__tests__/file-history.test.ts +177 -0
- package/src/__tests__/hooks.test.ts +145 -0
- package/src/__tests__/keybindings.test.ts +159 -0
- package/src/__tests__/model-patches.test.ts +82 -0
- package/src/__tests__/permissions-rules.test.ts +121 -0
- package/src/__tests__/permissions.test.ts +108 -0
- package/src/__tests__/project-config.test.ts +63 -0
- package/src/__tests__/retry.test.ts +321 -0
- package/src/__tests__/router.test.ts +158 -0
- package/src/__tests__/session-compact.test.ts +191 -0
- package/src/__tests__/session.test.ts +145 -0
- package/src/__tests__/skill-registry.test.ts +130 -0
- package/src/__tests__/speculation.test.ts +196 -0
- package/src/__tests__/tasks-v2.test.ts +267 -0
- package/src/__tests__/telemetry.test.ts +149 -0
- package/src/__tests__/tool-executor.test.ts +141 -0
- package/src/__tests__/tool-registry.test.ts +166 -0
- package/src/__tests__/undercover.test.ts +93 -0
- package/src/__tests__/workflow.test.ts +195 -0
- package/src/agent/async-context.ts +64 -0
- package/src/agent/context.ts +245 -0
- package/src/agent/cron.ts +189 -0
- package/src/agent/dream.ts +165 -0
- package/src/agent/error-handler.ts +108 -0
- package/src/agent/ipc.ts +256 -0
- package/src/agent/kairos.ts +207 -0
- package/src/agent/loop.ts +314 -0
- package/src/agent/model-patches.ts +68 -0
- package/src/agent/speculation.ts +219 -0
- package/src/agent/sub-agent.ts +125 -0
- package/src/agent/system-prompt.ts +231 -0
- package/src/agent/team.ts +220 -0
- package/src/agent/tool-executor.ts +162 -0
- package/src/agent/workflow.ts +189 -0
- package/src/agent/worktree-manager.ts +86 -0
- package/src/autopilot/queue.ts +186 -0
- package/src/autopilot/scanner.ts +245 -0
- package/src/autopilot/types.ts +58 -0
- package/src/bridge/bridge-client.ts +57 -0
- package/src/bridge/bridge-server.ts +81 -0
- package/src/cli.ts +1120 -0
- package/src/config/features.ts +51 -0
- package/src/config/git.ts +137 -0
- package/src/config/hooks.ts +201 -0
- package/src/config/permissions.ts +251 -0
- package/src/config/project-config.ts +63 -0
- package/src/config/remote-settings.ts +163 -0
- package/src/config/settings-sync.ts +170 -0
- package/src/config/settings.ts +113 -0
- package/src/config/undercover.ts +76 -0
- package/src/config/upgrade-notice.ts +65 -0
- package/src/mcp/client.ts +197 -0
- package/src/mcp/manager.ts +125 -0
- package/src/mcp/oauth.ts +252 -0
- package/src/mcp/types.ts +61 -0
- package/src/persistence/memory.ts +129 -0
- package/src/persistence/session.ts +289 -0
- package/src/planning/plan-mode.ts +128 -0
- package/src/planning/plan-tools.ts +138 -0
- package/src/providers/anthropic.ts +177 -0
- package/src/providers/cost-tracker.ts +184 -0
- package/src/providers/retry.ts +264 -0
- package/src/providers/router.ts +159 -0
- package/src/providers/types.ts +79 -0
- package/src/providers/xai.ts +217 -0
- package/src/repl.tsx +1384 -0
- package/src/setup.ts +119 -0
- package/src/skills/loader.ts +78 -0
- package/src/skills/registry.ts +78 -0
- package/src/skills/types.ts +11 -0
- package/src/state/file-history.ts +264 -0
- package/src/telemetry/event-log.ts +116 -0
- package/src/tools/agent.ts +133 -0
- package/src/tools/ask-user.ts +229 -0
- package/src/tools/bash.ts +146 -0
- package/src/tools/config.ts +147 -0
- package/src/tools/diff.ts +137 -0
- package/src/tools/file-edit.ts +123 -0
- package/src/tools/file-read.ts +82 -0
- package/src/tools/file-write.ts +82 -0
- package/src/tools/glob.ts +76 -0
- package/src/tools/grep.ts +187 -0
- package/src/tools/ls.ts +77 -0
- package/src/tools/lsp.ts +375 -0
- package/src/tools/mcp-resources.ts +83 -0
- package/src/tools/mcp-tool.ts +47 -0
- package/src/tools/memory.ts +148 -0
- package/src/tools/notebook-edit.ts +133 -0
- package/src/tools/peers.ts +113 -0
- package/src/tools/powershell.ts +83 -0
- package/src/tools/registry.ts +114 -0
- package/src/tools/send-message.ts +75 -0
- package/src/tools/sleep.ts +50 -0
- package/src/tools/snip.ts +143 -0
- package/src/tools/tasks.ts +349 -0
- package/src/tools/team.ts +309 -0
- package/src/tools/todo-write.ts +93 -0
- package/src/tools/tool-search.ts +83 -0
- package/src/tools/types.ts +52 -0
- package/src/tools/web-browser.ts +263 -0
- package/src/tools/web-fetch.ts +118 -0
- package/src/tools/web-search.ts +107 -0
- package/src/tools/workflow.ts +188 -0
- package/src/tools/worktree.ts +143 -0
- package/src/types/branded.ts +22 -0
- package/src/ui/App.tsx +184 -0
- package/src/ui/BuddyPanel.tsx +52 -0
- package/src/ui/PermissionPrompt.tsx +29 -0
- package/src/ui/banner.ts +217 -0
- package/src/ui/buddy-ai.ts +108 -0
- package/src/ui/buddy.ts +466 -0
- package/src/ui/context-bar.ts +60 -0
- package/src/ui/effort.ts +65 -0
- package/src/ui/keybindings.ts +143 -0
- package/src/ui/markdown.ts +271 -0
- package/src/ui/message-renderer.ts +73 -0
- package/src/ui/mode.ts +80 -0
- package/src/ui/notifications.ts +57 -0
- package/src/ui/speech-bubble.ts +95 -0
- package/src/ui/spinner.ts +116 -0
- package/src/ui/theme.ts +98 -0
- package/src/version.ts +5 -0
- package/src/voice/voice-mode.ts +169 -0
package/src/tools/ls.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LS tool — directory listing without Bash.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readdir, stat } from "fs/promises";
|
|
6
|
+
import { resolve, join } from "path";
|
|
7
|
+
import type { Tool, ToolContext } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export const lsTool: Tool = {
|
|
10
|
+
name: "LS",
|
|
11
|
+
|
|
12
|
+
prompt() {
|
|
13
|
+
return "List files and directories in a given path. Returns names with type indicators (/ for directories). Lighter than Bash ls.";
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
inputSchema() {
|
|
17
|
+
return {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
path: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Directory path to list (defaults to cwd)",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: [],
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
isReadOnly() {
|
|
30
|
+
return true;
|
|
31
|
+
},
|
|
32
|
+
isDestructive() {
|
|
33
|
+
return false;
|
|
34
|
+
},
|
|
35
|
+
isConcurrencySafe() {
|
|
36
|
+
return true;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
validateInput() {
|
|
40
|
+
return null;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async call(input, context) {
|
|
44
|
+
const dirPath = resolve(context.cwd, (input.path as string) ?? ".");
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const entries = await readdir(dirPath);
|
|
48
|
+
const details: string[] = [];
|
|
49
|
+
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (entry.startsWith(".")) continue; // Skip hidden files
|
|
52
|
+
try {
|
|
53
|
+
const s = await stat(join(dirPath, entry));
|
|
54
|
+
const indicator = s.isDirectory() ? "/" : "";
|
|
55
|
+
const size = s.isDirectory() ? "" : ` (${formatSize(s.size)})`;
|
|
56
|
+
details.push(`${entry}${indicator}${size}`);
|
|
57
|
+
} catch {
|
|
58
|
+
details.push(entry);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (details.length === 0) {
|
|
63
|
+
return `Empty directory: ${dirPath}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return details.join("\n");
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return `Error listing ${dirPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function formatSize(bytes: number): string {
|
|
74
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
75
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
76
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
77
|
+
}
|
package/src/tools/lsp.ts
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Tool — Language Server Protocol integration.
|
|
3
|
+
*
|
|
4
|
+
* Provides go-to-definition, find-references, and hover info by
|
|
5
|
+
* communicating with language servers (TypeScript, Python, etc.)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import type { Tool, ToolContext } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
// LSP server configs per language
|
|
13
|
+
interface LSPServerConfig {
|
|
14
|
+
command: string[];
|
|
15
|
+
initOptions?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SERVER_CONFIGS: Record<string, LSPServerConfig> = {
|
|
19
|
+
typescript: { command: ["npx", "typescript-language-server", "--stdio"] },
|
|
20
|
+
javascript: { command: ["npx", "typescript-language-server", "--stdio"] },
|
|
21
|
+
python: { command: ["pylsp"] },
|
|
22
|
+
rust: { command: ["rust-analyzer"] },
|
|
23
|
+
go: { command: ["gopls", "serve"] },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Detect language from file extension
|
|
27
|
+
function detectLanguage(filePath: string): string | null {
|
|
28
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
29
|
+
const langMap: Record<string, string> = {
|
|
30
|
+
ts: "typescript",
|
|
31
|
+
tsx: "typescript",
|
|
32
|
+
js: "javascript",
|
|
33
|
+
jsx: "javascript",
|
|
34
|
+
py: "python",
|
|
35
|
+
rs: "rust",
|
|
36
|
+
go: "go",
|
|
37
|
+
};
|
|
38
|
+
return langMap[ext ?? ""] ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Simple LSP client — sends requests via JSON-RPC over stdio.
|
|
43
|
+
* This is a lightweight implementation for basic operations.
|
|
44
|
+
*/
|
|
45
|
+
class SimpleLSPClient {
|
|
46
|
+
// Use `any` for the subprocess — Bun's Subprocess type has complex
|
|
47
|
+
// conditional generics that don't resolve cleanly for stdin/stdout.
|
|
48
|
+
private proc: any = null;
|
|
49
|
+
private requestId = 0;
|
|
50
|
+
private pendingRequests = new Map<
|
|
51
|
+
number,
|
|
52
|
+
{ resolve: (r: unknown) => void; reject: (e: Error) => void }
|
|
53
|
+
>();
|
|
54
|
+
private buffer = "";
|
|
55
|
+
|
|
56
|
+
async start(config: LSPServerConfig, cwd: string): Promise<void> {
|
|
57
|
+
this.proc = Bun.spawn(config.command, {
|
|
58
|
+
cwd,
|
|
59
|
+
stdin: "pipe",
|
|
60
|
+
stdout: "pipe",
|
|
61
|
+
stderr: "pipe",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Read responses in background
|
|
65
|
+
this.readLoop();
|
|
66
|
+
|
|
67
|
+
// Initialize the language server
|
|
68
|
+
await this.request("initialize", {
|
|
69
|
+
processId: process.pid,
|
|
70
|
+
rootUri: `file://${cwd}`,
|
|
71
|
+
capabilities: {},
|
|
72
|
+
...config.initOptions,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.notify("initialized", {});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async readLoop(): Promise<void> {
|
|
79
|
+
if (!this.proc) return;
|
|
80
|
+
const reader = this.proc.stdout.getReader();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
const { done, value } = await reader.read();
|
|
85
|
+
if (done) break;
|
|
86
|
+
this.buffer += Buffer.from(value).toString("utf-8");
|
|
87
|
+
this.processBuffer();
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Stream closed — expected on shutdown
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private processBuffer(): void {
|
|
95
|
+
while (true) {
|
|
96
|
+
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
97
|
+
if (headerEnd === -1) break;
|
|
98
|
+
|
|
99
|
+
const header = this.buffer.slice(0, headerEnd);
|
|
100
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
101
|
+
if (!match) {
|
|
102
|
+
this.buffer = this.buffer.slice(headerEnd + 4);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const contentLength = parseInt(match[1]!, 10);
|
|
107
|
+
const bodyStart = headerEnd + 4;
|
|
108
|
+
|
|
109
|
+
if (this.buffer.length < bodyStart + contentLength) break;
|
|
110
|
+
|
|
111
|
+
const body = this.buffer.slice(bodyStart, bodyStart + contentLength);
|
|
112
|
+
this.buffer = this.buffer.slice(bodyStart + contentLength);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const msg = JSON.parse(body) as {
|
|
116
|
+
id?: number;
|
|
117
|
+
error?: { message: string };
|
|
118
|
+
result?: unknown;
|
|
119
|
+
};
|
|
120
|
+
if (msg.id !== undefined && this.pendingRequests.has(msg.id)) {
|
|
121
|
+
const pending = this.pendingRequests.get(msg.id)!;
|
|
122
|
+
this.pendingRequests.delete(msg.id);
|
|
123
|
+
if (msg.error) pending.reject(new Error(msg.error.message));
|
|
124
|
+
else pending.resolve(msg.result);
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// Malformed JSON — skip
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async request(method: string, params: unknown): Promise<unknown> {
|
|
133
|
+
const id = ++this.requestId;
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
136
|
+
this.send({ jsonrpc: "2.0", id, method, params });
|
|
137
|
+
|
|
138
|
+
// Timeout after 10s
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
if (this.pendingRequests.has(id)) {
|
|
141
|
+
this.pendingRequests.delete(id);
|
|
142
|
+
reject(new Error(`LSP request timed out: ${method}`));
|
|
143
|
+
}
|
|
144
|
+
}, 10_000);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
notify(method: string, params: unknown): void {
|
|
149
|
+
this.send({ jsonrpc: "2.0", method, params });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private send(msg: Record<string, unknown>): void {
|
|
153
|
+
if (!this.proc) return;
|
|
154
|
+
const json = JSON.stringify(msg);
|
|
155
|
+
const header = `Content-Length: ${Buffer.byteLength(json, "utf-8")}\r\n\r\n`;
|
|
156
|
+
const buf = Buffer.concat([Buffer.from(header), Buffer.from(json, "utf-8")]);
|
|
157
|
+
this.proc.stdin.write(buf);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async stop(): Promise<void> {
|
|
161
|
+
if (!this.proc) return;
|
|
162
|
+
// Reject all pending requests before shutting down
|
|
163
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
164
|
+
pending.reject(new Error("LSP client stopped"));
|
|
165
|
+
}
|
|
166
|
+
this.pendingRequests.clear();
|
|
167
|
+
try {
|
|
168
|
+
await this.request("shutdown", null);
|
|
169
|
+
this.notify("exit", null);
|
|
170
|
+
this.proc.stdin.end();
|
|
171
|
+
} catch {
|
|
172
|
+
// Best-effort shutdown
|
|
173
|
+
}
|
|
174
|
+
try { this.proc.kill(); } catch {}
|
|
175
|
+
this.proc = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Cache active LSP clients by language:cwd
|
|
180
|
+
const clients = new Map<string, SimpleLSPClient>();
|
|
181
|
+
const inFlight = new Map<string, Promise<SimpleLSPClient>>();
|
|
182
|
+
|
|
183
|
+
async function getClient(
|
|
184
|
+
language: string,
|
|
185
|
+
cwd: string,
|
|
186
|
+
): Promise<SimpleLSPClient> {
|
|
187
|
+
const key = `${language}:${cwd}`;
|
|
188
|
+
if (clients.has(key)) return clients.get(key)!;
|
|
189
|
+
if (inFlight.has(key)) return inFlight.get(key)!;
|
|
190
|
+
|
|
191
|
+
const config = SERVER_CONFIGS[language];
|
|
192
|
+
if (!config) throw new Error(`No LSP server configured for ${language}`);
|
|
193
|
+
|
|
194
|
+
const p = (async () => {
|
|
195
|
+
const client = new SimpleLSPClient();
|
|
196
|
+
await client.start(config, cwd);
|
|
197
|
+
clients.set(key, client);
|
|
198
|
+
inFlight.delete(key);
|
|
199
|
+
return client;
|
|
200
|
+
})();
|
|
201
|
+
inFlight.set(key, p);
|
|
202
|
+
return p;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// LSP location result shapes
|
|
206
|
+
interface LSPLocation {
|
|
207
|
+
uri?: string;
|
|
208
|
+
targetUri?: string;
|
|
209
|
+
range?: { start?: { line?: number } };
|
|
210
|
+
targetRange?: { start?: { line?: number } };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
interface LSPHoverResult {
|
|
214
|
+
contents?:
|
|
215
|
+
| string
|
|
216
|
+
| { value?: string }
|
|
217
|
+
| Array<string | { value?: string }>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function formatLocations(result: unknown): string {
|
|
221
|
+
if (!result) return "No results found";
|
|
222
|
+
const locations = (
|
|
223
|
+
Array.isArray(result) ? result : [result]
|
|
224
|
+
) as LSPLocation[];
|
|
225
|
+
return locations
|
|
226
|
+
.map((loc) => {
|
|
227
|
+
const path =
|
|
228
|
+
(loc.uri ?? loc.targetUri)?.replace("file://", "") ?? "unknown";
|
|
229
|
+
const range = loc.range ?? loc.targetRange;
|
|
230
|
+
const line = (range?.start?.line ?? 0) + 1;
|
|
231
|
+
return `${path}:${line}`;
|
|
232
|
+
})
|
|
233
|
+
.join("\n");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function formatHover(result: unknown): string {
|
|
237
|
+
const hover = result as LSPHoverResult | null;
|
|
238
|
+
if (!hover?.contents) return "No hover info available";
|
|
239
|
+
const contents = hover.contents;
|
|
240
|
+
if (typeof contents === "string") return contents;
|
|
241
|
+
if ("value" in contents && contents.value) return contents.value;
|
|
242
|
+
if (Array.isArray(contents))
|
|
243
|
+
return contents
|
|
244
|
+
.map((c) => (typeof c === "string" ? c : c.value ?? ""))
|
|
245
|
+
.join("\n");
|
|
246
|
+
return JSON.stringify(contents);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export const lspTool: Tool = {
|
|
250
|
+
name: "LSP",
|
|
251
|
+
|
|
252
|
+
prompt() {
|
|
253
|
+
return `Language Server Protocol integration. Use for:
|
|
254
|
+
- go-to-definition: Find where a symbol is defined
|
|
255
|
+
- find-references: Find all usages of a symbol
|
|
256
|
+
- hover: Get type info and documentation for a symbol
|
|
257
|
+
|
|
258
|
+
Supported languages: TypeScript, JavaScript, Python, Rust, Go.
|
|
259
|
+
Requires the language server to be installed (e.g., typescript-language-server for TS).`;
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
inputSchema() {
|
|
263
|
+
return {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties: {
|
|
266
|
+
action: {
|
|
267
|
+
type: "string",
|
|
268
|
+
enum: ["definition", "references", "hover"],
|
|
269
|
+
description: "LSP operation to perform",
|
|
270
|
+
},
|
|
271
|
+
file: {
|
|
272
|
+
type: "string",
|
|
273
|
+
description: "File path containing the symbol",
|
|
274
|
+
},
|
|
275
|
+
line: {
|
|
276
|
+
type: "number",
|
|
277
|
+
description: "Line number (1-indexed)",
|
|
278
|
+
},
|
|
279
|
+
column: {
|
|
280
|
+
type: "number",
|
|
281
|
+
description: "Column number (1-indexed)",
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
required: ["action", "file", "line", "column"],
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
isReadOnly() {
|
|
289
|
+
return true;
|
|
290
|
+
},
|
|
291
|
+
isDestructive() {
|
|
292
|
+
return false;
|
|
293
|
+
},
|
|
294
|
+
isConcurrencySafe() {
|
|
295
|
+
return true;
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
validateInput(input) {
|
|
299
|
+
if (!input.action) return "action is required";
|
|
300
|
+
if (!["definition", "references", "hover"].includes(input.action as string))
|
|
301
|
+
return "action must be one of: definition, references, hover";
|
|
302
|
+
if (!input.file) return "file is required";
|
|
303
|
+
if (!input.line || !input.column) return "line and column are required";
|
|
304
|
+
return null;
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
async call(input, context) {
|
|
308
|
+
const file = input.file as string;
|
|
309
|
+
const line = (input.line as number) - 1; // LSP uses 0-indexed positions
|
|
310
|
+
const column = (input.column as number) - 1;
|
|
311
|
+
const action = input.action as string;
|
|
312
|
+
|
|
313
|
+
const fullPath = resolve(context.cwd, file);
|
|
314
|
+
const language = detectLanguage(fullPath);
|
|
315
|
+
if (!language)
|
|
316
|
+
return `Unsupported file type: ${file}. Supported: .ts, .tsx, .js, .jsx, .py, .rs, .go`;
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const client = await getClient(language, context.cwd);
|
|
320
|
+
const uri = `file://${fullPath}`;
|
|
321
|
+
const position = { line, character: column };
|
|
322
|
+
|
|
323
|
+
// Open the document so the server knows about it
|
|
324
|
+
const content = await readFile(fullPath, "utf-8");
|
|
325
|
+
client.notify("textDocument/didOpen", {
|
|
326
|
+
textDocument: {
|
|
327
|
+
uri,
|
|
328
|
+
languageId: language,
|
|
329
|
+
version: 1,
|
|
330
|
+
text: content,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
switch (action) {
|
|
335
|
+
case "definition": {
|
|
336
|
+
const result = await client.request("textDocument/definition", {
|
|
337
|
+
textDocument: { uri },
|
|
338
|
+
position,
|
|
339
|
+
});
|
|
340
|
+
return formatLocations(result);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
case "references": {
|
|
344
|
+
const result = await client.request("textDocument/references", {
|
|
345
|
+
textDocument: { uri },
|
|
346
|
+
position,
|
|
347
|
+
context: { includeDeclaration: true },
|
|
348
|
+
});
|
|
349
|
+
return formatLocations(result);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
case "hover": {
|
|
353
|
+
const result = await client.request("textDocument/hover", {
|
|
354
|
+
textDocument: { uri },
|
|
355
|
+
position,
|
|
356
|
+
});
|
|
357
|
+
return formatHover(result);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
default:
|
|
361
|
+
return `Unknown action: ${action}`;
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
return `LSP error: ${err instanceof Error ? err.message : String(err)}`;
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
/** Shut down all cached LSP clients. Call on process exit. */
|
|
370
|
+
export async function shutdownLSP(): Promise<void> {
|
|
371
|
+
for (const client of clients.values()) {
|
|
372
|
+
await client.stop().catch(() => {});
|
|
373
|
+
}
|
|
374
|
+
clients.clear();
|
|
375
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListMcpResources tool — enumerate tools and resources from connected MCP servers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Tool, ToolContext } from "./types.ts";
|
|
6
|
+
import type { MCPManager } from "../mcp/manager.ts";
|
|
7
|
+
|
|
8
|
+
// Module-level MCP manager reference (set during init)
|
|
9
|
+
let _mcpManager: MCPManager | null = null;
|
|
10
|
+
|
|
11
|
+
export function setMCPManager(manager: MCPManager): void {
|
|
12
|
+
_mcpManager = manager;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const listMcpResourcesTool: Tool = {
|
|
16
|
+
name: "ListMcpResources",
|
|
17
|
+
|
|
18
|
+
prompt() {
|
|
19
|
+
return "List available MCP (Model Context Protocol) resources and tools from connected servers.";
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
inputSchema() {
|
|
23
|
+
return {
|
|
24
|
+
type: "object" as const,
|
|
25
|
+
properties: {
|
|
26
|
+
server: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Optional: filter by server name",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
isReadOnly() {
|
|
35
|
+
return true;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
isDestructive() {
|
|
39
|
+
return false;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
isConcurrencySafe() {
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
validateInput() {
|
|
47
|
+
return null;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async call(input: Record<string, unknown>, _context: ToolContext): Promise<string> {
|
|
51
|
+
if (!_mcpManager) return "No MCP servers connected.";
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const serverNames = _mcpManager.getServerNames();
|
|
55
|
+
if (serverNames.length === 0) return "No MCP servers connected.";
|
|
56
|
+
|
|
57
|
+
const serverFilter = input.server as string | undefined;
|
|
58
|
+
const allTools = _mcpManager.getAllTools();
|
|
59
|
+
const lines: string[] = [];
|
|
60
|
+
|
|
61
|
+
for (const name of serverNames) {
|
|
62
|
+
if (serverFilter && !name.includes(serverFilter)) continue;
|
|
63
|
+
|
|
64
|
+
const serverTools = allTools.filter(t => t.serverName === name);
|
|
65
|
+
lines.push(`\n Server: ${name}`);
|
|
66
|
+
|
|
67
|
+
if (serverTools.length > 0) {
|
|
68
|
+
lines.push(` Tools (${serverTools.length}):`);
|
|
69
|
+
for (const { tool } of serverTools.slice(0, 10)) {
|
|
70
|
+
lines.push(` - ${tool.name}: ${(tool.description ?? "").slice(0, 60)}`);
|
|
71
|
+
}
|
|
72
|
+
if (serverTools.length > 10) {
|
|
73
|
+
lines.push(` ... and ${serverTools.length - 10} more`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return lines.join("\n") || "No resources found.";
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return `Error listing MCP resources: ${err instanceof Error ? err.message : String(err)}`;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool wrapper — wraps an MCP server tool as an AshlrCode Tool.
|
|
3
|
+
*
|
|
4
|
+
* Tool naming: mcp__<server>__<tool>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Tool, ToolContext } from "./types.ts";
|
|
8
|
+
import type { MCPManager } from "../mcp/manager.ts";
|
|
9
|
+
import type { MCPToolInfo } from "../mcp/types.ts";
|
|
10
|
+
|
|
11
|
+
export function createMCPTool(
|
|
12
|
+
serverName: string,
|
|
13
|
+
toolInfo: MCPToolInfo,
|
|
14
|
+
manager: MCPManager
|
|
15
|
+
): Tool {
|
|
16
|
+
const fullName = `mcp__${serverName}__${toolInfo.name}`;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
name: fullName,
|
|
20
|
+
|
|
21
|
+
prompt() {
|
|
22
|
+
return toolInfo.description ?? `MCP tool: ${toolInfo.name} from ${serverName}`;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
inputSchema() {
|
|
26
|
+
return toolInfo.inputSchema;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
isReadOnly() {
|
|
30
|
+
return false; // Can't know, default to cautious
|
|
31
|
+
},
|
|
32
|
+
isDestructive() {
|
|
33
|
+
return false;
|
|
34
|
+
},
|
|
35
|
+
isConcurrencySafe() {
|
|
36
|
+
return true; // MCP tools are independent
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
validateInput() {
|
|
40
|
+
return null; // Schema validation happens on the MCP server
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async call(input, _context) {
|
|
44
|
+
return await manager.callTool(serverName, toolInfo.name, input);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|