@oh-my-pi/pi-coding-agent 1.341.0 → 2.1.1337
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 +86 -0
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +10 -9
- package/src/bun-imports.d.ts +16 -0
- package/src/cli/args.ts +5 -6
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +2 -2
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/session-picker.ts +2 -2
- package/src/cli/update-cli.ts +273 -0
- package/src/cli.ts +1 -1
- package/src/config.ts +23 -75
- package/src/core/agent-session.ts +158 -16
- package/src/core/auth-storage.ts +2 -3
- package/src/core/bash-executor.ts +50 -10
- package/src/core/compaction/branch-summarization.ts +5 -5
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/compaction/index.ts +3 -3
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +232 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +3 -3
- package/src/core/custom-tools/loader.ts +10 -8
- package/src/core/custom-tools/types.ts +11 -6
- package/src/core/custom-tools/wrapper.ts +2 -1
- package/src/core/exec.ts +22 -12
- package/src/core/export-html/index.ts +38 -123
- package/src/core/export-html/template.css +0 -7
- package/src/core/export-html/template.html +3 -4
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +5 -5
- package/src/core/hooks/loader.ts +21 -16
- package/src/core/hooks/runner.ts +6 -6
- package/src/core/hooks/tool-wrapper.ts +2 -2
- package/src/core/hooks/types.ts +12 -15
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +3 -3
- package/src/core/mcp/config.ts +1 -1
- package/src/core/mcp/index.ts +12 -12
- package/src/core/mcp/loader.ts +2 -2
- package/src/core/mcp/manager.ts +6 -6
- package/src/core/mcp/tool-bridge.ts +3 -3
- package/src/core/mcp/transports/http.ts +1 -1
- package/src/core/mcp/transports/index.ts +2 -2
- package/src/core/mcp/transports/stdio.ts +1 -1
- package/src/core/messages.ts +22 -0
- package/src/core/model-registry.ts +2 -2
- package/src/core/model-resolver.ts +2 -2
- package/src/core/plugins/doctor.ts +1 -1
- package/src/core/plugins/index.ts +6 -6
- package/src/core/plugins/installer.ts +4 -4
- package/src/core/plugins/loader.ts +4 -9
- package/src/core/plugins/manager.ts +5 -5
- package/src/core/plugins/paths.ts +3 -3
- package/src/core/sdk.ts +77 -35
- package/src/core/session-manager.ts +6 -6
- package/src/core/settings-manager.ts +16 -3
- package/src/core/skills.ts +5 -5
- package/src/core/slash-commands.ts +60 -45
- package/src/core/system-prompt.ts +6 -6
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/bash.ts +32 -155
- package/src/core/tools/context.ts +2 -2
- package/src/core/tools/edit-diff.ts +3 -3
- package/src/core/tools/edit.ts +18 -5
- package/src/core/tools/exa/company.ts +3 -3
- package/src/core/tools/exa/index.ts +16 -17
- package/src/core/tools/exa/linkedin.ts +3 -3
- package/src/core/tools/exa/mcp-client.ts +9 -9
- package/src/core/tools/exa/render.ts +5 -5
- package/src/core/tools/exa/researcher.ts +3 -3
- package/src/core/tools/exa/search.ts +6 -5
- package/src/core/tools/exa/types.ts +5 -6
- package/src/core/tools/exa/websets.ts +3 -3
- package/src/core/tools/find.ts +3 -3
- package/src/core/tools/grep.ts +3 -3
- package/src/core/tools/index.ts +48 -34
- package/src/core/tools/ls.ts +4 -4
- package/src/core/tools/lsp/client.ts +161 -90
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/edits.ts +2 -2
- package/src/core/tools/lsp/index.ts +15 -13
- package/src/core/tools/lsp/render.ts +2 -2
- package/src/core/tools/lsp/rust-analyzer.ts +3 -3
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/output.ts +175 -0
- package/src/core/tools/read.ts +7 -7
- package/src/core/tools/renderers.ts +92 -13
- package/src/core/tools/review.ts +268 -0
- package/src/core/tools/task/agents.ts +22 -38
- package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
- package/src/core/tools/task/commands.ts +31 -10
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +145 -28
- package/src/core/tools/task/index.ts +78 -30
- package/src/core/tools/task/model-resolver.ts +30 -20
- package/src/core/tools/task/parallel.ts +1 -1
- package/src/core/tools/task/render.ts +219 -30
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +36 -2
- package/src/core/tools/web-fetch.ts +5 -3
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/web-search/index.ts +17 -15
- package/src/core/tools/web-search/providers/anthropic.ts +2 -2
- package/src/core/tools/web-search/providers/exa.ts +3 -5
- package/src/core/tools/web-search/providers/perplexity.ts +1 -1
- package/src/core/tools/web-search/render.ts +3 -3
- package/src/core/tools/write.ts +4 -4
- package/src/index.ts +29 -18
- package/src/main.ts +50 -33
- package/src/migrations.ts +3 -3
- package/src/modes/index.ts +5 -5
- package/src/modes/interactive/components/armin.ts +1 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/bash-execution.ts +4 -4
- package/src/modes/interactive/components/bordered-loader.ts +2 -2
- package/src/modes/interactive/components/branch-summary-message.ts +2 -2
- package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
- package/src/modes/interactive/components/diff.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/footer.ts +5 -5
- package/src/modes/interactive/components/hook-editor.ts +2 -2
- package/src/modes/interactive/components/hook-input.ts +2 -2
- package/src/modes/interactive/components/hook-message.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +2 -2
- package/src/modes/interactive/components/model-selector.ts +281 -59
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/plugin-settings.ts +4 -4
- package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
- package/src/modes/interactive/components/session-selector.ts +4 -4
- package/src/modes/interactive/components/settings-defs.ts +1 -1
- package/src/modes/interactive/components/settings-selector.ts +5 -5
- package/src/modes/interactive/components/show-images-selector.ts +2 -2
- package/src/modes/interactive/components/theme-selector.ts +2 -2
- package/src/modes/interactive/components/thinking-selector.ts +2 -2
- package/src/modes/interactive/components/tool-execution.ts +26 -8
- package/src/modes/interactive/components/tree-selector.ts +3 -3
- package/src/modes/interactive/components/user-message-selector.ts +2 -2
- package/src/modes/interactive/components/user-message.ts +1 -1
- package/src/modes/interactive/components/welcome.ts +2 -2
- package/src/modes/interactive/interactive-mode.ts +86 -42
- package/src/modes/interactive/theme/theme.ts +15 -17
- package/src/modes/print-mode.ts +4 -3
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +22 -12
- package/src/modes/rpc/rpc-types.ts +3 -3
- package/src/utils/changelog.ts +2 -2
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +93 -13
- package/src/utils/tools-manager.ts +1 -1
- package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/exa/logger.ts +0 -56
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
|
|
3
1
|
import {
|
|
4
2
|
appendFileSync,
|
|
5
3
|
closeSync,
|
|
@@ -11,16 +9,18 @@ import {
|
|
|
11
9
|
readSync,
|
|
12
10
|
statSync,
|
|
13
11
|
writeFileSync,
|
|
14
|
-
} from "fs";
|
|
15
|
-
import { join, resolve } from "path";
|
|
16
|
-
import {
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
15
|
+
import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
|
|
16
|
+
import { getAgentDir as getDefaultAgentDir } from "../config";
|
|
17
17
|
import {
|
|
18
18
|
type BashExecutionMessage,
|
|
19
19
|
createBranchSummaryMessage,
|
|
20
20
|
createCompactionSummaryMessage,
|
|
21
21
|
createHookMessage,
|
|
22
22
|
type HookMessage,
|
|
23
|
-
} from "./messages
|
|
23
|
+
} from "./messages";
|
|
24
24
|
|
|
25
25
|
export const CURRENT_SESSION_VERSION = 2;
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { dirname, join } from "path";
|
|
3
|
-
import { CONFIG_DIR_NAME, getAgentDir } from "../config
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "../config";
|
|
4
4
|
|
|
5
5
|
export interface CompactionSettings {
|
|
6
6
|
enabled?: boolean; // default: true
|
|
@@ -30,6 +30,11 @@ export interface SkillsSettings {
|
|
|
30
30
|
includeSkills?: string[]; // default: [] (empty = include all; glob patterns to filter)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export interface CommandsSettings {
|
|
34
|
+
enableClaudeUser?: boolean; // default: true (load from ~/.claude/commands/)
|
|
35
|
+
enableClaudeProject?: boolean; // default: true (load from .claude/commands/)
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
export interface TerminalSettings {
|
|
34
39
|
showImages?: boolean; // default: true (only relevant if terminal supports images)
|
|
35
40
|
}
|
|
@@ -78,6 +83,7 @@ export interface Settings {
|
|
|
78
83
|
hooks?: string[]; // Array of hook file paths
|
|
79
84
|
customTools?: string[]; // Array of custom tool file paths
|
|
80
85
|
skills?: SkillsSettings;
|
|
86
|
+
commands?: CommandsSettings;
|
|
81
87
|
terminal?: TerminalSettings;
|
|
82
88
|
enabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)
|
|
83
89
|
exa?: ExaSettings;
|
|
@@ -399,6 +405,13 @@ export class SettingsManager {
|
|
|
399
405
|
};
|
|
400
406
|
}
|
|
401
407
|
|
|
408
|
+
getCommandsSettings(): Required<CommandsSettings> {
|
|
409
|
+
return {
|
|
410
|
+
enableClaudeUser: this.settings.commands?.enableClaudeUser ?? true,
|
|
411
|
+
enableClaudeProject: this.settings.commands?.enableClaudeProject ?? true,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
402
415
|
getShowImages(): boolean {
|
|
403
416
|
return this.settings.terminal?.showImages ?? true;
|
|
404
417
|
}
|
package/src/core/skills.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "fs";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
2
4
|
import { minimatch } from "minimatch";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
|
|
6
|
-
import type { SkillsSettings } from "./settings-manager.js";
|
|
5
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "../config";
|
|
6
|
+
import type { SkillsSettings } from "./settings-manager";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Standard frontmatter fields per Agent Skills spec.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { CONFIG_DIR_NAME, getCommandsDir } from "../config";
|
|
5
|
+
import { logger } from "./logger";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Represents a custom slash command loaded from a file
|
|
@@ -98,14 +100,12 @@ export function substituteArgs(content: string, args: string[]): string {
|
|
|
98
100
|
return result;
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
type CommandSource = "builtin" | "claude-user" | "claude-project" | "user" | "project";
|
|
104
|
+
|
|
101
105
|
/**
|
|
102
106
|
* Recursively scan a directory for .md files (and symlinks to .md files) and load them as slash commands
|
|
103
107
|
*/
|
|
104
|
-
function loadCommandsFromDir(
|
|
105
|
-
dir: string,
|
|
106
|
-
source: "builtin" | "user" | "project",
|
|
107
|
-
subdir: string = "",
|
|
108
|
-
): FileSlashCommand[] {
|
|
108
|
+
function loadCommandsFromDir(dir: string, source: CommandSource, subdir: string = ""): FileSlashCommand[] {
|
|
109
109
|
const commands: FileSlashCommand[] = [];
|
|
110
110
|
|
|
111
111
|
if (!existsSync(dir)) {
|
|
@@ -129,15 +129,18 @@ function loadCommandsFromDir(
|
|
|
129
129
|
|
|
130
130
|
const name = entry.name.slice(0, -3); // Remove .md extension
|
|
131
131
|
|
|
132
|
-
// Build source string
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
// Build source string based on source type
|
|
133
|
+
const sourceLabel =
|
|
134
|
+
source === "builtin"
|
|
135
|
+
? "builtin"
|
|
136
|
+
: source === "claude-user"
|
|
137
|
+
? "claude-user"
|
|
138
|
+
: source === "claude-project"
|
|
139
|
+
? "claude-project"
|
|
140
|
+
: source === "user"
|
|
141
|
+
? "user"
|
|
142
|
+
: "project";
|
|
143
|
+
const sourceStr = subdir ? `(${sourceLabel}:${subdir})` : `(${sourceLabel})`;
|
|
141
144
|
|
|
142
145
|
// Get description from frontmatter or first non-empty line
|
|
143
146
|
let description = frontmatter.description || "";
|
|
@@ -159,13 +162,13 @@ function loadCommandsFromDir(
|
|
|
159
162
|
content,
|
|
160
163
|
source: sourceStr,
|
|
161
164
|
});
|
|
162
|
-
} catch (
|
|
163
|
-
|
|
165
|
+
} catch (err) {
|
|
166
|
+
logger.debug("Failed to read slash command file", { error: String(err) });
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
|
-
} catch (
|
|
168
|
-
|
|
170
|
+
} catch (err) {
|
|
171
|
+
logger.debug("Failed to read slash command directory", { error: String(err) });
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
return commands;
|
|
@@ -176,54 +179,66 @@ export interface LoadSlashCommandsOptions {
|
|
|
176
179
|
cwd?: string;
|
|
177
180
|
/** Agent config directory for global commands. Default: from getCommandsDir() */
|
|
178
181
|
agentDir?: string;
|
|
182
|
+
/** Enable loading from ~/.claude/commands/. Default: true */
|
|
183
|
+
enableClaudeUser?: boolean;
|
|
184
|
+
/** Enable loading from .claude/commands/. Default: true */
|
|
185
|
+
enableClaudeProject?: boolean;
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
/**
|
|
182
189
|
* Load all custom slash commands from:
|
|
183
190
|
* 1. Builtin: package commands/
|
|
184
|
-
* 2.
|
|
185
|
-
* 3.
|
|
191
|
+
* 2. Claude user: ~/.claude/commands/
|
|
192
|
+
* 3. Claude project: .claude/commands/
|
|
193
|
+
* 4. Pi user: agentDir/commands/
|
|
194
|
+
* 5. Pi project: cwd/{CONFIG_DIR_NAME}/commands/
|
|
195
|
+
*
|
|
196
|
+
* First occurrence wins (earlier sources have priority).
|
|
186
197
|
*/
|
|
187
198
|
export function loadSlashCommands(options: LoadSlashCommandsOptions = {}): FileSlashCommand[] {
|
|
188
199
|
const resolvedCwd = options.cwd ?? process.cwd();
|
|
189
200
|
const resolvedAgentDir = options.agentDir ?? getCommandsDir();
|
|
201
|
+
const enableClaudeUser = options.enableClaudeUser ?? true;
|
|
202
|
+
const enableClaudeProject = options.enableClaudeProject ?? true;
|
|
190
203
|
|
|
191
204
|
const commands: FileSlashCommand[] = [];
|
|
192
205
|
const seenNames = new Set<string>();
|
|
193
206
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (existsSync(builtinDir)) {
|
|
197
|
-
const builtinCommands = loadCommandsFromDir(builtinDir, "builtin");
|
|
198
|
-
for (const cmd of builtinCommands) {
|
|
207
|
+
const addCommands = (newCommands: FileSlashCommand[]) => {
|
|
208
|
+
for (const cmd of newCommands) {
|
|
199
209
|
if (!seenNames.has(cmd.name)) {
|
|
200
210
|
commands.push(cmd);
|
|
201
211
|
seenNames.add(cmd.name);
|
|
202
212
|
}
|
|
203
213
|
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// 1. Builtin commands (from package)
|
|
217
|
+
const builtinDir = join(import.meta.dir, "../commands");
|
|
218
|
+
if (existsSync(builtinDir)) {
|
|
219
|
+
addCommands(loadCommandsFromDir(builtinDir, "builtin"));
|
|
204
220
|
}
|
|
205
221
|
|
|
206
|
-
// 2.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
for (const cmd of globalCommands) {
|
|
211
|
-
if (!seenNames.has(cmd.name)) {
|
|
212
|
-
commands.push(cmd);
|
|
213
|
-
seenNames.add(cmd.name);
|
|
214
|
-
}
|
|
222
|
+
// 2. Claude user commands (~/.claude/commands/)
|
|
223
|
+
if (enableClaudeUser) {
|
|
224
|
+
const claudeUserDir = join(homedir(), ".claude", "commands");
|
|
225
|
+
addCommands(loadCommandsFromDir(claudeUserDir, "claude-user"));
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
// 3.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!seenNames.has(cmd.name)) {
|
|
222
|
-
commands.push(cmd);
|
|
223
|
-
seenNames.add(cmd.name);
|
|
224
|
-
}
|
|
228
|
+
// 3. Claude project commands (.claude/commands/)
|
|
229
|
+
if (enableClaudeProject) {
|
|
230
|
+
const claudeProjectDir = resolve(resolvedCwd, ".claude", "commands");
|
|
231
|
+
addCommands(loadCommandsFromDir(claudeProjectDir, "claude-project"));
|
|
225
232
|
}
|
|
226
233
|
|
|
234
|
+
// 4. Pi user commands (agentDir/commands/)
|
|
235
|
+
const globalCommandsDir = options.agentDir ? join(options.agentDir, "commands") : resolvedAgentDir;
|
|
236
|
+
addCommands(loadCommandsFromDir(globalCommandsDir, "user"));
|
|
237
|
+
|
|
238
|
+
// 5. Pi project commands (cwd/{CONFIG_DIR_NAME}/commands/)
|
|
239
|
+
const projectCommandsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "commands");
|
|
240
|
+
addCommands(loadCommandsFromDir(projectCommandsDir, "project"));
|
|
241
|
+
|
|
227
242
|
return commands;
|
|
228
243
|
}
|
|
229
244
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* System prompt construction and project context loading
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
5
7
|
import chalk from "chalk";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import type {
|
|
10
|
-
import { formatSkillsForPrompt, loadSkills, type Skill } from "./skills.js";
|
|
11
|
-
import type { ToolName } from "./tools/index.js";
|
|
8
|
+
import { getAgentDir, getDocsPath, getExamplesPath, getReadmePath } from "../config";
|
|
9
|
+
import type { SkillsSettings } from "./settings-manager";
|
|
10
|
+
import { formatSkillsForPrompt, loadSkills, type Skill } from "./skills";
|
|
11
|
+
import type { ToolName } from "./tools/index";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Execute a git command synchronously and return stdout or null on failure.
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
7
|
-
import type { ModelRegistry } from "./model-registry
|
|
8
|
-
import { findSmolModel } from "./model-resolver
|
|
7
|
+
import type { ModelRegistry } from "./model-registry";
|
|
8
|
+
import { findSmolModel } from "./model-resolver";
|
|
9
9
|
|
|
10
10
|
const TITLE_SYSTEM_PROMPT = `Generate a very short title (3-6 words) for a coding session based on the user's first message. The title should capture the main task or topic. Output ONLY the title, nothing else. No quotes, no punctuation at the end.
|
|
11
11
|
|
package/src/core/tools/bash.ts
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
|
-
import { createWriteStream } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
5
2
|
import { Type } from "@sinclair/typebox";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate.js";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Generate a unique temp file path for bash output
|
|
12
|
-
*/
|
|
13
|
-
function getTempFilePath(): string {
|
|
14
|
-
const randomId = crypto.getRandomValues(new Uint8Array(8));
|
|
15
|
-
const id = Array.from(randomId, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
16
|
-
return join(tmpdir(), `pi-bash-${id}.log`);
|
|
17
|
-
}
|
|
3
|
+
import { executeBash } from "../bash-executor";
|
|
4
|
+
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
|
|
18
5
|
|
|
19
6
|
const bashSchema = Type.Object({
|
|
20
7
|
command: Type.String({ description: "Bash command to execute" }),
|
|
@@ -74,140 +61,34 @@ Usage notes:
|
|
|
74
61
|
signal?: AbortSignal,
|
|
75
62
|
onUpdate?,
|
|
76
63
|
) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
cwd,
|
|
80
|
-
stdin: "ignore",
|
|
81
|
-
stdout: "pipe",
|
|
82
|
-
stderr: "pipe",
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// We'll stream to a temp file if output gets large
|
|
86
|
-
let tempFilePath: string | undefined;
|
|
87
|
-
let tempFileStream: ReturnType<typeof createWriteStream> | undefined;
|
|
88
|
-
let totalBytes = 0;
|
|
89
|
-
|
|
90
|
-
// Keep a rolling buffer of the last chunks for tail truncation
|
|
91
|
-
const chunks: Buffer[] = [];
|
|
92
|
-
let chunksBytes = 0;
|
|
93
|
-
const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
|
|
94
|
-
|
|
95
|
-
let timedOut = false;
|
|
96
|
-
let aborted = false;
|
|
97
|
-
|
|
98
|
-
// Handle abort signal
|
|
99
|
-
const onAbort = () => {
|
|
100
|
-
aborted = true;
|
|
101
|
-
if (child.pid) {
|
|
102
|
-
killProcessTree(child.pid);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
64
|
+
// Track output for streaming updates
|
|
65
|
+
let currentOutput = "";
|
|
105
66
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}, timeout * 1000);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const handleData = (data: Buffer) => {
|
|
124
|
-
totalBytes += data.length;
|
|
125
|
-
|
|
126
|
-
// Start writing to temp file once we exceed the threshold
|
|
127
|
-
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
128
|
-
tempFilePath = getTempFilePath();
|
|
129
|
-
tempFileStream = createWriteStream(tempFilePath);
|
|
130
|
-
for (const chunk of chunks) {
|
|
131
|
-
tempFileStream.write(chunk);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (tempFileStream) {
|
|
136
|
-
tempFileStream.write(data);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Keep rolling buffer of recent data
|
|
140
|
-
chunks.push(data);
|
|
141
|
-
chunksBytes += data.length;
|
|
142
|
-
|
|
143
|
-
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
144
|
-
const removed = chunks.shift()!;
|
|
145
|
-
chunksBytes -= removed.length;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Stream partial output to callback
|
|
149
|
-
if (onUpdate) {
|
|
150
|
-
const fullBuffer = Buffer.concat(chunks);
|
|
151
|
-
const fullText = fullBuffer.toString("utf-8");
|
|
152
|
-
const truncation = truncateTail(fullText);
|
|
153
|
-
onUpdate({
|
|
154
|
-
content: [{ type: "text", text: truncation.content || "" }],
|
|
155
|
-
details: {
|
|
156
|
-
truncation: truncation.truncated ? truncation : undefined,
|
|
157
|
-
fullOutputPath: tempFilePath,
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Read streams using Bun's ReadableStream API
|
|
164
|
-
const stdoutReader = (child.stdout as ReadableStream<Uint8Array>).getReader();
|
|
165
|
-
const stderrReader = (child.stderr as ReadableStream<Uint8Array>).getReader();
|
|
166
|
-
|
|
167
|
-
await Promise.all([
|
|
168
|
-
(async () => {
|
|
169
|
-
while (true) {
|
|
170
|
-
const { done, value } = await stdoutReader.read();
|
|
171
|
-
if (done) break;
|
|
172
|
-
handleData(Buffer.from(value));
|
|
173
|
-
}
|
|
174
|
-
})(),
|
|
175
|
-
(async () => {
|
|
176
|
-
while (true) {
|
|
177
|
-
const { done, value } = await stderrReader.read();
|
|
178
|
-
if (done) break;
|
|
179
|
-
handleData(Buffer.from(value));
|
|
67
|
+
const result = await executeBash(command, {
|
|
68
|
+
cwd,
|
|
69
|
+
timeout: timeout ? timeout * 1000 : undefined, // Convert to milliseconds
|
|
70
|
+
signal,
|
|
71
|
+
onChunk: (chunk) => {
|
|
72
|
+
currentOutput += chunk;
|
|
73
|
+
if (onUpdate) {
|
|
74
|
+
const truncation = truncateTail(currentOutput);
|
|
75
|
+
onUpdate({
|
|
76
|
+
content: [{ type: "text", text: truncation.content || "" }],
|
|
77
|
+
details: {
|
|
78
|
+
truncation: truncation.truncated ? truncation : undefined,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
180
81
|
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const exitCode = await child.exited;
|
|
185
|
-
|
|
186
|
-
// Cleanup
|
|
187
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
188
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
189
|
-
if (tempFileStream) tempFileStream.end();
|
|
190
|
-
|
|
191
|
-
// Combine all buffered chunks
|
|
192
|
-
const fullBuffer = Buffer.concat(chunks);
|
|
193
|
-
const fullOutput = fullBuffer.toString("utf-8");
|
|
194
|
-
|
|
195
|
-
if (aborted && !timedOut) {
|
|
196
|
-
let output = fullOutput;
|
|
197
|
-
if (output) output += "\n\n";
|
|
198
|
-
output += "Command aborted";
|
|
199
|
-
throw new Error(output);
|
|
200
|
-
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
201
84
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
output += `Command timed out after ${timeout} seconds`;
|
|
206
|
-
throw new Error(output);
|
|
85
|
+
// Handle errors
|
|
86
|
+
if (result.cancelled) {
|
|
87
|
+
throw new Error(result.output || "Command aborted");
|
|
207
88
|
}
|
|
208
89
|
|
|
209
|
-
// Apply tail truncation
|
|
210
|
-
const truncation = truncateTail(
|
|
90
|
+
// Apply tail truncation for final output
|
|
91
|
+
const truncation = truncateTail(result.output);
|
|
211
92
|
let outputText = truncation.content || "(no output)";
|
|
212
93
|
|
|
213
94
|
let details: BashToolDetails | undefined;
|
|
@@ -215,28 +96,24 @@ Usage notes:
|
|
|
215
96
|
if (truncation.truncated) {
|
|
216
97
|
details = {
|
|
217
98
|
truncation,
|
|
218
|
-
fullOutputPath:
|
|
99
|
+
fullOutputPath: result.fullOutputPath,
|
|
219
100
|
};
|
|
220
101
|
|
|
221
102
|
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
222
103
|
const endLine = truncation.totalLines;
|
|
223
104
|
|
|
224
105
|
if (truncation.lastLinePartial) {
|
|
225
|
-
const lastLineSize = formatSize(Buffer.byteLength(
|
|
226
|
-
outputText += `\n\n[Showing last ${formatSize(
|
|
227
|
-
truncation.outputBytes,
|
|
228
|
-
)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;
|
|
106
|
+
const lastLineSize = formatSize(Buffer.byteLength(result.output.split("\n").pop() || "", "utf-8"));
|
|
107
|
+
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${result.fullOutputPath}]`;
|
|
229
108
|
} else if (truncation.truncatedBy === "lines") {
|
|
230
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${
|
|
109
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${result.fullOutputPath}]`;
|
|
231
110
|
} else {
|
|
232
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(
|
|
233
|
-
DEFAULT_MAX_BYTES,
|
|
234
|
-
)} limit). Full output: ${tempFilePath}]`;
|
|
111
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${result.fullOutputPath}]`;
|
|
235
112
|
}
|
|
236
113
|
}
|
|
237
114
|
|
|
238
|
-
if (exitCode !== 0 && exitCode !==
|
|
239
|
-
outputText += `\n\nCommand exited with code ${exitCode}`;
|
|
115
|
+
if (result.exitCode !== 0 && result.exitCode !== undefined) {
|
|
116
|
+
outputText += `\n\nCommand exited with code ${result.exitCode}`;
|
|
240
117
|
throw new Error(outputText);
|
|
241
118
|
}
|
|
242
119
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentToolContext } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { CustomToolContext } from "../custom-tools/types
|
|
3
|
-
import type { HookUIContext } from "../hooks/types
|
|
2
|
+
import type { CustomToolContext } from "../custom-tools/types";
|
|
3
|
+
import type { HookUIContext } from "../hooks/types";
|
|
4
4
|
|
|
5
5
|
declare module "@oh-my-pi/pi-agent-core" {
|
|
6
6
|
interface AgentToolContext extends CustomToolContext {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { constants } from "node:fs";
|
|
7
|
+
import { access, readFile } from "node:fs/promises";
|
|
6
8
|
import * as Diff from "diff";
|
|
7
|
-
import {
|
|
8
|
-
import { access, readFile } from "fs/promises";
|
|
9
|
-
import { resolveToCwd } from "./path-utils.js";
|
|
9
|
+
import { resolveToCwd } from "./path-utils";
|
|
10
10
|
|
|
11
11
|
export function detectLineEnding(content: string): "\r\n" | "\n" {
|
|
12
12
|
const crlfIdx = content.indexOf("\r\n");
|
package/src/core/tools/edit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, readFile, writeFile } from "node:fs/promises";
|
|
1
3
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
4
|
import { Type } from "@sinclair/typebox";
|
|
3
|
-
import { constants } from "fs";
|
|
4
|
-
import { access, readFile, writeFile } from "fs/promises";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_FUZZY_THRESHOLD,
|
|
7
7
|
detectLineEnding,
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
normalizeToLF,
|
|
12
12
|
restoreLineEndings,
|
|
13
13
|
stripBom,
|
|
14
|
-
} from "./edit-diff
|
|
15
|
-
import type { FileDiagnosticsResult } from "./lsp/index
|
|
16
|
-
import { resolveToCwd } from "./path-utils
|
|
14
|
+
} from "./edit-diff";
|
|
15
|
+
import type { FileDiagnosticsResult } from "./lsp/index";
|
|
16
|
+
import { resolveToCwd } from "./path-utils";
|
|
17
17
|
|
|
18
18
|
const editSchema = Type.Object({
|
|
19
19
|
path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
|
|
@@ -63,6 +63,19 @@ Usage:
|
|
|
63
63
|
) => {
|
|
64
64
|
const absolutePath = resolveToCwd(path, cwd);
|
|
65
65
|
|
|
66
|
+
// Reject .ipynb files - use NotebookEdit tool instead
|
|
67
|
+
if (absolutePath.endsWith(".ipynb")) {
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: "Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
details: undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
return new Promise<{
|
|
67
80
|
content: Array<{ type: "text"; text: string }>;
|
|
68
81
|
details: EditToolDetails | undefined;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Type } from "@sinclair/typebox";
|
|
8
|
-
import type { CustomTool } from "../../custom-tools/types
|
|
9
|
-
import type { ExaRenderDetails } from "./types
|
|
8
|
+
import type { CustomTool } from "../../custom-tools/types";
|
|
9
|
+
import type { ExaRenderDetails } from "./types";
|
|
10
10
|
|
|
11
11
|
/** exa_company - Company research */
|
|
12
12
|
export const companyTool: CustomTool<any, ExaRenderDetails> = {
|
|
@@ -34,7 +34,7 @@ Parameters:
|
|
|
34
34
|
details: { error: "EXA_API_KEY not found", toolName: "exa_company" },
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
const response = await callExaTool("
|
|
37
|
+
const response = await callExaTool("company_research", params, apiKey);
|
|
38
38
|
|
|
39
39
|
if (isSearchResponse(response)) {
|
|
40
40
|
const formatted = formatSearchResults(response);
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* - 14 websets tools (CRUD, items, search, enrichment, monitor)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { CustomTool } from "../../custom-tools/types
|
|
13
|
-
import type { ExaSettings } from "../../settings-manager
|
|
14
|
-
import { companyTool } from "./company
|
|
15
|
-
import { linkedinTool } from "./linkedin
|
|
16
|
-
import { researcherTools } from "./researcher
|
|
17
|
-
import { searchTools } from "./search
|
|
18
|
-
import type { ExaRenderDetails } from "./types
|
|
19
|
-
import { websetsTools } from "./websets
|
|
12
|
+
import type { CustomTool } from "../../custom-tools/types";
|
|
13
|
+
import type { ExaSettings } from "../../settings-manager";
|
|
14
|
+
import { companyTool } from "./company";
|
|
15
|
+
import { linkedinTool } from "./linkedin";
|
|
16
|
+
import { researcherTools } from "./researcher";
|
|
17
|
+
import { searchTools } from "./search";
|
|
18
|
+
import type { ExaRenderDetails } from "./types";
|
|
19
|
+
import { websetsTools } from "./websets";
|
|
20
20
|
|
|
21
21
|
/** All Exa tools (22 total) - static export for backward compatibility */
|
|
22
22
|
export const exaTools: CustomTool<any, ExaRenderDetails>[] = [
|
|
@@ -42,9 +42,8 @@ export function getExaTools(settings: Required<ExaSettings>): CustomTool<any, Ex
|
|
|
42
42
|
return tools;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export { companyTool } from "./company
|
|
46
|
-
export { linkedinTool } from "./linkedin
|
|
47
|
-
export { logExaError, logViewError } from "./logger.js";
|
|
45
|
+
export { companyTool } from "./company";
|
|
46
|
+
export { linkedinTool } from "./linkedin";
|
|
48
47
|
export {
|
|
49
48
|
callExaTool,
|
|
50
49
|
callWebsetsTool,
|
|
@@ -54,11 +53,11 @@ export {
|
|
|
54
53
|
findApiKey,
|
|
55
54
|
formatSearchResults,
|
|
56
55
|
isSearchResponse,
|
|
57
|
-
} from "./mcp-client
|
|
58
|
-
export { renderExaCall, renderExaResult } from "./render
|
|
59
|
-
export { researcherTools } from "./researcher
|
|
56
|
+
} from "./mcp-client";
|
|
57
|
+
export { renderExaCall, renderExaResult } from "./render";
|
|
58
|
+
export { researcherTools } from "./researcher";
|
|
60
59
|
// Re-export individual modules for selective importing
|
|
61
|
-
export { searchTools } from "./search
|
|
60
|
+
export { searchTools } from "./search";
|
|
62
61
|
// Re-export types and utilities
|
|
63
|
-
export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult, MCPToolWrapperConfig } from "./types
|
|
64
|
-
export { websetsTools } from "./websets
|
|
62
|
+
export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult, MCPToolWrapperConfig } from "./types";
|
|
63
|
+
export { websetsTools } from "./websets";
|