@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/read.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
4
4
|
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
7
|
+
import { ptree, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import { Type } from "@sinclair/typebox";
|
|
8
9
|
import { CONFIG_DIR_NAME } from "../../config";
|
|
9
10
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
@@ -14,7 +15,7 @@ import { ensureTool } from "../../utils/tools-manager";
|
|
|
14
15
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
15
16
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
16
17
|
import type { ToolSession } from "../sdk";
|
|
17
|
-
import {
|
|
18
|
+
import { runFd } from "./find";
|
|
18
19
|
import { LsTool } from "./ls";
|
|
19
20
|
import { resolveReadPath, resolveToCwd } from "./path-utils";
|
|
20
21
|
import { shortenPath, wrapBrackets } from "./render-utils";
|
|
@@ -153,53 +154,6 @@ function similarityScore(a: string, b: string): number {
|
|
|
153
154
|
return 1 - distance / maxLen;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
async function captureCommandOutput(
|
|
157
|
-
command: string,
|
|
158
|
-
args: string[],
|
|
159
|
-
signal?: AbortSignal,
|
|
160
|
-
): Promise<{ stdout: string; stderr: string; exitCode: number | null; aborted: boolean }> {
|
|
161
|
-
const child = Bun.spawn([command, ...args], {
|
|
162
|
-
stdin: "ignore",
|
|
163
|
-
stdout: "pipe",
|
|
164
|
-
stderr: "pipe",
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
using scope = new ScopeSignal(signal ? { signal } : undefined);
|
|
168
|
-
scope.catch(() => {
|
|
169
|
-
child.kill();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const stdoutReader = (child.stdout as ReadableStream<Uint8Array>).getReader();
|
|
173
|
-
const stderrReader = (child.stderr as ReadableStream<Uint8Array>).getReader();
|
|
174
|
-
const stdoutDecoder = new TextDecoder();
|
|
175
|
-
const stderrDecoder = new TextDecoder();
|
|
176
|
-
let stdout = "";
|
|
177
|
-
let stderr = "";
|
|
178
|
-
|
|
179
|
-
await Promise.all([
|
|
180
|
-
(async () => {
|
|
181
|
-
while (true) {
|
|
182
|
-
const { done, value } = await stdoutReader.read();
|
|
183
|
-
if (done) break;
|
|
184
|
-
stdout += stdoutDecoder.decode(value, { stream: true });
|
|
185
|
-
}
|
|
186
|
-
stdout += stdoutDecoder.decode();
|
|
187
|
-
})(),
|
|
188
|
-
(async () => {
|
|
189
|
-
while (true) {
|
|
190
|
-
const { done, value } = await stderrReader.read();
|
|
191
|
-
if (done) break;
|
|
192
|
-
stderr += stderrDecoder.decode(value, { stream: true });
|
|
193
|
-
}
|
|
194
|
-
stderr += stderrDecoder.decode();
|
|
195
|
-
})(),
|
|
196
|
-
]);
|
|
197
|
-
|
|
198
|
-
const exitCode = await child.exited;
|
|
199
|
-
|
|
200
|
-
return { stdout, stderr, exitCode, aborted: scope.aborted };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
157
|
async function listCandidateFiles(
|
|
204
158
|
searchRoot: string,
|
|
205
159
|
signal?: AbortSignal,
|
|
@@ -238,10 +192,7 @@ async function listCandidateFiles(
|
|
|
238
192
|
".git",
|
|
239
193
|
searchRoot,
|
|
240
194
|
];
|
|
241
|
-
const { stdout
|
|
242
|
-
if (aborted) {
|
|
243
|
-
throw new Error("Operation aborted");
|
|
244
|
-
}
|
|
195
|
+
const { stdout } = await runFd(fdPath, gitignoreArgs, signal);
|
|
245
196
|
const output = stdout.trim();
|
|
246
197
|
if (output) {
|
|
247
198
|
const nestedGitignores = output
|
|
@@ -269,17 +220,11 @@ async function listCandidateFiles(
|
|
|
269
220
|
|
|
270
221
|
args.push(".", searchRoot);
|
|
271
222
|
|
|
272
|
-
const { stdout, stderr, exitCode
|
|
273
|
-
|
|
274
|
-
if (aborted) {
|
|
275
|
-
throw new Error("Operation aborted");
|
|
276
|
-
}
|
|
277
|
-
|
|
223
|
+
const { stdout, stderr, exitCode } = await runFd(fdPath, args, signal);
|
|
278
224
|
const output = stdout.trim();
|
|
279
225
|
|
|
280
226
|
if (exitCode !== 0 && !output) {
|
|
281
|
-
|
|
282
|
-
return { files: [], truncated: false, error: errorMsg };
|
|
227
|
+
return { files: [], truncated: false, error: stderr.trim() || `fd exited with code ${exitCode ?? -1}` };
|
|
283
228
|
}
|
|
284
229
|
|
|
285
230
|
if (!output) {
|
|
@@ -400,17 +345,22 @@ async function convertWithMarkitdown(
|
|
|
400
345
|
return { content: "", ok: false, error: "markitdown not found (uv/pip unavailable)" };
|
|
401
346
|
}
|
|
402
347
|
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
348
|
+
const child = ptree.cspawn([cmd, filePath], { signal });
|
|
349
|
+
let stdout: string;
|
|
350
|
+
try {
|
|
351
|
+
stdout = await child.nothrow().text();
|
|
352
|
+
} catch (err) {
|
|
353
|
+
if (err instanceof ptree.Exception && err.aborted) {
|
|
354
|
+
throw new Error("Operation aborted");
|
|
355
|
+
}
|
|
356
|
+
throw err;
|
|
407
357
|
}
|
|
408
358
|
|
|
409
|
-
if (exitCode === 0 && stdout.length > 0) {
|
|
359
|
+
if (child.exitCode === 0 && stdout.length > 0) {
|
|
410
360
|
return { content: stdout, ok: true };
|
|
411
361
|
}
|
|
412
362
|
|
|
413
|
-
return { content: "", ok: false, error:
|
|
363
|
+
return { content: "", ok: false, error: child.peekStderr().trim() || "Conversion failed" };
|
|
414
364
|
}
|
|
415
365
|
|
|
416
366
|
const readSchema = Type.Object({
|
|
@@ -605,9 +555,20 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
605
555
|
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
606
556
|
const startLineDisplay = startLine + 1; // For display (1-indexed)
|
|
607
557
|
|
|
608
|
-
// Check if offset is out of bounds
|
|
558
|
+
// Check if offset is out of bounds - return graceful message instead of throwing
|
|
609
559
|
if (startLine >= allLines.length) {
|
|
610
|
-
|
|
560
|
+
const suggestion =
|
|
561
|
+
allLines.length === 0
|
|
562
|
+
? "The file is empty."
|
|
563
|
+
: `Use offset=1 to read from the start, or offset=${allLines.length} to read the last line.`;
|
|
564
|
+
return {
|
|
565
|
+
content: [
|
|
566
|
+
{
|
|
567
|
+
type: "text",
|
|
568
|
+
text: `Offset ${offset} is beyond end of file (${allLines.length} lines total). ${suggestion}`,
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
};
|
|
611
572
|
}
|
|
612
573
|
|
|
613
574
|
// If limit is specified by user, use it; otherwise we'll let truncateHead decide
|
|
@@ -324,11 +324,6 @@ export class ToolUIKit {
|
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
/** @deprecated Use `new ToolUIKit(theme)` instead */
|
|
328
|
-
export function createToolUIKit(theme: Theme): ToolUIKit {
|
|
329
|
-
return new ToolUIKit(theme);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
327
|
interface ParsedDiagnostic {
|
|
333
328
|
filePath: string;
|
|
334
329
|
line: number;
|
package/src/core/tools/ssh.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { SSHHostInfo } from "../ssh/connection-manager";
|
|
|
13
13
|
import { ensureHostInfo, getHostInfoForHost } from "../ssh/connection-manager";
|
|
14
14
|
import { executeSSH } from "../ssh/ssh-executor";
|
|
15
15
|
import type { ToolSession } from "./index";
|
|
16
|
-
import {
|
|
16
|
+
import { ToolUIKit } from "./render-utils";
|
|
17
17
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
|
|
18
18
|
|
|
19
19
|
const sshSchema = Type.Object({
|
|
@@ -245,7 +245,7 @@ interface SshRenderContext {
|
|
|
245
245
|
|
|
246
246
|
export const sshToolRenderer = {
|
|
247
247
|
renderCall(args: SshRenderArgs, uiTheme: Theme): Component {
|
|
248
|
-
const ui =
|
|
248
|
+
const ui = new ToolUIKit(uiTheme);
|
|
249
249
|
const host = args.host || uiTheme.format.ellipsis;
|
|
250
250
|
const command = args.command || uiTheme.format.ellipsis;
|
|
251
251
|
const text = ui.title(`[${host}] $ ${command}`);
|
|
@@ -260,7 +260,7 @@ export const sshToolRenderer = {
|
|
|
260
260
|
options: RenderResultOptions & { renderContext?: SshRenderContext },
|
|
261
261
|
uiTheme: Theme,
|
|
262
262
|
): Component {
|
|
263
|
-
const ui =
|
|
263
|
+
const ui = new ToolUIKit(uiTheme);
|
|
264
264
|
const { expanded, renderContext } = options;
|
|
265
265
|
const details = result.details;
|
|
266
266
|
const lines: string[] = [];
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - "omp/slow" or "pi/slow" → configured slow model from settings
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { $ } from "bun";
|
|
14
15
|
import { type Settings as SettingsFile, settingsCapability } from "../../../capability/settings";
|
|
15
16
|
import { loadCapability } from "../../../discovery";
|
|
16
17
|
import type { Settings as SettingsData } from "../../settings-manager";
|
|
@@ -29,7 +30,7 @@ const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
29
30
|
* Returns models in "provider/modelId" format.
|
|
30
31
|
* Caches the result for performance.
|
|
31
32
|
*/
|
|
32
|
-
export function getAvailableModels(): string[] {
|
|
33
|
+
export async function getAvailableModels(): Promise<string[]> {
|
|
33
34
|
const now = Date.now();
|
|
34
35
|
if (cachedModels !== null && now < cacheExpiry) {
|
|
35
36
|
return cachedModels;
|
|
@@ -37,20 +38,17 @@ export function getAvailableModels(): string[] {
|
|
|
37
38
|
|
|
38
39
|
try {
|
|
39
40
|
const ompCommand = resolveOmpCommand();
|
|
40
|
-
const result =
|
|
41
|
-
|
|
42
|
-
stdout: "pipe",
|
|
43
|
-
stderr: "pipe",
|
|
44
|
-
});
|
|
41
|
+
const result = await $`${ompCommand.cmd} ${ompCommand.args} --list-models`.quiet().nothrow();
|
|
42
|
+
const stdout = result.stdout?.toString() ?? "";
|
|
45
43
|
|
|
46
|
-
if (result.exitCode !== 0 || !
|
|
44
|
+
if (result.exitCode !== 0 || !stdout.trim()) {
|
|
47
45
|
cachedModels = [];
|
|
48
46
|
cacheExpiry = now + CACHE_TTL_MS;
|
|
49
47
|
return cachedModels;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
// Parse output: skip header line, extract provider/model
|
|
53
|
-
const lines =
|
|
51
|
+
const lines = stdout.trim().split("\n");
|
|
54
52
|
cachedModels = lines
|
|
55
53
|
.slice(1) // Skip header
|
|
56
54
|
.map((line) => {
|
|
@@ -151,7 +149,7 @@ export async function resolveModelPattern(
|
|
|
151
149
|
return undefined;
|
|
152
150
|
}
|
|
153
151
|
|
|
154
|
-
const models = availableModels ?? getAvailableModels();
|
|
152
|
+
const models = availableModels ?? (await getAvailableModels());
|
|
155
153
|
if (models.length === 0) {
|
|
156
154
|
// Fallback: return pattern as-is if we can't get available models
|
|
157
155
|
return pattern;
|
|
@@ -15,19 +15,18 @@
|
|
|
15
15
|
|
|
16
16
|
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
17
17
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
18
|
+
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
18
19
|
import type { TSchema } from "@sinclair/typebox";
|
|
19
20
|
import lspDescription from "../../../prompts/tools/lsp.md" with { type: "text" };
|
|
20
21
|
import type { AgentSessionEvent } from "../../agent-session";
|
|
21
22
|
import { AuthStorage } from "../../auth-storage";
|
|
22
23
|
import type { CustomTool } from "../../custom-tools/types";
|
|
23
|
-
import { logger } from "../../logger";
|
|
24
24
|
import { ModelRegistry } from "../../model-registry";
|
|
25
25
|
import { parseModelPattern, parseModelString } from "../../model-resolver";
|
|
26
26
|
import { renderPromptTemplate } from "../../prompt-templates";
|
|
27
27
|
import { createAgentSession, discoverAuthStorage, discoverModels } from "../../sdk";
|
|
28
28
|
import { SessionManager } from "../../session-manager";
|
|
29
29
|
import { SettingsManager } from "../../settings-manager";
|
|
30
|
-
import { untilAborted } from "../../utils";
|
|
31
30
|
import { type LspToolDetails, lspSchema } from "../lsp/types";
|
|
32
31
|
import { getPythonToolDescription, type PythonToolDetails, type PythonToolParams, pythonSchema } from "../python";
|
|
33
32
|
import type {
|
|
@@ -94,54 +93,58 @@ function callMCPToolViaParent(
|
|
|
94
93
|
signal?: AbortSignal,
|
|
95
94
|
timeoutMs = MCP_CALL_TIMEOUT_MS,
|
|
96
95
|
): Promise<{ content: Array<{ type: string; text?: string; [key: string]: unknown }>; isError?: boolean }> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
const { promise, resolve, reject } = Promise.withResolvers<{
|
|
97
|
+
content: Array<{ type: string; text?: string; [key: string]: unknown }>;
|
|
98
|
+
isError?: boolean;
|
|
99
|
+
}>();
|
|
100
|
+
const callId = generateMCPCallId();
|
|
101
|
+
if (signal?.aborted) {
|
|
102
|
+
reject(new Error("Aborted"));
|
|
103
|
+
return promise;
|
|
104
|
+
}
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const timeoutId = setTimeout(() => {
|
|
107
|
+
pendingMCPCalls.delete(callId);
|
|
108
|
+
reject(new Error(`MCP call timed out after ${timeoutMs}ms`));
|
|
109
|
+
}, timeoutMs);
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (typeof signal?.addEventListener === "function") {
|
|
115
|
-
signal.addEventListener(
|
|
116
|
-
"abort",
|
|
117
|
-
() => {
|
|
118
|
-
cleanup();
|
|
119
|
-
reject(new Error("Aborted"));
|
|
120
|
-
},
|
|
121
|
-
{ once: true },
|
|
122
|
-
);
|
|
123
|
-
}
|
|
111
|
+
const cleanup = () => {
|
|
112
|
+
clearTimeout(timeoutId);
|
|
113
|
+
pendingMCPCalls.delete(callId);
|
|
114
|
+
};
|
|
124
115
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
130
|
-
reject: (error) => {
|
|
116
|
+
if (typeof signal?.addEventListener === "function") {
|
|
117
|
+
signal.addEventListener(
|
|
118
|
+
"abort",
|
|
119
|
+
() => {
|
|
131
120
|
cleanup();
|
|
132
|
-
reject(
|
|
121
|
+
reject(new Error("Aborted"));
|
|
133
122
|
},
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
{ once: true },
|
|
124
|
+
);
|
|
125
|
+
}
|
|
136
126
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
127
|
+
pendingMCPCalls.set(callId, {
|
|
128
|
+
resolve: (result) => {
|
|
129
|
+
cleanup();
|
|
130
|
+
resolve(result ?? { content: [] });
|
|
131
|
+
},
|
|
132
|
+
reject: (error) => {
|
|
133
|
+
cleanup();
|
|
134
|
+
reject(error);
|
|
135
|
+
},
|
|
136
|
+
timeoutId,
|
|
144
137
|
});
|
|
138
|
+
|
|
139
|
+
postMessageSafe({
|
|
140
|
+
type: "mcp_tool_call",
|
|
141
|
+
callId,
|
|
142
|
+
toolName,
|
|
143
|
+
params,
|
|
144
|
+
timeoutMs,
|
|
145
|
+
} as SubagentWorkerResponse);
|
|
146
|
+
|
|
147
|
+
return promise;
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
function callPythonToolViaParent(
|
|
@@ -149,64 +152,65 @@ function callPythonToolViaParent(
|
|
|
149
152
|
signal?: AbortSignal,
|
|
150
153
|
timeoutMs?: number,
|
|
151
154
|
): Promise<PythonToolCallResponse["result"]> {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
const { promise, resolve, reject } = Promise.withResolvers<PythonToolCallResponse["result"]>();
|
|
156
|
+
const callId = generatePythonCallId();
|
|
157
|
+
if (signal?.aborted) {
|
|
158
|
+
reject(new Error("Aborted"));
|
|
159
|
+
return promise;
|
|
160
|
+
}
|
|
158
161
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
pendingPythonCalls.delete(callId);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
if (typeof signal?.addEventListener === "function") {
|
|
180
|
-
signal.addEventListener(
|
|
181
|
-
"abort",
|
|
182
|
-
() => {
|
|
183
|
-
cleanup();
|
|
184
|
-
sendCancel("Aborted");
|
|
185
|
-
reject(new Error("Aborted"));
|
|
186
|
-
},
|
|
187
|
-
{ once: true },
|
|
188
|
-
);
|
|
162
|
+
const sendCancel = (reason: string) => {
|
|
163
|
+
postMessageSafe({ type: "python_tool_cancel", callId, reason } as SubagentWorkerResponse);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const timeoutId =
|
|
167
|
+
typeof timeoutMs === "number" && Number.isFinite(timeoutMs)
|
|
168
|
+
? setTimeout(() => {
|
|
169
|
+
pendingPythonCalls.delete(callId);
|
|
170
|
+
sendCancel(`Python call timed out after ${timeoutMs}ms`);
|
|
171
|
+
reject(new Error(`Python call timed out after ${timeoutMs}ms`));
|
|
172
|
+
}, timeoutMs)
|
|
173
|
+
: undefined;
|
|
174
|
+
|
|
175
|
+
const cleanup = () => {
|
|
176
|
+
if (timeoutId) {
|
|
177
|
+
clearTimeout(timeoutId);
|
|
189
178
|
}
|
|
179
|
+
pendingPythonCalls.delete(callId);
|
|
180
|
+
};
|
|
190
181
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
},
|
|
196
|
-
reject: (error) => {
|
|
182
|
+
if (typeof signal?.addEventListener === "function") {
|
|
183
|
+
signal.addEventListener(
|
|
184
|
+
"abort",
|
|
185
|
+
() => {
|
|
197
186
|
cleanup();
|
|
198
|
-
|
|
187
|
+
sendCancel("Aborted");
|
|
188
|
+
reject(new Error("Aborted"));
|
|
199
189
|
},
|
|
200
|
-
|
|
201
|
-
|
|
190
|
+
{ once: true },
|
|
191
|
+
);
|
|
192
|
+
}
|
|
202
193
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
194
|
+
pendingPythonCalls.set(callId, {
|
|
195
|
+
resolve: (result) => {
|
|
196
|
+
cleanup();
|
|
197
|
+
resolve(result ?? { content: [] });
|
|
198
|
+
},
|
|
199
|
+
reject: (error) => {
|
|
200
|
+
cleanup();
|
|
201
|
+
reject(error);
|
|
202
|
+
},
|
|
203
|
+
timeoutId,
|
|
209
204
|
});
|
|
205
|
+
|
|
206
|
+
postMessageSafe({
|
|
207
|
+
type: "python_tool_call",
|
|
208
|
+
callId,
|
|
209
|
+
params,
|
|
210
|
+
timeoutMs,
|
|
211
|
+
} as SubagentWorkerResponse);
|
|
212
|
+
|
|
213
|
+
return promise;
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
function callLspToolViaParent(
|
|
@@ -214,58 +218,59 @@ function callLspToolViaParent(
|
|
|
214
218
|
signal?: AbortSignal,
|
|
215
219
|
timeoutMs?: number,
|
|
216
220
|
): Promise<LspToolCallResponse["result"]> {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
const { promise, resolve, reject } = Promise.withResolvers<LspToolCallResponse["result"]>();
|
|
222
|
+
const callId = generateLspCallId();
|
|
223
|
+
if (signal?.aborted) {
|
|
224
|
+
reject(new Error("Aborted"));
|
|
225
|
+
return promise;
|
|
226
|
+
}
|
|
223
227
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
pendingLspCalls.delete(callId);
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
if (typeof signal?.addEventListener === "function") {
|
|
240
|
-
signal.addEventListener(
|
|
241
|
-
"abort",
|
|
242
|
-
() => {
|
|
243
|
-
cleanup();
|
|
244
|
-
reject(new Error("Aborted"));
|
|
245
|
-
},
|
|
246
|
-
{ once: true },
|
|
247
|
-
);
|
|
228
|
+
const timeoutId =
|
|
229
|
+
typeof timeoutMs === "number" && Number.isFinite(timeoutMs)
|
|
230
|
+
? setTimeout(() => {
|
|
231
|
+
pendingLspCalls.delete(callId);
|
|
232
|
+
reject(new Error(`LSP call timed out after ${timeoutMs}ms`));
|
|
233
|
+
}, timeoutMs)
|
|
234
|
+
: undefined;
|
|
235
|
+
|
|
236
|
+
const cleanup = () => {
|
|
237
|
+
if (timeoutId) {
|
|
238
|
+
clearTimeout(timeoutId);
|
|
248
239
|
}
|
|
240
|
+
pendingLspCalls.delete(callId);
|
|
241
|
+
};
|
|
249
242
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
},
|
|
255
|
-
reject: (error) => {
|
|
243
|
+
if (typeof signal?.addEventListener === "function") {
|
|
244
|
+
signal.addEventListener(
|
|
245
|
+
"abort",
|
|
246
|
+
() => {
|
|
256
247
|
cleanup();
|
|
257
|
-
reject(
|
|
248
|
+
reject(new Error("Aborted"));
|
|
258
249
|
},
|
|
259
|
-
|
|
260
|
-
|
|
250
|
+
{ once: true },
|
|
251
|
+
);
|
|
252
|
+
}
|
|
261
253
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
254
|
+
pendingLspCalls.set(callId, {
|
|
255
|
+
resolve: (result) => {
|
|
256
|
+
cleanup();
|
|
257
|
+
resolve(result ?? { content: [] });
|
|
258
|
+
},
|
|
259
|
+
reject: (error) => {
|
|
260
|
+
cleanup();
|
|
261
|
+
reject(error);
|
|
262
|
+
},
|
|
263
|
+
timeoutId,
|
|
268
264
|
});
|
|
265
|
+
|
|
266
|
+
postMessageSafe({
|
|
267
|
+
type: "lsp_tool_call",
|
|
268
|
+
callId,
|
|
269
|
+
params,
|
|
270
|
+
timeoutMs,
|
|
271
|
+
} as SubagentWorkerResponse);
|
|
272
|
+
|
|
273
|
+
return promise;
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
function handleMCPToolResult(response: MCPToolCallResponse): void {
|
|
@@ -5,12 +5,12 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
5
5
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
8
9
|
import { Type } from "@sinclair/typebox";
|
|
9
10
|
import chalk from "chalk";
|
|
10
11
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
11
12
|
import todoWriteDescription from "../../prompts/tools/todo-write.md" with { type: "text" };
|
|
12
13
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
13
|
-
import { logger } from "../logger";
|
|
14
14
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
15
15
|
import type { ToolSession } from "../sdk";
|
|
16
16
|
import { ensureArtifactsDir, getArtifactsDir } from "./task/artifacts";
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
export const DEFAULT_MAX_LINES = 2000;
|
|
13
13
|
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
14
|
-
export const
|
|
14
|
+
export const DEFAULT_MAX_COLUMN = 1024; // Max chars per grep match line
|
|
15
15
|
|
|
16
16
|
export interface TruncationResult {
|
|
17
17
|
/** The truncated content */
|
|
@@ -282,7 +282,7 @@ export function truncateStringToBytesFromStart(str: string, maxBytes: number): {
|
|
|
282
282
|
*/
|
|
283
283
|
export function truncateLine(
|
|
284
284
|
line: string,
|
|
285
|
-
maxChars: number =
|
|
285
|
+
maxChars: number = DEFAULT_MAX_COLUMN,
|
|
286
286
|
): { text: string; wasTruncated: boolean } {
|
|
287
287
|
if (line.length <= maxChars) {
|
|
288
288
|
return { text: line, wasTruncated: false };
|