decorated-pi 0.3.0 → 0.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 -34
- package/extensions/file-times.ts +60 -2
- package/extensions/guidance.ts +5 -3
- package/extensions/index.ts +2 -0
- package/extensions/io.ts +210 -29
- package/extensions/lsp/client.ts +181 -428
- package/extensions/lsp/env.ts +45 -12
- package/extensions/lsp/format.ts +102 -237
- package/extensions/lsp/index.ts +8 -11
- package/extensions/lsp/manager.ts +249 -0
- package/extensions/lsp/prompt.ts +3 -42
- package/extensions/lsp/protocol.ts +219 -0
- package/extensions/lsp/servers.ts +80 -160
- package/extensions/lsp/tools.ts +160 -553
- package/extensions/lsp/types.ts +42 -0
- package/extensions/mcp/builtin.ts +126 -0
- package/extensions/mcp/client.ts +106 -0
- package/extensions/mcp/index.ts +123 -0
- package/extensions/patch.ts +291 -73
- package/extensions/providers/ark-coding.ts +2 -0
- package/extensions/safety/detect.ts +20 -744
- package/extensions/safety/entropy.ts +226 -0
- package/extensions/safety/index.ts +1 -93
- package/extensions/safety/patterns.ts +155 -0
- package/extensions/safety/types.ts +50 -0
- package/extensions/settings.ts +8 -0
- package/extensions/slash.ts +161 -7
- package/extensions/smart-at.ts +5 -5
- package/extensions/subdir-agents.ts +43 -13
- package/package.json +2 -3
- package/tsconfig.json +16 -0
- package/extensions/lsp/server-manager.ts +0 -309
- package/extensions/lsp/trust.ts +0 -45
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LSP Server Manager — lifecycle, per-language instances, project-local trust
|
|
3
|
-
*
|
|
4
|
-
* Based on @spences10/pi-lsp by Scott Spence
|
|
5
|
-
* https://github.com/spences10/my-pi/tree/main/packages/pi-lsp (MIT License)
|
|
6
|
-
*/
|
|
7
|
-
import { resolve_project_trust } from "@spences10/pi-project-trust";
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
9
|
-
import { isAbsolute, resolve } from "node:path";
|
|
10
|
-
import {
|
|
11
|
-
file_path_to_uri,
|
|
12
|
-
LspClient,
|
|
13
|
-
type LspClientOptions,
|
|
14
|
-
} from "./client.js";
|
|
15
|
-
import {
|
|
16
|
-
LspToolError,
|
|
17
|
-
to_lsp_tool_error,
|
|
18
|
-
} from "./format.js";
|
|
19
|
-
import {
|
|
20
|
-
detect_language,
|
|
21
|
-
find_workspace_root,
|
|
22
|
-
get_server_config,
|
|
23
|
-
language_id_for_file,
|
|
24
|
-
type LanguageConfig,
|
|
25
|
-
} from "./servers.js";
|
|
26
|
-
import {
|
|
27
|
-
create_lsp_binary_trust_subject,
|
|
28
|
-
default_lsp_trust_store_path,
|
|
29
|
-
is_lsp_binary_trusted,
|
|
30
|
-
} from "./trust.js";
|
|
31
|
-
|
|
32
|
-
const LSP_PROJECT_BINARY_ENV = "MY_PI_LSP_PROJECT_BINARY";
|
|
33
|
-
|
|
34
|
-
class LspStartupCancelledError extends Error {
|
|
35
|
-
constructor(language: string, workspace_root: string) {
|
|
36
|
-
super(`Startup cancelled for ${language} LSP in ${workspace_root}`);
|
|
37
|
-
this.name = "LspStartupCancelledError";
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface FileState {
|
|
42
|
-
client: LspClient;
|
|
43
|
-
language: string;
|
|
44
|
-
workspace_root: string;
|
|
45
|
-
root_uri: string;
|
|
46
|
-
command: string;
|
|
47
|
-
install_hint: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface ResolvedFileState {
|
|
51
|
-
abs: string;
|
|
52
|
-
uri: string;
|
|
53
|
-
state: FileState;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface Startup {
|
|
57
|
-
cancelled: boolean;
|
|
58
|
-
promise: Promise<FileState | undefined>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface LspServerManagerOptions {
|
|
62
|
-
cwd?: () => string;
|
|
63
|
-
create_client?: (options: LspClientOptions) => LspClient;
|
|
64
|
-
read_file?: (path: string) => Promise<string>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export class LspServerManager {
|
|
68
|
-
cwd: string;
|
|
69
|
-
clients_by_server = new Map<string, FileState>();
|
|
70
|
-
failed_servers = new Map<string, any>();
|
|
71
|
-
#create_client: (options: LspClientOptions) => LspClient;
|
|
72
|
-
#read_file: (path: string) => Promise<string>;
|
|
73
|
-
#starting_servers = new Map<string, Startup>();
|
|
74
|
-
|
|
75
|
-
constructor(options: LspServerManagerOptions = {}) {
|
|
76
|
-
this.cwd = options.cwd?.() ?? process.cwd();
|
|
77
|
-
this.#create_client =
|
|
78
|
-
options.create_client ??
|
|
79
|
-
((client_options) => new LspClient(client_options));
|
|
80
|
-
this.#read_file =
|
|
81
|
-
options.read_file ?? ((path) => readFile(path, "utf-8"));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
resolve_abs(file: string): string {
|
|
85
|
-
return isAbsolute(file) ? file : resolve(this.cwd, file);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async clear_language_state(language?: string): Promise<void> {
|
|
89
|
-
const states = language
|
|
90
|
-
? Array.from(this.clients_by_server.entries()).filter(
|
|
91
|
-
([, state]) => state.language === language
|
|
92
|
-
)
|
|
93
|
-
: Array.from(this.clients_by_server.entries());
|
|
94
|
-
|
|
95
|
-
const starting = language
|
|
96
|
-
? Array.from(this.#starting_servers.entries()).filter(([key]) =>
|
|
97
|
-
key.startsWith(`${language}\u0000`)
|
|
98
|
-
)
|
|
99
|
-
: Array.from(this.#starting_servers.entries());
|
|
100
|
-
|
|
101
|
-
for (const [key, startup] of starting) {
|
|
102
|
-
startup.cancelled = true;
|
|
103
|
-
this.#starting_servers.delete(key);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
await Promise.allSettled(states.map(([, state]) => state.client.stop()));
|
|
107
|
-
|
|
108
|
-
for (const [key] of states) {
|
|
109
|
-
this.clients_by_server.delete(key);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!language) {
|
|
113
|
-
this.failed_servers.clear();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
for (const [key, failure] of this.failed_servers.entries()) {
|
|
118
|
-
if (failure.language === language) {
|
|
119
|
-
this.failed_servers.delete(key);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async resolve_file_state(
|
|
125
|
-
file: string,
|
|
126
|
-
ctx?: any
|
|
127
|
-
): Promise<
|
|
128
|
-
| { ok: true; result: ResolvedFileState }
|
|
129
|
-
| { ok: false; error: any }
|
|
130
|
-
> {
|
|
131
|
-
const abs = this.resolve_abs(file);
|
|
132
|
-
try {
|
|
133
|
-
const result = await this.#get_file_state(abs, ctx);
|
|
134
|
-
if (!result) {
|
|
135
|
-
return {
|
|
136
|
-
ok: false,
|
|
137
|
-
error: {
|
|
138
|
-
kind: "unsupported_language",
|
|
139
|
-
file: abs,
|
|
140
|
-
message: `No language server configured for ${abs}`,
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
return { ok: true, result };
|
|
145
|
-
} catch (error) {
|
|
146
|
-
if (error instanceof LspToolError) {
|
|
147
|
-
return { ok: false, error: error.details };
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
ok: false,
|
|
151
|
-
error: {
|
|
152
|
-
kind: "tool_execution_failed",
|
|
153
|
-
file: abs,
|
|
154
|
-
message: error instanceof Error ? error.message : String(error),
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async #get_file_state(
|
|
161
|
-
file: string,
|
|
162
|
-
ctx?: any
|
|
163
|
-
): Promise<ResolvedFileState | undefined> {
|
|
164
|
-
const abs = this.resolve_abs(file);
|
|
165
|
-
const state = await this.#get_or_start_client(abs, ctx);
|
|
166
|
-
if (!state) return undefined;
|
|
167
|
-
const uri = await this.#open_file(state, abs);
|
|
168
|
-
return { abs, uri, state };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async #get_or_start_client(
|
|
172
|
-
file_path: string,
|
|
173
|
-
ctx?: any
|
|
174
|
-
): Promise<FileState | undefined> {
|
|
175
|
-
const language = detect_language(file_path);
|
|
176
|
-
if (!language) return undefined;
|
|
177
|
-
|
|
178
|
-
const workspace_root = find_workspace_root(file_path, this.cwd);
|
|
179
|
-
const key = `${language}\u0000${workspace_root}`;
|
|
180
|
-
|
|
181
|
-
const existing = this.clients_by_server.get(key);
|
|
182
|
-
if (existing) return existing;
|
|
183
|
-
|
|
184
|
-
const failed = this.failed_servers.get(key);
|
|
185
|
-
if (failed) {
|
|
186
|
-
throw new LspToolError(failed);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const in_flight = this.#starting_servers.get(key);
|
|
190
|
-
if (in_flight) return in_flight.promise;
|
|
191
|
-
|
|
192
|
-
let server_config = get_server_config(language, workspace_root);
|
|
193
|
-
if (!server_config) return undefined;
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
server_config.is_project_local &&
|
|
197
|
-
!(await should_use_project_lsp_binary(server_config, ctx))
|
|
198
|
-
) {
|
|
199
|
-
server_config = get_server_config(language, "/");
|
|
200
|
-
if (!server_config) return undefined;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const root_uri = file_path_to_uri(workspace_root);
|
|
204
|
-
|
|
205
|
-
const startup: Startup = {
|
|
206
|
-
cancelled: false,
|
|
207
|
-
promise: Promise.resolve(undefined),
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const start_promise = (async () => {
|
|
211
|
-
const client = this.#create_client({
|
|
212
|
-
command: server_config!.command,
|
|
213
|
-
args: server_config!.args,
|
|
214
|
-
root_uri,
|
|
215
|
-
language_id_for_uri: (uri) => language_id_for_file(uri),
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
await client.start();
|
|
220
|
-
} catch (error) {
|
|
221
|
-
if (startup.cancelled) {
|
|
222
|
-
throw new LspStartupCancelledError(language, workspace_root);
|
|
223
|
-
}
|
|
224
|
-
const failure = to_lsp_tool_error(
|
|
225
|
-
file_path,
|
|
226
|
-
language,
|
|
227
|
-
workspace_root,
|
|
228
|
-
server_config!.command,
|
|
229
|
-
server_config!.install_hint,
|
|
230
|
-
error
|
|
231
|
-
);
|
|
232
|
-
this.failed_servers.set(key, failure);
|
|
233
|
-
throw new LspToolError(failure);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (startup.cancelled) {
|
|
237
|
-
await Promise.allSettled([client.stop()]);
|
|
238
|
-
throw new LspStartupCancelledError(language, workspace_root);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const state: FileState = {
|
|
242
|
-
client,
|
|
243
|
-
language,
|
|
244
|
-
workspace_root,
|
|
245
|
-
root_uri,
|
|
246
|
-
command: server_config!.command,
|
|
247
|
-
install_hint: server_config!.install_hint,
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
this.clients_by_server.set(key, state);
|
|
251
|
-
this.failed_servers.delete(key);
|
|
252
|
-
return state;
|
|
253
|
-
})();
|
|
254
|
-
|
|
255
|
-
startup.promise = start_promise;
|
|
256
|
-
this.#starting_servers.set(key, startup);
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
return await start_promise;
|
|
260
|
-
} finally {
|
|
261
|
-
if (this.#starting_servers.get(key) === startup) {
|
|
262
|
-
this.#starting_servers.delete(key);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async #open_file(state: FileState, abs_path: string): Promise<string> {
|
|
268
|
-
const text = await this.#read_file(abs_path);
|
|
269
|
-
const uri = file_path_to_uri(abs_path);
|
|
270
|
-
await state.client.ensure_document_open(uri, text);
|
|
271
|
-
return uri;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function should_use_project_lsp_binary(
|
|
276
|
-
server_config: LanguageConfig,
|
|
277
|
-
ctx?: any
|
|
278
|
-
): Promise<boolean> {
|
|
279
|
-
if (!server_config.is_project_local) return true;
|
|
280
|
-
|
|
281
|
-
if (is_lsp_binary_trusted(server_config.command)) return true;
|
|
282
|
-
|
|
283
|
-
const subject = {
|
|
284
|
-
...create_lsp_binary_trust_subject(server_config.command),
|
|
285
|
-
prompt_title:
|
|
286
|
-
"Project-local language server binaries can execute code.\nTrust this LSP binary?",
|
|
287
|
-
summary_lines: [
|
|
288
|
-
`Language: ${server_config.language}`,
|
|
289
|
-
`Binary: ${server_config.command}`,
|
|
290
|
-
],
|
|
291
|
-
headless_warning: `Skipping untrusted project-local LSP binary: ${server_config.command}. Set ${LSP_PROJECT_BINARY_ENV}=allow to enable it for this run.`,
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const decision = await resolve_project_trust(subject, {
|
|
295
|
-
env: process.env,
|
|
296
|
-
has_ui: ctx?.hasUI,
|
|
297
|
-
select: ctx?.hasUI
|
|
298
|
-
? async (message: string, choices: any) =>
|
|
299
|
-
(await ctx.ui.select(message, choices)) ?? ""
|
|
300
|
-
: undefined,
|
|
301
|
-
warn: console.warn,
|
|
302
|
-
trust_store_path: default_lsp_trust_store_path(),
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
return (
|
|
306
|
-
decision.action === "allow-once" ||
|
|
307
|
-
decision.action === "trust-persisted"
|
|
308
|
-
);
|
|
309
|
-
}
|
package/extensions/lsp/trust.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LSP Binary Trust Store — project-local binary trust management
|
|
3
|
-
*
|
|
4
|
-
* Based on @spences10/pi-lsp by Scott Spence
|
|
5
|
-
* https://github.com/spences10/my-pi/tree/main/packages/pi-lsp (MIT License)
|
|
6
|
-
*/
|
|
7
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import {
|
|
9
|
-
is_project_subject_trusted,
|
|
10
|
-
read_project_trust_store,
|
|
11
|
-
trust_project_subject,
|
|
12
|
-
} from "@spences10/pi-project-trust";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
|
|
15
|
-
const LSP_PROJECT_BINARY_ENV = "MY_PI_LSP_PROJECT_BINARY";
|
|
16
|
-
|
|
17
|
-
export function default_lsp_trust_store_path(): string {
|
|
18
|
-
return join(getAgentDir(), "trusted-lsp-binaries.json");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function create_lsp_binary_trust_subject(binary_path: string) {
|
|
22
|
-
return {
|
|
23
|
-
kind: "lsp-binary" as const,
|
|
24
|
-
id: binary_path,
|
|
25
|
-
store_key: binary_path,
|
|
26
|
-
env_key: LSP_PROJECT_BINARY_ENV,
|
|
27
|
-
prompt_title: "Trust project-local LSP binary?",
|
|
28
|
-
fallback: "global" as const,
|
|
29
|
-
choices: {
|
|
30
|
-
allow_once: "Allow once for this session",
|
|
31
|
-
trust: "Trust this binary path",
|
|
32
|
-
skip: "Use global PATH binary instead",
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function is_lsp_binary_trusted(
|
|
38
|
-
binary_path: string,
|
|
39
|
-
trust_store_path: string = default_lsp_trust_store_path()
|
|
40
|
-
): boolean {
|
|
41
|
-
const subject = create_lsp_binary_trust_subject(binary_path);
|
|
42
|
-
if (is_project_subject_trusted(subject, trust_store_path)) return true;
|
|
43
|
-
const entry = read_project_trust_store(trust_store_path)[binary_path];
|
|
44
|
-
return entry?.binary_path === binary_path;
|
|
45
|
-
}
|