@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,887 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { runHeadlessPrompt } from "../headless.js";
|
|
4
|
+
import { createPairingToken, validateDeviceToken } from "./auth.js";
|
|
5
|
+
import { advertiseMdns, getLocalHostname } from "./mdns.js";
|
|
6
|
+
import { ActiveInteractionCancelledError, ActiveInteractionNotFoundError, ActiveInteractionRegistry, ActiveInteractionStateError } from "../interactions.js";
|
|
7
|
+
import { cancelAgentTask, completeAgentTask, spawnAgentTask, startAgentTask, waitAgentTask } from "../agents/task-queue.js";
|
|
8
|
+
import { listLocalPlugins } from "../plugins/manifest.js";
|
|
9
|
+
import { discoverLocalMarketplaceSources, loadMarketplace } from "../plugins/marketplace.js";
|
|
10
|
+
import { listSkills } from "../skills/loader.js";
|
|
11
|
+
import { openApiDocument, renderPanelClient, renderWebPanel } from "../web/panel.js";
|
|
12
|
+
import { triggerHooks } from "../hooks/events.js";
|
|
13
|
+
import { cronStorePathFromRoot, takeDueCronJobs } from "../tools/cron.js";
|
|
14
|
+
import { toEventView } from "../events.js";
|
|
15
|
+
import { normalizeAskUserQuestionAnswer } from "../tools/user-question.js";
|
|
16
|
+
export async function startControlServer(input) {
|
|
17
|
+
const interactions = new ActiveInteractionRegistry({
|
|
18
|
+
timeoutMs: parseInteractionTimeoutMs(input.env?.MAGI_INTERACTION_TIMEOUT_MS)
|
|
19
|
+
});
|
|
20
|
+
const runningJobs = new Map();
|
|
21
|
+
const server = http.createServer((request, response) => {
|
|
22
|
+
void handleRequest({ ...input, interactions, runningJobs, request, response });
|
|
23
|
+
});
|
|
24
|
+
await new Promise((resolve, reject) => {
|
|
25
|
+
server.once("error", (err) => {
|
|
26
|
+
if (err.code === "EADDRINUSE") {
|
|
27
|
+
reject(new Error([
|
|
28
|
+
`Cannot start control server: port ${input.runtime.controlPort} is already in use on ${input.runtime.controlBind}.`,
|
|
29
|
+
``,
|
|
30
|
+
`Common fixes:`,
|
|
31
|
+
` - 'magi daemon status' — see if Magi is already running`,
|
|
32
|
+
` - 'lsof -i :${input.runtime.controlPort}' — find what's using the port`,
|
|
33
|
+
` - 'MAGI_CONTROL_PORT=8780 magi serve' — pick a different port`
|
|
34
|
+
].join("\n")));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (err.code === "EACCES") {
|
|
38
|
+
reject(new Error(`Cannot bind to ${input.runtime.controlBind}:${input.runtime.controlPort} — permission denied. Pick a port above 1024 or run with elevated privileges.`));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
reject(err);
|
|
42
|
+
});
|
|
43
|
+
server.listen(input.runtime.controlPort, input.runtime.controlBind, () => resolve());
|
|
44
|
+
});
|
|
45
|
+
const address = server.address();
|
|
46
|
+
const cronRunner = startCronRunner(input);
|
|
47
|
+
// Advertise this daemon via mDNS so phones and other Magi instances can discover it.
|
|
48
|
+
let mdnsHandle;
|
|
49
|
+
if (input.env?.MAGI_DISABLE_MDNS !== "1") {
|
|
50
|
+
try {
|
|
51
|
+
const hostname = getLocalHostname();
|
|
52
|
+
const instanceName = `magi-${address.port}-${process.pid}`;
|
|
53
|
+
mdnsHandle = advertiseMdns({
|
|
54
|
+
hostname,
|
|
55
|
+
instanceName,
|
|
56
|
+
port: address.port,
|
|
57
|
+
txt: {
|
|
58
|
+
version: "0.1.0-alpha.0",
|
|
59
|
+
cwd: input.cwd,
|
|
60
|
+
bind: input.runtime.controlBind
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (input.env?.MAGI_DEBUG_MDNS === "1") {
|
|
64
|
+
process.stdout.write(`[mdns] Advertising ${instanceName} on _magi._tcp.local. (port ${address.port})\n`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (input.env?.MAGI_DEBUG_MDNS === "1") {
|
|
69
|
+
process.stdout.write(`[mdns] Failed to advertise: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
server,
|
|
75
|
+
url: `http://${input.runtime.controlBind}:${address.port}`,
|
|
76
|
+
interactions,
|
|
77
|
+
close: async () => {
|
|
78
|
+
mdnsHandle?.stop();
|
|
79
|
+
cronRunner.close();
|
|
80
|
+
for (const running of runningJobs.values()) {
|
|
81
|
+
running.controller.abort("control server closing");
|
|
82
|
+
}
|
|
83
|
+
await Promise.allSettled([...runningJobs.values()].map((running) => running.promise));
|
|
84
|
+
interactions.close();
|
|
85
|
+
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function runDueCronJobs(input) {
|
|
90
|
+
const due = takeDueCronJobs(cronStorePathFromRoot(input.paths.stateRoot), input.now ?? new Date());
|
|
91
|
+
return Promise.all(due.map(async ({ job, prompt }) => {
|
|
92
|
+
const result = await runHeadlessPrompt({
|
|
93
|
+
prompt,
|
|
94
|
+
cwd: input.cwd,
|
|
95
|
+
store: input.store,
|
|
96
|
+
config: input.config,
|
|
97
|
+
env: input.env,
|
|
98
|
+
paths: input.paths,
|
|
99
|
+
stateRoot: input.paths.stateRoot,
|
|
100
|
+
modelAlias: "main",
|
|
101
|
+
sessionName: `cron ${job.id}`
|
|
102
|
+
});
|
|
103
|
+
input.store.recordAudit({
|
|
104
|
+
sessionId: result.sessionId,
|
|
105
|
+
jobId: result.jobId,
|
|
106
|
+
action: "cron.job.executed",
|
|
107
|
+
target: job.id,
|
|
108
|
+
metadata: {
|
|
109
|
+
cron: job.cron,
|
|
110
|
+
prompt,
|
|
111
|
+
recurring: job.recurring,
|
|
112
|
+
nextRunAt: job.nextRunAt
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return { cronJob: job, result };
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
function startCronRunner(input) {
|
|
119
|
+
const intervalMs = parseCronIntervalMs(input.env?.MAGI_CRON_POLL_MS);
|
|
120
|
+
let running = false;
|
|
121
|
+
const tick = async () => {
|
|
122
|
+
if (running) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
running = true;
|
|
126
|
+
try {
|
|
127
|
+
await runDueCronJobs(input);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const sessionId = input.store.createSession({
|
|
131
|
+
title: "cron runner error",
|
|
132
|
+
cwd: input.cwd,
|
|
133
|
+
metadata: { source: "cron-runner" }
|
|
134
|
+
});
|
|
135
|
+
input.store.recordAudit({
|
|
136
|
+
sessionId,
|
|
137
|
+
action: "cron.runner.failed",
|
|
138
|
+
metadata: { error: error instanceof Error ? error.message : String(error) }
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
running = false;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const timer = setInterval(() => {
|
|
146
|
+
void tick();
|
|
147
|
+
}, intervalMs);
|
|
148
|
+
timer.unref?.();
|
|
149
|
+
void tick();
|
|
150
|
+
return {
|
|
151
|
+
close: () => clearInterval(timer)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function parseCronIntervalMs(raw) {
|
|
155
|
+
if (!raw) {
|
|
156
|
+
return 60_000;
|
|
157
|
+
}
|
|
158
|
+
if (!/^\d+$/.test(raw)) {
|
|
159
|
+
throw new Error(`MAGI_CRON_POLL_MS must be an integer >= 1000, got ${JSON.stringify(raw)}`);
|
|
160
|
+
}
|
|
161
|
+
const interval = Number(raw);
|
|
162
|
+
if (!Number.isInteger(interval) || interval < 1000) {
|
|
163
|
+
throw new Error(`MAGI_CRON_POLL_MS must be an integer >= 1000, got ${JSON.stringify(raw)}`);
|
|
164
|
+
}
|
|
165
|
+
return interval;
|
|
166
|
+
}
|
|
167
|
+
function parseInteractionTimeoutMs(raw) {
|
|
168
|
+
if (!raw) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
if (!/^\d+$/.test(raw)) {
|
|
172
|
+
throw new Error(`MAGI_INTERACTION_TIMEOUT_MS must be an integer >= 1, got ${JSON.stringify(raw)}`);
|
|
173
|
+
}
|
|
174
|
+
const timeout = Number(raw);
|
|
175
|
+
if (!Number.isInteger(timeout) || timeout < 1) {
|
|
176
|
+
throw new Error(`MAGI_INTERACTION_TIMEOUT_MS must be an integer >= 1, got ${JSON.stringify(raw)}`);
|
|
177
|
+
}
|
|
178
|
+
return timeout;
|
|
179
|
+
}
|
|
180
|
+
async function handleRequest(input) {
|
|
181
|
+
try {
|
|
182
|
+
const url = new URL(input.request.url ?? "/", `http://${input.request.headers.host ?? "127.0.0.1"}`);
|
|
183
|
+
if (input.request.method === "GET" && url.pathname === "/health") {
|
|
184
|
+
return sendJson(input.response, 200, {
|
|
185
|
+
ok: true,
|
|
186
|
+
root: input.paths.root,
|
|
187
|
+
control: { bind: input.runtime.controlBind, port: input.runtime.controlPort }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (input.request.method === "GET" && url.pathname === "/panel") {
|
|
191
|
+
return sendText(input.response, 200, "text/html; charset=utf-8", renderWebPanel());
|
|
192
|
+
}
|
|
193
|
+
if (input.request.method === "GET" && url.pathname === "/panel-client.js") {
|
|
194
|
+
return sendText(input.response, 200, "text/javascript; charset=utf-8", renderPanelClient());
|
|
195
|
+
}
|
|
196
|
+
if (input.request.method === "GET" && url.pathname === "/openapi.json") {
|
|
197
|
+
return sendJson(input.response, 200, openApiDocument());
|
|
198
|
+
}
|
|
199
|
+
if (input.request.method === "POST" && url.pathname === "/pairing") {
|
|
200
|
+
// Pairing endpoint: only allow from loopback OR with an existing valid device token.
|
|
201
|
+
// This prevents an attacker on the network from minting tokens for themselves.
|
|
202
|
+
if (!isLoopbackRequest(input.request) && !isAuthorized(input.request, input.store)) {
|
|
203
|
+
return sendJson(input.response, 403, {
|
|
204
|
+
error: "forbidden",
|
|
205
|
+
message: "Pairing must be initiated from the local machine. Run 'magi pair' on the daemon host."
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
const body = await readJson(input.request);
|
|
209
|
+
const token = createPairingToken({
|
|
210
|
+
store: input.store,
|
|
211
|
+
deviceName: typeof body.name === "string" ? body.name : "unnamed device"
|
|
212
|
+
});
|
|
213
|
+
input.store.recordAudit({
|
|
214
|
+
sessionId: input.store.createSession({ title: "control pairing", cwd: input.cwd, metadata: { command: "pairing" } }),
|
|
215
|
+
action: "control.pairing.created",
|
|
216
|
+
target: token.deviceId,
|
|
217
|
+
metadata: { expiresAt: token.expiresAt }
|
|
218
|
+
});
|
|
219
|
+
return sendJson(input.response, 200, token);
|
|
220
|
+
}
|
|
221
|
+
if (!isAuthorized(input.request, input.store)) {
|
|
222
|
+
return sendJson(input.response, 401, { error: "unauthorized" });
|
|
223
|
+
}
|
|
224
|
+
if (input.request.method === "GET" && url.pathname === "/sessions") {
|
|
225
|
+
return sendJson(input.response, 200, { sessions: input.store.listSessions(50) });
|
|
226
|
+
}
|
|
227
|
+
if (input.request.method === "POST" && url.pathname === "/sessions") {
|
|
228
|
+
const body = await readJson(input.request);
|
|
229
|
+
const sessionId = input.store.createSession({
|
|
230
|
+
id: readOptionalString(body.id),
|
|
231
|
+
title: readOptionalString(body.title),
|
|
232
|
+
cwd: readOptionalString(body.cwd) ?? input.cwd,
|
|
233
|
+
metadata: readOptionalRecord(body.metadata)
|
|
234
|
+
});
|
|
235
|
+
return sendJson(input.response, 200, { session: input.store.getSession(sessionId) });
|
|
236
|
+
}
|
|
237
|
+
const sessionEventsRoute = /^\/sessions\/([^/]+)\/events$/.exec(url.pathname);
|
|
238
|
+
if (sessionEventsRoute && input.request.method === "GET") {
|
|
239
|
+
const sessionId = decodeURIComponent(sessionEventsRoute[1]);
|
|
240
|
+
const session = input.store.getSession(sessionId);
|
|
241
|
+
if (!session) {
|
|
242
|
+
return sendJson(input.response, 404, { error: "session not found" });
|
|
243
|
+
}
|
|
244
|
+
const limit = readLimit(url.searchParams.get("limit"), 100);
|
|
245
|
+
return sendJson(input.response, 200, {
|
|
246
|
+
events: input.store.listSessionAuditEvents(sessionId, limit).map(toEventView)
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const sessionRoute = /^\/sessions\/([^/]+)(?:\/messages)?$/.exec(url.pathname);
|
|
250
|
+
if (sessionRoute) {
|
|
251
|
+
const sessionId = decodeURIComponent(sessionRoute[1]);
|
|
252
|
+
const isMessagesRoute = url.pathname.endsWith("/messages");
|
|
253
|
+
const session = input.store.getSession(sessionId);
|
|
254
|
+
if (!session) {
|
|
255
|
+
return sendJson(input.response, 404, { error: "session not found" });
|
|
256
|
+
}
|
|
257
|
+
if (input.request.method === "GET" && !isMessagesRoute) {
|
|
258
|
+
return sendJson(input.response, 200, { session });
|
|
259
|
+
}
|
|
260
|
+
if (input.request.method === "POST" && isMessagesRoute) {
|
|
261
|
+
const body = await readJson(input.request);
|
|
262
|
+
if (typeof body.prompt !== "string" || !body.prompt.trim()) {
|
|
263
|
+
return sendJson(input.response, 400, { error: "prompt is required" });
|
|
264
|
+
}
|
|
265
|
+
const result = await runControlJob(input, body, { sessionId, cwd: session.cwd });
|
|
266
|
+
return sendJson(input.response, 200, result);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (input.request.method === "GET" && url.pathname === "/jobs") {
|
|
270
|
+
return sendJson(input.response, 200, { jobs: input.store.listJobs(50) });
|
|
271
|
+
}
|
|
272
|
+
const jobEventsRoute = /^\/jobs\/([^/]+)\/events$/.exec(url.pathname);
|
|
273
|
+
if (jobEventsRoute && input.request.method === "GET") {
|
|
274
|
+
const jobId = decodeURIComponent(jobEventsRoute[1]);
|
|
275
|
+
const job = input.store.getJob(jobId);
|
|
276
|
+
if (!job) {
|
|
277
|
+
return sendJson(input.response, 404, { error: "job not found" });
|
|
278
|
+
}
|
|
279
|
+
const limit = readLimit(url.searchParams.get("limit"), 100);
|
|
280
|
+
return sendJson(input.response, 200, {
|
|
281
|
+
events: input.store.listJobAuditEvents(jobId, limit).map(toEventView)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const jobApprovalRoute = /^\/jobs\/([^/]+)\/approvals\/([^/]+)$/.exec(url.pathname);
|
|
285
|
+
if (jobApprovalRoute && input.request.method === "POST") {
|
|
286
|
+
const jobId = decodeURIComponent(jobApprovalRoute[1]);
|
|
287
|
+
const toolUseId = decodeURIComponent(jobApprovalRoute[2]);
|
|
288
|
+
const body = await readJson(input.request);
|
|
289
|
+
const job = input.store.getJob(jobId);
|
|
290
|
+
if (!job) {
|
|
291
|
+
return sendJson(input.response, 404, { error: "job not found" });
|
|
292
|
+
}
|
|
293
|
+
const decision = readApprovalDecision(body);
|
|
294
|
+
if (decision === undefined) {
|
|
295
|
+
return sendJson(input.response, 400, { error: "decision must be approve, deny, approved, denied, true, or false" });
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const interaction = input.interactions.resolveApproval({ jobId, toolUseId, approved: decision });
|
|
299
|
+
input.store.recordAudit({
|
|
300
|
+
sessionId: job.sessionId,
|
|
301
|
+
jobId,
|
|
302
|
+
action: "control.approval.resolved",
|
|
303
|
+
target: toolUseId,
|
|
304
|
+
metadata: {
|
|
305
|
+
status: "resolved",
|
|
306
|
+
interactionKind: "approval",
|
|
307
|
+
toolUseId,
|
|
308
|
+
approved: decision,
|
|
309
|
+
responder: readOptionalString(body.responder),
|
|
310
|
+
interaction
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return sendJson(input.response, 200, { ok: true, interaction });
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
return sendInteractionError(input.response, error);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const jobQuestionRoute = /^\/jobs\/([^/]+)\/questions\/([^/]+)$/.exec(url.pathname);
|
|
320
|
+
if (jobQuestionRoute && input.request.method === "POST") {
|
|
321
|
+
const jobId = decodeURIComponent(jobQuestionRoute[1]);
|
|
322
|
+
const toolUseId = decodeURIComponent(jobQuestionRoute[2]);
|
|
323
|
+
const body = await readJson(input.request);
|
|
324
|
+
const job = input.store.getJob(jobId);
|
|
325
|
+
if (!job) {
|
|
326
|
+
return sendJson(input.response, 404, { error: "job not found" });
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const pending = input.interactions.getPendingQuestion({ jobId, toolUseId });
|
|
330
|
+
const answer = normalizeControlQuestionAnswer(body, pending.question);
|
|
331
|
+
const interaction = input.interactions.resolveQuestion({ jobId, toolUseId, answer });
|
|
332
|
+
input.store.recordAudit({
|
|
333
|
+
sessionId: job.sessionId,
|
|
334
|
+
jobId,
|
|
335
|
+
action: "control.user_question.resolved",
|
|
336
|
+
target: toolUseId,
|
|
337
|
+
metadata: {
|
|
338
|
+
status: "resolved",
|
|
339
|
+
interactionKind: "question",
|
|
340
|
+
toolUseId,
|
|
341
|
+
answer,
|
|
342
|
+
responder: readOptionalString(body.responder),
|
|
343
|
+
interaction
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
return sendJson(input.response, 200, { ok: true, interaction });
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
return sendInteractionError(input.response, error);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const jobInteractionCancelRoute = /^\/jobs\/([^/]+)\/(approvals|questions)\/([^/]+)\/cancel$/.exec(url.pathname);
|
|
353
|
+
if (jobInteractionCancelRoute && input.request.method === "POST") {
|
|
354
|
+
const jobId = decodeURIComponent(jobInteractionCancelRoute[1]);
|
|
355
|
+
const interactionType = jobInteractionCancelRoute[2] === "approvals" ? "approval" : "question";
|
|
356
|
+
const toolUseId = decodeURIComponent(jobInteractionCancelRoute[3]);
|
|
357
|
+
const body = await readJson(input.request);
|
|
358
|
+
const job = input.store.getJob(jobId);
|
|
359
|
+
if (!job) {
|
|
360
|
+
return sendJson(input.response, 404, { error: "job not found" });
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const interaction = input.interactions.cancelInteraction({
|
|
364
|
+
jobId,
|
|
365
|
+
toolUseId,
|
|
366
|
+
reason: readOptionalString(body.reason) ?? "cancelled by control API"
|
|
367
|
+
});
|
|
368
|
+
input.store.recordAudit({
|
|
369
|
+
sessionId: job.sessionId,
|
|
370
|
+
jobId,
|
|
371
|
+
action: interactionType === "approval" ? "control.approval.cancelled" : "control.user_question.cancelled",
|
|
372
|
+
target: toolUseId,
|
|
373
|
+
metadata: {
|
|
374
|
+
status: "cancelled",
|
|
375
|
+
interactionKind: interactionType,
|
|
376
|
+
toolUseId,
|
|
377
|
+
reason: interaction.cancelReason,
|
|
378
|
+
interaction
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
return sendJson(input.response, 200, { ok: true, interaction });
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
return sendInteractionError(input.response, error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const jobRoute = /^\/jobs\/([^/]+)$/.exec(url.pathname);
|
|
388
|
+
if (jobRoute && input.request.method === "GET") {
|
|
389
|
+
const job = input.store.getJob(decodeURIComponent(jobRoute[1]));
|
|
390
|
+
return job ? sendJson(input.response, 200, { job }) : sendJson(input.response, 404, { error: "job not found" });
|
|
391
|
+
}
|
|
392
|
+
const jobCancelRoute = /^\/jobs\/([^/]+)\/cancel$/.exec(url.pathname);
|
|
393
|
+
if (jobCancelRoute && input.request.method === "POST") {
|
|
394
|
+
const jobId = decodeURIComponent(jobCancelRoute[1]);
|
|
395
|
+
const body = await readJson(input.request);
|
|
396
|
+
const running = input.runningJobs.get(jobId);
|
|
397
|
+
const reason = readOptionalString(body.reason) ?? "cancelled by control API";
|
|
398
|
+
if (running) {
|
|
399
|
+
running.controller.abort(reason);
|
|
400
|
+
input.store.recordAudit({
|
|
401
|
+
sessionId: running.sessionId,
|
|
402
|
+
jobId,
|
|
403
|
+
action: "control.job.cancel_requested",
|
|
404
|
+
target: jobId,
|
|
405
|
+
metadata: { reason }
|
|
406
|
+
});
|
|
407
|
+
return sendJson(input.response, 200, { ok: true, status: "cancelling", jobId, reason });
|
|
408
|
+
}
|
|
409
|
+
const job = input.store.getJob(jobId);
|
|
410
|
+
if (!job) {
|
|
411
|
+
return sendJson(input.response, 404, { error: "job not found" });
|
|
412
|
+
}
|
|
413
|
+
if (job.status === "running") {
|
|
414
|
+
input.store.updateJobStatus({
|
|
415
|
+
id: jobId,
|
|
416
|
+
status: "cancelled",
|
|
417
|
+
metadata: { reason, cancelledWithoutActiveRunner: true }
|
|
418
|
+
});
|
|
419
|
+
input.store.recordAudit({
|
|
420
|
+
sessionId: job.sessionId,
|
|
421
|
+
jobId,
|
|
422
|
+
action: "control.job.cancelled",
|
|
423
|
+
target: jobId,
|
|
424
|
+
metadata: { reason, cancelledWithoutActiveRunner: true }
|
|
425
|
+
});
|
|
426
|
+
return sendJson(input.response, 200, { ok: true, status: "cancelled", jobId, reason });
|
|
427
|
+
}
|
|
428
|
+
return sendJson(input.response, 409, { error: `job is ${job.status}` });
|
|
429
|
+
}
|
|
430
|
+
if (input.request.method === "GET" && url.pathname === "/agents") {
|
|
431
|
+
return sendJson(input.response, 200, { tasks: input.store.listAgentTasks(50) });
|
|
432
|
+
}
|
|
433
|
+
if (input.request.method === "GET" && url.pathname === "/providers") {
|
|
434
|
+
return sendJson(input.response, 200, {
|
|
435
|
+
providers: Object.entries(input.config.providers).map(([name, provider]) => ({
|
|
436
|
+
name,
|
|
437
|
+
type: provider.type,
|
|
438
|
+
defaultModel: provider.defaultModel,
|
|
439
|
+
configured: Boolean(provider.apiKeyEnv ? input.env?.[provider.apiKeyEnv] : true)
|
|
440
|
+
})),
|
|
441
|
+
aliases: input.config.models.aliases
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (input.request.method === "GET" && url.pathname === "/plugins") {
|
|
445
|
+
return sendJson(input.response, 200, {
|
|
446
|
+
plugins: listLocalPlugins(input.paths),
|
|
447
|
+
marketplaces: discoverLocalMarketplaceSources(input.paths).map(loadMarketplace)
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (input.request.method === "GET" && url.pathname === "/skills") {
|
|
451
|
+
return sendJson(input.response, 200, { skills: listSkills(input.paths) });
|
|
452
|
+
}
|
|
453
|
+
if (input.request.method === "POST" && url.pathname === "/agents") {
|
|
454
|
+
const body = await readJson(input.request);
|
|
455
|
+
if (body.role !== "explorer" && body.role !== "worker") {
|
|
456
|
+
return sendJson(input.response, 400, { error: "role must be explorer or worker" });
|
|
457
|
+
}
|
|
458
|
+
if (typeof body.prompt !== "string" || !body.prompt.trim()) {
|
|
459
|
+
return sendJson(input.response, 400, { error: "prompt is required" });
|
|
460
|
+
}
|
|
461
|
+
const writeFiles = Array.isArray(body.writeFiles)
|
|
462
|
+
? body.writeFiles.filter((item) => typeof item === "string")
|
|
463
|
+
: [];
|
|
464
|
+
const cwd = typeof body.cwd === "string" ? body.cwd : input.cwd;
|
|
465
|
+
const sessionId = input.store.createSession({ title: `control agent task ${body.role}`, cwd });
|
|
466
|
+
const task = spawnAgentTask(input.store, {
|
|
467
|
+
role: body.role,
|
|
468
|
+
prompt: body.prompt,
|
|
469
|
+
cwd,
|
|
470
|
+
sessionId,
|
|
471
|
+
writeFiles
|
|
472
|
+
});
|
|
473
|
+
const hooks = await triggerHooks({
|
|
474
|
+
event: "task_created",
|
|
475
|
+
hooks: input.config.hooks,
|
|
476
|
+
store: input.store,
|
|
477
|
+
sessionId,
|
|
478
|
+
cwd,
|
|
479
|
+
env: input.env,
|
|
480
|
+
context: {
|
|
481
|
+
taskId: task.id,
|
|
482
|
+
taskSubject: task.prompt,
|
|
483
|
+
taskDescription: task.prompt,
|
|
484
|
+
agentId: task.id,
|
|
485
|
+
agentType: task.role
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
return sendJson(input.response, 200, {
|
|
489
|
+
task,
|
|
490
|
+
hooks
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
const agentAction = /^\/agents\/([^/]+)\/(start|wait|cancel|complete)$/.exec(url.pathname);
|
|
494
|
+
if (agentAction && input.request.method === "POST") {
|
|
495
|
+
const [, taskId, action] = agentAction;
|
|
496
|
+
const body = await readJson(input.request);
|
|
497
|
+
if (action === "start") {
|
|
498
|
+
const task = waitAgentTask(input.store, startAgentTask(input.store, taskId).id);
|
|
499
|
+
const sessionId = task.sessionId ?? input.store.createSession({ title: "control agent start", cwd: task.cwd });
|
|
500
|
+
const hooks = await triggerHooks({
|
|
501
|
+
event: "subagent_start",
|
|
502
|
+
hooks: input.config.hooks,
|
|
503
|
+
store: input.store,
|
|
504
|
+
sessionId,
|
|
505
|
+
cwd: task.cwd,
|
|
506
|
+
env: input.env,
|
|
507
|
+
context: {
|
|
508
|
+
agentId: task.id,
|
|
509
|
+
agentType: task.role,
|
|
510
|
+
taskId: task.id,
|
|
511
|
+
taskSubject: task.prompt
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
return sendJson(input.response, 200, { task, hooks });
|
|
515
|
+
}
|
|
516
|
+
if (action === "wait") {
|
|
517
|
+
return sendJson(input.response, 200, { task: waitAgentTask(input.store, taskId) });
|
|
518
|
+
}
|
|
519
|
+
if (action === "cancel") {
|
|
520
|
+
const task = cancelAgentTask(input.store, taskId);
|
|
521
|
+
const sessionId = task.sessionId ?? input.store.createSession({ title: "control agent stop", cwd: task.cwd });
|
|
522
|
+
const hooks = await triggerHooks({
|
|
523
|
+
event: "stop",
|
|
524
|
+
hooks: input.config.hooks,
|
|
525
|
+
store: input.store,
|
|
526
|
+
sessionId,
|
|
527
|
+
cwd: task.cwd,
|
|
528
|
+
env: input.env,
|
|
529
|
+
context: {
|
|
530
|
+
message: `Agent task ${task.id} cancelled`,
|
|
531
|
+
notificationType: "agent_task_cancelled",
|
|
532
|
+
lastAssistantMessage: task.result ?? undefined
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
const subagentHooks = await triggerHooks({
|
|
536
|
+
event: "subagent_stop",
|
|
537
|
+
hooks: input.config.hooks,
|
|
538
|
+
store: input.store,
|
|
539
|
+
sessionId,
|
|
540
|
+
cwd: task.cwd,
|
|
541
|
+
env: input.env,
|
|
542
|
+
context: {
|
|
543
|
+
agentId: task.id,
|
|
544
|
+
agentType: task.role,
|
|
545
|
+
taskId: task.id,
|
|
546
|
+
taskSubject: task.prompt,
|
|
547
|
+
message: `Agent task ${task.id} cancelled`,
|
|
548
|
+
notificationType: "agent_task_cancelled",
|
|
549
|
+
lastAssistantMessage: task.result ?? undefined
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
return sendJson(input.response, 200, { task, hooks: [...hooks, ...subagentHooks] });
|
|
553
|
+
}
|
|
554
|
+
const task = completeAgentTask(input.store, taskId, typeof body.result === "string" ? body.result : "");
|
|
555
|
+
const sessionId = task.sessionId ?? input.store.createSession({ title: "control agent notification", cwd: task.cwd });
|
|
556
|
+
const hooks = await triggerHooks({
|
|
557
|
+
event: "notification",
|
|
558
|
+
hooks: input.config.hooks,
|
|
559
|
+
store: input.store,
|
|
560
|
+
sessionId,
|
|
561
|
+
cwd: task.cwd,
|
|
562
|
+
env: input.env,
|
|
563
|
+
context: {
|
|
564
|
+
message: `Agent task ${task.id} completed`,
|
|
565
|
+
title: "Agent task completed",
|
|
566
|
+
notificationType: "agent_task_completed",
|
|
567
|
+
lastAssistantMessage: task.result ?? undefined
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
const taskHooks = await triggerHooks({
|
|
571
|
+
event: "task_completed",
|
|
572
|
+
hooks: input.config.hooks,
|
|
573
|
+
store: input.store,
|
|
574
|
+
sessionId,
|
|
575
|
+
cwd: task.cwd,
|
|
576
|
+
env: input.env,
|
|
577
|
+
context: {
|
|
578
|
+
taskId: task.id,
|
|
579
|
+
taskSubject: task.prompt,
|
|
580
|
+
taskDescription: task.prompt,
|
|
581
|
+
agentId: task.id,
|
|
582
|
+
agentType: task.role,
|
|
583
|
+
lastAssistantMessage: task.result ?? undefined
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
const subagentHooks = await triggerHooks({
|
|
587
|
+
event: "subagent_stop",
|
|
588
|
+
hooks: input.config.hooks,
|
|
589
|
+
store: input.store,
|
|
590
|
+
sessionId,
|
|
591
|
+
cwd: task.cwd,
|
|
592
|
+
env: input.env,
|
|
593
|
+
context: {
|
|
594
|
+
agentId: task.id,
|
|
595
|
+
agentType: task.role,
|
|
596
|
+
taskId: task.id,
|
|
597
|
+
taskSubject: task.prompt,
|
|
598
|
+
lastAssistantMessage: task.result ?? undefined
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
return sendJson(input.response, 200, { task, hooks: [...hooks, ...taskHooks, ...subagentHooks] });
|
|
602
|
+
}
|
|
603
|
+
if (input.request.method === "GET" && url.pathname === "/audit") {
|
|
604
|
+
return sendJson(input.response, 200, { audit: input.store.listAuditEvents(100) });
|
|
605
|
+
}
|
|
606
|
+
if (input.request.method === "GET" && url.pathname === "/events.json") {
|
|
607
|
+
const limit = readLimit(url.searchParams.get("limit"), 100);
|
|
608
|
+
const sessionId = readOptionalString(url.searchParams.get("sessionId") ?? undefined);
|
|
609
|
+
const jobId = readOptionalString(url.searchParams.get("jobId") ?? undefined);
|
|
610
|
+
return sendJson(input.response, 200, {
|
|
611
|
+
events: input.store.listRecentAuditEvents({ sessionId, jobId, limit }).map(toEventView)
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
if (input.request.method === "POST" && url.pathname === "/jobs") {
|
|
615
|
+
const body = await readJson(input.request);
|
|
616
|
+
if (typeof body.prompt !== "string" || !body.prompt.trim()) {
|
|
617
|
+
return sendJson(input.response, 400, { error: "prompt is required" });
|
|
618
|
+
}
|
|
619
|
+
const sessionId = readOptionalString(body.sessionId);
|
|
620
|
+
const existingSession = sessionId ? input.store.getSession(sessionId) : undefined;
|
|
621
|
+
if (sessionId && !existingSession) {
|
|
622
|
+
return sendJson(input.response, 404, { error: "session not found" });
|
|
623
|
+
}
|
|
624
|
+
if (body.background === true || body.async === true) {
|
|
625
|
+
const result = startBackgroundControlJob(input, body, {
|
|
626
|
+
sessionId,
|
|
627
|
+
cwd: existingSession?.cwd
|
|
628
|
+
});
|
|
629
|
+
return sendJson(input.response, 202, result);
|
|
630
|
+
}
|
|
631
|
+
const result = await runControlJob(input, body, {
|
|
632
|
+
sessionId,
|
|
633
|
+
cwd: existingSession?.cwd
|
|
634
|
+
});
|
|
635
|
+
return sendJson(input.response, 200, result);
|
|
636
|
+
}
|
|
637
|
+
const activeInteractionsRoute = /^\/jobs\/([^/]+)\/interactions$/.exec(url.pathname);
|
|
638
|
+
if (activeInteractionsRoute && input.request.method === "GET") {
|
|
639
|
+
const jobId = decodeURIComponent(activeInteractionsRoute[1]);
|
|
640
|
+
const job = input.store.getJob(jobId);
|
|
641
|
+
if (!job) {
|
|
642
|
+
return sendJson(input.response, 404, { error: "job not found" });
|
|
643
|
+
}
|
|
644
|
+
return sendJson(input.response, 200, {
|
|
645
|
+
interactions: input.interactions.listInteractions({ jobId })
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
if (input.request.method === "POST" && url.pathname === "/approvals") {
|
|
649
|
+
const body = await readJson(input.request);
|
|
650
|
+
const sessionId = typeof body.sessionId === "string"
|
|
651
|
+
? body.sessionId
|
|
652
|
+
: input.store.createSession({ title: "control approval", cwd: input.cwd });
|
|
653
|
+
input.store.recordAudit({
|
|
654
|
+
sessionId,
|
|
655
|
+
jobId: typeof body.jobId === "string" ? body.jobId : undefined,
|
|
656
|
+
action: "control.approval.recorded",
|
|
657
|
+
target: typeof body.decision === "string" ? body.decision : "unknown",
|
|
658
|
+
metadata: body
|
|
659
|
+
});
|
|
660
|
+
return sendJson(input.response, 200, { ok: true, approvalId: randomUUID() });
|
|
661
|
+
}
|
|
662
|
+
if (input.request.method === "GET" && url.pathname === "/events") {
|
|
663
|
+
streamEvents({
|
|
664
|
+
request: input.request,
|
|
665
|
+
response: input.response,
|
|
666
|
+
store: input.store,
|
|
667
|
+
limit: readLimit(url.searchParams.get("limit"), 50),
|
|
668
|
+
afterId: readOptionalId(url.searchParams.get("after")),
|
|
669
|
+
sessionId: readOptionalString(url.searchParams.get("sessionId") ?? undefined),
|
|
670
|
+
jobId: readOptionalString(url.searchParams.get("jobId") ?? undefined)
|
|
671
|
+
});
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
return sendJson(input.response, 404, { error: "not found" });
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
678
|
+
if (!input.response.headersSent) {
|
|
679
|
+
return sendJson(input.response, 500, { error: message });
|
|
680
|
+
}
|
|
681
|
+
input.response.end();
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function streamEvents(input) {
|
|
686
|
+
input.response.writeHead(200, {
|
|
687
|
+
"content-type": "text/event-stream",
|
|
688
|
+
"cache-control": "no-cache, no-transform",
|
|
689
|
+
connection: "keep-alive",
|
|
690
|
+
"x-accel-buffering": "no"
|
|
691
|
+
});
|
|
692
|
+
input.response.write(`event: ready\ndata: ${JSON.stringify({
|
|
693
|
+
ok: true,
|
|
694
|
+
sessionId: input.sessionId,
|
|
695
|
+
jobId: input.jobId
|
|
696
|
+
})}\n\n`);
|
|
697
|
+
const historical = input.store.listRecentAuditEvents({
|
|
698
|
+
sessionId: input.sessionId,
|
|
699
|
+
jobId: input.jobId,
|
|
700
|
+
afterId: input.afterId,
|
|
701
|
+
limit: input.limit,
|
|
702
|
+
order: "asc"
|
|
703
|
+
}).map(toEventView);
|
|
704
|
+
for (const event of historical) {
|
|
705
|
+
writeSseEvent(input.response, "audit", event);
|
|
706
|
+
}
|
|
707
|
+
const unsubscribe = input.store.subscribeAuditEvents((event) => {
|
|
708
|
+
if (!matchesEventStreamFilter(event, input)) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
writeSseEvent(input.response, "audit", toEventView(event));
|
|
712
|
+
});
|
|
713
|
+
const heartbeat = setInterval(() => {
|
|
714
|
+
input.response.write(`: heartbeat ${new Date().toISOString()}\n\n`);
|
|
715
|
+
}, 15_000);
|
|
716
|
+
heartbeat.unref?.();
|
|
717
|
+
const close = () => {
|
|
718
|
+
clearInterval(heartbeat);
|
|
719
|
+
unsubscribe();
|
|
720
|
+
input.response.end();
|
|
721
|
+
};
|
|
722
|
+
input.request.once("close", close);
|
|
723
|
+
}
|
|
724
|
+
function writeSseEvent(response, eventName, event) {
|
|
725
|
+
response.write(`id: ${event.id}\n`);
|
|
726
|
+
response.write(`event: ${eventName}\n`);
|
|
727
|
+
response.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
728
|
+
}
|
|
729
|
+
function matchesEventStreamFilter(event, filter) {
|
|
730
|
+
if (filter.afterId !== undefined && event.id <= filter.afterId) {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
if (filter.sessionId && event.sessionId !== filter.sessionId) {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
if (filter.jobId && event.jobId !== filter.jobId) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
function runControlJob(input, body, options = {}) {
|
|
742
|
+
return runHeadlessPrompt({
|
|
743
|
+
prompt: String(body.prompt),
|
|
744
|
+
cwd: readOptionalString(body.cwd) ?? options.cwd ?? input.cwd,
|
|
745
|
+
store: input.store,
|
|
746
|
+
config: input.config,
|
|
747
|
+
env: input.env,
|
|
748
|
+
paths: input.paths,
|
|
749
|
+
stateRoot: input.paths.stateRoot,
|
|
750
|
+
modelAlias: readOptionalString(body.model),
|
|
751
|
+
sessionId: options.sessionId,
|
|
752
|
+
sessionName: readOptionalString(body.sessionName),
|
|
753
|
+
persistSession: typeof body.persistSession === "boolean" ? body.persistSession : undefined,
|
|
754
|
+
collectEvents: body.collectEvents === true,
|
|
755
|
+
permissionMode: readPermissionMode(body.permissionMode) ?? "default",
|
|
756
|
+
activeInteractions: input.interactions
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function startBackgroundControlJob(input, body, options = {}) {
|
|
760
|
+
const cwd = readOptionalString(body.cwd) ?? options.cwd ?? input.cwd;
|
|
761
|
+
const prompt = String(body.prompt);
|
|
762
|
+
const sessionId = options.sessionId ?? input.store.createSession({
|
|
763
|
+
title: readOptionalString(body.sessionName) ?? prompt.slice(0, 80),
|
|
764
|
+
cwd,
|
|
765
|
+
metadata: { mode: "control-background" }
|
|
766
|
+
});
|
|
767
|
+
const jobId = randomUUID();
|
|
768
|
+
const controller = new AbortController();
|
|
769
|
+
const promise = runHeadlessPrompt({
|
|
770
|
+
prompt,
|
|
771
|
+
cwd,
|
|
772
|
+
store: input.store,
|
|
773
|
+
config: input.config,
|
|
774
|
+
env: input.env,
|
|
775
|
+
paths: input.paths,
|
|
776
|
+
stateRoot: input.paths.stateRoot,
|
|
777
|
+
modelAlias: readOptionalString(body.model),
|
|
778
|
+
jobId,
|
|
779
|
+
sessionId,
|
|
780
|
+
sessionName: readOptionalString(body.sessionName),
|
|
781
|
+
persistSession: true,
|
|
782
|
+
collectEvents: body.collectEvents === true,
|
|
783
|
+
permissionMode: readPermissionMode(body.permissionMode) ?? "default",
|
|
784
|
+
activeInteractions: input.interactions,
|
|
785
|
+
signal: controller.signal
|
|
786
|
+
}).finally(() => {
|
|
787
|
+
input.runningJobs.delete(jobId);
|
|
788
|
+
});
|
|
789
|
+
input.runningJobs.set(jobId, { jobId, sessionId, controller, promise });
|
|
790
|
+
promise.catch(() => undefined);
|
|
791
|
+
return { sessionId, jobId, status: "running" };
|
|
792
|
+
}
|
|
793
|
+
function isAuthorized(request, store) {
|
|
794
|
+
const deviceId = headerValue(request.headers["x-magi-device-id"]);
|
|
795
|
+
const token = headerValue(request.headers.authorization)?.replace(/^Bearer\s+/i, "");
|
|
796
|
+
return validateDeviceToken({ store, deviceId, token });
|
|
797
|
+
}
|
|
798
|
+
function isLoopbackRequest(request) {
|
|
799
|
+
const addr = request.socket.remoteAddress ?? "";
|
|
800
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
801
|
+
}
|
|
802
|
+
async function readJson(request) {
|
|
803
|
+
const chunks = [];
|
|
804
|
+
for await (const chunk of request) {
|
|
805
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
806
|
+
}
|
|
807
|
+
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
808
|
+
if (!text) {
|
|
809
|
+
return {};
|
|
810
|
+
}
|
|
811
|
+
const parsed = JSON.parse(text);
|
|
812
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
813
|
+
}
|
|
814
|
+
function sendJson(response, status, body) {
|
|
815
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
816
|
+
response.end(`${JSON.stringify(body)}\n`);
|
|
817
|
+
}
|
|
818
|
+
function sendText(response, status, contentType, body) {
|
|
819
|
+
response.writeHead(status, { "content-type": contentType });
|
|
820
|
+
response.end(body);
|
|
821
|
+
}
|
|
822
|
+
function sendInteractionError(response, error) {
|
|
823
|
+
if (error instanceof ActiveInteractionNotFoundError) {
|
|
824
|
+
return sendJson(response, 404, { error: error.message });
|
|
825
|
+
}
|
|
826
|
+
if (error instanceof ActiveInteractionStateError || error instanceof ActiveInteractionCancelledError) {
|
|
827
|
+
return sendJson(response, 409, { error: error.message });
|
|
828
|
+
}
|
|
829
|
+
return sendJson(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
830
|
+
}
|
|
831
|
+
function headerValue(value) {
|
|
832
|
+
return Array.isArray(value) ? value[0] : value;
|
|
833
|
+
}
|
|
834
|
+
function readOptionalString(value) {
|
|
835
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
836
|
+
}
|
|
837
|
+
function readOptionalRecord(value) {
|
|
838
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
839
|
+
}
|
|
840
|
+
function readApprovalDecision(body) {
|
|
841
|
+
const raw = body.approved ?? body.decision;
|
|
842
|
+
if (raw === true || raw === "approve" || raw === "approved" || raw === "allow" || raw === "allowed") {
|
|
843
|
+
return true;
|
|
844
|
+
}
|
|
845
|
+
if (raw === false || raw === "deny" || raw === "denied" || raw === "reject" || raw === "rejected") {
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
return undefined;
|
|
849
|
+
}
|
|
850
|
+
function readPermissionMode(value) {
|
|
851
|
+
return value === "default" || value === "acceptEdits" || value === "bypassPermissions" || value === "plan"
|
|
852
|
+
? value
|
|
853
|
+
: undefined;
|
|
854
|
+
}
|
|
855
|
+
function normalizeControlQuestionAnswer(body, question) {
|
|
856
|
+
const rawAnswer = readOptionalRecord(body.answer) ?? body;
|
|
857
|
+
const candidate = rawAnswer.answers === undefined && Array.isArray(body.selectedLabels)
|
|
858
|
+
? {
|
|
859
|
+
answers: [{
|
|
860
|
+
question: question.questions[0]?.question ?? "",
|
|
861
|
+
selectedLabels: body.selectedLabels
|
|
862
|
+
}]
|
|
863
|
+
}
|
|
864
|
+
: rawAnswer;
|
|
865
|
+
return normalizeAskUserQuestionAnswer(question, candidate);
|
|
866
|
+
}
|
|
867
|
+
function readLimit(raw, fallback) {
|
|
868
|
+
if (raw === null || raw === "") {
|
|
869
|
+
return fallback;
|
|
870
|
+
}
|
|
871
|
+
if (!/^\d+$/.test(raw)) {
|
|
872
|
+
return fallback;
|
|
873
|
+
}
|
|
874
|
+
const value = Number(raw);
|
|
875
|
+
return Number.isInteger(value) && value >= 1 && value <= 500 ? value : fallback;
|
|
876
|
+
}
|
|
877
|
+
function readOptionalId(raw) {
|
|
878
|
+
if (raw === null || raw === "") {
|
|
879
|
+
return undefined;
|
|
880
|
+
}
|
|
881
|
+
if (!/^\d+$/.test(raw)) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
const value = Number(raw);
|
|
885
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
886
|
+
}
|
|
887
|
+
//# sourceMappingURL=server.js.map
|