@oh-my-pi/pi-coding-agent 6.7.670 → 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 +1 -1
- 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
|
@@ -1,179 +1,137 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bash command execution with streaming support and cancellation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - AgentSession.executeBash() for interactive and RPC modes
|
|
6
|
-
* - Direct calls from modes that need bash execution
|
|
4
|
+
* Provides unified bash execution for AgentSession.executeBash() and direct calls.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
import
|
|
10
|
-
import { getShellConfig
|
|
7
|
+
import { cspawn, Exception } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { getShellConfig } from "../utils/shell";
|
|
11
9
|
import { getOrCreateSnapshot, getSnapshotSourceCommand } from "../utils/shell-snapshot";
|
|
12
|
-
import { OutputSink
|
|
10
|
+
import { OutputSink } from "./streaming-output";
|
|
13
11
|
import type { BashOperations } from "./tools/bash";
|
|
14
|
-
import { DEFAULT_MAX_BYTES } from "./tools/truncate";
|
|
15
|
-
import { ScopeSignal } from "./utils";
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Types
|
|
19
|
-
// ============================================================================
|
|
20
12
|
|
|
21
13
|
export interface BashExecutorOptions {
|
|
22
|
-
/** Working directory for command execution */
|
|
23
14
|
cwd?: string;
|
|
24
|
-
/** Timeout in milliseconds */
|
|
25
15
|
timeout?: number;
|
|
26
|
-
/** Callback for streaming output chunks (already sanitized) */
|
|
27
16
|
onChunk?: (chunk: string) => void;
|
|
28
|
-
/** AbortSignal for cancellation */
|
|
29
17
|
signal?: AbortSignal;
|
|
30
18
|
}
|
|
31
19
|
|
|
32
20
|
export interface BashResult {
|
|
33
|
-
/** Combined stdout + stderr output (sanitized, possibly truncated) */
|
|
34
21
|
output: string;
|
|
35
|
-
/** Process exit code (undefined if killed/cancelled) */
|
|
36
22
|
exitCode: number | undefined;
|
|
37
|
-
/** Whether the command was cancelled via signal */
|
|
38
23
|
cancelled: boolean;
|
|
39
|
-
/** Whether the output was truncated */
|
|
40
24
|
truncated: boolean;
|
|
41
|
-
/** Path to temp file containing full output (if output exceeded truncation threshold) */
|
|
42
25
|
fullOutputPath?: string;
|
|
43
26
|
}
|
|
44
27
|
|
|
45
|
-
// ============================================================================
|
|
46
|
-
// Implementation
|
|
47
|
-
// ============================================================================
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Execute a bash command with optional streaming and cancellation support.
|
|
51
|
-
*
|
|
52
|
-
* Features:
|
|
53
|
-
* - Streams sanitized output via onChunk callback
|
|
54
|
-
* - Writes large output to temp file for later retrieval
|
|
55
|
-
* - Supports cancellation via AbortSignal
|
|
56
|
-
* - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)
|
|
57
|
-
* - Truncates output if it exceeds the default max bytes
|
|
58
|
-
*
|
|
59
|
-
* @param command - The bash command to execute
|
|
60
|
-
* @param options - Optional streaming callback and abort signal
|
|
61
|
-
* @returns Promise resolving to execution result
|
|
62
|
-
*/
|
|
63
28
|
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
64
29
|
const { shell, args, env, prefix } = await getShellConfig();
|
|
65
30
|
|
|
66
|
-
// Get or create shell snapshot (for aliases, functions, options)
|
|
67
31
|
const snapshotPath = await getOrCreateSnapshot(shell, env);
|
|
68
32
|
const snapshotPrefix = getSnapshotSourceCommand(snapshotPath);
|
|
69
33
|
|
|
70
|
-
// Build final command: snapshot + prefix + command
|
|
71
34
|
const prefixedCommand = prefix ? `${prefix} ${command}` : command;
|
|
72
35
|
const finalCommand = `${snapshotPrefix}${prefixedCommand}`;
|
|
73
36
|
|
|
74
|
-
|
|
37
|
+
const stream = new OutputSink({ onLine: options?.onChunk });
|
|
75
38
|
|
|
76
|
-
const child
|
|
39
|
+
const child = cspawn([shell, ...args, finalCommand], {
|
|
77
40
|
cwd: options?.cwd,
|
|
78
|
-
stdin: "ignore",
|
|
79
|
-
stdout: "pipe",
|
|
80
|
-
stderr: "pipe",
|
|
81
41
|
env,
|
|
42
|
+
signal: options?.signal,
|
|
43
|
+
timeout: options?.timeout,
|
|
82
44
|
});
|
|
83
45
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
46
|
+
// Pump streams - errors during abort/timeout are expected
|
|
47
|
+
await Promise.allSettled([
|
|
48
|
+
child.stdout.pipeTo(stream.createWritable()),
|
|
49
|
+
child.stderr.pipeTo(stream.createWritable()),
|
|
50
|
+
])
|
|
51
|
+
.then(() => stream.close())
|
|
52
|
+
.catch(() => {});
|
|
87
53
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const writer = sink.getWriter();
|
|
54
|
+
// Wait for process exit
|
|
91
55
|
try {
|
|
92
|
-
await
|
|
93
|
-
pumpStream(child.stdout as ReadableStream<Uint8Array>, writer),
|
|
94
|
-
pumpStream(child.stderr as ReadableStream<Uint8Array>, writer),
|
|
95
|
-
]);
|
|
96
|
-
} finally {
|
|
97
|
-
await writer.close();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Non-zero exit codes or signal-killed processes are considered cancelled if killed via signal
|
|
101
|
-
const exitCode = await child.exited;
|
|
102
|
-
|
|
103
|
-
const cancelled = exitCode === null || (exitCode !== 0 && (options?.signal?.aborted ?? false));
|
|
104
|
-
|
|
105
|
-
if (signal.timedOut()) {
|
|
106
|
-
const secs = Math.round(options!.timeout! / 1000);
|
|
56
|
+
await child.exited;
|
|
107
57
|
return {
|
|
108
|
-
exitCode:
|
|
109
|
-
cancelled:
|
|
110
|
-
...
|
|
58
|
+
exitCode: child.exitCode ?? 0,
|
|
59
|
+
cancelled: false,
|
|
60
|
+
...stream.dump(),
|
|
111
61
|
};
|
|
112
|
-
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// Exception covers NonZeroExitError, AbortError, TimeoutError
|
|
64
|
+
if (err instanceof Exception) {
|
|
65
|
+
if (err.aborted) {
|
|
66
|
+
const isTimeout = err.message.includes("timed out");
|
|
67
|
+
const annotation = isTimeout
|
|
68
|
+
? `Command timed out after ${Math.round((options?.timeout ?? 0) / 1000)} seconds`
|
|
69
|
+
: undefined;
|
|
70
|
+
return {
|
|
71
|
+
exitCode: undefined,
|
|
72
|
+
cancelled: true,
|
|
73
|
+
...stream.dump(annotation),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// NonZeroExitError
|
|
78
|
+
return {
|
|
79
|
+
exitCode: err.exitCode,
|
|
80
|
+
cancelled: false,
|
|
81
|
+
...stream.dump(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
113
84
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
cancelled,
|
|
117
|
-
...sink.dump(),
|
|
118
|
-
};
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
119
87
|
}
|
|
120
88
|
|
|
121
|
-
/**
|
|
122
|
-
* Execute a bash command using custom BashOperations.
|
|
123
|
-
* Used for remote execution (SSH, containers, etc.).
|
|
124
|
-
*/
|
|
125
89
|
export async function executeBashWithOperations(
|
|
126
90
|
command: string,
|
|
127
91
|
cwd: string,
|
|
128
92
|
operations: BashOperations,
|
|
129
93
|
options?: BashExecutorOptions,
|
|
130
94
|
): Promise<BashResult> {
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
95
|
+
const stream = new OutputSink({ onLine: options?.onChunk });
|
|
96
|
+
const writable = stream.createWritable();
|
|
97
|
+
const writer = writable.getWriter();
|
|
98
|
+
|
|
99
|
+
const closeStreams = async () => {
|
|
100
|
+
try {
|
|
101
|
+
await writer.close();
|
|
102
|
+
} catch {}
|
|
103
|
+
try {
|
|
104
|
+
await writable.close();
|
|
105
|
+
} catch {}
|
|
106
|
+
try {
|
|
107
|
+
await stream.close();
|
|
108
|
+
} catch {}
|
|
144
109
|
};
|
|
145
110
|
|
|
146
|
-
// Start pumping the stream (will complete when stream closes)
|
|
147
|
-
const pumpPromise = pumpStream(dataStream, writer);
|
|
148
|
-
|
|
149
111
|
try {
|
|
150
112
|
const result = await operations.exec(command, cwd, {
|
|
151
|
-
onData,
|
|
113
|
+
onData: (data) => writer.write(data),
|
|
152
114
|
signal: options?.signal,
|
|
153
115
|
timeout: options?.timeout,
|
|
154
116
|
});
|
|
155
117
|
|
|
156
|
-
|
|
157
|
-
await pumpPromise;
|
|
158
|
-
await writer.close();
|
|
118
|
+
await closeStreams();
|
|
159
119
|
|
|
160
120
|
const cancelled = options?.signal?.aborted ?? false;
|
|
161
121
|
|
|
162
122
|
return {
|
|
163
123
|
exitCode: cancelled ? undefined : (result.exitCode ?? undefined),
|
|
164
124
|
cancelled,
|
|
165
|
-
...
|
|
125
|
+
...stream.dump(),
|
|
166
126
|
};
|
|
167
127
|
} catch (err) {
|
|
168
|
-
|
|
169
|
-
await pumpPromise;
|
|
170
|
-
await writer.close();
|
|
128
|
+
await closeStreams();
|
|
171
129
|
|
|
172
130
|
if (options?.signal?.aborted) {
|
|
173
131
|
return {
|
|
174
132
|
exitCode: undefined,
|
|
175
133
|
cancelled: true,
|
|
176
|
-
...
|
|
134
|
+
...stream.dump(),
|
|
177
135
|
};
|
|
178
136
|
}
|
|
179
137
|
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import { type Dirent, existsSync, readdirSync } from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
10
11
|
import * as typebox from "@sinclair/typebox";
|
|
11
12
|
import { getAgentDir, getConfigDirs } from "../../config";
|
|
12
13
|
import * as piCodingAgent from "../../index";
|
|
13
14
|
import { execCommand } from "../exec";
|
|
14
|
-
import { logger } from "../logger";
|
|
15
15
|
import { ReviewCommand } from "./bundled/review";
|
|
16
16
|
import { WorktreeCommand } from "./bundled/wt";
|
|
17
17
|
import type {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
9
10
|
import * as typebox from "@sinclair/typebox";
|
|
10
11
|
import { toolCapability } from "../../capability/tool";
|
|
11
12
|
import { type CustomTool, loadCapability } from "../../discovery";
|
|
@@ -15,7 +16,6 @@ import { theme } from "../../modes/interactive/theme/theme";
|
|
|
15
16
|
import type { ExecOptions } from "../exec";
|
|
16
17
|
import { execCommand } from "../exec";
|
|
17
18
|
import type { HookUIContext } from "../hooks/types";
|
|
18
|
-
import { logger } from "../logger";
|
|
19
19
|
import { getAllPluginToolPaths } from "../plugins/loader";
|
|
20
20
|
import type { CustomToolAPI, CustomToolFactory, LoadedCustomTool, ToolLoadError } from "./types";
|
|
21
21
|
|
|
@@ -12,7 +12,6 @@ import type { Static, TSchema } from "@sinclair/typebox";
|
|
|
12
12
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
13
13
|
import type { ExecOptions, ExecResult } from "../exec";
|
|
14
14
|
import type { HookUIContext } from "../hooks/types";
|
|
15
|
-
import type { Logger } from "../logger";
|
|
16
15
|
import type { ModelRegistry } from "../model-registry";
|
|
17
16
|
import type { ReadonlySessionManager } from "../session-manager";
|
|
18
17
|
|
|
@@ -36,7 +35,7 @@ export interface CustomToolAPI {
|
|
|
36
35
|
/** Whether UI is available (false in print/RPC mode) */
|
|
37
36
|
hasUI: boolean;
|
|
38
37
|
/** File logger for error/warning/debug messages */
|
|
39
|
-
logger:
|
|
38
|
+
logger: typeof import("@oh-my-pi/pi-utils").logger;
|
|
40
39
|
/** Injected @sinclair/typebox module */
|
|
41
40
|
typebox: typeof import("@sinclair/typebox");
|
|
42
41
|
/** Injected pi-coding-agent exports */
|
package/src/core/exec.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Shared command execution utilities for hooks and custom tools.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import { logger } from "./logger";
|
|
5
|
+
import { ptree } from "@oh-my-pi/pi-utils";
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Options for executing shell commands.
|
|
@@ -37,103 +36,20 @@ export async function execCommand(
|
|
|
37
36
|
cwd: string,
|
|
38
37
|
options?: ExecOptions,
|
|
39
38
|
): Promise<ExecResult> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
stdout: "pipe",
|
|
45
|
-
stderr: "pipe",
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
let stdout = "";
|
|
49
|
-
let stderr = "";
|
|
50
|
-
let killed = false;
|
|
51
|
-
let timeoutId: Timer | undefined;
|
|
52
|
-
|
|
53
|
-
const killProcess = () => {
|
|
54
|
-
if (!killed) {
|
|
55
|
-
killed = true;
|
|
56
|
-
proc.kill();
|
|
57
|
-
// Force kill after 5 seconds if first kill doesn't work
|
|
58
|
-
setTimeout(() => {
|
|
59
|
-
try {
|
|
60
|
-
proc.kill(9);
|
|
61
|
-
} catch {
|
|
62
|
-
// Ignore if already dead
|
|
63
|
-
}
|
|
64
|
-
}, 5000);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Handle abort signal
|
|
69
|
-
if (options?.signal) {
|
|
70
|
-
if (options.signal.aborted) {
|
|
71
|
-
killProcess();
|
|
72
|
-
} else {
|
|
73
|
-
options.signal.addEventListener("abort", killProcess, { once: true });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Handle timeout
|
|
78
|
-
if (options?.timeout && options.timeout > 0) {
|
|
79
|
-
timeoutId = setTimeout(() => {
|
|
80
|
-
killProcess();
|
|
81
|
-
}, options.timeout);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Read streams asynchronously
|
|
85
|
-
(async () => {
|
|
86
|
-
try {
|
|
87
|
-
const stdoutReader = (proc.stdout as ReadableStream<Uint8Array>).getReader();
|
|
88
|
-
const stderrReader = (proc.stderr as ReadableStream<Uint8Array>).getReader();
|
|
89
|
-
|
|
90
|
-
// Read both streams and wait for process exit
|
|
91
|
-
const [stdoutResult, stderrResult, exitCode] = await Promise.all([
|
|
92
|
-
(async () => {
|
|
93
|
-
const chunks: Uint8Array[] = [];
|
|
94
|
-
try {
|
|
95
|
-
while (true) {
|
|
96
|
-
const { done, value } = await stdoutReader.read();
|
|
97
|
-
if (done) break;
|
|
98
|
-
chunks.push(value);
|
|
99
|
-
}
|
|
100
|
-
} finally {
|
|
101
|
-
stdoutReader.releaseLock();
|
|
102
|
-
}
|
|
103
|
-
return Buffer.concat(chunks).toString();
|
|
104
|
-
})(),
|
|
105
|
-
(async () => {
|
|
106
|
-
const chunks: Uint8Array[] = [];
|
|
107
|
-
try {
|
|
108
|
-
while (true) {
|
|
109
|
-
const { done, value } = await stderrReader.read();
|
|
110
|
-
if (done) break;
|
|
111
|
-
chunks.push(value);
|
|
112
|
-
}
|
|
113
|
-
} finally {
|
|
114
|
-
stderrReader.releaseLock();
|
|
115
|
-
}
|
|
116
|
-
return Buffer.concat(chunks).toString();
|
|
117
|
-
})(),
|
|
118
|
-
proc.exited,
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
|
-
stdout = stdoutResult;
|
|
122
|
-
stderr = stderrResult;
|
|
123
|
-
|
|
124
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
125
|
-
if (options?.signal) {
|
|
126
|
-
options.signal.removeEventListener("abort", killProcess);
|
|
127
|
-
}
|
|
128
|
-
resolve({ stdout, stderr, code: exitCode ?? 0, killed });
|
|
129
|
-
} catch (err) {
|
|
130
|
-
logger.debug("Process stream error", { error: String(err) });
|
|
131
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
132
|
-
if (options?.signal) {
|
|
133
|
-
options.signal.removeEventListener("abort", killProcess);
|
|
134
|
-
}
|
|
135
|
-
resolve({ stdout, stderr, code: 1, killed });
|
|
136
|
-
}
|
|
137
|
-
})();
|
|
39
|
+
const proc = ptree.cspawn([command, ...args], {
|
|
40
|
+
cwd,
|
|
41
|
+
signal: options?.signal,
|
|
42
|
+
timeout: options?.timeout,
|
|
138
43
|
});
|
|
44
|
+
try {
|
|
45
|
+
await proc.exited;
|
|
46
|
+
} catch {
|
|
47
|
+
// ChildProcess rejects on non-zero exit; we handle it below
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
stdout: await proc.stdout.text(),
|
|
51
|
+
stderr: await proc.stderr.text(),
|
|
52
|
+
code: proc.exitCode ?? 0,
|
|
53
|
+
killed: proc.exitReason instanceof ptree.AbortError,
|
|
54
|
+
};
|
|
139
55
|
}
|
|
@@ -110,10 +110,4 @@ export {
|
|
|
110
110
|
isReadToolResult,
|
|
111
111
|
isWriteToolResult,
|
|
112
112
|
} from "./types";
|
|
113
|
-
export {
|
|
114
|
-
ExtensionToolWrapper,
|
|
115
|
-
RegisteredToolAdapter,
|
|
116
|
-
wrapRegisteredTool,
|
|
117
|
-
wrapRegisteredTools,
|
|
118
|
-
wrapToolWithExtensions,
|
|
119
|
-
} from "./wrapper";
|
|
113
|
+
export { ExtensionToolWrapper, RegisteredToolAdapter, wrapRegisteredTool, wrapRegisteredTools } from "./wrapper";
|
|
@@ -7,6 +7,7 @@ import * as path from "node:path";
|
|
|
7
7
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
10
11
|
import type { TSchema } from "@sinclair/typebox";
|
|
11
12
|
import * as TypeBox from "@sinclair/typebox";
|
|
12
13
|
import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module";
|
|
@@ -16,7 +17,6 @@ import * as piCodingAgent from "../../index";
|
|
|
16
17
|
import { EventBus } from "../event-bus";
|
|
17
18
|
import type { ExecOptions } from "../exec";
|
|
18
19
|
import { execCommand } from "../exec";
|
|
19
|
-
import { logger } from "../logger";
|
|
20
20
|
import type { CustomMessage } from "../messages";
|
|
21
21
|
import type {
|
|
22
22
|
Extension,
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
8
9
|
import { type Theme, theme } from "../../modes/interactive/theme/theme";
|
|
9
|
-
import { logger } from "../logger";
|
|
10
10
|
import type { ModelRegistry } from "../model-registry";
|
|
11
11
|
import type { SessionManager } from "../session-manager";
|
|
12
12
|
import type {
|
|
@@ -33,8 +33,8 @@ import type { BashOperations } from "../tools/bash";
|
|
|
33
33
|
import type { EditToolDetails } from "../tools/patch";
|
|
34
34
|
|
|
35
35
|
export type { ExecOptions, ExecResult } from "../exec";
|
|
36
|
-
export type { AgentToolResult, AgentToolUpdateCallback };
|
|
37
36
|
export type { AppAction, KeybindingsManager } from "../keybindings";
|
|
37
|
+
export type { AgentToolResult, AgentToolUpdateCallback };
|
|
38
38
|
|
|
39
39
|
// ============================================================================
|
|
40
40
|
// UI Context
|
|
@@ -633,7 +633,7 @@ export interface ExtensionAPI {
|
|
|
633
633
|
// =========================================================================
|
|
634
634
|
|
|
635
635
|
/** File logger for error/warning/debug messages */
|
|
636
|
-
logger: typeof import("
|
|
636
|
+
logger: typeof import("@oh-my-pi/pi-utils").logger;
|
|
637
637
|
|
|
638
638
|
/** Injected @sinclair/typebox module for defining tool parameters */
|
|
639
639
|
typebox: typeof import("@sinclair/typebox");
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import type { Static, TSchema } from "@sinclair/typebox";
|
|
7
8
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
8
9
|
import type { ExtensionRunner } from "./runner";
|
|
9
10
|
import type { RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types";
|
|
@@ -70,16 +71,18 @@ export function wrapRegisteredTools(registeredTools: RegisteredTool[], runner: E
|
|
|
70
71
|
* - Emits tool_call event before execution (can block)
|
|
71
72
|
* - Emits tool_result event after execution (can modify result)
|
|
72
73
|
*/
|
|
73
|
-
export class ExtensionToolWrapper<
|
|
74
|
+
export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetails = unknown>
|
|
75
|
+
implements AgentTool<TParameters, TDetails>
|
|
76
|
+
{
|
|
74
77
|
name: string;
|
|
75
78
|
label: string;
|
|
76
79
|
description: string;
|
|
77
|
-
parameters:
|
|
78
|
-
renderCall?: AgentTool["renderCall"];
|
|
79
|
-
renderResult?: AgentTool["renderResult"];
|
|
80
|
+
parameters: TParameters;
|
|
81
|
+
renderCall?: AgentTool<TParameters, TDetails>["renderCall"];
|
|
82
|
+
renderResult?: AgentTool<TParameters, TDetails>["renderResult"];
|
|
80
83
|
|
|
81
84
|
constructor(
|
|
82
|
-
private tool: AgentTool<
|
|
85
|
+
private tool: AgentTool<TParameters, TDetails>,
|
|
83
86
|
private runner: ExtensionRunner,
|
|
84
87
|
) {
|
|
85
88
|
this.name = tool.name;
|
|
@@ -92,9 +95,9 @@ export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
|
92
95
|
|
|
93
96
|
async execute(
|
|
94
97
|
toolCallId: string,
|
|
95
|
-
params:
|
|
98
|
+
params: Static<TParameters>,
|
|
96
99
|
signal?: AbortSignal,
|
|
97
|
-
onUpdate?: AgentToolUpdateCallback<
|
|
100
|
+
onUpdate?: AgentToolUpdateCallback<TDetails, TParameters>,
|
|
98
101
|
context?: AgentToolContext,
|
|
99
102
|
) {
|
|
100
103
|
// Emit tool_call event - extensions can block execution
|
|
@@ -104,7 +107,7 @@ export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
|
104
107
|
type: "tool_call",
|
|
105
108
|
toolName: this.tool.name,
|
|
106
109
|
toolCallId,
|
|
107
|
-
input: params,
|
|
110
|
+
input: params as Record<string, unknown>,
|
|
108
111
|
})) as ToolCallEventResult | undefined;
|
|
109
112
|
|
|
110
113
|
if (callResult?.block) {
|
|
@@ -120,7 +123,7 @@ export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// Execute the actual tool
|
|
123
|
-
let result: { content: any; details?:
|
|
126
|
+
let result: { content: any; details?: TDetails };
|
|
124
127
|
let executionError: Error | undefined;
|
|
125
128
|
|
|
126
129
|
try {
|
|
@@ -129,7 +132,7 @@ export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
|
129
132
|
executionError = err instanceof Error ? err : new Error(String(err));
|
|
130
133
|
result = {
|
|
131
134
|
content: [{ type: "text", text: executionError.message }],
|
|
132
|
-
details: undefined as
|
|
135
|
+
details: undefined as TDetails,
|
|
133
136
|
};
|
|
134
137
|
}
|
|
135
138
|
|
|
@@ -139,7 +142,7 @@ export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
|
139
142
|
type: "tool_result",
|
|
140
143
|
toolName: this.tool.name,
|
|
141
144
|
toolCallId,
|
|
142
|
-
input: params,
|
|
145
|
+
input: params as Record<string, unknown>,
|
|
143
146
|
content: result.content,
|
|
144
147
|
details: result.details,
|
|
145
148
|
isError: !!executionError,
|
|
@@ -147,7 +150,7 @@ export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
|
147
150
|
|
|
148
151
|
if (resultResult) {
|
|
149
152
|
const modifiedContent: (TextContent | ImageContent)[] = resultResult.content ?? result.content;
|
|
150
|
-
const modifiedDetails = (resultResult.details ?? result.details) as
|
|
153
|
+
const modifiedDetails = (resultResult.details ?? result.details) as TDetails;
|
|
151
154
|
|
|
152
155
|
// Extension can override error status
|
|
153
156
|
if (resultResult.isError === true && !executionError) {
|
|
@@ -176,11 +179,3 @@ export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
|
176
179
|
return result;
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Wrap a tool with extension callbacks for interception.
|
|
182
|
-
* @deprecated Use `new ExtensionToolWrapper()` directly
|
|
183
|
-
*/
|
|
184
|
-
export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: ExtensionRunner): AgentTool<any, T> {
|
|
185
|
-
return new ExtensionToolWrapper(tool, runner);
|
|
186
|
-
}
|
package/src/core/frontmatter.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
3
5
|
import { getAgentDir } from "../config";
|
|
4
|
-
import { logger } from "./logger";
|
|
5
6
|
|
|
6
7
|
export interface HistoryEntry {
|
|
7
8
|
id: number;
|
|
@@ -134,11 +135,7 @@ END;
|
|
|
134
135
|
|
|
135
136
|
private ensureDir(dbPath: string): void {
|
|
136
137
|
const dir = dirname(dbPath);
|
|
137
|
-
|
|
138
|
-
if (result.exitCode !== 0) {
|
|
139
|
-
const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : "";
|
|
140
|
-
throw new Error(`Failed to create history directory: ${dir} ${stderr}`.trim());
|
|
141
|
-
}
|
|
138
|
+
mkdirSync(dir, { recursive: true });
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
private normalizeLimit(limit: number): number {
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// biome-ignore assist/source/organizeImports: biome is not smart
|
|
2
|
+
export type { ReadonlySessionManager, UsageStatistics } from "../session-manager";
|
|
2
3
|
export {
|
|
3
4
|
discoverAndLoadHooks,
|
|
4
5
|
loadHooks,
|
|
@@ -11,6 +12,5 @@ export {
|
|
|
11
12
|
type SendMessageHandler,
|
|
12
13
|
} from "./loader";
|
|
13
14
|
export { execCommand, HookRunner, type HookErrorListener } from "./runner";
|
|
14
|
-
export { HookToolWrapper
|
|
15
|
+
export { HookToolWrapper } from "./tool-wrapper";
|
|
15
16
|
export * from "./types";
|
|
16
|
-
export type { UsageStatistics, ReadonlySessionManager } from "../session-manager";
|
package/src/core/hooks/loader.ts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import * as typebox from "@sinclair/typebox";
|
|
7
8
|
import { hookCapability } from "../../capability/hook";
|
|
8
9
|
import type { Hook } from "../../discovery";
|
|
9
10
|
import { loadCapability } from "../../discovery";
|
|
10
11
|
import { expandPath } from "../../discovery/helpers";
|
|
11
12
|
import * as piCodingAgent from "../../index";
|
|
12
|
-
import { logger } from "../logger";
|
|
13
13
|
import type { HookMessage } from "../messages";
|
|
14
14
|
import type { SessionManager } from "../session-manager";
|
|
15
15
|
import { execCommand } from "./runner";
|