@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.2
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/CHANGELOG.md +316 -1
- package/package.json +86 -24
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +116 -30
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +123 -178
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -8
- package/src/commit/agentic/index.ts +22 -26
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/tools/git-file-diff.ts +3 -6
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +6 -9
- package/src/commit/agentic/tools/index.ts +6 -8
- package/src/commit/agentic/tools/propose-commit.ts +4 -7
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/split-commit.ts +4 -4
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/changelog/index.ts +5 -9
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +13 -16
- package/src/config/keybindings.ts +7 -6
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +98 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
- package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +4 -2
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
- package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +2 -2
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +125 -47
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/footer.ts +9 -29
- package/src/modes/components/hook-editor.ts +3 -3
- package/src/modes/components/hook-selector.ts +6 -1
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/session-observer-overlay.ts +472 -0
- package/src/modes/components/settings-defs.ts +24 -0
- package/src/modes/components/status-line.ts +15 -61
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +4 -2
- package/src/modes/controllers/event-controller.ts +59 -2
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +15 -8
- package/src/modes/controllers/selector-controller.ts +26 -0
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +278 -69
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/shared.ts +0 -42
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +4 -2
- package/src/modes/utils/keybinding-matchers.ts +9 -0
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +8 -1
- package/src/prompts/tools/chunk-edit.md +219 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +216 -165
- package/src/secrets/index.ts +1 -1
- package/src/secrets/obfuscator.ts +25 -17
- package/src/session/agent-session.ts +381 -286
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-manager.ts +15 -5
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +25 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +32 -4
- package/src/task/index.ts +91 -82
- package/src/task/template.ts +2 -2
- package/src/task/types.ts +25 -0
- package/src/task/worktree.ts +131 -149
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +3 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +130 -308
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +211 -146
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/external-editor.ts +11 -5
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +1400 -0
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/web/search/render.ts +6 -4
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -210
- package/src/commit/git/operations.ts +0 -54
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/gh-cli.ts +0 -125
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
package/src/lsp/index.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
-
import { logger, once, untilAborted } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { logger, once, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import type { BunFile } from "bun";
|
|
6
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
7
6
|
import { type Theme, theme } from "../modes/theme/theme";
|
|
8
7
|
import lspDescription from "../prompts/tools/lsp.md" with { type: "text" };
|
|
9
8
|
import type { ToolSession } from "../tools";
|
|
@@ -40,6 +39,7 @@ import {
|
|
|
40
39
|
type LspParams,
|
|
41
40
|
type LspToolDetails,
|
|
42
41
|
lspSchema,
|
|
42
|
+
type PublishedDiagnostics,
|
|
43
43
|
type ServerConfig,
|
|
44
44
|
type SymbolInformation,
|
|
45
45
|
type TextEdit,
|
|
@@ -71,14 +71,16 @@ import {
|
|
|
71
71
|
export type { LspServerStatus } from "./client";
|
|
72
72
|
export type { LspToolDetails } from "./types";
|
|
73
73
|
|
|
74
|
+
export interface LspStartupServerInfo {
|
|
75
|
+
name: string;
|
|
76
|
+
status: "connecting" | "ready" | "error";
|
|
77
|
+
fileTypes: string[];
|
|
78
|
+
error?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
/** Result from warming up LSP servers */
|
|
75
82
|
export interface LspWarmupResult {
|
|
76
|
-
servers: Array<{
|
|
77
|
-
name: string;
|
|
78
|
-
status: "ready" | "error";
|
|
79
|
-
fileTypes: string[];
|
|
80
|
-
error?: string;
|
|
81
|
-
}>;
|
|
83
|
+
servers: Array<LspStartupServerInfo & { status: "ready" | "error" }>;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/** Options for warming up LSP servers */
|
|
@@ -87,6 +89,15 @@ export interface LspWarmupOptions {
|
|
|
87
89
|
onConnecting?: (serverNames: string[]) => void;
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
export function discoverStartupLspServers(cwd: string): LspStartupServerInfo[] {
|
|
93
|
+
const config = loadConfig(cwd);
|
|
94
|
+
return getLspServers(config).map(([name, serverConfig]) => ({
|
|
95
|
+
name,
|
|
96
|
+
status: "connecting",
|
|
97
|
+
fileTypes: serverConfig.fileTypes,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
/**
|
|
91
102
|
* Warm up LSP servers for a directory by connecting to all detected servers.
|
|
92
103
|
* This should be called at startup to avoid cold-start delays.
|
|
@@ -312,22 +323,59 @@ async function reloadServer(client: LspClient, serverName: string, signal?: Abor
|
|
|
312
323
|
return output;
|
|
313
324
|
}
|
|
314
325
|
|
|
326
|
+
interface WaitForDiagnosticsOptions {
|
|
327
|
+
timeoutMs?: number;
|
|
328
|
+
signal?: AbortSignal;
|
|
329
|
+
minVersion?: number;
|
|
330
|
+
expectedDocumentVersion?: number;
|
|
331
|
+
allowUnversioned?: boolean;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function getAcceptedDiagnostics(
|
|
335
|
+
publishedDiagnostics: PublishedDiagnostics | undefined,
|
|
336
|
+
expectedDocumentVersion?: number,
|
|
337
|
+
allowUnversioned = true,
|
|
338
|
+
): Diagnostic[] | undefined {
|
|
339
|
+
if (!publishedDiagnostics) {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
if (expectedDocumentVersion === undefined) {
|
|
343
|
+
return publishedDiagnostics.diagnostics;
|
|
344
|
+
}
|
|
345
|
+
if (publishedDiagnostics.version === expectedDocumentVersion) {
|
|
346
|
+
return publishedDiagnostics.diagnostics;
|
|
347
|
+
}
|
|
348
|
+
if (allowUnversioned && publishedDiagnostics.version == null) {
|
|
349
|
+
return publishedDiagnostics.diagnostics;
|
|
350
|
+
}
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
|
|
315
354
|
async function waitForDiagnostics(
|
|
316
355
|
client: LspClient,
|
|
317
356
|
uri: string,
|
|
318
|
-
|
|
319
|
-
signal?: AbortSignal,
|
|
320
|
-
minVersion?: number,
|
|
357
|
+
options: WaitForDiagnosticsOptions = {},
|
|
321
358
|
): Promise<Diagnostic[]> {
|
|
359
|
+
const { timeoutMs = 3000, signal, minVersion, expectedDocumentVersion, allowUnversioned = true } = options;
|
|
322
360
|
const start = Date.now();
|
|
323
361
|
while (Date.now() - start < timeoutMs) {
|
|
324
362
|
throwIfAborted(signal);
|
|
325
|
-
const diagnostics = client.diagnostics.get(uri);
|
|
326
363
|
const versionOk = minVersion === undefined || client.diagnosticsVersion > minVersion;
|
|
327
|
-
|
|
364
|
+
const diagnostics = getAcceptedDiagnostics(
|
|
365
|
+
client.diagnostics.get(uri),
|
|
366
|
+
expectedDocumentVersion,
|
|
367
|
+
allowUnversioned,
|
|
368
|
+
);
|
|
369
|
+
if (diagnostics !== undefined && versionOk) {
|
|
370
|
+
return diagnostics;
|
|
371
|
+
}
|
|
328
372
|
await Bun.sleep(100);
|
|
329
373
|
}
|
|
330
|
-
|
|
374
|
+
const versionOk = minVersion === undefined || client.diagnosticsVersion > minVersion;
|
|
375
|
+
if (!versionOk) {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
return getAcceptedDiagnostics(client.diagnostics.get(uri), expectedDocumentVersion, allowUnversioned) ?? [];
|
|
331
379
|
}
|
|
332
380
|
|
|
333
381
|
/** Project type detection result */
|
|
@@ -426,8 +474,14 @@ export interface FileDiagnosticsResult {
|
|
|
426
474
|
formatter?: FileFormatResult;
|
|
427
475
|
}
|
|
428
476
|
|
|
429
|
-
|
|
430
|
-
|
|
477
|
+
type ServerVersionMap = Map<string, number>;
|
|
478
|
+
|
|
479
|
+
interface GetDiagnosticsForFileOptions {
|
|
480
|
+
signal?: AbortSignal;
|
|
481
|
+
minVersions?: ServerVersionMap;
|
|
482
|
+
expectedDocumentVersions?: ServerVersionMap;
|
|
483
|
+
allowUnversionedLspDiagnostics?: boolean;
|
|
484
|
+
}
|
|
431
485
|
|
|
432
486
|
/**
|
|
433
487
|
* Capture current diagnostic versions for all LSP servers.
|
|
@@ -436,7 +490,7 @@ type DiagnosticVersions = Map<string, number>;
|
|
|
436
490
|
async function captureDiagnosticVersions(
|
|
437
491
|
cwd: string,
|
|
438
492
|
servers: Array<[string, ServerConfig]>,
|
|
439
|
-
): Promise<
|
|
493
|
+
): Promise<ServerVersionMap> {
|
|
440
494
|
const versions = new Map<string, number>();
|
|
441
495
|
await Promise.allSettled(
|
|
442
496
|
servers.map(async ([serverName, serverConfig]) => {
|
|
@@ -448,6 +502,25 @@ async function captureDiagnosticVersions(
|
|
|
448
502
|
return versions;
|
|
449
503
|
}
|
|
450
504
|
|
|
505
|
+
async function captureOpenFileVersions(
|
|
506
|
+
absolutePath: string,
|
|
507
|
+
cwd: string,
|
|
508
|
+
servers: Array<[string, ServerConfig]>,
|
|
509
|
+
): Promise<ServerVersionMap> {
|
|
510
|
+
const uri = fileToUri(absolutePath);
|
|
511
|
+
const versions = new Map<string, number>();
|
|
512
|
+
await Promise.allSettled(
|
|
513
|
+
servers.map(async ([serverName, serverConfig]) => {
|
|
514
|
+
const client = await getOrCreateClient(serverConfig, cwd);
|
|
515
|
+
const version = client.openFiles.get(uri)?.version;
|
|
516
|
+
if (version !== undefined) {
|
|
517
|
+
versions.set(serverName, version);
|
|
518
|
+
}
|
|
519
|
+
}),
|
|
520
|
+
);
|
|
521
|
+
return versions;
|
|
522
|
+
}
|
|
523
|
+
|
|
451
524
|
/**
|
|
452
525
|
* Get diagnostics for a file using LSP or custom linter client.
|
|
453
526
|
*
|
|
@@ -461,9 +534,9 @@ async function getDiagnosticsForFile(
|
|
|
461
534
|
absolutePath: string,
|
|
462
535
|
cwd: string,
|
|
463
536
|
servers: Array<[string, ServerConfig]>,
|
|
464
|
-
|
|
465
|
-
minVersions?: DiagnosticVersions,
|
|
537
|
+
options: GetDiagnosticsForFileOptions = {},
|
|
466
538
|
): Promise<FileDiagnosticsResult | undefined> {
|
|
539
|
+
const { signal, minVersions, expectedDocumentVersions, allowUnversionedLspDiagnostics = true } = options;
|
|
467
540
|
if (servers.length === 0) {
|
|
468
541
|
return undefined;
|
|
469
542
|
}
|
|
@@ -489,7 +562,14 @@ async function getDiagnosticsForFile(
|
|
|
489
562
|
throwIfAborted(signal);
|
|
490
563
|
// Content already synced + didSave sent, wait for fresh diagnostics
|
|
491
564
|
const minVersion = minVersions?.get(serverName);
|
|
492
|
-
const
|
|
565
|
+
const expectedDocumentVersion = expectedDocumentVersions?.get(serverName);
|
|
566
|
+
const diagnostics = await waitForDiagnostics(client, uri, {
|
|
567
|
+
timeoutMs: 3000,
|
|
568
|
+
signal,
|
|
569
|
+
minVersion,
|
|
570
|
+
expectedDocumentVersion,
|
|
571
|
+
allowUnversioned: allowUnversionedLspDiagnostics,
|
|
572
|
+
});
|
|
493
573
|
return { serverName, diagnostics };
|
|
494
574
|
}),
|
|
495
575
|
);
|
|
@@ -622,8 +702,25 @@ export interface WritethroughOptions {
|
|
|
622
702
|
enableFormat?: boolean;
|
|
623
703
|
/** Whether to get LSP diagnostics after writing */
|
|
624
704
|
enableDiagnostics?: boolean;
|
|
705
|
+
/** Called when diagnostics arrive after the main timeout. */
|
|
706
|
+
onDeferredDiagnostics?: (diagnostics: FileDiagnosticsResult) => void;
|
|
707
|
+
/** Signal to cancel a pending deferred diagnostics fetch. */
|
|
708
|
+
deferredSignal?: AbortSignal;
|
|
625
709
|
}
|
|
626
710
|
|
|
711
|
+
/** Internal resolved form of {@link WritethroughOptions} that the writethrough machinery operates on. */
|
|
712
|
+
type ResolvedWritethroughOptions = {
|
|
713
|
+
enableFormat: boolean;
|
|
714
|
+
enableDiagnostics: boolean;
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
/** Per-file deferred LSP diagnostics wiring for {@link WritethroughCallback}. */
|
|
718
|
+
export type WritethroughDeferredHandle = {
|
|
719
|
+
onDeferredDiagnostics: (diagnostics: FileDiagnosticsResult) => void;
|
|
720
|
+
signal: AbortSignal;
|
|
721
|
+
finalize: (diagnostics: FileDiagnosticsResult | undefined) => void;
|
|
722
|
+
};
|
|
723
|
+
|
|
627
724
|
/** Callback type for the LSP writethrough */
|
|
628
725
|
export type WritethroughCallback = (
|
|
629
726
|
dst: string,
|
|
@@ -631,6 +728,7 @@ export type WritethroughCallback = (
|
|
|
631
728
|
signal?: AbortSignal,
|
|
632
729
|
file?: BunFile,
|
|
633
730
|
batch?: LspWritethroughBatchRequest,
|
|
731
|
+
getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
634
732
|
) => Promise<FileDiagnosticsResult | undefined>;
|
|
635
733
|
|
|
636
734
|
/** No-op writethrough callback */
|
|
@@ -639,6 +737,8 @@ export async function writethroughNoop(
|
|
|
639
737
|
content: string,
|
|
640
738
|
_signal?: AbortSignal,
|
|
641
739
|
file?: BunFile,
|
|
740
|
+
_batch?: LspWritethroughBatchRequest,
|
|
741
|
+
_getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
642
742
|
): Promise<FileDiagnosticsResult | undefined> {
|
|
643
743
|
if (file) {
|
|
644
744
|
await file.write(content);
|
|
@@ -661,12 +761,12 @@ interface LspWritethroughBatchRequest {
|
|
|
661
761
|
|
|
662
762
|
interface LspWritethroughBatchState {
|
|
663
763
|
entries: Map<string, PendingWritethrough>;
|
|
664
|
-
options:
|
|
764
|
+
options: ResolvedWritethroughOptions;
|
|
665
765
|
}
|
|
666
766
|
|
|
667
767
|
const writethroughBatches = new Map<string, LspWritethroughBatchState>();
|
|
668
768
|
|
|
669
|
-
function getOrCreateWritethroughBatch(id: string, options:
|
|
769
|
+
function getOrCreateWritethroughBatch(id: string, options: ResolvedWritethroughOptions): LspWritethroughBatchState {
|
|
670
770
|
const existing = writethroughBatches.get(id);
|
|
671
771
|
if (existing) {
|
|
672
772
|
existing.options.enableFormat ||= options.enableFormat;
|
|
@@ -717,7 +817,7 @@ function summarizeDiagnosticMessages(messages: string[]): { summary: string; err
|
|
|
717
817
|
|
|
718
818
|
function mergeDiagnostics(
|
|
719
819
|
results: Array<FileDiagnosticsResult | undefined>,
|
|
720
|
-
options:
|
|
820
|
+
options: ResolvedWritethroughOptions,
|
|
721
821
|
): FileDiagnosticsResult | undefined {
|
|
722
822
|
const messages: string[] = [];
|
|
723
823
|
const servers = new Set<string>();
|
|
@@ -771,13 +871,41 @@ function mergeDiagnostics(
|
|
|
771
871
|
};
|
|
772
872
|
}
|
|
773
873
|
|
|
874
|
+
async function scheduleDeferredDiagnosticsFetch(args: {
|
|
875
|
+
dst: string;
|
|
876
|
+
cwd: string;
|
|
877
|
+
servers: Array<[string, ServerConfig]>;
|
|
878
|
+
minVersions: ServerVersionMap | undefined;
|
|
879
|
+
expectedDocumentVersions: ServerVersionMap | undefined;
|
|
880
|
+
signal: AbortSignal;
|
|
881
|
+
callback: (diagnostics: FileDiagnosticsResult) => void;
|
|
882
|
+
}): Promise<void> {
|
|
883
|
+
try {
|
|
884
|
+
const deferredTimeout = AbortSignal.timeout(25_000);
|
|
885
|
+
const combined = AbortSignal.any([args.signal, deferredTimeout]);
|
|
886
|
+
const diagnostics = await getDiagnosticsForFile(args.dst, args.cwd, args.servers, {
|
|
887
|
+
signal: combined,
|
|
888
|
+
minVersions: args.minVersions,
|
|
889
|
+
expectedDocumentVersions: args.expectedDocumentVersions,
|
|
890
|
+
});
|
|
891
|
+
if (args.signal.aborted || diagnostics === undefined) return;
|
|
892
|
+
args.callback(diagnostics);
|
|
893
|
+
} catch {
|
|
894
|
+
// Cancelled or LSP gave up; silently discard.
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
774
898
|
async function runLspWritethrough(
|
|
775
899
|
dst: string,
|
|
776
900
|
content: string,
|
|
777
901
|
cwd: string,
|
|
778
|
-
options:
|
|
902
|
+
options: ResolvedWritethroughOptions,
|
|
779
903
|
signal?: AbortSignal,
|
|
780
904
|
file?: BunFile,
|
|
905
|
+
deferred?: {
|
|
906
|
+
onDeferredDiagnostics: (diagnostics: FileDiagnosticsResult) => void;
|
|
907
|
+
signal: AbortSignal;
|
|
908
|
+
},
|
|
781
909
|
): Promise<FileDiagnosticsResult | undefined> {
|
|
782
910
|
const { enableFormat, enableDiagnostics } = options;
|
|
783
911
|
const config = getConfig(cwd);
|
|
@@ -794,12 +922,13 @@ async function runLspWritethrough(
|
|
|
794
922
|
|
|
795
923
|
// Capture diagnostic versions BEFORE syncing to detect stale diagnostics
|
|
796
924
|
const minVersions = enableDiagnostics ? await captureDiagnosticVersions(cwd, servers) : undefined;
|
|
925
|
+
let expectedDocumentVersions: ServerVersionMap | undefined;
|
|
797
926
|
|
|
798
927
|
let formatter: FileFormatResult | undefined;
|
|
799
928
|
let diagnostics: FileDiagnosticsResult | undefined;
|
|
800
929
|
let timedOut = false;
|
|
801
930
|
try {
|
|
802
|
-
const timeoutSignal = AbortSignal.timeout(
|
|
931
|
+
const timeoutSignal = AbortSignal.timeout(5_000);
|
|
803
932
|
timeoutSignal.addEventListener(
|
|
804
933
|
"abort",
|
|
805
934
|
() => {
|
|
@@ -835,18 +964,39 @@ async function runLspWritethrough(
|
|
|
835
964
|
await getWritePromise();
|
|
836
965
|
}
|
|
837
966
|
|
|
967
|
+
if (enableDiagnostics) {
|
|
968
|
+
expectedDocumentVersions = await captureOpenFileVersions(dst, cwd, lspServers);
|
|
969
|
+
}
|
|
970
|
+
|
|
838
971
|
// 5. Notify saved to LSP servers
|
|
839
972
|
await notifyFileSaved(dst, cwd, lspServers, operationSignal);
|
|
840
973
|
|
|
841
974
|
// 6. Get diagnostics from all servers (wait for fresh results)
|
|
842
975
|
if (enableDiagnostics) {
|
|
843
|
-
diagnostics = await getDiagnosticsForFile(dst, cwd, servers,
|
|
976
|
+
diagnostics = await getDiagnosticsForFile(dst, cwd, servers, {
|
|
977
|
+
signal: operationSignal,
|
|
978
|
+
minVersions,
|
|
979
|
+
expectedDocumentVersions,
|
|
980
|
+
allowUnversionedLspDiagnostics: false,
|
|
981
|
+
});
|
|
844
982
|
}
|
|
845
983
|
});
|
|
846
984
|
} catch {
|
|
847
985
|
if (timedOut) {
|
|
848
986
|
formatter = undefined;
|
|
849
987
|
diagnostics = undefined;
|
|
988
|
+
// Schedule background diagnostic fetch if caller wants deferred results
|
|
989
|
+
if (deferred && !deferred.signal.aborted && enableDiagnostics) {
|
|
990
|
+
void scheduleDeferredDiagnosticsFetch({
|
|
991
|
+
dst,
|
|
992
|
+
cwd,
|
|
993
|
+
servers,
|
|
994
|
+
minVersions,
|
|
995
|
+
expectedDocumentVersions,
|
|
996
|
+
signal: deferred.signal,
|
|
997
|
+
callback: deferred.onDeferredDiagnostics,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
850
1000
|
}
|
|
851
1001
|
await getWritePromise();
|
|
852
1002
|
}
|
|
@@ -867,22 +1017,32 @@ async function runLspWritethrough(
|
|
|
867
1017
|
async function flushWritethroughBatch(
|
|
868
1018
|
batch: PendingWritethrough[],
|
|
869
1019
|
cwd: string,
|
|
870
|
-
options:
|
|
1020
|
+
options: ResolvedWritethroughOptions,
|
|
871
1021
|
signal?: AbortSignal,
|
|
1022
|
+
getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
872
1023
|
): Promise<FileDiagnosticsResult | undefined> {
|
|
873
1024
|
if (batch.length === 0) {
|
|
874
1025
|
return undefined;
|
|
875
1026
|
}
|
|
876
1027
|
const results: Array<FileDiagnosticsResult | undefined> = [];
|
|
877
1028
|
for (const entry of batch) {
|
|
878
|
-
|
|
1029
|
+
const bundle = getDeferred?.(entry.dst);
|
|
1030
|
+
const deferredInner =
|
|
1031
|
+
bundle &&
|
|
1032
|
+
({
|
|
1033
|
+
onDeferredDiagnostics: bundle.onDeferredDiagnostics,
|
|
1034
|
+
signal: bundle.signal,
|
|
1035
|
+
} as const);
|
|
1036
|
+
const diag = await runLspWritethrough(entry.dst, entry.content, cwd, options, signal, entry.file, deferredInner);
|
|
1037
|
+
bundle?.finalize(diag);
|
|
1038
|
+
results.push(diag);
|
|
879
1039
|
}
|
|
880
1040
|
return mergeDiagnostics(results, options);
|
|
881
1041
|
}
|
|
882
1042
|
|
|
883
1043
|
/** Create a writethrough callback for LSP aware write operations */
|
|
884
1044
|
export function createLspWritethrough(cwd: string, options?: WritethroughOptions): WritethroughCallback {
|
|
885
|
-
const resolvedOptions:
|
|
1045
|
+
const resolvedOptions: ResolvedWritethroughOptions = {
|
|
886
1046
|
enableFormat: options?.enableFormat ?? false,
|
|
887
1047
|
enableDiagnostics: options?.enableDiagnostics ?? false,
|
|
888
1048
|
};
|
|
@@ -895,9 +1055,19 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions
|
|
|
895
1055
|
signal?: AbortSignal,
|
|
896
1056
|
file?: BunFile,
|
|
897
1057
|
batch?: LspWritethroughBatchRequest,
|
|
1058
|
+
getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
898
1059
|
) => {
|
|
899
1060
|
if (!batch) {
|
|
900
|
-
|
|
1061
|
+
const bundle = getDeferred?.(dst);
|
|
1062
|
+
const deferredInner =
|
|
1063
|
+
bundle &&
|
|
1064
|
+
({
|
|
1065
|
+
onDeferredDiagnostics: bundle.onDeferredDiagnostics,
|
|
1066
|
+
signal: bundle.signal,
|
|
1067
|
+
} as const);
|
|
1068
|
+
const diagnostics = await runLspWritethrough(dst, content, cwd, resolvedOptions, signal, file, deferredInner);
|
|
1069
|
+
bundle?.finalize(diagnostics);
|
|
1070
|
+
return diagnostics;
|
|
901
1071
|
}
|
|
902
1072
|
|
|
903
1073
|
const state = getOrCreateWritethroughBatch(batch.id, resolvedOptions);
|
|
@@ -909,7 +1079,7 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions
|
|
|
909
1079
|
}
|
|
910
1080
|
|
|
911
1081
|
writethroughBatches.delete(batch.id);
|
|
912
|
-
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal);
|
|
1082
|
+
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal, getDeferred);
|
|
913
1083
|
};
|
|
914
1084
|
}
|
|
915
1085
|
|
|
@@ -928,7 +1098,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
928
1098
|
readonly inline = true;
|
|
929
1099
|
|
|
930
1100
|
constructor(private readonly session: ToolSession) {
|
|
931
|
-
this.description =
|
|
1101
|
+
this.description = prompt.render(lspDescription);
|
|
932
1102
|
}
|
|
933
1103
|
|
|
934
1104
|
static createIf(session: ToolSession): LspTool | null {
|
|
@@ -974,8 +1144,8 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
974
1144
|
|
|
975
1145
|
// Diagnostics can be batch or single-file - queries all applicable servers
|
|
976
1146
|
if (action === "diagnostics") {
|
|
977
|
-
if (
|
|
978
|
-
//
|
|
1147
|
+
if (file === "*") {
|
|
1148
|
+
// `*` => run workspace diagnostics across all configured servers
|
|
979
1149
|
const result = await runWorkspaceDiagnostics(this.session.cwd, signal);
|
|
980
1150
|
return {
|
|
981
1151
|
content: [
|
|
@@ -988,6 +1158,18 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
988
1158
|
};
|
|
989
1159
|
}
|
|
990
1160
|
|
|
1161
|
+
if (!file) {
|
|
1162
|
+
return {
|
|
1163
|
+
content: [
|
|
1164
|
+
{
|
|
1165
|
+
type: "text",
|
|
1166
|
+
text: "Error: file parameter required. Use `*` for workspace-wide diagnostics or a path/glob for specific files.",
|
|
1167
|
+
},
|
|
1168
|
+
],
|
|
1169
|
+
details: { action, success: false, request: params },
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
991
1173
|
let targets: string[];
|
|
992
1174
|
let truncatedGlobTargets = false;
|
|
993
1175
|
if (hasGlobPattern(file)) {
|
|
@@ -1044,13 +1226,13 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1044
1226
|
const client = await getOrCreateClient(serverConfig, this.session.cwd);
|
|
1045
1227
|
const minVersion = client.diagnosticsVersion;
|
|
1046
1228
|
await refreshFile(client, resolved, signal);
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
diagnosticsWaitTimeoutMs,
|
|
1229
|
+
const expectedDocumentVersion = client.openFiles.get(uri)?.version;
|
|
1230
|
+
const diagnostics = await waitForDiagnostics(client, uri, {
|
|
1231
|
+
timeoutMs: diagnosticsWaitTimeoutMs,
|
|
1051
1232
|
signal,
|
|
1052
1233
|
minVersion,
|
|
1053
|
-
|
|
1234
|
+
expectedDocumentVersion,
|
|
1235
|
+
});
|
|
1054
1236
|
allDiagnostics.push(...diagnostics);
|
|
1055
1237
|
} catch (err) {
|
|
1056
1238
|
if (err instanceof ToolAbortError || signal?.aborted) {
|
|
@@ -1106,17 +1288,24 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1106
1288
|
};
|
|
1107
1289
|
}
|
|
1108
1290
|
|
|
1109
|
-
|
|
1291
|
+
// `*` means workspace scope for symbols/reload; other actions need a concrete file.
|
|
1292
|
+
const isWorkspace = file === "*";
|
|
1293
|
+
const requiresFile = !file && action !== "reload";
|
|
1110
1294
|
|
|
1111
1295
|
if (requiresFile) {
|
|
1112
1296
|
return {
|
|
1113
|
-
content: [
|
|
1297
|
+
content: [
|
|
1298
|
+
{
|
|
1299
|
+
type: "text",
|
|
1300
|
+
text: "Error: file parameter required. Use `*` for workspace scope where supported.",
|
|
1301
|
+
},
|
|
1302
|
+
],
|
|
1114
1303
|
details: { action, success: false },
|
|
1115
1304
|
};
|
|
1116
1305
|
}
|
|
1117
1306
|
|
|
1118
|
-
const resolvedFile = file ? resolveToCwd(file, this.session.cwd) : null;
|
|
1119
|
-
if (action === "symbols" && !resolvedFile) {
|
|
1307
|
+
const resolvedFile = file && !isWorkspace ? resolveToCwd(file, this.session.cwd) : null;
|
|
1308
|
+
if (action === "symbols" && (isWorkspace || !resolvedFile)) {
|
|
1120
1309
|
const normalizedQuery = query?.trim();
|
|
1121
1310
|
if (!normalizedQuery) {
|
|
1122
1311
|
return {
|
|
@@ -1188,7 +1377,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1188
1377
|
};
|
|
1189
1378
|
}
|
|
1190
1379
|
|
|
1191
|
-
if (action === "reload" && !resolvedFile) {
|
|
1380
|
+
if (action === "reload" && (isWorkspace || !resolvedFile)) {
|
|
1192
1381
|
const servers = getLspServers(config);
|
|
1193
1382
|
if (servers.length === 0) {
|
|
1194
1383
|
return {
|
|
@@ -1372,7 +1561,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1372
1561
|
}
|
|
1373
1562
|
|
|
1374
1563
|
case "code_actions": {
|
|
1375
|
-
const diagnostics = client.diagnostics.get(uri) ?? [];
|
|
1564
|
+
const diagnostics = client.diagnostics.get(uri)?.diagnostics ?? [];
|
|
1376
1565
|
const context: CodeActionContext = {
|
|
1377
1566
|
diagnostics,
|
|
1378
1567
|
only: !apply && query ? [query] : undefined,
|
package/src/lsp/lspmux.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { $env, logger } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { $env, $which, logger } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { TOML } from "bun";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -141,7 +141,7 @@ export async function detectLspmux(): Promise<LspmuxState> {
|
|
|
141
141
|
return cachedState;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
const binaryPath =
|
|
144
|
+
const binaryPath = $which("lspmux");
|
|
145
145
|
if (!binaryPath) {
|
|
146
146
|
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
147
147
|
cacheTimestamp = now;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LspStartupServerInfo } from "./index";
|
|
2
|
+
|
|
3
|
+
export const LSP_STARTUP_EVENT_CHANNEL = "lsp:startup";
|
|
4
|
+
|
|
5
|
+
export type LspStartupEvent =
|
|
6
|
+
| {
|
|
7
|
+
type: "completed";
|
|
8
|
+
servers: Array<LspStartupServerInfo & { status: "ready" | "error" }>;
|
|
9
|
+
}
|
|
10
|
+
| {
|
|
11
|
+
type: "failed";
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
package/src/lsp/types.ts
CHANGED
|
@@ -93,6 +93,17 @@ export interface Diagnostic {
|
|
|
93
93
|
data?: unknown;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
export interface PublishedDiagnostics {
|
|
97
|
+
diagnostics: Diagnostic[];
|
|
98
|
+
version: number | null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface PublishDiagnosticsParams {
|
|
102
|
+
uri: string;
|
|
103
|
+
diagnostics: Diagnostic[];
|
|
104
|
+
version?: number | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
// =============================================================================
|
|
97
108
|
// Text Edits
|
|
98
109
|
// =============================================================================
|
|
@@ -392,7 +403,7 @@ export interface LspClient {
|
|
|
392
403
|
config: ServerConfig;
|
|
393
404
|
proc: ptree.ChildProcess<"pipe">;
|
|
394
405
|
requestId: number;
|
|
395
|
-
diagnostics: Map<string,
|
|
406
|
+
diagnostics: Map<string, PublishedDiagnostics>;
|
|
396
407
|
diagnosticsVersion: number;
|
|
397
408
|
openFiles: Map<string, OpenFile>;
|
|
398
409
|
pendingRequests: Map<number, PendingRequest>;
|
package/src/lsp/utils.ts
CHANGED
|
@@ -52,6 +52,7 @@ const LANGUAGE_MAP: Record<string, string> = {
|
|
|
52
52
|
".zsh": "shellscript",
|
|
53
53
|
".fish": "fish",
|
|
54
54
|
".pl": "perl",
|
|
55
|
+
".pm": "perl",
|
|
55
56
|
".php": "php",
|
|
56
57
|
|
|
57
58
|
// JVM languages
|
|
@@ -76,6 +77,7 @@ const LANGUAGE_MAP: Record<string, string> = {
|
|
|
76
77
|
".less": "less",
|
|
77
78
|
".vue": "vue",
|
|
78
79
|
".svelte": "svelte",
|
|
80
|
+
".astro": "astro",
|
|
79
81
|
|
|
80
82
|
// Data formats
|
|
81
83
|
".json": "json",
|
|
@@ -129,6 +131,8 @@ const LANGUAGE_MAP: Record<string, string> = {
|
|
|
129
131
|
".psm1": "powershell",
|
|
130
132
|
".bat": "bat",
|
|
131
133
|
".cmd": "bat",
|
|
134
|
+
".tla": "tlaplus",
|
|
135
|
+
".tlaplus": "tlaplus",
|
|
132
136
|
};
|
|
133
137
|
|
|
134
138
|
/**
|
|
@@ -140,12 +144,15 @@ export function detectLanguageId(filePath: string): string {
|
|
|
140
144
|
const basename = path.basename(filePath).toLowerCase();
|
|
141
145
|
|
|
142
146
|
// Handle special filenames
|
|
143
|
-
if (basename === "dockerfile" || basename.startsWith("dockerfile.")) {
|
|
147
|
+
if (basename === "dockerfile" || basename.startsWith("dockerfile.") || basename === "containerfile") {
|
|
144
148
|
return "dockerfile";
|
|
145
149
|
}
|
|
146
150
|
if (basename === "makefile" || basename === "gnumakefile") {
|
|
147
151
|
return "makefile";
|
|
148
152
|
}
|
|
153
|
+
if (basename === "justfile") {
|
|
154
|
+
return "just";
|
|
155
|
+
}
|
|
149
156
|
if (basename === "cmakelists.txt" || ext === ".cmake") {
|
|
150
157
|
return "cmake";
|
|
151
158
|
}
|