@oh-my-pi/pi-coding-agent 1.337.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 +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash intent interceptor - redirects common shell patterns to proper tools.
|
|
3
|
+
*
|
|
4
|
+
* When an LLM calls bash with patterns like `grep`, `cat`, `find`, etc.,
|
|
5
|
+
* this interceptor provides helpful error messages directing them to use
|
|
6
|
+
* the specialized tools instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface InterceptionResult {
|
|
10
|
+
/** If true, the bash command should be blocked */
|
|
11
|
+
block: boolean;
|
|
12
|
+
/** Error message to return instead of executing */
|
|
13
|
+
message?: string;
|
|
14
|
+
/** Suggested tool to use instead */
|
|
15
|
+
suggestedTool?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Patterns that should NEVER use bash when specialized tools exist.
|
|
20
|
+
* Each pattern maps to a helpful error message.
|
|
21
|
+
*/
|
|
22
|
+
const forbiddenPatterns: Array<{
|
|
23
|
+
pattern: RegExp;
|
|
24
|
+
tool: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}> = [
|
|
27
|
+
// File reading
|
|
28
|
+
{
|
|
29
|
+
pattern: /^\s*(cat|head|tail|less|more)\s+/,
|
|
30
|
+
tool: "read",
|
|
31
|
+
message: "Use the `read` tool instead of cat/head/tail. It provides better context and handles binary files.",
|
|
32
|
+
},
|
|
33
|
+
// Content search (grep variants)
|
|
34
|
+
{
|
|
35
|
+
pattern: /^\s*(grep|rg|ripgrep|ag|ack)\s+/,
|
|
36
|
+
tool: "grep",
|
|
37
|
+
message: "Use the `grep` tool instead of grep/rg. It respects .gitignore and provides structured output.",
|
|
38
|
+
},
|
|
39
|
+
// File finding
|
|
40
|
+
{
|
|
41
|
+
pattern: /^\s*(find|fd|locate)\s+.*(-name|-iname|-type|--type|-glob)/,
|
|
42
|
+
tool: "find",
|
|
43
|
+
message: "Use the `find` tool instead of find/fd. It respects .gitignore and is faster for glob patterns.",
|
|
44
|
+
},
|
|
45
|
+
// In-place file editing
|
|
46
|
+
{
|
|
47
|
+
pattern: /^\s*sed\s+(-i|--in-place)/,
|
|
48
|
+
tool: "edit",
|
|
49
|
+
message: "Use the `edit` tool instead of sed -i. It provides diff preview and fuzzy matching.",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
pattern: /^\s*perl\s+.*-[pn]?i/,
|
|
53
|
+
tool: "edit",
|
|
54
|
+
message: "Use the `edit` tool instead of perl -i. It provides diff preview and fuzzy matching.",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
pattern: /^\s*awk\s+.*-i\s+inplace/,
|
|
58
|
+
tool: "edit",
|
|
59
|
+
message: "Use the `edit` tool instead of awk -i inplace. It provides diff preview and fuzzy matching.",
|
|
60
|
+
},
|
|
61
|
+
// File creation via redirection (but allow legitimate uses like piping)
|
|
62
|
+
{
|
|
63
|
+
pattern: /^\s*(echo|printf|cat\s*<<)\s+.*[^|]>\s*\S/,
|
|
64
|
+
tool: "write",
|
|
65
|
+
message: "Use the `write` tool instead of echo/cat redirection. It handles encoding and provides confirmation.",
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a bash command should be intercepted.
|
|
71
|
+
*
|
|
72
|
+
* @param command The bash command to check
|
|
73
|
+
* @param availableTools Set of tool names that are available
|
|
74
|
+
* @returns InterceptionResult indicating if the command should be blocked
|
|
75
|
+
*/
|
|
76
|
+
export function checkBashInterception(command: string, availableTools: Set<string>): InterceptionResult {
|
|
77
|
+
// Normalize command for pattern matching
|
|
78
|
+
const normalizedCommand = command.trim();
|
|
79
|
+
|
|
80
|
+
for (const { pattern, tool, message } of forbiddenPatterns) {
|
|
81
|
+
// Only block if the suggested tool is actually available
|
|
82
|
+
if (!availableTools.has(tool)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (pattern.test(normalizedCommand)) {
|
|
87
|
+
return {
|
|
88
|
+
block: true,
|
|
89
|
+
message: `❌ Blocked: ${message}\n\nOriginal command: ${command}`,
|
|
90
|
+
suggestedTool: tool,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { block: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if a command is a simple directory listing that should use `ls` tool.
|
|
100
|
+
* Only applies to bare `ls` without complex flags.
|
|
101
|
+
*/
|
|
102
|
+
export function checkSimpleLsInterception(command: string, availableTools: Set<string>): InterceptionResult {
|
|
103
|
+
if (!availableTools.has("ls")) {
|
|
104
|
+
return { block: false };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Match simple ls commands (ls, ls -la, ls /path, etc.)
|
|
108
|
+
// Don't intercept complex pipes or commands
|
|
109
|
+
const simpleLsPattern = /^\s*ls(\s+(-[a-zA-Z]+\s*)*)?(\s+[^|;&]+)?\s*$/;
|
|
110
|
+
|
|
111
|
+
if (simpleLsPattern.test(command.trim())) {
|
|
112
|
+
return {
|
|
113
|
+
block: true,
|
|
114
|
+
message: `Use the \`ls\` tool instead of bash ls. It provides structured output.\n\nOriginal command: ${command}`,
|
|
115
|
+
suggestedTool: "ls",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { block: false };
|
|
120
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { createWriteStream } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
5
|
+
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import type { Subprocess } from "bun";
|
|
7
|
+
import { getShellConfig, killProcessTree } from "../../utils/shell.js";
|
|
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
|
+
}
|
|
18
|
+
|
|
19
|
+
const bashSchema = Type.Object({
|
|
20
|
+
command: Type.String({ description: "Bash command to execute" }),
|
|
21
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export interface BashToolDetails {
|
|
25
|
+
truncation?: TruncationResult;
|
|
26
|
+
fullOutputPath?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
|
|
30
|
+
return {
|
|
31
|
+
name: "bash",
|
|
32
|
+
label: "Bash",
|
|
33
|
+
description: `Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
|
|
34
|
+
|
|
35
|
+
IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
|
|
36
|
+
|
|
37
|
+
Before executing the command, please follow these steps:
|
|
38
|
+
|
|
39
|
+
1. Directory Verification:
|
|
40
|
+
- If the command will create new directories or files, first use \`ls\` to verify the parent directory exists and is the correct location
|
|
41
|
+
- For example, before running "mkdir foo/bar", first use \`ls foo\` to check that "foo" exists and is the intended parent directory
|
|
42
|
+
|
|
43
|
+
2. Command Execution:
|
|
44
|
+
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
|
45
|
+
- Examples of proper quoting:
|
|
46
|
+
- cd "/Users/name/My Documents" (correct)
|
|
47
|
+
- cd /Users/name/My Documents (incorrect - will fail)
|
|
48
|
+
- python "/path/with spaces/script.py" (correct)
|
|
49
|
+
- python /path/with spaces/script.py (incorrect - will fail)
|
|
50
|
+
- After ensuring proper quoting, execute the command.
|
|
51
|
+
- Capture the output of the command.
|
|
52
|
+
|
|
53
|
+
Usage notes:
|
|
54
|
+
- The command argument is required.
|
|
55
|
+
- You can specify an optional timeout in seconds.
|
|
56
|
+
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
|
|
57
|
+
- If the output exceeds 50KB characters, output will be truncated before being returned to you.
|
|
58
|
+
- Avoid using Bash with the \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or when these commands are truly necessary for the task. Instead, always prefer using the dedicated tools for these commands:
|
|
59
|
+
- File search: Use find (NOT find or ls)
|
|
60
|
+
- Content search: Use grep (NOT grep or rg)
|
|
61
|
+
- Read files: Use read (NOT cat/head/tail)
|
|
62
|
+
- Edit files: Use edit (NOT sed/awk)
|
|
63
|
+
- Write files: Use write (NOT echo >/cat <<EOF)
|
|
64
|
+
- Communication: Output text directly (NOT echo/printf)
|
|
65
|
+
- When issuing multiple commands:
|
|
66
|
+
- If the commands are independent and can run in parallel, make multiple bash tool calls in a single message. For example, if you need to run "git status" and "git diff", send a single message with two bash tool calls in parallel.
|
|
67
|
+
- If the commands depend on each other and must run sequentially, use a single bash call with '&&' to chain them together (e.g., \`git add . && git commit -m "message" && git push\`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead.
|
|
68
|
+
- Use ';' only when you need to run commands sequentially but don't care if earlier commands fail
|
|
69
|
+
- DO NOT use newlines to separate commands (newlines are ok in quoted strings)
|
|
70
|
+
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it.`,
|
|
71
|
+
parameters: bashSchema,
|
|
72
|
+
execute: async (
|
|
73
|
+
_toolCallId: string,
|
|
74
|
+
{ command, timeout }: { command: string; timeout?: number },
|
|
75
|
+
signal?: AbortSignal,
|
|
76
|
+
onUpdate?,
|
|
77
|
+
) => {
|
|
78
|
+
const { shell, args } = getShellConfig();
|
|
79
|
+
const child: Subprocess = Bun.spawn([shell, ...args, command], {
|
|
80
|
+
cwd,
|
|
81
|
+
stdin: "ignore",
|
|
82
|
+
stdout: "pipe",
|
|
83
|
+
stderr: "pipe",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// We'll stream to a temp file if output gets large
|
|
87
|
+
let tempFilePath: string | undefined;
|
|
88
|
+
let tempFileStream: ReturnType<typeof createWriteStream> | undefined;
|
|
89
|
+
let totalBytes = 0;
|
|
90
|
+
|
|
91
|
+
// Keep a rolling buffer of the last chunks for tail truncation
|
|
92
|
+
const chunks: Buffer[] = [];
|
|
93
|
+
let chunksBytes = 0;
|
|
94
|
+
const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
|
|
95
|
+
|
|
96
|
+
let timedOut = false;
|
|
97
|
+
let aborted = false;
|
|
98
|
+
|
|
99
|
+
// Handle abort signal
|
|
100
|
+
const onAbort = () => {
|
|
101
|
+
aborted = true;
|
|
102
|
+
if (child.pid) {
|
|
103
|
+
killProcessTree(child.pid);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (signal) {
|
|
108
|
+
if (signal.aborted) {
|
|
109
|
+
child.kill();
|
|
110
|
+
throw new Error("Command aborted");
|
|
111
|
+
}
|
|
112
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Set timeout if provided
|
|
116
|
+
let timeoutHandle: Timer | undefined;
|
|
117
|
+
if (timeout !== undefined && timeout > 0) {
|
|
118
|
+
timeoutHandle = setTimeout(() => {
|
|
119
|
+
timedOut = true;
|
|
120
|
+
onAbort();
|
|
121
|
+
}, timeout * 1000);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const handleData = (data: Buffer) => {
|
|
125
|
+
totalBytes += data.length;
|
|
126
|
+
|
|
127
|
+
// Start writing to temp file once we exceed the threshold
|
|
128
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
129
|
+
tempFilePath = getTempFilePath();
|
|
130
|
+
tempFileStream = createWriteStream(tempFilePath);
|
|
131
|
+
for (const chunk of chunks) {
|
|
132
|
+
tempFileStream.write(chunk);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (tempFileStream) {
|
|
137
|
+
tempFileStream.write(data);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Keep rolling buffer of recent data
|
|
141
|
+
chunks.push(data);
|
|
142
|
+
chunksBytes += data.length;
|
|
143
|
+
|
|
144
|
+
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
145
|
+
const removed = chunks.shift()!;
|
|
146
|
+
chunksBytes -= removed.length;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Stream partial output to callback
|
|
150
|
+
if (onUpdate) {
|
|
151
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
152
|
+
const fullText = fullBuffer.toString("utf-8");
|
|
153
|
+
const truncation = truncateTail(fullText);
|
|
154
|
+
onUpdate({
|
|
155
|
+
content: [{ type: "text", text: truncation.content || "" }],
|
|
156
|
+
details: {
|
|
157
|
+
truncation: truncation.truncated ? truncation : undefined,
|
|
158
|
+
fullOutputPath: tempFilePath,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Read streams using Bun's ReadableStream API
|
|
165
|
+
const stdoutReader = (child.stdout as ReadableStream<Uint8Array>).getReader();
|
|
166
|
+
const stderrReader = (child.stderr as ReadableStream<Uint8Array>).getReader();
|
|
167
|
+
|
|
168
|
+
await Promise.all([
|
|
169
|
+
(async () => {
|
|
170
|
+
while (true) {
|
|
171
|
+
const { done, value } = await stdoutReader.read();
|
|
172
|
+
if (done) break;
|
|
173
|
+
handleData(Buffer.from(value));
|
|
174
|
+
}
|
|
175
|
+
})(),
|
|
176
|
+
(async () => {
|
|
177
|
+
while (true) {
|
|
178
|
+
const { done, value } = await stderrReader.read();
|
|
179
|
+
if (done) break;
|
|
180
|
+
handleData(Buffer.from(value));
|
|
181
|
+
}
|
|
182
|
+
})(),
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
const exitCode = await child.exited;
|
|
186
|
+
|
|
187
|
+
// Cleanup
|
|
188
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
189
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
190
|
+
if (tempFileStream) tempFileStream.end();
|
|
191
|
+
|
|
192
|
+
// Combine all buffered chunks
|
|
193
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
194
|
+
const fullOutput = fullBuffer.toString("utf-8");
|
|
195
|
+
|
|
196
|
+
if (aborted && !timedOut) {
|
|
197
|
+
let output = fullOutput;
|
|
198
|
+
if (output) output += "\n\n";
|
|
199
|
+
output += "Command aborted";
|
|
200
|
+
throw new Error(output);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (timedOut) {
|
|
204
|
+
let output = fullOutput;
|
|
205
|
+
if (output) output += "\n\n";
|
|
206
|
+
output += `Command timed out after ${timeout} seconds`;
|
|
207
|
+
throw new Error(output);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Apply tail truncation
|
|
211
|
+
const truncation = truncateTail(fullOutput);
|
|
212
|
+
let outputText = truncation.content || "(no output)";
|
|
213
|
+
|
|
214
|
+
let details: BashToolDetails | undefined;
|
|
215
|
+
|
|
216
|
+
if (truncation.truncated) {
|
|
217
|
+
details = {
|
|
218
|
+
truncation,
|
|
219
|
+
fullOutputPath: tempFilePath,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
223
|
+
const endLine = truncation.totalLines;
|
|
224
|
+
|
|
225
|
+
if (truncation.lastLinePartial) {
|
|
226
|
+
const lastLineSize = formatSize(Buffer.byteLength(fullOutput.split("\n").pop() || "", "utf-8"));
|
|
227
|
+
outputText += `\n\n[Showing last ${formatSize(
|
|
228
|
+
truncation.outputBytes,
|
|
229
|
+
)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;
|
|
230
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
231
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;
|
|
232
|
+
} else {
|
|
233
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(
|
|
234
|
+
DEFAULT_MAX_BYTES,
|
|
235
|
+
)} limit). Full output: ${tempFilePath}]`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (exitCode !== 0 && exitCode !== null) {
|
|
240
|
+
outputText += `\n\nCommand exited with code ${exitCode}`;
|
|
241
|
+
throw new Error(outputText);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { content: [{ type: "text", text: outputText }], details };
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Default bash tool using process.cwd() - for backwards compatibility */
|
|
250
|
+
export const bashTool = createBashTool(process.cwd());
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AgentToolContext } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { CustomToolContext } from "../custom-tools/types.js";
|
|
3
|
+
import type { HookUIContext } from "../hooks/types.js";
|
|
4
|
+
|
|
5
|
+
declare module "@oh-my-pi/pi-agent-core" {
|
|
6
|
+
interface AgentToolContext extends CustomToolContext {
|
|
7
|
+
ui?: HookUIContext;
|
|
8
|
+
hasUI?: boolean;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToolContextStore {
|
|
13
|
+
getContext(): AgentToolContext;
|
|
14
|
+
setUIContext(uiContext: HookUIContext, hasUI: boolean): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createToolContextStore(getBaseContext: () => CustomToolContext): ToolContextStore {
|
|
18
|
+
let uiContext: HookUIContext | undefined;
|
|
19
|
+
let hasUI = false;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
getContext: () => ({
|
|
23
|
+
...getBaseContext(),
|
|
24
|
+
ui: uiContext,
|
|
25
|
+
hasUI,
|
|
26
|
+
}),
|
|
27
|
+
setUIContext: (context, uiAvailable) => {
|
|
28
|
+
uiContext = context;
|
|
29
|
+
hasUI = uiAvailable;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|