@edwardlee5423/magi 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/agent/headless-agent.d.ts +15 -0
- package/dist/agent/headless-agent.js +105 -0
- package/dist/agent/headless-agent.js.map +1 -0
- package/dist/agent/local-plan.d.ts +20 -0
- package/dist/agent/local-plan.js +30 -0
- package/dist/agent/local-plan.js.map +1 -0
- package/dist/agent/query-engine.d.ts +71 -0
- package/dist/agent/query-engine.js +938 -0
- package/dist/agent/query-engine.js.map +1 -0
- package/dist/agent/query.d.ts +128 -0
- package/dist/agent/query.js +674 -0
- package/dist/agent/query.js.map +1 -0
- package/dist/agent/system-prompt.d.ts +12 -0
- package/dist/agent/system-prompt.js +104 -0
- package/dist/agent/system-prompt.js.map +1 -0
- package/dist/agent/tools.d.ts +71 -0
- package/dist/agent/tools.js +37 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/agents/roles.d.ts +9 -0
- package/dist/agents/roles.js +18 -0
- package/dist/agents/roles.js.map +1 -0
- package/dist/agents/task-queue.d.ts +13 -0
- package/dist/agents/task-queue.js +50 -0
- package/dist/agents/task-queue.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +1540 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors.d.ts +20 -0
- package/dist/colors.js +92 -0
- package/dist/colors.js.map +1 -0
- package/dist/commands/agents.d.ts +9 -0
- package/dist/commands/agents.js +37 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/clear.d.ts +9 -0
- package/dist/commands/clear.js +20 -0
- package/dist/commands/clear.js.map +1 -0
- package/dist/commands/commit.d.ts +8 -0
- package/dist/commands/commit.js +35 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/compact.d.ts +8 -0
- package/dist/commands/compact.js +29 -0
- package/dist/commands/compact.js.map +1 -0
- package/dist/commands/copy.d.ts +8 -0
- package/dist/commands/copy.js +49 -0
- package/dist/commands/copy.js.map +1 -0
- package/dist/commands/cost.d.ts +8 -0
- package/dist/commands/cost.js +94 -0
- package/dist/commands/cost.js.map +1 -0
- package/dist/commands/diff.d.ts +8 -0
- package/dist/commands/diff.js +38 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +23 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.js +42 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/fork.d.ts +8 -0
- package/dist/commands/fork.js +20 -0
- package/dist/commands/fork.js.map +1 -0
- package/dist/commands/goal.d.ts +9 -0
- package/dist/commands/goal.js +54 -0
- package/dist/commands/goal.js.map +1 -0
- package/dist/commands/help.d.ts +8 -0
- package/dist/commands/help.js +65 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/hooks.d.ts +8 -0
- package/dist/commands/hooks.js +56 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/image.d.ts +17 -0
- package/dist/commands/image.js +74 -0
- package/dist/commands/image.js.map +1 -0
- package/dist/commands/init.d.ts +26 -0
- package/dist/commands/init.js +214 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp.d.ts +8 -0
- package/dist/commands/mcp.js +217 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +8 -0
- package/dist/commands/memory.js +79 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/model.d.ts +8 -0
- package/dist/commands/model.js +21 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/permissions.d.ts +9 -0
- package/dist/commands/permissions.js +30 -0
- package/dist/commands/permissions.js.map +1 -0
- package/dist/commands/proactive.d.ts +9 -0
- package/dist/commands/proactive.js +24 -0
- package/dist/commands/proactive.js.map +1 -0
- package/dist/commands/register-all.d.ts +1 -0
- package/dist/commands/register-all.js +68 -0
- package/dist/commands/register-all.js.map +1 -0
- package/dist/commands/registry.d.ts +64 -0
- package/dist/commands/registry.js +235 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/rename.d.ts +8 -0
- package/dist/commands/rename.js +16 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/review.d.ts +8 -0
- package/dist/commands/review.js +13 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/rewind.d.ts +8 -0
- package/dist/commands/rewind.js +45 -0
- package/dist/commands/rewind.js.map +1 -0
- package/dist/commands/route.d.ts +9 -0
- package/dist/commands/route.js +83 -0
- package/dist/commands/route.js.map +1 -0
- package/dist/commands/sessions.d.ts +9 -0
- package/dist/commands/sessions.js +25 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/skill.d.ts +9 -0
- package/dist/commands/skill.js +46 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +41 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/summary.d.ts +8 -0
- package/dist/commands/summary.js +39 -0
- package/dist/commands/summary.js.map +1 -0
- package/dist/commands/tasks.d.ts +9 -0
- package/dist/commands/tasks.js +70 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/commands/todos.d.ts +9 -0
- package/dist/commands/todos.js +27 -0
- package/dist/commands/todos.js.map +1 -0
- package/dist/commands/tutorial.d.ts +8 -0
- package/dist/commands/tutorial.js +159 -0
- package/dist/commands/tutorial.js.map +1 -0
- package/dist/commands/upgrade.d.ts +9 -0
- package/dist/commands/upgrade.js +73 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/usage.d.ts +8 -0
- package/dist/commands/usage.js +54 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/vim.d.ts +10 -0
- package/dist/commands/vim.js +34 -0
- package/dist/commands/vim.js.map +1 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.js +533 -0
- package/dist/config.js.map +1 -0
- package/dist/context/compaction.d.ts +66 -0
- package/dist/context/compaction.js +353 -0
- package/dist/context/compaction.js.map +1 -0
- package/dist/context/layers.d.ts +32 -0
- package/dist/context/layers.js +114 -0
- package/dist/context/layers.js.map +1 -0
- package/dist/context/token-budget.d.ts +21 -0
- package/dist/context/token-budget.js +70 -0
- package/dist/context/token-budget.js.map +1 -0
- package/dist/control/auth.d.ts +17 -0
- package/dist/control/auth.js +31 -0
- package/dist/control/auth.js.map +1 -0
- package/dist/control/daemon.d.ts +38 -0
- package/dist/control/daemon.js +135 -0
- package/dist/control/daemon.js.map +1 -0
- package/dist/control/mdns.d.ts +51 -0
- package/dist/control/mdns.js +344 -0
- package/dist/control/mdns.js.map +1 -0
- package/dist/control/peer-client.d.ts +49 -0
- package/dist/control/peer-client.js +170 -0
- package/dist/control/peer-client.js.map +1 -0
- package/dist/control/server.d.ts +30 -0
- package/dist/control/server.js +887 -0
- package/dist/control/server.js.map +1 -0
- package/dist/cost.d.ts +23 -0
- package/dist/cost.js +80 -0
- package/dist/cost.js.map +1 -0
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.js +22 -0
- package/dist/doctor.js.map +1 -0
- package/dist/env.d.ts +7 -0
- package/dist/env.js +70 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +19 -0
- package/dist/events.js +236 -0
- package/dist/events.js.map +1 -0
- package/dist/fs-utils.d.ts +21 -0
- package/dist/fs-utils.js +48 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/goal.d.ts +32 -0
- package/dist/goal.js +172 -0
- package/dist/goal.js.map +1 -0
- package/dist/headless.d.ts +38 -0
- package/dist/headless.js +547 -0
- package/dist/headless.js.map +1 -0
- package/dist/history.d.ts +8 -0
- package/dist/history.js +77 -0
- package/dist/history.js.map +1 -0
- package/dist/hooks/events.d.ts +14 -0
- package/dist/hooks/events.js +37 -0
- package/dist/hooks/events.js.map +1 -0
- package/dist/hooks/runner.d.ts +73 -0
- package/dist/hooks/runner.js +256 -0
- package/dist/hooks/runner.js.map +1 -0
- package/dist/hooks/trigger.d.ts +9 -0
- package/dist/hooks/trigger.js +18 -0
- package/dist/hooks/trigger.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions.d.ts +102 -0
- package/dist/interactions.js +250 -0
- package/dist/interactions.js.map +1 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.js +115 -0
- package/dist/logger.js.map +1 -0
- package/dist/markdown.d.ts +18 -0
- package/dist/markdown.js +293 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mcp/approval.d.ts +9 -0
- package/dist/mcp/approval.js +27 -0
- package/dist/mcp/approval.js.map +1 -0
- package/dist/mcp/client.d.ts +61 -0
- package/dist/mcp/client.js +276 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/connection-manager.d.ts +30 -0
- package/dist/mcp/connection-manager.js +126 -0
- package/dist/mcp/connection-manager.js.map +1 -0
- package/dist/mcp/oauth-callback.d.ts +23 -0
- package/dist/mcp/oauth-callback.js +108 -0
- package/dist/mcp/oauth-callback.js.map +1 -0
- package/dist/mcp/oauth-flow.d.ts +45 -0
- package/dist/mcp/oauth-flow.js +145 -0
- package/dist/mcp/oauth-flow.js.map +1 -0
- package/dist/mcp/oauth.d.ts +82 -0
- package/dist/mcp/oauth.js +169 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp/tool-registry.d.ts +29 -0
- package/dist/mcp/tool-registry.js +207 -0
- package/dist/mcp/tool-registry.js.map +1 -0
- package/dist/mcp/transport.d.ts +33 -0
- package/dist/mcp/transport.js +493 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/mcp/types.d.ts +57 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/memdir.d.ts +58 -0
- package/dist/memdir.js +219 -0
- package/dist/memdir.js.map +1 -0
- package/dist/memory-selection.d.ts +30 -0
- package/dist/memory-selection.js +102 -0
- package/dist/memory-selection.js.map +1 -0
- package/dist/memory.d.ts +85 -0
- package/dist/memory.js +259 -0
- package/dist/memory.js.map +1 -0
- package/dist/paths.d.ts +25 -0
- package/dist/paths.js +99 -0
- package/dist/paths.js.map +1 -0
- package/dist/permissions.d.ts +17 -0
- package/dist/permissions.js +72 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugins/manifest.d.ts +17 -0
- package/dist/plugins/manifest.js +98 -0
- package/dist/plugins/manifest.js.map +1 -0
- package/dist/plugins/marketplace.d.ts +22 -0
- package/dist/plugins/marketplace.js +98 -0
- package/dist/plugins/marketplace.js.map +1 -0
- package/dist/proactive.d.ts +15 -0
- package/dist/proactive.js +49 -0
- package/dist/proactive.js.map +1 -0
- package/dist/providers/errors.d.ts +14 -0
- package/dist/providers/errors.js +66 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/format-proxy.d.ts +100 -0
- package/dist/providers/format-proxy.js +235 -0
- package/dist/providers/format-proxy.js.map +1 -0
- package/dist/providers/http.d.ts +4 -0
- package/dist/providers/http.js +21 -0
- package/dist/providers/http.js.map +1 -0
- package/dist/providers/ir.d.ts +84 -0
- package/dist/providers/ir.js +76 -0
- package/dist/providers/ir.js.map +1 -0
- package/dist/providers/messages-compatible.d.ts +19 -0
- package/dist/providers/messages-compatible.js +592 -0
- package/dist/providers/messages-compatible.js.map +1 -0
- package/dist/providers/openai.d.ts +18 -0
- package/dist/providers/openai.js +430 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +10 -0
- package/dist/providers/registry.js +16 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/sse.d.ts +6 -0
- package/dist/providers/sse.js +104 -0
- package/dist/providers/sse.js.map +1 -0
- package/dist/routing/model-alias.d.ts +8 -0
- package/dist/routing/model-alias.js +23 -0
- package/dist/routing/model-alias.js.map +1 -0
- package/dist/routing/model-router.d.ts +55 -0
- package/dist/routing/model-router.js +233 -0
- package/dist/routing/model-router.js.map +1 -0
- package/dist/routing/router.d.ts +26 -0
- package/dist/routing/router.js +63 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/rules/agents-loader.d.ts +12 -0
- package/dist/rules/agents-loader.js +61 -0
- package/dist/rules/agents-loader.js.map +1 -0
- package/dist/runner/client.d.ts +67 -0
- package/dist/runner/client.js +175 -0
- package/dist/runner/client.js.map +1 -0
- package/dist/session-store.d.ts +250 -0
- package/dist/session-store.js +629 -0
- package/dist/session-store.js.map +1 -0
- package/dist/skills/bundled.d.ts +14 -0
- package/dist/skills/bundled.js +148 -0
- package/dist/skills/bundled.js.map +1 -0
- package/dist/skills/loader.d.ts +11 -0
- package/dist/skills/loader.js +73 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/slash-menu.d.ts +14 -0
- package/dist/slash-menu.js +166 -0
- package/dist/slash-menu.js.map +1 -0
- package/dist/slash.d.ts +75 -0
- package/dist/slash.js +126 -0
- package/dist/slash.js.map +1 -0
- package/dist/spinner.d.ts +27 -0
- package/dist/spinner.js +68 -0
- package/dist/spinner.js.map +1 -0
- package/dist/ssh/exec.d.ts +20 -0
- package/dist/ssh/exec.js +39 -0
- package/dist/ssh/exec.js.map +1 -0
- package/dist/ssh/file.d.ts +32 -0
- package/dist/ssh/file.js +60 -0
- package/dist/ssh/file.js.map +1 -0
- package/dist/syntax-highlight.d.ts +19 -0
- package/dist/syntax-highlight.js +181 -0
- package/dist/syntax-highlight.js.map +1 -0
- package/dist/tools/agent-tool.d.ts +53 -0
- package/dist/tools/agent-tool.js +62 -0
- package/dist/tools/agent-tool.js.map +1 -0
- package/dist/tools/archive-create.d.ts +35 -0
- package/dist/tools/archive-create.js +37 -0
- package/dist/tools/archive-create.js.map +1 -0
- package/dist/tools/archive-extract.d.ts +28 -0
- package/dist/tools/archive-extract.js +36 -0
- package/dist/tools/archive-extract.js.map +1 -0
- package/dist/tools/base64.d.ts +27 -0
- package/dist/tools/base64.js +19 -0
- package/dist/tools/base64.js.map +1 -0
- package/dist/tools/browser.d.ts +125 -0
- package/dist/tools/browser.js +241 -0
- package/dist/tools/browser.js.map +1 -0
- package/dist/tools/config-tool.d.ts +31 -0
- package/dist/tools/config-tool.js +167 -0
- package/dist/tools/config-tool.js.map +1 -0
- package/dist/tools/cron.d.ts +110 -0
- package/dist/tools/cron.js +299 -0
- package/dist/tools/cron.js.map +1 -0
- package/dist/tools/date.d.ts +24 -0
- package/dist/tools/date.js +26 -0
- package/dist/tools/date.js.map +1 -0
- package/dist/tools/dir-create.d.ts +21 -0
- package/dist/tools/dir-create.js +19 -0
- package/dist/tools/dir-create.js.map +1 -0
- package/dist/tools/dir-list.d.ts +36 -0
- package/dist/tools/dir-list.js +47 -0
- package/dist/tools/dir-list.js.map +1 -0
- package/dist/tools/disk-usage.d.ts +30 -0
- package/dist/tools/disk-usage.js +22 -0
- package/dist/tools/disk-usage.js.map +1 -0
- package/dist/tools/download-file.d.ts +28 -0
- package/dist/tools/download-file.js +42 -0
- package/dist/tools/download-file.js.map +1 -0
- package/dist/tools/environment.d.ts +31 -0
- package/dist/tools/environment.js +33 -0
- package/dist/tools/environment.js.map +1 -0
- package/dist/tools/errors.d.ts +5 -0
- package/dist/tools/errors.js +9 -0
- package/dist/tools/errors.js.map +1 -0
- package/dist/tools/file-copy.d.ts +34 -0
- package/dist/tools/file-copy.js +45 -0
- package/dist/tools/file-copy.js.map +1 -0
- package/dist/tools/file-delete.d.ts +34 -0
- package/dist/tools/file-delete.js +42 -0
- package/dist/tools/file-delete.js.map +1 -0
- package/dist/tools/file-find.d.ts +46 -0
- package/dist/tools/file-find.js +33 -0
- package/dist/tools/file-find.js.map +1 -0
- package/dist/tools/file-move.d.ts +33 -0
- package/dist/tools/file-move.js +32 -0
- package/dist/tools/file-move.js.map +1 -0
- package/dist/tools/files.d.ts +38 -0
- package/dist/tools/files.js +123 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/git-branch-delete.d.ts +27 -0
- package/dist/tools/git-branch-delete.js +25 -0
- package/dist/tools/git-branch-delete.js.map +1 -0
- package/dist/tools/git-reset.d.ts +30 -0
- package/dist/tools/git-reset.js +28 -0
- package/dist/tools/git-reset.js.map +1 -0
- package/dist/tools/git-stash.d.ts +28 -0
- package/dist/tools/git-stash.js +24 -0
- package/dist/tools/git-stash.js.map +1 -0
- package/dist/tools/git.d.ts +77 -0
- package/dist/tools/git.js +300 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/github.d.ts +68 -0
- package/dist/tools/github.js +77 -0
- package/dist/tools/github.js.map +1 -0
- package/dist/tools/head-tail.d.ts +34 -0
- package/dist/tools/head-tail.js +23 -0
- package/dist/tools/head-tail.js.map +1 -0
- package/dist/tools/http-request.d.ts +44 -0
- package/dist/tools/http-request.js +57 -0
- package/dist/tools/http-request.js.map +1 -0
- package/dist/tools/json-query.d.ts +27 -0
- package/dist/tools/json-query.js +90 -0
- package/dist/tools/json-query.js.map +1 -0
- package/dist/tools/kill-process.d.ts +32 -0
- package/dist/tools/kill-process.js +51 -0
- package/dist/tools/kill-process.js.map +1 -0
- package/dist/tools/lsp.d.ts +98 -0
- package/dist/tools/lsp.js +398 -0
- package/dist/tools/lsp.js.map +1 -0
- package/dist/tools/monitor.d.ts +29 -0
- package/dist/tools/monitor.js +78 -0
- package/dist/tools/monitor.js.map +1 -0
- package/dist/tools/network-check.d.ts +34 -0
- package/dist/tools/network-check.js +57 -0
- package/dist/tools/network-check.js.map +1 -0
- package/dist/tools/notebook.d.ts +63 -0
- package/dist/tools/notebook.js +158 -0
- package/dist/tools/notebook.js.map +1 -0
- package/dist/tools/plan-mode.d.ts +48 -0
- package/dist/tools/plan-mode.js +57 -0
- package/dist/tools/plan-mode.js.map +1 -0
- package/dist/tools/process-list.d.ts +35 -0
- package/dist/tools/process-list.js +42 -0
- package/dist/tools/process-list.js.map +1 -0
- package/dist/tools/registry.d.ts +132 -0
- package/dist/tools/registry.js +2262 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/search.d.ts +38 -0
- package/dist/tools/search.js +327 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/shell.d.ts +18 -0
- package/dist/tools/shell.js +249 -0
- package/dist/tools/shell.js.map +1 -0
- package/dist/tools/skill-tool.d.ts +22 -0
- package/dist/tools/skill-tool.js +66 -0
- package/dist/tools/skill-tool.js.map +1 -0
- package/dist/tools/sleep.d.ts +16 -0
- package/dist/tools/sleep.js +24 -0
- package/dist/tools/sleep.js.map +1 -0
- package/dist/tools/snip.d.ts +26 -0
- package/dist/tools/snip.js +95 -0
- package/dist/tools/snip.js.map +1 -0
- package/dist/tools/system-info.d.ts +17 -0
- package/dist/tools/system-info.js +33 -0
- package/dist/tools/system-info.js.map +1 -0
- package/dist/tools/tasks.d.ts +191 -0
- package/dist/tools/tasks.js +348 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/text-stats.d.ts +25 -0
- package/dist/tools/text-stats.js +25 -0
- package/dist/tools/text-stats.js.map +1 -0
- package/dist/tools/todo.d.ts +69 -0
- package/dist/tools/todo.js +197 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/tool-search.d.ts +29 -0
- package/dist/tools/tool-search.js +114 -0
- package/dist/tools/tool-search.js.map +1 -0
- package/dist/tools/tree-view.d.ts +34 -0
- package/dist/tools/tree-view.js +39 -0
- package/dist/tools/tree-view.js.map +1 -0
- package/dist/tools/user-message.d.ts +39 -0
- package/dist/tools/user-message.js +70 -0
- package/dist/tools/user-message.js.map +1 -0
- package/dist/tools/user-question.d.ts +88 -0
- package/dist/tools/user-question.js +236 -0
- package/dist/tools/user-question.js.map +1 -0
- package/dist/tools/web-browser.d.ts +53 -0
- package/dist/tools/web-browser.js +373 -0
- package/dist/tools/web-browser.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +22 -0
- package/dist/tools/web-fetch.js +146 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +51 -0
- package/dist/tools/web-search.js +198 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tools/which.d.ts +22 -0
- package/dist/tools/which.js +21 -0
- package/dist/tools/which.js.map +1 -0
- package/dist/tools/whoami.d.ts +17 -0
- package/dist/tools/whoami.js +24 -0
- package/dist/tools/whoami.js.map +1 -0
- package/dist/tools/workspace-diagnostics.d.ts +62 -0
- package/dist/tools/workspace-diagnostics.js +421 -0
- package/dist/tools/workspace-diagnostics.js.map +1 -0
- package/dist/tools/workspace.d.ts +14 -0
- package/dist/tools/workspace.js +65 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/tools/worktree.d.ts +70 -0
- package/dist/tools/worktree.js +150 -0
- package/dist/tools/worktree.js.map +1 -0
- package/dist/tui/interactions.d.ts +25 -0
- package/dist/tui/interactions.js +232 -0
- package/dist/tui/interactions.js.map +1 -0
- package/dist/tui/multiline-input.d.ts +14 -0
- package/dist/tui/multiline-input.js +550 -0
- package/dist/tui/multiline-input.js.map +1 -0
- package/dist/tui/paste.d.ts +40 -0
- package/dist/tui/paste.js +113 -0
- package/dist/tui/paste.js.map +1 -0
- package/dist/tui/prompt-reader.d.ts +26 -0
- package/dist/tui/prompt-reader.js +553 -0
- package/dist/tui/prompt-reader.js.map +1 -0
- package/dist/tui/transcript.d.ts +34 -0
- package/dist/tui/transcript.js +275 -0
- package/dist/tui/transcript.js.map +1 -0
- package/dist/tui.d.ts +53 -0
- package/dist/tui.js +669 -0
- package/dist/tui.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/dist/vim/lineEditor.d.ts +18 -0
- package/dist/vim/lineEditor.js +420 -0
- package/dist/vim/lineEditor.js.map +1 -0
- package/dist/web/panel-html.d.ts +1 -0
- package/dist/web/panel-html.js +532 -0
- package/dist/web/panel-html.js.map +1 -0
- package/dist/web/panel.d.ts +4 -0
- package/dist/web/panel.js +121 -0
- package/dist/web/panel.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,2262 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { executeConfigTool, ConfigToolInputSchema, parseConfigToolInput } from "./config-tool.js";
|
|
5
|
+
import { addCronJob, applyCronUpdate, CRON_CREATE_SCHEMA, CRON_DELETE_SCHEMA, CRON_LIST_SCHEMA, CRON_UPDATE_SCHEMA, cronStorePathFromRoot, deleteCronJob, formatCronJob, formatCronList, listCronJobs } from "./cron.js";
|
|
6
|
+
import { createUnifiedDiff, editWorkspaceFile, readWorkspaceFile, writeWorkspaceFile } from "./files.js";
|
|
7
|
+
import { checkoutGitBranch, createGitBranch, getGitDiff, getGitLog, getGitShow, getGitStatus, getGitSummary, listGitBranches, stageGitPaths } from "./git.js";
|
|
8
|
+
import { executeLspRequest, LSP_SCHEMA, parseLspRequest } from "./lsp.js";
|
|
9
|
+
import { formatSearchMatches, globWorkspace, searchWorkspace } from "./search.js";
|
|
10
|
+
import { runShellCommand } from "./shell.js";
|
|
11
|
+
import { formatMonitorResult, getMonitorData, MonitorInputSchema, parseMonitorInput } from "./monitor.js";
|
|
12
|
+
import { executeSleep, parseSleepInput, SleepInputSchema } from "./sleep.js";
|
|
13
|
+
import { executeFileCopy, formatFileCopyResult, FileCopyInputSchema, parseFileCopyInput } from "./file-copy.js";
|
|
14
|
+
import { executeFileMove, formatFileMoveResult, FileMoveInputSchema, parseFileMoveInput } from "./file-move.js";
|
|
15
|
+
import { executeFileDelete, FileDeleteInputSchema, formatFileDeleteResult, parseFileDeleteInput } from "./file-delete.js";
|
|
16
|
+
import { DirCreateInputSchema, executeDirCreate, formatDirCreateResult, parseDirCreateInput } from "./dir-create.js";
|
|
17
|
+
import { DirListInputSchema, executeDirList, formatDirListResult, parseDirListInput } from "./dir-list.js";
|
|
18
|
+
import { executeProcessList, formatProcessListResult, parseProcessListInput, ProcessListInputSchema } from "./process-list.js";
|
|
19
|
+
import { executeKillProcess, formatKillProcessResult, KillProcessInputSchema, parseKillProcessInput } from "./kill-process.js";
|
|
20
|
+
import { EnvironmentInputSchema, executeEnvironment, formatEnvironmentResult, parseEnvironmentInput } from "./environment.js";
|
|
21
|
+
import { DiskUsageInputSchema, executeDiskUsage, formatDiskUsageResult, parseDiskUsageInput } from "./disk-usage.js";
|
|
22
|
+
import { executeSystemInfo, formatSystemInfoResult, SystemInfoInputSchema } from "./system-info.js";
|
|
23
|
+
import { executeHttpRequest, formatHttpRequestResult, HttpRequestInputSchema, parseHttpRequestInput } from "./http-request.js";
|
|
24
|
+
import { executeJsonQuery, formatJsonQueryResult, JsonQueryInputSchema, parseJsonQueryInput } from "./json-query.js";
|
|
25
|
+
import { ArchiveCreateInputSchema, executeArchiveCreate, formatArchiveCreateResult, parseArchiveCreateInput } from "./archive-create.js";
|
|
26
|
+
import { ArchiveExtractInputSchema, executeArchiveExtract, formatArchiveExtractResult, parseArchiveExtractInput } from "./archive-extract.js";
|
|
27
|
+
import { executeGitBranchDelete, formatGitBranchDeleteResult, GitBranchDeleteInputSchema, parseGitBranchDeleteInput } from "./git-branch-delete.js";
|
|
28
|
+
import { executeGitStash, formatGitStashResult, GitStashInputSchema, parseGitStashInput } from "./git-stash.js";
|
|
29
|
+
import { executeGitReset, formatGitResetResult, GitResetInputSchema, parseGitResetInput } from "./git-reset.js";
|
|
30
|
+
import { executeFileFind, FileFindInputSchema, formatFileFindResult, parseFileFindInput } from "./file-find.js";
|
|
31
|
+
import { executeHeadTail, formatHeadTailResult, HeadTailInputSchema, parseHeadTailInput } from "./head-tail.js";
|
|
32
|
+
import { executeTextStats, formatTextStatsResult, parseTextStatsInput, TextStatsInputSchema } from "./text-stats.js";
|
|
33
|
+
import { executeTreeView, formatTreeViewResult, parseTreeViewInput, TreeViewInputSchema } from "./tree-view.js";
|
|
34
|
+
import { executeWhoAmI, formatWhoAmIResult, WhoAmIInputSchema } from "./whoami.js";
|
|
35
|
+
import { executeNetworkCheck, formatNetworkCheckResult, NetworkCheckInputSchema, parseNetworkCheckInput } from "./network-check.js";
|
|
36
|
+
import { Base64InputSchema, executeBase64, formatBase64Result, parseBase64Input } from "./base64.js";
|
|
37
|
+
import { executeWhich, formatWhichResult, parseWhichInput, WhichInputSchema } from "./which.js";
|
|
38
|
+
import { DateInputSchema, executeDate, formatDateResult } from "./date.js";
|
|
39
|
+
import { sshExec } from "../ssh/exec.js";
|
|
40
|
+
import { sshFileRead, sshFileWrite } from "../ssh/file.js";
|
|
41
|
+
import { executeSnip, formatSnipResult, parseSnipInput, SnipInputSchema } from "./snip.js";
|
|
42
|
+
import { executeSkillTool, parseSkillToolInput, SkillToolInputSchema } from "./skill-tool.js";
|
|
43
|
+
import { writeMemdirEntry } from "../memdir.js";
|
|
44
|
+
import { formatTodoWriteResult, parseTodoWriteInput, replaceTodoList, TodoWriteInputSchema } from "./todo.js";
|
|
45
|
+
import { executeToolSearch, parseToolSearchInput, ToolSearchInputSchema } from "./tool-search.js";
|
|
46
|
+
import { executeTaskCreate, executeTaskGet, executeTaskList, executeTaskOutput, executeTaskStop, executeTaskUpdate, formatTaskCreateResult, formatTaskGetResult, formatTaskListResult, formatTaskOutputResult, formatTaskStopResult, formatTaskUpdateResult, parseTaskCreateInput, parseTaskUpdateInput, TaskCreateInputSchema, TaskGetInputSchema, TaskListInputSchema, TaskOutputInputSchema, TaskStopInputSchema, TaskUpdateInputSchema } from "./tasks.js";
|
|
47
|
+
import { EnterPlanModeInputSchema, ExitPlanModeInputSchema, formatEnterPlanModeResult, formatExitPlanModeResult, parseEnterPlanModeInput, parseExitPlanModeInput } from "./plan-mode.js";
|
|
48
|
+
import { EnterWorktreeInputSchema, ExitWorktreeInputSchema, executeEnterWorktree, executeExitWorktree, formatEnterWorktreeResult, formatExitWorktreeResult, parseEnterWorktreeInput, parseExitWorktreeInput } from "./worktree.js";
|
|
49
|
+
import { ghIssueView, ghPRDiff, ghPRList, ghPRView, GitHubIssueViewInputSchema, GitHubPRDiffInputSchema, GitHubPRListInputSchema, GitHubPRViewInputSchema } from "./github.js";
|
|
50
|
+
import { AgentToolInputSchema, formatAgentToolResult, parseAgentToolInput } from "./agent-tool.js";
|
|
51
|
+
import { executeNotebookEdit, executeNotebookRead, NotebookEditInputSchema, NotebookReadInputSchema, parseNotebookEditInput, parseNotebookReadInput } from "./notebook.js";
|
|
52
|
+
import { defaultUserMessageSink, formatSendUserMessageResult, parseSendUserMessageInput, SEND_USER_MESSAGE_SCHEMA } from "./user-message.js";
|
|
53
|
+
import { ASK_USER_QUESTION_SCHEMA, formatAskUserQuestionAnswer, normalizeAskUserQuestionAnswer, parseAskUserQuestionInput } from "./user-question.js";
|
|
54
|
+
import { readWebFetchAllowlist, webFetch, webFetchHostAllowed } from "./web-fetch.js";
|
|
55
|
+
import { BrowserActionInputSchema, executeBrowserAction, formatBrowserActionResult } from "./browser.js";
|
|
56
|
+
import { executeWebBrowser, formatWebBrowserResult, parseWebBrowserInput, WebBrowserInputSchema } from "./web-browser.js";
|
|
57
|
+
import { formatWebSearchResult, parseWebSearchInput, webSearch, WebSearchInputSchema } from "./web-search.js";
|
|
58
|
+
import { formatWorkspaceDiagnostics, parseWorkspaceDiagnosticsInput, runWorkspaceDiagnostics, WorkspaceDiagnosticsInputSchema } from "./workspace-diagnostics.js";
|
|
59
|
+
import { resolveWorkspacePath } from "./workspace.js";
|
|
60
|
+
export function getBuiltinToolRegistry() {
|
|
61
|
+
return new Map(BUILTIN_TOOLS.map((tool) => [tool.name, tool]));
|
|
62
|
+
}
|
|
63
|
+
export function getBuiltinToolDefinitions() {
|
|
64
|
+
return BUILTIN_TOOLS.map((tool) => ({
|
|
65
|
+
name: tool.name,
|
|
66
|
+
description: tool.description,
|
|
67
|
+
inputSchema: tool.inputSchema
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
export async function executeRegisteredTool(input) {
|
|
71
|
+
const registry = getBuiltinToolRegistry();
|
|
72
|
+
const tool = registry.get(input.toolUse.name);
|
|
73
|
+
if (!tool) {
|
|
74
|
+
return errorResult(input.toolUse, `Unknown tool: ${input.toolUse.name}`);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const context = {
|
|
78
|
+
cwd: input.cwd,
|
|
79
|
+
env: input.env,
|
|
80
|
+
permissionMode: input.permissionMode ?? "default",
|
|
81
|
+
rules: input.rules,
|
|
82
|
+
outputRoot: input.outputRoot,
|
|
83
|
+
stateRoot: input.stateRoot,
|
|
84
|
+
sessionId: input.sessionId,
|
|
85
|
+
webSearchConfig: input.webSearchConfig,
|
|
86
|
+
promptModel: input.promptModel,
|
|
87
|
+
userQuestionResolver: input.userQuestionResolver,
|
|
88
|
+
userMessageSink: input.userMessageSink,
|
|
89
|
+
toolUse: input.toolUse,
|
|
90
|
+
spawnSubAgent: input.spawnSubAgent,
|
|
91
|
+
signal: input.signal
|
|
92
|
+
};
|
|
93
|
+
const permission = checkToolPermission({
|
|
94
|
+
toolUse: input.toolUse,
|
|
95
|
+
mode: context.permissionMode,
|
|
96
|
+
rules: context.rules,
|
|
97
|
+
tool,
|
|
98
|
+
env: context.env
|
|
99
|
+
});
|
|
100
|
+
// Generate diff preview for FileWrite/FileEdit when approval is needed
|
|
101
|
+
if (permission.decision === "ask" && (input.toolUse.name === "FileWrite" || input.toolUse.name === "FileEdit")) {
|
|
102
|
+
try {
|
|
103
|
+
const filePath = readString(input.toolUse.input, "file_path");
|
|
104
|
+
const resolved = resolveWorkspacePath(input.cwd, filePath);
|
|
105
|
+
const before = existsSync(resolved.absolutePath) ? readFileSync(resolved.absolutePath, "utf8") : "";
|
|
106
|
+
let after;
|
|
107
|
+
if (input.toolUse.name === "FileWrite") {
|
|
108
|
+
after = readString(input.toolUse.input, "content");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const oldString = readString(input.toolUse.input, "old_string");
|
|
112
|
+
const newString = readString(input.toolUse.input, "new_string");
|
|
113
|
+
const replaceAll = Boolean(input.toolUse.input.replace_all);
|
|
114
|
+
after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
115
|
+
}
|
|
116
|
+
permission.diff = createUnifiedDiff(resolved.relativePath, before, after);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Diff preview is best-effort
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const approvalPermission = permission.decision === "ask" ? permission : undefined;
|
|
123
|
+
if (permission.decision === "ask") {
|
|
124
|
+
const approved = await input.approvalResolver?.({ toolUse: input.toolUse, permission });
|
|
125
|
+
if (!approved) {
|
|
126
|
+
return errorResult(input.toolUse, `Permission ask: ${permission.reason}`, permission);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (permission.decision !== "allow") {
|
|
130
|
+
return errorResult(input.toolUse, `Permission ${permission.decision}: ${permission.reason}`);
|
|
131
|
+
}
|
|
132
|
+
const raw = await tool.call(input.toolUse.input, context);
|
|
133
|
+
return {
|
|
134
|
+
toolCallId: input.toolUse.id,
|
|
135
|
+
toolName: input.toolUse.name,
|
|
136
|
+
content: formatToolResult({
|
|
137
|
+
content: raw,
|
|
138
|
+
outputRoot: context.outputRoot,
|
|
139
|
+
maxChars: 30_000,
|
|
140
|
+
previewChars: 2_000
|
|
141
|
+
}),
|
|
142
|
+
permission: approvalPermission
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (shouldRethrowToolExecutionError(error)) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
return errorResult(input.toolUse, error instanceof Error ? error.message : String(error));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function shouldRethrowToolExecutionError(error) {
|
|
153
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (!(error instanceof Error)) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return error.name === "AbortError"
|
|
160
|
+
|| error.name === "ActiveInteractionCancelledError"
|
|
161
|
+
|| error.name === "ActiveInteractionTimeoutError";
|
|
162
|
+
}
|
|
163
|
+
export async function executeRegisteredTools(input) {
|
|
164
|
+
const registry = getBuiltinToolRegistry();
|
|
165
|
+
const results = new Array(input.toolUses.length);
|
|
166
|
+
const concurrent = [];
|
|
167
|
+
const sequential = [];
|
|
168
|
+
input.toolUses.forEach((toolUse, index) => {
|
|
169
|
+
const tool = registry.get(toolUse.name);
|
|
170
|
+
if (tool?.isConcurrencySafe(toolUse.input)) {
|
|
171
|
+
concurrent.push({ index, toolUse });
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
sequential.push({ index, toolUse });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
await Promise.all(concurrent.map(async ({ index, toolUse }) => {
|
|
178
|
+
results[index] = await executeRegisteredTool({ ...input, toolUse });
|
|
179
|
+
}));
|
|
180
|
+
for (const { index, toolUse } of sequential) {
|
|
181
|
+
results[index] = await executeRegisteredTool({ ...input, toolUse });
|
|
182
|
+
}
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
export function checkToolPermission(input) {
|
|
186
|
+
const tool = input.tool ?? getBuiltinToolRegistry().get(input.toolUse.name);
|
|
187
|
+
if (!tool) {
|
|
188
|
+
return { decision: "deny", reason: `Unknown tool: ${input.toolUse.name}` };
|
|
189
|
+
}
|
|
190
|
+
const context = {
|
|
191
|
+
cwd: ".",
|
|
192
|
+
permissionMode: input.mode,
|
|
193
|
+
rules: input.rules,
|
|
194
|
+
env: input.env
|
|
195
|
+
};
|
|
196
|
+
const custom = tool.checkPermissions?.(input.toolUse.input, context);
|
|
197
|
+
if (custom) {
|
|
198
|
+
return custom;
|
|
199
|
+
}
|
|
200
|
+
const ruleDecision = matchRules(input.toolUse, input.rules);
|
|
201
|
+
if (ruleDecision) {
|
|
202
|
+
return ruleDecision;
|
|
203
|
+
}
|
|
204
|
+
if (input.mode === "bypassPermissions" || input.mode === "acceptEdits") {
|
|
205
|
+
return { decision: "allow", reason: `${input.mode} mode` };
|
|
206
|
+
}
|
|
207
|
+
if (input.mode === "plan" && !tool.isReadOnly(input.toolUse.input)) {
|
|
208
|
+
return { decision: "deny", reason: `${input.toolUse.name} is not allowed in plan mode` };
|
|
209
|
+
}
|
|
210
|
+
if (input.mode === "default" && !tool.isReadOnly(input.toolUse.input)) {
|
|
211
|
+
return { decision: "ask", reason: `${input.toolUse.name} requires approval` };
|
|
212
|
+
}
|
|
213
|
+
return { decision: "allow", reason: "read-only tool" };
|
|
214
|
+
}
|
|
215
|
+
export function formatToolResult(input) {
|
|
216
|
+
const maxChars = input.maxChars ?? 30_000;
|
|
217
|
+
if (input.content.length <= maxChars) {
|
|
218
|
+
return input.content;
|
|
219
|
+
}
|
|
220
|
+
if (!input.outputRoot) {
|
|
221
|
+
return `${input.content.slice(0, input.previewChars ?? 2_000)}\n...[truncated]...`;
|
|
222
|
+
}
|
|
223
|
+
mkdirSync(input.outputRoot, { recursive: true });
|
|
224
|
+
const file = path.join(input.outputRoot, `${randomUUID()}.txt`);
|
|
225
|
+
writeFileSync(file, input.content, "utf8");
|
|
226
|
+
return `${input.content.slice(0, input.previewChars ?? 2_000)}\n...[truncated]...\n\nFull output saved to: ${file}`;
|
|
227
|
+
}
|
|
228
|
+
const BUILTIN_TOOLS = [
|
|
229
|
+
{
|
|
230
|
+
name: "FileRead",
|
|
231
|
+
description: "Read a UTF-8 text file inside the current workspace.",
|
|
232
|
+
category: "files",
|
|
233
|
+
tags: ["file", "read", "workspace"],
|
|
234
|
+
inputSchema: objectSchema({
|
|
235
|
+
file_path: { type: "string" },
|
|
236
|
+
max_bytes: { type: "number" }
|
|
237
|
+
}, ["file_path"]),
|
|
238
|
+
call: (input, context) => {
|
|
239
|
+
const result = readWorkspaceFile({
|
|
240
|
+
cwd: context.cwd,
|
|
241
|
+
filePath: readString(input, "file_path"),
|
|
242
|
+
maxBytes: readOptionalNumber(input, "max_bytes")
|
|
243
|
+
});
|
|
244
|
+
return `Read ${result.path} (${result.sizeBytes} bytes)\n${result.content}`;
|
|
245
|
+
},
|
|
246
|
+
isReadOnly: () => true,
|
|
247
|
+
isDestructive: () => false,
|
|
248
|
+
isConcurrencySafe: () => true
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "FileWrite",
|
|
252
|
+
description: "Create or overwrite a UTF-8 text file inside the current workspace.",
|
|
253
|
+
category: "files",
|
|
254
|
+
tags: ["file", "write", "workspace"],
|
|
255
|
+
inputSchema: objectSchema({
|
|
256
|
+
file_path: { type: "string" },
|
|
257
|
+
content: { type: "string" }
|
|
258
|
+
}, ["file_path", "content"]),
|
|
259
|
+
call: (input, context) => {
|
|
260
|
+
const result = writeWorkspaceFile({
|
|
261
|
+
cwd: context.cwd,
|
|
262
|
+
filePath: readString(input, "file_path"),
|
|
263
|
+
content: readString(input, "content"),
|
|
264
|
+
approved: true
|
|
265
|
+
});
|
|
266
|
+
return `Wrote ${result.path}\n${result.diff}`;
|
|
267
|
+
},
|
|
268
|
+
isReadOnly: () => false,
|
|
269
|
+
isDestructive: () => true,
|
|
270
|
+
isConcurrencySafe: () => false
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: "FileEdit",
|
|
274
|
+
description: "Replace text in a UTF-8 file inside the current workspace.",
|
|
275
|
+
category: "files",
|
|
276
|
+
tags: ["file", "edit", "workspace"],
|
|
277
|
+
inputSchema: objectSchema({
|
|
278
|
+
file_path: { type: "string" },
|
|
279
|
+
old_string: { type: "string" },
|
|
280
|
+
new_string: { type: "string" },
|
|
281
|
+
replace_all: { type: "boolean" }
|
|
282
|
+
}, ["file_path", "old_string", "new_string"]),
|
|
283
|
+
call: (input, context) => {
|
|
284
|
+
const result = editWorkspaceFile({
|
|
285
|
+
cwd: context.cwd,
|
|
286
|
+
filePath: readString(input, "file_path"),
|
|
287
|
+
oldString: readString(input, "old_string"),
|
|
288
|
+
newString: readString(input, "new_string"),
|
|
289
|
+
replaceAll: readOptionalBoolean(input, "replace_all"),
|
|
290
|
+
approved: true
|
|
291
|
+
});
|
|
292
|
+
return `Wrote ${result.path}\n${result.diff}`;
|
|
293
|
+
},
|
|
294
|
+
isReadOnly: () => false,
|
|
295
|
+
isDestructive: () => false,
|
|
296
|
+
isConcurrencySafe: () => false
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "NotebookEdit",
|
|
300
|
+
description: "Edit a Jupyter notebook (.ipynb) cell: replace, insert, or delete.",
|
|
301
|
+
category: "files",
|
|
302
|
+
tags: ["notebook", "jupyter", "ipynb", "edit"],
|
|
303
|
+
inputSchema: NotebookEditInputSchema,
|
|
304
|
+
call: (input, context) => {
|
|
305
|
+
const parsed = parseNotebookEditInput(input);
|
|
306
|
+
return executeNotebookEdit(context.cwd, parsed);
|
|
307
|
+
},
|
|
308
|
+
isReadOnly: () => false,
|
|
309
|
+
isDestructive: (input) => input.edit_mode === "delete",
|
|
310
|
+
isConcurrencySafe: () => false
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "NotebookRead",
|
|
314
|
+
description: "Read a Jupyter notebook (.ipynb) and display cell contents.",
|
|
315
|
+
category: "files",
|
|
316
|
+
tags: ["notebook", "jupyter", "ipynb", "read"],
|
|
317
|
+
inputSchema: NotebookReadInputSchema,
|
|
318
|
+
call: (input, context) => {
|
|
319
|
+
const parsed = parseNotebookReadInput(input);
|
|
320
|
+
return executeNotebookRead(context.cwd, parsed);
|
|
321
|
+
},
|
|
322
|
+
isReadOnly: () => true,
|
|
323
|
+
isDestructive: () => false,
|
|
324
|
+
isConcurrencySafe: () => true
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "Glob",
|
|
328
|
+
description: "Find files by glob pattern inside the current workspace.",
|
|
329
|
+
category: "search",
|
|
330
|
+
tags: ["glob", "file", "workspace"],
|
|
331
|
+
inputSchema: objectSchema({
|
|
332
|
+
pattern: { type: "string" },
|
|
333
|
+
path: { type: "string" },
|
|
334
|
+
max_matches: { type: "number" }
|
|
335
|
+
}, ["pattern"]),
|
|
336
|
+
call: (input, context) => {
|
|
337
|
+
const matches = globWorkspace({
|
|
338
|
+
cwd: context.cwd,
|
|
339
|
+
pattern: readString(input, "pattern"),
|
|
340
|
+
basePath: readOptionalString(input, "path"),
|
|
341
|
+
maxMatches: readOptionalNumber(input, "max_matches")
|
|
342
|
+
});
|
|
343
|
+
return matches.length === 0 ? "No matches" : matches.join("\n");
|
|
344
|
+
},
|
|
345
|
+
isReadOnly: () => true,
|
|
346
|
+
isDestructive: () => false,
|
|
347
|
+
isConcurrencySafe: () => true
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: "Grep",
|
|
351
|
+
description: "Search text in the current workspace with ripgrep-compatible options.",
|
|
352
|
+
category: "search",
|
|
353
|
+
tags: ["grep", "search", "ripgrep", "workspace"],
|
|
354
|
+
inputSchema: objectSchema({
|
|
355
|
+
pattern: { type: "string" },
|
|
356
|
+
path: { type: "string" },
|
|
357
|
+
glob: { type: "string" },
|
|
358
|
+
type: { type: "string" },
|
|
359
|
+
output_mode: { type: "string" },
|
|
360
|
+
max_matches: { type: "number" },
|
|
361
|
+
head_limit: { type: "number" },
|
|
362
|
+
ignore_case: { type: "boolean" },
|
|
363
|
+
fixed_strings: { type: "boolean" },
|
|
364
|
+
line_numbers: { type: "boolean" },
|
|
365
|
+
before_context: { type: "number" },
|
|
366
|
+
after_context: { type: "number" },
|
|
367
|
+
context: { type: "number" }
|
|
368
|
+
}, ["pattern"]),
|
|
369
|
+
call: (input, context) => {
|
|
370
|
+
const outputMode = readOutputMode(input, "output_mode");
|
|
371
|
+
const contextLines = readOptionalNumber(input, "context");
|
|
372
|
+
const matches = searchWorkspace({
|
|
373
|
+
cwd: context.cwd,
|
|
374
|
+
query: readString(input, "pattern"),
|
|
375
|
+
basePath: readOptionalString(input, "path"),
|
|
376
|
+
glob: readOptionalString(input, "glob"),
|
|
377
|
+
type: readOptionalString(input, "type"),
|
|
378
|
+
maxMatches: readOptionalNumber(input, "max_matches"),
|
|
379
|
+
headLimit: readOptionalNumber(input, "head_limit"),
|
|
380
|
+
ignoreCase: readOptionalBoolean(input, "ignore_case"),
|
|
381
|
+
fixedStrings: readOptionalBoolean(input, "fixed_strings"),
|
|
382
|
+
beforeContext: readOptionalNumber(input, "before_context") ?? contextLines,
|
|
383
|
+
afterContext: readOptionalNumber(input, "after_context") ?? contextLines
|
|
384
|
+
});
|
|
385
|
+
return formatSearchMatches(matches, {
|
|
386
|
+
outputMode,
|
|
387
|
+
pattern: readString(input, "pattern"),
|
|
388
|
+
lineNumbers: readOptionalBoolean(input, "line_numbers") ?? true
|
|
389
|
+
});
|
|
390
|
+
},
|
|
391
|
+
isReadOnly: () => true,
|
|
392
|
+
isDestructive: () => false,
|
|
393
|
+
isConcurrencySafe: () => true
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "Bash",
|
|
397
|
+
description: "Run a shell command in the current workspace.",
|
|
398
|
+
category: "shell",
|
|
399
|
+
tags: ["bash", "command", "terminal"],
|
|
400
|
+
inputSchema: objectSchema({
|
|
401
|
+
command: { type: "string" },
|
|
402
|
+
timeout_ms: { type: "number" }
|
|
403
|
+
}, ["command"]),
|
|
404
|
+
call: async (input, context) => {
|
|
405
|
+
const result = await runShellCommand({
|
|
406
|
+
cwd: context.cwd,
|
|
407
|
+
command: readString(input, "command"),
|
|
408
|
+
timeoutMs: readOptionalNumber(input, "timeout_ms"),
|
|
409
|
+
approveDangerous: context.env?.MAGI_APPROVE_DANGEROUS_COMMANDS === "1",
|
|
410
|
+
signal: context.signal
|
|
411
|
+
});
|
|
412
|
+
return [
|
|
413
|
+
`Command exited ${result.exitCode}`,
|
|
414
|
+
result.stdout ? `stdout:\n${result.stdout.trimEnd()}` : undefined,
|
|
415
|
+
result.stderr ? `stderr:\n${result.stderr.trimEnd()}` : undefined
|
|
416
|
+
].filter((line) => Boolean(line)).join("\n");
|
|
417
|
+
},
|
|
418
|
+
isReadOnly: () => false,
|
|
419
|
+
isDestructive: () => false,
|
|
420
|
+
isConcurrencySafe: () => false
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "GitSummary",
|
|
424
|
+
description: "Return git branch, status, and diffstat for the current workspace.",
|
|
425
|
+
category: "git",
|
|
426
|
+
tags: ["git", "status", "diff"],
|
|
427
|
+
inputSchema: objectSchema({}, []),
|
|
428
|
+
call: (_input, context) => {
|
|
429
|
+
const git = getGitSummary(context.cwd);
|
|
430
|
+
if (!git.gitAvailable || !git.isRepository) {
|
|
431
|
+
return git.reason ?? "Git summary is unavailable";
|
|
432
|
+
}
|
|
433
|
+
return [
|
|
434
|
+
`branch: ${git.branch}`,
|
|
435
|
+
git.status ? `status:\n${git.status}` : "status: clean",
|
|
436
|
+
git.diffStat ? `diffStat:\n${git.diffStat}` : "diffStat: none"
|
|
437
|
+
].join("\n");
|
|
438
|
+
},
|
|
439
|
+
isReadOnly: () => true,
|
|
440
|
+
isDestructive: () => false,
|
|
441
|
+
isConcurrencySafe: () => true
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "GitStatus",
|
|
445
|
+
description: "Return git working tree status for the current workspace.",
|
|
446
|
+
category: "git",
|
|
447
|
+
tags: ["git", "status", "workspace"],
|
|
448
|
+
inputSchema: objectSchema({
|
|
449
|
+
path: { type: "string" },
|
|
450
|
+
branch: { type: "boolean" },
|
|
451
|
+
porcelain: { type: "boolean" },
|
|
452
|
+
untracked: { type: "string" }
|
|
453
|
+
}, []),
|
|
454
|
+
call: (input, context) => getGitStatus(context.cwd, {
|
|
455
|
+
path: readOptionalString(input, "path"),
|
|
456
|
+
branch: readOptionalBoolean(input, "branch"),
|
|
457
|
+
porcelain: readOptionalBoolean(input, "porcelain"),
|
|
458
|
+
untracked: readGitUntracked(input, "untracked")
|
|
459
|
+
}),
|
|
460
|
+
isReadOnly: () => true,
|
|
461
|
+
isDestructive: () => false,
|
|
462
|
+
isConcurrencySafe: () => true
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "GitDiff",
|
|
466
|
+
description: "Return git diff output for unstaged or staged workspace changes.",
|
|
467
|
+
category: "git",
|
|
468
|
+
tags: ["git", "diff", "workspace"],
|
|
469
|
+
inputSchema: objectSchema({
|
|
470
|
+
path: { type: "string" },
|
|
471
|
+
staged: { type: "boolean" },
|
|
472
|
+
stat: { type: "boolean" },
|
|
473
|
+
name_only: { type: "boolean" },
|
|
474
|
+
context: { type: "number" }
|
|
475
|
+
}, []),
|
|
476
|
+
call: (input, context) => getGitDiff(context.cwd, {
|
|
477
|
+
path: readOptionalString(input, "path"),
|
|
478
|
+
staged: readOptionalBoolean(input, "staged"),
|
|
479
|
+
stat: readOptionalBoolean(input, "stat"),
|
|
480
|
+
nameOnly: readOptionalBoolean(input, "name_only"),
|
|
481
|
+
context: readOptionalNumber(input, "context")
|
|
482
|
+
}),
|
|
483
|
+
isReadOnly: () => true,
|
|
484
|
+
isDestructive: () => false,
|
|
485
|
+
isConcurrencySafe: () => true
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: "GitLog",
|
|
489
|
+
description: "Return recent git commits for the current workspace.",
|
|
490
|
+
category: "git",
|
|
491
|
+
tags: ["git", "log", "commits"],
|
|
492
|
+
inputSchema: objectSchema({
|
|
493
|
+
path: { type: "string" },
|
|
494
|
+
max_count: { type: "number" },
|
|
495
|
+
oneline: { type: "boolean" }
|
|
496
|
+
}, []),
|
|
497
|
+
call: (input, context) => getGitLog(context.cwd, {
|
|
498
|
+
path: readOptionalString(input, "path"),
|
|
499
|
+
maxCount: readOptionalNumber(input, "max_count"),
|
|
500
|
+
oneline: readOptionalBoolean(input, "oneline")
|
|
501
|
+
}),
|
|
502
|
+
isReadOnly: () => true,
|
|
503
|
+
isDestructive: () => false,
|
|
504
|
+
isConcurrencySafe: () => true
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: "GitShow",
|
|
508
|
+
description: "Return a git object or commit using a simple revision name, tag, or commit id.",
|
|
509
|
+
category: "git",
|
|
510
|
+
tags: ["git", "show", "commit"],
|
|
511
|
+
inputSchema: objectSchema({
|
|
512
|
+
rev: { type: "string" },
|
|
513
|
+
stat: { type: "boolean" },
|
|
514
|
+
name_only: { type: "boolean" },
|
|
515
|
+
max_bytes: { type: "number" }
|
|
516
|
+
}, []),
|
|
517
|
+
call: (input, context) => getGitShow(context.cwd, {
|
|
518
|
+
rev: readOptionalString(input, "rev"),
|
|
519
|
+
stat: readOptionalBoolean(input, "stat"),
|
|
520
|
+
nameOnly: readOptionalBoolean(input, "name_only"),
|
|
521
|
+
maxBytes: readOptionalNumber(input, "max_bytes")
|
|
522
|
+
}),
|
|
523
|
+
isReadOnly: () => true,
|
|
524
|
+
isDestructive: () => false,
|
|
525
|
+
isConcurrencySafe: () => true
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: "GitBranchList",
|
|
529
|
+
description: "List local or all branches for the current git workspace.",
|
|
530
|
+
category: "git",
|
|
531
|
+
tags: ["git", "branch", "list"],
|
|
532
|
+
inputSchema: objectSchema({
|
|
533
|
+
all: { type: "boolean" }
|
|
534
|
+
}, []),
|
|
535
|
+
call: (input, context) => listGitBranches(context.cwd, {
|
|
536
|
+
all: readOptionalBoolean(input, "all")
|
|
537
|
+
}),
|
|
538
|
+
isReadOnly: () => true,
|
|
539
|
+
isDestructive: () => false,
|
|
540
|
+
isConcurrencySafe: () => true
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: "GitBranchCreate",
|
|
544
|
+
description: "Create a new git branch, optionally checking it out.",
|
|
545
|
+
category: "git",
|
|
546
|
+
tags: ["git", "branch", "create", "mutation"],
|
|
547
|
+
inputSchema: objectSchema({
|
|
548
|
+
name: { type: "string" },
|
|
549
|
+
start_point: { type: "string" },
|
|
550
|
+
checkout: { type: "boolean" }
|
|
551
|
+
}, ["name"]),
|
|
552
|
+
call: (input, context) => createGitBranch(context.cwd, {
|
|
553
|
+
name: readString(input, "name"),
|
|
554
|
+
startPoint: readOptionalString(input, "start_point"),
|
|
555
|
+
checkout: readOptionalBoolean(input, "checkout")
|
|
556
|
+
}),
|
|
557
|
+
isReadOnly: () => false,
|
|
558
|
+
isDestructive: () => false,
|
|
559
|
+
isConcurrencySafe: () => false
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "GitCheckout",
|
|
563
|
+
description: "Check out an existing git branch, or create and check out a new branch.",
|
|
564
|
+
category: "git",
|
|
565
|
+
tags: ["git", "checkout", "branch", "mutation"],
|
|
566
|
+
inputSchema: objectSchema({
|
|
567
|
+
branch: { type: "string" },
|
|
568
|
+
create: { type: "boolean" },
|
|
569
|
+
start_point: { type: "string" }
|
|
570
|
+
}, ["branch"]),
|
|
571
|
+
call: (input, context) => checkoutGitBranch(context.cwd, {
|
|
572
|
+
branch: readString(input, "branch"),
|
|
573
|
+
create: readOptionalBoolean(input, "create"),
|
|
574
|
+
startPoint: readOptionalString(input, "start_point")
|
|
575
|
+
}),
|
|
576
|
+
isReadOnly: () => false,
|
|
577
|
+
isDestructive: () => false,
|
|
578
|
+
isConcurrencySafe: () => false
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: "GitStage",
|
|
582
|
+
description: "Stage or unstage specific workspace paths.",
|
|
583
|
+
category: "git",
|
|
584
|
+
tags: ["git", "stage", "index", "mutation"],
|
|
585
|
+
inputSchema: objectSchema({
|
|
586
|
+
paths: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 100 },
|
|
587
|
+
mode: { type: "string" }
|
|
588
|
+
}, ["paths"]),
|
|
589
|
+
call: (input, context) => stageGitPaths(context.cwd, {
|
|
590
|
+
paths: readStringArray(input, "paths"),
|
|
591
|
+
mode: readGitStageMode(input, "mode")
|
|
592
|
+
}),
|
|
593
|
+
isReadOnly: () => false,
|
|
594
|
+
isDestructive: (input) => input.mode === "unstage",
|
|
595
|
+
isConcurrencySafe: () => false
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: "WebFetch",
|
|
599
|
+
description: "Fetch a web page and use the active model to extract information from it.",
|
|
600
|
+
category: "web",
|
|
601
|
+
tags: ["web", "fetch", "http", "summarize"],
|
|
602
|
+
inputSchema: objectSchema({
|
|
603
|
+
url: { type: "string" },
|
|
604
|
+
prompt: { type: "string" },
|
|
605
|
+
max_bytes: { type: "number" }
|
|
606
|
+
}, ["url", "prompt"]),
|
|
607
|
+
call: async (input, context) => {
|
|
608
|
+
if (!context.promptModel) {
|
|
609
|
+
throw new Error("WebFetch requires an active model route");
|
|
610
|
+
}
|
|
611
|
+
const result = await webFetch({
|
|
612
|
+
url: readString(input, "url"),
|
|
613
|
+
prompt: readString(input, "prompt"),
|
|
614
|
+
maxBytes: readOptionalNumber(input, "max_bytes"),
|
|
615
|
+
promptModel: context.promptModel
|
|
616
|
+
});
|
|
617
|
+
return [
|
|
618
|
+
`Title: ${result.title}`,
|
|
619
|
+
`URL: ${result.url}`,
|
|
620
|
+
`Fetched bytes: ${result.fetchedBytes}`,
|
|
621
|
+
"",
|
|
622
|
+
result.summary
|
|
623
|
+
].join("\n");
|
|
624
|
+
},
|
|
625
|
+
isReadOnly: () => true,
|
|
626
|
+
isDestructive: () => false,
|
|
627
|
+
isConcurrencySafe: () => false,
|
|
628
|
+
checkPermissions: (input, context) => {
|
|
629
|
+
const url = readString(input, "url");
|
|
630
|
+
const allowed = webFetchHostAllowed(url, readWebFetchAllowlist(context.env));
|
|
631
|
+
if (allowed) {
|
|
632
|
+
return { decision: "allow", reason: "WebFetch URL is allowlisted" };
|
|
633
|
+
}
|
|
634
|
+
if (context.permissionMode === "bypassPermissions") {
|
|
635
|
+
return { decision: "allow", reason: "bypassPermissions mode" };
|
|
636
|
+
}
|
|
637
|
+
return { decision: "ask", reason: `WebFetch requires approval for ${new URL(url).hostname}` };
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: "WebSearch",
|
|
642
|
+
description: "Search the web through the configured Magi Next HTTP JSON search provider.",
|
|
643
|
+
category: "web",
|
|
644
|
+
tags: ["web", "search", "http", "sources"],
|
|
645
|
+
inputSchema: WebSearchInputSchema,
|
|
646
|
+
call: async (input, context) => {
|
|
647
|
+
const request = parseWebSearchInput(input);
|
|
648
|
+
// Fallback: if no endpoint configured, silently use WebBrowser (Bing HTML)
|
|
649
|
+
if (!context.webSearchConfig || !context.webSearchConfig.endpoint) {
|
|
650
|
+
const browserResult = await executeWebBrowser({
|
|
651
|
+
action: "search",
|
|
652
|
+
query: request.query,
|
|
653
|
+
maxResults: request.maxResults
|
|
654
|
+
});
|
|
655
|
+
return formatWebBrowserResult(browserResult);
|
|
656
|
+
}
|
|
657
|
+
const result = await webSearch({
|
|
658
|
+
request,
|
|
659
|
+
config: context.webSearchConfig,
|
|
660
|
+
env: context.env
|
|
661
|
+
});
|
|
662
|
+
return formatWebSearchResult(result);
|
|
663
|
+
},
|
|
664
|
+
isReadOnly: () => true,
|
|
665
|
+
isDestructive: () => false,
|
|
666
|
+
isConcurrencySafe: () => false
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
name: "WebBrowser",
|
|
670
|
+
description: "Browse the web: search via DuckDuckGo (no API key needed) or fetch a URL and extract readable text.",
|
|
671
|
+
category: "web",
|
|
672
|
+
tags: ["web", "browser", "search", "fetch", "duckduckgo"],
|
|
673
|
+
inputSchema: WebBrowserInputSchema,
|
|
674
|
+
call: async (input) => {
|
|
675
|
+
const parsed = parseWebBrowserInput(input);
|
|
676
|
+
const result = await executeWebBrowser(parsed);
|
|
677
|
+
return formatWebBrowserResult(result);
|
|
678
|
+
},
|
|
679
|
+
isReadOnly: () => true,
|
|
680
|
+
isDestructive: () => false,
|
|
681
|
+
isConcurrencySafe: () => false
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: "AskUserQuestion",
|
|
685
|
+
description: "Ask the user 1 to 4 structured multiple-choice questions and return their selections.",
|
|
686
|
+
category: "communication",
|
|
687
|
+
tags: ["user", "question", "approval", "choice"],
|
|
688
|
+
inputSchema: ASK_USER_QUESTION_SCHEMA,
|
|
689
|
+
call: async (input, context) => {
|
|
690
|
+
if (!context.userQuestionResolver) {
|
|
691
|
+
throw new Error("AskUserQuestion requires an interactive user question resolver");
|
|
692
|
+
}
|
|
693
|
+
const request = parseAskUserQuestionInput(input);
|
|
694
|
+
const rawAnswer = await context.userQuestionResolver({
|
|
695
|
+
toolUse: context.toolUse ?? { type: "tool-use", id: "AskUserQuestion", name: "AskUserQuestion", input },
|
|
696
|
+
question: request
|
|
697
|
+
});
|
|
698
|
+
return formatAskUserQuestionAnswer(normalizeAskUserQuestionAnswer(request, rawAnswer));
|
|
699
|
+
},
|
|
700
|
+
isReadOnly: () => true,
|
|
701
|
+
isDestructive: () => false,
|
|
702
|
+
isConcurrencySafe: () => false
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
name: "SendUserMessage",
|
|
706
|
+
description: "Send a markdown message from the agent to the user, optionally with attachments.",
|
|
707
|
+
category: "communication",
|
|
708
|
+
tags: ["user", "message", "notification"],
|
|
709
|
+
inputSchema: SEND_USER_MESSAGE_SCHEMA,
|
|
710
|
+
call: async (input, context) => {
|
|
711
|
+
const request = parseSendUserMessageInput(input);
|
|
712
|
+
const result = await (context.userMessageSink ?? defaultUserMessageSink)({
|
|
713
|
+
toolUse: context.toolUse ?? { type: "tool-use", id: "SendUserMessage", name: "SendUserMessage", input },
|
|
714
|
+
message: request
|
|
715
|
+
});
|
|
716
|
+
return formatSendUserMessageResult(request, result);
|
|
717
|
+
},
|
|
718
|
+
isReadOnly: () => true,
|
|
719
|
+
isDestructive: () => false,
|
|
720
|
+
isConcurrencySafe: () => false
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: "Brief",
|
|
724
|
+
description: "Alias of SendUserMessage for concise agent-to-user updates.",
|
|
725
|
+
category: "communication",
|
|
726
|
+
tags: ["user", "message", "brief"],
|
|
727
|
+
inputSchema: SEND_USER_MESSAGE_SCHEMA,
|
|
728
|
+
call: async (input, context) => {
|
|
729
|
+
const request = parseSendUserMessageInput(input);
|
|
730
|
+
const result = await (context.userMessageSink ?? defaultUserMessageSink)({
|
|
731
|
+
toolUse: context.toolUse ?? { type: "tool-use", id: "Brief", name: "Brief", input },
|
|
732
|
+
message: request
|
|
733
|
+
});
|
|
734
|
+
return formatSendUserMessageResult(request, result);
|
|
735
|
+
},
|
|
736
|
+
isReadOnly: () => true,
|
|
737
|
+
isDestructive: () => false,
|
|
738
|
+
isConcurrencySafe: () => false
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
name: "CronCreate",
|
|
742
|
+
description: "Create a 5-field local-time cron job that queues a prompt for future execution.",
|
|
743
|
+
category: "schedule",
|
|
744
|
+
tags: ["cron", "schedule", "job"],
|
|
745
|
+
inputSchema: CRON_CREATE_SCHEMA,
|
|
746
|
+
call: (input, context) => {
|
|
747
|
+
const job = addCronJob(requireStateFile(context), {
|
|
748
|
+
cron: readString(input, "cron"),
|
|
749
|
+
prompt: readString(input, "prompt"),
|
|
750
|
+
recurring: readOptionalBoolean(input, "recurring"),
|
|
751
|
+
durable: readOptionalBoolean(input, "durable")
|
|
752
|
+
});
|
|
753
|
+
return `Created cron job\n${formatCronJob(job)}`;
|
|
754
|
+
},
|
|
755
|
+
isReadOnly: () => false,
|
|
756
|
+
isDestructive: () => false,
|
|
757
|
+
isConcurrencySafe: () => false
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
name: "CronUpdate",
|
|
761
|
+
description: "Update an existing cron job by id.",
|
|
762
|
+
category: "schedule",
|
|
763
|
+
tags: ["cron", "schedule", "update"],
|
|
764
|
+
inputSchema: CRON_UPDATE_SCHEMA,
|
|
765
|
+
call: (input, context) => {
|
|
766
|
+
const job = applyCronUpdate(requireStateFile(context), {
|
|
767
|
+
id: readString(input, "id"),
|
|
768
|
+
cron: readOptionalString(input, "cron"),
|
|
769
|
+
prompt: readOptionalString(input, "prompt"),
|
|
770
|
+
recurring: readOptionalBoolean(input, "recurring"),
|
|
771
|
+
durable: readOptionalBoolean(input, "durable"),
|
|
772
|
+
enabled: readOptionalBoolean(input, "enabled")
|
|
773
|
+
});
|
|
774
|
+
return `Updated cron job\n${formatCronJob(job)}`;
|
|
775
|
+
},
|
|
776
|
+
isReadOnly: () => false,
|
|
777
|
+
isDestructive: () => false,
|
|
778
|
+
isConcurrencySafe: () => false
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "CronDelete",
|
|
782
|
+
description: "Delete an existing cron job by id.",
|
|
783
|
+
category: "schedule",
|
|
784
|
+
tags: ["cron", "schedule", "delete"],
|
|
785
|
+
inputSchema: CRON_DELETE_SCHEMA,
|
|
786
|
+
call: (input, context) => {
|
|
787
|
+
const job = deleteCronJob(requireStateFile(context), readString(input, "id"));
|
|
788
|
+
return `Deleted cron job\n${formatCronJob(job)}`;
|
|
789
|
+
},
|
|
790
|
+
isReadOnly: () => false,
|
|
791
|
+
isDestructive: () => true,
|
|
792
|
+
isConcurrencySafe: () => false
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
name: "CronList",
|
|
796
|
+
description: "List all configured cron jobs.",
|
|
797
|
+
category: "schedule",
|
|
798
|
+
tags: ["cron", "schedule", "list"],
|
|
799
|
+
inputSchema: CRON_LIST_SCHEMA,
|
|
800
|
+
call: (_input, context) => formatCronList(listCronJobs(requireStateFile(context))),
|
|
801
|
+
isReadOnly: () => true,
|
|
802
|
+
isDestructive: () => false,
|
|
803
|
+
isConcurrencySafe: () => false
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
name: "TodoWrite",
|
|
807
|
+
description: "Replace the current session todo list with a validated complete list.",
|
|
808
|
+
category: "state",
|
|
809
|
+
tags: ["todo", "plan", "state"],
|
|
810
|
+
inputSchema: TodoWriteInputSchema,
|
|
811
|
+
call: async (input, context) => {
|
|
812
|
+
const todoContext = requireTodoContext(context);
|
|
813
|
+
return formatTodoWriteResult(await replaceTodoList({
|
|
814
|
+
stateRoot: todoContext.stateRoot,
|
|
815
|
+
sessionId: todoContext.sessionId,
|
|
816
|
+
todos: parseTodoWriteInput(input)
|
|
817
|
+
}));
|
|
818
|
+
},
|
|
819
|
+
isReadOnly: () => false,
|
|
820
|
+
isDestructive: () => true,
|
|
821
|
+
isConcurrencySafe: () => false
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
name: "TaskCreate",
|
|
825
|
+
description: "Create a new task to track progress on multi-step work.",
|
|
826
|
+
category: "state",
|
|
827
|
+
tags: ["task", "create", "progress"],
|
|
828
|
+
inputSchema: TaskCreateInputSchema,
|
|
829
|
+
call: (input, context) => {
|
|
830
|
+
const todoContext = requireTodoContext(context);
|
|
831
|
+
return formatTaskCreateResult(executeTaskCreate({
|
|
832
|
+
stateRoot: todoContext.stateRoot,
|
|
833
|
+
sessionId: todoContext.sessionId,
|
|
834
|
+
task: parseTaskCreateInput(input)
|
|
835
|
+
}));
|
|
836
|
+
},
|
|
837
|
+
isReadOnly: () => false,
|
|
838
|
+
isDestructive: () => false,
|
|
839
|
+
isConcurrencySafe: () => false
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
name: "TaskUpdate",
|
|
843
|
+
description: "Update a task's status, subject, or priority. Set status to 'deleted' to remove.",
|
|
844
|
+
category: "state",
|
|
845
|
+
tags: ["task", "update", "progress"],
|
|
846
|
+
inputSchema: TaskUpdateInputSchema,
|
|
847
|
+
call: (input, context) => {
|
|
848
|
+
const todoContext = requireTodoContext(context);
|
|
849
|
+
return formatTaskUpdateResult(executeTaskUpdate({
|
|
850
|
+
stateRoot: todoContext.stateRoot,
|
|
851
|
+
sessionId: todoContext.sessionId,
|
|
852
|
+
update: parseTaskUpdateInput(input)
|
|
853
|
+
}));
|
|
854
|
+
},
|
|
855
|
+
isReadOnly: () => false,
|
|
856
|
+
isDestructive: (input) => input.status === "deleted",
|
|
857
|
+
isConcurrencySafe: () => false
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
name: "TaskList",
|
|
861
|
+
description: "List all tasks in the current session.",
|
|
862
|
+
category: "state",
|
|
863
|
+
tags: ["task", "list", "progress"],
|
|
864
|
+
inputSchema: TaskListInputSchema,
|
|
865
|
+
call: (_input, context) => {
|
|
866
|
+
const todoContext = requireTodoContext(context);
|
|
867
|
+
return formatTaskListResult(executeTaskList({
|
|
868
|
+
stateRoot: todoContext.stateRoot,
|
|
869
|
+
sessionId: todoContext.sessionId
|
|
870
|
+
}));
|
|
871
|
+
},
|
|
872
|
+
isReadOnly: () => true,
|
|
873
|
+
isDestructive: () => false,
|
|
874
|
+
isConcurrencySafe: () => true
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: "TaskGet",
|
|
878
|
+
description: "Retrieve a task by ID with full details including description and status.",
|
|
879
|
+
category: "state",
|
|
880
|
+
tags: ["task", "get", "detail"],
|
|
881
|
+
inputSchema: TaskGetInputSchema,
|
|
882
|
+
call: (input, context) => {
|
|
883
|
+
const todoContext = requireTodoContext(context);
|
|
884
|
+
return formatTaskGetResult(executeTaskGet({
|
|
885
|
+
stateRoot: todoContext.stateRoot,
|
|
886
|
+
sessionId: todoContext.sessionId,
|
|
887
|
+
taskId: readString(input, "taskId")
|
|
888
|
+
}));
|
|
889
|
+
},
|
|
890
|
+
isReadOnly: () => true,
|
|
891
|
+
isDestructive: () => false,
|
|
892
|
+
isConcurrencySafe: () => true
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
name: "TaskOutput",
|
|
896
|
+
description: "Retrieve output from a running or completed background task.",
|
|
897
|
+
category: "state",
|
|
898
|
+
tags: ["task", "output", "background"],
|
|
899
|
+
inputSchema: TaskOutputInputSchema,
|
|
900
|
+
call: async (input, context) => {
|
|
901
|
+
const todoContext = requireTodoContext(context);
|
|
902
|
+
return formatTaskOutputResult(await executeTaskOutput({
|
|
903
|
+
stateRoot: todoContext.stateRoot,
|
|
904
|
+
sessionId: todoContext.sessionId,
|
|
905
|
+
taskId: readString(input, "taskId")
|
|
906
|
+
}));
|
|
907
|
+
},
|
|
908
|
+
isReadOnly: () => true,
|
|
909
|
+
isDestructive: () => false,
|
|
910
|
+
isConcurrencySafe: () => true
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: "TaskStop",
|
|
914
|
+
description: "Stop a running background task by its ID.",
|
|
915
|
+
category: "state",
|
|
916
|
+
tags: ["task", "stop", "cancel"],
|
|
917
|
+
inputSchema: TaskStopInputSchema,
|
|
918
|
+
call: async (input, context) => {
|
|
919
|
+
const todoContext = requireTodoContext(context);
|
|
920
|
+
return formatTaskStopResult(await executeTaskStop({
|
|
921
|
+
stateRoot: todoContext.stateRoot,
|
|
922
|
+
sessionId: todoContext.sessionId,
|
|
923
|
+
taskId: readString(input, "taskId")
|
|
924
|
+
}));
|
|
925
|
+
},
|
|
926
|
+
isReadOnly: () => false,
|
|
927
|
+
isDestructive: () => false,
|
|
928
|
+
isConcurrencySafe: () => false
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
name: "ToolSearch",
|
|
932
|
+
description: "Search built-in tool documentation or select one tool by name for its full schema.",
|
|
933
|
+
category: "tools",
|
|
934
|
+
tags: ["tool", "search", "schema", "docs"],
|
|
935
|
+
inputSchema: ToolSearchInputSchema,
|
|
936
|
+
call: (input) => executeToolSearch(parseToolSearchInput(input), BUILTIN_TOOLS),
|
|
937
|
+
isReadOnly: () => true,
|
|
938
|
+
isDestructive: () => false,
|
|
939
|
+
isConcurrencySafe: () => true
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
name: "WorkspaceDiagnostics",
|
|
943
|
+
description: "Inspect the current workspace for manifests, languages, package scripts, suggested commands, and git status without executing project commands.",
|
|
944
|
+
category: "workspace",
|
|
945
|
+
tags: ["workspace", "diagnostics", "scripts", "setup"],
|
|
946
|
+
inputSchema: WorkspaceDiagnosticsInputSchema,
|
|
947
|
+
call: (input, context) => {
|
|
948
|
+
const request = parseWorkspaceDiagnosticsInput(input);
|
|
949
|
+
return formatWorkspaceDiagnostics(runWorkspaceDiagnostics({
|
|
950
|
+
cwd: context.cwd,
|
|
951
|
+
request
|
|
952
|
+
}), request.format);
|
|
953
|
+
},
|
|
954
|
+
isReadOnly: () => true,
|
|
955
|
+
isDestructive: () => false,
|
|
956
|
+
isConcurrencySafe: () => true
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
name: "Config",
|
|
960
|
+
description: "Read or update allowlisted Magi Next configuration settings.",
|
|
961
|
+
category: "config",
|
|
962
|
+
tags: ["config", "settings", "magi"],
|
|
963
|
+
inputSchema: ConfigToolInputSchema,
|
|
964
|
+
call: (input, context) => executeConfigTool({
|
|
965
|
+
request: parseConfigToolInput(input),
|
|
966
|
+
configFile: requireConfigFile(context),
|
|
967
|
+
env: context.env
|
|
968
|
+
}),
|
|
969
|
+
isReadOnly: (input) => input.value === undefined,
|
|
970
|
+
isDestructive: () => false,
|
|
971
|
+
isConcurrencySafe: (input) => input.value === undefined
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: "Memorize",
|
|
975
|
+
description: "Save a durable memory (memdir entry) for future conversations. Use sparingly for genuine user preferences, project facts, corrections, or reference pointers — not for ephemeral conversation state.",
|
|
976
|
+
category: "memory",
|
|
977
|
+
tags: ["memory", "memdir", "persist"],
|
|
978
|
+
inputSchema: {
|
|
979
|
+
type: "object",
|
|
980
|
+
additionalProperties: false,
|
|
981
|
+
properties: {
|
|
982
|
+
type: {
|
|
983
|
+
type: "string",
|
|
984
|
+
enum: ["user", "feedback", "project", "reference"],
|
|
985
|
+
description: "Memory type. user=facts about the user, feedback=corrections/preferences, project=ongoing work facts, reference=pointers to external systems."
|
|
986
|
+
},
|
|
987
|
+
name: { type: "string", minLength: 1, maxLength: 80, description: "Short title (e.g. 'User role', 'Prefers tabs over spaces')." },
|
|
988
|
+
description: { type: "string", minLength: 1, maxLength: 200, description: "One-line description used to decide relevance in future conversations." },
|
|
989
|
+
body: { type: "string", minLength: 1, maxLength: 4000, description: "Memory content. For feedback/project entries, include why and how to apply." }
|
|
990
|
+
},
|
|
991
|
+
required: ["type", "name", "description", "body"]
|
|
992
|
+
},
|
|
993
|
+
call: (input, context) => {
|
|
994
|
+
const type = String(input.type ?? "");
|
|
995
|
+
if (!isValidMemdirType(type)) {
|
|
996
|
+
throw new Error(`Invalid memory type: ${type}`);
|
|
997
|
+
}
|
|
998
|
+
const name = String(input.name ?? "").trim();
|
|
999
|
+
const description = String(input.description ?? "").trim();
|
|
1000
|
+
const body = String(input.body ?? "").trim();
|
|
1001
|
+
if (!name || !description || !body) {
|
|
1002
|
+
throw new Error("Memorize requires name, description, and body");
|
|
1003
|
+
}
|
|
1004
|
+
if (!context.stateRoot) {
|
|
1005
|
+
throw new Error("Memorize requires Magi stateRoot");
|
|
1006
|
+
}
|
|
1007
|
+
const root = path.dirname(context.stateRoot);
|
|
1008
|
+
const entry = writeMemdirEntry({
|
|
1009
|
+
paths: { root },
|
|
1010
|
+
type,
|
|
1011
|
+
name,
|
|
1012
|
+
description,
|
|
1013
|
+
body
|
|
1014
|
+
});
|
|
1015
|
+
return `Saved memory: ${entry.filename} (type=${entry.type})`;
|
|
1016
|
+
},
|
|
1017
|
+
isReadOnly: () => false,
|
|
1018
|
+
isDestructive: () => false,
|
|
1019
|
+
isConcurrencySafe: () => false
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
name: "Skill",
|
|
1023
|
+
description: "List installed Magi Next skills or load one skill's instructions by name.",
|
|
1024
|
+
category: "skills",
|
|
1025
|
+
tags: ["skill", "instructions", "workflow"],
|
|
1026
|
+
inputSchema: SkillToolInputSchema,
|
|
1027
|
+
call: (input, context) => executeSkillTool({
|
|
1028
|
+
request: parseSkillToolInput(input),
|
|
1029
|
+
skillsRoot: requireSkillsRoot(context)
|
|
1030
|
+
}),
|
|
1031
|
+
isReadOnly: () => true,
|
|
1032
|
+
isDestructive: () => false,
|
|
1033
|
+
isConcurrencySafe: () => true
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
name: "LSP",
|
|
1037
|
+
description: "Run TypeScript/JavaScript workspace symbol, definition, reference, and hover queries.",
|
|
1038
|
+
category: "lsp",
|
|
1039
|
+
tags: ["lsp", "typescript", "symbols", "references"],
|
|
1040
|
+
inputSchema: LSP_SCHEMA,
|
|
1041
|
+
call: (input, context) => executeLspRequest({
|
|
1042
|
+
cwd: context.cwd,
|
|
1043
|
+
request: parseLspRequest(input)
|
|
1044
|
+
}),
|
|
1045
|
+
isReadOnly: () => true,
|
|
1046
|
+
isDestructive: () => false,
|
|
1047
|
+
isConcurrencySafe: () => true
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
name: "EnterPlanMode",
|
|
1051
|
+
description: "Switch to plan mode for non-trivial tasks. Blocks mutations until the plan is approved by the user.",
|
|
1052
|
+
category: "planning",
|
|
1053
|
+
tags: ["plan", "mode", "architecture"],
|
|
1054
|
+
inputSchema: EnterPlanModeInputSchema,
|
|
1055
|
+
call: (input) => formatEnterPlanModeResult(parseEnterPlanModeInput(input)),
|
|
1056
|
+
isReadOnly: () => true,
|
|
1057
|
+
isDestructive: () => false,
|
|
1058
|
+
isConcurrencySafe: () => true
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
name: "ExitPlanMode",
|
|
1062
|
+
description: "Submit the implementation plan for user approval and exit plan mode.",
|
|
1063
|
+
category: "planning",
|
|
1064
|
+
tags: ["plan", "mode", "approval"],
|
|
1065
|
+
inputSchema: ExitPlanModeInputSchema,
|
|
1066
|
+
call: async (input, context) => {
|
|
1067
|
+
const parsed = parseExitPlanModeInput(input);
|
|
1068
|
+
// Ask the user to approve or reject the plan
|
|
1069
|
+
if (context.userQuestionResolver && context.toolUse) {
|
|
1070
|
+
try {
|
|
1071
|
+
const answer = await context.userQuestionResolver({
|
|
1072
|
+
toolUse: context.toolUse,
|
|
1073
|
+
question: {
|
|
1074
|
+
questions: [
|
|
1075
|
+
{
|
|
1076
|
+
question: "Do you approve this plan and want me to proceed with implementation?",
|
|
1077
|
+
header: "Plan review",
|
|
1078
|
+
options: [
|
|
1079
|
+
{ label: "Yes, proceed", description: "Approve the plan and start implementing" },
|
|
1080
|
+
{ label: "No, revise", description: "I want to give feedback or change the approach" }
|
|
1081
|
+
],
|
|
1082
|
+
multiSelect: false
|
|
1083
|
+
}
|
|
1084
|
+
]
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
const selection = answer.answers?.[0];
|
|
1088
|
+
const approved = selection?.selectedLabels?.includes("Yes, proceed") ?? false;
|
|
1089
|
+
if (approved) {
|
|
1090
|
+
return [
|
|
1091
|
+
"Plan approved. Proceeding with implementation.",
|
|
1092
|
+
"",
|
|
1093
|
+
"---",
|
|
1094
|
+
parsed.plan,
|
|
1095
|
+
"---"
|
|
1096
|
+
].join("\n");
|
|
1097
|
+
}
|
|
1098
|
+
const feedback = selection?.selectedLabels?.join(", ") ?? "User wants to revise the plan.";
|
|
1099
|
+
return [
|
|
1100
|
+
`Plan not approved. User response: ${feedback}`,
|
|
1101
|
+
"",
|
|
1102
|
+
"Stay in plan mode. Revise the approach based on the feedback above and call ExitPlanMode again with an updated plan."
|
|
1103
|
+
].join("\n");
|
|
1104
|
+
}
|
|
1105
|
+
catch {
|
|
1106
|
+
return formatExitPlanModeResult(parsed);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return formatExitPlanModeResult(parsed);
|
|
1110
|
+
},
|
|
1111
|
+
isReadOnly: () => true,
|
|
1112
|
+
isDestructive: () => false,
|
|
1113
|
+
isConcurrencySafe: () => true
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
name: "EnterWorktree",
|
|
1117
|
+
description: "Create an isolated git worktree for safe agent execution. Changes do not affect the main working tree.",
|
|
1118
|
+
category: "git",
|
|
1119
|
+
tags: ["git", "worktree", "isolation", "mutation"],
|
|
1120
|
+
inputSchema: EnterWorktreeInputSchema,
|
|
1121
|
+
call: async (input, context) => {
|
|
1122
|
+
const parsed = parseEnterWorktreeInput(input);
|
|
1123
|
+
const state = executeEnterWorktree({ cwd: context.cwd, name: parsed.name });
|
|
1124
|
+
// Persist worktree state in the session so ExitWorktree can find it
|
|
1125
|
+
if (context.sessionId && context.stateRoot) {
|
|
1126
|
+
try {
|
|
1127
|
+
const dbPath = path.join(context.stateRoot, "sessions.sqlite");
|
|
1128
|
+
if (existsSync(dbPath)) {
|
|
1129
|
+
const mod = await import("../session-store.js");
|
|
1130
|
+
const store = new mod.SessionStore(dbPath);
|
|
1131
|
+
try {
|
|
1132
|
+
store.updateSessionMetadata(context.sessionId, { worktree: state });
|
|
1133
|
+
}
|
|
1134
|
+
finally {
|
|
1135
|
+
store.close();
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
catch {
|
|
1140
|
+
// Best effort — the worktree itself is created, state just isn't persisted
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return formatEnterWorktreeResult(state);
|
|
1144
|
+
},
|
|
1145
|
+
isReadOnly: () => false,
|
|
1146
|
+
isDestructive: () => false,
|
|
1147
|
+
isConcurrencySafe: () => false
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
name: "ExitWorktree",
|
|
1151
|
+
description: "Exit the current worktree session. Use action 'keep' to preserve or 'remove' to delete.",
|
|
1152
|
+
category: "git",
|
|
1153
|
+
tags: ["git", "worktree", "cleanup"],
|
|
1154
|
+
inputSchema: ExitWorktreeInputSchema,
|
|
1155
|
+
call: async (input, context) => {
|
|
1156
|
+
const parsed = parseExitWorktreeInput(input);
|
|
1157
|
+
// Try to find the worktree state for this session
|
|
1158
|
+
let state;
|
|
1159
|
+
if (context.sessionId && context.stateRoot) {
|
|
1160
|
+
try {
|
|
1161
|
+
const dbPath = path.join(context.stateRoot, "sessions.sqlite");
|
|
1162
|
+
if (existsSync(dbPath)) {
|
|
1163
|
+
const mod = await import("../session-store.js");
|
|
1164
|
+
const store = new mod.SessionStore(dbPath);
|
|
1165
|
+
try {
|
|
1166
|
+
const session = store.getSession(context.sessionId);
|
|
1167
|
+
const meta = session?.metadata;
|
|
1168
|
+
if (meta && meta.worktree && typeof meta.worktree === "object") {
|
|
1169
|
+
state = meta.worktree;
|
|
1170
|
+
}
|
|
1171
|
+
if (parsed.action === "remove" && state) {
|
|
1172
|
+
// Clear from metadata
|
|
1173
|
+
store.updateSessionMetadata(context.sessionId, { worktree: undefined });
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
finally {
|
|
1177
|
+
store.close();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
catch {
|
|
1182
|
+
// Best effort
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (!state) {
|
|
1186
|
+
return [
|
|
1187
|
+
"No active worktree session found.",
|
|
1188
|
+
"If you created a worktree manually with `git worktree add`, this tool only operates on worktrees created by EnterWorktree."
|
|
1189
|
+
].join("\n");
|
|
1190
|
+
}
|
|
1191
|
+
const result = executeExitWorktree({
|
|
1192
|
+
cwd: context.cwd,
|
|
1193
|
+
state,
|
|
1194
|
+
action: parsed.action,
|
|
1195
|
+
discardChanges: parsed.discardChanges
|
|
1196
|
+
});
|
|
1197
|
+
return formatExitWorktreeResult(result);
|
|
1198
|
+
},
|
|
1199
|
+
isReadOnly: () => false,
|
|
1200
|
+
isDestructive: (input) => input.action === "remove",
|
|
1201
|
+
isConcurrencySafe: () => false
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
name: "GitHubIssueView",
|
|
1205
|
+
description: "View a GitHub issue by number or URL using the gh CLI.",
|
|
1206
|
+
category: "github",
|
|
1207
|
+
tags: ["github", "issue", "read"],
|
|
1208
|
+
inputSchema: GitHubIssueViewInputSchema,
|
|
1209
|
+
call: (input, context) => ghIssueView(context.cwd, readString(input, "issue")),
|
|
1210
|
+
isReadOnly: () => true,
|
|
1211
|
+
isDestructive: () => false,
|
|
1212
|
+
isConcurrencySafe: () => true
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
name: "GitHubPRView",
|
|
1216
|
+
description: "View a GitHub pull request by number or URL using the gh CLI.",
|
|
1217
|
+
category: "github",
|
|
1218
|
+
tags: ["github", "pr", "read"],
|
|
1219
|
+
inputSchema: GitHubPRViewInputSchema,
|
|
1220
|
+
call: (input, context) => ghPRView(context.cwd, readString(input, "pr")),
|
|
1221
|
+
isReadOnly: () => true,
|
|
1222
|
+
isDestructive: () => false,
|
|
1223
|
+
isConcurrencySafe: () => true
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
name: "GitHubPRList",
|
|
1227
|
+
description: "List GitHub pull requests for the current repository.",
|
|
1228
|
+
category: "github",
|
|
1229
|
+
tags: ["github", "pr", "list"],
|
|
1230
|
+
inputSchema: GitHubPRListInputSchema,
|
|
1231
|
+
call: (input, context) => ghPRList(context.cwd, {
|
|
1232
|
+
state: readOptionalString(input, "state"),
|
|
1233
|
+
limit: readOptionalNumber(input, "limit"),
|
|
1234
|
+
author: readOptionalString(input, "author"),
|
|
1235
|
+
label: readOptionalString(input, "label")
|
|
1236
|
+
}),
|
|
1237
|
+
isReadOnly: () => true,
|
|
1238
|
+
isDestructive: () => false,
|
|
1239
|
+
isConcurrencySafe: () => true
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
name: "GitHubPRDiff",
|
|
1243
|
+
description: "View the diff of a GitHub pull request.",
|
|
1244
|
+
category: "github",
|
|
1245
|
+
tags: ["github", "pr", "diff"],
|
|
1246
|
+
inputSchema: GitHubPRDiffInputSchema,
|
|
1247
|
+
call: (input, context) => ghPRDiff(context.cwd, readString(input, "pr")),
|
|
1248
|
+
isReadOnly: () => true,
|
|
1249
|
+
isDestructive: () => false,
|
|
1250
|
+
isConcurrencySafe: () => true
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
name: "Snip",
|
|
1254
|
+
description: "Take a screenshot of the current screen and save it to a file.",
|
|
1255
|
+
category: "system",
|
|
1256
|
+
tags: ["screenshot", "snip", "image"],
|
|
1257
|
+
inputSchema: SnipInputSchema,
|
|
1258
|
+
call: async (input, context) => {
|
|
1259
|
+
const parsed = parseSnipInput(input);
|
|
1260
|
+
const result = await executeSnip({
|
|
1261
|
+
format: parsed.format,
|
|
1262
|
+
cwd: context.cwd
|
|
1263
|
+
});
|
|
1264
|
+
return formatSnipResult(result);
|
|
1265
|
+
},
|
|
1266
|
+
isReadOnly: () => true,
|
|
1267
|
+
isDestructive: () => false,
|
|
1268
|
+
isConcurrencySafe: () => true
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
name: "SshExec",
|
|
1272
|
+
description: "Run a shell command on a remote host via SSH.",
|
|
1273
|
+
category: "ssh",
|
|
1274
|
+
tags: ["ssh", "remote", "shell"],
|
|
1275
|
+
inputSchema: objectSchema({
|
|
1276
|
+
host: { type: "string" },
|
|
1277
|
+
command: { type: "string" },
|
|
1278
|
+
user: { type: "string" },
|
|
1279
|
+
port: { type: "number" },
|
|
1280
|
+
timeoutMs: { type: "number" }
|
|
1281
|
+
}, ["host", "command"]),
|
|
1282
|
+
call: async (input, context) => {
|
|
1283
|
+
const result = await sshExec({
|
|
1284
|
+
host: readString(input, "host"),
|
|
1285
|
+
command: readString(input, "command"),
|
|
1286
|
+
user: readOptionalString(input, "user"),
|
|
1287
|
+
port: readOptionalNumber(input, "port"),
|
|
1288
|
+
timeoutMs: readOptionalNumber(input, "timeoutMs")
|
|
1289
|
+
});
|
|
1290
|
+
return [
|
|
1291
|
+
`Host: ${result.host}`,
|
|
1292
|
+
`Command: ${result.command}`,
|
|
1293
|
+
`Exit code: ${result.exitCode}`,
|
|
1294
|
+
result.stdout ? `\n${result.stdout}` : "",
|
|
1295
|
+
result.stderr ? `\nstderr:\n${result.stderr}` : ""
|
|
1296
|
+
].filter(Boolean).join("\n");
|
|
1297
|
+
},
|
|
1298
|
+
isReadOnly: () => false,
|
|
1299
|
+
isDestructive: () => false,
|
|
1300
|
+
isConcurrencySafe: () => false
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
name: "SshFileRead",
|
|
1304
|
+
description: "Read a file from a remote host via SSH.",
|
|
1305
|
+
category: "ssh",
|
|
1306
|
+
tags: ["ssh", "remote", "file", "read"],
|
|
1307
|
+
inputSchema: objectSchema({
|
|
1308
|
+
host: { type: "string" },
|
|
1309
|
+
path: { type: "string" },
|
|
1310
|
+
user: { type: "string" },
|
|
1311
|
+
port: { type: "number" }
|
|
1312
|
+
}, ["host", "path"]),
|
|
1313
|
+
call: async (input, context) => {
|
|
1314
|
+
const result = await sshFileRead({
|
|
1315
|
+
host: readString(input, "host"),
|
|
1316
|
+
path: readString(input, "path"),
|
|
1317
|
+
user: readOptionalString(input, "user"),
|
|
1318
|
+
port: readOptionalNumber(input, "port")
|
|
1319
|
+
});
|
|
1320
|
+
return `Read ${result.path} on ${result.host} (${result.sizeBytes} bytes)\n${result.content}`;
|
|
1321
|
+
},
|
|
1322
|
+
isReadOnly: () => true,
|
|
1323
|
+
isDestructive: () => false,
|
|
1324
|
+
isConcurrencySafe: () => false
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
name: "SshFileWrite",
|
|
1328
|
+
description: "Write a file to a remote host via SSH.",
|
|
1329
|
+
category: "ssh",
|
|
1330
|
+
tags: ["ssh", "remote", "file", "write"],
|
|
1331
|
+
inputSchema: objectSchema({
|
|
1332
|
+
host: { type: "string" },
|
|
1333
|
+
path: { type: "string" },
|
|
1334
|
+
content: { type: "string" },
|
|
1335
|
+
user: { type: "string" },
|
|
1336
|
+
port: { type: "number" }
|
|
1337
|
+
}, ["host", "path", "content"]),
|
|
1338
|
+
call: async (input, context) => {
|
|
1339
|
+
const result = await sshFileWrite({
|
|
1340
|
+
host: readString(input, "host"),
|
|
1341
|
+
path: readString(input, "path"),
|
|
1342
|
+
content: readString(input, "content"),
|
|
1343
|
+
user: readOptionalString(input, "user"),
|
|
1344
|
+
port: readOptionalNumber(input, "port")
|
|
1345
|
+
});
|
|
1346
|
+
return `Wrote ${result.path} on ${result.host} (${result.sizeBytes} bytes)`;
|
|
1347
|
+
},
|
|
1348
|
+
isReadOnly: () => false,
|
|
1349
|
+
isDestructive: () => true,
|
|
1350
|
+
isConcurrencySafe: () => false
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
name: "Sleep",
|
|
1354
|
+
description: "Pause execution for a specified number of milliseconds.",
|
|
1355
|
+
category: "system",
|
|
1356
|
+
tags: ["sleep", "wait", "delay"],
|
|
1357
|
+
inputSchema: SleepInputSchema,
|
|
1358
|
+
call: async (input) => {
|
|
1359
|
+
const parsed = parseSleepInput(input);
|
|
1360
|
+
return executeSleep(parsed);
|
|
1361
|
+
},
|
|
1362
|
+
isReadOnly: () => true,
|
|
1363
|
+
isDestructive: () => false,
|
|
1364
|
+
isConcurrencySafe: () => true
|
|
1365
|
+
},
|
|
1366
|
+
{
|
|
1367
|
+
name: "Monitor",
|
|
1368
|
+
description: "Show system resource usage (CPU, memory, disk).",
|
|
1369
|
+
category: "system",
|
|
1370
|
+
tags: ["monitor", "system", "resources"],
|
|
1371
|
+
inputSchema: MonitorInputSchema,
|
|
1372
|
+
call: (input) => {
|
|
1373
|
+
const parsed = parseMonitorInput(input);
|
|
1374
|
+
const data = getMonitorData();
|
|
1375
|
+
return formatMonitorResult(data, parsed.scope ?? "quick");
|
|
1376
|
+
},
|
|
1377
|
+
isReadOnly: () => true,
|
|
1378
|
+
isDestructive: () => false,
|
|
1379
|
+
isConcurrencySafe: () => true
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
name: "FileCopy",
|
|
1383
|
+
description: "Copy a file with safety checks (refuses overwrite without overwrite flag).",
|
|
1384
|
+
category: "files",
|
|
1385
|
+
tags: ["file", "copy"],
|
|
1386
|
+
inputSchema: FileCopyInputSchema,
|
|
1387
|
+
call: (input, context) => {
|
|
1388
|
+
const parsed = parseFileCopyInput(input);
|
|
1389
|
+
const result = executeFileCopy({ ...parsed, cwd: context.cwd });
|
|
1390
|
+
return formatFileCopyResult(result);
|
|
1391
|
+
},
|
|
1392
|
+
isReadOnly: () => false,
|
|
1393
|
+
isDestructive: () => true,
|
|
1394
|
+
isConcurrencySafe: () => false
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
name: "FileMove",
|
|
1398
|
+
description: "Move or rename a file with safety checks.",
|
|
1399
|
+
category: "files",
|
|
1400
|
+
tags: ["file", "move", "rename"],
|
|
1401
|
+
inputSchema: FileMoveInputSchema,
|
|
1402
|
+
call: (input, context) => {
|
|
1403
|
+
const parsed = parseFileMoveInput(input);
|
|
1404
|
+
const result = executeFileMove({ ...parsed, cwd: context.cwd });
|
|
1405
|
+
return formatFileMoveResult(result);
|
|
1406
|
+
},
|
|
1407
|
+
isReadOnly: () => false,
|
|
1408
|
+
isDestructive: () => true,
|
|
1409
|
+
isConcurrencySafe: () => false
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
name: "FileDelete",
|
|
1413
|
+
description: "Delete a file or directory with path safety checks.",
|
|
1414
|
+
category: "files",
|
|
1415
|
+
tags: ["file", "delete", "remove"],
|
|
1416
|
+
inputSchema: FileDeleteInputSchema,
|
|
1417
|
+
call: (input, context) => {
|
|
1418
|
+
const parsed = parseFileDeleteInput(input);
|
|
1419
|
+
const result = executeFileDelete({ ...parsed, cwd: context.cwd });
|
|
1420
|
+
return formatFileDeleteResult(result);
|
|
1421
|
+
},
|
|
1422
|
+
isReadOnly: () => false,
|
|
1423
|
+
isDestructive: () => true,
|
|
1424
|
+
isConcurrencySafe: () => false
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
name: "DirCreate",
|
|
1428
|
+
description: "Create a directory (recursive).",
|
|
1429
|
+
category: "files",
|
|
1430
|
+
tags: ["file", "directory", "create", "mkdir"],
|
|
1431
|
+
inputSchema: DirCreateInputSchema,
|
|
1432
|
+
call: (input, context) => {
|
|
1433
|
+
const parsed = parseDirCreateInput(input);
|
|
1434
|
+
const result = executeDirCreate({ ...parsed, cwd: context.cwd });
|
|
1435
|
+
return formatDirCreateResult(result);
|
|
1436
|
+
},
|
|
1437
|
+
isReadOnly: () => false,
|
|
1438
|
+
isDestructive: () => true,
|
|
1439
|
+
isConcurrencySafe: () => false
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
name: "DirList",
|
|
1443
|
+
description: "List directory contents with file sizes and modification dates.",
|
|
1444
|
+
category: "files",
|
|
1445
|
+
tags: ["file", "directory", "list", "ls"],
|
|
1446
|
+
inputSchema: DirListInputSchema,
|
|
1447
|
+
call: (input, context) => {
|
|
1448
|
+
const parsed = parseDirListInput(input);
|
|
1449
|
+
const result = executeDirList({ ...parsed, cwd: context.cwd });
|
|
1450
|
+
return formatDirListResult(result);
|
|
1451
|
+
},
|
|
1452
|
+
isReadOnly: () => true,
|
|
1453
|
+
isDestructive: () => false,
|
|
1454
|
+
isConcurrencySafe: () => true
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
name: "ProcessList",
|
|
1458
|
+
description: "List running processes sorted by CPU or memory usage.",
|
|
1459
|
+
category: "system",
|
|
1460
|
+
tags: ["process", "ps", "system"],
|
|
1461
|
+
inputSchema: ProcessListInputSchema,
|
|
1462
|
+
call: (input) => {
|
|
1463
|
+
const parsed = parseProcessListInput(input);
|
|
1464
|
+
const result = executeProcessList(parsed);
|
|
1465
|
+
return formatProcessListResult(result);
|
|
1466
|
+
},
|
|
1467
|
+
isReadOnly: () => true,
|
|
1468
|
+
isDestructive: () => false,
|
|
1469
|
+
isConcurrencySafe: () => true
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
name: "KillProcess",
|
|
1473
|
+
description: "Kill a process by PID or process name.",
|
|
1474
|
+
category: "system",
|
|
1475
|
+
tags: ["process", "kill", "signal"],
|
|
1476
|
+
inputSchema: KillProcessInputSchema,
|
|
1477
|
+
call: (input) => {
|
|
1478
|
+
const parsed = parseKillProcessInput(input);
|
|
1479
|
+
const result = executeKillProcess(parsed);
|
|
1480
|
+
return formatKillProcessResult(result);
|
|
1481
|
+
},
|
|
1482
|
+
isReadOnly: () => false,
|
|
1483
|
+
isDestructive: () => true,
|
|
1484
|
+
isConcurrencySafe: () => false
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
name: "Environment",
|
|
1488
|
+
description: "Show environment variables with optional filtering.",
|
|
1489
|
+
category: "system",
|
|
1490
|
+
tags: ["env", "environment"],
|
|
1491
|
+
inputSchema: EnvironmentInputSchema,
|
|
1492
|
+
call: (input) => {
|
|
1493
|
+
const parsed = parseEnvironmentInput(input);
|
|
1494
|
+
const result = executeEnvironment(parsed);
|
|
1495
|
+
return formatEnvironmentResult(result);
|
|
1496
|
+
},
|
|
1497
|
+
isReadOnly: () => true,
|
|
1498
|
+
isDestructive: () => false,
|
|
1499
|
+
isConcurrencySafe: () => true
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
name: "DiskUsage",
|
|
1503
|
+
description: "Show disk usage for a path.",
|
|
1504
|
+
category: "system",
|
|
1505
|
+
tags: ["disk", "df", "usage"],
|
|
1506
|
+
inputSchema: DiskUsageInputSchema,
|
|
1507
|
+
call: (input) => {
|
|
1508
|
+
const parsed = parseDiskUsageInput(input);
|
|
1509
|
+
const result = executeDiskUsage(parsed);
|
|
1510
|
+
return formatDiskUsageResult(result);
|
|
1511
|
+
},
|
|
1512
|
+
isReadOnly: () => true,
|
|
1513
|
+
isDestructive: () => false,
|
|
1514
|
+
isConcurrencySafe: () => true
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
name: "SystemInfo",
|
|
1518
|
+
description: "Show system information: hostname, OS, uptime, load, users, processes.",
|
|
1519
|
+
category: "system",
|
|
1520
|
+
tags: ["system", "uptime", "info"],
|
|
1521
|
+
inputSchema: SystemInfoInputSchema,
|
|
1522
|
+
call: () => {
|
|
1523
|
+
const result = executeSystemInfo();
|
|
1524
|
+
return formatSystemInfoResult(result);
|
|
1525
|
+
},
|
|
1526
|
+
isReadOnly: () => true,
|
|
1527
|
+
isDestructive: () => false,
|
|
1528
|
+
isConcurrencySafe: () => true
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
name: "HttpRequest",
|
|
1532
|
+
description: "Send an HTTP request (GET/POST/PUT/DELETE) with custom headers and body.",
|
|
1533
|
+
category: "web",
|
|
1534
|
+
tags: ["http", "api", "request"],
|
|
1535
|
+
inputSchema: HttpRequestInputSchema,
|
|
1536
|
+
call: async (input) => {
|
|
1537
|
+
const parsed = parseHttpRequestInput(input);
|
|
1538
|
+
const result = await executeHttpRequest(parsed);
|
|
1539
|
+
return formatHttpRequestResult(result);
|
|
1540
|
+
},
|
|
1541
|
+
isReadOnly: () => false,
|
|
1542
|
+
isDestructive: () => true,
|
|
1543
|
+
isConcurrencySafe: () => false
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
name: "JsonQuery",
|
|
1547
|
+
description: "Query/filter JSON data using simple path expressions (e.g., items[0].name).",
|
|
1548
|
+
category: "data",
|
|
1549
|
+
tags: ["json", "query", "parse"],
|
|
1550
|
+
inputSchema: JsonQueryInputSchema,
|
|
1551
|
+
call: (input) => {
|
|
1552
|
+
const parsed = parseJsonQueryInput(input);
|
|
1553
|
+
const result = executeJsonQuery(parsed);
|
|
1554
|
+
return formatJsonQueryResult(result);
|
|
1555
|
+
},
|
|
1556
|
+
isReadOnly: () => true,
|
|
1557
|
+
isDestructive: () => false,
|
|
1558
|
+
isConcurrencySafe: () => true
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
name: "ArchiveCreate",
|
|
1562
|
+
description: "Create a tar.gz or zip archive from files or directories.",
|
|
1563
|
+
category: "files",
|
|
1564
|
+
tags: ["archive", "tar", "zip", "compress"],
|
|
1565
|
+
inputSchema: ArchiveCreateInputSchema,
|
|
1566
|
+
call: (input, context) => {
|
|
1567
|
+
const parsed = parseArchiveCreateInput(input);
|
|
1568
|
+
const result = executeArchiveCreate({ ...parsed, cwd: context.cwd });
|
|
1569
|
+
return formatArchiveCreateResult(result);
|
|
1570
|
+
},
|
|
1571
|
+
isReadOnly: () => false,
|
|
1572
|
+
isDestructive: () => true,
|
|
1573
|
+
isConcurrencySafe: () => false
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
name: "ArchiveExtract",
|
|
1577
|
+
description: "Extract a tar.gz or zip archive to a directory.",
|
|
1578
|
+
category: "files",
|
|
1579
|
+
tags: ["archive", "tar", "zip", "extract"],
|
|
1580
|
+
inputSchema: ArchiveExtractInputSchema,
|
|
1581
|
+
call: (input, context) => {
|
|
1582
|
+
const parsed = parseArchiveExtractInput(input);
|
|
1583
|
+
const result = executeArchiveExtract({ ...parsed, cwd: context.cwd });
|
|
1584
|
+
return formatArchiveExtractResult(result);
|
|
1585
|
+
},
|
|
1586
|
+
isReadOnly: () => false,
|
|
1587
|
+
isDestructive: () => true,
|
|
1588
|
+
isConcurrencySafe: () => false
|
|
1589
|
+
},
|
|
1590
|
+
{
|
|
1591
|
+
name: "GitBranchDelete",
|
|
1592
|
+
description: "Delete a git branch (refuses if it is the current branch).",
|
|
1593
|
+
category: "git",
|
|
1594
|
+
tags: ["git", "branch", "delete"],
|
|
1595
|
+
inputSchema: GitBranchDeleteInputSchema,
|
|
1596
|
+
call: (input, context) => {
|
|
1597
|
+
const parsed = parseGitBranchDeleteInput(input);
|
|
1598
|
+
const result = executeGitBranchDelete({ ...parsed, cwd: context.cwd });
|
|
1599
|
+
return formatGitBranchDeleteResult(result);
|
|
1600
|
+
},
|
|
1601
|
+
isReadOnly: () => false,
|
|
1602
|
+
isDestructive: () => true,
|
|
1603
|
+
isConcurrencySafe: () => false
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
name: "GitStash",
|
|
1607
|
+
description: "Stash, pop, list, drop, or apply git stashes.",
|
|
1608
|
+
category: "git",
|
|
1609
|
+
tags: ["git", "stash"],
|
|
1610
|
+
inputSchema: GitStashInputSchema,
|
|
1611
|
+
call: (input, context) => {
|
|
1612
|
+
const parsed = parseGitStashInput(input);
|
|
1613
|
+
const result = executeGitStash({ ...parsed, cwd: context.cwd });
|
|
1614
|
+
return formatGitStashResult(result);
|
|
1615
|
+
},
|
|
1616
|
+
isReadOnly: (input) => input.action === "list",
|
|
1617
|
+
isDestructive: (input) => input.action !== "list",
|
|
1618
|
+
isConcurrencySafe: () => false
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
name: "GitReset",
|
|
1622
|
+
description: "Unstage files or hard-reset the working tree.",
|
|
1623
|
+
category: "git",
|
|
1624
|
+
tags: ["git", "reset", "unstage"],
|
|
1625
|
+
inputSchema: GitResetInputSchema,
|
|
1626
|
+
call: (input, context) => {
|
|
1627
|
+
const parsed = parseGitResetInput(input);
|
|
1628
|
+
const result = executeGitReset({ ...parsed, cwd: context.cwd });
|
|
1629
|
+
return formatGitResetResult(result);
|
|
1630
|
+
},
|
|
1631
|
+
isReadOnly: () => false,
|
|
1632
|
+
isDestructive: (input) => input.hard === true,
|
|
1633
|
+
isConcurrencySafe: () => false
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
name: "FileFind",
|
|
1637
|
+
description: "Find files by name pattern, size range, or modification time.",
|
|
1638
|
+
category: "search",
|
|
1639
|
+
tags: ["find", "file", "search"],
|
|
1640
|
+
inputSchema: FileFindInputSchema,
|
|
1641
|
+
call: (input, context) => {
|
|
1642
|
+
const parsed = parseFileFindInput(input);
|
|
1643
|
+
const result = executeFileFind({ ...parsed, cwd: context.cwd });
|
|
1644
|
+
return formatFileFindResult(result);
|
|
1645
|
+
},
|
|
1646
|
+
isReadOnly: () => true,
|
|
1647
|
+
isDestructive: () => false,
|
|
1648
|
+
isConcurrencySafe: () => true
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
name: "HeadTail",
|
|
1652
|
+
description: "Read the first or last N lines of a file.",
|
|
1653
|
+
category: "files",
|
|
1654
|
+
tags: ["file", "head", "tail", "read"],
|
|
1655
|
+
inputSchema: HeadTailInputSchema,
|
|
1656
|
+
call: (input, context) => {
|
|
1657
|
+
const parsed = parseHeadTailInput(input);
|
|
1658
|
+
const result = executeHeadTail({ ...parsed, cwd: context.cwd });
|
|
1659
|
+
return formatHeadTailResult(result);
|
|
1660
|
+
},
|
|
1661
|
+
isReadOnly: () => true,
|
|
1662
|
+
isDestructive: () => false,
|
|
1663
|
+
isConcurrencySafe: () => true
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
name: "TextStats",
|
|
1667
|
+
description: "Count lines, words, characters, and bytes in a file.",
|
|
1668
|
+
category: "files",
|
|
1669
|
+
tags: ["file", "count", "wc", "stats"],
|
|
1670
|
+
inputSchema: TextStatsInputSchema,
|
|
1671
|
+
call: (input, context) => {
|
|
1672
|
+
const parsed = parseTextStatsInput(input);
|
|
1673
|
+
const result = executeTextStats({ ...parsed, cwd: context.cwd });
|
|
1674
|
+
return formatTextStatsResult(result);
|
|
1675
|
+
},
|
|
1676
|
+
isReadOnly: () => true,
|
|
1677
|
+
isDestructive: () => false,
|
|
1678
|
+
isConcurrencySafe: () => true
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
name: "TreeView",
|
|
1682
|
+
description: "Show a directory tree of the workspace (depth-limited).",
|
|
1683
|
+
category: "search",
|
|
1684
|
+
tags: ["tree", "directory", "ls"],
|
|
1685
|
+
inputSchema: TreeViewInputSchema,
|
|
1686
|
+
call: (input, context) => {
|
|
1687
|
+
const parsed = parseTreeViewInput(input);
|
|
1688
|
+
const result = executeTreeView({ ...parsed, cwd: context.cwd });
|
|
1689
|
+
return formatTreeViewResult(result);
|
|
1690
|
+
},
|
|
1691
|
+
isReadOnly: () => true,
|
|
1692
|
+
isDestructive: () => false,
|
|
1693
|
+
isConcurrencySafe: () => true
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
name: "WhoAmI",
|
|
1697
|
+
description: "Show current user information (username, home, shell, groups).",
|
|
1698
|
+
category: "system",
|
|
1699
|
+
tags: ["user", "whoami"],
|
|
1700
|
+
inputSchema: WhoAmIInputSchema,
|
|
1701
|
+
call: () => {
|
|
1702
|
+
const result = executeWhoAmI();
|
|
1703
|
+
return formatWhoAmIResult(result);
|
|
1704
|
+
},
|
|
1705
|
+
isReadOnly: () => true,
|
|
1706
|
+
isDestructive: () => false,
|
|
1707
|
+
isConcurrencySafe: () => true
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
name: "NetworkCheck",
|
|
1711
|
+
description: "Check if a host is reachable via ping or TCP port check.",
|
|
1712
|
+
category: "system",
|
|
1713
|
+
tags: ["network", "ping", "connectivity"],
|
|
1714
|
+
inputSchema: NetworkCheckInputSchema,
|
|
1715
|
+
call: async (input) => {
|
|
1716
|
+
const parsed = parseNetworkCheckInput(input);
|
|
1717
|
+
const result = await executeNetworkCheck(parsed);
|
|
1718
|
+
return formatNetworkCheckResult(result);
|
|
1719
|
+
},
|
|
1720
|
+
isReadOnly: () => true,
|
|
1721
|
+
isDestructive: () => false,
|
|
1722
|
+
isConcurrencySafe: () => true
|
|
1723
|
+
},
|
|
1724
|
+
{
|
|
1725
|
+
name: "Base64",
|
|
1726
|
+
description: "Encode or decode base64 strings.",
|
|
1727
|
+
category: "data",
|
|
1728
|
+
tags: ["base64", "encode", "decode"],
|
|
1729
|
+
inputSchema: Base64InputSchema,
|
|
1730
|
+
call: (input) => {
|
|
1731
|
+
const parsed = parseBase64Input(input);
|
|
1732
|
+
const result = executeBase64(parsed);
|
|
1733
|
+
return formatBase64Result(result);
|
|
1734
|
+
},
|
|
1735
|
+
isReadOnly: () => true,
|
|
1736
|
+
isDestructive: () => false,
|
|
1737
|
+
isConcurrencySafe: () => true
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
name: "Which",
|
|
1741
|
+
description: "Locate an executable in the system PATH.",
|
|
1742
|
+
category: "system",
|
|
1743
|
+
tags: ["which", "path", "executable"],
|
|
1744
|
+
inputSchema: WhichInputSchema,
|
|
1745
|
+
call: (input) => {
|
|
1746
|
+
const parsed = parseWhichInput(input);
|
|
1747
|
+
const result = executeWhich(parsed);
|
|
1748
|
+
return formatWhichResult(result);
|
|
1749
|
+
},
|
|
1750
|
+
isReadOnly: () => true,
|
|
1751
|
+
isDestructive: () => false,
|
|
1752
|
+
isConcurrencySafe: () => true
|
|
1753
|
+
},
|
|
1754
|
+
{
|
|
1755
|
+
name: "Date",
|
|
1756
|
+
description: "Show current date/time in ISO, Unix, UTC, and local formats.",
|
|
1757
|
+
category: "system",
|
|
1758
|
+
tags: ["date", "time"],
|
|
1759
|
+
inputSchema: DateInputSchema,
|
|
1760
|
+
call: () => {
|
|
1761
|
+
const result = executeDate();
|
|
1762
|
+
return formatDateResult(result);
|
|
1763
|
+
},
|
|
1764
|
+
isReadOnly: () => true,
|
|
1765
|
+
isDestructive: () => false,
|
|
1766
|
+
isConcurrencySafe: () => true
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
name: "Agent",
|
|
1770
|
+
description: "Spawn a sub-agent to handle complex tasks autonomously, preserving the main context window.",
|
|
1771
|
+
category: "agent",
|
|
1772
|
+
tags: ["agent", "subagent", "parallel", "research"],
|
|
1773
|
+
inputSchema: AgentToolInputSchema,
|
|
1774
|
+
call: async (input, context) => {
|
|
1775
|
+
const parsed = parseAgentToolInput(input);
|
|
1776
|
+
if (!context.spawnSubAgent) {
|
|
1777
|
+
throw new Error("Agent tool requires a spawnSubAgent executor (not available in this context)");
|
|
1778
|
+
}
|
|
1779
|
+
const result = await context.spawnSubAgent({
|
|
1780
|
+
prompt: parsed.prompt,
|
|
1781
|
+
description: parsed.description,
|
|
1782
|
+
subagentType: parsed.subagentType ?? "general",
|
|
1783
|
+
runInBackground: parsed.runInBackground ?? false,
|
|
1784
|
+
target: parsed.target
|
|
1785
|
+
});
|
|
1786
|
+
return formatAgentToolResult({
|
|
1787
|
+
agentId: result.agentId,
|
|
1788
|
+
type: (parsed.subagentType ?? "general"),
|
|
1789
|
+
status: result.status,
|
|
1790
|
+
result: result.result,
|
|
1791
|
+
error: result.error
|
|
1792
|
+
});
|
|
1793
|
+
},
|
|
1794
|
+
isReadOnly: () => true,
|
|
1795
|
+
isDestructive: () => false,
|
|
1796
|
+
isConcurrencySafe: () => true
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
name: "ListPeers",
|
|
1800
|
+
description: "List all Magi peers reachable on the local network. Use this to discover targets for the Agent tool's `target` parameter when distributing work across multiple machines. Returns peer name, address, and status.",
|
|
1801
|
+
category: "agent",
|
|
1802
|
+
tags: ["agent", "peers", "swarm", "discovery"],
|
|
1803
|
+
inputSchema: {
|
|
1804
|
+
type: "object",
|
|
1805
|
+
additionalProperties: false,
|
|
1806
|
+
properties: {
|
|
1807
|
+
timeoutMs: {
|
|
1808
|
+
type: "integer",
|
|
1809
|
+
minimum: 500,
|
|
1810
|
+
maximum: 10000,
|
|
1811
|
+
description: "How long to wait for mDNS responses (default 2500ms)"
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
call: async (input) => {
|
|
1816
|
+
const timeoutMs = typeof input.timeoutMs === "number"
|
|
1817
|
+
? input.timeoutMs
|
|
1818
|
+
: 2500;
|
|
1819
|
+
// Discover via mDNS
|
|
1820
|
+
const { browseMdns } = await import("../control/mdns.js");
|
|
1821
|
+
const browser = browseMdns({});
|
|
1822
|
+
await new Promise(resolve => setTimeout(resolve, timeoutMs));
|
|
1823
|
+
const peers = browser.peers();
|
|
1824
|
+
browser.stop();
|
|
1825
|
+
// Also include saved peers (manually configured with credentials)
|
|
1826
|
+
// Note: we read from a stand-alone SessionStore; saved peers are stored as
|
|
1827
|
+
// mcp_oauth_tokens with serverName starting with "peer:".
|
|
1828
|
+
let savedPeers = [];
|
|
1829
|
+
try {
|
|
1830
|
+
const path = await import("node:path");
|
|
1831
|
+
const { existsSync } = await import("node:fs");
|
|
1832
|
+
const stateDir = process.env.MAGI_HOME
|
|
1833
|
+
? path.join(process.env.MAGI_HOME, "state")
|
|
1834
|
+
: path.join(process.env.HOME ?? "/tmp", ".magi-next", "state");
|
|
1835
|
+
const dbPath = path.join(stateDir, "sessions.sqlite");
|
|
1836
|
+
if (existsSync(dbPath)) {
|
|
1837
|
+
const mod = await import("../session-store.js");
|
|
1838
|
+
const store = new mod.SessionStore(dbPath);
|
|
1839
|
+
savedPeers = store.listMcpOAuthTokens()
|
|
1840
|
+
.filter(t => t.serverName.startsWith("peer:"))
|
|
1841
|
+
.map(t => ({
|
|
1842
|
+
name: t.serverName.replace(/^peer:/, ""),
|
|
1843
|
+
url: t.metadata?.peerUrl ?? t.authServerUrl ?? "?"
|
|
1844
|
+
}));
|
|
1845
|
+
store.close();
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
catch {
|
|
1849
|
+
// best effort
|
|
1850
|
+
}
|
|
1851
|
+
const lines = [];
|
|
1852
|
+
if (peers.length === 0 && savedPeers.length === 0) {
|
|
1853
|
+
return [
|
|
1854
|
+
"No peers found.",
|
|
1855
|
+
"",
|
|
1856
|
+
"To add a peer:",
|
|
1857
|
+
" 1. On the remote machine: `magi daemon start` and `magi pair`",
|
|
1858
|
+
" 2. On this machine: `magi peers add <name> <url> <device-id> <token>`",
|
|
1859
|
+
" 3. Use Agent({ target: <name>, ... }) to dispatch to it."
|
|
1860
|
+
].join("\n");
|
|
1861
|
+
}
|
|
1862
|
+
if (peers.length > 0) {
|
|
1863
|
+
lines.push(`Discovered ${peers.length} peer(s) via mDNS:`);
|
|
1864
|
+
for (const p of peers) {
|
|
1865
|
+
lines.push(` ${p.instanceName.padEnd(28)} ${p.address}:${p.port} ${p.hostname}`);
|
|
1866
|
+
if (p.txt && Object.keys(p.txt).length > 0) {
|
|
1867
|
+
lines.push(` info: ${Object.entries(p.txt).map(([k, v]) => `${k}=${v}`).join(", ")}`);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
if (savedPeers.length > 0) {
|
|
1872
|
+
if (lines.length > 0)
|
|
1873
|
+
lines.push("");
|
|
1874
|
+
lines.push(`${savedPeers.length} saved peer(s) with credentials (use these as Agent target):`);
|
|
1875
|
+
for (const p of savedPeers) {
|
|
1876
|
+
lines.push(` ${p.name.padEnd(28)} ${p.url}`);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
lines.push("");
|
|
1880
|
+
lines.push("To dispatch a sub-agent to a peer, call:");
|
|
1881
|
+
lines.push(" Agent({ target: <peer-name>, subagent_type: <type>, prompt: <task>, description: <short> })");
|
|
1882
|
+
lines.push("Multiple Agent calls in the same response run in parallel — split work across peers for speed.");
|
|
1883
|
+
return lines.join("\n");
|
|
1884
|
+
},
|
|
1885
|
+
isReadOnly: () => true,
|
|
1886
|
+
isDestructive: () => false,
|
|
1887
|
+
isConcurrencySafe: () => true
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
name: "DiscoverSkills",
|
|
1891
|
+
description: "List user-installed skills with their descriptions. Skills are reusable workflows; invoke them by replying with their name as a slash command (e.g. `/verify`) or calling the Skill tool with the skill name.",
|
|
1892
|
+
category: "agent",
|
|
1893
|
+
tags: ["skills", "discovery"],
|
|
1894
|
+
inputSchema: {
|
|
1895
|
+
type: "object",
|
|
1896
|
+
additionalProperties: false,
|
|
1897
|
+
properties: {}
|
|
1898
|
+
},
|
|
1899
|
+
call: async (_input, context) => {
|
|
1900
|
+
if (!context.stateRoot) {
|
|
1901
|
+
return "Skills require a configured Magi state root.";
|
|
1902
|
+
}
|
|
1903
|
+
const skillsRoot = path.join(path.dirname(context.stateRoot), "skills");
|
|
1904
|
+
const { listSkills } = await import("../skills/loader.js");
|
|
1905
|
+
const fakePaths = { skillsRoot };
|
|
1906
|
+
const skills = listSkills(fakePaths);
|
|
1907
|
+
if (skills.length === 0) {
|
|
1908
|
+
return [
|
|
1909
|
+
"No skills installed.",
|
|
1910
|
+
`Skills directory: ${skillsRoot}`,
|
|
1911
|
+
"Bundled skills (verify, debug, stuck) are auto-installed on first run."
|
|
1912
|
+
].join("\n");
|
|
1913
|
+
}
|
|
1914
|
+
const lines = ["Installed skills:", ""];
|
|
1915
|
+
for (const s of skills) {
|
|
1916
|
+
lines.push(` /${s.name.padEnd(20)} ${s.summary}`);
|
|
1917
|
+
}
|
|
1918
|
+
lines.push("");
|
|
1919
|
+
lines.push("To run a skill: reply with its name as a slash command, or call Skill({name: \"...\"}).");
|
|
1920
|
+
return lines.join("\n");
|
|
1921
|
+
},
|
|
1922
|
+
isReadOnly: () => true,
|
|
1923
|
+
isDestructive: () => false,
|
|
1924
|
+
isConcurrencySafe: () => true
|
|
1925
|
+
},
|
|
1926
|
+
{
|
|
1927
|
+
name: "CtxInspect",
|
|
1928
|
+
description: "Show the current session's context size: message count, approximate token usage, and the most recent message titles. Use to decide whether to compact the session.",
|
|
1929
|
+
category: "agent",
|
|
1930
|
+
tags: ["context", "introspection"],
|
|
1931
|
+
inputSchema: {
|
|
1932
|
+
type: "object",
|
|
1933
|
+
additionalProperties: false,
|
|
1934
|
+
properties: {}
|
|
1935
|
+
},
|
|
1936
|
+
call: async (_input, context) => {
|
|
1937
|
+
if (!context.sessionId || !context.stateRoot) {
|
|
1938
|
+
return "CtxInspect requires an active session.";
|
|
1939
|
+
}
|
|
1940
|
+
const dbPath = path.join(context.stateRoot, "sessions.sqlite");
|
|
1941
|
+
const { existsSync } = await import("node:fs");
|
|
1942
|
+
if (!existsSync(dbPath))
|
|
1943
|
+
return "No session database yet.";
|
|
1944
|
+
const mod = await import("../session-store.js");
|
|
1945
|
+
const store = new mod.SessionStore(dbPath);
|
|
1946
|
+
try {
|
|
1947
|
+
const session = store.getSession(context.sessionId);
|
|
1948
|
+
if (!session)
|
|
1949
|
+
return `Session not found: ${context.sessionId}`;
|
|
1950
|
+
let totalChars = 0;
|
|
1951
|
+
const counts = { user: 0, assistant: 0, tool: 0, system: 0 };
|
|
1952
|
+
for (const m of session.messages) {
|
|
1953
|
+
totalChars += m.content.length;
|
|
1954
|
+
counts[m.role] = (counts[m.role] ?? 0) + 1;
|
|
1955
|
+
}
|
|
1956
|
+
const tokens = Math.ceil(totalChars / 4);
|
|
1957
|
+
const lines = [
|
|
1958
|
+
`Session: ${session.id}`,
|
|
1959
|
+
`Title: ${session.title ?? "(untitled)"}`,
|
|
1960
|
+
`Messages: ${session.messages.length}`,
|
|
1961
|
+
` user=${counts.user ?? 0} assistant=${counts.assistant ?? 0} tool=${counts.tool ?? 0} system=${counts.system ?? 0}`,
|
|
1962
|
+
`Approx tokens: ~${tokens.toLocaleString()} (chars/4 estimate)`,
|
|
1963
|
+
""
|
|
1964
|
+
];
|
|
1965
|
+
const recent = session.messages.slice(-5);
|
|
1966
|
+
if (recent.length > 0) {
|
|
1967
|
+
lines.push("Most recent:");
|
|
1968
|
+
for (const m of recent) {
|
|
1969
|
+
const preview = m.content.replace(/\s+/g, " ").trim().slice(0, 80);
|
|
1970
|
+
lines.push(` [${m.role}] ${preview}${m.content.length > 80 ? "..." : ""}`);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
if (tokens > 100_000) {
|
|
1974
|
+
lines.push("");
|
|
1975
|
+
lines.push("Context is large. Consider /compact to summarize older messages.");
|
|
1976
|
+
}
|
|
1977
|
+
return lines.join("\n");
|
|
1978
|
+
}
|
|
1979
|
+
finally {
|
|
1980
|
+
store.close();
|
|
1981
|
+
}
|
|
1982
|
+
},
|
|
1983
|
+
isReadOnly: () => true,
|
|
1984
|
+
isDestructive: () => false,
|
|
1985
|
+
isConcurrencySafe: () => true
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
name: "VerifyPlanExecution",
|
|
1989
|
+
description: "Run the project's build and test commands and return a PASS/FAIL/PARTIAL verdict with evidence. Use after implementation work that touches multiple files. Detects npm/pnpm/yarn, cargo, go, mvn/gradle automatically.",
|
|
1990
|
+
category: "verification",
|
|
1991
|
+
tags: ["verify", "test", "build"],
|
|
1992
|
+
inputSchema: {
|
|
1993
|
+
type: "object",
|
|
1994
|
+
additionalProperties: false,
|
|
1995
|
+
properties: {
|
|
1996
|
+
skipTests: { type: "boolean", description: "Skip test step (build only). Default false." },
|
|
1997
|
+
skipLint: { type: "boolean", description: "Skip lint step. Default false." }
|
|
1998
|
+
}
|
|
1999
|
+
},
|
|
2000
|
+
call: async (input, context) => {
|
|
2001
|
+
const { spawnSync } = await import("node:child_process");
|
|
2002
|
+
const { existsSync } = await import("node:fs");
|
|
2003
|
+
const cwd = context.cwd;
|
|
2004
|
+
const opts = input;
|
|
2005
|
+
const skipTests = opts.skipTests === true;
|
|
2006
|
+
const skipLint = opts.skipLint === true;
|
|
2007
|
+
function detectStack() {
|
|
2008
|
+
if (existsSync(path.join(cwd, "package.json"))) {
|
|
2009
|
+
const pm = existsSync(path.join(cwd, "pnpm-lock.yaml")) ? "pnpm"
|
|
2010
|
+
: existsSync(path.join(cwd, "yarn.lock")) ? "yarn"
|
|
2011
|
+
: "npm";
|
|
2012
|
+
return {
|
|
2013
|
+
build: [pm, "run", "build"],
|
|
2014
|
+
test: [pm, "test", "--", "--run"],
|
|
2015
|
+
lint: [pm, "run", "lint"]
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
if (existsSync(path.join(cwd, "Cargo.toml")))
|
|
2019
|
+
return { build: ["cargo", "build"], test: ["cargo", "test"], lint: ["cargo", "clippy"] };
|
|
2020
|
+
if (existsSync(path.join(cwd, "go.mod")))
|
|
2021
|
+
return { build: ["go", "build", "./..."], test: ["go", "test", "./..."], lint: ["go", "vet", "./..."] };
|
|
2022
|
+
if (existsSync(path.join(cwd, "pom.xml")))
|
|
2023
|
+
return { build: ["mvn", "compile"], test: ["mvn", "test"] };
|
|
2024
|
+
return undefined;
|
|
2025
|
+
}
|
|
2026
|
+
const stack = detectStack();
|
|
2027
|
+
if (!stack) {
|
|
2028
|
+
return [
|
|
2029
|
+
"VERDICT: PARTIAL",
|
|
2030
|
+
"EVIDENCE: no build system detected",
|
|
2031
|
+
` Looked in: ${cwd}`,
|
|
2032
|
+
` Did not find: package.json, Cargo.toml, go.mod, pom.xml`,
|
|
2033
|
+
"Run the relevant build/test commands manually to verify."
|
|
2034
|
+
].join("\n");
|
|
2035
|
+
}
|
|
2036
|
+
function run(label, argv) {
|
|
2037
|
+
const r = spawnSync(argv[0], argv.slice(1), { cwd, encoding: "utf8", env: { ...process.env, CI: "1" }, timeout: 5 * 60 * 1000 });
|
|
2038
|
+
const stdout = (r.stdout ?? "").toString();
|
|
2039
|
+
const stderr = (r.stderr ?? "").toString();
|
|
2040
|
+
const tail = (stdout + stderr).split("\n").slice(-15).join("\n");
|
|
2041
|
+
return { ok: r.status === 0, output: `${label}: ${argv.join(" ")}\n${tail}` };
|
|
2042
|
+
}
|
|
2043
|
+
const evidence = [];
|
|
2044
|
+
const issues = [];
|
|
2045
|
+
let allPassed = true;
|
|
2046
|
+
let anyRun = false;
|
|
2047
|
+
const buildResult = run("BUILD", stack.build);
|
|
2048
|
+
anyRun = true;
|
|
2049
|
+
evidence.push(buildResult.output);
|
|
2050
|
+
if (!buildResult.ok) {
|
|
2051
|
+
allPassed = false;
|
|
2052
|
+
issues.push("build failed");
|
|
2053
|
+
}
|
|
2054
|
+
if (!skipTests) {
|
|
2055
|
+
const testResult = run("TEST", stack.test);
|
|
2056
|
+
anyRun = true;
|
|
2057
|
+
evidence.push(testResult.output);
|
|
2058
|
+
if (!testResult.ok) {
|
|
2059
|
+
allPassed = false;
|
|
2060
|
+
issues.push("tests failed");
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
if (!skipLint && stack.lint) {
|
|
2064
|
+
const lintResult = run("LINT", stack.lint);
|
|
2065
|
+
anyRun = true;
|
|
2066
|
+
evidence.push(lintResult.output);
|
|
2067
|
+
if (!lintResult.ok) {
|
|
2068
|
+
allPassed = false;
|
|
2069
|
+
issues.push("lint failed");
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
const verdict = !anyRun ? "PARTIAL" : allPassed ? "PASS" : "FAIL";
|
|
2073
|
+
const lines = [`VERDICT: ${verdict}`, "", "EVIDENCE:"];
|
|
2074
|
+
for (const e of evidence) {
|
|
2075
|
+
lines.push("---");
|
|
2076
|
+
lines.push(e);
|
|
2077
|
+
}
|
|
2078
|
+
if (issues.length > 0) {
|
|
2079
|
+
lines.push("");
|
|
2080
|
+
lines.push("ISSUES:");
|
|
2081
|
+
for (const i of issues)
|
|
2082
|
+
lines.push(` - ${i}`);
|
|
2083
|
+
}
|
|
2084
|
+
return lines.join("\n");
|
|
2085
|
+
},
|
|
2086
|
+
isReadOnly: () => false,
|
|
2087
|
+
isDestructive: () => false,
|
|
2088
|
+
isConcurrencySafe: () => false
|
|
2089
|
+
},
|
|
2090
|
+
{
|
|
2091
|
+
name: "Browser",
|
|
2092
|
+
description: "Control a real Chromium browser. Actions: navigate, click, type, scroll, screenshot, extract_text, wait, evaluate, close. Use navigate to open URLs, click to interact, screenshot to see the page (vision), evaluate to run JS. Browser stays open between calls so you can do multiple actions in sequence.",
|
|
2093
|
+
category: "web",
|
|
2094
|
+
tags: ["browser", "web", "automation", "playwright"],
|
|
2095
|
+
inputSchema: BrowserActionInputSchema,
|
|
2096
|
+
call: async (input) => {
|
|
2097
|
+
const result = await executeBrowserAction(input);
|
|
2098
|
+
return formatBrowserActionResult(result);
|
|
2099
|
+
},
|
|
2100
|
+
// Read-only actions (navigate, scroll, screenshot, extract_text, wait,
|
|
2101
|
+
// close) don't modify any third-party state. Write actions (click, type,
|
|
2102
|
+
// evaluate) might submit forms / post comments / run arbitrary JS — those
|
|
2103
|
+
// need approval.
|
|
2104
|
+
isReadOnly: (input) => {
|
|
2105
|
+
const action = input.action;
|
|
2106
|
+
return action === "navigate" || action === "scroll" || action === "screenshot"
|
|
2107
|
+
|| action === "extract_text" || action === "wait" || action === "close";
|
|
2108
|
+
},
|
|
2109
|
+
isDestructive: () => false,
|
|
2110
|
+
isConcurrencySafe: () => false
|
|
2111
|
+
}
|
|
2112
|
+
];
|
|
2113
|
+
function matchRules(toolUse, rules) {
|
|
2114
|
+
for (const [decision, list] of [
|
|
2115
|
+
["deny", rules?.deny ?? []],
|
|
2116
|
+
["ask", rules?.ask ?? []],
|
|
2117
|
+
["allow", rules?.allow ?? []]
|
|
2118
|
+
]) {
|
|
2119
|
+
const rule = list.find((item) => ruleMatches(item, toolUse));
|
|
2120
|
+
if (rule) {
|
|
2121
|
+
return { decision, reason: `matched rule ${rule}` };
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return undefined;
|
|
2125
|
+
}
|
|
2126
|
+
function ruleMatches(rule, toolUse) {
|
|
2127
|
+
const parsed = /^([A-Za-z0-9_]+)\((.*)\)$/.exec(rule.trim());
|
|
2128
|
+
if (!parsed || parsed[1] !== toolUse.name) {
|
|
2129
|
+
return false;
|
|
2130
|
+
}
|
|
2131
|
+
const selector = parsed[2];
|
|
2132
|
+
if (selector === "*") {
|
|
2133
|
+
return true;
|
|
2134
|
+
}
|
|
2135
|
+
const haystack = String(toolUse.input.command
|
|
2136
|
+
?? toolUse.input.file_path
|
|
2137
|
+
?? toolUse.input.pattern
|
|
2138
|
+
?? toolUse.input.url
|
|
2139
|
+
?? "");
|
|
2140
|
+
return globPattern(selector, haystack);
|
|
2141
|
+
}
|
|
2142
|
+
function globPattern(pattern, value) {
|
|
2143
|
+
const regex = new RegExp(`^${pattern.split("*").map(escapeRegExp).join(".*")}$`);
|
|
2144
|
+
return regex.test(value);
|
|
2145
|
+
}
|
|
2146
|
+
function objectSchema(properties, required) {
|
|
2147
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
2148
|
+
}
|
|
2149
|
+
function errorResult(toolUse, content, permission) {
|
|
2150
|
+
return { toolCallId: toolUse.id, toolName: toolUse.name, content, isError: true, permission };
|
|
2151
|
+
}
|
|
2152
|
+
function readString(input, name) {
|
|
2153
|
+
const value = input[name];
|
|
2154
|
+
if (typeof value !== "string") {
|
|
2155
|
+
throw new Error(`Tool input ${name} must be a string`);
|
|
2156
|
+
}
|
|
2157
|
+
return value;
|
|
2158
|
+
}
|
|
2159
|
+
function readOptionalString(input, name) {
|
|
2160
|
+
const value = input[name];
|
|
2161
|
+
if (value === undefined)
|
|
2162
|
+
return undefined;
|
|
2163
|
+
if (typeof value !== "string")
|
|
2164
|
+
throw new Error(`Tool input ${name} must be a string`);
|
|
2165
|
+
return value;
|
|
2166
|
+
}
|
|
2167
|
+
function readOptionalNumber(input, name) {
|
|
2168
|
+
const value = input[name];
|
|
2169
|
+
if (value === undefined)
|
|
2170
|
+
return undefined;
|
|
2171
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2172
|
+
throw new Error(`Tool input ${name} must be a number`);
|
|
2173
|
+
}
|
|
2174
|
+
return value;
|
|
2175
|
+
}
|
|
2176
|
+
function readOptionalBoolean(input, name) {
|
|
2177
|
+
const value = input[name];
|
|
2178
|
+
if (value === undefined)
|
|
2179
|
+
return undefined;
|
|
2180
|
+
if (typeof value !== "boolean")
|
|
2181
|
+
throw new Error(`Tool input ${name} must be a boolean`);
|
|
2182
|
+
return value;
|
|
2183
|
+
}
|
|
2184
|
+
function readStringArray(input, name) {
|
|
2185
|
+
const value = input[name];
|
|
2186
|
+
if (!Array.isArray(value)) {
|
|
2187
|
+
throw new Error(`Tool input ${name} must be an array`);
|
|
2188
|
+
}
|
|
2189
|
+
if (value.length < 1 || value.length > 100) {
|
|
2190
|
+
throw new Error(`Tool input ${name} must contain 1 to 100 strings`);
|
|
2191
|
+
}
|
|
2192
|
+
return value.map((item, index) => {
|
|
2193
|
+
if (typeof item !== "string") {
|
|
2194
|
+
throw new Error(`Tool input ${name}.${index} must be a string`);
|
|
2195
|
+
}
|
|
2196
|
+
return item;
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
function readGitUntracked(input, name) {
|
|
2200
|
+
const value = input[name];
|
|
2201
|
+
if (value === undefined)
|
|
2202
|
+
return undefined;
|
|
2203
|
+
if (value === "all" || value === "normal" || value === "none") {
|
|
2204
|
+
return value;
|
|
2205
|
+
}
|
|
2206
|
+
throw new Error(`Tool input ${name} must be all, normal, or none`);
|
|
2207
|
+
}
|
|
2208
|
+
function readGitStageMode(input, name) {
|
|
2209
|
+
const value = input[name];
|
|
2210
|
+
if (value === undefined)
|
|
2211
|
+
return undefined;
|
|
2212
|
+
if (value === "stage" || value === "unstage") {
|
|
2213
|
+
return value;
|
|
2214
|
+
}
|
|
2215
|
+
throw new Error(`Tool input ${name} must be stage or unstage`);
|
|
2216
|
+
}
|
|
2217
|
+
function readOutputMode(input, name) {
|
|
2218
|
+
const value = input[name];
|
|
2219
|
+
if (value === undefined)
|
|
2220
|
+
return "content";
|
|
2221
|
+
if (value === "content" || value === "files_with_matches" || value === "count") {
|
|
2222
|
+
return value;
|
|
2223
|
+
}
|
|
2224
|
+
throw new Error(`Tool input ${name} must be content, files_with_matches, or count`);
|
|
2225
|
+
}
|
|
2226
|
+
function escapeRegExp(value) {
|
|
2227
|
+
return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
2228
|
+
}
|
|
2229
|
+
function requireStateFile(context) {
|
|
2230
|
+
if (!context.stateRoot) {
|
|
2231
|
+
throw new Error("Cron tools require Magi stateRoot");
|
|
2232
|
+
}
|
|
2233
|
+
return cronStorePathFromRoot(context.stateRoot);
|
|
2234
|
+
}
|
|
2235
|
+
function requireTodoContext(context) {
|
|
2236
|
+
if (!context.stateRoot) {
|
|
2237
|
+
throw new Error("TodoWrite requires Magi stateRoot");
|
|
2238
|
+
}
|
|
2239
|
+
if (!context.sessionId) {
|
|
2240
|
+
throw new Error("TodoWrite requires a Magi sessionId");
|
|
2241
|
+
}
|
|
2242
|
+
return {
|
|
2243
|
+
stateRoot: context.stateRoot,
|
|
2244
|
+
sessionId: context.sessionId
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
function requireConfigFile(context) {
|
|
2248
|
+
if (!context.stateRoot) {
|
|
2249
|
+
throw new Error("Config requires Magi stateRoot");
|
|
2250
|
+
}
|
|
2251
|
+
return path.join(path.dirname(context.stateRoot), "config.yaml");
|
|
2252
|
+
}
|
|
2253
|
+
function requireSkillsRoot(context) {
|
|
2254
|
+
if (!context.stateRoot) {
|
|
2255
|
+
throw new Error("Skill requires Magi stateRoot");
|
|
2256
|
+
}
|
|
2257
|
+
return path.join(path.dirname(context.stateRoot), "skills");
|
|
2258
|
+
}
|
|
2259
|
+
function isValidMemdirType(value) {
|
|
2260
|
+
return value === "user" || value === "feedback" || value === "project" || value === "reference";
|
|
2261
|
+
}
|
|
2262
|
+
//# sourceMappingURL=registry.js.map
|