orquesta-cli 0.2.108 → 0.2.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +23 -1
- package/dist/core/commands/help.js +5 -0
- package/dist/core/commands/index.js +7 -0
- package/dist/core/commands/lsp.d.ts +3 -0
- package/dist/core/commands/lsp.js +37 -0
- package/dist/core/commands/mcp.d.ts +3 -0
- package/dist/core/commands/mcp.js +46 -0
- package/dist/core/commands/undo.d.ts +4 -0
- package/dist/core/commands/undo.js +45 -0
- package/dist/core/config/config-manager.d.ts +7 -1
- package/dist/core/config/config-manager.js +36 -0
- package/dist/core/file-snapshot-store.d.ts +25 -0
- package/dist/core/file-snapshot-store.js +104 -0
- package/dist/core/lsp/index.d.ts +6 -0
- package/dist/core/lsp/index.js +75 -0
- package/dist/core/lsp/jsonrpc.d.ts +18 -0
- package/dist/core/lsp/jsonrpc.js +38 -0
- package/dist/core/lsp/lsp-client.d.ts +40 -0
- package/dist/core/lsp/lsp-client.js +201 -0
- package/dist/core/lsp/server-registry.d.ts +14 -0
- package/dist/core/lsp/server-registry.js +85 -0
- package/dist/eval/eval-runner.js +14 -0
- package/dist/orchestration/plan-executor.js +2 -0
- package/dist/tools/llm/simple/file-tools.js +8 -2
- package/dist/tools/mcp/index.d.ts +3 -0
- package/dist/tools/mcp/index.js +3 -0
- package/dist/tools/mcp/mcp-client.d.ts +16 -0
- package/dist/tools/mcp/mcp-client.js +180 -0
- package/dist/tools/mcp/mcp-config.d.ts +4 -0
- package/dist/tools/mcp/mcp-config.js +87 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/ui/components/PlanExecuteApp.js +25 -2
- package/dist/ui/hooks/slashCommandProcessor.js +17 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -349,6 +349,16 @@ program
|
|
|
349
349
|
const toolsPromise = initializeOptionalTools().then(() => {
|
|
350
350
|
logger.flow('Optional tools initialized');
|
|
351
351
|
});
|
|
352
|
+
const mcpPromise = import('./tools/mcp/index.js')
|
|
353
|
+
.then(({ initializeMcpServers }) => initializeMcpServers())
|
|
354
|
+
.then((r) => {
|
|
355
|
+
if (r.servers > 0 || r.errors.length > 0) {
|
|
356
|
+
logger.flow('MCP initialized', { servers: r.servers, tools: r.tools, errors: r.errors.length });
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
.catch((error) => {
|
|
360
|
+
logger.warn('MCP init failed', { error: error instanceof Error ? error.message : String(error) });
|
|
361
|
+
});
|
|
352
362
|
const syncPromise = (configManager.hasOrquestaConnection() && configManager.shouldAutoSync())
|
|
353
363
|
? syncOrquestaConfigs().then(syncResult => {
|
|
354
364
|
if (syncResult.success && (syncResult.added > 0 || syncResult.updated > 0)) {
|
|
@@ -375,7 +385,7 @@ program
|
|
|
375
385
|
logger.flow('No LLM endpoints configured');
|
|
376
386
|
}
|
|
377
387
|
await Promise.race([
|
|
378
|
-
Promise.all([toolsPromise, syncPromise]),
|
|
388
|
+
Promise.all([toolsPromise, syncPromise, mcpPromise]),
|
|
379
389
|
new Promise(resolve => setTimeout(resolve, Number(process.env['ORQUESTA_INIT_TIMEOUT_MS']) || 12000).unref?.()),
|
|
380
390
|
]);
|
|
381
391
|
spinner.stop();
|
|
@@ -414,6 +424,18 @@ program
|
|
|
414
424
|
sessionId: sessionManager.getCurrentSessionId(),
|
|
415
425
|
exitReason: 'normal',
|
|
416
426
|
});
|
|
427
|
+
try {
|
|
428
|
+
const { disconnectMcpServers } = await import('./tools/mcp/index.js');
|
|
429
|
+
await disconnectMcpServers();
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
const { shutdownAllLspClients } = await import('./core/lsp/index.js');
|
|
435
|
+
await shutdownAllLspClients();
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
}
|
|
417
439
|
if (cleanup) {
|
|
418
440
|
await cleanup();
|
|
419
441
|
}
|
|
@@ -6,12 +6,16 @@ export const helpCommand = {
|
|
|
6
6
|
Available commands:
|
|
7
7
|
/exit, /quit - Exit the application
|
|
8
8
|
/clear - Clear conversation and TODOs
|
|
9
|
+
/undo - Revert the file changes from the last turn
|
|
10
|
+
/redo - Re-apply the changes undone by /undo
|
|
9
11
|
/compact - Compact conversation to free up context
|
|
10
12
|
/memory - Persistent memory: /memory add <note> | list | remove <n> | clear
|
|
11
13
|
/settings - Open settings menu
|
|
12
14
|
/model - Switch between LLM models
|
|
13
15
|
/project - Switch between Orquesta projects
|
|
14
16
|
/tool - Enable/disable optional tools (Browser, Background)
|
|
17
|
+
/mcp - List connected MCP servers and their tools
|
|
18
|
+
/lsp - LSP diagnostics after edits: status | on | off
|
|
15
19
|
/load - Load a saved session
|
|
16
20
|
/usage - Show token usage statistics
|
|
17
21
|
/cost - Estimated USD spend this process (by model)
|
|
@@ -26,6 +30,7 @@ Available commands:
|
|
|
26
30
|
|
|
27
31
|
Keyboard shortcuts:
|
|
28
32
|
Ctrl+C - Exit
|
|
33
|
+
Ctrl+S - Send now / steer the running task (no wait for next turn)
|
|
29
34
|
Ctrl+T - Toggle TODO details
|
|
30
35
|
ESC - Interrupt current execution
|
|
31
36
|
@ - File browser
|
|
@@ -4,10 +4,17 @@ import { compactCommand } from './compact.js';
|
|
|
4
4
|
import { memoryCommand } from './memory.js';
|
|
5
5
|
import { recallCommand } from './recall.js';
|
|
6
6
|
import { helpCommand } from './help.js';
|
|
7
|
+
import { mcpCommand } from './mcp.js';
|
|
8
|
+
import { undoCommand, redoCommand } from './undo.js';
|
|
9
|
+
import { lspCommand } from './lsp.js';
|
|
7
10
|
commandRegistry.register(clearCommand);
|
|
8
11
|
commandRegistry.register(compactCommand);
|
|
9
12
|
commandRegistry.register(memoryCommand);
|
|
10
13
|
commandRegistry.register(recallCommand);
|
|
11
14
|
commandRegistry.register(helpCommand);
|
|
15
|
+
commandRegistry.register(mcpCommand);
|
|
16
|
+
commandRegistry.register(undoCommand);
|
|
17
|
+
commandRegistry.register(redoCommand);
|
|
18
|
+
commandRegistry.register(lspCommand);
|
|
12
19
|
export { commandRegistry } from './registry.js';
|
|
13
20
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { configManager } from '../config/config-manager.js';
|
|
2
|
+
import { getServerDefs, resolveServerBinary, getActiveLspClientCount } from '../lsp/index.js';
|
|
3
|
+
export const lspCommand = {
|
|
4
|
+
name: '/lsp',
|
|
5
|
+
description: 'LSP diagnostics: /lsp | on | off',
|
|
6
|
+
async execute(context, rawInput) {
|
|
7
|
+
const reply = (content) => {
|
|
8
|
+
const updatedMessages = [...context.messages, { role: 'assistant', content }];
|
|
9
|
+
context.setMessages(updatedMessages);
|
|
10
|
+
return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
|
|
11
|
+
};
|
|
12
|
+
const arg = rawInput.slice('/lsp'.length).trim().toLowerCase();
|
|
13
|
+
if (arg === 'on' || arg === 'enable') {
|
|
14
|
+
await configManager.setLspEnabled(true);
|
|
15
|
+
return reply('✓ LSP diagnostics enabled.');
|
|
16
|
+
}
|
|
17
|
+
if (arg === 'off' || arg === 'disable') {
|
|
18
|
+
await configManager.setLspEnabled(false);
|
|
19
|
+
return reply('✓ LSP diagnostics disabled.');
|
|
20
|
+
}
|
|
21
|
+
const enabled = configManager.isLspEnabled();
|
|
22
|
+
const lines = [
|
|
23
|
+
`🩺 LSP diagnostics: ${enabled ? 'ON' : 'OFF'} (${getActiveLspClientCount()} server(s) running)`,
|
|
24
|
+
'',
|
|
25
|
+
'Language servers (✓ = found on PATH):',
|
|
26
|
+
];
|
|
27
|
+
for (const def of getServerDefs()) {
|
|
28
|
+
const bin = resolveServerBinary(def.command);
|
|
29
|
+
const mark = bin ? '✓' : '·';
|
|
30
|
+
const where = bin ? `→ ${bin}` : 'not installed';
|
|
31
|
+
lines.push(` ${mark} ${def.id} (${def.extensions.join(', ')}) ${def.command} ${where}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push('', 'Toggle with /lsp on | /lsp off');
|
|
34
|
+
return reply(lines.join('\n'));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=lsp.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getConnectedMcpServers } from '../../tools/mcp/index.js';
|
|
2
|
+
import { loadMcpServerConfigs } from '../../tools/mcp/mcp-config.js';
|
|
3
|
+
export const mcpCommand = {
|
|
4
|
+
name: '/mcp',
|
|
5
|
+
description: 'List connected MCP servers and their tools',
|
|
6
|
+
async execute(context) {
|
|
7
|
+
const reply = (content) => {
|
|
8
|
+
const updatedMessages = [...context.messages, { role: 'assistant', content }];
|
|
9
|
+
context.setMessages(updatedMessages);
|
|
10
|
+
return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
|
|
11
|
+
};
|
|
12
|
+
const live = getConnectedMcpServers();
|
|
13
|
+
const configured = loadMcpServerConfigs();
|
|
14
|
+
if (configured.length === 0 && live.length === 0) {
|
|
15
|
+
return reply('No MCP servers configured.\n\n' +
|
|
16
|
+
'Add one to ~/.orquesta-cli/config.json:\n' +
|
|
17
|
+
' "mcpServers": [\n' +
|
|
18
|
+
' { "name": "filesystem", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }\n' +
|
|
19
|
+
' ]\n\n' +
|
|
20
|
+
'Or drop a Claude-compatible .mcp.json in the project root.');
|
|
21
|
+
}
|
|
22
|
+
const liveByName = new Map(live.map((s) => [s.name, s]));
|
|
23
|
+
const lines = [`🔌 MCP servers (${live.length} connected / ${configured.length} configured):`];
|
|
24
|
+
for (const cfg of configured) {
|
|
25
|
+
const conn = liveByName.get(cfg.name);
|
|
26
|
+
const transport = cfg.url ? `http ${cfg.url}` : `stdio ${cfg.command ?? ''}`.trim();
|
|
27
|
+
if (conn) {
|
|
28
|
+
lines.push(`\n ✓ ${cfg.name} (${transport}) — ${conn.tools.length} tool(s)`);
|
|
29
|
+
for (const t of conn.tools)
|
|
30
|
+
lines.push(` • ${t}`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
lines.push(`\n ✗ ${cfg.name} (${transport}) — not connected`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const s of live) {
|
|
37
|
+
if (!configured.some((c) => c.name === s.name)) {
|
|
38
|
+
lines.push(`\n ✓ ${s.name} (runtime) — ${s.tools.length} tool(s)`);
|
|
39
|
+
for (const t of s.tools)
|
|
40
|
+
lines.push(` • ${t}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return reply(lines.join('\n'));
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { fileSnapshotStore } from '../file-snapshot-store.js';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
function rel(p) {
|
|
4
|
+
const r = path.relative(process.cwd(), p);
|
|
5
|
+
return r && !r.startsWith('..') ? r : p;
|
|
6
|
+
}
|
|
7
|
+
function formatResult(verb, result) {
|
|
8
|
+
const lines = [`↩️ ${verb} "${result.label}" — ${result.restored.length} file(s):`];
|
|
9
|
+
for (const p of result.restored)
|
|
10
|
+
lines.push(` • ${rel(p)}`);
|
|
11
|
+
if (result.failed.length > 0) {
|
|
12
|
+
lines.push(` ⚠️ ${result.failed.length} could not be restored:`);
|
|
13
|
+
for (const p of result.failed)
|
|
14
|
+
lines.push(` ✗ ${rel(p)}`);
|
|
15
|
+
}
|
|
16
|
+
return lines.join('\n');
|
|
17
|
+
}
|
|
18
|
+
function reply(context, content) {
|
|
19
|
+
const updatedMessages = [...context.messages, { role: 'assistant', content }];
|
|
20
|
+
context.setMessages(updatedMessages);
|
|
21
|
+
return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
|
|
22
|
+
}
|
|
23
|
+
export const undoCommand = {
|
|
24
|
+
name: '/undo',
|
|
25
|
+
description: 'Revert the file changes from the last turn',
|
|
26
|
+
async execute(context) {
|
|
27
|
+
const result = await fileSnapshotStore.undo();
|
|
28
|
+
if (!result) {
|
|
29
|
+
return reply(context, 'Nothing to undo. (Only agent create_file/edit_file changes are tracked — not bash edits.)');
|
|
30
|
+
}
|
|
31
|
+
return reply(context, formatResult('Reverted', result));
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export const redoCommand = {
|
|
35
|
+
name: '/redo',
|
|
36
|
+
description: 'Re-apply the changes undone by /undo',
|
|
37
|
+
async execute(context) {
|
|
38
|
+
const result = await fileSnapshotStore.redo();
|
|
39
|
+
if (!result) {
|
|
40
|
+
return reply(context, 'Nothing to redo.');
|
|
41
|
+
}
|
|
42
|
+
return reply(context, formatResult('Re-applied', result));
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=undo.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OpenConfig, EndpointConfig, ModelInfo, OrquestaConfig, OrchestrationConfig, OrchestrationRole } from '../../types/index.js';
|
|
1
|
+
import { OpenConfig, EndpointConfig, ModelInfo, OrquestaConfig, OrchestrationConfig, OrchestrationRole, McpServerConfig, LspServerConfig } from '../../types/index.js';
|
|
2
2
|
export declare class ConfigManager {
|
|
3
3
|
private config;
|
|
4
4
|
private initialized;
|
|
@@ -62,6 +62,12 @@ export declare class ConfigManager {
|
|
|
62
62
|
setRefinerEnabled(enabled: boolean): Promise<void>;
|
|
63
63
|
isSingleAgentMode(): boolean;
|
|
64
64
|
setSingleAgentMode(enabled: boolean): Promise<void>;
|
|
65
|
+
getMcpServers(): McpServerConfig[];
|
|
66
|
+
addMcpServer(server: McpServerConfig): Promise<void>;
|
|
67
|
+
removeMcpServer(name: string): Promise<boolean>;
|
|
68
|
+
getLspServers(): LspServerConfig[];
|
|
69
|
+
isLspEnabled(): boolean;
|
|
70
|
+
setLspEnabled(enabled: boolean): Promise<void>;
|
|
65
71
|
isWorktreeIsolationEnabled(): boolean;
|
|
66
72
|
setWorktreeIsolationEnabled(enabled: boolean): Promise<void>;
|
|
67
73
|
}
|
|
@@ -425,6 +425,42 @@ export class ConfigManager {
|
|
|
425
425
|
config.orchestration.singleAgentMode = enabled;
|
|
426
426
|
await this.saveConfig();
|
|
427
427
|
}
|
|
428
|
+
getMcpServers() {
|
|
429
|
+
return this.config?.mcpServers ?? [];
|
|
430
|
+
}
|
|
431
|
+
async addMcpServer(server) {
|
|
432
|
+
const config = this.getConfig();
|
|
433
|
+
const list = config.mcpServers ?? [];
|
|
434
|
+
const idx = list.findIndex((s) => s.name === server.name);
|
|
435
|
+
if (idx >= 0)
|
|
436
|
+
list[idx] = server;
|
|
437
|
+
else
|
|
438
|
+
list.push(server);
|
|
439
|
+
config.mcpServers = list;
|
|
440
|
+
await this.saveConfig();
|
|
441
|
+
}
|
|
442
|
+
async removeMcpServer(name) {
|
|
443
|
+
const config = this.getConfig();
|
|
444
|
+
const list = config.mcpServers ?? [];
|
|
445
|
+
const next = list.filter((s) => s.name !== name);
|
|
446
|
+
const removed = next.length !== list.length;
|
|
447
|
+
if (removed) {
|
|
448
|
+
config.mcpServers = next;
|
|
449
|
+
await this.saveConfig();
|
|
450
|
+
}
|
|
451
|
+
return removed;
|
|
452
|
+
}
|
|
453
|
+
getLspServers() {
|
|
454
|
+
return this.config?.lspServers ?? [];
|
|
455
|
+
}
|
|
456
|
+
isLspEnabled() {
|
|
457
|
+
return this.config?.lsp?.enabled ?? true;
|
|
458
|
+
}
|
|
459
|
+
async setLspEnabled(enabled) {
|
|
460
|
+
const config = this.getConfig();
|
|
461
|
+
config.lsp = { ...config.lsp, enabled };
|
|
462
|
+
await this.saveConfig();
|
|
463
|
+
}
|
|
428
464
|
isWorktreeIsolationEnabled() {
|
|
429
465
|
return this.config?.orchestration?.worktreeIsolation ?? false;
|
|
430
466
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface UndoRedoResult {
|
|
2
|
+
label: string;
|
|
3
|
+
restored: string[];
|
|
4
|
+
failed: string[];
|
|
5
|
+
}
|
|
6
|
+
declare class FileSnapshotStore {
|
|
7
|
+
private current;
|
|
8
|
+
private undoStack;
|
|
9
|
+
private redoStack;
|
|
10
|
+
beginTurn(label: string): void;
|
|
11
|
+
seal(): void;
|
|
12
|
+
record(absPath: string, priorContent: string | null): void;
|
|
13
|
+
private apply;
|
|
14
|
+
undo(): Promise<UndoRedoResult | null>;
|
|
15
|
+
redo(): Promise<UndoRedoResult | null>;
|
|
16
|
+
getStatus(): {
|
|
17
|
+
undo: number;
|
|
18
|
+
redo: number;
|
|
19
|
+
pendingEdits: number;
|
|
20
|
+
};
|
|
21
|
+
reset(): void;
|
|
22
|
+
}
|
|
23
|
+
export declare const fileSnapshotStore: FileSnapshotStore;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=file-snapshot-store.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { invalidateCache } from './file-cache.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
const MAX_SNAPSHOT_BYTES = 5 * 1024 * 1024;
|
|
6
|
+
const MAX_STACK = 50;
|
|
7
|
+
class FileSnapshotStore {
|
|
8
|
+
current = null;
|
|
9
|
+
undoStack = [];
|
|
10
|
+
redoStack = [];
|
|
11
|
+
beginTurn(label) {
|
|
12
|
+
this.seal();
|
|
13
|
+
this.redoStack = [];
|
|
14
|
+
this.current = { label: label.slice(0, 120), ts: Date.now(), entries: [] };
|
|
15
|
+
}
|
|
16
|
+
seal() {
|
|
17
|
+
if (this.current && this.current.entries.length > 0) {
|
|
18
|
+
this.undoStack.push(this.current);
|
|
19
|
+
if (this.undoStack.length > MAX_STACK)
|
|
20
|
+
this.undoStack.shift();
|
|
21
|
+
}
|
|
22
|
+
this.current = null;
|
|
23
|
+
}
|
|
24
|
+
record(absPath, priorContent) {
|
|
25
|
+
if (!this.current) {
|
|
26
|
+
this.current = { label: '(edits)', ts: Date.now(), entries: [] };
|
|
27
|
+
}
|
|
28
|
+
if (this.current.entries.some((e) => e.path === absPath))
|
|
29
|
+
return;
|
|
30
|
+
if (priorContent !== null && Buffer.byteLength(priorContent, 'utf-8') > MAX_SNAPSHOT_BYTES) {
|
|
31
|
+
logger.warn('Skipping snapshot: file too large for undo', { path: absPath });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.current.entries.push({ path: absPath, content: priorContent });
|
|
35
|
+
}
|
|
36
|
+
async apply(cp) {
|
|
37
|
+
const restored = [];
|
|
38
|
+
const failed = [];
|
|
39
|
+
for (const entry of cp.entries) {
|
|
40
|
+
let captured;
|
|
41
|
+
try {
|
|
42
|
+
captured = await fs.readFile(entry.path, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
captured = null;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
if (entry.content === null) {
|
|
49
|
+
await fs.rm(entry.path, { force: true });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
await fs.mkdir(path.dirname(entry.path), { recursive: true });
|
|
53
|
+
await fs.writeFile(entry.path, entry.content, 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
invalidateCache(entry.path);
|
|
56
|
+
entry.content = captured;
|
|
57
|
+
restored.push(entry.path);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
logger.warn('Undo/redo failed for path', {
|
|
61
|
+
path: entry.path,
|
|
62
|
+
error: err instanceof Error ? err.message : String(err),
|
|
63
|
+
});
|
|
64
|
+
failed.push(entry.path);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { label: cp.label, restored, failed };
|
|
68
|
+
}
|
|
69
|
+
async undo() {
|
|
70
|
+
this.seal();
|
|
71
|
+
const cp = this.undoStack.pop();
|
|
72
|
+
if (!cp)
|
|
73
|
+
return null;
|
|
74
|
+
const result = await this.apply(cp);
|
|
75
|
+
this.redoStack.push(cp);
|
|
76
|
+
if (this.redoStack.length > MAX_STACK)
|
|
77
|
+
this.redoStack.shift();
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
async redo() {
|
|
81
|
+
const cp = this.redoStack.pop();
|
|
82
|
+
if (!cp)
|
|
83
|
+
return null;
|
|
84
|
+
const result = await this.apply(cp);
|
|
85
|
+
this.undoStack.push(cp);
|
|
86
|
+
if (this.undoStack.length > MAX_STACK)
|
|
87
|
+
this.undoStack.shift();
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
getStatus() {
|
|
91
|
+
return {
|
|
92
|
+
undo: this.undoStack.length + (this.current && this.current.entries.length > 0 ? 1 : 0),
|
|
93
|
+
redo: this.redoStack.length,
|
|
94
|
+
pendingEdits: this.current?.entries.length ?? 0,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
reset() {
|
|
98
|
+
this.current = null;
|
|
99
|
+
this.undoStack = [];
|
|
100
|
+
this.redoStack = [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export const fileSnapshotStore = new FileSnapshotStore();
|
|
104
|
+
//# sourceMappingURL=file-snapshot-store.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { shutdownAllLspClients, getActiveLspClientCount, Diagnostic } from './lsp-client.js';
|
|
2
|
+
export declare function formatDiagnostics(relPath: string, diags: Diagnostic[]): string;
|
|
3
|
+
export declare function getDiagnosticsForFile(absPath: string): Promise<string>;
|
|
4
|
+
export { shutdownAllLspClients, getActiveLspClientCount };
|
|
5
|
+
export { findServerForFile, getServerDefs, resolveServerBinary } from './server-registry.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { findServerForFile } from './server-registry.js';
|
|
4
|
+
import { getOrCreateClient, shutdownAllLspClients, getActiveLspClientCount } from './lsp-client.js';
|
|
5
|
+
import { configManager } from '../config/config-manager.js';
|
|
6
|
+
import { logger } from '../../utils/logger.js';
|
|
7
|
+
const ROOT_MARKERS = ['tsconfig.json', 'package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', '.git'];
|
|
8
|
+
function findProjectRoot(absPath) {
|
|
9
|
+
let dir = path.dirname(absPath);
|
|
10
|
+
for (;;) {
|
|
11
|
+
for (const marker of ROOT_MARKERS) {
|
|
12
|
+
if (fs.existsSync(path.join(dir, marker)))
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
const parent = path.dirname(dir);
|
|
16
|
+
if (parent === dir)
|
|
17
|
+
return path.dirname(absPath);
|
|
18
|
+
dir = parent;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isEnabled() {
|
|
22
|
+
try {
|
|
23
|
+
return configManager.isLspEnabled();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const SEVERITY_LABEL = { 1: 'error', 2: 'warning', 3: 'info', 4: 'hint' };
|
|
30
|
+
export function formatDiagnostics(relPath, diags) {
|
|
31
|
+
const relevant = diags.filter((d) => d.severity <= 2);
|
|
32
|
+
if (relevant.length === 0)
|
|
33
|
+
return '';
|
|
34
|
+
relevant.sort((a, b) => a.severity - b.severity || a.line - b.line);
|
|
35
|
+
const MAX = 20;
|
|
36
|
+
const shown = relevant.slice(0, MAX);
|
|
37
|
+
const lines = shown.map((d) => {
|
|
38
|
+
const label = SEVERITY_LABEL[d.severity] ?? 'error';
|
|
39
|
+
const src = d.source ? ` (${d.source})` : '';
|
|
40
|
+
return ` ${label} [${d.line + 1}:${d.character + 1}]${src}: ${d.message.replace(/\s+/g, ' ').trim()}`;
|
|
41
|
+
});
|
|
42
|
+
const errorCount = relevant.filter((d) => d.severity === 1).length;
|
|
43
|
+
const warnCount = relevant.length - errorCount;
|
|
44
|
+
const header = `Diagnostics for ${relPath} — ${errorCount} error(s), ${warnCount} warning(s):`;
|
|
45
|
+
const more = relevant.length > MAX ? `\n …and ${relevant.length - MAX} more` : '';
|
|
46
|
+
return `\n\n${header}\n${lines.join('\n')}${more}`;
|
|
47
|
+
}
|
|
48
|
+
export async function getDiagnosticsForFile(absPath) {
|
|
49
|
+
if (!isEnabled())
|
|
50
|
+
return '';
|
|
51
|
+
try {
|
|
52
|
+
const server = findServerForFile(absPath);
|
|
53
|
+
if (!server)
|
|
54
|
+
return '';
|
|
55
|
+
let text;
|
|
56
|
+
try {
|
|
57
|
+
text = fs.readFileSync(absPath, 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
const root = findProjectRoot(absPath);
|
|
63
|
+
const client = getOrCreateClient(server.def, root);
|
|
64
|
+
const diags = await client.getDiagnostics(absPath, text);
|
|
65
|
+
const rel = path.relative(process.cwd(), absPath) || path.basename(absPath);
|
|
66
|
+
return formatDiagnostics(rel, diags);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
logger.warn('LSP diagnostics failed', { path: absPath, error: err instanceof Error ? err.message : String(err) });
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export { shutdownAllLspClients, getActiveLspClientCount };
|
|
74
|
+
export { findServerForFile, getServerDefs, resolveServerBinary } from './server-registry.js';
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface JsonRpcMessage {
|
|
2
|
+
jsonrpc: '2.0';
|
|
3
|
+
id?: number | string;
|
|
4
|
+
method?: string;
|
|
5
|
+
params?: unknown;
|
|
6
|
+
result?: unknown;
|
|
7
|
+
error?: {
|
|
8
|
+
code: number;
|
|
9
|
+
message: string;
|
|
10
|
+
data?: unknown;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function encodeMessage(msg: JsonRpcMessage): Buffer;
|
|
14
|
+
export declare class MessageDecoder {
|
|
15
|
+
private buffer;
|
|
16
|
+
push(chunk: Buffer): JsonRpcMessage[];
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=jsonrpc.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function encodeMessage(msg) {
|
|
2
|
+
const json = JSON.stringify(msg);
|
|
3
|
+
const body = Buffer.from(json, 'utf-8');
|
|
4
|
+
const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, 'ascii');
|
|
5
|
+
return Buffer.concat([header, body]);
|
|
6
|
+
}
|
|
7
|
+
export class MessageDecoder {
|
|
8
|
+
buffer = Buffer.alloc(0);
|
|
9
|
+
push(chunk) {
|
|
10
|
+
this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
|
|
11
|
+
const out = [];
|
|
12
|
+
for (;;) {
|
|
13
|
+
const headerEnd = this.buffer.indexOf('\r\n\r\n');
|
|
14
|
+
if (headerEnd === -1)
|
|
15
|
+
break;
|
|
16
|
+
const header = this.buffer.subarray(0, headerEnd).toString('ascii');
|
|
17
|
+
const match = /Content-Length:\s*(\d+)/i.exec(header);
|
|
18
|
+
if (!match) {
|
|
19
|
+
this.buffer = this.buffer.subarray(headerEnd + 4);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const contentLength = parseInt(match[1], 10);
|
|
23
|
+
const bodyStart = headerEnd + 4;
|
|
24
|
+
const bodyEnd = bodyStart + contentLength;
|
|
25
|
+
if (this.buffer.length < bodyEnd)
|
|
26
|
+
break;
|
|
27
|
+
const body = this.buffer.subarray(bodyStart, bodyEnd).toString('utf-8');
|
|
28
|
+
this.buffer = this.buffer.subarray(bodyEnd);
|
|
29
|
+
try {
|
|
30
|
+
out.push(JSON.parse(body));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=jsonrpc.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { LspServerDef } from './server-registry.js';
|
|
3
|
+
export interface Diagnostic {
|
|
4
|
+
severity: number;
|
|
5
|
+
line: number;
|
|
6
|
+
character: number;
|
|
7
|
+
message: string;
|
|
8
|
+
source?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class LspClient {
|
|
11
|
+
private def;
|
|
12
|
+
private rootDir;
|
|
13
|
+
private proc;
|
|
14
|
+
private decoder;
|
|
15
|
+
private nextId;
|
|
16
|
+
private pendingRequests;
|
|
17
|
+
private diagnostics;
|
|
18
|
+
private diagWaiters;
|
|
19
|
+
private openDocs;
|
|
20
|
+
private initialized;
|
|
21
|
+
private version;
|
|
22
|
+
constructor(def: LspServerDef, rootDir: string);
|
|
23
|
+
private send;
|
|
24
|
+
private request;
|
|
25
|
+
private notify;
|
|
26
|
+
private handleMessage;
|
|
27
|
+
start(): Promise<void>;
|
|
28
|
+
getDiagnostics(absPath: string, text: string): Promise<Diagnostic[]>;
|
|
29
|
+
private onceDiagnostics;
|
|
30
|
+
private delay;
|
|
31
|
+
private waitFor;
|
|
32
|
+
private teardown;
|
|
33
|
+
stop(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function getOrCreateClient(def: LspServerDef, rootDir: string): LspClient;
|
|
36
|
+
export declare function shutdownAllLspClients(): Promise<void>;
|
|
37
|
+
export declare function getActiveLspClientCount(): number;
|
|
38
|
+
export { fileURLToPath };
|
|
39
|
+
export type { LspClient };
|
|
40
|
+
//# sourceMappingURL=lsp-client.d.ts.map
|