@oh-my-pi/pi-coding-agent 8.12.9 → 8.13.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/CHANGELOG.md +14 -0
- package/package.json +7 -9
- package/src/config/settings-manager.ts +61 -3
- package/src/debug/index.ts +362 -0
- package/src/debug/profiler.ts +158 -0
- package/src/debug/report-bundle.ts +280 -0
- package/src/debug/system-info.ts +91 -0
- package/src/exec/bash-executor.ts +2 -2
- package/src/extensibility/plugins/installer.ts +2 -2
- package/src/extensibility/plugins/manager.ts +3 -3
- package/src/index.ts +0 -1
- package/src/ipy/gateway-coordinator.ts +12 -22
- package/src/ipy/kernel.ts +3 -3
- package/src/lsp/client.ts +27 -21
- package/src/lsp/index.ts +1 -0
- package/src/lsp/render.ts +13 -19
- package/src/lsp/types.ts +2 -2
- package/src/main.ts +1 -1
- package/src/modes/controllers/command-controller.ts +3 -42
- package/src/modes/controllers/input-controller.ts +2 -2
- package/src/modes/controllers/selector-controller.ts +8 -0
- package/src/modes/interactive-mode.ts +8 -5
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/theme/theme.ts +27 -47
- package/src/modes/types.ts +2 -2
- package/src/sdk.ts +1 -1
- package/src/session/agent-storage.ts +26 -2
- package/src/tools/fetch.ts +55 -44
- package/src/tools/gemini-image.ts +2 -7
- package/src/tools/read.ts +175 -55
- package/src/utils/tools-manager.ts +35 -28
- package/src/web/scrapers/github.ts +11 -14
- package/src/web/scrapers/types.ts +2 -36
- package/src/web/scrapers/utils.ts +11 -14
- package/src/web/scrapers/youtube.ts +5 -15
- package/src/web/search/providers/codex.ts +358 -0
- package/src/web/search/providers/gemini.ts +426 -0
- package/src/web/search/types.ts +1 -1
- package/src/utils/shell.ts +0 -302
package/src/lsp/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { isEnoent, logger, ptree } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
|
|
3
3
|
import { applyWorkspaceEdit } from "./edits";
|
|
4
4
|
import { getLspmuxCommand, isLspmuxSupported } from "./lspmux";
|
|
@@ -206,7 +206,7 @@ function concatBuffers(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
async function writeMessage(
|
|
209
|
-
sink:
|
|
209
|
+
sink: Bun.FileSink,
|
|
210
210
|
message: LspJsonRpcRequest | LspJsonRpcNotification | LspJsonRpcResponse,
|
|
211
211
|
): Promise<void> {
|
|
212
212
|
const content = JSON.stringify(message);
|
|
@@ -230,7 +230,7 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
230
230
|
if (client.isReading) return;
|
|
231
231
|
client.isReading = true;
|
|
232
232
|
|
|
233
|
-
const reader = (client.
|
|
233
|
+
const reader = (client.proc.stdout as ReadableStream<Uint8Array>).getReader();
|
|
234
234
|
|
|
235
235
|
try {
|
|
236
236
|
while (true) {
|
|
@@ -364,7 +364,7 @@ async function sendResponse(
|
|
|
364
364
|
};
|
|
365
365
|
|
|
366
366
|
try {
|
|
367
|
-
await writeMessage(client.
|
|
367
|
+
await writeMessage(client.proc.stdin, response);
|
|
368
368
|
} catch (err) {
|
|
369
369
|
logger.error("LSP failed to respond.", { method, error: String(err) });
|
|
370
370
|
}
|
|
@@ -409,18 +409,17 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
409
409
|
? await getLspmuxCommand(baseCommand, baseArgs)
|
|
410
410
|
: { command: baseCommand, args: baseArgs };
|
|
411
411
|
|
|
412
|
-
const proc =
|
|
412
|
+
const proc = ptree.spawn([command, ...args], {
|
|
413
413
|
cwd,
|
|
414
|
+
detached: true,
|
|
414
415
|
stdin: "pipe",
|
|
415
|
-
stdout: "pipe",
|
|
416
|
-
stderr: "pipe",
|
|
417
416
|
env: env ? { ...process.env, ...env } : undefined,
|
|
418
417
|
});
|
|
419
418
|
|
|
420
419
|
const client: LspClient = {
|
|
421
420
|
name: key,
|
|
422
421
|
cwd,
|
|
423
|
-
|
|
422
|
+
proc,
|
|
424
423
|
config,
|
|
425
424
|
requestId: 0,
|
|
426
425
|
diagnostics: new Map(),
|
|
@@ -686,7 +685,7 @@ export function shutdownClient(key: string): void {
|
|
|
686
685
|
sendRequest(client, "shutdown", null).catch(() => {});
|
|
687
686
|
|
|
688
687
|
// Kill process
|
|
689
|
-
client.
|
|
688
|
+
client.proc.kill();
|
|
690
689
|
clients.delete(key);
|
|
691
690
|
}
|
|
692
691
|
|
|
@@ -773,7 +772,7 @@ export async function sendRequest(
|
|
|
773
772
|
});
|
|
774
773
|
|
|
775
774
|
// Write request
|
|
776
|
-
writeMessage(client.
|
|
775
|
+
writeMessage(client.proc.stdin, request).catch(err => {
|
|
777
776
|
if (timeout) clearTimeout(timeout);
|
|
778
777
|
client.pendingRequests.delete(id);
|
|
779
778
|
cleanup();
|
|
@@ -793,26 +792,33 @@ export async function sendNotification(client: LspClient, method: string, params
|
|
|
793
792
|
};
|
|
794
793
|
|
|
795
794
|
client.lastActivity = Date.now();
|
|
796
|
-
await writeMessage(client.
|
|
795
|
+
await writeMessage(client.proc.stdin, notification);
|
|
797
796
|
}
|
|
798
797
|
|
|
799
798
|
/**
|
|
800
799
|
* Shutdown all LSP clients.
|
|
801
800
|
*/
|
|
802
801
|
export function shutdownAll(): void {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
for (const pending of Array.from(client.pendingRequests.values())) {
|
|
806
|
-
pending.reject(new Error("LSP client shutdown"));
|
|
807
|
-
}
|
|
808
|
-
client.pendingRequests.clear();
|
|
802
|
+
const clientsToShutdown = Array.from(clients.values());
|
|
803
|
+
clients.clear();
|
|
809
804
|
|
|
810
|
-
|
|
811
|
-
|
|
805
|
+
const err = new Error("LSP client shutdown");
|
|
806
|
+
for (const client of clientsToShutdown) {
|
|
807
|
+
/// Reject all pending requests
|
|
808
|
+
const reqs = Array.from(client.pendingRequests.values());
|
|
809
|
+
client.pendingRequests.clear();
|
|
810
|
+
for (const pending of reqs) {
|
|
811
|
+
pending.reject(err);
|
|
812
|
+
}
|
|
812
813
|
|
|
813
|
-
|
|
814
|
+
void (async () => {
|
|
815
|
+
// Send shutdown request (best effort, don't wait)
|
|
816
|
+
const timeout = Bun.sleep(5_000);
|
|
817
|
+
const result = sendRequest(client, "shutdown", null).catch(() => {});
|
|
818
|
+
await Promise.race([result, timeout]);
|
|
819
|
+
client.proc.kill();
|
|
820
|
+
})().catch(() => {});
|
|
814
821
|
}
|
|
815
|
-
clients.clear();
|
|
816
822
|
}
|
|
817
823
|
|
|
818
824
|
/** Status of an LSP server */
|
package/src/lsp/index.ts
CHANGED
|
@@ -118,6 +118,7 @@ export async function warmupLspServers(cwd: string, options?: LspWarmupOptions):
|
|
|
118
118
|
});
|
|
119
119
|
} else {
|
|
120
120
|
const errorMsg = result.reason?.message ?? String(result.reason);
|
|
121
|
+
logger.warn("LSP server failed to start", { server: name, error: errorMsg });
|
|
121
122
|
servers.push({
|
|
122
123
|
name,
|
|
123
124
|
status: "error",
|
package/src/lsp/render.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - Collapsible/expandable views
|
|
9
9
|
*/
|
|
10
10
|
import type { RenderResultOptions } from "@oh-my-pi/pi-agent-core";
|
|
11
|
+
import { type HighlightColors, highlightCode as nativeHighlightCode, supportsLanguage } from "@oh-my-pi/pi-natives";
|
|
11
12
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
12
|
-
import { highlight, supportsLanguage } from "cli-highlight";
|
|
13
13
|
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
14
14
|
import {
|
|
15
15
|
formatExpandHint,
|
|
@@ -291,29 +291,23 @@ function renderHover(
|
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
/**
|
|
294
|
-
* Syntax highlight code using
|
|
294
|
+
* Syntax highlight code using native WASM highlighter.
|
|
295
295
|
*/
|
|
296
296
|
function highlightCode(codeText: string, language: string, theme: Theme): string[] {
|
|
297
297
|
const validLang = language && supportsLanguage(language) ? language : undefined;
|
|
298
298
|
try {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
string:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
type: (s: string) => theme.fg("syntaxType", s),
|
|
310
|
-
attr: (s: string) => theme.fg("syntaxVariable", s),
|
|
311
|
-
variable: (s: string) => theme.fg("syntaxVariable", s),
|
|
312
|
-
params: (s: string) => theme.fg("syntaxVariable", s),
|
|
313
|
-
operator: (s: string) => theme.fg("syntaxOperator", s),
|
|
314
|
-
punctuation: (s: string) => theme.fg("syntaxPunctuation", s),
|
|
299
|
+
const colors: HighlightColors = {
|
|
300
|
+
comment: theme.getFgAnsi("syntaxComment"),
|
|
301
|
+
keyword: theme.getFgAnsi("syntaxKeyword"),
|
|
302
|
+
function: theme.getFgAnsi("syntaxFunction"),
|
|
303
|
+
variable: theme.getFgAnsi("syntaxVariable"),
|
|
304
|
+
string: theme.getFgAnsi("syntaxString"),
|
|
305
|
+
number: theme.getFgAnsi("syntaxNumber"),
|
|
306
|
+
type: theme.getFgAnsi("syntaxType"),
|
|
307
|
+
operator: theme.getFgAnsi("syntaxOperator"),
|
|
308
|
+
punctuation: theme.getFgAnsi("syntaxPunctuation"),
|
|
315
309
|
};
|
|
316
|
-
return
|
|
310
|
+
return nativeHighlightCode(codeText, validLang, colors).split("\n");
|
|
317
311
|
} catch {
|
|
318
312
|
return codeText.split("\n");
|
|
319
313
|
}
|
package/src/lsp/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { ptree } from "@oh-my-pi/pi-utils";
|
|
2
3
|
import { type Static, Type } from "@sinclair/typebox";
|
|
3
|
-
import type { Subprocess } from "bun";
|
|
4
4
|
|
|
5
5
|
// =============================================================================
|
|
6
6
|
// Tool Schema
|
|
@@ -400,7 +400,7 @@ export interface LspClient {
|
|
|
400
400
|
name: string;
|
|
401
401
|
cwd: string;
|
|
402
402
|
config: ServerConfig;
|
|
403
|
-
|
|
403
|
+
proc: ptree.ChildProcess<"pipe">;
|
|
404
404
|
requestId: number;
|
|
405
405
|
diagnostics: Map<string, Diagnostic[]>;
|
|
406
406
|
diagnosticsVersion: number;
|
package/src/main.ts
CHANGED
|
@@ -87,7 +87,7 @@ async function runInteractiveMode(
|
|
|
87
87
|
versionCheckPromise: Promise<string | undefined>,
|
|
88
88
|
initialMessages: string[],
|
|
89
89
|
setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
|
90
|
-
lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined,
|
|
90
|
+
lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }> | undefined,
|
|
91
91
|
mcpManager: import("./mcp").MCPManager | undefined,
|
|
92
92
|
initialMessage?: string,
|
|
93
93
|
initialImages?: ImageContent[],
|
|
@@ -5,7 +5,6 @@ import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
|
|
5
5
|
import { Loader, Markdown, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { $ } from "bun";
|
|
7
7
|
import { nanoid } from "nanoid";
|
|
8
|
-
import { getDebugLogPath } from "../../config";
|
|
9
8
|
import { loadCustomShare } from "../../export/custom-share";
|
|
10
9
|
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
11
10
|
import { getGatewayStatus } from "../../ipy/gateway-coordinator";
|
|
@@ -266,7 +265,9 @@ export class CommandController {
|
|
|
266
265
|
info += `\n${theme.bold("LSP Servers")}\n`;
|
|
267
266
|
for (const server of this.ctx.lspServers) {
|
|
268
267
|
const statusColor = server.status === "ready" ? "success" : "error";
|
|
269
|
-
|
|
268
|
+
const statusText =
|
|
269
|
+
server.status === "error" && server.error ? `${server.status}: ${server.error}` : server.status;
|
|
270
|
+
info += `${theme.fg("dim", `${server.name}:`)} ${theme.fg(statusColor, statusText)} ${theme.fg("dim", `(${server.fileTypes.join(", ")})`)}\n`;
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
|
|
@@ -447,46 +448,6 @@ export class CommandController {
|
|
|
447
448
|
this.ctx.ui.requestRender();
|
|
448
449
|
}
|
|
449
450
|
|
|
450
|
-
async handleDebugCommand(): Promise<void> {
|
|
451
|
-
const width = this.ctx.ui.terminal.columns;
|
|
452
|
-
const allLines = this.ctx.ui.render(width);
|
|
453
|
-
|
|
454
|
-
const debugLogPath = getDebugLogPath();
|
|
455
|
-
const debugData = [
|
|
456
|
-
`Debug output at ${new Date().toISOString()}`,
|
|
457
|
-
`Terminal width: ${width}`,
|
|
458
|
-
`Total lines: ${allLines.length}`,
|
|
459
|
-
"",
|
|
460
|
-
"=== All rendered lines with visible widths ===",
|
|
461
|
-
...allLines.map((line, idx) => {
|
|
462
|
-
const vw = visibleWidth(line);
|
|
463
|
-
const escaped = JSON.stringify(line);
|
|
464
|
-
return `[${idx}] (w=${vw}) ${escaped}`;
|
|
465
|
-
}),
|
|
466
|
-
"",
|
|
467
|
-
"=== Agent messages (JSONL) ===",
|
|
468
|
-
...this.ctx.session.messages.map(msg => JSON.stringify(msg)),
|
|
469
|
-
"",
|
|
470
|
-
].join("\n");
|
|
471
|
-
|
|
472
|
-
try {
|
|
473
|
-
await Bun.write(debugLogPath, debugData);
|
|
474
|
-
} catch (error) {
|
|
475
|
-
this.ctx.showError(`Failed to write debug log: ${error instanceof Error ? error.message : String(error)}`);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
480
|
-
this.ctx.chatContainer.addChild(
|
|
481
|
-
new Text(
|
|
482
|
-
`${theme.fg("accent", `${theme.status.success} Debug log written`)}\n${theme.fg("muted", debugLogPath)}`,
|
|
483
|
-
1,
|
|
484
|
-
1,
|
|
485
|
-
),
|
|
486
|
-
);
|
|
487
|
-
this.ctx.ui.requestRender();
|
|
488
|
-
}
|
|
489
|
-
|
|
490
451
|
handleArminSaysHi(): void {
|
|
491
452
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
492
453
|
this.ctx.chatContainer.addChild(new ArminComponent(this.ctx.ui));
|
|
@@ -64,7 +64,7 @@ export class InputController {
|
|
|
64
64
|
this.ctx.editor.onAltP = () => this.ctx.showModelSelector({ temporaryOnly: true });
|
|
65
65
|
|
|
66
66
|
// Global debug handler on TUI (works regardless of focus)
|
|
67
|
-
this.ctx.ui.onDebug = () =>
|
|
67
|
+
this.ctx.ui.onDebug = () => this.ctx.showDebugSelector();
|
|
68
68
|
this.ctx.editor.onCtrlL = () => this.ctx.showModelSelector();
|
|
69
69
|
this.ctx.editor.onCtrlR = () => this.ctx.showHistorySearch();
|
|
70
70
|
this.ctx.editor.onCtrlT = () => this.ctx.toggleTodoExpansion();
|
|
@@ -293,7 +293,7 @@ export class InputController {
|
|
|
293
293
|
return;
|
|
294
294
|
}
|
|
295
295
|
if (text === "/debug") {
|
|
296
|
-
|
|
296
|
+
this.ctx.showDebugSelector();
|
|
297
297
|
this.ctx.editor.setText("");
|
|
298
298
|
return;
|
|
299
299
|
}
|
|
@@ -3,6 +3,7 @@ import type { OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { getAgentDbPath } from "../../config";
|
|
6
|
+
import { DebugSelectorComponent } from "../../debug";
|
|
6
7
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
7
8
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
8
9
|
import { ExtensionDashboard } from "../../modes/components/extensions";
|
|
@@ -625,4 +626,11 @@ export class SelectorController {
|
|
|
625
626
|
return { component: selector, focus: selector };
|
|
626
627
|
});
|
|
627
628
|
}
|
|
629
|
+
|
|
630
|
+
showDebugSelector(): void {
|
|
631
|
+
this.showSelector(done => {
|
|
632
|
+
const selector = new DebugSelectorComponent(this.ctx, done);
|
|
633
|
+
return { component: selector, focus: selector };
|
|
634
|
+
});
|
|
635
|
+
}
|
|
628
636
|
}
|
|
@@ -134,8 +134,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
134
134
|
private planModePreviousTools: string[] | undefined;
|
|
135
135
|
private planModePreviousModel: Model<any> | undefined;
|
|
136
136
|
private planModeHasEntered = false;
|
|
137
|
-
public readonly lspServers:
|
|
138
|
-
|
|
137
|
+
public readonly lspServers:
|
|
138
|
+
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>
|
|
139
|
+
| undefined = undefined;
|
|
139
140
|
public mcpManager?: import("../mcp").MCPManager;
|
|
140
141
|
private readonly toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
141
142
|
|
|
@@ -151,7 +152,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
151
152
|
version: string,
|
|
152
153
|
changelogMarkdown: string | undefined = undefined,
|
|
153
154
|
setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
|
|
154
|
-
lspServers:
|
|
155
|
+
lspServers:
|
|
156
|
+
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>
|
|
157
|
+
| undefined = undefined,
|
|
155
158
|
mcpManager?: import("../mcp").MCPManager,
|
|
156
159
|
) {
|
|
157
160
|
this.session = session;
|
|
@@ -861,8 +864,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
861
864
|
return this.commandController.handleForkCommand();
|
|
862
865
|
}
|
|
863
866
|
|
|
864
|
-
|
|
865
|
-
|
|
867
|
+
showDebugSelector(): void {
|
|
868
|
+
this.selectorController.showDebugSelector();
|
|
866
869
|
}
|
|
867
870
|
|
|
868
871
|
handleArminSaysHi(): void {
|
|
@@ -524,7 +524,7 @@ export class RpcClient {
|
|
|
524
524
|
|
|
525
525
|
// Write to stdin after registering the handler
|
|
526
526
|
const stdin = this.process!.stdin as import("bun").FileSink;
|
|
527
|
-
stdin.write(
|
|
527
|
+
stdin.write(`${JSON.stringify(fullCommand)}\n`);
|
|
528
528
|
// flush() returns number | Promise<number> - handle both cases
|
|
529
529
|
const flushResult = stdin.flush();
|
|
530
530
|
if (flushResult instanceof Promise) {
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type HighlightColors as NativeHighlightColors,
|
|
5
|
+
highlightCode as nativeHighlightCode,
|
|
6
|
+
supportsLanguage as nativeSupportsLanguage,
|
|
7
|
+
} from "@oh-my-pi/pi-natives";
|
|
3
8
|
import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
|
|
4
9
|
import { adjustHsv, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
5
10
|
import { type Static, Type } from "@sinclair/typebox";
|
|
6
11
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
7
12
|
import chalk from "chalk";
|
|
8
|
-
import { highlight, supportsLanguage } from "cli-highlight";
|
|
9
13
|
import { getCustomThemesDir } from "../../config";
|
|
10
14
|
// Embed theme JSON files at build time
|
|
11
15
|
import darkThemeJson from "./dark.json" with { type: "json" };
|
|
@@ -2029,37 +2033,25 @@ export async function getThemeExportColors(themeName?: string): Promise<{
|
|
|
2029
2033
|
// TUI Helpers
|
|
2030
2034
|
// ============================================================================
|
|
2031
2035
|
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
attr: (s: string) => t.fg("syntaxVariable", s),
|
|
2050
|
-
variable: (s: string) => t.fg("syntaxVariable", s),
|
|
2051
|
-
params: (s: string) => t.fg("syntaxVariable", s),
|
|
2052
|
-
operator: (s: string) => t.fg("syntaxOperator", s),
|
|
2053
|
-
punctuation: (s: string) => t.fg("syntaxPunctuation", s),
|
|
2054
|
-
};
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
function getCliHighlightTheme(t: Theme): CliHighlightTheme {
|
|
2058
|
-
if (cachedHighlightThemeFor !== t || !cachedCliHighlightTheme) {
|
|
2059
|
-
cachedHighlightThemeFor = t;
|
|
2060
|
-
cachedCliHighlightTheme = buildCliHighlightTheme(t);
|
|
2036
|
+
let cachedHighlightColorsFor: Theme | undefined;
|
|
2037
|
+
let cachedHighlightColors: NativeHighlightColors | undefined;
|
|
2038
|
+
|
|
2039
|
+
function getHighlightColors(t: Theme): NativeHighlightColors {
|
|
2040
|
+
if (cachedHighlightColorsFor !== t || !cachedHighlightColors) {
|
|
2041
|
+
cachedHighlightColorsFor = t;
|
|
2042
|
+
cachedHighlightColors = {
|
|
2043
|
+
comment: t.getFgAnsi("syntaxComment"),
|
|
2044
|
+
keyword: t.getFgAnsi("syntaxKeyword"),
|
|
2045
|
+
function: t.getFgAnsi("syntaxFunction"),
|
|
2046
|
+
variable: t.getFgAnsi("syntaxVariable"),
|
|
2047
|
+
string: t.getFgAnsi("syntaxString"),
|
|
2048
|
+
number: t.getFgAnsi("syntaxNumber"),
|
|
2049
|
+
type: t.getFgAnsi("syntaxType"),
|
|
2050
|
+
operator: t.getFgAnsi("syntaxOperator"),
|
|
2051
|
+
punctuation: t.getFgAnsi("syntaxPunctuation"),
|
|
2052
|
+
};
|
|
2061
2053
|
}
|
|
2062
|
-
return
|
|
2054
|
+
return cachedHighlightColors;
|
|
2063
2055
|
}
|
|
2064
2056
|
|
|
2065
2057
|
/**
|
|
@@ -2067,15 +2059,9 @@ function getCliHighlightTheme(t: Theme): CliHighlightTheme {
|
|
|
2067
2059
|
* Returns array of highlighted lines.
|
|
2068
2060
|
*/
|
|
2069
2061
|
export function highlightCode(code: string, lang?: string): string[] {
|
|
2070
|
-
|
|
2071
|
-
const validLang = lang && supportsLanguage(lang) ? lang : undefined;
|
|
2072
|
-
const opts = {
|
|
2073
|
-
language: validLang,
|
|
2074
|
-
ignoreIllegals: true,
|
|
2075
|
-
theme: getCliHighlightTheme(theme),
|
|
2076
|
-
};
|
|
2062
|
+
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2077
2063
|
try {
|
|
2078
|
-
return
|
|
2064
|
+
return nativeHighlightCode(code, validLang, getHighlightColors(theme)).split("\n");
|
|
2079
2065
|
} catch {
|
|
2080
2066
|
return code.split("\n");
|
|
2081
2067
|
}
|
|
@@ -2212,15 +2198,9 @@ export function getMarkdownTheme(): MarkdownTheme {
|
|
|
2212
2198
|
symbols: getSymbolTheme(),
|
|
2213
2199
|
getMermaidImage,
|
|
2214
2200
|
highlightCode: (code: string, lang?: string): string[] => {
|
|
2215
|
-
|
|
2216
|
-
const validLang = lang && supportsLanguage(lang) ? lang : undefined;
|
|
2217
|
-
const opts = {
|
|
2218
|
-
language: validLang,
|
|
2219
|
-
ignoreIllegals: true,
|
|
2220
|
-
theme: getCliHighlightTheme(theme),
|
|
2221
|
-
};
|
|
2201
|
+
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2222
2202
|
try {
|
|
2223
|
-
return
|
|
2203
|
+
return nativeHighlightCode(code, validLang, getHighlightColors(theme)).split("\n");
|
|
2224
2204
|
} catch {
|
|
2225
2205
|
return code.split("\n").map(line => theme.fg("mdCodeBlock", line));
|
|
2226
2206
|
}
|
package/src/modes/types.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface InteractiveModeContext {
|
|
|
51
51
|
agent: AgentSession["agent"];
|
|
52
52
|
historyStorage?: HistoryStorage;
|
|
53
53
|
mcpManager?: MCPManager;
|
|
54
|
-
lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>;
|
|
54
|
+
lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>;
|
|
55
55
|
|
|
56
56
|
// State
|
|
57
57
|
isInitialized: boolean;
|
|
@@ -144,7 +144,6 @@ export interface InteractiveModeContext {
|
|
|
144
144
|
handleDumpCommand(): Promise<void>;
|
|
145
145
|
handleClearCommand(): Promise<void>;
|
|
146
146
|
handleForkCommand(): Promise<void>;
|
|
147
|
-
handleDebugCommand(): Promise<void>;
|
|
148
147
|
handleArminSaysHi(): void;
|
|
149
148
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
150
149
|
handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
|
|
@@ -164,6 +163,7 @@ export interface InteractiveModeContext {
|
|
|
164
163
|
handleResumeSession(sessionPath: string): Promise<void>;
|
|
165
164
|
showOAuthSelector(mode: "login" | "logout"): Promise<void>;
|
|
166
165
|
showHookConfirm(title: string, message: string): Promise<boolean>;
|
|
166
|
+
showDebugSelector(): void;
|
|
167
167
|
|
|
168
168
|
// Input handling
|
|
169
169
|
handleCtrlC(): void;
|
package/src/sdk.ts
CHANGED
|
@@ -204,7 +204,7 @@ export interface CreateAgentSessionResult {
|
|
|
204
204
|
/** Warning if session was restored with a different model than saved */
|
|
205
205
|
modelFallbackMessage?: string;
|
|
206
206
|
/** LSP servers that were warmed up at startup */
|
|
207
|
-
lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>;
|
|
207
|
+
lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// Re-exports
|
|
@@ -138,7 +138,18 @@ export class AgentStorage {
|
|
|
138
138
|
|
|
139
139
|
private constructor(dbPath: string) {
|
|
140
140
|
this.ensureDir(dbPath);
|
|
141
|
-
|
|
141
|
+
try {
|
|
142
|
+
this.db = new Database(dbPath);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const dir = path.dirname(dbPath);
|
|
145
|
+
const dirExists = fs.existsSync(dir);
|
|
146
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Failed to open agent database at '${dbPath}': ${errMsg}\n` +
|
|
149
|
+
`Directory '${dir}' exists: ${dirExists}\n` +
|
|
150
|
+
`Ensure the directory is writable and not corrupted.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
142
153
|
|
|
143
154
|
this.initializeSchema();
|
|
144
155
|
this.hardenPermissions(dbPath);
|
|
@@ -537,7 +548,20 @@ CREATE TABLE settings (
|
|
|
537
548
|
* @param dbPath - Path to the database file
|
|
538
549
|
*/
|
|
539
550
|
private ensureDir(dbPath: string): void {
|
|
540
|
-
|
|
551
|
+
const dir = path.dirname(dbPath);
|
|
552
|
+
try {
|
|
553
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
554
|
+
} catch (err) {
|
|
555
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
556
|
+
// EEXIST is fine - directory already exists
|
|
557
|
+
if (code !== "EEXIST") {
|
|
558
|
+
throw new Error(`Failed to create agent storage directory '${dir}': ${code || err}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Verify directory was created
|
|
562
|
+
if (!fs.existsSync(dir)) {
|
|
563
|
+
throw new Error(`Agent storage directory '${dir}' does not exist after creation attempt`);
|
|
564
|
+
}
|
|
541
565
|
}
|
|
542
566
|
|
|
543
567
|
private hardenPermissions(dbPath: string): void {
|