macro-agent 0.0.10 → 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 +56 -51
- package/.sudocode/specs.jsonl +8 -1
- package/CLAUDE.md +121 -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 +40 -1
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +172 -8
- 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/wake.d.ts +15 -0
- package/dist/agent/wake.d.ts.map +1 -1
- package/dist/agent/wake.js +15 -0
- package/dist/agent/wake.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 +5 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +362 -0
- package/dist/api/server.js.map +1 -1
- package/dist/api/types.d.ts +50 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/cli/acp.d.ts +2 -0
- package/dist/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +8 -1
- package/dist/cli/acp.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/mail/conversation-map.d.ts +33 -0
- package/dist/mail/conversation-map.d.ts.map +1 -0
- package/dist/mail/conversation-map.js +61 -0
- package/dist/mail/conversation-map.js.map +1 -0
- package/dist/mail/index.d.ts +11 -0
- package/dist/mail/index.d.ts.map +1 -0
- package/dist/mail/index.js +11 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/mail/mail-service.d.ts +85 -0
- package/dist/mail/mail-service.d.ts.map +1 -0
- package/dist/mail/mail-service.js +121 -0
- package/dist/mail/mail-service.js.map +1 -0
- package/dist/mail/stores/eventstore-conversation-store.d.ts +40 -0
- package/dist/mail/stores/eventstore-conversation-store.d.ts.map +1 -0
- package/dist/mail/stores/eventstore-conversation-store.js +131 -0
- package/dist/mail/stores/eventstore-conversation-store.js.map +1 -0
- package/dist/mail/stores/eventstore-participant-store.d.ts +43 -0
- package/dist/mail/stores/eventstore-participant-store.d.ts.map +1 -0
- package/dist/mail/stores/eventstore-participant-store.js +145 -0
- package/dist/mail/stores/eventstore-participant-store.js.map +1 -0
- package/dist/mail/stores/eventstore-thread-store.d.ts +46 -0
- package/dist/mail/stores/eventstore-thread-store.d.ts.map +1 -0
- package/dist/mail/stores/eventstore-thread-store.js +118 -0
- package/dist/mail/stores/eventstore-thread-store.js.map +1 -0
- package/dist/mail/stores/eventstore-turn-store.d.ts +47 -0
- package/dist/mail/stores/eventstore-turn-store.d.ts.map +1 -0
- package/dist/mail/stores/eventstore-turn-store.js +153 -0
- package/dist/mail/stores/eventstore-turn-store.js.map +1 -0
- package/dist/mail/stores/index.d.ts +12 -0
- package/dist/mail/stores/index.d.ts.map +1 -0
- package/dist/mail/stores/index.js +12 -0
- package/dist/mail/stores/index.js.map +1 -0
- package/dist/mail/stores/types.d.ts +146 -0
- package/dist/mail/stores/types.d.ts.map +1 -0
- package/dist/mail/stores/types.js +13 -0
- package/dist/mail/stores/types.js.map +1 -0
- package/dist/mail/turn-recorder.d.ts +30 -0
- package/dist/mail/turn-recorder.d.ts.map +1 -0
- package/dist/mail/turn-recorder.js +98 -0
- package/dist/mail/turn-recorder.js.map +1 -0
- 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 +4 -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/map/adapter/mail-handler-adapter.d.ts +27 -0
- package/dist/map/adapter/mail-handler-adapter.d.ts.map +1 -0
- package/dist/map/adapter/mail-handler-adapter.js +292 -0
- package/dist/map/adapter/mail-handler-adapter.js.map +1 -0
- package/dist/map/adapter/map-adapter.d.ts +34 -10
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +110 -14
- package/dist/map/adapter/map-adapter.js.map +1 -1
- package/dist/map/adapter/rpc-handler.d.ts +4 -1
- package/dist/map/adapter/rpc-handler.d.ts.map +1 -1
- package/dist/map/adapter/rpc-handler.js +6 -0
- package/dist/map/adapter/rpc-handler.js.map +1 -1
- package/dist/map/index.d.ts +1 -0
- package/dist/map/index.d.ts.map +1 -1
- package/dist/map/index.js +2 -0
- package/dist/map/index.js.map +1 -1
- package/dist/map/types.d.ts +3 -1
- package/dist/map/types.d.ts.map +1 -1
- package/dist/map/types.js.map +1 -1
- 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 +15 -2
- package/dist/mcp/tools/done.d.ts.map +1 -1
- package/dist/mcp/tools/done.js +45 -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/channels.d.ts +2 -4
- package/dist/router/channels.d.ts.map +1 -1
- package/dist/router/channels.js.map +1 -1
- package/dist/router/message-router.d.ts +85 -9
- package/dist/router/message-router.d.ts.map +1 -1
- package/dist/router/message-router.js +203 -14
- package/dist/router/message-router.js.map +1 -1
- package/dist/router/role-resolver.d.ts +10 -1
- package/dist/router/role-resolver.d.ts.map +1 -1
- package/dist/router/role-resolver.js +15 -1
- package/dist/router/role-resolver.js.map +1 -1
- package/dist/router/types.d.ts +30 -1
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.js.map +1 -1
- package/dist/server/combined-server.d.ts +6 -0
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +24 -2
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.d.ts +14 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +456 -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/conversations.d.ts +91 -0
- package/dist/store/types/conversations.d.ts.map +1 -0
- package/dist/store/types/conversations.js +8 -0
- package/dist/store/types/conversations.js.map +1 -0
- 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 +2 -0
- package/dist/store/types/index.d.ts.map +1 -1
- package/dist/store/types/index.js +2 -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/trigger/router/trigger-router.d.ts +30 -3
- package/dist/trigger/router/trigger-router.d.ts.map +1 -1
- package/dist/trigger/router/trigger-router.js +30 -3
- package/dist/trigger/router/trigger-router.js.map +1 -1
- package/dist/trigger/wake/types.d.ts +31 -5
- package/dist/trigger/wake/types.d.ts.map +1 -1
- package/dist/trigger/wake/types.js +19 -0
- package/dist/trigger/wake/types.js.map +1 -1
- 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/mail-integration.md +608 -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 +5 -3
- 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 +278 -6
- package/src/agent/types.ts +27 -0
- package/src/agent/wake.ts +15 -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/__tests__/conversation-api.test.ts +468 -0
- package/src/api/server.ts +425 -1
- package/src/api/types.ts +64 -1
- package/src/cli/acp.ts +9 -1
- 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/mail/__tests__/conversation-lifecycle.test.ts +409 -0
- package/src/mail/__tests__/eventstore-stores.test.ts +1073 -0
- package/src/mail/__tests__/mail-full-agent.e2e.test.ts +575 -0
- package/src/mail/__tests__/mail-integration.test.ts +759 -0
- package/src/mail/__tests__/mail-map-protocol.e2e.test.ts +1068 -0
- package/src/mail/__tests__/mail-service.test.ts +506 -0
- package/src/mail/__tests__/turn-recorder.test.ts +328 -0
- package/src/mail/conversation-map.ts +107 -0
- package/src/mail/index.ts +25 -0
- package/src/mail/mail-service.ts +257 -0
- package/src/mail/stores/eventstore-conversation-store.ts +146 -0
- package/src/mail/stores/eventstore-participant-store.ts +172 -0
- package/src/mail/stores/eventstore-thread-store.ts +129 -0
- package/src/mail/stores/eventstore-turn-store.ts +173 -0
- package/src/mail/stores/index.ts +12 -0
- package/src/mail/stores/types.ts +160 -0
- package/src/mail/turn-recorder.ts +124 -0
- package/src/map/README.md +79 -0
- package/src/map/adapter/__tests__/extensions.test.ts +359 -0
- package/src/map/adapter/__tests__/map-adapter.test.ts +90 -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 +4 -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/map/adapter/mail-handler-adapter.ts +429 -0
- package/src/map/adapter/map-adapter.ts +173 -27
- package/src/map/adapter/rpc-handler.ts +8 -1
- package/src/map/index.ts +3 -0
- package/src/map/types.ts +3 -1
- package/src/mcp/mcp-server.ts +67 -0
- package/src/mcp/tools/claim_task.ts +86 -0
- package/src/mcp/tools/done.ts +59 -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/README.md +120 -0
- package/src/router/__tests__/message-router.test.ts +561 -0
- package/src/router/channels.ts +3 -4
- package/src/router/message-router.ts +308 -22
- package/src/router/role-resolver.ts +22 -1
- package/src/router/types.ts +36 -1
- package/src/server/combined-server.ts +36 -2
- package/src/store/README.md +134 -0
- package/src/store/event-store.ts +546 -3
- package/src/store/types/agents.ts +1 -1
- package/src/store/types/conversations.ts +129 -0
- package/src/store/types/events.ts +5 -1
- package/src/store/types/index.ts +2 -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/trigger/router/trigger-router.ts +30 -3
- package/src/trigger/wake/types.ts +32 -5
- package/src/trigger/wake/wake-manager.ts +2 -2
- 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/openspec/AGENTS.md +0 -456
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/design.md +0 -128
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/proposal.md +0 -49
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/agent-manager/spec.md +0 -150
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/cli-api/spec.md +0 -258
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/event-store/spec.md +0 -160
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/mcp-tools/spec.md +0 -224
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/message-router/spec.md +0 -153
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/task-manager/spec.md +0 -136
- package/openspec/changes/archive/2025-12-21-add-mvp-foundation/tasks.md +0 -147
- package/openspec/project.md +0 -31
- package/openspec/specs/agent-manager/spec.md +0 -154
- package/openspec/specs/cli-api/spec.md +0 -262
- package/openspec/specs/event-store/spec.md +0 -164
- package/openspec/specs/mcp-tools/spec.md +0 -228
- package/openspec/specs/message-router/spec.md +0 -157
- package/openspec/specs/task-manager/spec.md +0 -140
- 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 -82
- package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -9
- 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 -460
- 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/package-lock.json +0 -3239
- package/references/multi-agent-protocol/package.json +0 -56
- package/references/multi-agent-protocol/schema/meta.json +0 -337
- package/references/multi-agent-protocol/schema/schema.json +0 -1828
|
@@ -0,0 +1,983 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Subsystem Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests interactions between subsystems:
|
|
5
|
+
* - Strategy ↔ Worker Handler
|
|
6
|
+
* - Task Backend claim/unclaim cycle
|
|
7
|
+
* - Team Config → Spawn Interceptor → Worker Done pipeline
|
|
8
|
+
* - Metrics ↔ EventStore realistic events
|
|
9
|
+
* - Pull mode lifecycle ↔ handler dispatch
|
|
10
|
+
* - Role resolution ↔ capability gating
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
14
|
+
import * as path from "path";
|
|
15
|
+
import { loadTeam } from "../team-loader.js";
|
|
16
|
+
import { TeamRuntime, type TeamServices } from "../team-runtime.js";
|
|
17
|
+
import { DefaultRoleRegistry } from "../../roles/registry.js";
|
|
18
|
+
import {
|
|
19
|
+
handleWorkerDone,
|
|
20
|
+
type WorkerHandlerDeps,
|
|
21
|
+
} from "../../lifecycle/handlers/worker.js";
|
|
22
|
+
import {
|
|
23
|
+
dispatchDone,
|
|
24
|
+
createHandlerRegistry,
|
|
25
|
+
type AllHandlerDeps,
|
|
26
|
+
} from "../../lifecycle/handlers/index.js";
|
|
27
|
+
import type {
|
|
28
|
+
LifecycleContext,
|
|
29
|
+
DoneArgs,
|
|
30
|
+
CleanupStatus,
|
|
31
|
+
} from "../../lifecycle/types.js";
|
|
32
|
+
import type {
|
|
33
|
+
IntegrationStrategy,
|
|
34
|
+
LandRequest,
|
|
35
|
+
LandResult,
|
|
36
|
+
} from "../../workspace/strategies/types.js";
|
|
37
|
+
import { defaultStrategyRegistry } from "../../workspace/strategies/registry.js";
|
|
38
|
+
import {
|
|
39
|
+
createClaimTaskHandler,
|
|
40
|
+
type ClaimTaskDeps,
|
|
41
|
+
} from "../../mcp/tools/claim_task.js";
|
|
42
|
+
import {
|
|
43
|
+
createUnclaimTaskHandler,
|
|
44
|
+
type UnclaimTaskDeps,
|
|
45
|
+
} from "../../mcp/tools/unclaim_task.js";
|
|
46
|
+
import {
|
|
47
|
+
createListClaimableTasksHandler,
|
|
48
|
+
} from "../../mcp/tools/list_claimable_tasks.js";
|
|
49
|
+
import type { TaskBackend, ClaimFilter, ExtendedTask } from "../../task/backend/types.js";
|
|
50
|
+
import type { ToolContext } from "../../mcp/types.js";
|
|
51
|
+
import {
|
|
52
|
+
getThroughputMetrics,
|
|
53
|
+
getUtilizationMetrics,
|
|
54
|
+
getErrorMetrics,
|
|
55
|
+
} from "../../metrics/index.js";
|
|
56
|
+
import {
|
|
57
|
+
TASK_CAPABILITIES,
|
|
58
|
+
CAPABILITY_TOOL_MAP,
|
|
59
|
+
} from "../../roles/capabilities.js";
|
|
60
|
+
import type { AgentManager, SpawnInterceptor } from "../../agent/agent-manager.js";
|
|
61
|
+
import type { MessageRouter } from "../../router/message-router.js";
|
|
62
|
+
import type { EventStore } from "../../store/event-store.js";
|
|
63
|
+
import type { SpawnAgentOptions } from "../../agent/types.js";
|
|
64
|
+
import type { Event, Agent } from "../../store/types/index.js";
|
|
65
|
+
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// Shared Helpers
|
|
68
|
+
// =============================================================================
|
|
69
|
+
|
|
70
|
+
const PROJECT_ROOT = path.resolve(import.meta.dirname, "../../..");
|
|
71
|
+
|
|
72
|
+
function createMockEventStore(events: Event[] = []): EventStore & { _events: Event[] } {
|
|
73
|
+
return {
|
|
74
|
+
emit: vi.fn((input: Record<string, unknown>) => {
|
|
75
|
+
const event = {
|
|
76
|
+
id: `evt_${events.length}`,
|
|
77
|
+
type: input.type,
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
source: input.source,
|
|
80
|
+
target: input.target,
|
|
81
|
+
payload: input.payload,
|
|
82
|
+
} as unknown as Event;
|
|
83
|
+
events.push(event);
|
|
84
|
+
return event;
|
|
85
|
+
}),
|
|
86
|
+
persist: vi.fn().mockResolvedValue(undefined),
|
|
87
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
88
|
+
query: vi.fn((filter: { type?: string; after?: number }) => {
|
|
89
|
+
return events.filter((e) => {
|
|
90
|
+
if (filter.type && e.type !== filter.type) return false;
|
|
91
|
+
if (filter.after && e.timestamp < filter.after) return false;
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
}),
|
|
95
|
+
getAgent: vi.fn().mockReturnValue(null),
|
|
96
|
+
getTask: vi.fn().mockReturnValue(null),
|
|
97
|
+
listAgents: vi.fn().mockReturnValue([]),
|
|
98
|
+
onAgentChange: vi.fn(),
|
|
99
|
+
onTaskChange: vi.fn(),
|
|
100
|
+
instanceId: "test-instance",
|
|
101
|
+
_events: events,
|
|
102
|
+
} as unknown as EventStore & { _events: Event[] };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createMockMessageRouter(): MessageRouter {
|
|
106
|
+
return {
|
|
107
|
+
sendToAddress: vi.fn().mockResolvedValue({ delivered: true }),
|
|
108
|
+
emitStatus: vi.fn(),
|
|
109
|
+
getMessages: vi.fn().mockReturnValue([]),
|
|
110
|
+
subscribe: vi.fn(),
|
|
111
|
+
unsubscribe: vi.fn(),
|
|
112
|
+
getSubscriptions: vi.fn().mockReturnValue([]),
|
|
113
|
+
setupDefaultSubscriptions: vi.fn(),
|
|
114
|
+
} as unknown as MessageRouter;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function createMockStrategy(
|
|
118
|
+
name: string,
|
|
119
|
+
landResult: LandResult = { status: "landed", commitHash: "abc123" }
|
|
120
|
+
): IntegrationStrategy & { land: ReturnType<typeof vi.fn> } {
|
|
121
|
+
return {
|
|
122
|
+
name,
|
|
123
|
+
land: vi.fn().mockResolvedValue(landResult),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function createWorkerContext(overrides: Partial<LifecycleContext> = {}): LifecycleContext {
|
|
128
|
+
return {
|
|
129
|
+
agentId: "worker-1",
|
|
130
|
+
role: "worker",
|
|
131
|
+
taskId: "task-1",
|
|
132
|
+
workspacePath: "/tmp/test-workspace",
|
|
133
|
+
branch: "feature/test",
|
|
134
|
+
integrationBranch: "integration",
|
|
135
|
+
streamId: "stream-1",
|
|
136
|
+
...overrides,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createCleanupStatus(): CleanupStatus {
|
|
141
|
+
return { ready: true };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// =============================================================================
|
|
145
|
+
// Tests: Strategy ↔ Worker Handler Integration
|
|
146
|
+
// =============================================================================
|
|
147
|
+
|
|
148
|
+
describe("Strategy ↔ Worker Handler", () => {
|
|
149
|
+
let messageRouter: MessageRouter;
|
|
150
|
+
let agentManager: AgentManager;
|
|
151
|
+
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
messageRouter = createMockMessageRouter();
|
|
154
|
+
agentManager = {
|
|
155
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
156
|
+
} as unknown as AgentManager;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("dispatches to integration strategy when configured", async () => {
|
|
160
|
+
const strategy = createMockStrategy("trunk");
|
|
161
|
+
const deps: WorkerHandlerDeps = {
|
|
162
|
+
messageRouter,
|
|
163
|
+
agentManager,
|
|
164
|
+
integrationStrategy: strategy,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const context = createWorkerContext();
|
|
168
|
+
const args: DoneArgs = { status: "completed", summary: "Done" };
|
|
169
|
+
|
|
170
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
171
|
+
|
|
172
|
+
expect(strategy.land).toHaveBeenCalledTimes(1);
|
|
173
|
+
expect(strategy.land).toHaveBeenCalledWith(
|
|
174
|
+
expect.objectContaining({
|
|
175
|
+
sourceBranch: "feature/test",
|
|
176
|
+
targetBranch: "integration",
|
|
177
|
+
workspacePath: "/tmp/test-workspace",
|
|
178
|
+
agentId: "worker-1",
|
|
179
|
+
taskId: "task-1",
|
|
180
|
+
streamId: "stream-1",
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
expect(result.signalsEmitted).toContain("WORKER_INTEGRATED");
|
|
184
|
+
expect(result.shouldTerminate).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("falls back to merge queue when no strategy configured", async () => {
|
|
188
|
+
const mergeQueue = {
|
|
189
|
+
submit: vi.fn().mockReturnValue("mr-1"),
|
|
190
|
+
};
|
|
191
|
+
const deps: WorkerHandlerDeps = {
|
|
192
|
+
messageRouter,
|
|
193
|
+
agentManager,
|
|
194
|
+
mergeQueue: mergeQueue as any,
|
|
195
|
+
// No integrationStrategy
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const context = createWorkerContext();
|
|
199
|
+
const args: DoneArgs = { status: "completed" };
|
|
200
|
+
|
|
201
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
202
|
+
|
|
203
|
+
expect(mergeQueue.submit).toHaveBeenCalledTimes(1);
|
|
204
|
+
expect(result.signalsEmitted).toContain("MERGE_REQUEST");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("strategy takes priority over merge queue", async () => {
|
|
208
|
+
const strategy = createMockStrategy("trunk");
|
|
209
|
+
const mergeQueue = { submit: vi.fn() };
|
|
210
|
+
const deps: WorkerHandlerDeps = {
|
|
211
|
+
messageRouter,
|
|
212
|
+
agentManager,
|
|
213
|
+
integrationStrategy: strategy,
|
|
214
|
+
mergeQueue: mergeQueue as any,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const context = createWorkerContext();
|
|
218
|
+
const args: DoneArgs = { status: "completed" };
|
|
219
|
+
|
|
220
|
+
await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
221
|
+
|
|
222
|
+
expect(strategy.land).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(mergeQueue.submit).not.toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("handles strategy conflict result gracefully", async () => {
|
|
227
|
+
const strategy = createMockStrategy("trunk", {
|
|
228
|
+
status: "conflict",
|
|
229
|
+
conflictFiles: ["src/foo.ts"],
|
|
230
|
+
error: "Rebase conflict after 3 retries",
|
|
231
|
+
});
|
|
232
|
+
const deps: WorkerHandlerDeps = {
|
|
233
|
+
messageRouter,
|
|
234
|
+
agentManager,
|
|
235
|
+
integrationStrategy: strategy,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const context = createWorkerContext();
|
|
239
|
+
const args: DoneArgs = { status: "completed" };
|
|
240
|
+
|
|
241
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
242
|
+
|
|
243
|
+
expect(result.warnings).toBeDefined();
|
|
244
|
+
expect(result.warnings!.some((w) => w.includes("conflict"))).toBe(true);
|
|
245
|
+
expect(result.signalsEmitted).not.toContain("WORKER_INTEGRATED");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("handles strategy failure result gracefully", async () => {
|
|
249
|
+
const strategy = createMockStrategy("trunk", {
|
|
250
|
+
status: "failed",
|
|
251
|
+
error: "Push rejected",
|
|
252
|
+
});
|
|
253
|
+
const deps: WorkerHandlerDeps = {
|
|
254
|
+
messageRouter,
|
|
255
|
+
agentManager,
|
|
256
|
+
integrationStrategy: strategy,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const context = createWorkerContext();
|
|
260
|
+
const args: DoneArgs = { status: "completed" };
|
|
261
|
+
|
|
262
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
263
|
+
|
|
264
|
+
expect(result.warnings).toBeDefined();
|
|
265
|
+
expect(result.warnings!.some((w) => w.includes("failed"))).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("handles strategy exception gracefully", async () => {
|
|
269
|
+
const strategy = createMockStrategy("trunk");
|
|
270
|
+
strategy.land.mockRejectedValue(new Error("Git process crashed"));
|
|
271
|
+
const deps: WorkerHandlerDeps = {
|
|
272
|
+
messageRouter,
|
|
273
|
+
agentManager,
|
|
274
|
+
integrationStrategy: strategy,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const context = createWorkerContext();
|
|
278
|
+
const args: DoneArgs = { status: "completed" };
|
|
279
|
+
|
|
280
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
281
|
+
|
|
282
|
+
// Should not throw — graceful degradation
|
|
283
|
+
expect(result.warnings).toBeDefined();
|
|
284
|
+
expect(result.warnings!.some((w) => w.includes("Git process crashed"))).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("skips strategy when status is not completed", async () => {
|
|
288
|
+
const strategy = createMockStrategy("trunk");
|
|
289
|
+
const deps: WorkerHandlerDeps = {
|
|
290
|
+
messageRouter,
|
|
291
|
+
agentManager,
|
|
292
|
+
integrationStrategy: strategy,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const context = createWorkerContext();
|
|
296
|
+
const args: DoneArgs = { status: "failed", summary: "Build failed" };
|
|
297
|
+
|
|
298
|
+
await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
299
|
+
|
|
300
|
+
expect(strategy.land).not.toHaveBeenCalled();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// =============================================================================
|
|
305
|
+
// Tests: Pull Mode Lifecycle
|
|
306
|
+
// =============================================================================
|
|
307
|
+
|
|
308
|
+
describe("Pull Mode ↔ Worker Handler", () => {
|
|
309
|
+
let messageRouter: MessageRouter;
|
|
310
|
+
let agentManager: AgentManager;
|
|
311
|
+
|
|
312
|
+
beforeEach(() => {
|
|
313
|
+
messageRouter = createMockMessageRouter();
|
|
314
|
+
agentManager = {
|
|
315
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
316
|
+
} as unknown as AgentManager;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("pull mode workers stay alive after completion", async () => {
|
|
320
|
+
const strategy = createMockStrategy("trunk");
|
|
321
|
+
const deps: WorkerHandlerDeps = {
|
|
322
|
+
messageRouter,
|
|
323
|
+
agentManager,
|
|
324
|
+
integrationStrategy: strategy,
|
|
325
|
+
taskMode: "pull",
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const context = createWorkerContext();
|
|
329
|
+
const args: DoneArgs = { status: "completed" };
|
|
330
|
+
|
|
331
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
332
|
+
|
|
333
|
+
expect(result.shouldTerminate).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("pull mode workers still terminate on failure", async () => {
|
|
337
|
+
const strategy = createMockStrategy("trunk");
|
|
338
|
+
const deps: WorkerHandlerDeps = {
|
|
339
|
+
messageRouter,
|
|
340
|
+
agentManager,
|
|
341
|
+
integrationStrategy: strategy,
|
|
342
|
+
taskMode: "pull",
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const context = createWorkerContext();
|
|
346
|
+
const args: DoneArgs = { status: "failed", summary: "Error" };
|
|
347
|
+
|
|
348
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
349
|
+
|
|
350
|
+
expect(result.shouldTerminate).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("push mode workers always terminate", async () => {
|
|
354
|
+
const strategy = createMockStrategy("trunk");
|
|
355
|
+
const deps: WorkerHandlerDeps = {
|
|
356
|
+
messageRouter,
|
|
357
|
+
agentManager,
|
|
358
|
+
integrationStrategy: strategy,
|
|
359
|
+
taskMode: "push",
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const context = createWorkerContext();
|
|
363
|
+
const args: DoneArgs = { status: "completed" };
|
|
364
|
+
|
|
365
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
366
|
+
|
|
367
|
+
expect(result.shouldTerminate).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("undefined taskMode defaults to terminate (push behavior)", async () => {
|
|
371
|
+
const strategy = createMockStrategy("trunk");
|
|
372
|
+
const deps: WorkerHandlerDeps = {
|
|
373
|
+
messageRouter,
|
|
374
|
+
agentManager,
|
|
375
|
+
integrationStrategy: strategy,
|
|
376
|
+
// taskMode undefined
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const context = createWorkerContext();
|
|
380
|
+
const args: DoneArgs = { status: "completed" };
|
|
381
|
+
|
|
382
|
+
const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
|
|
383
|
+
|
|
384
|
+
expect(result.shouldTerminate).toBe(true);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// =============================================================================
|
|
389
|
+
// Tests: Handler Registry ↔ Team Roles
|
|
390
|
+
// =============================================================================
|
|
391
|
+
|
|
392
|
+
describe("Handler Registry ↔ Team Roles", () => {
|
|
393
|
+
it("team-defined roles dispatch to correct base handler", async () => {
|
|
394
|
+
const messageRouter = createMockMessageRouter();
|
|
395
|
+
const agentManager = {
|
|
396
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
397
|
+
} as unknown as AgentManager;
|
|
398
|
+
|
|
399
|
+
const strategy = createMockStrategy("trunk");
|
|
400
|
+
const deps: AllHandlerDeps = {
|
|
401
|
+
messageRouter,
|
|
402
|
+
agentManager,
|
|
403
|
+
integrationStrategy: strategy,
|
|
404
|
+
taskMode: "pull",
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// "grinder" extends "worker" — should get the worker handler
|
|
408
|
+
const context = createWorkerContext({ role: "worker", agentId: "grinder-1" });
|
|
409
|
+
const args: DoneArgs = { status: "completed" };
|
|
410
|
+
|
|
411
|
+
const result = await dispatchDone(context, args, createCleanupStatus(), deps);
|
|
412
|
+
|
|
413
|
+
expect(strategy.land).toHaveBeenCalledTimes(1);
|
|
414
|
+
expect(result.shouldTerminate).toBe(false); // pull mode
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("monitor roles do not trigger strategy dispatch", async () => {
|
|
418
|
+
const messageRouter = createMockMessageRouter();
|
|
419
|
+
const agentManager = {
|
|
420
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
421
|
+
} as unknown as AgentManager;
|
|
422
|
+
|
|
423
|
+
const strategy = createMockStrategy("trunk");
|
|
424
|
+
const deps: AllHandlerDeps = {
|
|
425
|
+
messageRouter,
|
|
426
|
+
agentManager,
|
|
427
|
+
integrationStrategy: strategy,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const context: LifecycleContext = {
|
|
431
|
+
agentId: "judge-1",
|
|
432
|
+
role: "monitor",
|
|
433
|
+
};
|
|
434
|
+
const args: DoneArgs = { status: "completed", summary: "Health OK" };
|
|
435
|
+
|
|
436
|
+
const result = await dispatchDone(context, args, createCleanupStatus(), deps);
|
|
437
|
+
|
|
438
|
+
expect(strategy.land).not.toHaveBeenCalled();
|
|
439
|
+
expect(result.shouldTerminate).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// =============================================================================
|
|
444
|
+
// Tests: Task Backend Claim/Unclaim Cycle
|
|
445
|
+
// =============================================================================
|
|
446
|
+
|
|
447
|
+
describe("Task Claim/Unclaim Cycle via MCP Tools", () => {
|
|
448
|
+
function createMockTaskBackend(): TaskBackend {
|
|
449
|
+
const tasks: ExtendedTask[] = [
|
|
450
|
+
{
|
|
451
|
+
id: "task-1",
|
|
452
|
+
description: "Fix auth bug",
|
|
453
|
+
status: "pending",
|
|
454
|
+
created_at: Date.now() - 5000,
|
|
455
|
+
tags: ["bugfix"],
|
|
456
|
+
isBlocked: false,
|
|
457
|
+
} as ExtendedTask,
|
|
458
|
+
{
|
|
459
|
+
id: "task-2",
|
|
460
|
+
description: "Add feature X",
|
|
461
|
+
status: "pending",
|
|
462
|
+
created_at: Date.now() - 3000,
|
|
463
|
+
tags: ["feature"],
|
|
464
|
+
isBlocked: false,
|
|
465
|
+
} as ExtendedTask,
|
|
466
|
+
{
|
|
467
|
+
id: "task-3",
|
|
468
|
+
description: "Blocked task",
|
|
469
|
+
status: "pending",
|
|
470
|
+
created_at: Date.now() - 1000,
|
|
471
|
+
tags: ["bugfix"],
|
|
472
|
+
isBlocked: true,
|
|
473
|
+
} as ExtendedTask,
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
claim: vi.fn(async (agentId: string, filter?: ClaimFilter) => {
|
|
478
|
+
const candidates = tasks.filter((t) => {
|
|
479
|
+
if (t.status !== "pending" || t.isBlocked) return false;
|
|
480
|
+
if (t.assigned_agent) return false;
|
|
481
|
+
if (filter?.tags) {
|
|
482
|
+
const taskTags = t.tags ?? [];
|
|
483
|
+
if (!filter.tags.some((ft) => taskTags.includes(ft))) return false;
|
|
484
|
+
}
|
|
485
|
+
return true;
|
|
486
|
+
});
|
|
487
|
+
if (candidates.length === 0) return null;
|
|
488
|
+
const claimed = candidates[0];
|
|
489
|
+
claimed.status = "in_progress";
|
|
490
|
+
claimed.assigned_agent = agentId;
|
|
491
|
+
return claimed;
|
|
492
|
+
}),
|
|
493
|
+
unclaim: vi.fn(async (taskId: string) => {
|
|
494
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
495
|
+
if (task) {
|
|
496
|
+
task.status = "pending";
|
|
497
|
+
task.assigned_agent = undefined;
|
|
498
|
+
}
|
|
499
|
+
}),
|
|
500
|
+
listClaimable: vi.fn(async (filter?: ClaimFilter) => {
|
|
501
|
+
return tasks.filter((t) => {
|
|
502
|
+
if (t.status !== "pending" || t.isBlocked) return false;
|
|
503
|
+
if (t.assigned_agent) return false;
|
|
504
|
+
if (filter?.tags) {
|
|
505
|
+
const taskTags = t.tags ?? [];
|
|
506
|
+
if (!filter.tags.some((ft) => taskTags.includes(ft))) return false;
|
|
507
|
+
}
|
|
508
|
+
return true;
|
|
509
|
+
});
|
|
510
|
+
}),
|
|
511
|
+
} as unknown as TaskBackend;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const toolContext: ToolContext = {
|
|
515
|
+
agent_id: "grinder-1",
|
|
516
|
+
session_id: "session-1",
|
|
517
|
+
} as ToolContext;
|
|
518
|
+
|
|
519
|
+
it("claim_task claims the first available task", async () => {
|
|
520
|
+
const backend = createMockTaskBackend();
|
|
521
|
+
const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
|
|
522
|
+
|
|
523
|
+
const result = await handler({});
|
|
524
|
+
|
|
525
|
+
expect(result.claimed).toBe(true);
|
|
526
|
+
expect(result.task).toBeDefined();
|
|
527
|
+
expect(result.task!.id).toBe("task-1");
|
|
528
|
+
expect(backend.claim).toHaveBeenCalledWith("grinder-1", {});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("claim_task filters by tags", async () => {
|
|
532
|
+
const backend = createMockTaskBackend();
|
|
533
|
+
const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
|
|
534
|
+
|
|
535
|
+
const result = await handler({ tags: ["feature"] });
|
|
536
|
+
|
|
537
|
+
expect(result.claimed).toBe(true);
|
|
538
|
+
expect(result.task!.id).toBe("task-2");
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("claim_task returns not claimed when no tasks available", async () => {
|
|
542
|
+
const backend = createMockTaskBackend();
|
|
543
|
+
(backend.claim as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
544
|
+
|
|
545
|
+
const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
|
|
546
|
+
const result = await handler({});
|
|
547
|
+
|
|
548
|
+
expect(result.claimed).toBe(false);
|
|
549
|
+
expect(result.task).toBeUndefined();
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("unclaim_task returns task to pending pool", async () => {
|
|
553
|
+
const backend = createMockTaskBackend();
|
|
554
|
+
const unclaimHandler = createUnclaimTaskHandler(toolContext, {
|
|
555
|
+
taskBackend: backend,
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const result = await unclaimHandler({ task_id: "task-1" });
|
|
559
|
+
|
|
560
|
+
expect(result.success).toBe(true);
|
|
561
|
+
expect(backend.unclaim).toHaveBeenCalledWith("task-1");
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it("list_claimable_tasks returns only claimable tasks", async () => {
|
|
565
|
+
const backend = createMockTaskBackend();
|
|
566
|
+
const listHandler = createListClaimableTasksHandler(toolContext, {
|
|
567
|
+
taskBackend: backend,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const result = await listHandler({});
|
|
571
|
+
|
|
572
|
+
expect(result.tasks.length).toBe(2); // task-3 is blocked
|
|
573
|
+
expect(result.tasks.every((t: ExtendedTask) => !t.isBlocked)).toBe(true);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("claim → unclaim → re-claim cycle works", async () => {
|
|
577
|
+
const backend = createMockTaskBackend();
|
|
578
|
+
const claimHandler = createClaimTaskHandler(toolContext, {
|
|
579
|
+
taskBackend: backend,
|
|
580
|
+
});
|
|
581
|
+
const unclaimHandler = createUnclaimTaskHandler(toolContext, {
|
|
582
|
+
taskBackend: backend,
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// Claim task-1
|
|
586
|
+
const claim1 = await claimHandler({});
|
|
587
|
+
expect(claim1.claimed).toBe(true);
|
|
588
|
+
expect(claim1.task!.id).toBe("task-1");
|
|
589
|
+
|
|
590
|
+
// Unclaim task-1
|
|
591
|
+
await unclaimHandler({ task_id: "task-1" });
|
|
592
|
+
|
|
593
|
+
// Re-claim should get task-1 again (it's back to pending)
|
|
594
|
+
const claim2 = await claimHandler({});
|
|
595
|
+
expect(claim2.claimed).toBe(true);
|
|
596
|
+
expect(claim2.task!.id).toBe("task-1");
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("claim_task fails gracefully when backend lacks claim support", async () => {
|
|
600
|
+
const backend = {} as TaskBackend; // No claim method
|
|
601
|
+
const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
|
|
602
|
+
|
|
603
|
+
const result = await handler({});
|
|
604
|
+
|
|
605
|
+
expect(result.claimed).toBe(false);
|
|
606
|
+
expect(result.message).toContain("does not support");
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// =============================================================================
|
|
611
|
+
// Tests: Metrics ↔ Realistic EventStore Events
|
|
612
|
+
// =============================================================================
|
|
613
|
+
|
|
614
|
+
describe("Metrics ↔ EventStore Integration", () => {
|
|
615
|
+
it("throughput metrics count task events correctly", () => {
|
|
616
|
+
const events: Event[] = [];
|
|
617
|
+
const now = Date.now();
|
|
618
|
+
|
|
619
|
+
// Simulate task events
|
|
620
|
+
events.push({
|
|
621
|
+
id: "e1", type: "task", timestamp: now - 1000,
|
|
622
|
+
source: { agent_id: "planner-1" },
|
|
623
|
+
payload: { action: "created", task_id: "t1" },
|
|
624
|
+
} as unknown as Event);
|
|
625
|
+
events.push({
|
|
626
|
+
id: "e2", type: "task", timestamp: now - 800,
|
|
627
|
+
source: { agent_id: "planner-1" },
|
|
628
|
+
payload: { action: "created", task_id: "t2" },
|
|
629
|
+
} as unknown as Event);
|
|
630
|
+
events.push({
|
|
631
|
+
id: "e3", type: "task", timestamp: now - 500,
|
|
632
|
+
source: { agent_id: "grinder-1" },
|
|
633
|
+
payload: { action: "completed", task_id: "t1" },
|
|
634
|
+
} as unknown as Event);
|
|
635
|
+
events.push({
|
|
636
|
+
id: "e4", type: "task", timestamp: now - 200,
|
|
637
|
+
source: { agent_id: "grinder-2" },
|
|
638
|
+
payload: { action: "failed", task_id: "t2" },
|
|
639
|
+
} as unknown as Event);
|
|
640
|
+
|
|
641
|
+
const store = createMockEventStore(events);
|
|
642
|
+
|
|
643
|
+
const metrics = getThroughputMetrics(store, 60000);
|
|
644
|
+
|
|
645
|
+
expect(metrics.tasksCreated).toBe(2);
|
|
646
|
+
expect(metrics.tasksCompleted).toBe(1);
|
|
647
|
+
expect(metrics.tasksFailed).toBe(1);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it("utilization metrics reflect active agents", () => {
|
|
651
|
+
const events: Event[] = [];
|
|
652
|
+
const now = Date.now();
|
|
653
|
+
|
|
654
|
+
events.push({
|
|
655
|
+
id: "e1", type: "spawn", timestamp: now - 5000,
|
|
656
|
+
source: { agent_id: "system" },
|
|
657
|
+
payload: { agent_id: "planner-1" },
|
|
658
|
+
} as unknown as Event);
|
|
659
|
+
events.push({
|
|
660
|
+
id: "e2", type: "spawn", timestamp: now - 3000,
|
|
661
|
+
source: { agent_id: "planner-1" },
|
|
662
|
+
payload: { agent_id: "grinder-1" },
|
|
663
|
+
} as unknown as Event);
|
|
664
|
+
events.push({
|
|
665
|
+
id: "e3", type: "terminate", timestamp: now - 1000,
|
|
666
|
+
source: { agent_id: "grinder-1" },
|
|
667
|
+
payload: { reason: "completed" },
|
|
668
|
+
} as unknown as Event);
|
|
669
|
+
|
|
670
|
+
const agents: Agent[] = [
|
|
671
|
+
{ id: "planner-1", state: "running", role: "planner" } as unknown as Agent,
|
|
672
|
+
{ id: "grinder-1", state: "stopped", role: "grinder" } as unknown as Agent,
|
|
673
|
+
];
|
|
674
|
+
|
|
675
|
+
const store = createMockEventStore(events);
|
|
676
|
+
vi.mocked(store.listAgents).mockReturnValue(agents);
|
|
677
|
+
|
|
678
|
+
const metrics = getUtilizationMetrics(store, 60000);
|
|
679
|
+
|
|
680
|
+
expect(metrics.activeAgents).toBe(1);
|
|
681
|
+
expect(metrics.totalSpawned).toBe(2);
|
|
682
|
+
expect(metrics.totalStopped).toBe(1);
|
|
683
|
+
expect(metrics.agentsByRole).toEqual({ planner: 1 });
|
|
684
|
+
expect(metrics.agentsByState).toEqual({ running: 1, stopped: 1 });
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it("error metrics capture both status and task failures", () => {
|
|
688
|
+
const events: Event[] = [];
|
|
689
|
+
const now = Date.now();
|
|
690
|
+
|
|
691
|
+
events.push({
|
|
692
|
+
id: "e1", type: "status", timestamp: now - 2000,
|
|
693
|
+
source: { agent_id: "grinder-1" },
|
|
694
|
+
payload: { status_type: "failed", summary: "OOM killed", details: { signal: "SIGKILL" } },
|
|
695
|
+
} as unknown as Event);
|
|
696
|
+
events.push({
|
|
697
|
+
id: "e2", type: "status", timestamp: now - 1500,
|
|
698
|
+
source: { agent_id: "grinder-2" },
|
|
699
|
+
payload: { status_type: "completed", summary: "Done" }, // Not an error
|
|
700
|
+
} as unknown as Event);
|
|
701
|
+
events.push({
|
|
702
|
+
id: "e3", type: "task", timestamp: now - 1000,
|
|
703
|
+
source: { agent_id: "grinder-3" },
|
|
704
|
+
payload: { action: "failed", task_id: "t5" },
|
|
705
|
+
} as unknown as Event);
|
|
706
|
+
|
|
707
|
+
const store = createMockEventStore(events);
|
|
708
|
+
|
|
709
|
+
const metrics = getErrorMetrics(store, 60000, 10);
|
|
710
|
+
|
|
711
|
+
expect(metrics.totalErrors).toBe(2);
|
|
712
|
+
expect(metrics.errorsByType["SIGKILL"]).toBe(1);
|
|
713
|
+
expect(metrics.errorsByType["task_failed"]).toBe(1);
|
|
714
|
+
expect(metrics.recentErrors).toHaveLength(2);
|
|
715
|
+
// Most recent first
|
|
716
|
+
expect(metrics.recentErrors[0].type).toBe("task_failed");
|
|
717
|
+
expect(metrics.recentErrors[1].type).toBe("SIGKILL");
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it("metrics respect time window boundaries", () => {
|
|
721
|
+
const events: Event[] = [];
|
|
722
|
+
const now = Date.now();
|
|
723
|
+
|
|
724
|
+
// Event inside 10-second window
|
|
725
|
+
events.push({
|
|
726
|
+
id: "e1", type: "task", timestamp: now - 5000,
|
|
727
|
+
source: { agent_id: "a1" },
|
|
728
|
+
payload: { action: "completed", task_id: "t1" },
|
|
729
|
+
} as unknown as Event);
|
|
730
|
+
// Event outside 10-second window
|
|
731
|
+
events.push({
|
|
732
|
+
id: "e2", type: "task", timestamp: now - 30000,
|
|
733
|
+
source: { agent_id: "a1" },
|
|
734
|
+
payload: { action: "completed", task_id: "t2" },
|
|
735
|
+
} as unknown as Event);
|
|
736
|
+
|
|
737
|
+
const store = createMockEventStore(events);
|
|
738
|
+
|
|
739
|
+
const metrics = getThroughputMetrics(store, 10000);
|
|
740
|
+
|
|
741
|
+
expect(metrics.tasksCompleted).toBe(1); // Only the recent one
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// =============================================================================
|
|
746
|
+
// Tests: Strategy Registry ↔ Team Config
|
|
747
|
+
// =============================================================================
|
|
748
|
+
|
|
749
|
+
describe("Strategy Registry ↔ Team Config", () => {
|
|
750
|
+
it("strategy registry can instantiate all strategies referenced by templates", async () => {
|
|
751
|
+
const roleRegistry = new DefaultRoleRegistry();
|
|
752
|
+
|
|
753
|
+
// Load self-driving template
|
|
754
|
+
const selfDriving = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
|
|
755
|
+
const sdStrategy = selfDriving.macro_agent.integration!.strategy;
|
|
756
|
+
expect(defaultStrategyRegistry.has(sdStrategy)).toBe(true);
|
|
757
|
+
|
|
758
|
+
// Load structured template
|
|
759
|
+
const structured = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
|
|
760
|
+
const stStrategy = structured.macro_agent.integration!.strategy;
|
|
761
|
+
expect(defaultStrategyRegistry.has(stStrategy)).toBe(true);
|
|
762
|
+
|
|
763
|
+
// Instantiate each
|
|
764
|
+
const trunkStrategy = defaultStrategyRegistry.get(sdStrategy, selfDriving.macro_agent.integration!.config);
|
|
765
|
+
expect(trunkStrategy.name).toBe("trunk");
|
|
766
|
+
|
|
767
|
+
const queueStrategy = defaultStrategyRegistry.get(stStrategy, structured.macro_agent.integration!.config);
|
|
768
|
+
expect(queueStrategy.name).toBe("queue");
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("custom strategy can be registered and resolved", () => {
|
|
772
|
+
defaultStrategyRegistry.register("custom-ci", (config) => ({
|
|
773
|
+
name: "custom-ci",
|
|
774
|
+
async land(request: LandRequest): Promise<LandResult> {
|
|
775
|
+
return { status: "landed", commitHash: "custom-hash" };
|
|
776
|
+
},
|
|
777
|
+
}));
|
|
778
|
+
|
|
779
|
+
expect(defaultStrategyRegistry.has("custom-ci")).toBe(true);
|
|
780
|
+
const strategy = defaultStrategyRegistry.get("custom-ci");
|
|
781
|
+
expect(strategy.name).toBe("custom-ci");
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
// =============================================================================
|
|
786
|
+
// Tests: Role Capability ↔ Tool Gating
|
|
787
|
+
// =============================================================================
|
|
788
|
+
|
|
789
|
+
describe("Role Capability ↔ Tool Gating", () => {
|
|
790
|
+
it("task.claim capability maps to all claim-related tools", () => {
|
|
791
|
+
const tools = CAPABILITY_TOOL_MAP[TASK_CAPABILITIES.CLAIM as keyof typeof CAPABILITY_TOOL_MAP];
|
|
792
|
+
expect(tools).toBeDefined();
|
|
793
|
+
expect(tools).toContain("claim_task");
|
|
794
|
+
expect(tools).toContain("unclaim_task");
|
|
795
|
+
expect(tools).toContain("list_claimable_tasks");
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it("self-driving grinder role has task.claim capability", async () => {
|
|
799
|
+
const roleRegistry = new DefaultRoleRegistry();
|
|
800
|
+
const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
|
|
801
|
+
|
|
802
|
+
const grinder = manifest._resolvedRoles.get("grinder");
|
|
803
|
+
expect(grinder!.capabilities).toContain("task.claim");
|
|
804
|
+
|
|
805
|
+
// Grinder inherits from worker — should still have worker capabilities
|
|
806
|
+
expect(grinder!.capabilities).toContain("file.read");
|
|
807
|
+
expect(grinder!.capabilities).toContain("file.write");
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it("structured developer role does NOT have task.claim capability", async () => {
|
|
811
|
+
const roleRegistry = new DefaultRoleRegistry();
|
|
812
|
+
const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
|
|
813
|
+
|
|
814
|
+
const developer = manifest._resolvedRoles.get("developer");
|
|
815
|
+
expect(developer!.capabilities).not.toContain("task.claim");
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it("registered team roles are resolvable from RoleRegistry", async () => {
|
|
819
|
+
const roleRegistry = new DefaultRoleRegistry();
|
|
820
|
+
const eventStore = createMockEventStore();
|
|
821
|
+
const messageRouter = createMockMessageRouter();
|
|
822
|
+
let capturedInterceptor: SpawnInterceptor | null = null;
|
|
823
|
+
const agentManager = {
|
|
824
|
+
spawn: vi.fn().mockResolvedValue({ id: "agent_0" }),
|
|
825
|
+
getRoleRegistry: () => roleRegistry,
|
|
826
|
+
setSpawnInterceptor: vi.fn((i: SpawnInterceptor | null) => { capturedInterceptor = i; }),
|
|
827
|
+
onLifecycleEvent: vi.fn(() => () => {}),
|
|
828
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
829
|
+
} as unknown as AgentManager;
|
|
830
|
+
|
|
831
|
+
const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
|
|
832
|
+
const runtime = new TeamRuntime(manifest, {
|
|
833
|
+
agentManager,
|
|
834
|
+
messageRouter,
|
|
835
|
+
eventStore,
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
await runtime.initialize();
|
|
839
|
+
|
|
840
|
+
// After initialization, team roles should be in the registry
|
|
841
|
+
const planner = roleRegistry.resolveRole("planner");
|
|
842
|
+
expect(planner).toBeDefined();
|
|
843
|
+
expect(planner.capabilities).toContain("task.claim");
|
|
844
|
+
|
|
845
|
+
const grinder = roleRegistry.resolveRole("grinder");
|
|
846
|
+
expect(grinder).toBeDefined();
|
|
847
|
+
expect(grinder.capabilities).toContain("task.claim");
|
|
848
|
+
expect(grinder.capabilities).toContain("git.push");
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// =============================================================================
|
|
853
|
+
// Tests: Team Config → Worker Done Pipeline (end-to-end wiring)
|
|
854
|
+
// =============================================================================
|
|
855
|
+
|
|
856
|
+
describe("Team Config → Worker Done Pipeline", () => {
|
|
857
|
+
it("full pipeline: team config flows through handler registry to strategy", async () => {
|
|
858
|
+
const messageRouter = createMockMessageRouter();
|
|
859
|
+
const agentManager = {
|
|
860
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
861
|
+
} as unknown as AgentManager;
|
|
862
|
+
const strategy = createMockStrategy("trunk");
|
|
863
|
+
|
|
864
|
+
// Build deps as they would be wired from MCPServices → DoneToolDeps → AllHandlerDeps
|
|
865
|
+
const allDeps: AllHandlerDeps = {
|
|
866
|
+
messageRouter,
|
|
867
|
+
agentManager,
|
|
868
|
+
integrationStrategy: strategy,
|
|
869
|
+
taskMode: "pull",
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
// Create registry (this is what createDoneHandler does internally)
|
|
873
|
+
const registry = createHandlerRegistry(allDeps);
|
|
874
|
+
|
|
875
|
+
// Dispatch as a "grinder" (extends worker)
|
|
876
|
+
const context: LifecycleContext = {
|
|
877
|
+
agentId: "grinder-42",
|
|
878
|
+
role: "worker", // resolved base role
|
|
879
|
+
taskId: "task-99",
|
|
880
|
+
workspacePath: "/workspace/grinder-42",
|
|
881
|
+
branch: "feature/task-99",
|
|
882
|
+
integrationBranch: "main",
|
|
883
|
+
streamId: "stream-main",
|
|
884
|
+
};
|
|
885
|
+
const args: DoneArgs = { status: "completed", summary: "Implemented task-99" };
|
|
886
|
+
|
|
887
|
+
const result = await dispatchDone(
|
|
888
|
+
context,
|
|
889
|
+
args,
|
|
890
|
+
createCleanupStatus(),
|
|
891
|
+
allDeps,
|
|
892
|
+
registry
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
// Strategy should have been called
|
|
896
|
+
expect(strategy.land).toHaveBeenCalledWith(
|
|
897
|
+
expect.objectContaining({
|
|
898
|
+
agentId: "grinder-42",
|
|
899
|
+
taskId: "task-99",
|
|
900
|
+
sourceBranch: "feature/task-99",
|
|
901
|
+
targetBranch: "main",
|
|
902
|
+
})
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
// Pull mode: should not terminate
|
|
906
|
+
expect(result.shouldTerminate).toBe(false);
|
|
907
|
+
|
|
908
|
+
// Integration signal emitted
|
|
909
|
+
expect(result.signalsEmitted).toContain("WORKER_DONE");
|
|
910
|
+
expect(result.signalsEmitted).toContain("WORKER_INTEGRATED");
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it("no strategy and no queue: emits signal but warns about missing queue", async () => {
|
|
914
|
+
const messageRouter = createMockMessageRouter();
|
|
915
|
+
const agentManager = {
|
|
916
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
917
|
+
} as unknown as AgentManager;
|
|
918
|
+
|
|
919
|
+
const allDeps: AllHandlerDeps = {
|
|
920
|
+
messageRouter,
|
|
921
|
+
agentManager,
|
|
922
|
+
// No strategy, no mergeQueue
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
const context = createWorkerContext();
|
|
926
|
+
const args: DoneArgs = { status: "completed" };
|
|
927
|
+
|
|
928
|
+
const result = await dispatchDone(
|
|
929
|
+
context,
|
|
930
|
+
args,
|
|
931
|
+
createCleanupStatus(),
|
|
932
|
+
allDeps
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
expect(result.signalsEmitted).toContain("WORKER_DONE");
|
|
936
|
+
expect(result.signalsEmitted).toContain("MERGE_REQUEST");
|
|
937
|
+
// Should mention "no queue configured" in cleanup actions
|
|
938
|
+
expect(result.cleanupActions?.some((a) => a.includes("no queue configured"))).toBe(true);
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// =============================================================================
|
|
943
|
+
// Tests: Communication Topology Validation
|
|
944
|
+
// =============================================================================
|
|
945
|
+
|
|
946
|
+
describe("Communication Topology Validation", () => {
|
|
947
|
+
it("rejects templates with unknown role in subscriptions", async () => {
|
|
948
|
+
// This tests the loader's validation — not a runtime test
|
|
949
|
+
const roleRegistry = new DefaultRoleRegistry();
|
|
950
|
+
|
|
951
|
+
// self-driving template is valid — should load without error
|
|
952
|
+
const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
|
|
953
|
+
|
|
954
|
+
// Verify all subscription roles exist in the roles list
|
|
955
|
+
for (const roleName of Object.keys(manifest.communication.subscriptions ?? {})) {
|
|
956
|
+
expect(manifest.roles).toContain(roleName);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Verify all emission roles exist in the roles list
|
|
960
|
+
for (const roleName of Object.keys(manifest.communication.emissions ?? {})) {
|
|
961
|
+
expect(manifest.roles).toContain(roleName);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Verify all peer routing roles exist
|
|
965
|
+
for (const peer of manifest.communication.routing?.peers ?? []) {
|
|
966
|
+
expect(manifest.roles).toContain(peer.from);
|
|
967
|
+
expect(manifest.roles).toContain(peer.to);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it("all subscribed channels exist in channel definitions", async () => {
|
|
972
|
+
const roleRegistry = new DefaultRoleRegistry();
|
|
973
|
+
const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
|
|
974
|
+
|
|
975
|
+
const channelNames = new Set(Object.keys(manifest.communication.channels ?? {}));
|
|
976
|
+
|
|
977
|
+
for (const [, subs] of Object.entries(manifest.communication.subscriptions ?? {})) {
|
|
978
|
+
for (const sub of subs) {
|
|
979
|
+
expect(channelNames.has(sub.channel)).toBe(true);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
});
|