@ebowwa/coder 0.7.63 → 0.7.65
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/dist/core/__tests__/permissions.test.d.ts +12 -0
- package/dist/core/__tests__/permissions.test.d.ts.map +1 -0
- package/dist/core/__tests__/permissions.test.js +851 -0
- package/dist/core/agent-loop/__tests__/compaction.test.d.ts +5 -0
- package/dist/core/agent-loop/__tests__/compaction.test.d.ts.map +1 -0
- package/dist/core/agent-loop/__tests__/compaction.test.js +209 -0
- package/dist/core/agent-loop/__tests__/formatters.test.d.ts +5 -0
- package/dist/core/agent-loop/__tests__/formatters.test.d.ts.map +1 -0
- package/dist/core/agent-loop/__tests__/formatters.test.js +195 -0
- package/dist/core/agent-loop/__tests__/index.test.d.ts +5 -0
- package/dist/core/agent-loop/__tests__/index.test.d.ts.map +1 -0
- package/dist/core/agent-loop/__tests__/index.test.js +121 -0
- package/dist/core/agent-loop/__tests__/loop-state.test.d.ts +5 -0
- package/dist/core/agent-loop/__tests__/loop-state.test.d.ts.map +1 -0
- package/dist/core/agent-loop/__tests__/loop-state.test.js +340 -0
- package/dist/core/agent-loop/__tests__/message-builder.test.d.ts +5 -0
- package/dist/core/agent-loop/__tests__/message-builder.test.d.ts.map +1 -0
- package/dist/core/agent-loop/__tests__/message-builder.test.js +178 -0
- package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts +5 -0
- package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts.map +1 -0
- package/dist/core/agent-loop/__tests__/tool-executor.test.js +331 -0
- package/dist/core/agent-loop/compaction.d.ts +39 -0
- package/dist/core/agent-loop/compaction.d.ts.map +1 -0
- package/dist/core/agent-loop/compaction.js +51 -0
- package/dist/core/agent-loop/formatters.d.ts +21 -0
- package/dist/core/agent-loop/formatters.d.ts.map +1 -0
- package/dist/core/agent-loop/formatters.js +42 -0
- package/dist/core/agent-loop/index.d.ts +25 -0
- package/dist/core/agent-loop/index.d.ts.map +1 -0
- package/dist/core/agent-loop/index.js +83 -0
- package/dist/core/agent-loop/loop-state.d.ts +74 -0
- package/dist/core/agent-loop/loop-state.d.ts.map +1 -0
- package/dist/core/agent-loop/loop-state.js +147 -0
- package/dist/core/agent-loop/message-builder.d.ts +13 -0
- package/dist/core/agent-loop/message-builder.d.ts.map +1 -0
- package/dist/core/agent-loop/message-builder.js +49 -0
- package/dist/core/agent-loop/tool-executor.d.ts +23 -0
- package/dist/core/agent-loop/tool-executor.d.ts.map +1 -0
- package/dist/core/agent-loop/tool-executor.js +152 -0
- package/dist/core/agent-loop/turn-executor.d.ts +57 -0
- package/dist/core/agent-loop/turn-executor.d.ts.map +1 -0
- package/dist/core/agent-loop/turn-executor.js +124 -0
- package/dist/core/agent-loop/types.d.ts +141 -0
- package/dist/core/agent-loop/types.d.ts.map +1 -0
- package/dist/core/agent-loop/types.js +4 -0
- package/dist/core/agent-loop.d.ts +17 -0
- package/dist/core/agent-loop.d.ts.map +1 -0
- package/dist/core/agent-loop.js +16 -0
- package/dist/core/api-client-impl.d.ts +62 -0
- package/dist/core/api-client-impl.d.ts.map +1 -0
- package/dist/core/api-client-impl.js +479 -0
- package/dist/core/api-client.d.ts +6 -0
- package/dist/core/api-client.d.ts.map +1 -0
- package/dist/core/api-client.js +5 -0
- package/dist/core/checkpoints.d.ts +128 -0
- package/dist/core/checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints.js +438 -0
- package/dist/core/claude-md.d.ts +71 -0
- package/dist/core/claude-md.d.ts.map +1 -0
- package/dist/core/claude-md.js +198 -0
- package/dist/core/cognitive-security/hooks.d.ts +138 -0
- package/dist/core/cognitive-security/hooks.d.ts.map +1 -0
- package/dist/core/cognitive-security/hooks.js +389 -0
- package/dist/core/cognitive-security/index.d.ts +751 -0
- package/dist/core/cognitive-security/index.d.ts.map +1 -0
- package/dist/core/cognitive-security/index.js +1123 -0
- package/dist/core/cognitive-security/middleware.d.ts +136 -0
- package/dist/core/cognitive-security/middleware.d.ts.map +1 -0
- package/dist/core/cognitive-security/middleware.js +376 -0
- package/dist/core/config-loader.d.ts +127 -0
- package/dist/core/config-loader.d.ts.map +1 -0
- package/dist/core/config-loader.js +219 -0
- package/dist/core/context-compaction.d.ts +87 -0
- package/dist/core/context-compaction.d.ts.map +1 -0
- package/dist/core/context-compaction.js +428 -0
- package/dist/core/git-status.d.ts +25 -0
- package/dist/core/git-status.d.ts.map +1 -0
- package/dist/core/git-status.js +204 -0
- package/dist/core/image.d.ts +69 -0
- package/dist/core/image.d.ts.map +1 -0
- package/dist/core/image.js +290 -0
- package/dist/core/image.test.d.ts +2 -0
- package/dist/core/image.test.d.ts.map +1 -0
- package/dist/core/image.test.js +149 -0
- package/dist/core/models.d.ts +123 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +325 -0
- package/dist/core/permissions.d.ts +81 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +327 -0
- package/dist/core/retry.d.ts +25 -0
- package/dist/core/retry.d.ts.map +1 -0
- package/dist/core/retry.js +121 -0
- package/dist/core/session-store.d.ts +9 -0
- package/dist/core/session-store.d.ts.map +1 -0
- package/dist/core/session-store.js +10 -0
- package/dist/core/sessions/export.d.ts +47 -0
- package/dist/core/sessions/export.d.ts.map +1 -0
- package/dist/core/sessions/export.js +256 -0
- package/dist/core/sessions/index.d.ts +132 -0
- package/dist/core/sessions/index.d.ts.map +1 -0
- package/dist/core/sessions/index.js +442 -0
- package/dist/core/sessions/metadata.d.ts +77 -0
- package/dist/core/sessions/metadata.d.ts.map +1 -0
- package/dist/core/sessions/metadata.js +233 -0
- package/dist/core/sessions/persistence.d.ts +72 -0
- package/dist/core/sessions/persistence.d.ts.map +1 -0
- package/dist/core/sessions/persistence.js +201 -0
- package/dist/core/sessions/types.d.ts +110 -0
- package/dist/core/sessions/types.d.ts.map +1 -0
- package/dist/core/sessions/types.js +4 -0
- package/dist/core/stream-highlighter.d.ts +18 -0
- package/dist/core/stream-highlighter.d.ts.map +1 -0
- package/dist/core/stream-highlighter.js +916 -0
- package/dist/core/system-reminders.d.ts +89 -0
- package/dist/core/system-reminders.d.ts.map +1 -0
- package/dist/core/system-reminders.js +285 -0
- package/dist/ecosystem/hooks/__tests__/index.test.d.ts +5 -0
- package/dist/ecosystem/hooks/__tests__/index.test.d.ts.map +1 -0
- package/dist/ecosystem/hooks/__tests__/index.test.js +458 -0
- package/dist/ecosystem/hooks/index.d.ts +59 -0
- package/dist/ecosystem/hooks/index.d.ts.map +1 -0
- package/dist/ecosystem/hooks/index.js +294 -0
- package/dist/ecosystem/hooks/prompt-evaluator.d.ts +32 -0
- package/dist/ecosystem/hooks/prompt-evaluator.d.ts.map +1 -0
- package/dist/ecosystem/hooks/prompt-evaluator.js +229 -0
- package/dist/ecosystem/skills/index.d.ts +55 -0
- package/dist/ecosystem/skills/index.d.ts.map +1 -0
- package/dist/ecosystem/skills/index.js +258 -0
- package/dist/ecosystem/tools/__tests__/index.test.d.ts +7 -0
- package/dist/ecosystem/tools/__tests__/index.test.d.ts.map +1 -0
- package/dist/ecosystem/tools/__tests__/index.test.js +856 -0
- package/dist/ecosystem/tools/index.d.ts +24 -0
- package/dist/ecosystem/tools/index.d.ts.map +1 -0
- package/dist/ecosystem/tools/index.js +1709 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33688 -49712
- package/dist/interfaces/mcp/client.d.ts +40 -0
- package/dist/interfaces/mcp/client.d.ts.map +1 -0
- package/dist/interfaces/mcp/client.js +309 -0
- package/dist/interfaces/ui/index.d.ts +36 -0
- package/dist/interfaces/ui/index.d.ts.map +1 -0
- package/dist/interfaces/ui/index.js +61 -0
- package/dist/interfaces/ui/spinner.d.ts +140 -0
- package/dist/interfaces/ui/spinner.d.ts.map +1 -0
- package/dist/interfaces/ui/spinner.js +342 -0
- package/dist/interfaces/ui/terminal/cli/index.d.ts +12 -0
- package/dist/interfaces/ui/terminal/cli/index.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/cli/index.js +32012 -50526
- package/dist/interfaces/ui/terminal/native/README.md +53 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
- package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
- package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
- package/dist/interfaces/ui/terminal/native/index.js +43 -0
- package/dist/interfaces/ui/terminal/native/index.node +0 -0
- package/dist/interfaces/ui/terminal/native/package.json +34 -0
- package/dist/interfaces/ui/terminal/shared/args.d.ts +39 -0
- package/dist/interfaces/ui/terminal/shared/args.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/shared/args.js +176 -0
- package/dist/interfaces/ui/terminal/shared/index.d.ts +11 -0
- package/dist/interfaces/ui/terminal/shared/index.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/shared/index.js +16 -0
- package/dist/interfaces/ui/terminal/shared/loading-state.d.ts +124 -0
- package/dist/interfaces/ui/terminal/shared/loading-state.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/shared/loading-state.js +246 -0
- package/dist/interfaces/ui/terminal/shared/query.d.ts +22 -0
- package/dist/interfaces/ui/terminal/shared/query.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/shared/query.js +100 -0
- package/dist/interfaces/ui/terminal/shared/setup.d.ts +33 -0
- package/dist/interfaces/ui/terminal/shared/setup.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/shared/setup.js +226 -0
- package/dist/interfaces/ui/terminal/shared/status-line.d.ts +117 -0
- package/dist/interfaces/ui/terminal/shared/status-line.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/shared/status-line.js +267 -0
- package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts +38 -0
- package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/shared/system-prompt.js +102 -0
- package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts +39 -0
- package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/HelpPanel.js +215 -0
- package/dist/interfaces/ui/terminal/tui/InputContext.d.ts +91 -0
- package/dist/interfaces/ui/terminal/tui/InputContext.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/InputContext.js +154 -0
- package/dist/interfaces/ui/terminal/tui/InputField.d.ts +18 -0
- package/dist/interfaces/ui/terminal/tui/InputField.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/InputField.js +41 -0
- package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts +16 -0
- package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/InteractiveTUI.js +451 -0
- package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts +10 -0
- package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/MessageArea.js +91 -0
- package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts +48 -0
- package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/MessageStore.js +151 -0
- package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts +9 -0
- package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/StatusBar.js +36 -0
- package/dist/interfaces/ui/terminal/tui/commands.d.ts +21 -0
- package/dist/interfaces/ui/terminal/tui/commands.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/commands.js +359 -0
- package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts +115 -0
- package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.js +306 -0
- package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts +92 -0
- package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/components/MultilineInput.js +399 -0
- package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts +59 -0
- package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/components/PaneManager.js +139 -0
- package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts +68 -0
- package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/components/Sidebar.js +340 -0
- package/dist/interfaces/ui/terminal/tui/components/index.d.ts +23 -0
- package/dist/interfaces/ui/terminal/tui/components/index.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/components/index.js +51 -0
- package/dist/interfaces/ui/terminal/tui/console.d.ts +20 -0
- package/dist/interfaces/ui/terminal/tui/console.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/console.js +46 -0
- package/dist/interfaces/ui/terminal/tui/index.d.ts +20 -0
- package/dist/interfaces/ui/terminal/tui/index.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/index.js +28 -0
- package/dist/interfaces/ui/terminal/tui/run.d.ts +13 -0
- package/dist/interfaces/ui/terminal/tui/run.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/run.js +31 -0
- package/dist/interfaces/ui/terminal/tui/spinner.d.ts +44 -0
- package/dist/interfaces/ui/terminal/tui/spinner.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/spinner.js +59 -0
- package/dist/interfaces/ui/terminal/tui/tui-app.d.ts +39 -0
- package/dist/interfaces/ui/terminal/tui/tui-app.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/tui-app.js +198 -0
- package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts +167 -0
- package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/tui-footer.js +330 -0
- package/dist/interfaces/ui/terminal/tui/types.d.ts +165 -0
- package/dist/interfaces/ui/terminal/tui/types.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/types.js +5 -0
- package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts +23 -0
- package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/useInputHandler.js +72 -0
- package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts +90 -0
- package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts.map +1 -0
- package/dist/interfaces/ui/terminal/tui/useNativeInput.js +188 -0
- package/dist/native/README.md +53 -0
- package/dist/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/native/claude_code_native.dylib +0 -0
- package/dist/native/index.d.ts +0 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.darwin-arm64.node +0 -0
- package/dist/native/index.js +43 -0
- package/dist/native/index.node +0 -0
- package/dist/native/package.json +34 -0
- package/dist/teammates/index.d.ts +161 -0
- package/dist/teammates/index.d.ts.map +1 -0
- package/dist/teammates/index.js +827 -0
- package/dist/types/index.d.ts +482 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +52 -0
- package/native/index.darwin-arm64.node +0 -0
- package/native/index.js +33 -19
- package/package.json +6 -3
- package/packages/src/core/__tests__/permissions.test.ts +1091 -0
- package/packages/src/core/agent-loop/__tests__/compaction.test.ts +283 -0
- package/packages/src/core/agent-loop/__tests__/formatters.test.ts +234 -0
- package/packages/src/core/agent-loop/__tests__/index.test.ts +162 -0
- package/packages/src/core/agent-loop/__tests__/loop-state.test.ts +413 -0
- package/packages/src/core/agent-loop/__tests__/message-builder.test.ts +229 -0
- package/packages/src/core/agent-loop/__tests__/tool-executor.test.ts +457 -0
- package/packages/src/core/agent-loop/compaction.ts +92 -0
- package/packages/src/core/agent-loop/formatters.ts +50 -0
- package/packages/src/core/agent-loop/index.ts +137 -0
- package/packages/src/core/agent-loop/loop-state.ts +187 -0
- package/packages/src/core/agent-loop/message-builder.ts +62 -0
- package/packages/src/core/agent-loop/tool-executor.ts +211 -0
- package/packages/src/core/agent-loop/turn-executor.ts +226 -0
- package/packages/src/core/agent-loop/types.ts +152 -0
- package/packages/src/core/agent-loop.ts +18 -0
- package/packages/src/core/api-client-impl.ts +729 -0
- package/packages/src/core/api-client.ts +6 -0
- package/packages/src/core/checkpoints.ts +606 -0
- package/packages/src/core/claude-md.ts +272 -0
- package/packages/src/core/cognitive-security/hooks.ts +591 -0
- package/packages/src/core/cognitive-security/index.ts +2041 -0
- package/packages/src/core/cognitive-security/middleware.ts +536 -0
- package/packages/src/core/config/todo +7 -0
- package/packages/src/core/config-loader.ts +324 -0
- package/packages/src/core/context/__tests__/integration.test.ts +334 -0
- package/packages/src/core/context/compaction.ts +170 -0
- package/packages/src/core/context/constants.ts +58 -0
- package/packages/src/core/context/extraction.ts +85 -0
- package/packages/src/core/context/index.ts +66 -0
- package/packages/src/core/context/summarization.ts +251 -0
- package/packages/src/core/context/token-estimation.ts +98 -0
- package/packages/src/core/context/types.ts +59 -0
- package/packages/src/core/git-status.ts +262 -0
- package/packages/src/core/image.test.ts +180 -0
- package/packages/src/core/image.ts +350 -0
- package/packages/src/core/lmdb.db +0 -0
- package/packages/src/core/lmdb.db-lock +0 -0
- package/packages/src/core/models.ts +507 -0
- package/packages/src/core/normalizers/todo +8 -0
- package/packages/src/core/permissions.ts +431 -0
- package/packages/src/core/providers/README.md +230 -0
- package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
- package/packages/src/core/providers/index.ts +419 -0
- package/packages/src/core/providers/types.ts +132 -0
- package/packages/src/core/retry.ts +180 -0
- package/packages/src/core/session-store.ts +36 -0
- package/packages/src/core/sessions/export.ts +329 -0
- package/packages/src/core/sessions/index.ts +587 -0
- package/packages/src/core/sessions/metadata.ts +309 -0
- package/packages/src/core/sessions/persistence.ts +244 -0
- package/packages/src/core/sessions/types.ts +169 -0
- package/packages/src/core/stream-highlighter.ts +1123 -0
- package/packages/src/core/system-reminders.ts +402 -0
- package/packages/src/core/todo +8 -0
- package/packages/src/ecosystem/hooks/__tests__/index.test.ts +561 -0
- package/packages/src/ecosystem/hooks/index.ts +341 -0
- package/packages/src/ecosystem/hooks/prompt-evaluator.ts +300 -0
- package/packages/src/ecosystem/skills/index.ts +295 -0
- package/packages/src/ecosystem/tools/__tests__/index.test.ts +1335 -0
- package/packages/src/ecosystem/tools/index.ts +2051 -0
- package/packages/src/index.ts +141 -0
- package/packages/src/interfaces/mcp/client.ts +389 -0
- package/packages/src/interfaces/ui/index.ts +158 -0
- package/packages/src/interfaces/ui/lmdb.db +0 -0
- package/packages/src/interfaces/ui/lmdb.db-lock +0 -0
- package/packages/src/interfaces/ui/spinner.ts +451 -0
- package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
- package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
- package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
- package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
- package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
- package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
- package/packages/src/interfaces/ui/terminal/cli/index.ts +415 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +393 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
- package/packages/src/interfaces/ui/terminal/lmdb.db +0 -0
- package/packages/src/interfaces/ui/terminal/lmdb.db-lock +0 -0
- package/packages/src/interfaces/ui/terminal/shared/args.ts +222 -0
- package/packages/src/interfaces/ui/terminal/shared/index.ts +84 -0
- package/packages/src/interfaces/ui/terminal/shared/loading-state.ts +322 -0
- package/packages/src/interfaces/ui/terminal/shared/query.ts +152 -0
- package/packages/src/interfaces/ui/terminal/shared/setup.ts +299 -0
- package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
- package/packages/src/interfaces/ui/terminal/shared/status-line.ts +366 -0
- package/packages/src/interfaces/ui/terminal/shared/system-prompt.ts +146 -0
- package/packages/src/lmdb.db +0 -0
- package/packages/src/lmdb.db-lock +0 -0
- package/packages/src/native/index.ts +2722 -0
- package/packages/src/native/tui_v2_types.ts +39 -0
- package/packages/src/teammates/coordination.test.ts +279 -0
- package/packages/src/teammates/coordination.ts +646 -0
- package/packages/src/teammates/index.ts +1052 -0
- package/packages/src/teammates/integration.test.ts +272 -0
- package/packages/src/teammates/runner.test.ts +235 -0
- package/packages/src/teammates/runner.ts +750 -0
- package/packages/src/teammates/schemas.ts +673 -0
- package/packages/src/types/index.ts +723 -0
|
@@ -0,0 +1,2051 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ToolDefinition, ToolResult, ToolContext, ImageBlock } from "../../types/index.js";
|
|
6
|
+
import { glob as globAsync } from "glob";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { apply_multi_edits, validate_multi_edits, preview_multi_edits, type MultiEditEntry } from "../../native/index.js";
|
|
9
|
+
import {
|
|
10
|
+
isImageExtension,
|
|
11
|
+
isBinaryExclusion,
|
|
12
|
+
readImageFile,
|
|
13
|
+
toImageBlock,
|
|
14
|
+
formatImageResult,
|
|
15
|
+
} from "../../core/image.js";
|
|
16
|
+
import { MODEL_ALIASES, resolveModelAlias } from "../../core/models.js";
|
|
17
|
+
import * as path from "path";
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// READ TOOL
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
export const ReadTool: ToolDefinition = {
|
|
24
|
+
name: "Read",
|
|
25
|
+
description:
|
|
26
|
+
"Reads a file from the local filesystem. You can access any file directly by using this tool.\n\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid.\n\nThis tool allows Coder to read images (PNG, JPG, JPEG, GIF, WEBP) and PDF files.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, reads up to 2000 lines starting from the beginning of the file\n- You can optionally specify line offset and limit (especially handy for long files)\n- Any lines longer than 2000 characters will be truncated\n- Results are returned using cat -n format, with line numbers starting at 1\n\nThis tool can read images (PNG, JPG, JPEG, GIF, WEBP). When reading images, the tool displays them visually.\n\nFor PDF files:\n- Get the pages parameter to read specific page ranges (e.g., pages: \"1-5\")\n- Maximum 20 pages per request\n- For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges.\n\nTry to read the whole file by default, but for particularly large files, you should consider reading the file in chunks.\n\nIf you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.",
|
|
27
|
+
input_schema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
file_path: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "The absolute path to the file to read",
|
|
33
|
+
},
|
|
34
|
+
offset: {
|
|
35
|
+
type: "number",
|
|
36
|
+
description: "The line number to start reading from (1-based)",
|
|
37
|
+
},
|
|
38
|
+
limit: {
|
|
39
|
+
type: "number",
|
|
40
|
+
description: "The number of lines to read",
|
|
41
|
+
},
|
|
42
|
+
pages: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Page range for PDF files (e.g., \"1-5\")",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ["file_path"],
|
|
48
|
+
},
|
|
49
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
50
|
+
const filePath = args.file_path as string;
|
|
51
|
+
const offset = (args.offset as number) || 1;
|
|
52
|
+
const limit = (args.limit as number) || 2000;
|
|
53
|
+
|
|
54
|
+
// Validate required parameters
|
|
55
|
+
if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
|
|
56
|
+
return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Get file extension to check for image files
|
|
61
|
+
const ext = path.extname(filePath).toLowerCase().slice(1);
|
|
62
|
+
|
|
63
|
+
// Check if this is an image file (GI8 set in binary)
|
|
64
|
+
if (isImageExtension(ext)) {
|
|
65
|
+
return await handleImageRead(filePath, context.abortSignal);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check for binary exclusions (CI8 set in binary)
|
|
69
|
+
if (isBinaryExclusion(ext)) {
|
|
70
|
+
return {
|
|
71
|
+
content: `Binary file detected: ${filePath}\nThis file type (${ext}) is not supported for text reading.`,
|
|
72
|
+
is_error: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default text file reading
|
|
77
|
+
const file = Bun.file(filePath);
|
|
78
|
+
|
|
79
|
+
// Check if file exists
|
|
80
|
+
if (!(await file.exists())) {
|
|
81
|
+
return { content: `Error: File not found: ${filePath}`, is_error: true };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const text = await file.text();
|
|
85
|
+
const lines = text.split("\n");
|
|
86
|
+
|
|
87
|
+
// Apply offset and limit (1-based offset)
|
|
88
|
+
const startLine = Math.max(0, offset - 1);
|
|
89
|
+
const endLine = Math.min(lines.length, startLine + limit);
|
|
90
|
+
const selectedLines = lines.slice(startLine, endLine);
|
|
91
|
+
|
|
92
|
+
// Check for truncation
|
|
93
|
+
const wasTruncated = endLine < lines.length;
|
|
94
|
+
|
|
95
|
+
// Format with line numbers
|
|
96
|
+
const formatted = selectedLines
|
|
97
|
+
.map((line, i) => `${startLine + i + 1}\t${line}`)
|
|
98
|
+
.join("\n");
|
|
99
|
+
|
|
100
|
+
// Add truncation notice if applicable
|
|
101
|
+
let result = formatted;
|
|
102
|
+
if (wasTruncated) {
|
|
103
|
+
result += `\n\n> WARNING: ${filePath} is ${lines.length} lines (limit: ${limit}). Only the first ${limit} lines were loaded.`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { content: result };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
109
|
+
return { content: `Error reading file: ${errorMessage}`, is_error: true };
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Handle reading image files (bwA function in binary)
|
|
116
|
+
*/
|
|
117
|
+
async function handleImageRead(filePath: string, signal?: AbortSignal): Promise<ToolResult> {
|
|
118
|
+
try {
|
|
119
|
+
const result = await readImageFile(filePath, 25000, signal);
|
|
120
|
+
const imageBlock = toImageBlock(result);
|
|
121
|
+
|
|
122
|
+
// Return as ContentBlock array for proper image handling
|
|
123
|
+
// TODO: Return image block when using native Anthropic API (proxies like Z.AI don't support images in tool_result)
|
|
124
|
+
// For now, return text description only
|
|
125
|
+
// When proxy support is detected, return: [{ type: "text", text: formatImageResult(result) }, imageBlock]
|
|
126
|
+
return {
|
|
127
|
+
content: `${formatImageResult(result)}
|
|
128
|
+
|
|
129
|
+
Note: Image content was read but cannot be displayed through this API proxy. When using native Anthropic API, the image would be included for visual analysis.`,
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
133
|
+
return { content: `Error reading image: ${errorMessage}`, is_error: true };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================
|
|
138
|
+
// WRITE TOOL
|
|
139
|
+
// ============================================
|
|
140
|
+
|
|
141
|
+
export const WriteTool: ToolDefinition = {
|
|
142
|
+
name: "Write",
|
|
143
|
+
description:
|
|
144
|
+
"Writes a file to the local filesystem. This tool will overwrite the existing file if there is one at the provided path.",
|
|
145
|
+
input_schema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
file_path: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description: "The absolute path to the file to write",
|
|
151
|
+
},
|
|
152
|
+
content: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "The content to write to the file",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
required: ["file_path", "content"],
|
|
158
|
+
},
|
|
159
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
160
|
+
const filePath = args.file_path as string;
|
|
161
|
+
const content = args.content as string;
|
|
162
|
+
|
|
163
|
+
// Validate required parameters
|
|
164
|
+
if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
|
|
165
|
+
return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
|
|
166
|
+
}
|
|
167
|
+
if (content === undefined || content === null) {
|
|
168
|
+
return { content: "Error: content parameter is required", is_error: true };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await Bun.write(filePath, content);
|
|
173
|
+
return { content: `Successfully wrote to ${filePath}` };
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
176
|
+
return { content: `Error writing file: ${errorMessage}`, is_error: true };
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// ============================================
|
|
182
|
+
// EDIT TOOL
|
|
183
|
+
// ============================================
|
|
184
|
+
|
|
185
|
+
export const EditTool: ToolDefinition = {
|
|
186
|
+
name: "Edit",
|
|
187
|
+
description:
|
|
188
|
+
"Performs exact string replacements in files. Use this tool to modify existing files.",
|
|
189
|
+
input_schema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
file_path: {
|
|
193
|
+
type: "string",
|
|
194
|
+
description: "The absolute path to the file to modify",
|
|
195
|
+
},
|
|
196
|
+
old_string: {
|
|
197
|
+
type: "string",
|
|
198
|
+
description: "The text to replace",
|
|
199
|
+
},
|
|
200
|
+
new_string: {
|
|
201
|
+
type: "string",
|
|
202
|
+
description: "The text to replace it with",
|
|
203
|
+
},
|
|
204
|
+
replace_all: {
|
|
205
|
+
type: "boolean",
|
|
206
|
+
description: "Replace all occurrences (default false)",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
required: ["file_path", "old_string", "new_string"],
|
|
210
|
+
},
|
|
211
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
212
|
+
const filePath = args.file_path as string;
|
|
213
|
+
const oldString = args.old_string as string;
|
|
214
|
+
const newString = args.new_string as string;
|
|
215
|
+
const replaceAll = (args.replace_all as boolean) || false;
|
|
216
|
+
|
|
217
|
+
// Validate required parameters
|
|
218
|
+
if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
|
|
219
|
+
return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
|
|
220
|
+
}
|
|
221
|
+
if (oldString === undefined || oldString === null) {
|
|
222
|
+
return { content: "Error: old_string parameter is required", is_error: true };
|
|
223
|
+
}
|
|
224
|
+
if (newString === undefined || newString === null) {
|
|
225
|
+
return { content: "Error: new_string parameter is required", is_error: true };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const file = Bun.file(filePath);
|
|
230
|
+
let content = await file.text();
|
|
231
|
+
|
|
232
|
+
if (replaceAll) {
|
|
233
|
+
const originalContent = content;
|
|
234
|
+
content = content.split(oldString).join(newString);
|
|
235
|
+
const count = (originalContent.match(new RegExp(escapeRegex(oldString), "g")) || []).length;
|
|
236
|
+
if (count === 0) {
|
|
237
|
+
return { content: `Error: String not found in file`, is_error: true };
|
|
238
|
+
}
|
|
239
|
+
await Bun.write(filePath, content);
|
|
240
|
+
return { content: `Successfully replaced ${count} occurrences` };
|
|
241
|
+
} else {
|
|
242
|
+
const index = content.indexOf(oldString);
|
|
243
|
+
if (index === -1) {
|
|
244
|
+
return { content: `Error: String not found in file`, is_error: true };
|
|
245
|
+
}
|
|
246
|
+
// Check for uniqueness
|
|
247
|
+
const secondIndex = content.indexOf(oldString, index + 1);
|
|
248
|
+
if (secondIndex !== -1) {
|
|
249
|
+
return {
|
|
250
|
+
content: `Error: String appears multiple times in file. Use replace_all or provide more context.`,
|
|
251
|
+
is_error: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
content = content.replace(oldString, newString);
|
|
255
|
+
await Bun.write(filePath, content);
|
|
256
|
+
return { content: `Successfully edited ${filePath}` };
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
260
|
+
return { content: `Error editing file: ${errorMessage}`, is_error: true };
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
function escapeRegex(string: string): string {
|
|
266
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================
|
|
270
|
+
// BASH TOOL
|
|
271
|
+
// ============================================
|
|
272
|
+
|
|
273
|
+
export const BashTool: ToolDefinition = {
|
|
274
|
+
name: "Bash",
|
|
275
|
+
description:
|
|
276
|
+
"Executes a given bash command with optional timeout. Working directory persists between commands.",
|
|
277
|
+
input_schema: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
command: {
|
|
281
|
+
type: "string",
|
|
282
|
+
description: "The command to execute",
|
|
283
|
+
},
|
|
284
|
+
timeout: {
|
|
285
|
+
type: "number",
|
|
286
|
+
description: "Optional timeout in milliseconds (max 600000)",
|
|
287
|
+
},
|
|
288
|
+
description: {
|
|
289
|
+
type: "string",
|
|
290
|
+
description: "Clear, concise description of what this command does",
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
required: ["command"],
|
|
294
|
+
},
|
|
295
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
296
|
+
const command = args.command as string;
|
|
297
|
+
const timeout = (args.timeout as number) || 120000;
|
|
298
|
+
|
|
299
|
+
// Validate required parameters
|
|
300
|
+
if (!command || typeof command !== 'string' || command.trim() === '') {
|
|
301
|
+
return { content: "Error: command parameter is required and must be a non-empty string", is_error: true };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const result = Bun.spawnSync(["sh", "-c", command], {
|
|
306
|
+
cwd: context.workingDirectory,
|
|
307
|
+
timeout,
|
|
308
|
+
maxBuffer: 1024 * 1024 * 30, // 30MB
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const stdout = result.stdout?.toString() || "";
|
|
312
|
+
const stderr = result.stderr?.toString() || "";
|
|
313
|
+
|
|
314
|
+
if (result.exitCode !== 0) {
|
|
315
|
+
return {
|
|
316
|
+
content: `Exit code: ${result.exitCode}\n${stdout}\n${stderr}`.trim(),
|
|
317
|
+
is_error: true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { content: stdout || "(no output)" };
|
|
322
|
+
} catch (error) {
|
|
323
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
324
|
+
return { content: `Error executing command: ${errorMessage}`, is_error: true };
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// ============================================
|
|
330
|
+
// GLOB TOOL
|
|
331
|
+
// ============================================
|
|
332
|
+
|
|
333
|
+
export const GlobTool: ToolDefinition = {
|
|
334
|
+
name: "Glob",
|
|
335
|
+
description:
|
|
336
|
+
"Fast file pattern matching tool that works with any codebase size. Supports glob patterns.",
|
|
337
|
+
input_schema: {
|
|
338
|
+
type: "object",
|
|
339
|
+
properties: {
|
|
340
|
+
pattern: {
|
|
341
|
+
type: "string",
|
|
342
|
+
description: "The glob pattern to match files",
|
|
343
|
+
},
|
|
344
|
+
path: {
|
|
345
|
+
type: "string",
|
|
346
|
+
description: "The directory to search (default: current directory)",
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
required: ["pattern"],
|
|
350
|
+
},
|
|
351
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
352
|
+
const pattern = args.pattern as string;
|
|
353
|
+
const searchPath = (args.path as string) || context.workingDirectory;
|
|
354
|
+
|
|
355
|
+
// Validate required parameters
|
|
356
|
+
if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
|
|
357
|
+
return { content: "Error: pattern parameter is required and must be a non-empty string", is_error: true };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const files = await globAsync(pattern, {
|
|
362
|
+
cwd: searchPath,
|
|
363
|
+
absolute: true,
|
|
364
|
+
nodir: true,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
if (files.length === 0) {
|
|
368
|
+
return { content: "No files found matching pattern" };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return { content: files.sort().join("\n") };
|
|
372
|
+
} catch (error) {
|
|
373
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
374
|
+
return { content: `Error searching files: ${errorMessage}`, is_error: true };
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// ============================================
|
|
380
|
+
// GREP TOOL
|
|
381
|
+
// ============================================
|
|
382
|
+
|
|
383
|
+
export const GrepTool: ToolDefinition = {
|
|
384
|
+
name: "Grep",
|
|
385
|
+
description:
|
|
386
|
+
"A powerful search tool built on ripgrep. Supports full regex syntax.",
|
|
387
|
+
input_schema: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
pattern: {
|
|
391
|
+
type: "string",
|
|
392
|
+
description: "The regular expression pattern to search for",
|
|
393
|
+
},
|
|
394
|
+
path: {
|
|
395
|
+
type: "string",
|
|
396
|
+
description: "File or directory to search",
|
|
397
|
+
},
|
|
398
|
+
glob: {
|
|
399
|
+
type: "string",
|
|
400
|
+
description: "Glob pattern to filter files",
|
|
401
|
+
},
|
|
402
|
+
output_mode: {
|
|
403
|
+
type: "string",
|
|
404
|
+
enum: ["content", "files_with_matches", "count"],
|
|
405
|
+
description: "Output mode (default: content)",
|
|
406
|
+
},
|
|
407
|
+
"-i:": {
|
|
408
|
+
type: "boolean",
|
|
409
|
+
description: "Case insensitive search",
|
|
410
|
+
},
|
|
411
|
+
"-C:": {
|
|
412
|
+
type: "number",
|
|
413
|
+
description: "Context lines around match",
|
|
414
|
+
},
|
|
415
|
+
head_limit: {
|
|
416
|
+
type: "number",
|
|
417
|
+
description: "Maximum number of results",
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
required: ["pattern"],
|
|
421
|
+
},
|
|
422
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
423
|
+
const pattern = args.pattern as string;
|
|
424
|
+
const searchPath = (args.path as string) || context.workingDirectory;
|
|
425
|
+
const glob = args.glob as string | undefined;
|
|
426
|
+
const outputMode = (args.output_mode as string) || "content";
|
|
427
|
+
const caseInsensitive = args["-i:"] as boolean;
|
|
428
|
+
const contextLines = args["-C:"] as number;
|
|
429
|
+
const headLimit = args.head_limit as number;
|
|
430
|
+
|
|
431
|
+
// Validate required parameters
|
|
432
|
+
if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
|
|
433
|
+
return { content: "Error: pattern parameter is required and must be a non-empty string", is_error: true };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
// Build ripgrep arguments
|
|
438
|
+
const rgArgs = ["--json"];
|
|
439
|
+
if (caseInsensitive) rgArgs.push("-i");
|
|
440
|
+
if (contextLines) rgArgs.push("-C", String(contextLines));
|
|
441
|
+
if (glob) rgArgs.push("--glob", glob);
|
|
442
|
+
if (outputMode === "files_with_matches") rgArgs.push("--files-with-matches");
|
|
443
|
+
if (outputMode === "count") rgArgs.push("--count");
|
|
444
|
+
|
|
445
|
+
rgArgs.push(pattern, searchPath);
|
|
446
|
+
|
|
447
|
+
const result = Bun.spawnSync(["rg", ...rgArgs], {
|
|
448
|
+
cwd: searchPath,
|
|
449
|
+
maxBuffer: 1024 * 1024 * 10, // 10MB
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const stdout = result.stdout?.toString() || "";
|
|
453
|
+
|
|
454
|
+
if (!stdout.trim()) {
|
|
455
|
+
return { content: "No matches found" };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Parse JSON output for content mode
|
|
459
|
+
if (outputMode === "content") {
|
|
460
|
+
const lines = stdout.trim().split("\n");
|
|
461
|
+
const matches: string[] = [];
|
|
462
|
+
|
|
463
|
+
for (const line of lines.slice(0, headLimit || 100)) {
|
|
464
|
+
try {
|
|
465
|
+
const parsed = JSON.parse(line);
|
|
466
|
+
if (parsed.type === "match") {
|
|
467
|
+
const filePath = parsed.data?.path?.text || "";
|
|
468
|
+
const lineNum = parsed.data?.line_number || 0;
|
|
469
|
+
const text = parsed.data?.lines?.text || "";
|
|
470
|
+
matches.push(`${filePath}:${lineNum}:${text.trim()}`);
|
|
471
|
+
}
|
|
472
|
+
} catch {
|
|
473
|
+
// Not JSON, use raw line
|
|
474
|
+
matches.push(line);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { content: matches.join("\n") || "No matches found" };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return { content: stdout.trim() };
|
|
482
|
+
} catch (error) {
|
|
483
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
484
|
+
return { content: `Error searching: ${errorMessage}`, is_error: true };
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// ============================================
|
|
490
|
+
// TASK TOOL (Subagents)
|
|
491
|
+
// ============================================
|
|
492
|
+
|
|
493
|
+
export const TaskTool: ToolDefinition = {
|
|
494
|
+
name: "Task",
|
|
495
|
+
description: `Launch a new agent to handle complex, multi-step tasks autonomously.
|
|
496
|
+
|
|
497
|
+
The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
|
|
498
|
+
|
|
499
|
+
Available agent types and their tools:
|
|
500
|
+
- Bash: Command execution specialist for running bash commands. Use for git operations, command execution, and other terminal tasks.
|
|
501
|
+
- general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks.
|
|
502
|
+
- Explore: Fast agent specialized for exploring codebases. Use to quickly find files by patterns, search code for keywords, or answer questions about the codebase.
|
|
503
|
+
- Plan: Software architect agent for designing implementation plans. Returns step-by-step plans, identifies critical files and considers architectural trade-offs.
|
|
504
|
+
|
|
505
|
+
When using the Task tool, you must specify a subagent_type parameter to select the agent type.
|
|
506
|
+
|
|
507
|
+
Usage notes:
|
|
508
|
+
- Always include a short description (3-5 words) summarizing what the agent will do
|
|
509
|
+
- Launch multiple agents concurrently whenever possible to maximize performance
|
|
510
|
+
- Agents can be resumed using the "resume" parameter by passing the agent ID from a previous invocation.`,
|
|
511
|
+
input_schema: {
|
|
512
|
+
type: "object",
|
|
513
|
+
properties: {
|
|
514
|
+
subagent_type: {
|
|
515
|
+
type: "string",
|
|
516
|
+
enum: ["Bash", "general-purpose", "Explore", "Plan"],
|
|
517
|
+
description: "The agent type to launch",
|
|
518
|
+
},
|
|
519
|
+
description: {
|
|
520
|
+
type: "string",
|
|
521
|
+
description: "A short (3-5 word) description of what the agent will do",
|
|
522
|
+
},
|
|
523
|
+
prompt: {
|
|
524
|
+
type: "string",
|
|
525
|
+
description: "The task for the agent to perform",
|
|
526
|
+
},
|
|
527
|
+
resume: {
|
|
528
|
+
type: "string",
|
|
529
|
+
description: "Resume a previous agent by its ID",
|
|
530
|
+
},
|
|
531
|
+
model: {
|
|
532
|
+
type: "string",
|
|
533
|
+
enum: ["sonnet", "opus", "haiku"],
|
|
534
|
+
description: "Model for the subagent (default: haiku for quick tasks)",
|
|
535
|
+
},
|
|
536
|
+
run_in_background: {
|
|
537
|
+
type: "boolean",
|
|
538
|
+
description: "Run the agent in the background",
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
required: ["subagent_type", "prompt"],
|
|
542
|
+
},
|
|
543
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
544
|
+
const subagentType = args.subagent_type as string;
|
|
545
|
+
const prompt = args.prompt as string;
|
|
546
|
+
const description = args.description as string | undefined;
|
|
547
|
+
const model = (args.model as string) || "haiku";
|
|
548
|
+
const resume = args.resume as string | undefined;
|
|
549
|
+
const runInBackground = args.run_in_background as boolean;
|
|
550
|
+
|
|
551
|
+
// Generate a unique agent ID
|
|
552
|
+
const agentId = resume || `${subagentType.toLowerCase()}-${Date.now().toString(36)}`;
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
// Get API key from environment (check multiple env var names)
|
|
556
|
+
const apiKey = process.env.ANTHROPIC_API_KEY ||
|
|
557
|
+
process.env.CLAUDE_API_KEY ||
|
|
558
|
+
process.env.ANTHROPIC_AUTH_TOKEN ||
|
|
559
|
+
process.env.Z_AI_API_KEY || "";
|
|
560
|
+
if (!apiKey) {
|
|
561
|
+
return { content: "Error: No API key available for subagent. Set ANTHROPIC_API_KEY, CLAUDE_API_KEY, ANTHROPIC_AUTH_TOKEN, or Z_AI_API_KEY environment variable.", is_error: true };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Map model names using centralized aliases
|
|
565
|
+
const fullModel = resolveModelAlias(model) || MODEL_ALIASES.haiku!;
|
|
566
|
+
|
|
567
|
+
// Find the CLI - check multiple locations
|
|
568
|
+
const possiblePaths = [
|
|
569
|
+
import.meta.dir + "/../../dist/cli.js", // Built CLI
|
|
570
|
+
import.meta.dir + "/../interfaces/ui/terminal/cli/index.ts", // Source CLI
|
|
571
|
+
process.cwd() + "/dist/cli.js", // Built CLI in cwd
|
|
572
|
+
process.cwd() + "/src/interfaces/ui/terminal/cli/index.ts", // Source CLI in cwd
|
|
573
|
+
];
|
|
574
|
+
|
|
575
|
+
let cliPath: string | null = null;
|
|
576
|
+
for (const path of possiblePaths) {
|
|
577
|
+
try {
|
|
578
|
+
const file = Bun.file(path);
|
|
579
|
+
if (await file.exists()) {
|
|
580
|
+
cliPath = path;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
} catch {
|
|
584
|
+
// Continue to next path
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!cliPath) {
|
|
589
|
+
return {
|
|
590
|
+
content: `Error: Could not find CLI. Tried:\n${possiblePaths.join("\n")}`,
|
|
591
|
+
is_error: true,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Build command arguments
|
|
596
|
+
const cmdArgs = [
|
|
597
|
+
"run",
|
|
598
|
+
cliPath,
|
|
599
|
+
"-m", fullModel,
|
|
600
|
+
"-p", context.permissionMode,
|
|
601
|
+
"-q", prompt,
|
|
602
|
+
];
|
|
603
|
+
|
|
604
|
+
if (runInBackground) {
|
|
605
|
+
// Spawn in background with proper env
|
|
606
|
+
const child = Bun.spawn(["bun", ...cmdArgs], {
|
|
607
|
+
cwd: context.workingDirectory,
|
|
608
|
+
detached: true,
|
|
609
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
610
|
+
env: {
|
|
611
|
+
...process.env,
|
|
612
|
+
ANTHROPIC_API_KEY: apiKey,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Don't wait for background tasks
|
|
617
|
+
child.unref();
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
content: JSON.stringify({
|
|
621
|
+
agentId,
|
|
622
|
+
status: "running",
|
|
623
|
+
message: `Agent started in background. Use TaskOutput tool with task_id: "${agentId}" to check results.`,
|
|
624
|
+
description: description || "Background task",
|
|
625
|
+
}),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Run synchronously with timeout
|
|
630
|
+
const result = Bun.spawnSync(["bun", ...cmdArgs], {
|
|
631
|
+
cwd: context.workingDirectory,
|
|
632
|
+
timeout: 300000, // 5 minutes max
|
|
633
|
+
maxBuffer: 1024 * 1024 * 10, // 10MB
|
|
634
|
+
env: {
|
|
635
|
+
...process.env,
|
|
636
|
+
ANTHROPIC_API_KEY: apiKey,
|
|
637
|
+
},
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
const stdout = result.stdout?.toString() || "";
|
|
641
|
+
const stderr = result.stderr?.toString() || "";
|
|
642
|
+
|
|
643
|
+
if (result.exitCode !== 0) {
|
|
644
|
+
return {
|
|
645
|
+
content: `Agent failed with exit code ${result.exitCode}\n${stderr}\n${stdout}`.trim(),
|
|
646
|
+
is_error: true,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
content: JSON.stringify({
|
|
652
|
+
agentId,
|
|
653
|
+
status: "completed",
|
|
654
|
+
output: stdout,
|
|
655
|
+
description: description || "Task completed",
|
|
656
|
+
}),
|
|
657
|
+
};
|
|
658
|
+
} catch (error) {
|
|
659
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
660
|
+
return { content: `Error running subagent: ${errorMessage}`, is_error: true };
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// ============================================
|
|
666
|
+
// TASK OUTPUT TOOL
|
|
667
|
+
// ============================================
|
|
668
|
+
|
|
669
|
+
export const TaskOutputTool: ToolDefinition = {
|
|
670
|
+
name: "TaskOutput",
|
|
671
|
+
description: `Retrieves output from a running or completed task (background shell, agent, or remote session).
|
|
672
|
+
|
|
673
|
+
Takes a task_id parameter identifying the task.
|
|
674
|
+
Returns the task output along with status information.
|
|
675
|
+
Use block=true (default) to wait for task completion.
|
|
676
|
+
Use block=false for non-blocking check of current status.
|
|
677
|
+
|
|
678
|
+
Task IDs can be found using the /tasks command
|
|
679
|
+
Works with all task types: background shells, async agents, and remote sessions`,
|
|
680
|
+
input_schema: {
|
|
681
|
+
type: "object",
|
|
682
|
+
properties: {
|
|
683
|
+
task_id: {
|
|
684
|
+
type: "string",
|
|
685
|
+
description: "The task ID to get output from",
|
|
686
|
+
},
|
|
687
|
+
block: {
|
|
688
|
+
type: "boolean",
|
|
689
|
+
description: "Whether to wait for completion (default: true)",
|
|
690
|
+
default: true,
|
|
691
|
+
},
|
|
692
|
+
timeout: {
|
|
693
|
+
type: "number",
|
|
694
|
+
description: "Max wait time in ms (default: 30000, max: 600000)",
|
|
695
|
+
default: 30000,
|
|
696
|
+
minimum: 0,
|
|
697
|
+
maximum: 600000,
|
|
698
|
+
},
|
|
699
|
+
},
|
|
700
|
+
required: ["task_id"],
|
|
701
|
+
},
|
|
702
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
703
|
+
const taskId = args.task_id as string;
|
|
704
|
+
const block = (args.block as boolean) ?? true;
|
|
705
|
+
const timeout = (args.timeout as number) ?? 30000;
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
// In a real implementation, this would check a task registry
|
|
709
|
+
// For now, we'll check for background task output files
|
|
710
|
+
const taskFile = `${context.workingDirectory}/.claude/tasks/${taskId}.json`;
|
|
711
|
+
|
|
712
|
+
const file = Bun.file(taskFile);
|
|
713
|
+
if (!(await file.exists())) {
|
|
714
|
+
return {
|
|
715
|
+
content: `Task not found: ${taskId}. Use /tasks to list available tasks.`,
|
|
716
|
+
is_error: true,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const taskData = await file.json() as {
|
|
721
|
+
status: string;
|
|
722
|
+
output?: string;
|
|
723
|
+
error?: string;
|
|
724
|
+
startTime: number;
|
|
725
|
+
endTime?: number;
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
if (block && taskData.status === "running") {
|
|
729
|
+
// Wait for task completion with timeout
|
|
730
|
+
const startTime = Date.now();
|
|
731
|
+
while (Date.now() - startTime < timeout) {
|
|
732
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
733
|
+
const updatedFile = Bun.file(taskFile);
|
|
734
|
+
if (await updatedFile.exists()) {
|
|
735
|
+
const updatedData = await updatedFile.json() as typeof taskData;
|
|
736
|
+
if (updatedData.status !== "running") {
|
|
737
|
+
return {
|
|
738
|
+
content: JSON.stringify({
|
|
739
|
+
task_id: taskId,
|
|
740
|
+
status: updatedData.status,
|
|
741
|
+
output: updatedData.output,
|
|
742
|
+
error: updatedData.error,
|
|
743
|
+
duration: updatedData.endTime
|
|
744
|
+
? updatedData.endTime - updatedData.startTime
|
|
745
|
+
: null,
|
|
746
|
+
}, null, 2),
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
content: JSON.stringify({
|
|
753
|
+
task_id: taskId,
|
|
754
|
+
status: "timeout",
|
|
755
|
+
message: `Task still running after ${timeout}ms`,
|
|
756
|
+
}, null, 2),
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return {
|
|
761
|
+
content: JSON.stringify({
|
|
762
|
+
task_id: taskId,
|
|
763
|
+
status: taskData.status,
|
|
764
|
+
output: taskData.output,
|
|
765
|
+
error: taskData.error,
|
|
766
|
+
duration: taskData.endTime
|
|
767
|
+
? taskData.endTime - taskData.startTime
|
|
768
|
+
: null,
|
|
769
|
+
}, null, 2),
|
|
770
|
+
};
|
|
771
|
+
} catch (error) {
|
|
772
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
773
|
+
return { content: `Error getting task output: ${errorMessage}`, is_error: true };
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
// ============================================
|
|
779
|
+
// ASK USER QUESTION TOOL
|
|
780
|
+
// ============================================
|
|
781
|
+
|
|
782
|
+
export const AskUserQuestionTool: ToolDefinition = {
|
|
783
|
+
name: "AskUserQuestion",
|
|
784
|
+
description: `Use this tool when you need to ask the user questions during execution.
|
|
785
|
+
|
|
786
|
+
This allows you to:
|
|
787
|
+
1. Gather user preferences or requirements
|
|
788
|
+
2. Clarify ambiguous instructions
|
|
789
|
+
3. Get decisions on implementation choices
|
|
790
|
+
4. Offer choices to the user about what direction to take
|
|
791
|
+
|
|
792
|
+
Plan mode note: In plan mode, use this tool to clarify requirements or choose between approaches BEFORE finalizing your plan. Do NOT use this tool if your plan is ready - that's what ExitPlanMode is for.
|
|
793
|
+
|
|
794
|
+
The options array should have 2-4 options. Each option should be a distinct, mutually exclusive choice.
|
|
795
|
+
The preview feature allows showing markdown content in a side-by-side layout.
|
|
796
|
+
|
|
797
|
+
User can always select "Other" to provide custom text input.`,
|
|
798
|
+
input_schema: {
|
|
799
|
+
type: "object",
|
|
800
|
+
properties: {
|
|
801
|
+
questions: {
|
|
802
|
+
type: "array",
|
|
803
|
+
description: "Questions to ask the user (1-4 questions)",
|
|
804
|
+
items: {
|
|
805
|
+
type: "object",
|
|
806
|
+
properties: {
|
|
807
|
+
question: {
|
|
808
|
+
type: "string",
|
|
809
|
+
description: "The complete question to ask the user",
|
|
810
|
+
},
|
|
811
|
+
header: {
|
|
812
|
+
type: "string",
|
|
813
|
+
description: "Very short label displayed as a chip/tag (max 12 chars)",
|
|
814
|
+
},
|
|
815
|
+
options: {
|
|
816
|
+
type: "array",
|
|
817
|
+
description: "The available choices (2-4 options)",
|
|
818
|
+
items: {
|
|
819
|
+
type: "object",
|
|
820
|
+
properties: {
|
|
821
|
+
label: {
|
|
822
|
+
type: "string",
|
|
823
|
+
description: "The display text for this option (5 words max)",
|
|
824
|
+
},
|
|
825
|
+
description: {
|
|
826
|
+
type: "string",
|
|
827
|
+
description: "Explanation of what this option means",
|
|
828
|
+
},
|
|
829
|
+
markdown: {
|
|
830
|
+
type: "string",
|
|
831
|
+
description: "Optional preview content shown in a monospace box",
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
required: ["label", "description"],
|
|
835
|
+
},
|
|
836
|
+
minItems: 2,
|
|
837
|
+
maxItems: 4,
|
|
838
|
+
},
|
|
839
|
+
multiSelect: {
|
|
840
|
+
type: "boolean",
|
|
841
|
+
description: "Allow selecting multiple options (default: false)",
|
|
842
|
+
default: false,
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
required: ["question", "header", "options"],
|
|
846
|
+
},
|
|
847
|
+
minItems: 1,
|
|
848
|
+
maxItems: 4,
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
required: ["questions"],
|
|
852
|
+
},
|
|
853
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
854
|
+
const questions = args.questions as Array<{
|
|
855
|
+
question: string;
|
|
856
|
+
header: string;
|
|
857
|
+
options: Array<{ label: string; description: string; markdown?: string }>;
|
|
858
|
+
multiSelect?: boolean;
|
|
859
|
+
}>;
|
|
860
|
+
|
|
861
|
+
try {
|
|
862
|
+
// Format questions for display
|
|
863
|
+
const formattedQuestions = questions.map((q, i) => {
|
|
864
|
+
const options = q.options
|
|
865
|
+
.map((opt, j) => {
|
|
866
|
+
let optionText = ` ${j + 1}. ${opt.label}`;
|
|
867
|
+
if (opt.description) {
|
|
868
|
+
optionText += ` - ${opt.description}`;
|
|
869
|
+
}
|
|
870
|
+
return optionText;
|
|
871
|
+
})
|
|
872
|
+
.join("\n");
|
|
873
|
+
|
|
874
|
+
return `## Question ${i + 1}: [${q.header}]\n${q.question}\n\nOptions:\n${options}${q.multiSelect ? "\n(multi-select enabled)" : ""}`;
|
|
875
|
+
}).join("\n\n---\n\n");
|
|
876
|
+
|
|
877
|
+
// In interactive mode, this would prompt the user
|
|
878
|
+
// For now, return the formatted questions
|
|
879
|
+
return {
|
|
880
|
+
content: JSON.stringify({
|
|
881
|
+
type: "user_question",
|
|
882
|
+
questions: questions,
|
|
883
|
+
formatted: formattedQuestions,
|
|
884
|
+
message: "Questions prepared for user response",
|
|
885
|
+
}, null, 2),
|
|
886
|
+
};
|
|
887
|
+
} catch (error) {
|
|
888
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
889
|
+
return { content: `Error preparing questions: ${errorMessage}`, is_error: true };
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// ============================================
|
|
895
|
+
// ENTER PLAN MODE TOOL
|
|
896
|
+
// ============================================
|
|
897
|
+
|
|
898
|
+
export const EnterPlanModeTool: ToolDefinition = {
|
|
899
|
+
name: "EnterPlanMode",
|
|
900
|
+
description: `Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.
|
|
901
|
+
|
|
902
|
+
How This Tool Works:
|
|
903
|
+
- You should have already written your plan to the plan file specified in the plan mode system message
|
|
904
|
+
- This tool does NOT take the plan content as a parameter - it will read the plan from the file you wrote
|
|
905
|
+
- This tool simply signals that you're done planning and ready for the user to review and approve
|
|
906
|
+
|
|
907
|
+
When to Use This Tool:
|
|
908
|
+
IMPORTANT: Only use this tool when the task requires planning the implementation steps of a task that requires writing code. For research tasks where you're gathering information, searching files, reading files or in general trying to understand the codebase - do NOT use this tool.
|
|
909
|
+
|
|
910
|
+
Plan mode note: In plan mode, use this tool to clarify requirements or choose between approaches BEFORE finalizing your plan. Do NOT use this tool if your plan is ready - that's what ExitPlanMode is for.
|
|
911
|
+
|
|
912
|
+
Examples:
|
|
913
|
+
- "Search for and understand the implementation of vim mode" - Do NOT use this tool
|
|
914
|
+
- "Help me implement yank mode for vim" - Use EnterPlanMode
|
|
915
|
+
|
|
916
|
+
Important notes:
|
|
917
|
+
- NEVER run additional commands to read or explore code, besides git bash commands
|
|
918
|
+
- NEVER use the TodoWrite or Task tools
|
|
919
|
+
- DO NOT commit files that likely contain secrets (.env, credentials.json, etc.)
|
|
920
|
+
- If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting`,
|
|
921
|
+
input_schema: {
|
|
922
|
+
type: "object",
|
|
923
|
+
properties: {
|
|
924
|
+
allowedPrompts: {
|
|
925
|
+
type: "array",
|
|
926
|
+
description: "Prompt-based permissions needed to implement the plan",
|
|
927
|
+
items: {
|
|
928
|
+
type: "object",
|
|
929
|
+
properties: {
|
|
930
|
+
tool: {
|
|
931
|
+
type: "string",
|
|
932
|
+
description: "The tool this prompt applies to (e.g., 'Bash')",
|
|
933
|
+
},
|
|
934
|
+
prompt: {
|
|
935
|
+
type: "string",
|
|
936
|
+
description: "Semantic description of the action (e.g., 'run tests', 'install dependencies')",
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
required: ["tool", "prompt"],
|
|
940
|
+
},
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
required: [],
|
|
944
|
+
},
|
|
945
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
946
|
+
const allowedPrompts = args.allowedPrompts as Array<{ tool: string; prompt: string }> | undefined;
|
|
947
|
+
|
|
948
|
+
try {
|
|
949
|
+
// Read the plan file
|
|
950
|
+
const planFile = `${context.workingDirectory}/.claude/plan.md`;
|
|
951
|
+
const file = Bun.file(planFile);
|
|
952
|
+
|
|
953
|
+
if (!(await file.exists())) {
|
|
954
|
+
return {
|
|
955
|
+
content: "Error: No plan file found. Please write your plan to .claude/plan.md first.",
|
|
956
|
+
is_error: true,
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const planContent = await file.text();
|
|
961
|
+
|
|
962
|
+
return {
|
|
963
|
+
content: JSON.stringify({
|
|
964
|
+
type: "plan_ready",
|
|
965
|
+
planFile: planFile,
|
|
966
|
+
planLength: planContent.length,
|
|
967
|
+
allowedPrompts: allowedPrompts || [],
|
|
968
|
+
message: "Plan is ready for user review. ExitPlanMode will request user approval.",
|
|
969
|
+
}, null, 2),
|
|
970
|
+
};
|
|
971
|
+
} catch (error) {
|
|
972
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
973
|
+
return { content: `Error entering plan mode: ${errorMessage}`, is_error: true };
|
|
974
|
+
}
|
|
975
|
+
},
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
// ============================================
|
|
979
|
+
// EXIT PLAN MODE TOOL
|
|
980
|
+
// ============================================
|
|
981
|
+
|
|
982
|
+
export const ExitPlanModeTool: ToolDefinition = {
|
|
983
|
+
name: "ExitPlanMode",
|
|
984
|
+
description: `Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.
|
|
985
|
+
|
|
986
|
+
This tool does NOT take the plan content as a parameter - it will read the plan from the file you wrote.
|
|
987
|
+
This tool simply signals that you're done planning and ready for the user to review and approve.
|
|
988
|
+
|
|
989
|
+
IMPORTANT: Only use this tool when the task requires planning the implementation steps of a task that requires writing code.
|
|
990
|
+
ExitPlanMode inherently requests user approval of the plan.`,
|
|
991
|
+
input_schema: {
|
|
992
|
+
type: "object",
|
|
993
|
+
properties: {
|
|
994
|
+
allowedPrompts: {
|
|
995
|
+
type: "array",
|
|
996
|
+
description: "Prompt-based permissions needed to implement the plan",
|
|
997
|
+
items: {
|
|
998
|
+
type: "object",
|
|
999
|
+
properties: {
|
|
1000
|
+
tool: {
|
|
1001
|
+
type: "string",
|
|
1002
|
+
description: "The tool this prompt applies to",
|
|
1003
|
+
},
|
|
1004
|
+
prompt: {
|
|
1005
|
+
type: "string",
|
|
1006
|
+
description: "Semantic description of the action",
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
required: ["tool", "prompt"],
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
},
|
|
1013
|
+
required: [],
|
|
1014
|
+
},
|
|
1015
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
1016
|
+
const allowedPrompts = args.allowedPrompts as Array<{ tool: string; prompt: string }> | undefined;
|
|
1017
|
+
|
|
1018
|
+
try {
|
|
1019
|
+
// Read the plan file
|
|
1020
|
+
const planFile = `${context.workingDirectory}/.claude/plan.md`;
|
|
1021
|
+
const file = Bun.file(planFile);
|
|
1022
|
+
|
|
1023
|
+
if (!(await file.exists())) {
|
|
1024
|
+
return {
|
|
1025
|
+
content: "Error: No plan file found at .claude/plan.md",
|
|
1026
|
+
is_error: true,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const planContent = await file.text();
|
|
1031
|
+
|
|
1032
|
+
return {
|
|
1033
|
+
content: JSON.stringify({
|
|
1034
|
+
type: "exit_plan_mode",
|
|
1035
|
+
status: "awaiting_approval",
|
|
1036
|
+
planFile: planFile,
|
|
1037
|
+
planPreview: planContent.slice(0, 500) + (planContent.length > 500 ? "..." : ""),
|
|
1038
|
+
allowedPrompts: allowedPrompts || [],
|
|
1039
|
+
message: "Plan submitted for user approval.",
|
|
1040
|
+
}, null, 2),
|
|
1041
|
+
};
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1044
|
+
return { content: `Error exiting plan mode: ${errorMessage}`, is_error: true };
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
// ============================================
|
|
1050
|
+
// SKILL TOOL
|
|
1051
|
+
// ============================================
|
|
1052
|
+
|
|
1053
|
+
export const SkillTool: ToolDefinition = {
|
|
1054
|
+
name: "Skill",
|
|
1055
|
+
description: `Execute a skill within the main conversation.
|
|
1056
|
+
|
|
1057
|
+
When users ask you to perform tasks, check if any of the available skills match. Skills provide specialized capabilities and domain knowledge.
|
|
1058
|
+
|
|
1059
|
+
When users reference a "slash command" or "/<something>" (e.g., "/commit", "/review-pr"), they are referring to a skill. Use this tool to invoke it.
|
|
1060
|
+
|
|
1061
|
+
How to invoke:
|
|
1062
|
+
- Use this tool with the skill name and optional arguments
|
|
1063
|
+
- Examples:
|
|
1064
|
+
- skill: "commit" - invoke the commit skill
|
|
1065
|
+
- skill: "review-pr", args: "123" - invoke with arguments
|
|
1066
|
+
- Use fully qualified name for namespaced skills: skill: "ms-office-suite:pdf"
|
|
1067
|
+
|
|
1068
|
+
Available skills are listed in system-reminder messages in the conversation.
|
|
1069
|
+
When a skill matches the user's request, this is a BLOCKING REQUIREMENT: invoke the relevant Skill tool BEFORE generating any other response about the task.
|
|
1070
|
+
|
|
1071
|
+
Important:
|
|
1072
|
+
- NEVER mention a skill without actually calling this tool
|
|
1073
|
+
- Do not invoke a skill that is already running
|
|
1074
|
+
- Do not use this tool for built-in CLI commands (like /help, /clear)`,
|
|
1075
|
+
input_schema: {
|
|
1076
|
+
type: "object",
|
|
1077
|
+
properties: {
|
|
1078
|
+
skill: {
|
|
1079
|
+
type: "string",
|
|
1080
|
+
description: "The skill name (e.g., 'commit', 'review-pr', or fully qualified 'namespace:skill')",
|
|
1081
|
+
},
|
|
1082
|
+
args: {
|
|
1083
|
+
type: "string",
|
|
1084
|
+
description: "Optional arguments for the skill",
|
|
1085
|
+
},
|
|
1086
|
+
},
|
|
1087
|
+
required: ["skill"],
|
|
1088
|
+
},
|
|
1089
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
1090
|
+
const skillName = args.skill as string;
|
|
1091
|
+
const skillArgs = args.args as string | undefined;
|
|
1092
|
+
|
|
1093
|
+
try {
|
|
1094
|
+
// Load available skills
|
|
1095
|
+
const skillsDir = `${context.workingDirectory}/.claude/skills`;
|
|
1096
|
+
const globalSkillsDir = `${process.env.HOME || ""}/.claude/skills`;
|
|
1097
|
+
|
|
1098
|
+
// Check for skill file in multiple locations
|
|
1099
|
+
const possiblePaths = [
|
|
1100
|
+
`${skillsDir}/${skillName}.md`,
|
|
1101
|
+
`${skillsDir}/${skillName}/skill.md`,
|
|
1102
|
+
`${globalSkillsDir}/${skillName}.md`,
|
|
1103
|
+
`${globalSkillsDir}/${skillName}/skill.md`,
|
|
1104
|
+
];
|
|
1105
|
+
|
|
1106
|
+
let skillFile: string | null = null;
|
|
1107
|
+
for (const path of possiblePaths) {
|
|
1108
|
+
const file = Bun.file(path);
|
|
1109
|
+
if (await file.exists()) {
|
|
1110
|
+
skillFile = path;
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (!skillFile) {
|
|
1116
|
+
return {
|
|
1117
|
+
content: `Skill not found: ${skillName}. Available skills can be listed with /help.`,
|
|
1118
|
+
is_error: true,
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Read and return the skill content
|
|
1123
|
+
const file = Bun.file(skillFile);
|
|
1124
|
+
const skillContent = await file.text();
|
|
1125
|
+
|
|
1126
|
+
return {
|
|
1127
|
+
content: JSON.stringify({
|
|
1128
|
+
type: "skill_invocation",
|
|
1129
|
+
skill: skillName,
|
|
1130
|
+
args: skillArgs,
|
|
1131
|
+
skillFile: skillFile,
|
|
1132
|
+
content: skillContent,
|
|
1133
|
+
message: `Skill "${skillName}" loaded. Follow the instructions in the skill content.`,
|
|
1134
|
+
}, null, 2),
|
|
1135
|
+
};
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1138
|
+
return { content: `Error invoking skill: ${errorMessage}`, is_error: true };
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
// ============================================
|
|
1144
|
+
// TASK STOP TOOL
|
|
1145
|
+
// ============================================
|
|
1146
|
+
|
|
1147
|
+
export const TaskStopTool: ToolDefinition = {
|
|
1148
|
+
name: "TaskStop",
|
|
1149
|
+
description: `Stops a running background task by its ID.
|
|
1150
|
+
Takes a task_id parameter identifying the task to stop.
|
|
1151
|
+
Returns a success or failure status.
|
|
1152
|
+
Use this tool to terminate a long-running task.`,
|
|
1153
|
+
input_schema: {
|
|
1154
|
+
type: "object",
|
|
1155
|
+
properties: {
|
|
1156
|
+
task_id: {
|
|
1157
|
+
type: "string",
|
|
1158
|
+
description: "The ID of the background task to stop",
|
|
1159
|
+
},
|
|
1160
|
+
shell_id: {
|
|
1161
|
+
type: "string",
|
|
1162
|
+
description: "Deprecated: use task_id instead",
|
|
1163
|
+
},
|
|
1164
|
+
},
|
|
1165
|
+
required: ["task_id"],
|
|
1166
|
+
},
|
|
1167
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
1168
|
+
const taskId = args.task_id as string;
|
|
1169
|
+
|
|
1170
|
+
try {
|
|
1171
|
+
const taskFile = `${context.workingDirectory}/.claude/tasks/${taskId}.json`;
|
|
1172
|
+
const file = Bun.file(taskFile);
|
|
1173
|
+
|
|
1174
|
+
if (!(await file.exists())) {
|
|
1175
|
+
return {
|
|
1176
|
+
content: `Task not found: ${taskId}`,
|
|
1177
|
+
is_error: true,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const taskData = await file.json() as {
|
|
1182
|
+
status: string;
|
|
1183
|
+
pid?: number;
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
if (taskData.status !== "running") {
|
|
1187
|
+
return {
|
|
1188
|
+
content: JSON.stringify({
|
|
1189
|
+
task_id: taskId,
|
|
1190
|
+
status: taskData.status,
|
|
1191
|
+
message: `Task is already ${taskData.status}`,
|
|
1192
|
+
}, null, 2),
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// In a real implementation, this would kill the process
|
|
1197
|
+
// For now, just update the status
|
|
1198
|
+
taskData.status = "stopped";
|
|
1199
|
+
await Bun.write(taskFile, JSON.stringify(taskData, null, 2));
|
|
1200
|
+
|
|
1201
|
+
return {
|
|
1202
|
+
content: JSON.stringify({
|
|
1203
|
+
task_id: taskId,
|
|
1204
|
+
status: "stopped",
|
|
1205
|
+
message: "Task stopped successfully",
|
|
1206
|
+
}, null, 2),
|
|
1207
|
+
};
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1210
|
+
return { content: `Error stopping task: ${errorMessage}`, is_error: true };
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
// ============================================
|
|
1216
|
+
// MULTI-EDIT TOOL (Atomic Multi-File Editing)
|
|
1217
|
+
// ============================================
|
|
1218
|
+
|
|
1219
|
+
export const MultiEditTool: ToolDefinition = {
|
|
1220
|
+
name: "MultiEdit",
|
|
1221
|
+
description: `Performs atomic multi-file editing with rollback on failure.
|
|
1222
|
+
|
|
1223
|
+
This tool allows you to edit multiple files simultaneously in a single atomic operation.
|
|
1224
|
+
If any edit fails, all changes are automatically rolled back to maintain consistency.
|
|
1225
|
+
|
|
1226
|
+
Key features:
|
|
1227
|
+
- Validates all edits before applying (files exist, strings found)
|
|
1228
|
+
- Creates automatic backups before editing
|
|
1229
|
+
- Applies all edits atomically (all-or-nothing)
|
|
1230
|
+
- Rolls back on any failure
|
|
1231
|
+
|
|
1232
|
+
Use this when you need to make coordinated changes across multiple files and want
|
|
1233
|
+
to ensure either all changes succeed or none are applied.
|
|
1234
|
+
|
|
1235
|
+
IMPORTANT: You MUST read the files first before using this tool. Only edit files you have already read.`,
|
|
1236
|
+
input_schema: {
|
|
1237
|
+
type: "object",
|
|
1238
|
+
properties: {
|
|
1239
|
+
edits: {
|
|
1240
|
+
type: "array",
|
|
1241
|
+
description: "Array of edit operations to apply atomically",
|
|
1242
|
+
items: {
|
|
1243
|
+
type: "object",
|
|
1244
|
+
properties: {
|
|
1245
|
+
file_path: {
|
|
1246
|
+
type: "string",
|
|
1247
|
+
description: "The absolute path to the file to edit",
|
|
1248
|
+
},
|
|
1249
|
+
old_string: {
|
|
1250
|
+
type: "string",
|
|
1251
|
+
description: "The text to find and replace",
|
|
1252
|
+
},
|
|
1253
|
+
new_string: {
|
|
1254
|
+
type: "string",
|
|
1255
|
+
description: "The text to replace it with",
|
|
1256
|
+
},
|
|
1257
|
+
replace_all: {
|
|
1258
|
+
type: "boolean",
|
|
1259
|
+
description: "Replace all occurrences (default: false)",
|
|
1260
|
+
},
|
|
1261
|
+
},
|
|
1262
|
+
required: ["file_path", "old_string", "new_string"],
|
|
1263
|
+
},
|
|
1264
|
+
},
|
|
1265
|
+
dry_run: {
|
|
1266
|
+
type: "boolean",
|
|
1267
|
+
description: "Preview changes without applying them (default: false)",
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1270
|
+
required: ["edits"],
|
|
1271
|
+
},
|
|
1272
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
1273
|
+
const rawEdits = args.edits as Array<{
|
|
1274
|
+
file_path?: string;
|
|
1275
|
+
old_string?: string;
|
|
1276
|
+
new_string?: string;
|
|
1277
|
+
replace_all?: boolean;
|
|
1278
|
+
}>;
|
|
1279
|
+
const dryRun = (args.dry_run as boolean) || false;
|
|
1280
|
+
|
|
1281
|
+
try {
|
|
1282
|
+
// Validate inputs
|
|
1283
|
+
if (!Array.isArray(rawEdits) || rawEdits.length === 0) {
|
|
1284
|
+
return { content: "Error: edits must be a non-empty array", is_error: true };
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Validate each edit has required fields and convert to native format
|
|
1288
|
+
const edits: MultiEditEntry[] = [];
|
|
1289
|
+
for (let i = 0; i < rawEdits.length; i++) {
|
|
1290
|
+
const rawEdit = rawEdits[i];
|
|
1291
|
+
if (!rawEdit || !rawEdit.file_path || !rawEdit.old_string || rawEdit.new_string === undefined) {
|
|
1292
|
+
return {
|
|
1293
|
+
content: `Error: Edit at index ${i} is missing required fields (file_path, old_string, new_string)`,
|
|
1294
|
+
is_error: true,
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
edits.push({
|
|
1298
|
+
filePath: rawEdit.file_path,
|
|
1299
|
+
oldString: rawEdit.old_string,
|
|
1300
|
+
newString: rawEdit.new_string,
|
|
1301
|
+
replaceAll: rawEdit.replace_all || false,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// If dry run, just preview
|
|
1306
|
+
if (dryRun) {
|
|
1307
|
+
const preview = preview_multi_edits(edits);
|
|
1308
|
+
const errors = validate_multi_edits(edits);
|
|
1309
|
+
|
|
1310
|
+
if (errors.length > 0) {
|
|
1311
|
+
return {
|
|
1312
|
+
content: JSON.stringify({
|
|
1313
|
+
valid: false,
|
|
1314
|
+
errors,
|
|
1315
|
+
preview: preview,
|
|
1316
|
+
}, null, 2),
|
|
1317
|
+
is_error: true,
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
return {
|
|
1322
|
+
content: JSON.stringify({
|
|
1323
|
+
valid: true,
|
|
1324
|
+
preview: preview,
|
|
1325
|
+
total_files: preview.length,
|
|
1326
|
+
total_replacements: preview.reduce((sum, p) => sum + p.replacementCount, 0),
|
|
1327
|
+
message: "Dry run successful - no changes applied",
|
|
1328
|
+
}, null, 2),
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// Validate all edits first
|
|
1333
|
+
const errors = validate_multi_edits(edits);
|
|
1334
|
+
if (errors.length > 0) {
|
|
1335
|
+
return {
|
|
1336
|
+
content: `Validation failed:\n${errors.join("\n")}`,
|
|
1337
|
+
is_error: true,
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// Apply edits atomically
|
|
1342
|
+
const result = apply_multi_edits(edits);
|
|
1343
|
+
|
|
1344
|
+
if (result.success) {
|
|
1345
|
+
return {
|
|
1346
|
+
content: JSON.stringify({
|
|
1347
|
+
success: true,
|
|
1348
|
+
files_modified: result.filesModified,
|
|
1349
|
+
total_replacements: result.totalReplacements,
|
|
1350
|
+
message: `Successfully applied ${result.totalReplacements} replacement(s) across ${result.filesModified.length} file(s)`,
|
|
1351
|
+
}, null, 2),
|
|
1352
|
+
};
|
|
1353
|
+
} else {
|
|
1354
|
+
return {
|
|
1355
|
+
content: JSON.stringify({
|
|
1356
|
+
success: false,
|
|
1357
|
+
error: result.error,
|
|
1358
|
+
rolled_back: result.rolledBack,
|
|
1359
|
+
files_modified: result.filesModified,
|
|
1360
|
+
}, null, 2),
|
|
1361
|
+
is_error: true,
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1366
|
+
return { content: `Error applying multi-edit: ${errorMessage}`, is_error: true };
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
// ============================================
|
|
1372
|
+
// NOTEBOOK EDIT TOOL
|
|
1373
|
+
// ============================================
|
|
1374
|
+
|
|
1375
|
+
export const NotebookEditTool: ToolDefinition = {
|
|
1376
|
+
name: "NotebookEdit",
|
|
1377
|
+
description: `Completely replaces the contents of a specific cell in a Jupyter notebook (.ipynb file) with new source.
|
|
1378
|
+
|
|
1379
|
+
Jupyter notebooks are interactive documents that combine code, text, and visualizations. The notebook_path parameter must be an absolute path, not a relative path. The cell_number is 0-indexed. Use edit_mode=insert to add a new cell at the index specified by cell_number. Use edit_mode=delete to delete the cell at the index specified by cell_number.`,
|
|
1380
|
+
input_schema: {
|
|
1381
|
+
type: "object",
|
|
1382
|
+
properties: {
|
|
1383
|
+
notebook_path: {
|
|
1384
|
+
type: "string",
|
|
1385
|
+
description: "The absolute path to the Jupyter notebook file to edit",
|
|
1386
|
+
},
|
|
1387
|
+
cell_id: {
|
|
1388
|
+
type: "string",
|
|
1389
|
+
description: "The ID of the cell to edit (optional, alternative to cell_number)",
|
|
1390
|
+
},
|
|
1391
|
+
cell_number: {
|
|
1392
|
+
type: "number",
|
|
1393
|
+
description: "The index of the cell to edit (0-indexed)",
|
|
1394
|
+
},
|
|
1395
|
+
new_source: {
|
|
1396
|
+
type: "string",
|
|
1397
|
+
description: "The new source for the cell",
|
|
1398
|
+
},
|
|
1399
|
+
cell_type: {
|
|
1400
|
+
type: "string",
|
|
1401
|
+
enum: ["code", "markdown"],
|
|
1402
|
+
description: "The type of the cell (code or markdown). Defaults to code.",
|
|
1403
|
+
},
|
|
1404
|
+
edit_mode: {
|
|
1405
|
+
type: "string",
|
|
1406
|
+
enum: ["replace", "insert", "delete"],
|
|
1407
|
+
description: "The type of edit to perform (replace, insert, delete)",
|
|
1408
|
+
},
|
|
1409
|
+
},
|
|
1410
|
+
required: ["notebook_path"],
|
|
1411
|
+
},
|
|
1412
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
1413
|
+
const notebookPath = args.notebook_path as string;
|
|
1414
|
+
const cellId = args.cell_id as string | undefined;
|
|
1415
|
+
const cellNumber = args.cell_number as number | undefined;
|
|
1416
|
+
const newSource = args.new_source as string | undefined;
|
|
1417
|
+
const cellType = (args.cell_type as string) || "code";
|
|
1418
|
+
const editMode = (args.edit_mode as string) || "replace";
|
|
1419
|
+
|
|
1420
|
+
try {
|
|
1421
|
+
// Read the notebook
|
|
1422
|
+
const file = Bun.file(notebookPath);
|
|
1423
|
+
if (!await file.exists()) {
|
|
1424
|
+
return { content: `Error: Notebook not found: ${notebookPath}`, is_error: true };
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
const notebook = await file.json() as {
|
|
1428
|
+
cells: Array<{
|
|
1429
|
+
id?: string;
|
|
1430
|
+
cell_type: string;
|
|
1431
|
+
source: string | string[];
|
|
1432
|
+
outputs?: unknown[];
|
|
1433
|
+
metadata?: Record<string, unknown>;
|
|
1434
|
+
execution_count?: number | null;
|
|
1435
|
+
}>;
|
|
1436
|
+
metadata: Record<string, unknown>;
|
|
1437
|
+
nbformat: number;
|
|
1438
|
+
nbformat_minor: number;
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
// Validate notebook structure
|
|
1442
|
+
if (!notebook.cells || !Array.isArray(notebook.cells)) {
|
|
1443
|
+
return { content: "Error: Invalid notebook format - no cells array", is_error: true };
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// Find the cell to edit
|
|
1447
|
+
let targetIndex: number;
|
|
1448
|
+
|
|
1449
|
+
if (cellId) {
|
|
1450
|
+
// Find by cell ID
|
|
1451
|
+
targetIndex = notebook.cells.findIndex(c => c.id === cellId);
|
|
1452
|
+
if (targetIndex === -1) {
|
|
1453
|
+
return { content: `Error: Cell with ID "${cellId}" not found`, is_error: true };
|
|
1454
|
+
}
|
|
1455
|
+
} else if (cellNumber !== undefined) {
|
|
1456
|
+
targetIndex = cellNumber;
|
|
1457
|
+
if (targetIndex < 0 || targetIndex >= notebook.cells.length) {
|
|
1458
|
+
if (editMode === "insert") {
|
|
1459
|
+
// Allow inserting at the end
|
|
1460
|
+
targetIndex = notebook.cells.length;
|
|
1461
|
+
} else {
|
|
1462
|
+
return { content: `Error: Cell number ${targetIndex} out of range (0-${notebook.cells.length - 1})`, is_error: true };
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
} else if (editMode !== "insert") {
|
|
1466
|
+
return { content: "Error: Must specify either cell_id or cell_number", is_error: true };
|
|
1467
|
+
} else {
|
|
1468
|
+
targetIndex = notebook.cells.length;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// Perform the edit
|
|
1472
|
+
switch (editMode) {
|
|
1473
|
+
case "delete": {
|
|
1474
|
+
notebook.cells.splice(targetIndex, 1);
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
case "insert": {
|
|
1479
|
+
const newCell = {
|
|
1480
|
+
id: `cell-${Date.now()}`,
|
|
1481
|
+
cell_type: cellType,
|
|
1482
|
+
source: newSource || "",
|
|
1483
|
+
metadata: {},
|
|
1484
|
+
...(cellType === "code" ? { outputs: [] as unknown[], execution_count: null } : {}),
|
|
1485
|
+
};
|
|
1486
|
+
notebook.cells.splice(targetIndex, 0, newCell);
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
case "replace":
|
|
1491
|
+
default: {
|
|
1492
|
+
if (newSource === undefined) {
|
|
1493
|
+
return { content: "Error: new_source is required for replace mode", is_error: true };
|
|
1494
|
+
}
|
|
1495
|
+
const existingCell = notebook.cells[targetIndex];
|
|
1496
|
+
if (!existingCell) {
|
|
1497
|
+
return { content: `Error: Cell at index ${targetIndex} not found`, is_error: true };
|
|
1498
|
+
}
|
|
1499
|
+
notebook.cells[targetIndex] = {
|
|
1500
|
+
...existingCell,
|
|
1501
|
+
source: newSource,
|
|
1502
|
+
cell_type: cellType,
|
|
1503
|
+
...(cellType === "code" ? { execution_count: null } : {}),
|
|
1504
|
+
};
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Write the notebook back
|
|
1510
|
+
await Bun.write(notebookPath, JSON.stringify(notebook, null, 1));
|
|
1511
|
+
|
|
1512
|
+
return {
|
|
1513
|
+
content: JSON.stringify({
|
|
1514
|
+
success: true,
|
|
1515
|
+
message: `Successfully ${editMode}d cell in ${notebookPath}`,
|
|
1516
|
+
cellCount: notebook.cells.length,
|
|
1517
|
+
}),
|
|
1518
|
+
};
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1521
|
+
return { content: `Error editing notebook: ${errorMessage}`, is_error: true };
|
|
1522
|
+
}
|
|
1523
|
+
},
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1526
|
+
// ============================================
|
|
1527
|
+
// ALL BUILT-IN TOOLS
|
|
1528
|
+
// ============================================
|
|
1529
|
+
|
|
1530
|
+
// ============================================
|
|
1531
|
+
// TEMPGlmVision TOOL (Simple Vision via GLM)
|
|
1532
|
+
// ============================================
|
|
1533
|
+
|
|
1534
|
+
export const TempGlmVisionTool: ToolDefinition = {
|
|
1535
|
+
name: "tempglmvision",
|
|
1536
|
+
description: `Analyze images using GLM-4.6V vision model. Use this tool when you need to analyze, describe, or extract information from images. Supports PNG, JPG, JPEG, GIF, and WEBP formats. Accepts both local file paths and remote URLs.`,
|
|
1537
|
+
input_schema: {
|
|
1538
|
+
type: "object",
|
|
1539
|
+
properties: {
|
|
1540
|
+
imageSource: {
|
|
1541
|
+
type: "string",
|
|
1542
|
+
description: "Local file path or remote URL to the image (supports PNG, JPG, JPEG, GIF, WEBP)",
|
|
1543
|
+
},
|
|
1544
|
+
prompt: {
|
|
1545
|
+
type: "string",
|
|
1546
|
+
description: "Detailed text prompt describing what to analyze, extract, or understand from the image.",
|
|
1547
|
+
},
|
|
1548
|
+
},
|
|
1549
|
+
required: ["imageSource", "prompt"],
|
|
1550
|
+
},
|
|
1551
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
1552
|
+
const imageSource = args.imageSource as string;
|
|
1553
|
+
const prompt = args.prompt as string;
|
|
1554
|
+
|
|
1555
|
+
try {
|
|
1556
|
+
// Get API credentials from environment
|
|
1557
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1558
|
+
if (!apiKey) {
|
|
1559
|
+
return {
|
|
1560
|
+
content: "Error: No API key found. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.",
|
|
1561
|
+
is_error: true,
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
|
|
1566
|
+
|
|
1567
|
+
// Read and encode the image
|
|
1568
|
+
let imageData: string;
|
|
1569
|
+
let mediaType: string;
|
|
1570
|
+
|
|
1571
|
+
if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
|
|
1572
|
+
// Fetch remote image
|
|
1573
|
+
const response = await fetch(imageSource);
|
|
1574
|
+
if (!response.ok) {
|
|
1575
|
+
return {
|
|
1576
|
+
content: `Error fetching image: ${response.status} ${response.statusText}`,
|
|
1577
|
+
is_error: true,
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1581
|
+
imageData = buffer.toString("base64");
|
|
1582
|
+
|
|
1583
|
+
// Detect media type from content-type header or extension
|
|
1584
|
+
const contentType = response.headers.get("content-type");
|
|
1585
|
+
if (contentType?.includes("image/png")) {
|
|
1586
|
+
mediaType = "image/png";
|
|
1587
|
+
} else if (contentType?.includes("image/gif")) {
|
|
1588
|
+
mediaType = "image/gif";
|
|
1589
|
+
} else if (contentType?.includes("image/webp")) {
|
|
1590
|
+
mediaType = "image/webp";
|
|
1591
|
+
} else {
|
|
1592
|
+
mediaType = "image/jpeg"; // Default to JPEG
|
|
1593
|
+
}
|
|
1594
|
+
} else {
|
|
1595
|
+
// Local file path
|
|
1596
|
+
const file = Bun.file(imageSource);
|
|
1597
|
+
if (!(await file.exists())) {
|
|
1598
|
+
return {
|
|
1599
|
+
content: `Error: Image file not found: ${imageSource}`,
|
|
1600
|
+
is_error: true,
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
1604
|
+
imageData = buffer.toString("base64");
|
|
1605
|
+
|
|
1606
|
+
// Detect media type from extension
|
|
1607
|
+
const ext = path.extname(imageSource).toLowerCase();
|
|
1608
|
+
if (ext === ".png") {
|
|
1609
|
+
mediaType = "image/png";
|
|
1610
|
+
} else if (ext === ".gif") {
|
|
1611
|
+
mediaType = "image/gif";
|
|
1612
|
+
} else if (ext === ".webp") {
|
|
1613
|
+
mediaType = "image/webp";
|
|
1614
|
+
} else {
|
|
1615
|
+
mediaType = "image/jpeg";
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Build request for vision model
|
|
1620
|
+
// Use GLM-5 for vision (supportsVision: true in models.ts)
|
|
1621
|
+
const request = {
|
|
1622
|
+
model: "glm-5",
|
|
1623
|
+
max_tokens: 4096,
|
|
1624
|
+
messages: [
|
|
1625
|
+
{
|
|
1626
|
+
role: "user",
|
|
1627
|
+
content: [
|
|
1628
|
+
{
|
|
1629
|
+
type: "image",
|
|
1630
|
+
source: {
|
|
1631
|
+
type: "base64",
|
|
1632
|
+
media_type: mediaType,
|
|
1633
|
+
data: imageData,
|
|
1634
|
+
},
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
type: "text",
|
|
1638
|
+
text: prompt,
|
|
1639
|
+
},
|
|
1640
|
+
],
|
|
1641
|
+
},
|
|
1642
|
+
],
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
// Make API request
|
|
1646
|
+
const response = await fetch(`${baseUrl}/v1/messages`, {
|
|
1647
|
+
method: "POST",
|
|
1648
|
+
headers: {
|
|
1649
|
+
"Content-Type": "application/json",
|
|
1650
|
+
"x-api-key": apiKey,
|
|
1651
|
+
"anthropic-version": "2023-06-01",
|
|
1652
|
+
},
|
|
1653
|
+
body: JSON.stringify(request),
|
|
1654
|
+
signal: context.abortSignal,
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
if (!response.ok) {
|
|
1658
|
+
const errorText = await response.text();
|
|
1659
|
+
return {
|
|
1660
|
+
content: `Vision API error: ${response.status} - ${errorText}`,
|
|
1661
|
+
is_error: true,
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
const result = (await response.json()) as {
|
|
1666
|
+
content?: Array<{ type: string; text?: string }>;
|
|
1667
|
+
error?: { message: string };
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
if (result.error) {
|
|
1671
|
+
return {
|
|
1672
|
+
content: `Vision API error: ${result.error.message}`,
|
|
1673
|
+
is_error: true,
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// Extract text from response
|
|
1678
|
+
const textContent = result.content
|
|
1679
|
+
?.filter((block) => block.type === "text")
|
|
1680
|
+
.map((block) => block.text)
|
|
1681
|
+
.join("\n");
|
|
1682
|
+
|
|
1683
|
+
return {
|
|
1684
|
+
content: textContent || "No text content in vision response",
|
|
1685
|
+
};
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1688
|
+
return { content: `Error analyzing image: ${errorMessage}`, is_error: true };
|
|
1689
|
+
}
|
|
1690
|
+
},
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
// ============================================
|
|
1694
|
+
// ANALYZE IMAGE TOOL (Vision via GLM)
|
|
1695
|
+
// ============================================
|
|
1696
|
+
|
|
1697
|
+
export const AnalyzeImageTool: ToolDefinition = {
|
|
1698
|
+
name: "mcp__4_5v_mcp__analyze_image",
|
|
1699
|
+
description: `Analyze an image using advanced AI vision models with comprehensive understanding capabilities. Supports PNG, JPG, JPEG, GIF, and WEBP formats. Accepts both local file paths and remote URLs.`,
|
|
1700
|
+
input_schema: {
|
|
1701
|
+
type: "object",
|
|
1702
|
+
properties: {
|
|
1703
|
+
imageSource: {
|
|
1704
|
+
type: "string",
|
|
1705
|
+
description: "Local file path or remote URL to the image (supports PNG, JPG, JPEG, GIF, WEBP)",
|
|
1706
|
+
},
|
|
1707
|
+
prompt: {
|
|
1708
|
+
type: "string",
|
|
1709
|
+
description: "Detailed text prompt describing what to analyze, extract, or understand from the image. For front-end code replication, describe layout structure, color style, main components, and interactive elements.",
|
|
1710
|
+
},
|
|
1711
|
+
},
|
|
1712
|
+
required: ["imageSource", "prompt"],
|
|
1713
|
+
},
|
|
1714
|
+
handler: async (args, context: ToolContext): Promise<ToolResult> => {
|
|
1715
|
+
const imageSource = args.imageSource as string;
|
|
1716
|
+
const prompt = args.prompt as string;
|
|
1717
|
+
|
|
1718
|
+
try {
|
|
1719
|
+
// Get API credentials from environment
|
|
1720
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1721
|
+
if (!apiKey) {
|
|
1722
|
+
return {
|
|
1723
|
+
content: "Error: No API key found. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.",
|
|
1724
|
+
is_error: true,
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
|
|
1729
|
+
|
|
1730
|
+
// Read and encode the image
|
|
1731
|
+
let imageData: string;
|
|
1732
|
+
let mediaType: string;
|
|
1733
|
+
|
|
1734
|
+
if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
|
|
1735
|
+
// Fetch remote image
|
|
1736
|
+
const response = await fetch(imageSource);
|
|
1737
|
+
if (!response.ok) {
|
|
1738
|
+
return {
|
|
1739
|
+
content: `Error fetching image: ${response.status} ${response.statusText}`,
|
|
1740
|
+
is_error: true,
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1744
|
+
imageData = buffer.toString("base64");
|
|
1745
|
+
|
|
1746
|
+
// Detect media type from content-type header or extension
|
|
1747
|
+
const contentType = response.headers.get("content-type");
|
|
1748
|
+
if (contentType?.includes("image/png")) {
|
|
1749
|
+
mediaType = "image/png";
|
|
1750
|
+
} else if (contentType?.includes("image/gif")) {
|
|
1751
|
+
mediaType = "image/gif";
|
|
1752
|
+
} else if (contentType?.includes("image/webp")) {
|
|
1753
|
+
mediaType = "image/webp";
|
|
1754
|
+
} else {
|
|
1755
|
+
mediaType = "image/jpeg"; // Default to JPEG
|
|
1756
|
+
}
|
|
1757
|
+
} else {
|
|
1758
|
+
// Local file path
|
|
1759
|
+
const file = Bun.file(imageSource);
|
|
1760
|
+
if (!(await file.exists())) {
|
|
1761
|
+
return {
|
|
1762
|
+
content: `Error: Image file not found: ${imageSource}`,
|
|
1763
|
+
is_error: true,
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
1767
|
+
imageData = buffer.toString("base64");
|
|
1768
|
+
|
|
1769
|
+
// Detect media type from extension
|
|
1770
|
+
const ext = path.extname(imageSource).toLowerCase();
|
|
1771
|
+
if (ext === ".png") {
|
|
1772
|
+
mediaType = "image/png";
|
|
1773
|
+
} else if (ext === ".gif") {
|
|
1774
|
+
mediaType = "image/gif";
|
|
1775
|
+
} else if (ext === ".webp") {
|
|
1776
|
+
mediaType = "image/webp";
|
|
1777
|
+
} else {
|
|
1778
|
+
mediaType = "image/jpeg";
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// Build request for vision model
|
|
1783
|
+
const request = {
|
|
1784
|
+
model: "glm-5", // Vision-capable model
|
|
1785
|
+
max_tokens: 4096,
|
|
1786
|
+
messages: [
|
|
1787
|
+
{
|
|
1788
|
+
role: "user",
|
|
1789
|
+
content: [
|
|
1790
|
+
{
|
|
1791
|
+
type: "image",
|
|
1792
|
+
source: {
|
|
1793
|
+
type: "base64",
|
|
1794
|
+
media_type: mediaType,
|
|
1795
|
+
data: imageData,
|
|
1796
|
+
},
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
type: "text",
|
|
1800
|
+
text: prompt,
|
|
1801
|
+
},
|
|
1802
|
+
],
|
|
1803
|
+
},
|
|
1804
|
+
],
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
// Make API request
|
|
1808
|
+
const response = await fetch(`${baseUrl}/v1/messages`, {
|
|
1809
|
+
method: "POST",
|
|
1810
|
+
headers: {
|
|
1811
|
+
"Content-Type": "application/json",
|
|
1812
|
+
"x-api-key": apiKey,
|
|
1813
|
+
"anthropic-version": "2023-06-01",
|
|
1814
|
+
},
|
|
1815
|
+
body: JSON.stringify(request),
|
|
1816
|
+
signal: context.abortSignal,
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
if (!response.ok) {
|
|
1820
|
+
const errorText = await response.text();
|
|
1821
|
+
return {
|
|
1822
|
+
content: `Vision API error: ${response.status} - ${errorText}`,
|
|
1823
|
+
is_error: true,
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
const result = (await response.json()) as {
|
|
1828
|
+
content?: Array<{ type: string; text?: string }>;
|
|
1829
|
+
error?: { message: string };
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
if (result.error) {
|
|
1833
|
+
return {
|
|
1834
|
+
content: `Vision API error: ${result.error.message}`,
|
|
1835
|
+
is_error: true,
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// Extract text from response
|
|
1840
|
+
const textContent = result.content
|
|
1841
|
+
?.filter((block) => block.type === "text")
|
|
1842
|
+
.map((block) => block.text)
|
|
1843
|
+
.join("\n");
|
|
1844
|
+
|
|
1845
|
+
return {
|
|
1846
|
+
content: textContent || "No text content in vision response",
|
|
1847
|
+
};
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1850
|
+
return { content: `Error analyzing image: ${errorMessage}`, is_error: true };
|
|
1851
|
+
}
|
|
1852
|
+
},
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1855
|
+
// ============================================
|
|
1856
|
+
// TEAMMATE COORDINATION TOOLS
|
|
1857
|
+
// ============================================
|
|
1858
|
+
|
|
1859
|
+
/**
|
|
1860
|
+
* Check for new messages from teammates
|
|
1861
|
+
*/
|
|
1862
|
+
const TeammateCheckMessagesTool: ToolDefinition = {
|
|
1863
|
+
name: "teammate_check_messages",
|
|
1864
|
+
description:
|
|
1865
|
+
"Check for new messages from other teammates in your team. Returns pending messages and clears the inbox. Use this periodically when in teammate mode to receive tasks and coordination messages.",
|
|
1866
|
+
input_schema: {
|
|
1867
|
+
type: "object",
|
|
1868
|
+
properties: {},
|
|
1869
|
+
},
|
|
1870
|
+
handler: async (_args, _context: ToolContext): Promise<ToolResult> => {
|
|
1871
|
+
const { getTeammateRunner } = await import("../../teammates/runner.js");
|
|
1872
|
+
const runner = getTeammateRunner();
|
|
1873
|
+
if (!runner) {
|
|
1874
|
+
return {
|
|
1875
|
+
content: "Teammate mode is not active. Start with --teammate-mode flag.",
|
|
1876
|
+
is_error: true,
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
const messages = runner.getPendingMessages();
|
|
1881
|
+
if (messages.length === 0) {
|
|
1882
|
+
return { content: "No pending messages." };
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
const formatted = messages
|
|
1886
|
+
.map((m) => `[${m.type}] From: ${m.from}\n${m.content}`)
|
|
1887
|
+
.join("\n\n---\n\n");
|
|
1888
|
+
|
|
1889
|
+
return { content: `Received ${messages.length} message(s):\n\n${formatted}` };
|
|
1890
|
+
},
|
|
1891
|
+
};
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Send a direct message to another teammate
|
|
1895
|
+
*/
|
|
1896
|
+
const TeammateSendMessageTool: ToolDefinition = {
|
|
1897
|
+
name: "teammate_send_message",
|
|
1898
|
+
description:
|
|
1899
|
+
"Send a direct message to a specific teammate or broadcast to all teammates. Use this for coordination, sharing results, or requesting help.",
|
|
1900
|
+
input_schema: {
|
|
1901
|
+
type: "object",
|
|
1902
|
+
properties: {
|
|
1903
|
+
to: {
|
|
1904
|
+
type: "string",
|
|
1905
|
+
description:
|
|
1906
|
+
"Teammate ID to send to, or 'broadcast' to send to all teammates",
|
|
1907
|
+
},
|
|
1908
|
+
message: {
|
|
1909
|
+
type: "string",
|
|
1910
|
+
description: "The message content to send",
|
|
1911
|
+
},
|
|
1912
|
+
},
|
|
1913
|
+
required: ["to", "message"],
|
|
1914
|
+
},
|
|
1915
|
+
handler: async (args, _context: ToolContext): Promise<ToolResult> => {
|
|
1916
|
+
const { getTeammateRunner } = await import("../../teammates/runner.js");
|
|
1917
|
+
const runner = getTeammateRunner();
|
|
1918
|
+
if (!runner) {
|
|
1919
|
+
return {
|
|
1920
|
+
content: "Teammate mode is not active. Start with --teammate-mode flag.",
|
|
1921
|
+
is_error: true,
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
const to = args.to as string;
|
|
1926
|
+
const message = args.message as string;
|
|
1927
|
+
|
|
1928
|
+
if (to === "broadcast") {
|
|
1929
|
+
runner.broadcast(message);
|
|
1930
|
+
return { content: `Broadcast message sent to team.` };
|
|
1931
|
+
} else {
|
|
1932
|
+
runner.sendDirectMessage(to, message);
|
|
1933
|
+
return { content: `Direct message sent to ${to}.` };
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
};
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* Report idle status to team
|
|
1940
|
+
*/
|
|
1941
|
+
const TeammateReportIdleTool: ToolDefinition = {
|
|
1942
|
+
name: "teammate_report_idle",
|
|
1943
|
+
description:
|
|
1944
|
+
"Report that you are now idle and ready for new tasks. Use this when you complete your current work and are available for assignment.",
|
|
1945
|
+
input_schema: {
|
|
1946
|
+
type: "object",
|
|
1947
|
+
properties: {
|
|
1948
|
+
message: {
|
|
1949
|
+
type: "string",
|
|
1950
|
+
description: "Optional status message",
|
|
1951
|
+
},
|
|
1952
|
+
},
|
|
1953
|
+
},
|
|
1954
|
+
handler: async (args, _context: ToolContext): Promise<ToolResult> => {
|
|
1955
|
+
const { getTeammateRunner } = await import("../../teammates/runner.js");
|
|
1956
|
+
const runner = getTeammateRunner();
|
|
1957
|
+
if (!runner) {
|
|
1958
|
+
return {
|
|
1959
|
+
content: "Teammate mode is not active. Start with --teammate-mode flag.",
|
|
1960
|
+
is_error: true,
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
const message = args.message as string | undefined;
|
|
1965
|
+
runner.updateStatus("idle");
|
|
1966
|
+
runner.broadcast(message || "Now idle and ready for tasks.");
|
|
1967
|
+
return { content: "Status updated to idle. Team notified." };
|
|
1968
|
+
},
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1971
|
+
/**
|
|
1972
|
+
* Get teammate status information
|
|
1973
|
+
*/
|
|
1974
|
+
const TeammateGetStatusTool: ToolDefinition = {
|
|
1975
|
+
name: "teammate_get_status",
|
|
1976
|
+
description:
|
|
1977
|
+
"Get current teammate mode status including your info, team members, and inbox stats. Use this to check your state and team coordination.",
|
|
1978
|
+
input_schema: {
|
|
1979
|
+
type: "object",
|
|
1980
|
+
properties: {},
|
|
1981
|
+
},
|
|
1982
|
+
handler: async (_args, _context: ToolContext): Promise<ToolResult> => {
|
|
1983
|
+
const { getTeammateRunner } = await import("../../teammates/runner.js");
|
|
1984
|
+
const runner = getTeammateRunner();
|
|
1985
|
+
if (!runner) {
|
|
1986
|
+
return {
|
|
1987
|
+
content: "Teammate mode is not active. Start with --teammate-mode flag.",
|
|
1988
|
+
is_error: true,
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
const teammate = runner.getTeammate();
|
|
1993
|
+
const team = runner.getTeam();
|
|
1994
|
+
const status = runner.getStatus();
|
|
1995
|
+
const inbox = runner.getInboxStats();
|
|
1996
|
+
|
|
1997
|
+
const info = {
|
|
1998
|
+
teammate: teammate
|
|
1999
|
+
? {
|
|
2000
|
+
id: teammate.teammateId,
|
|
2001
|
+
name: teammate.name,
|
|
2002
|
+
color: teammate.color,
|
|
2003
|
+
}
|
|
2004
|
+
: null,
|
|
2005
|
+
team: team
|
|
2006
|
+
? {
|
|
2007
|
+
name: team.name,
|
|
2008
|
+
memberCount: team.teammates.length,
|
|
2009
|
+
members: team.teammates.map((t) => ({
|
|
2010
|
+
id: t.teammateId,
|
|
2011
|
+
name: t.name,
|
|
2012
|
+
status: t.status,
|
|
2013
|
+
})),
|
|
2014
|
+
}
|
|
2015
|
+
: null,
|
|
2016
|
+
status,
|
|
2017
|
+
inbox,
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
return { content: JSON.stringify(info, null, 2) };
|
|
2021
|
+
},
|
|
2022
|
+
};
|
|
2023
|
+
|
|
2024
|
+
export const builtInTools: ToolDefinition[] = [
|
|
2025
|
+
ReadTool,
|
|
2026
|
+
WriteTool,
|
|
2027
|
+
EditTool,
|
|
2028
|
+
MultiEditTool,
|
|
2029
|
+
BashTool,
|
|
2030
|
+
GlobTool,
|
|
2031
|
+
GrepTool,
|
|
2032
|
+
TaskTool,
|
|
2033
|
+
TaskOutputTool,
|
|
2034
|
+
TaskStopTool,
|
|
2035
|
+
AskUserQuestionTool,
|
|
2036
|
+
EnterPlanModeTool,
|
|
2037
|
+
ExitPlanModeTool,
|
|
2038
|
+
SkillTool,
|
|
2039
|
+
NotebookEditTool,
|
|
2040
|
+
AnalyzeImageTool,
|
|
2041
|
+
TempGlmVisionTool,
|
|
2042
|
+
// Teammate coordination tools
|
|
2043
|
+
TeammateCheckMessagesTool,
|
|
2044
|
+
TeammateSendMessageTool,
|
|
2045
|
+
TeammateReportIdleTool,
|
|
2046
|
+
TeammateGetStatusTool,
|
|
2047
|
+
];
|
|
2048
|
+
|
|
2049
|
+
export function getToolByName(name: string): ToolDefinition | undefined {
|
|
2050
|
+
return builtInTools.find((t) => t.name === name);
|
|
2051
|
+
}
|