@oh-my-pi/pi-coding-agent 0.1.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 +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -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 +670 -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/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 +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- 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 +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -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 +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -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 +199 -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/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -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 +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -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 +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -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 +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -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-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask Tool - Interactive user prompting during execution
|
|
3
|
+
*
|
|
4
|
+
* Use this tool when you need to ask the user questions during execution.
|
|
5
|
+
* This allows you to:
|
|
6
|
+
* 1. Gather user preferences or requirements
|
|
7
|
+
* 2. Clarify ambiguous instructions
|
|
8
|
+
* 3. Get decisions on implementation choices as you work
|
|
9
|
+
* 4. Offer choices to the user about what direction to take
|
|
10
|
+
*
|
|
11
|
+
* Usage notes:
|
|
12
|
+
* - Users will always be able to select "Other" to provide custom text input
|
|
13
|
+
* - Use multi: true to allow multiple answers to be selected for a question
|
|
14
|
+
* - If you recommend a specific option, make that the first option in the list
|
|
15
|
+
* and add "(Recommended)" at the end of the label
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
19
|
+
import { Type } from "@sinclair/typebox";
|
|
20
|
+
import { theme } from "../../modes/interactive/theme/theme";
|
|
21
|
+
import askDescription from "../../prompts/tools/ask.md" with { type: "text" };
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Types
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
const OptionItem = Type.Object({
|
|
28
|
+
label: Type.String({ description: "Display label for this option" }),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const askSchema = Type.Object({
|
|
32
|
+
question: Type.String({ description: "The question to ask the user" }),
|
|
33
|
+
options: Type.Array(OptionItem, {
|
|
34
|
+
description: "Available options for the user to choose from.",
|
|
35
|
+
minItems: 1,
|
|
36
|
+
}),
|
|
37
|
+
multi: Type.Optional(
|
|
38
|
+
Type.Boolean({
|
|
39
|
+
description: "Allow multiple options to be selected (default: false)",
|
|
40
|
+
default: false,
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export interface AskToolDetails {
|
|
46
|
+
question: string;
|
|
47
|
+
options: string[];
|
|
48
|
+
multi: boolean;
|
|
49
|
+
selectedOptions: string[];
|
|
50
|
+
customInput?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Constants
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
const OTHER_OPTION = "Other (type your own)";
|
|
58
|
+
function getDoneOptionLabel(): string {
|
|
59
|
+
return `${theme.status.success} Done selecting`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// Tool Implementation
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskToolDetails> {
|
|
67
|
+
return {
|
|
68
|
+
name: "ask",
|
|
69
|
+
label: "Ask",
|
|
70
|
+
description: askDescription,
|
|
71
|
+
parameters: askSchema,
|
|
72
|
+
|
|
73
|
+
async execute(
|
|
74
|
+
_toolCallId: string,
|
|
75
|
+
params: { question: string; options: Array<{ label: string }>; multi?: boolean },
|
|
76
|
+
_signal?: AbortSignal,
|
|
77
|
+
_onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
|
|
78
|
+
context?: AgentToolContext,
|
|
79
|
+
) {
|
|
80
|
+
const { question, options, multi = false } = params;
|
|
81
|
+
const optionLabels = options.map((o) => o.label);
|
|
82
|
+
const doneLabel = getDoneOptionLabel();
|
|
83
|
+
|
|
84
|
+
// Headless fallback - return error if no UI available
|
|
85
|
+
if (!context?.hasUI || !context.ui) {
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text" as const,
|
|
90
|
+
text: "Error: User prompt requires interactive mode",
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
details: {
|
|
94
|
+
question,
|
|
95
|
+
options: optionLabels,
|
|
96
|
+
multi,
|
|
97
|
+
selectedOptions: [],
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { ui } = context;
|
|
103
|
+
let selectedOptions: string[] = [];
|
|
104
|
+
let customInput: string | undefined;
|
|
105
|
+
|
|
106
|
+
if (multi) {
|
|
107
|
+
// Multi-select: show checkboxes in the label to indicate selection state
|
|
108
|
+
const selected = new Set<string>();
|
|
109
|
+
|
|
110
|
+
while (true) {
|
|
111
|
+
// Build options with checkbox indicators
|
|
112
|
+
const opts: string[] = [];
|
|
113
|
+
|
|
114
|
+
// Add "Done" option if any selected
|
|
115
|
+
if (selected.size > 0) {
|
|
116
|
+
opts.push(doneLabel);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add all options with checkbox prefix
|
|
120
|
+
for (const opt of optionLabels) {
|
|
121
|
+
const checkbox = selected.has(opt) ? theme.checkbox.checked : theme.checkbox.unchecked;
|
|
122
|
+
opts.push(`${checkbox} ${opt}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add "Other" option
|
|
126
|
+
opts.push(OTHER_OPTION);
|
|
127
|
+
|
|
128
|
+
const prefix = selected.size > 0 ? `(${selected.size} selected) ` : "";
|
|
129
|
+
const choice = await ui.select(`${prefix}${question}`, opts);
|
|
130
|
+
|
|
131
|
+
if (choice === undefined || choice === doneLabel) break;
|
|
132
|
+
|
|
133
|
+
if (choice === OTHER_OPTION) {
|
|
134
|
+
const input = await ui.input("Enter your response:");
|
|
135
|
+
if (input) customInput = input;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Toggle selection - extract the actual option name
|
|
140
|
+
const checkedPrefix = `${theme.checkbox.checked} `;
|
|
141
|
+
const uncheckedPrefix = `${theme.checkbox.unchecked} `;
|
|
142
|
+
let opt: string | undefined;
|
|
143
|
+
if (choice.startsWith(checkedPrefix)) {
|
|
144
|
+
opt = choice.slice(checkedPrefix.length);
|
|
145
|
+
} else if (choice.startsWith(uncheckedPrefix)) {
|
|
146
|
+
opt = choice.slice(uncheckedPrefix.length);
|
|
147
|
+
}
|
|
148
|
+
if (opt) {
|
|
149
|
+
if (selected.has(opt)) {
|
|
150
|
+
selected.delete(opt);
|
|
151
|
+
} else {
|
|
152
|
+
selected.add(opt);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
selectedOptions = Array.from(selected);
|
|
157
|
+
} else {
|
|
158
|
+
// Single select with "Other" option
|
|
159
|
+
const choice = await ui.select(question, [...optionLabels, OTHER_OPTION]);
|
|
160
|
+
if (choice === OTHER_OPTION) {
|
|
161
|
+
const input = await ui.input("Enter your response:");
|
|
162
|
+
if (input) customInput = input;
|
|
163
|
+
} else if (choice) {
|
|
164
|
+
selectedOptions = [choice];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const details: AskToolDetails = {
|
|
169
|
+
question,
|
|
170
|
+
options: optionLabels,
|
|
171
|
+
multi,
|
|
172
|
+
selectedOptions,
|
|
173
|
+
customInput,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
let responseText: string;
|
|
177
|
+
if (customInput) {
|
|
178
|
+
responseText = `User provided custom input: ${customInput}`;
|
|
179
|
+
} else if (selectedOptions.length > 0) {
|
|
180
|
+
responseText = multi
|
|
181
|
+
? `User selected: ${selectedOptions.join(", ")}`
|
|
182
|
+
: `User selected: ${selectedOptions[0]}`;
|
|
183
|
+
} else {
|
|
184
|
+
responseText = "User cancelled the selection";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { content: [{ type: "text" as const, text: responseText }], details };
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Default ask tool using process.cwd() - for backwards compatibility (no UI) */
|
|
193
|
+
export const askTool = createAskTool(process.cwd());
|
|
@@ -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,91 @@
|
|
|
1
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import bashDescription from "../../prompts/tools/bash.md" with { type: "text" };
|
|
4
|
+
import { executeBash } from "../bash-executor";
|
|
5
|
+
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
|
|
6
|
+
|
|
7
|
+
const bashSchema = Type.Object({
|
|
8
|
+
command: Type.String({ description: "Bash command to execute" }),
|
|
9
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export interface BashToolDetails {
|
|
13
|
+
truncation?: TruncationResult;
|
|
14
|
+
fullOutputPath?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
|
|
18
|
+
return {
|
|
19
|
+
name: "bash",
|
|
20
|
+
label: "Bash",
|
|
21
|
+
description: bashDescription,
|
|
22
|
+
parameters: bashSchema,
|
|
23
|
+
execute: async (
|
|
24
|
+
_toolCallId: string,
|
|
25
|
+
{ command, timeout }: { command: string; timeout?: number },
|
|
26
|
+
signal?: AbortSignal,
|
|
27
|
+
onUpdate?,
|
|
28
|
+
) => {
|
|
29
|
+
// Track output for streaming updates
|
|
30
|
+
let currentOutput = "";
|
|
31
|
+
|
|
32
|
+
const result = await executeBash(command, {
|
|
33
|
+
cwd,
|
|
34
|
+
timeout: timeout ? timeout * 1000 : undefined, // Convert to milliseconds
|
|
35
|
+
signal,
|
|
36
|
+
onChunk: (chunk) => {
|
|
37
|
+
currentOutput += chunk;
|
|
38
|
+
if (onUpdate) {
|
|
39
|
+
const truncation = truncateTail(currentOutput);
|
|
40
|
+
onUpdate({
|
|
41
|
+
content: [{ type: "text", text: truncation.content || "" }],
|
|
42
|
+
details: {
|
|
43
|
+
truncation: truncation.truncated ? truncation : undefined,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Handle errors
|
|
51
|
+
if (result.cancelled) {
|
|
52
|
+
throw new Error(result.output || "Command aborted");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Apply tail truncation for final output
|
|
56
|
+
const truncation = truncateTail(result.output);
|
|
57
|
+
let outputText = truncation.content || "(no output)";
|
|
58
|
+
|
|
59
|
+
let details: BashToolDetails | undefined;
|
|
60
|
+
|
|
61
|
+
if (truncation.truncated) {
|
|
62
|
+
details = {
|
|
63
|
+
truncation,
|
|
64
|
+
fullOutputPath: result.fullOutputPath,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
68
|
+
const endLine = truncation.totalLines;
|
|
69
|
+
|
|
70
|
+
if (truncation.lastLinePartial) {
|
|
71
|
+
const lastLineSize = formatSize(Buffer.byteLength(result.output.split("\n").pop() || "", "utf-8"));
|
|
72
|
+
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${result.fullOutputPath}]`;
|
|
73
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
74
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${result.fullOutputPath}]`;
|
|
75
|
+
} else {
|
|
76
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${result.fullOutputPath}]`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (result.exitCode !== 0 && result.exitCode !== undefined) {
|
|
81
|
+
outputText += `\n\nCommand exited with code ${result.exitCode}`;
|
|
82
|
+
throw new Error(outputText);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { content: [{ type: "text", text: outputText }], details };
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Default bash tool using process.cwd() - for backwards compatibility */
|
|
91
|
+
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";
|
|
3
|
+
import type { HookUIContext } from "../hooks/types";
|
|
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
|
+
}
|