@oh-my-pi/pi-coding-agent 6.7.67 → 6.8.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 +28 -0
- package/package.json +6 -7
- package/src/cli/session-picker.ts +27 -28
- package/src/cli/setup-cli.ts +7 -16
- package/src/cli/update-cli.ts +1 -1
- package/src/config.ts +1 -1
- package/src/core/agent-session.ts +202 -37
- package/src/core/agent-storage.ts +1 -1
- package/src/core/auth-storage.ts +15 -25
- package/src/core/bash-executor.ts +63 -105
- package/src/core/custom-commands/loader.ts +1 -1
- package/src/core/custom-tools/loader.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -2
- package/src/core/exec.ts +16 -100
- package/src/core/extensions/index.ts +1 -7
- package/src/core/extensions/loader.ts +1 -1
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +2 -2
- package/src/core/extensions/wrapper.ts +15 -20
- package/src/core/frontmatter.ts +1 -1
- package/src/core/history-storage.ts +3 -6
- package/src/core/hooks/index.ts +2 -2
- package/src/core/hooks/loader.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +14 -26
- package/src/core/hooks/types.ts +1 -2
- package/src/core/keybindings.ts +1 -1
- package/src/core/mcp/client.ts +13 -13
- package/src/core/mcp/json-rpc.ts +1 -1
- package/src/core/mcp/loader.ts +1 -1
- package/src/core/mcp/manager.ts +2 -2
- package/src/core/mcp/tool-cache.ts +1 -1
- package/src/core/mcp/transports/http.ts +32 -70
- package/src/core/model-registry.ts +1 -1
- package/src/core/plugins/installer.ts +13 -11
- package/src/core/prompt-templates.ts +4 -9
- package/src/core/python-executor.ts +23 -18
- package/src/core/python-gateway-coordinator.ts +29 -28
- package/src/core/python-kernel.ts +230 -211
- package/src/core/sdk.ts +10 -13
- package/src/core/session-manager.ts +1 -1
- package/src/core/settings-manager.ts +22 -9
- package/src/core/skills.ts +1 -1
- package/src/core/ssh/connection-manager.ts +19 -33
- package/src/core/ssh/ssh-executor.ts +39 -35
- package/src/core/ssh/sshfs-mount.ts +14 -33
- package/src/core/storage-migration.ts +1 -1
- package/src/core/streaming-output.ts +183 -127
- package/src/core/system-prompt.ts +119 -79
- package/src/core/title-generator.ts +1 -1
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +3 -3
- package/src/core/tools/calculator.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +1 -1
- package/src/core/tools/exa/render.ts +1 -1
- package/src/core/tools/find.ts +39 -71
- package/src/core/tools/gemini-image.ts +1 -1
- package/src/core/tools/grep.ts +88 -100
- package/src/core/tools/index.ts +1 -1
- package/src/core/tools/ls.ts +1 -1
- package/src/core/tools/lsp/client.ts +50 -50
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +1 -1
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/index.ts +2 -4
- package/src/core/tools/lsp/lspmux.ts +1 -1
- package/src/core/tools/lsp/rust-analyzer.ts +2 -2
- package/src/core/tools/lsp/utils.ts +0 -14
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/patch/shared.ts +3 -4
- package/src/core/tools/python.ts +3 -3
- package/src/core/tools/read.ts +29 -68
- package/src/core/tools/render-utils.ts +0 -5
- package/src/core/tools/ssh.ts +3 -3
- package/src/core/tools/task/model-resolver.ts +7 -9
- package/src/core/tools/task/worker.ts +144 -139
- package/src/core/tools/todo-write.ts +1 -1
- package/src/core/tools/truncate.ts +2 -2
- package/src/core/tools/web-fetch.ts +13 -15
- package/src/core/tools/web-scrapers/types.ts +1 -3
- package/src/core/tools/web-scrapers/utils.ts +14 -13
- package/src/core/tools/web-scrapers/youtube.ts +39 -12
- package/src/core/tools/web-search/auth.ts +9 -45
- package/src/core/tools/write.ts +1 -1
- package/src/core/ttsr.ts +1 -1
- package/src/core/utils.ts +1 -187
- package/src/core/voice-controller.ts +1 -1
- package/src/core/voice-supervisor.ts +11 -38
- package/src/core/voice.ts +1 -8
- package/src/discovery/codex.ts +1 -1
- package/src/index.ts +4 -4
- package/src/main.ts +5 -10
- package/src/migrations.ts +1 -1
- package/src/modes/index.ts +7 -40
- package/src/modes/interactive/components/extensions/state-manager.ts +1 -1
- package/src/modes/interactive/components/hook-editor.ts +12 -9
- package/src/modes/interactive/components/login-dialog.ts +24 -11
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line.ts +36 -35
- package/src/modes/interactive/components/todo-display.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +1 -1
- package/src/modes/interactive/controllers/command-controller.ts +50 -84
- package/src/modes/interactive/controllers/extension-ui-controller.ts +76 -76
- package/src/modes/interactive/controllers/input-controller.ts +12 -11
- package/src/modes/interactive/interactive-mode.ts +10 -11
- package/src/modes/interactive/theme/theme.ts +1 -1
- package/src/modes/interactive/types.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +91 -121
- package/src/modes/rpc/rpc-mode.ts +71 -79
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/utils/clipboard.ts +57 -141
- package/src/utils/shell-snapshot.ts +12 -60
- package/src/utils/shell.ts +35 -56
- package/src/utils/tools-manager.ts +42 -71
- package/src/core/logger.ts +0 -111
- package/src/modes/cleanup.ts +0 -23
package/src/core/tools/grep.ts
CHANGED
|
@@ -3,21 +3,22 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
3
3
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
|
+
import { ptree, readLines } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import { Type } from "@sinclair/typebox";
|
|
7
|
-
import
|
|
8
|
+
import { $ } from "bun";
|
|
8
9
|
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
9
10
|
import grepDescription from "../../prompts/tools/grep.md" with { type: "text" };
|
|
10
11
|
import { ensureTool } from "../../utils/tools-manager";
|
|
11
12
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
12
13
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
13
|
-
import {
|
|
14
|
+
import { untilAborted } from "../utils";
|
|
14
15
|
import type { ToolSession } from "./index";
|
|
15
16
|
import { resolveToCwd } from "./path-utils";
|
|
16
|
-
import {
|
|
17
|
+
import { PREVIEW_LIMITS, ToolUIKit } from "./render-utils";
|
|
17
18
|
import {
|
|
18
19
|
DEFAULT_MAX_BYTES,
|
|
20
|
+
DEFAULT_MAX_COLUMN,
|
|
19
21
|
formatSize,
|
|
20
|
-
GREP_MAX_LINE_LENGTH,
|
|
21
22
|
type TruncationResult,
|
|
22
23
|
truncateHead,
|
|
23
24
|
truncateLine,
|
|
@@ -118,10 +119,38 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
118
119
|
private readonly session: ToolSession;
|
|
119
120
|
private readonly ops: GrepOperations;
|
|
120
121
|
|
|
122
|
+
private readonly rgPath: Promise<string | undefined>;
|
|
123
|
+
|
|
121
124
|
constructor(session: ToolSession, options?: GrepToolOptions) {
|
|
122
125
|
this.session = session;
|
|
123
126
|
this.ops = options?.operations ?? defaultGrepOperations;
|
|
124
127
|
this.description = renderPromptTemplate(grepDescription);
|
|
128
|
+
this.rgPath = ensureTool("rg", true);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Validates a pattern against ripgrep's regex engine.
|
|
133
|
+
* Uses a quick dry-run against /dev/null to check for parse errors.
|
|
134
|
+
*/
|
|
135
|
+
private async validateRegexPattern(pattern: string): Promise<{ valid: boolean; error?: string }> {
|
|
136
|
+
const rgPath = await this.rgPath;
|
|
137
|
+
if (!rgPath) {
|
|
138
|
+
return { valid: true }; // Can't validate, assume valid
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Run ripgrep against /dev/null with the pattern - this validates regex syntax
|
|
142
|
+
// without searching any files
|
|
143
|
+
const result = await $`${rgPath} --no-config --quiet -- ${pattern} /dev/null`.quiet().nothrow();
|
|
144
|
+
const stderr = result.stderr?.toString() ?? "";
|
|
145
|
+
const exitCode = result.exitCode ?? 0;
|
|
146
|
+
|
|
147
|
+
// Exit code 1 = no matches (pattern is valid), 0 = matches found
|
|
148
|
+
// Exit code 2 = error (often regex parse error)
|
|
149
|
+
if (exitCode === 2 && stderr.includes("regex parse error")) {
|
|
150
|
+
return { valid: false, error: stderr.trim() };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { valid: true };
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
public async execute(
|
|
@@ -148,7 +177,17 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
148
177
|
} = params;
|
|
149
178
|
|
|
150
179
|
return untilAborted(signal, async () => {
|
|
151
|
-
|
|
180
|
+
// Auto-detect invalid regex patterns and switch to literal mode
|
|
181
|
+
// This handles cases like "abort(" which would cause ripgrep regex parse errors
|
|
182
|
+
let useLiteral = literal ?? false;
|
|
183
|
+
if (!useLiteral) {
|
|
184
|
+
const validation = await this.validateRegexPattern(pattern);
|
|
185
|
+
if (!validation.valid) {
|
|
186
|
+
useLiteral = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const rgPath = await this.rgPath;
|
|
152
191
|
if (!rgPath) {
|
|
153
192
|
throw new Error("ripgrep (rg) is not available and could not be downloaded");
|
|
154
193
|
}
|
|
@@ -221,7 +260,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
221
260
|
args.push("--multiline");
|
|
222
261
|
}
|
|
223
262
|
|
|
224
|
-
if (
|
|
263
|
+
if (useLiteral) {
|
|
225
264
|
args.push("--fixed-strings");
|
|
226
265
|
}
|
|
227
266
|
|
|
@@ -235,17 +274,11 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
235
274
|
|
|
236
275
|
args.push("--", pattern, searchPath);
|
|
237
276
|
|
|
238
|
-
const child
|
|
239
|
-
stdin: "ignore",
|
|
240
|
-
stdout: "pipe",
|
|
241
|
-
stderr: "pipe",
|
|
242
|
-
});
|
|
277
|
+
const child = ptree.cspawn([rgPath, ...args], { signal });
|
|
243
278
|
|
|
244
|
-
let stderr = "";
|
|
245
279
|
let matchCount = 0;
|
|
246
280
|
let matchLimitReached = false;
|
|
247
281
|
let linesTruncated = false;
|
|
248
|
-
let aborted = false;
|
|
249
282
|
let killedDueToLimit = false;
|
|
250
283
|
const outputLines: string[] = [];
|
|
251
284
|
const files = new Set<string>();
|
|
@@ -265,49 +298,18 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
265
298
|
fileMatchCounts.set(relative, (fileMatchCounts.get(relative) ?? 0) + 1);
|
|
266
299
|
};
|
|
267
300
|
|
|
268
|
-
const stopChild = (dueToLimit: boolean = false) => {
|
|
269
|
-
killedDueToLimit = dueToLimit;
|
|
270
|
-
child.kill();
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
using signalScope = new ScopeSignal(signal ? { signal } : undefined);
|
|
274
|
-
signalScope.catch(() => {
|
|
275
|
-
aborted = true;
|
|
276
|
-
stopChild();
|
|
277
|
-
});
|
|
278
|
-
|
|
279
301
|
// For simple output modes (files_with_matches, count), process text directly
|
|
280
302
|
if (effectiveOutputMode === "files_with_matches" || effectiveOutputMode === "count") {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
(async () => {
|
|
288
|
-
while (true) {
|
|
289
|
-
const { done, value } = await stdoutReader.read();
|
|
290
|
-
if (done) break;
|
|
291
|
-
stdout += decoder.decode(value, { stream: true });
|
|
292
|
-
}
|
|
293
|
-
})(),
|
|
294
|
-
(async () => {
|
|
295
|
-
while (true) {
|
|
296
|
-
const { done, value } = await stderrReader.read();
|
|
297
|
-
if (done) break;
|
|
298
|
-
stderr += decoder.decode(value, { stream: true });
|
|
299
|
-
}
|
|
300
|
-
})(),
|
|
301
|
-
]);
|
|
302
|
-
|
|
303
|
-
const exitCode = await child.exited;
|
|
304
|
-
|
|
305
|
-
if (aborted) {
|
|
306
|
-
throw new Error("Operation aborted");
|
|
307
|
-
}
|
|
303
|
+
const stdout = await child.text().catch((x) => {
|
|
304
|
+
if (x instanceof ptree.Exception && x.exitCode === 1) {
|
|
305
|
+
return "";
|
|
306
|
+
}
|
|
307
|
+
return Promise.reject(x);
|
|
308
|
+
});
|
|
308
309
|
|
|
310
|
+
const exitCode = child.exitCode ?? 0;
|
|
309
311
|
if (exitCode !== 0 && exitCode !== 1) {
|
|
310
|
-
const errorMsg =
|
|
312
|
+
const errorMsg = child.peekStderr().trim() || `ripgrep exited with code ${exitCode}`;
|
|
311
313
|
throw new Error(errorMsg);
|
|
312
314
|
}
|
|
313
315
|
|
|
@@ -484,57 +486,43 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
484
486
|
|
|
485
487
|
if (matchCount >= effectiveLimit) {
|
|
486
488
|
matchLimitReached = true;
|
|
487
|
-
|
|
489
|
+
killedDueToLimit = true;
|
|
490
|
+
child.kill("SIGKILL");
|
|
488
491
|
}
|
|
489
492
|
}
|
|
490
493
|
};
|
|
491
494
|
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
stdoutBuffer += decoder.decode(value, { stream: true });
|
|
506
|
-
const lines = stdoutBuffer.split("\n");
|
|
507
|
-
// Keep the last incomplete line in the buffer
|
|
508
|
-
stdoutBuffer = lines.pop() ?? "";
|
|
509
|
-
|
|
510
|
-
for (const line of lines) {
|
|
511
|
-
await processLine(line);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
// Process any remaining content
|
|
515
|
-
if (stdoutBuffer.trim()) {
|
|
516
|
-
await processLine(stdoutBuffer);
|
|
517
|
-
}
|
|
518
|
-
})(),
|
|
519
|
-
// Collect stderr
|
|
520
|
-
(async () => {
|
|
521
|
-
while (true) {
|
|
522
|
-
const { done, value } = await stderrReader.read();
|
|
523
|
-
if (done) break;
|
|
524
|
-
stderr += decoder.decode(value, { stream: true });
|
|
525
|
-
}
|
|
526
|
-
})(),
|
|
527
|
-
]);
|
|
528
|
-
|
|
529
|
-
const exitCode = await child.exited;
|
|
530
|
-
|
|
531
|
-
if (aborted) {
|
|
532
|
-
throw new Error("Operation aborted");
|
|
495
|
+
// Process stdout line by line
|
|
496
|
+
try {
|
|
497
|
+
for await (const line of readLines(child.stdout)) {
|
|
498
|
+
await processLine(line);
|
|
499
|
+
}
|
|
500
|
+
} catch (err) {
|
|
501
|
+
if (err instanceof ptree.Exception && err.aborted) {
|
|
502
|
+
throw new Error("Operation aborted");
|
|
503
|
+
}
|
|
504
|
+
// Stream may close early if we killed due to limit - that's ok
|
|
505
|
+
if (!killedDueToLimit) {
|
|
506
|
+
throw err;
|
|
507
|
+
}
|
|
533
508
|
}
|
|
534
509
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
510
|
+
// Wait for process to exit
|
|
511
|
+
try {
|
|
512
|
+
await child.exited;
|
|
513
|
+
} catch (err) {
|
|
514
|
+
if (err instanceof ptree.Exception) {
|
|
515
|
+
if (err.aborted) {
|
|
516
|
+
throw new Error("Operation aborted");
|
|
517
|
+
}
|
|
518
|
+
// Non-zero exit is ok if we killed due to limit or exit code 1 (no matches)
|
|
519
|
+
if (!killedDueToLimit && err.exitCode !== 1) {
|
|
520
|
+
const errorMsg = child.peekStderr().trim() || `ripgrep exited with code ${err.exitCode}`;
|
|
521
|
+
throw new Error(errorMsg);
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
throw err;
|
|
525
|
+
}
|
|
538
526
|
}
|
|
539
527
|
|
|
540
528
|
if (matchCount === 0) {
|
|
@@ -596,7 +584,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
596
584
|
}
|
|
597
585
|
|
|
598
586
|
if (linesTruncated) {
|
|
599
|
-
notices.push(`Some lines truncated to ${
|
|
587
|
+
notices.push(`Some lines truncated to ${DEFAULT_MAX_COLUMN} chars. Use read tool to see full lines`);
|
|
600
588
|
details.linesTruncated = true;
|
|
601
589
|
}
|
|
602
590
|
|
|
@@ -636,7 +624,7 @@ const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
|
636
624
|
export const grepToolRenderer = {
|
|
637
625
|
inline: true,
|
|
638
626
|
renderCall(args: GrepRenderArgs, uiTheme: Theme): Component {
|
|
639
|
-
const ui =
|
|
627
|
+
const ui = new ToolUIKit(uiTheme);
|
|
640
628
|
const label = ui.title("Grep");
|
|
641
629
|
let text = `${uiTheme.format.bullet} ${label} ${uiTheme.fg("accent", args.pattern || "?")}`;
|
|
642
630
|
|
|
@@ -665,7 +653,7 @@ export const grepToolRenderer = {
|
|
|
665
653
|
{ expanded }: RenderResultOptions,
|
|
666
654
|
uiTheme: Theme,
|
|
667
655
|
): Component {
|
|
668
|
-
const ui =
|
|
656
|
+
const ui = new ToolUIKit(uiTheme);
|
|
669
657
|
const details = result.details;
|
|
670
658
|
|
|
671
659
|
if (result.isError || details?.error) {
|
package/src/core/tools/index.ts
CHANGED
|
@@ -62,8 +62,8 @@ export {
|
|
|
62
62
|
export { WriteTool, type WriteToolDetails } from "./write";
|
|
63
63
|
|
|
64
64
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
65
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
65
66
|
import type { EventBus } from "../event-bus";
|
|
66
|
-
import { logger } from "../logger";
|
|
67
67
|
import { getPreludeDocs, warmPythonEnvironment } from "../python-executor";
|
|
68
68
|
import { checkPythonKernelAvailability } from "../python-kernel";
|
|
69
69
|
import type { BashInterceptorRule } from "../settings-manager";
|
package/src/core/tools/ls.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import nodePath from "node:path";
|
|
2
2
|
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { Type } from "@sinclair/typebox";
|
|
5
6
|
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
6
7
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
7
|
-
import { untilAborted } from "../utils";
|
|
8
8
|
import type { ToolSession } from "./index";
|
|
9
9
|
import { resolveToCwd } from "./path-utils";
|
|
10
10
|
import {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import { logger } from "
|
|
2
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { applyWorkspaceEdit } from "./edits";
|
|
4
4
|
import { getLspmuxCommand, isLspmuxSupported } from "./lspmux";
|
|
5
5
|
import type {
|
|
@@ -711,63 +711,63 @@ export async function sendRequest(
|
|
|
711
711
|
|
|
712
712
|
client.lastActivity = Date.now();
|
|
713
713
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (signal) {
|
|
718
|
-
signal.removeEventListener("abort", abortHandler);
|
|
719
|
-
}
|
|
720
|
-
};
|
|
721
|
-
const abortHandler = () => {
|
|
722
|
-
if (client.pendingRequests.has(id)) {
|
|
723
|
-
client.pendingRequests.delete(id);
|
|
724
|
-
}
|
|
725
|
-
if (timeout) clearTimeout(timeout);
|
|
726
|
-
cleanup();
|
|
727
|
-
const reason = signal?.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
728
|
-
reject(reason);
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
// Set timeout
|
|
732
|
-
timeout = setTimeout(() => {
|
|
733
|
-
if (client.pendingRequests.has(id)) {
|
|
734
|
-
client.pendingRequests.delete(id);
|
|
735
|
-
const err = new Error(`LSP request ${method} timed out`);
|
|
736
|
-
cleanup();
|
|
737
|
-
reject(err);
|
|
738
|
-
}
|
|
739
|
-
}, timeoutMs);
|
|
714
|
+
const { promise, resolve, reject } = Promise.withResolvers<unknown>();
|
|
715
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
716
|
+
const cleanup = () => {
|
|
740
717
|
if (signal) {
|
|
741
|
-
signal.
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
718
|
+
signal.removeEventListener("abort", abortHandler);
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
const abortHandler = () => {
|
|
722
|
+
if (client.pendingRequests.has(id)) {
|
|
723
|
+
client.pendingRequests.delete(id);
|
|
746
724
|
}
|
|
725
|
+
if (timeout) clearTimeout(timeout);
|
|
726
|
+
cleanup();
|
|
727
|
+
const reason = signal?.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
728
|
+
reject(reason);
|
|
729
|
+
};
|
|
747
730
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
731
|
+
// Set timeout
|
|
732
|
+
timeout = setTimeout(() => {
|
|
733
|
+
if (client.pendingRequests.has(id)) {
|
|
734
|
+
client.pendingRequests.delete(id);
|
|
735
|
+
const err = new Error(`LSP request ${method} timed out`);
|
|
736
|
+
cleanup();
|
|
737
|
+
reject(err);
|
|
738
|
+
}
|
|
739
|
+
}, timeoutMs);
|
|
740
|
+
if (signal) {
|
|
741
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
742
|
+
if (signal.aborted) {
|
|
743
|
+
abortHandler();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
762
747
|
|
|
763
|
-
|
|
764
|
-
|
|
748
|
+
// Register pending request with timeout wrapper
|
|
749
|
+
client.pendingRequests.set(id, {
|
|
750
|
+
resolve: (result) => {
|
|
751
|
+
if (timeout) clearTimeout(timeout);
|
|
752
|
+
cleanup();
|
|
753
|
+
resolve(result);
|
|
754
|
+
},
|
|
755
|
+
reject: (err) => {
|
|
765
756
|
if (timeout) clearTimeout(timeout);
|
|
766
|
-
client.pendingRequests.delete(id);
|
|
767
757
|
cleanup();
|
|
768
758
|
reject(err);
|
|
769
|
-
}
|
|
759
|
+
},
|
|
760
|
+
method,
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Write request
|
|
764
|
+
writeMessage(client.process.stdin as import("bun").FileSink, request).catch((err) => {
|
|
765
|
+
if (timeout) clearTimeout(timeout);
|
|
766
|
+
client.pendingRequests.delete(id);
|
|
767
|
+
cleanup();
|
|
768
|
+
reject(err);
|
|
770
769
|
});
|
|
770
|
+
return promise;
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { basename, extname, join } from "node:path";
|
|
3
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { YAML } from "bun";
|
|
4
5
|
import { globSync } from "glob";
|
|
5
6
|
import { getConfigDirPaths } from "../../../config";
|
|
6
|
-
import { logger } from "../../logger";
|
|
7
7
|
import { BiomeClient } from "./clients/biome-client";
|
|
8
8
|
import DEFAULTS from "./defaults.json" with { type: "json" };
|
|
9
9
|
import type { ServerConfig } from "./types";
|
|
@@ -2,12 +2,11 @@ import type { Dirent } from "node:fs";
|
|
|
2
2
|
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
5
|
+
import { logger, once, untilAborted } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import type { BunFile } from "bun";
|
|
6
7
|
import { type Theme, theme } from "../../../modes/interactive/theme/theme";
|
|
7
8
|
import lspDescription from "../../../prompts/tools/lsp.md" with { type: "text" };
|
|
8
|
-
import { logger } from "../../logger";
|
|
9
9
|
import { renderPromptTemplate } from "../../prompt-templates";
|
|
10
|
-
import { once, untilAborted } from "../../utils";
|
|
11
10
|
import type { ToolSession } from "../index";
|
|
12
11
|
import { resolveToCwd } from "../path-utils";
|
|
13
12
|
import {
|
|
@@ -57,7 +56,6 @@ import {
|
|
|
57
56
|
formatLocation,
|
|
58
57
|
formatSymbolInformation,
|
|
59
58
|
formatWorkspaceEdit,
|
|
60
|
-
sleep,
|
|
61
59
|
symbolKindToIcon,
|
|
62
60
|
uriToFile,
|
|
63
61
|
} from "./utils";
|
|
@@ -340,7 +338,7 @@ async function waitForDiagnostics(
|
|
|
340
338
|
const diagnostics = client.diagnostics.get(uri);
|
|
341
339
|
const versionOk = minVersion === undefined || client.diagnosticsVersion > minVersion;
|
|
342
340
|
if (diagnostics !== undefined && versionOk) return diagnostics;
|
|
343
|
-
await sleep(100);
|
|
341
|
+
await Bun.sleep(100);
|
|
344
342
|
}
|
|
345
343
|
return client.diagnostics.get(uri) ?? [];
|
|
346
344
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sendNotification, sendRequest } from "./client";
|
|
2
2
|
import type { Diagnostic, ExpandMacroResult, LspClient, RelatedTest, Runnable, WorkspaceEdit } from "./types";
|
|
3
|
-
import { fileToUri
|
|
3
|
+
import { fileToUri } from "./utils";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Run flycheck (cargo check) and collect diagnostics.
|
|
@@ -39,7 +39,7 @@ export async function flycheck(client: LspClient, file?: string): Promise<Diagno
|
|
|
39
39
|
let stableIterations = 0;
|
|
40
40
|
|
|
41
41
|
for (let i = 0; i < maxPollIterations; i++) {
|
|
42
|
-
await sleep(pollIntervalMs);
|
|
42
|
+
await Bun.sleep(pollIntervalMs);
|
|
43
43
|
|
|
44
44
|
const currentDiagnosticsVersion = client.diagnosticsVersion;
|
|
45
45
|
const currentDiagnosticsCount = countDiagnostics(client.diagnostics);
|
|
@@ -507,20 +507,6 @@ export function extractHoverText(
|
|
|
507
507
|
// General Utilities
|
|
508
508
|
// =============================================================================
|
|
509
509
|
|
|
510
|
-
/**
|
|
511
|
-
* Sleep for the specified number of milliseconds.
|
|
512
|
-
*/
|
|
513
|
-
export function sleep(ms: number): Promise<void> {
|
|
514
|
-
return Bun.sleep(ms);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Check if a command exists in PATH.
|
|
519
|
-
*/
|
|
520
|
-
export async function commandExists(command: string): Promise<boolean> {
|
|
521
|
-
return Bun.which(command) !== null;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
510
|
/**
|
|
525
511
|
* Truncate a string to a maximum length with ellipsis.
|
|
526
512
|
*/
|
|
@@ -2,11 +2,11 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
2
2
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import { type Static, Type } from "@sinclair/typebox";
|
|
6
7
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
7
8
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
8
9
|
import type { ToolSession } from "../sdk";
|
|
9
|
-
import { untilAborted } from "../utils";
|
|
10
10
|
import { resolveToCwd } from "./path-utils";
|
|
11
11
|
import {
|
|
12
12
|
formatCount,
|
|
@@ -10,12 +10,11 @@ import { getLanguageFromPath, type Theme } from "../../../modes/interactive/them
|
|
|
10
10
|
import type { RenderResultOptions } from "../../custom-tools/types";
|
|
11
11
|
import type { FileDiagnosticsResult } from "../lsp/index";
|
|
12
12
|
import {
|
|
13
|
-
createToolUIKit,
|
|
14
13
|
formatExpandHint,
|
|
15
14
|
formatStatusIcon,
|
|
16
15
|
getDiffStats,
|
|
17
16
|
shortenPath,
|
|
18
|
-
|
|
17
|
+
ToolUIKit,
|
|
19
18
|
truncateDiffByHunk,
|
|
20
19
|
} from "../render-utils";
|
|
21
20
|
import type { RenderCallOptions } from "../renderers";
|
|
@@ -157,7 +156,7 @@ export const editToolRenderer = {
|
|
|
157
156
|
mergeCallAndResult: true,
|
|
158
157
|
|
|
159
158
|
renderCall(args: EditRenderArgs, uiTheme: Theme, options?: RenderCallOptions): Component {
|
|
160
|
-
const ui =
|
|
159
|
+
const ui = new ToolUIKit(uiTheme);
|
|
161
160
|
const rawPath = args.file_path || args.path || "";
|
|
162
161
|
const filePath = shortenPath(rawPath);
|
|
163
162
|
const editLanguage = getLanguageFromPath(rawPath) ?? "text";
|
|
@@ -190,7 +189,7 @@ export const editToolRenderer = {
|
|
|
190
189
|
uiTheme: Theme,
|
|
191
190
|
args?: EditRenderArgs,
|
|
192
191
|
): Component {
|
|
193
|
-
const ui =
|
|
192
|
+
const ui = new ToolUIKit(uiTheme);
|
|
194
193
|
const { expanded, renderContext } = options;
|
|
195
194
|
const rawPath = args?.file_path || args?.path || "";
|
|
196
195
|
const filePath = shortenPath(rawPath);
|
package/src/core/tools/python.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { executePython, getPreludeDocs, type PythonExecutorOptions } from "../py
|
|
|
13
13
|
import type { PreludeHelper, PythonStatusEvent } from "../python-kernel";
|
|
14
14
|
import type { ToolSession } from "./index";
|
|
15
15
|
import { resolveToCwd } from "./path-utils";
|
|
16
|
-
import {
|
|
16
|
+
import { getTreeBranch, getTreeContinuePrefix, shortenPath, ToolUIKit, truncate } from "./render-utils";
|
|
17
17
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
|
|
18
18
|
|
|
19
19
|
export const PYTHON_DEFAULT_PREVIEW_LINES = 10;
|
|
@@ -610,7 +610,7 @@ function renderStatusEvents(events: PythonStatusEvent[], theme: Theme, expanded:
|
|
|
610
610
|
|
|
611
611
|
export const pythonToolRenderer = {
|
|
612
612
|
renderCall(args: PythonRenderArgs, uiTheme: Theme): Component {
|
|
613
|
-
const ui =
|
|
613
|
+
const ui = new ToolUIKit(uiTheme);
|
|
614
614
|
const code = args.code || uiTheme.format.ellipsis;
|
|
615
615
|
const prompt = uiTheme.fg("accent", ">>>");
|
|
616
616
|
const cwd = process.cwd();
|
|
@@ -642,7 +642,7 @@ export const pythonToolRenderer = {
|
|
|
642
642
|
options: RenderResultOptions & { renderContext?: PythonRenderContext },
|
|
643
643
|
uiTheme: Theme,
|
|
644
644
|
): Component {
|
|
645
|
-
const ui =
|
|
645
|
+
const ui = new ToolUIKit(uiTheme);
|
|
646
646
|
const { renderContext } = options;
|
|
647
647
|
const details = result.details;
|
|
648
648
|
|