macro-agent 0.0.11 → 0.0.12
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/.macro-agent/teams/self-driving/prompts/grinder.md +27 -0
- package/.macro-agent/teams/self-driving/prompts/judge.md +27 -0
- package/.macro-agent/teams/self-driving/prompts/planner.md +33 -0
- package/.macro-agent/teams/self-driving/roles/grinder.yaml +17 -0
- package/.macro-agent/teams/self-driving/roles/judge.yaml +24 -0
- package/.macro-agent/teams/self-driving/roles/planner.yaml +18 -0
- package/.macro-agent/teams/self-driving/team.yaml +103 -0
- package/.macro-agent/teams/structured/prompts/developer.md +26 -0
- package/.macro-agent/teams/structured/prompts/lead.md +25 -0
- package/.macro-agent/teams/structured/prompts/reviewer.md +24 -0
- package/.macro-agent/teams/structured/roles/developer.yaml +12 -0
- package/.macro-agent/teams/structured/roles/lead.yaml +11 -0
- package/.macro-agent/teams/structured/roles/reviewer.yaml +19 -0
- package/.macro-agent/teams/structured/team.yaml +89 -0
- package/.sudocode/issues.jsonl +6 -0
- package/.sudocode/specs.jsonl +7 -0
- package/CLAUDE.md +110 -30
- package/README.md +60 -3
- package/dist/acp/macro-agent.d.ts +4 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +50 -4
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/session-mapper.d.ts +20 -1
- package/dist/acp/session-mapper.d.ts.map +1 -1
- package/dist/acp/session-mapper.js +90 -1
- package/dist/acp/session-mapper.js.map +1 -1
- package/dist/acp/types.d.ts +24 -1
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js.map +1 -1
- package/dist/agent/agent-manager.d.ts +25 -1
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +93 -7
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +22 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/agent-detection/command-builder.d.ts +30 -0
- package/dist/agent-detection/command-builder.d.ts.map +1 -0
- package/dist/agent-detection/command-builder.js +71 -0
- package/dist/agent-detection/command-builder.js.map +1 -0
- package/dist/agent-detection/detector.d.ts +84 -0
- package/dist/agent-detection/detector.d.ts.map +1 -0
- package/dist/agent-detection/detector.js +240 -0
- package/dist/agent-detection/detector.js.map +1 -0
- package/dist/agent-detection/index.d.ts +12 -0
- package/dist/agent-detection/index.d.ts.map +1 -0
- package/dist/agent-detection/index.js +14 -0
- package/dist/agent-detection/index.js.map +1 -0
- package/dist/agent-detection/registry.d.ts +53 -0
- package/dist/agent-detection/registry.d.ts.map +1 -0
- package/dist/agent-detection/registry.js +177 -0
- package/dist/agent-detection/registry.js.map +1 -0
- package/dist/agent-detection/types.d.ts +121 -0
- package/dist/agent-detection/types.d.ts.map +1 -0
- package/dist/agent-detection/types.js +20 -0
- package/dist/agent-detection/types.js.map +1 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +95 -0
- package/dist/api/server.js.map +1 -1
- package/dist/cli/index.js +29 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.js +38 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/project-config.d.ts +46 -0
- package/dist/config/project-config.d.ts.map +1 -0
- package/dist/config/project-config.js +68 -0
- package/dist/config/project-config.js.map +1 -0
- package/dist/lifecycle/cascade.d.ts +1 -1
- package/dist/lifecycle/cascade.d.ts.map +1 -1
- package/dist/lifecycle/handlers/index.d.ts +4 -0
- package/dist/lifecycle/handlers/index.d.ts.map +1 -1
- package/dist/lifecycle/handlers/index.js +2 -0
- package/dist/lifecycle/handlers/index.js.map +1 -1
- package/dist/lifecycle/handlers/worker.d.ts +4 -0
- package/dist/lifecycle/handlers/worker.d.ts.map +1 -1
- package/dist/lifecycle/handlers/worker.js +35 -3
- package/dist/lifecycle/handlers/worker.js.map +1 -1
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
- package/dist/map/adapter/acp-over-map.js +32 -2
- package/dist/map/adapter/acp-over-map.js.map +1 -1
- package/dist/map/adapter/event-translator.d.ts.map +1 -1
- package/dist/map/adapter/event-translator.js +1 -0
- package/dist/map/adapter/event-translator.js.map +1 -1
- package/dist/map/adapter/extensions/agent-detection.d.ts +49 -0
- package/dist/map/adapter/extensions/agent-detection.d.ts.map +1 -0
- package/dist/map/adapter/extensions/agent-detection.js +91 -0
- package/dist/map/adapter/extensions/agent-detection.js.map +1 -0
- package/dist/map/adapter/extensions/index.d.ts +10 -1
- package/dist/map/adapter/extensions/index.d.ts.map +1 -1
- package/dist/map/adapter/extensions/index.js +39 -0
- package/dist/map/adapter/extensions/index.js.map +1 -1
- package/dist/map/adapter/extensions/resume.d.ts +47 -0
- package/dist/map/adapter/extensions/resume.d.ts.map +1 -0
- package/dist/map/adapter/extensions/resume.js +59 -0
- package/dist/map/adapter/extensions/resume.js.map +1 -0
- package/dist/map/adapter/extensions/workspace-files.d.ts +42 -0
- package/dist/map/adapter/extensions/workspace-files.d.ts.map +1 -0
- package/dist/map/adapter/extensions/workspace-files.js +338 -0
- package/dist/map/adapter/extensions/workspace-files.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +6 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +45 -0
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/tools/claim_task.d.ts +35 -0
- package/dist/mcp/tools/claim_task.d.ts.map +1 -0
- package/dist/mcp/tools/claim_task.js +58 -0
- package/dist/mcp/tools/claim_task.js.map +1 -0
- package/dist/mcp/tools/done.d.ts +11 -2
- package/dist/mcp/tools/done.d.ts.map +1 -1
- package/dist/mcp/tools/done.js +15 -10
- package/dist/mcp/tools/done.js.map +1 -1
- package/dist/mcp/tools/list_claimable_tasks.d.ts +38 -0
- package/dist/mcp/tools/list_claimable_tasks.d.ts.map +1 -0
- package/dist/mcp/tools/list_claimable_tasks.js +63 -0
- package/dist/mcp/tools/list_claimable_tasks.js.map +1 -0
- package/dist/mcp/tools/unclaim_task.d.ts +31 -0
- package/dist/mcp/tools/unclaim_task.d.ts.map +1 -0
- package/dist/mcp/tools/unclaim_task.js +47 -0
- package/dist/mcp/tools/unclaim_task.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +2 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/metrics.d.ts +79 -0
- package/dist/metrics/metrics.d.ts.map +1 -0
- package/dist/metrics/metrics.js +166 -0
- package/dist/metrics/metrics.js.map +1 -0
- package/dist/roles/capabilities.d.ts +1 -0
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +3 -0
- package/dist/roles/capabilities.js.map +1 -1
- package/dist/roles/types.d.ts +1 -1
- package/dist/roles/types.d.ts.map +1 -1
- package/dist/router/message-router.d.ts +41 -0
- package/dist/router/message-router.d.ts.map +1 -1
- package/dist/router/message-router.js +136 -5
- package/dist/router/message-router.js.map +1 -1
- package/dist/store/event-store.d.ts +8 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +120 -4
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/types/agents.d.ts +1 -1
- package/dist/store/types/agents.d.ts.map +1 -1
- package/dist/store/types/events.d.ts +1 -1
- package/dist/store/types/events.d.ts.map +1 -1
- package/dist/store/types/events.js.map +1 -1
- package/dist/store/types/index.d.ts +1 -0
- package/dist/store/types/index.d.ts.map +1 -1
- package/dist/store/types/index.js +1 -0
- package/dist/store/types/index.js.map +1 -1
- package/dist/store/types/sessions.d.ts +44 -0
- package/dist/store/types/sessions.d.ts.map +1 -0
- package/dist/store/types/sessions.js +9 -0
- package/dist/store/types/sessions.js.map +1 -0
- package/dist/store/types/tasks.d.ts +2 -0
- package/dist/store/types/tasks.d.ts.map +1 -1
- package/dist/task/backend/memory.d.ts +4 -1
- package/dist/task/backend/memory.d.ts.map +1 -1
- package/dist/task/backend/memory.js +81 -0
- package/dist/task/backend/memory.js.map +1 -1
- package/dist/task/backend/types.d.ts +30 -0
- package/dist/task/backend/types.d.ts.map +1 -1
- package/dist/task/backend/types.js.map +1 -1
- package/dist/teams/index.d.ts +4 -0
- package/dist/teams/index.d.ts.map +1 -0
- package/dist/teams/index.js +4 -0
- package/dist/teams/index.js.map +1 -0
- package/dist/teams/team-loader.d.ts +20 -0
- package/dist/teams/team-loader.d.ts.map +1 -0
- package/dist/teams/team-loader.js +293 -0
- package/dist/teams/team-loader.js.map +1 -0
- package/dist/teams/team-runtime.d.ts +139 -0
- package/dist/teams/team-runtime.d.ts.map +1 -0
- package/dist/teams/team-runtime.js +613 -0
- package/dist/teams/team-runtime.js.map +1 -0
- package/dist/teams/types.d.ts +266 -0
- package/dist/teams/types.d.ts.map +1 -0
- package/dist/teams/types.js +20 -0
- package/dist/teams/types.js.map +1 -0
- package/dist/workspace/dataplane-adapter.d.ts +1 -1
- package/dist/workspace/dataplane-adapter.d.ts.map +1 -1
- package/dist/workspace/dataplane-adapter.js +1 -1
- package/dist/workspace/dataplane-adapter.js.map +1 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/strategies/index.d.ts +6 -0
- package/dist/workspace/strategies/index.d.ts.map +1 -0
- package/dist/workspace/strategies/index.js +5 -0
- package/dist/workspace/strategies/index.js.map +1 -0
- package/dist/workspace/strategies/optimistic.d.ts +26 -0
- package/dist/workspace/strategies/optimistic.d.ts.map +1 -0
- package/dist/workspace/strategies/optimistic.js +121 -0
- package/dist/workspace/strategies/optimistic.js.map +1 -0
- package/dist/workspace/strategies/queue.d.ts +26 -0
- package/dist/workspace/strategies/queue.d.ts.map +1 -0
- package/dist/workspace/strategies/queue.js +67 -0
- package/dist/workspace/strategies/queue.js.map +1 -0
- package/dist/workspace/strategies/registry.d.ts +37 -0
- package/dist/workspace/strategies/registry.d.ts.map +1 -0
- package/dist/workspace/strategies/registry.js +63 -0
- package/dist/workspace/strategies/registry.js.map +1 -0
- package/dist/workspace/strategies/trunk.d.ts +20 -0
- package/dist/workspace/strategies/trunk.d.ts.map +1 -0
- package/dist/workspace/strategies/trunk.js +108 -0
- package/dist/workspace/strategies/trunk.js.map +1 -0
- package/dist/workspace/strategies/types.d.ts +104 -0
- package/dist/workspace/strategies/types.d.ts.map +1 -0
- package/dist/workspace/strategies/types.js +11 -0
- package/dist/workspace/strategies/types.js.map +1 -0
- package/dist/workspace/types.d.ts +1 -1
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +1 -1
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/docs/implementation-details.md +1127 -0
- package/docs/implementation-summary.md +448 -0
- package/docs/plan-self-driving-support.md +433 -0
- package/docs/spec-self-driving-support.md +462 -0
- package/docs/team-templates.md +860 -0
- package/docs/teams.md +233 -0
- package/package.json +4 -2
- package/src/acp/__tests__/integration.test.ts +161 -1
- package/src/acp/__tests__/macro-agent.test.ts +95 -0
- package/src/acp/__tests__/session-persistence.test.ts +276 -0
- package/src/acp/macro-agent.ts +79 -7
- package/src/acp/session-mapper.ts +108 -1
- package/src/acp/types.ts +33 -1
- package/src/agent/agent-manager.ts +158 -6
- package/src/agent/types.ts +27 -0
- package/src/agent-detection/__tests__/command-builder.test.ts +336 -0
- package/src/agent-detection/__tests__/detector.test.ts +768 -0
- package/src/agent-detection/__tests__/registry.test.ts +254 -0
- package/src/agent-detection/command-builder.ts +90 -0
- package/src/agent-detection/detector.ts +307 -0
- package/src/agent-detection/index.ts +36 -0
- package/src/agent-detection/registry.ts +200 -0
- package/src/agent-detection/types.ts +184 -0
- package/src/api/server.ts +110 -0
- package/src/cli/index.ts +44 -0
- package/src/cli/mcp.ts +47 -0
- package/src/config/index.ts +9 -0
- package/src/config/project-config.ts +107 -0
- package/src/lifecycle/cascade.ts +1 -1
- package/src/lifecycle/handlers/index.ts +8 -0
- package/src/lifecycle/handlers/worker.ts +48 -3
- package/src/map/adapter/__tests__/extensions.test.ts +359 -0
- package/src/map/adapter/__tests__/workspace-files.test.ts +673 -0
- package/src/map/adapter/acp-over-map.ts +45 -2
- package/src/map/adapter/event-translator.ts +1 -0
- package/src/map/adapter/extensions/agent-detection.ts +201 -0
- package/src/map/adapter/extensions/index.ts +63 -0
- package/src/map/adapter/extensions/resume.ts +114 -0
- package/src/map/adapter/extensions/workspace-files.ts +449 -0
- package/src/mcp/mcp-server.ts +67 -0
- package/src/mcp/tools/claim_task.ts +86 -0
- package/src/mcp/tools/done.ts +24 -10
- package/src/mcp/tools/list_claimable_tasks.ts +93 -0
- package/src/mcp/tools/unclaim_task.ts +71 -0
- package/src/metrics/index.ts +9 -0
- package/src/metrics/metrics.ts +280 -0
- package/src/roles/capabilities.ts +3 -0
- package/src/roles/types.ts +2 -1
- package/src/router/__tests__/message-router.test.ts +561 -0
- package/src/router/message-router.ts +223 -6
- package/src/store/event-store.ts +151 -3
- package/src/store/types/agents.ts +1 -1
- package/src/store/types/events.ts +2 -1
- package/src/store/types/index.ts +1 -0
- package/src/store/types/sessions.ts +53 -0
- package/src/store/types/tasks.ts +3 -0
- package/src/task/backend/memory.ts +116 -0
- package/src/task/backend/types.ts +43 -0
- package/src/teams/__tests__/cross-subsystem.integration.test.ts +983 -0
- package/src/teams/__tests__/e2e/team-runtime.e2e.test.ts +553 -0
- package/src/teams/__tests__/team-system.test.ts +1280 -0
- package/src/teams/index.ts +13 -0
- package/src/teams/team-loader.ts +434 -0
- package/src/teams/team-runtime.ts +727 -0
- package/src/teams/types.ts +377 -0
- package/src/workspace/dataplane-adapter.ts +1 -1
- package/src/workspace/index.ts +1 -1
- package/src/workspace/strategies/index.ts +18 -0
- package/src/workspace/strategies/optimistic.ts +136 -0
- package/src/workspace/strategies/queue.ts +81 -0
- package/src/workspace/strategies/registry.ts +89 -0
- package/src/workspace/strategies/trunk.ts +123 -0
- package/src/workspace/strategies/types.ts +145 -0
- package/src/workspace/types.ts +1 -1
- package/src/workspace/workspace-manager.ts +1 -1
- package/.claude/settings.local.json +0 -59
- package/dist/map/utils/address-translation.d.ts +0 -99
- package/dist/map/utils/address-translation.d.ts.map +0 -1
- package/dist/map/utils/address-translation.js +0 -285
- package/dist/map/utils/address-translation.js.map +0 -1
- package/dist/map/utils/index.d.ts +0 -7
- package/dist/map/utils/index.d.ts.map +0 -1
- package/dist/map/utils/index.js +0 -7
- package/dist/map/utils/index.js.map +0 -1
- package/references/acp-factory-ref/CHANGELOG.md +0 -33
- package/references/acp-factory-ref/LICENSE +0 -21
- package/references/acp-factory-ref/README.md +0 -341
- package/references/acp-factory-ref/package-lock.json +0 -3102
- package/references/acp-factory-ref/package.json +0 -96
- package/references/acp-factory-ref/python/CHANGELOG.md +0 -33
- package/references/acp-factory-ref/python/LICENSE +0 -21
- package/references/acp-factory-ref/python/Makefile +0 -57
- package/references/acp-factory-ref/python/README.md +0 -253
- package/references/acp-factory-ref/python/pyproject.toml +0 -73
- package/references/acp-factory-ref/python/tests/__init__.py +0 -0
- package/references/acp-factory-ref/python/tests/e2e/__init__.py +0 -1
- package/references/acp-factory-ref/python/tests/e2e/test_codex_e2e.py +0 -349
- package/references/acp-factory-ref/python/tests/e2e/test_gemini_e2e.py +0 -165
- package/references/acp-factory-ref/python/tests/e2e/test_opencode_e2e.py +0 -296
- package/references/acp-factory-ref/python/tests/test_client_handler.py +0 -543
- package/references/acp-factory-ref/python/tests/test_pushable.py +0 -199
- package/references/claude-code-acp/.github/workflows/ci.yml +0 -45
- package/references/claude-code-acp/.github/workflows/publish.yml +0 -34
- package/references/claude-code-acp/.prettierrc.json +0 -4
- package/references/claude-code-acp/CHANGELOG.md +0 -249
- package/references/claude-code-acp/LICENSE +0 -222
- package/references/claude-code-acp/README.md +0 -53
- package/references/claude-code-acp/docs/RELEASES.md +0 -24
- package/references/claude-code-acp/eslint.config.js +0 -48
- package/references/claude-code-acp/package-lock.json +0 -4570
- package/references/claude-code-acp/package.json +0 -88
- package/references/claude-code-acp/scripts/release.sh +0 -119
- package/references/claude-code-acp/src/acp-agent.ts +0 -2065
- package/references/claude-code-acp/src/index.ts +0 -26
- package/references/claude-code-acp/src/lib.ts +0 -38
- package/references/claude-code-acp/src/mcp-server.ts +0 -911
- package/references/claude-code-acp/src/settings.ts +0 -522
- package/references/claude-code-acp/src/tests/.claude/commands/quick-math.md +0 -5
- package/references/claude-code-acp/src/tests/.claude/commands/say-hello.md +0 -6
- package/references/claude-code-acp/src/tests/acp-agent-fork.test.ts +0 -479
- package/references/claude-code-acp/src/tests/acp-agent.test.ts +0 -1502
- package/references/claude-code-acp/src/tests/extract-lines.test.ts +0 -103
- package/references/claude-code-acp/src/tests/fork-session.test.ts +0 -335
- package/references/claude-code-acp/src/tests/replace-and-calculate-location.test.ts +0 -334
- package/references/claude-code-acp/src/tests/settings.test.ts +0 -617
- package/references/claude-code-acp/src/tests/skills-options.test.ts +0 -187
- package/references/claude-code-acp/src/tests/tools.test.ts +0 -318
- package/references/claude-code-acp/src/tests/typescript-declarations.test.ts +0 -558
- package/references/claude-code-acp/src/tools.ts +0 -819
- package/references/claude-code-acp/src/utils.ts +0 -171
- package/references/claude-code-acp/tsconfig.json +0 -18
- package/references/claude-code-acp/vitest.config.ts +0 -19
- package/references/multi-agent-protocol/.sudocode/issues.jsonl +0 -111
- package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -13
- package/references/multi-agent-protocol/LICENSE +0 -21
- package/references/multi-agent-protocol/README.md +0 -113
- package/references/multi-agent-protocol/docs/00-design-specification.md +0 -496
- package/references/multi-agent-protocol/docs/01-open-questions.md +0 -1050
- package/references/multi-agent-protocol/docs/02-wire-protocol.md +0 -296
- package/references/multi-agent-protocol/docs/03-streaming-semantics.md +0 -252
- package/references/multi-agent-protocol/docs/04-error-handling.md +0 -231
- package/references/multi-agent-protocol/docs/05-connection-model.md +0 -244
- package/references/multi-agent-protocol/docs/06-visibility-permissions.md +0 -243
- package/references/multi-agent-protocol/docs/07-federation.md +0 -259
- package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +0 -253
- package/references/multi-agent-protocol/docs/09-authentication.md +0 -680
- package/references/multi-agent-protocol/docs/10-mail-protocol.md +0 -553
- package/references/multi-agent-protocol/docs/agent-iam-integration.md +0 -877
- package/references/multi-agent-protocol/docs/agentic-mesh-integration-draft.md +0 -459
- package/references/multi-agent-protocol/docs/git-transport-draft.md +0 -251
- package/references/multi-agent-protocol/docs-site/Gemfile +0 -22
- package/references/multi-agent-protocol/docs-site/README.md +0 -82
- package/references/multi-agent-protocol/docs-site/_config.yml +0 -91
- package/references/multi-agent-protocol/docs-site/_includes/head_custom.html +0 -20
- package/references/multi-agent-protocol/docs-site/_sass/color_schemes/map.scss +0 -42
- package/references/multi-agent-protocol/docs-site/_sass/custom/custom.scss +0 -34
- package/references/multi-agent-protocol/docs-site/examples/full-integration.md +0 -510
- package/references/multi-agent-protocol/docs-site/examples/index.md +0 -138
- package/references/multi-agent-protocol/docs-site/examples/simple-chat.md +0 -282
- package/references/multi-agent-protocol/docs-site/examples/task-queue.md +0 -399
- package/references/multi-agent-protocol/docs-site/getting-started/index.md +0 -98
- package/references/multi-agent-protocol/docs-site/getting-started/installation.md +0 -219
- package/references/multi-agent-protocol/docs-site/getting-started/overview.md +0 -172
- package/references/multi-agent-protocol/docs-site/getting-started/quickstart.md +0 -237
- package/references/multi-agent-protocol/docs-site/index.md +0 -136
- package/references/multi-agent-protocol/docs-site/protocol/authentication.md +0 -391
- package/references/multi-agent-protocol/docs-site/protocol/connection-model.md +0 -376
- package/references/multi-agent-protocol/docs-site/protocol/design.md +0 -284
- package/references/multi-agent-protocol/docs-site/protocol/error-handling.md +0 -312
- package/references/multi-agent-protocol/docs-site/protocol/federation.md +0 -449
- package/references/multi-agent-protocol/docs-site/protocol/index.md +0 -129
- package/references/multi-agent-protocol/docs-site/protocol/permissions.md +0 -398
- package/references/multi-agent-protocol/docs-site/protocol/streaming.md +0 -353
- package/references/multi-agent-protocol/docs-site/protocol/wire-protocol.md +0 -369
- package/references/multi-agent-protocol/docs-site/sdk/api/agent.md +0 -357
- package/references/multi-agent-protocol/docs-site/sdk/api/client.md +0 -380
- package/references/multi-agent-protocol/docs-site/sdk/api/index.md +0 -62
- package/references/multi-agent-protocol/docs-site/sdk/api/server.md +0 -453
- package/references/multi-agent-protocol/docs-site/sdk/api/types.md +0 -468
- package/references/multi-agent-protocol/docs-site/sdk/guides/agent.md +0 -375
- package/references/multi-agent-protocol/docs-site/sdk/guides/authentication.md +0 -405
- package/references/multi-agent-protocol/docs-site/sdk/guides/client.md +0 -352
- package/references/multi-agent-protocol/docs-site/sdk/guides/index.md +0 -89
- package/references/multi-agent-protocol/docs-site/sdk/guides/server.md +0 -360
- package/references/multi-agent-protocol/docs-site/sdk/guides/testing.md +0 -446
- package/references/multi-agent-protocol/docs-site/sdk/guides/transports.md +0 -363
- package/references/multi-agent-protocol/docs-site/sdk/index.md +0 -206
- package/references/multi-agent-protocol/package-lock.json +0 -3886
- package/references/multi-agent-protocol/package.json +0 -56
- package/references/multi-agent-protocol/schema/meta.json +0 -467
- package/references/multi-agent-protocol/schema/schema.json +0 -2558
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Detector Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
import { AgentDetector, createAgentDetector, parseVersion } from "../detector.js";
|
|
9
|
+
import { AgentDetectionError } from "../types.js";
|
|
10
|
+
|
|
11
|
+
// Mock child_process.execFile
|
|
12
|
+
vi.mock("node:child_process", () => ({
|
|
13
|
+
execFile: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Mock node:util to return our mock
|
|
17
|
+
vi.mock("node:util", () => ({
|
|
18
|
+
promisify: (fn: unknown) => {
|
|
19
|
+
// Return a function that calls the mock and wraps in a promise
|
|
20
|
+
return (...args: unknown[]) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
(fn as Function)(...args, (err: Error | null, result: unknown) => {
|
|
23
|
+
if (err) reject(err);
|
|
24
|
+
else resolve(result);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const mockExecFile = vi.mocked(execFile);
|
|
32
|
+
|
|
33
|
+
function setupExecFileMock(responses: Record<string, { stdout?: string; stderr?: string; error?: Error }>) {
|
|
34
|
+
mockExecFile.mockImplementation(((
|
|
35
|
+
command: string,
|
|
36
|
+
args: string[],
|
|
37
|
+
_options: unknown,
|
|
38
|
+
callback: (err: Error | null, result: { stdout: string; stderr: string }) => void
|
|
39
|
+
) => {
|
|
40
|
+
// Build a key from the command
|
|
41
|
+
let key: string;
|
|
42
|
+
if (command === "which") {
|
|
43
|
+
key = `which:${args[0]}`;
|
|
44
|
+
} else if (command === "/bin/sh") {
|
|
45
|
+
key = `command-v:${args[1]?.replace("command -v ", "")}`;
|
|
46
|
+
} else {
|
|
47
|
+
key = `version:${command}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const response = responses[key];
|
|
51
|
+
if (response?.error) {
|
|
52
|
+
callback(response.error, { stdout: "", stderr: "" });
|
|
53
|
+
} else if (response) {
|
|
54
|
+
callback(null, { stdout: response.stdout ?? "", stderr: response.stderr ?? "" });
|
|
55
|
+
} else {
|
|
56
|
+
callback(new Error(`Command not found: ${key}`), { stdout: "", stderr: "" });
|
|
57
|
+
}
|
|
58
|
+
}) as unknown as typeof execFile);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("AgentDetector", () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
vi.clearAllMocks();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.restoreAllMocks();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ===========================================================================
|
|
71
|
+
// parseVersion()
|
|
72
|
+
// ===========================================================================
|
|
73
|
+
|
|
74
|
+
describe("parseVersion()", () => {
|
|
75
|
+
it("parses standard semver version", () => {
|
|
76
|
+
expect(parseVersion("1.2.3")).toBe("1.2.3");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("parses version from verbose output", () => {
|
|
80
|
+
expect(parseVersion("claude v1.0.30 (build abc123)")).toBe("1.0.30");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("parses version with only major.minor", () => {
|
|
84
|
+
expect(parseVersion("version 0.82")).toBe("0.82");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("parses version with four parts", () => {
|
|
88
|
+
expect(parseVersion("aider 0.82.1.2")).toBe("0.82.1.2");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("parses version from multiline output", () => {
|
|
92
|
+
expect(parseVersion("Tool name\nVersion: 2.3.4\nBuild: xyz")).toBe("2.3.4");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("returns null for output with no version", () => {
|
|
96
|
+
expect(parseVersion("no version here")).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns null for empty output", () => {
|
|
100
|
+
expect(parseVersion("")).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ===========================================================================
|
|
105
|
+
// Constructor & Configuration
|
|
106
|
+
// ===========================================================================
|
|
107
|
+
|
|
108
|
+
describe("constructor", () => {
|
|
109
|
+
it("creates detector with default configuration", () => {
|
|
110
|
+
const detector = new AgentDetector();
|
|
111
|
+
expect(detector.getRegistry().size).toBe(6);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("creates detector with custom agents", () => {
|
|
115
|
+
const detector = new AgentDetector({
|
|
116
|
+
additionalAgents: [
|
|
117
|
+
{
|
|
118
|
+
id: "custom",
|
|
119
|
+
name: "Custom",
|
|
120
|
+
description: "Custom agent",
|
|
121
|
+
binary: "custom",
|
|
122
|
+
versionArgs: ["--version"],
|
|
123
|
+
headless: { promptFlag: "--prompt" },
|
|
124
|
+
vendor: "Custom",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
expect(detector.getRegistry().size).toBe(7);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("creates detector with disabled agents", () => {
|
|
132
|
+
const detector = new AgentDetector({
|
|
133
|
+
disabledAgents: ["goose", "aider"],
|
|
134
|
+
});
|
|
135
|
+
// Registry still has all agents, but detection will skip disabled ones
|
|
136
|
+
expect(detector.getRegistry().size).toBe(6);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ===========================================================================
|
|
141
|
+
// Detection
|
|
142
|
+
// ===========================================================================
|
|
143
|
+
|
|
144
|
+
describe("detect()", () => {
|
|
145
|
+
it("detects installed agents", async () => {
|
|
146
|
+
setupExecFileMock({
|
|
147
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
148
|
+
"version:claude": { stdout: "claude v1.0.30\n" },
|
|
149
|
+
"which:codex": { error: new Error("not found") },
|
|
150
|
+
"command-v:codex": { error: new Error("not found") },
|
|
151
|
+
"which:gemini": { error: new Error("not found") },
|
|
152
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
153
|
+
"which:opencode": { error: new Error("not found") },
|
|
154
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
155
|
+
"which:aider": { stdout: "/home/user/.local/bin/aider\n" },
|
|
156
|
+
"version:aider": { stdout: "aider v0.82.1\n" },
|
|
157
|
+
"which:goose": { error: new Error("not found") },
|
|
158
|
+
"command-v:goose": { error: new Error("not found") },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const detector = createAgentDetector();
|
|
162
|
+
const result = await detector.detect();
|
|
163
|
+
|
|
164
|
+
expect(result.scanned).toBe(6);
|
|
165
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
166
|
+
|
|
167
|
+
const claude = result.agents.find((a) => a.id === "claude-code");
|
|
168
|
+
expect(claude).toBeDefined();
|
|
169
|
+
expect(claude!.installed).toBe(true);
|
|
170
|
+
expect(claude!.version).toBe("1.0.30");
|
|
171
|
+
expect(claude!.path).toBe("/usr/local/bin/claude");
|
|
172
|
+
|
|
173
|
+
const aider = result.agents.find((a) => a.id === "aider");
|
|
174
|
+
expect(aider).toBeDefined();
|
|
175
|
+
expect(aider!.installed).toBe(true);
|
|
176
|
+
expect(aider!.version).toBe("0.82.1");
|
|
177
|
+
|
|
178
|
+
const codex = result.agents.find((a) => a.id === "codex");
|
|
179
|
+
expect(codex).toBeDefined();
|
|
180
|
+
expect(codex!.installed).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("handles version output on stderr", async () => {
|
|
184
|
+
setupExecFileMock({
|
|
185
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
186
|
+
"version:claude": { stdout: "", stderr: "claude version 2.0.0\n" },
|
|
187
|
+
"which:codex": { error: new Error("not found") },
|
|
188
|
+
"command-v:codex": { error: new Error("not found") },
|
|
189
|
+
"which:gemini": { error: new Error("not found") },
|
|
190
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
191
|
+
"which:opencode": { error: new Error("not found") },
|
|
192
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
193
|
+
"which:aider": { error: new Error("not found") },
|
|
194
|
+
"command-v:aider": { error: new Error("not found") },
|
|
195
|
+
"which:goose": { error: new Error("not found") },
|
|
196
|
+
"command-v:goose": { error: new Error("not found") },
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const detector = createAgentDetector();
|
|
200
|
+
const result = await detector.detect();
|
|
201
|
+
|
|
202
|
+
const claude = result.agents.find((a) => a.id === "claude-code");
|
|
203
|
+
expect(claude!.installed).toBe(true);
|
|
204
|
+
expect(claude!.version).toBe("2.0.0");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("marks agent as installed even if version check fails", async () => {
|
|
208
|
+
setupExecFileMock({
|
|
209
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
210
|
+
"version:claude": { error: new Error("version failed") },
|
|
211
|
+
"which:codex": { error: new Error("not found") },
|
|
212
|
+
"command-v:codex": { error: new Error("not found") },
|
|
213
|
+
"which:gemini": { error: new Error("not found") },
|
|
214
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
215
|
+
"which:opencode": { error: new Error("not found") },
|
|
216
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
217
|
+
"which:aider": { error: new Error("not found") },
|
|
218
|
+
"command-v:aider": { error: new Error("not found") },
|
|
219
|
+
"which:goose": { error: new Error("not found") },
|
|
220
|
+
"command-v:goose": { error: new Error("not found") },
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const detector = createAgentDetector();
|
|
224
|
+
const result = await detector.detect();
|
|
225
|
+
|
|
226
|
+
const claude = result.agents.find((a) => a.id === "claude-code");
|
|
227
|
+
expect(claude!.installed).toBe(true);
|
|
228
|
+
expect(claude!.version).toBeUndefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("skips disabled agents", async () => {
|
|
232
|
+
setupExecFileMock({
|
|
233
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
234
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
235
|
+
"which:codex": { error: new Error("not found") },
|
|
236
|
+
"command-v:codex": { error: new Error("not found") },
|
|
237
|
+
"which:gemini": { error: new Error("not found") },
|
|
238
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
239
|
+
"which:opencode": { error: new Error("not found") },
|
|
240
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const detector = createAgentDetector({
|
|
244
|
+
disabledAgents: ["aider", "goose"],
|
|
245
|
+
});
|
|
246
|
+
const result = await detector.detect();
|
|
247
|
+
|
|
248
|
+
expect(result.scanned).toBe(4);
|
|
249
|
+
expect(result.agents.find((a) => a.id === "aider")).toBeUndefined();
|
|
250
|
+
expect(result.agents.find((a) => a.id === "goose")).toBeUndefined();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("falls back to command -v when which fails", async () => {
|
|
254
|
+
setupExecFileMock({
|
|
255
|
+
"which:claude": { error: new Error("which not found") },
|
|
256
|
+
"command-v:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
257
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
258
|
+
"which:codex": { error: new Error("not found") },
|
|
259
|
+
"command-v:codex": { error: new Error("not found") },
|
|
260
|
+
"which:gemini": { error: new Error("not found") },
|
|
261
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
262
|
+
"which:opencode": { error: new Error("not found") },
|
|
263
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
264
|
+
"which:aider": { error: new Error("not found") },
|
|
265
|
+
"command-v:aider": { error: new Error("not found") },
|
|
266
|
+
"which:goose": { error: new Error("not found") },
|
|
267
|
+
"command-v:goose": { error: new Error("not found") },
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const detector = createAgentDetector();
|
|
271
|
+
const result = await detector.detect();
|
|
272
|
+
|
|
273
|
+
const claude = result.agents.find((a) => a.id === "claude-code");
|
|
274
|
+
expect(claude!.installed).toBe(true);
|
|
275
|
+
expect(claude!.path).toBe("/usr/local/bin/claude");
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// ===========================================================================
|
|
280
|
+
// Caching
|
|
281
|
+
// ===========================================================================
|
|
282
|
+
|
|
283
|
+
describe("caching", () => {
|
|
284
|
+
it("returns cached result on subsequent calls", async () => {
|
|
285
|
+
setupExecFileMock({
|
|
286
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
287
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
288
|
+
"which:codex": { error: new Error("not found") },
|
|
289
|
+
"command-v:codex": { error: new Error("not found") },
|
|
290
|
+
"which:gemini": { error: new Error("not found") },
|
|
291
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
292
|
+
"which:opencode": { error: new Error("not found") },
|
|
293
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
294
|
+
"which:aider": { error: new Error("not found") },
|
|
295
|
+
"command-v:aider": { error: new Error("not found") },
|
|
296
|
+
"which:goose": { error: new Error("not found") },
|
|
297
|
+
"command-v:goose": { error: new Error("not found") },
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const detector = createAgentDetector();
|
|
301
|
+
const result1 = await detector.detect();
|
|
302
|
+
const callCount = mockExecFile.mock.calls.length;
|
|
303
|
+
|
|
304
|
+
const result2 = await detector.detect();
|
|
305
|
+
// No additional execFile calls — result was cached
|
|
306
|
+
expect(mockExecFile.mock.calls.length).toBe(callCount);
|
|
307
|
+
expect(result2).toEqual(result1);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("bypasses cache when refresh is true", async () => {
|
|
311
|
+
setupExecFileMock({
|
|
312
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
313
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
314
|
+
"which:codex": { error: new Error("not found") },
|
|
315
|
+
"command-v:codex": { error: new Error("not found") },
|
|
316
|
+
"which:gemini": { error: new Error("not found") },
|
|
317
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
318
|
+
"which:opencode": { error: new Error("not found") },
|
|
319
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
320
|
+
"which:aider": { error: new Error("not found") },
|
|
321
|
+
"command-v:aider": { error: new Error("not found") },
|
|
322
|
+
"which:goose": { error: new Error("not found") },
|
|
323
|
+
"command-v:goose": { error: new Error("not found") },
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const detector = createAgentDetector();
|
|
327
|
+
await detector.detect();
|
|
328
|
+
const callCountAfterFirst = mockExecFile.mock.calls.length;
|
|
329
|
+
|
|
330
|
+
await detector.detect({ refresh: true });
|
|
331
|
+
expect(mockExecFile.mock.calls.length).toBeGreaterThan(callCountAfterFirst);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("invalidates cache manually", async () => {
|
|
335
|
+
setupExecFileMock({
|
|
336
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
337
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
338
|
+
"which:codex": { error: new Error("not found") },
|
|
339
|
+
"command-v:codex": { error: new Error("not found") },
|
|
340
|
+
"which:gemini": { error: new Error("not found") },
|
|
341
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
342
|
+
"which:opencode": { error: new Error("not found") },
|
|
343
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
344
|
+
"which:aider": { error: new Error("not found") },
|
|
345
|
+
"command-v:aider": { error: new Error("not found") },
|
|
346
|
+
"which:goose": { error: new Error("not found") },
|
|
347
|
+
"command-v:goose": { error: new Error("not found") },
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const detector = createAgentDetector();
|
|
351
|
+
await detector.detect();
|
|
352
|
+
expect(detector.getCachedResult()).not.toBeNull();
|
|
353
|
+
|
|
354
|
+
detector.invalidateCache();
|
|
355
|
+
expect(detector.getCachedResult()).toBeNull();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("expires cache after TTL", async () => {
|
|
359
|
+
setupExecFileMock({
|
|
360
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
361
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
362
|
+
"which:codex": { error: new Error("not found") },
|
|
363
|
+
"command-v:codex": { error: new Error("not found") },
|
|
364
|
+
"which:gemini": { error: new Error("not found") },
|
|
365
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
366
|
+
"which:opencode": { error: new Error("not found") },
|
|
367
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
368
|
+
"which:aider": { error: new Error("not found") },
|
|
369
|
+
"command-v:aider": { error: new Error("not found") },
|
|
370
|
+
"which:goose": { error: new Error("not found") },
|
|
371
|
+
"command-v:goose": { error: new Error("not found") },
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const detector = createAgentDetector({ cacheTtlMs: 50 });
|
|
375
|
+
await detector.detect();
|
|
376
|
+
const callCountAfterFirst = mockExecFile.mock.calls.length;
|
|
377
|
+
|
|
378
|
+
// Wait for cache to expire
|
|
379
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
380
|
+
|
|
381
|
+
await detector.detect();
|
|
382
|
+
expect(mockExecFile.mock.calls.length).toBeGreaterThan(callCountAfterFirst);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ===========================================================================
|
|
387
|
+
// getAvailableAgents()
|
|
388
|
+
// ===========================================================================
|
|
389
|
+
|
|
390
|
+
describe("getAvailableAgents()", () => {
|
|
391
|
+
it("returns only installed agents by default", async () => {
|
|
392
|
+
setupExecFileMock({
|
|
393
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
394
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
395
|
+
"which:codex": { error: new Error("not found") },
|
|
396
|
+
"command-v:codex": { error: new Error("not found") },
|
|
397
|
+
"which:gemini": { error: new Error("not found") },
|
|
398
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
399
|
+
"which:opencode": { error: new Error("not found") },
|
|
400
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
401
|
+
"which:aider": { error: new Error("not found") },
|
|
402
|
+
"command-v:aider": { error: new Error("not found") },
|
|
403
|
+
"which:goose": { error: new Error("not found") },
|
|
404
|
+
"command-v:goose": { error: new Error("not found") },
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const detector = createAgentDetector();
|
|
408
|
+
const result = await detector.getAvailableAgents();
|
|
409
|
+
|
|
410
|
+
expect(result.agents).toHaveLength(1);
|
|
411
|
+
expect(result.agents[0].id).toBe("claude-code");
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("includes not-installed agents when requested", async () => {
|
|
415
|
+
setupExecFileMock({
|
|
416
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
417
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
418
|
+
"which:codex": { error: new Error("not found") },
|
|
419
|
+
"command-v:codex": { error: new Error("not found") },
|
|
420
|
+
"which:gemini": { error: new Error("not found") },
|
|
421
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
422
|
+
"which:opencode": { error: new Error("not found") },
|
|
423
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
424
|
+
"which:aider": { error: new Error("not found") },
|
|
425
|
+
"command-v:aider": { error: new Error("not found") },
|
|
426
|
+
"which:goose": { error: new Error("not found") },
|
|
427
|
+
"command-v:goose": { error: new Error("not found") },
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const detector = createAgentDetector();
|
|
431
|
+
const result = await detector.getAvailableAgents({ includeNotInstalled: true });
|
|
432
|
+
|
|
433
|
+
expect(result.agents).toHaveLength(6);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// ===========================================================================
|
|
438
|
+
// getDefinition()
|
|
439
|
+
// ===========================================================================
|
|
440
|
+
|
|
441
|
+
describe("getDefinition()", () => {
|
|
442
|
+
it("returns definition for known agent", () => {
|
|
443
|
+
const detector = createAgentDetector();
|
|
444
|
+
const def = detector.getDefinition("claude-code");
|
|
445
|
+
expect(def.id).toBe("claude-code");
|
|
446
|
+
expect(def.binary).toBe("claude");
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("throws AgentDetectionError with UNKNOWN_AGENT code for unknown agent", () => {
|
|
450
|
+
const detector = createAgentDetector();
|
|
451
|
+
try {
|
|
452
|
+
detector.getDefinition("foo-agent");
|
|
453
|
+
expect.fail("Expected AgentDetectionError to be thrown");
|
|
454
|
+
} catch (err) {
|
|
455
|
+
expect(err).toBeInstanceOf(AgentDetectionError);
|
|
456
|
+
const error = err as AgentDetectionError;
|
|
457
|
+
expect(error.code).toBe("UNKNOWN_AGENT");
|
|
458
|
+
expect(error.agentId).toBe("foo-agent");
|
|
459
|
+
expect(error.message).toBe("Unknown agent backend: foo-agent");
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// ===========================================================================
|
|
465
|
+
// isInstalled()
|
|
466
|
+
// ===========================================================================
|
|
467
|
+
|
|
468
|
+
describe("isInstalled()", () => {
|
|
469
|
+
it("returns true for installed agent", async () => {
|
|
470
|
+
setupExecFileMock({
|
|
471
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
472
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
473
|
+
"which:codex": { error: new Error("not found") },
|
|
474
|
+
"command-v:codex": { error: new Error("not found") },
|
|
475
|
+
"which:gemini": { error: new Error("not found") },
|
|
476
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
477
|
+
"which:opencode": { error: new Error("not found") },
|
|
478
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
479
|
+
"which:aider": { error: new Error("not found") },
|
|
480
|
+
"command-v:aider": { error: new Error("not found") },
|
|
481
|
+
"which:goose": { error: new Error("not found") },
|
|
482
|
+
"command-v:goose": { error: new Error("not found") },
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const detector = createAgentDetector();
|
|
486
|
+
expect(await detector.isInstalled("claude-code")).toBe(true);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("returns false for non-installed agent", async () => {
|
|
490
|
+
setupExecFileMock({
|
|
491
|
+
"which:claude": { error: new Error("not found") },
|
|
492
|
+
"command-v:claude": { error: new Error("not found") },
|
|
493
|
+
"which:codex": { error: new Error("not found") },
|
|
494
|
+
"command-v:codex": { error: new Error("not found") },
|
|
495
|
+
"which:gemini": { error: new Error("not found") },
|
|
496
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
497
|
+
"which:opencode": { error: new Error("not found") },
|
|
498
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
499
|
+
"which:aider": { error: new Error("not found") },
|
|
500
|
+
"command-v:aider": { error: new Error("not found") },
|
|
501
|
+
"which:goose": { error: new Error("not found") },
|
|
502
|
+
"command-v:goose": { error: new Error("not found") },
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const detector = createAgentDetector();
|
|
506
|
+
expect(await detector.isInstalled("codex")).toBe(false);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// ===========================================================================
|
|
511
|
+
// getAgent()
|
|
512
|
+
// ===========================================================================
|
|
513
|
+
|
|
514
|
+
describe("getAgent()", () => {
|
|
515
|
+
it("returns a specific detected agent by ID", async () => {
|
|
516
|
+
setupExecFileMock({
|
|
517
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
518
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
519
|
+
"which:codex": { error: new Error("not found") },
|
|
520
|
+
"command-v:codex": { error: new Error("not found") },
|
|
521
|
+
"which:gemini": { error: new Error("not found") },
|
|
522
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
523
|
+
"which:opencode": { error: new Error("not found") },
|
|
524
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
525
|
+
"which:aider": { error: new Error("not found") },
|
|
526
|
+
"command-v:aider": { error: new Error("not found") },
|
|
527
|
+
"which:goose": { error: new Error("not found") },
|
|
528
|
+
"command-v:goose": { error: new Error("not found") },
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const detector = createAgentDetector();
|
|
532
|
+
const agent = await detector.getAgent("claude-code");
|
|
533
|
+
expect(agent).toBeDefined();
|
|
534
|
+
expect(agent!.id).toBe("claude-code");
|
|
535
|
+
expect(agent!.installed).toBe(true);
|
|
536
|
+
expect(agent!.version).toBe("1.0.0");
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("returns undefined for unknown agent ID", async () => {
|
|
540
|
+
setupExecFileMock({
|
|
541
|
+
"which:claude": { error: new Error("not found") },
|
|
542
|
+
"command-v:claude": { error: new Error("not found") },
|
|
543
|
+
"which:codex": { error: new Error("not found") },
|
|
544
|
+
"command-v:codex": { error: new Error("not found") },
|
|
545
|
+
"which:gemini": { error: new Error("not found") },
|
|
546
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
547
|
+
"which:opencode": { error: new Error("not found") },
|
|
548
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
549
|
+
"which:aider": { error: new Error("not found") },
|
|
550
|
+
"command-v:aider": { error: new Error("not found") },
|
|
551
|
+
"which:goose": { error: new Error("not found") },
|
|
552
|
+
"command-v:goose": { error: new Error("not found") },
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const detector = createAgentDetector();
|
|
556
|
+
const agent = await detector.getAgent("nonexistent");
|
|
557
|
+
expect(agent).toBeUndefined();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("returns not-installed agent entry by ID", async () => {
|
|
561
|
+
setupExecFileMock({
|
|
562
|
+
"which:claude": { error: new Error("not found") },
|
|
563
|
+
"command-v:claude": { error: new Error("not found") },
|
|
564
|
+
"which:codex": { error: new Error("not found") },
|
|
565
|
+
"command-v:codex": { error: new Error("not found") },
|
|
566
|
+
"which:gemini": { error: new Error("not found") },
|
|
567
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
568
|
+
"which:opencode": { error: new Error("not found") },
|
|
569
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
570
|
+
"which:aider": { error: new Error("not found") },
|
|
571
|
+
"command-v:aider": { error: new Error("not found") },
|
|
572
|
+
"which:goose": { error: new Error("not found") },
|
|
573
|
+
"command-v:goose": { error: new Error("not found") },
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const detector = createAgentDetector();
|
|
577
|
+
const agent = await detector.getAgent("codex");
|
|
578
|
+
expect(agent).toBeDefined();
|
|
579
|
+
expect(agent!.id).toBe("codex");
|
|
580
|
+
expect(agent!.installed).toBe(false);
|
|
581
|
+
expect(agent!.version).toBeUndefined();
|
|
582
|
+
expect(agent!.path).toBeUndefined();
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// ===========================================================================
|
|
587
|
+
// All agents not found (no crash)
|
|
588
|
+
// ===========================================================================
|
|
589
|
+
|
|
590
|
+
describe("detection with no agents installed", () => {
|
|
591
|
+
it("handles all agents not found without crashing", async () => {
|
|
592
|
+
setupExecFileMock({
|
|
593
|
+
"which:claude": { error: new Error("not found") },
|
|
594
|
+
"command-v:claude": { error: new Error("not found") },
|
|
595
|
+
"which:codex": { error: new Error("not found") },
|
|
596
|
+
"command-v:codex": { error: new Error("not found") },
|
|
597
|
+
"which:gemini": { error: new Error("not found") },
|
|
598
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
599
|
+
"which:opencode": { error: new Error("not found") },
|
|
600
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
601
|
+
"which:aider": { error: new Error("not found") },
|
|
602
|
+
"command-v:aider": { error: new Error("not found") },
|
|
603
|
+
"which:goose": { error: new Error("not found") },
|
|
604
|
+
"command-v:goose": { error: new Error("not found") },
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const detector = createAgentDetector();
|
|
608
|
+
const result = await detector.detect();
|
|
609
|
+
|
|
610
|
+
expect(result.scanned).toBe(6);
|
|
611
|
+
expect(result.agents).toHaveLength(6);
|
|
612
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
613
|
+
|
|
614
|
+
// All agents should be marked as not installed
|
|
615
|
+
for (const agent of result.agents) {
|
|
616
|
+
expect(agent.installed).toBe(false);
|
|
617
|
+
expect(agent.version).toBeUndefined();
|
|
618
|
+
expect(agent.path).toBeUndefined();
|
|
619
|
+
expect(agent.definition).toBeDefined();
|
|
620
|
+
expect(agent.detectedAt).toBeGreaterThan(0);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it("getAvailableAgents returns empty list when nothing installed", async () => {
|
|
625
|
+
setupExecFileMock({
|
|
626
|
+
"which:claude": { error: new Error("not found") },
|
|
627
|
+
"command-v:claude": { error: new Error("not found") },
|
|
628
|
+
"which:codex": { error: new Error("not found") },
|
|
629
|
+
"command-v:codex": { error: new Error("not found") },
|
|
630
|
+
"which:gemini": { error: new Error("not found") },
|
|
631
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
632
|
+
"which:opencode": { error: new Error("not found") },
|
|
633
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
634
|
+
"which:aider": { error: new Error("not found") },
|
|
635
|
+
"command-v:aider": { error: new Error("not found") },
|
|
636
|
+
"which:goose": { error: new Error("not found") },
|
|
637
|
+
"command-v:goose": { error: new Error("not found") },
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
const detector = createAgentDetector();
|
|
641
|
+
const result = await detector.getAvailableAgents();
|
|
642
|
+
expect(result.agents).toHaveLength(0);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// ===========================================================================
|
|
647
|
+
// Concurrent detection deduplication
|
|
648
|
+
// ===========================================================================
|
|
649
|
+
|
|
650
|
+
describe("concurrent detection deduplication", () => {
|
|
651
|
+
it("deduplicates concurrent detect() calls into a single scan", async () => {
|
|
652
|
+
setupExecFileMock({
|
|
653
|
+
"which:claude": { stdout: "/usr/local/bin/claude\n" },
|
|
654
|
+
"version:claude": { stdout: "1.0.0\n" },
|
|
655
|
+
"which:codex": { error: new Error("not found") },
|
|
656
|
+
"command-v:codex": { error: new Error("not found") },
|
|
657
|
+
"which:gemini": { error: new Error("not found") },
|
|
658
|
+
"command-v:gemini": { error: new Error("not found") },
|
|
659
|
+
"which:opencode": { error: new Error("not found") },
|
|
660
|
+
"command-v:opencode": { error: new Error("not found") },
|
|
661
|
+
"which:aider": { error: new Error("not found") },
|
|
662
|
+
"command-v:aider": { error: new Error("not found") },
|
|
663
|
+
"which:goose": { error: new Error("not found") },
|
|
664
|
+
"command-v:goose": { error: new Error("not found") },
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const detector = createAgentDetector();
|
|
668
|
+
|
|
669
|
+
// Fire two detect() calls concurrently (no await)
|
|
670
|
+
const promise1 = detector.detect({ refresh: true });
|
|
671
|
+
const promise2 = detector.detect({ refresh: true });
|
|
672
|
+
|
|
673
|
+
const [result1, result2] = await Promise.all([promise1, promise2]);
|
|
674
|
+
|
|
675
|
+
// Both should return the same result object
|
|
676
|
+
expect(result1).toEqual(result2);
|
|
677
|
+
|
|
678
|
+
// execFile should only be called once per agent (not doubled),
|
|
679
|
+
// meaning only one scan ran. Each agent needs 1-2 calls (which + maybe version).
|
|
680
|
+
// With 6 agents, a single scan calls which for each (6 calls) plus
|
|
681
|
+
// version for installed ones. Two scans would roughly double the call count.
|
|
682
|
+
// We check that the total is consistent with a single scan.
|
|
683
|
+
const totalCalls = mockExecFile.mock.calls.length;
|
|
684
|
+
// A single scan: 6 which calls + some fallback/version calls.
|
|
685
|
+
// Should be well under 20 for a single scan (two scans would be ~24+).
|
|
686
|
+
expect(totalCalls).toBeLessThanOrEqual(14);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("isDetecting() returns true during active detection", async () => {
|
|
690
|
+
let resolveWhich: ((value: unknown) => void) | undefined;
|
|
691
|
+
const whichPromise = new Promise((resolve) => {
|
|
692
|
+
resolveWhich = resolve;
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Make the which call hang so we can check isDetecting()
|
|
696
|
+
mockExecFile.mockImplementation(((
|
|
697
|
+
_command: string,
|
|
698
|
+
_args: string[],
|
|
699
|
+
_options: unknown,
|
|
700
|
+
callback: (err: Error | null, result: { stdout: string; stderr: string }) => void
|
|
701
|
+
) => {
|
|
702
|
+
whichPromise.then(() => {
|
|
703
|
+
callback(new Error("not found"), { stdout: "", stderr: "" });
|
|
704
|
+
});
|
|
705
|
+
}) as unknown as typeof execFile);
|
|
706
|
+
|
|
707
|
+
const detector = createAgentDetector();
|
|
708
|
+
const detectPromise = detector.detect();
|
|
709
|
+
|
|
710
|
+
// While the mock is still pending, isDetecting should be true
|
|
711
|
+
expect(detector.isDetecting()).toBe(true);
|
|
712
|
+
|
|
713
|
+
// Resolve the hanging mock
|
|
714
|
+
resolveWhich!(undefined);
|
|
715
|
+
await detectPromise;
|
|
716
|
+
|
|
717
|
+
expect(detector.isDetecting()).toBe(false);
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// ===========================================================================
|
|
722
|
+
// AgentDetectionError
|
|
723
|
+
// ===========================================================================
|
|
724
|
+
|
|
725
|
+
describe("AgentDetectionError", () => {
|
|
726
|
+
it("has correct name property", () => {
|
|
727
|
+
const error = new AgentDetectionError("test", "UNKNOWN_AGENT", "foo");
|
|
728
|
+
expect(error.name).toBe("AgentDetectionError");
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it("has correct code property", () => {
|
|
732
|
+
const error = new AgentDetectionError("test", "AGENT_NOT_INSTALLED", "bar");
|
|
733
|
+
expect(error.code).toBe("AGENT_NOT_INSTALLED");
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it("has correct agentId property", () => {
|
|
737
|
+
const error = new AgentDetectionError("test", "UNKNOWN_AGENT", "my-agent");
|
|
738
|
+
expect(error.agentId).toBe("my-agent");
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it("has correct message", () => {
|
|
742
|
+
const error = new AgentDetectionError("Something went wrong", "DETECTION_FAILED");
|
|
743
|
+
expect(error.message).toBe("Something went wrong");
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it("is an instance of Error", () => {
|
|
747
|
+
const error = new AgentDetectionError("test", "UNKNOWN_AGENT");
|
|
748
|
+
expect(error).toBeInstanceOf(Error);
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it("works without agentId", () => {
|
|
752
|
+
const error = new AgentDetectionError("test", "DETECTION_TIMEOUT");
|
|
753
|
+
expect(error.agentId).toBeUndefined();
|
|
754
|
+
expect(error.code).toBe("DETECTION_TIMEOUT");
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// ===========================================================================
|
|
759
|
+
// isDetecting()
|
|
760
|
+
// ===========================================================================
|
|
761
|
+
|
|
762
|
+
describe("isDetecting()", () => {
|
|
763
|
+
it("returns false when not detecting", () => {
|
|
764
|
+
const detector = createAgentDetector();
|
|
765
|
+
expect(detector.isDetecting()).toBe(false);
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
});
|