macro-agent 0.1.1 → 0.1.2
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/.sudocode/issues.jsonl +28 -0
- package/.sudocode/specs.jsonl +4 -0
- package/CLAUDE.md +9 -3
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +111 -48
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +7 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/server.d.ts +5 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +100 -3
- package/dist/api/server.js.map +1 -1
- package/dist/api/types.d.ts +1 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +71 -1
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.js +27 -8
- package/dist/cli/mcp.js.map +1 -1
- package/dist/config/project-config.d.ts +13 -2
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +12 -2
- package/dist/config/project-config.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/handlers/index.d.ts +7 -3
- package/dist/lifecycle/handlers/index.d.ts.map +1 -1
- package/dist/lifecycle/handlers/index.js +25 -8
- package/dist/lifecycle/handlers/index.js.map +1 -1
- package/dist/lifecycle/types.d.ts +2 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/map/adapter/extensions/index.d.ts +4 -1
- package/dist/map/adapter/extensions/index.d.ts.map +1 -1
- package/dist/map/adapter/extensions/index.js +27 -0
- package/dist/map/adapter/extensions/index.js.map +1 -1
- package/dist/map/adapter/extensions/streams.d.ts +95 -0
- package/dist/map/adapter/extensions/streams.d.ts.map +1 -0
- package/dist/map/adapter/extensions/streams.js +515 -0
- package/dist/map/adapter/extensions/streams.js.map +1 -0
- package/dist/map/adapter/index.d.ts +1 -1
- package/dist/map/adapter/index.d.ts.map +1 -1
- package/dist/map/adapter/index.js +3 -1
- package/dist/map/adapter/index.js.map +1 -1
- package/dist/map/adapter/types.d.ts +1 -1
- package/dist/map/adapter/types.d.ts.map +1 -1
- package/dist/mcp/mcp-server.d.ts +2 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +12 -3
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/tools/done.d.ts.map +1 -1
- package/dist/mcp/tools/done.js +18 -0
- package/dist/mcp/tools/done.js.map +1 -1
- package/dist/roles/builtin/coordinator.d.ts.map +1 -1
- package/dist/roles/builtin/coordinator.js +2 -1
- package/dist/roles/builtin/coordinator.js.map +1 -1
- package/dist/roles/builtin/integrator.d.ts.map +1 -1
- package/dist/roles/builtin/integrator.js +2 -1
- package/dist/roles/builtin/integrator.js.map +1 -1
- package/dist/roles/builtin/worker.d.ts.map +1 -1
- package/dist/roles/builtin/worker.js +3 -1
- package/dist/roles/builtin/worker.js.map +1 -1
- package/dist/roles/capabilities.d.ts +6 -0
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +10 -0
- package/dist/roles/capabilities.js.map +1 -1
- package/dist/roles/config-loader.d.ts +1 -1
- package/dist/roles/config-loader.d.ts.map +1 -1
- package/dist/roles/config-loader.js +3 -2
- package/dist/roles/config-loader.js.map +1 -1
- package/dist/roles/types.d.ts +3 -1
- package/dist/roles/types.d.ts.map +1 -1
- package/dist/server/combined-server.d.ts +8 -1
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +6 -2
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +12 -5
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/instance.d.ts +1 -1
- package/dist/store/instance.d.ts.map +1 -1
- package/dist/store/instance.js +2 -2
- package/dist/store/instance.js.map +1 -1
- package/dist/store/types/agents.d.ts +5 -0
- package/dist/store/types/agents.d.ts.map +1 -1
- package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -1
- package/dist/task/backend/opentasks/daemon-manager.js +1 -1
- package/dist/task/backend/opentasks/daemon-manager.js.map +1 -1
- package/dist/teams/index.d.ts +3 -1
- package/dist/teams/index.d.ts.map +1 -1
- package/dist/teams/index.js +2 -0
- package/dist/teams/index.js.map +1 -1
- package/dist/teams/seed-defaults.d.ts +20 -0
- package/dist/teams/seed-defaults.d.ts.map +1 -0
- package/dist/teams/seed-defaults.js +71 -0
- package/dist/teams/seed-defaults.js.map +1 -0
- package/dist/teams/team-loader.d.ts +6 -2
- package/dist/teams/team-loader.d.ts.map +1 -1
- package/dist/teams/team-loader.js +154 -162
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-manager.d.ts +112 -0
- package/dist/teams/team-manager.d.ts.map +1 -0
- package/dist/teams/team-manager.js +305 -0
- package/dist/teams/team-manager.js.map +1 -0
- package/dist/teams/team-runtime.d.ts +125 -19
- package/dist/teams/team-runtime.d.ts.map +1 -1
- package/dist/teams/team-runtime.js +527 -119
- package/dist/teams/team-runtime.js.map +1 -1
- package/dist/teams/types.d.ts +41 -151
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/teams/types.js +2 -3
- package/dist/teams/types.js.map +1 -1
- package/docs/teams.md +73 -0
- package/package.json +2 -1
- package/references/minimem/.claude/settings.json +7 -0
- package/references/minimem/.sudocode/issues.jsonl +18 -0
- package/references/minimem/.sudocode/specs.jsonl +1 -0
- package/references/minimem/CLAUDE.md +310 -0
- package/references/minimem/README.md +562 -0
- package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
- package/references/minimem/claude-plugin/.mcp.json +7 -0
- package/references/minimem/claude-plugin/README.md +158 -0
- package/references/minimem/claude-plugin/commands/recall.md +47 -0
- package/references/minimem/claude-plugin/commands/remember.md +41 -0
- package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
- package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
- package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
- package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
- package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
- package/references/minimem/media/banner.png +0 -0
- package/references/minimem/package-lock.json +5373 -0
- package/references/minimem/package.json +72 -0
- package/references/minimem/scripts/postbuild.js +35 -0
- package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
- package/references/minimem/src/__tests__/errors.test.ts +265 -0
- package/references/minimem/src/__tests__/helpers.ts +199 -0
- package/references/minimem/src/__tests__/internal.test.ts +407 -0
- package/references/minimem/src/__tests__/knowledge.test.ts +287 -0
- package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
- package/references/minimem/src/__tests__/session.test.ts +190 -0
- package/references/minimem/src/cli/__tests__/commands.test.ts +759 -0
- package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
- package/references/minimem/src/cli/commands/append.ts +76 -0
- package/references/minimem/src/cli/commands/config.ts +262 -0
- package/references/minimem/src/cli/commands/conflicts.ts +413 -0
- package/references/minimem/src/cli/commands/daemon.ts +169 -0
- package/references/minimem/src/cli/commands/index.ts +12 -0
- package/references/minimem/src/cli/commands/init.ts +88 -0
- package/references/minimem/src/cli/commands/mcp.ts +177 -0
- package/references/minimem/src/cli/commands/push-pull.ts +213 -0
- package/references/minimem/src/cli/commands/search.ts +158 -0
- package/references/minimem/src/cli/commands/status.ts +84 -0
- package/references/minimem/src/cli/commands/sync-init.ts +290 -0
- package/references/minimem/src/cli/commands/sync.ts +70 -0
- package/references/minimem/src/cli/commands/upsert.ts +197 -0
- package/references/minimem/src/cli/config.ts +584 -0
- package/references/minimem/src/cli/index.ts +264 -0
- package/references/minimem/src/cli/shared.ts +161 -0
- package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
- package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
- package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
- package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
- package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
- package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
- package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
- package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
- package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
- package/references/minimem/src/cli/sync/central.ts +292 -0
- package/references/minimem/src/cli/sync/conflicts.ts +204 -0
- package/references/minimem/src/cli/sync/daemon.ts +407 -0
- package/references/minimem/src/cli/sync/detection.ts +138 -0
- package/references/minimem/src/cli/sync/index.ts +107 -0
- package/references/minimem/src/cli/sync/operations.ts +373 -0
- package/references/minimem/src/cli/sync/registry.ts +279 -0
- package/references/minimem/src/cli/sync/state.ts +355 -0
- package/references/minimem/src/cli/sync/validation.ts +206 -0
- package/references/minimem/src/cli/sync/watcher.ts +234 -0
- package/references/minimem/src/cli/version.ts +34 -0
- package/references/minimem/src/core/index.ts +9 -0
- package/references/minimem/src/core/indexer.ts +628 -0
- package/references/minimem/src/core/searcher.ts +221 -0
- package/references/minimem/src/db/schema.ts +183 -0
- package/references/minimem/src/db/sqlite-vec.ts +24 -0
- package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
- package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
- package/references/minimem/src/embeddings/batch-openai.ts +409 -0
- package/references/minimem/src/embeddings/embeddings.ts +434 -0
- package/references/minimem/src/index.ts +109 -0
- package/references/minimem/src/internal.ts +299 -0
- package/references/minimem/src/minimem.ts +1276 -0
- package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
- package/references/minimem/src/search/graph.ts +234 -0
- package/references/minimem/src/search/hybrid.ts +151 -0
- package/references/minimem/src/search/search.ts +256 -0
- package/references/minimem/src/server/__tests__/mcp.test.ts +341 -0
- package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
- package/references/minimem/src/server/mcp.ts +326 -0
- package/references/minimem/src/server/tools.ts +720 -0
- package/references/minimem/src/session.ts +460 -0
- package/references/minimem/tsconfig.json +19 -0
- package/references/minimem/tsup.config.ts +26 -0
- package/references/minimem/vitest.config.ts +24 -0
- package/references/openteams/.claude/settings.json +6 -0
- package/references/openteams/README.md +1 -0
- package/references/openteams/SKILL.md +341 -0
- package/references/openteams/design.md +411 -0
- package/references/openteams/examples/bmad-method/prompts/analyst/ROLE.md +16 -0
- package/references/openteams/examples/bmad-method/prompts/analyst/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/architect/ROLE.md +24 -0
- package/references/openteams/examples/bmad-method/prompts/architect/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/developer/ROLE.md +25 -0
- package/references/openteams/examples/bmad-method/prompts/developer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/master/ROLE.md +21 -0
- package/references/openteams/examples/bmad-method/prompts/master/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/pm/ROLE.md +20 -0
- package/references/openteams/examples/bmad-method/prompts/pm/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/qa/ROLE.md +17 -0
- package/references/openteams/examples/bmad-method/prompts/qa/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/ROLE.md +23 -0
- package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/scrum-master/ROLE.md +27 -0
- package/references/openteams/examples/bmad-method/prompts/scrum-master/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/tech-writer/ROLE.md +21 -0
- package/references/openteams/examples/bmad-method/prompts/tech-writer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/ux-designer/ROLE.md +16 -0
- package/references/openteams/examples/bmad-method/prompts/ux-designer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/roles/analyst.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/architect.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/developer.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/master.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/pm.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/qa.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/quick-flow-dev.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/scrum-master.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/tech-writer.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/ux-designer.yaml +8 -0
- package/references/openteams/examples/bmad-method/team.yaml +161 -0
- package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/ROLE.md +17 -0
- package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/debugger/ROLE.md +25 -0
- package/references/openteams/examples/get-shit-done/prompts/debugger/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/executor/ROLE.md +34 -0
- package/references/openteams/examples/get-shit-done/prompts/executor/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/integration-checker/ROLE.md +18 -0
- package/references/openteams/examples/get-shit-done/prompts/integration-checker/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/orchestrator/ROLE.md +42 -0
- package/references/openteams/examples/get-shit-done/prompts/orchestrator/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/phase-researcher/ROLE.md +15 -0
- package/references/openteams/examples/get-shit-done/prompts/phase-researcher/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/plan-checker/ROLE.md +17 -0
- package/references/openteams/examples/get-shit-done/prompts/plan-checker/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/planner/ROLE.md +28 -0
- package/references/openteams/examples/get-shit-done/prompts/planner/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/project-researcher/ROLE.md +16 -0
- package/references/openteams/examples/get-shit-done/prompts/project-researcher/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/ROLE.md +13 -0
- package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/roadmapper/ROLE.md +14 -0
- package/references/openteams/examples/get-shit-done/prompts/roadmapper/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/verifier/ROLE.md +19 -0
- package/references/openteams/examples/get-shit-done/prompts/verifier/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/roles/codebase-mapper.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/debugger.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/executor.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/integration-checker.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/orchestrator.yaml +9 -0
- package/references/openteams/examples/get-shit-done/roles/phase-researcher.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/plan-checker.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/planner.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/project-researcher.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/research-synthesizer.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/roadmapper.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/verifier.yaml +8 -0
- package/references/openteams/examples/get-shit-done/team.yaml +154 -0
- package/references/openteams/package-lock.json +2181 -0
- package/references/openteams/package.json +48 -0
- package/references/openteams/schema/role.schema.json +125 -0
- package/references/openteams/schema/team.schema.json +284 -0
- package/references/openteams/src/cli/agent.ts +104 -0
- package/references/openteams/src/cli/cli.test.ts +381 -0
- package/references/openteams/src/cli/generate.ts +220 -0
- package/references/openteams/src/cli/message.ts +241 -0
- package/references/openteams/src/cli/task.ts +154 -0
- package/references/openteams/src/cli/team.ts +104 -0
- package/references/openteams/src/cli/template.ts +207 -0
- package/references/openteams/src/cli.ts +45 -0
- package/references/openteams/src/db/database.test.ts +185 -0
- package/references/openteams/src/db/database.ts +240 -0
- package/references/openteams/src/generators/agent-prompt-generator.test.ts +332 -0
- package/references/openteams/src/generators/agent-prompt-generator.ts +521 -0
- package/references/openteams/src/generators/package-generator.test.ts +129 -0
- package/references/openteams/src/generators/package-generator.ts +102 -0
- package/references/openteams/src/generators/skill-generator.test.ts +246 -0
- package/references/openteams/src/generators/skill-generator.ts +374 -0
- package/references/openteams/src/index.ts +104 -0
- package/references/openteams/src/services/agent-service.test.ts +158 -0
- package/references/openteams/src/services/agent-service.ts +84 -0
- package/references/openteams/src/services/communication-service.test.ts +455 -0
- package/references/openteams/src/services/communication-service.ts +371 -0
- package/references/openteams/src/services/message-service.test.ts +342 -0
- package/references/openteams/src/services/message-service.ts +203 -0
- package/references/openteams/src/services/task-service.test.ts +434 -0
- package/references/openteams/src/services/task-service.ts +239 -0
- package/references/openteams/src/services/team-service.test.ts +181 -0
- package/references/openteams/src/services/team-service.ts +139 -0
- package/references/openteams/src/services/template-service.test.ts +306 -0
- package/references/openteams/src/services/template-service.ts +182 -0
- package/references/openteams/src/spawner/acp-factory.ts +96 -0
- package/references/openteams/src/spawner/interface.ts +31 -0
- package/references/openteams/src/spawner/mock.test.ts +93 -0
- package/references/openteams/src/spawner/mock.ts +59 -0
- package/references/openteams/src/template/loader.test.ts +1319 -0
- package/references/openteams/src/template/loader.ts +698 -0
- package/references/openteams/src/template/types.ts +200 -0
- package/references/openteams/src/types.ts +205 -0
- package/references/openteams/tsconfig.json +18 -0
- package/references/openteams/vitest.config.ts +9 -0
- package/references/skill-tree/.claude/settings.json +6 -0
- package/references/skill-tree/.sudocode/issues.jsonl +11 -0
- package/references/skill-tree/.sudocode/specs.jsonl +1 -0
- package/references/skill-tree/CLAUDE.md +150 -0
- package/references/skill-tree/README.md +324 -0
- package/references/skill-tree/docs/GAPS_v1.md +221 -0
- package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
- package/references/skill-tree/docs/TODOS.md +91 -0
- package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
- package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
- package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
- package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
- package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
- package/references/skill-tree/docs/scraper/README.md +170 -0
- package/references/skill-tree/examples/basic-usage.ts +190 -0
- package/references/skill-tree/package-lock.json +1509 -0
- package/references/skill-tree/package.json +66 -0
- package/references/skill-tree/scraper/README.md +123 -0
- package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
- package/references/skill-tree/scraper/docs/PLAN.md +336 -0
- package/references/skill-tree/scraper/drizzle.config.ts +10 -0
- package/references/skill-tree/scraper/package-lock.json +6329 -0
- package/references/skill-tree/scraper/package.json +68 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
- package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
- package/references/skill-tree/scraper/tsup.config.ts +14 -0
- package/references/skill-tree/scraper/vitest.config.ts +17 -0
- package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
- package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
- package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
- package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
- package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
- package/references/skill-tree/test/run-all.ts +106 -0
- package/references/skill-tree/test/utils.ts +128 -0
- package/references/skill-tree/vitest.config.ts +16 -0
- package/src/agent/agent-manager.ts +143 -72
- package/src/agent/types.ts +9 -0
- package/src/api/__tests__/server.test.ts +203 -4
- package/src/api/server.ts +130 -5
- package/src/api/types.ts +3 -1
- package/src/cli/acp.ts +68 -1
- package/src/cli/index.ts +5 -1
- package/src/cli/mcp.ts +27 -13
- package/src/config/project-config.ts +27 -3
- package/src/index.ts +3 -0
- package/src/lifecycle/__tests__/handlers.test.ts +53 -0
- package/src/lifecycle/handlers/index.ts +25 -8
- package/src/lifecycle/types.ts +3 -0
- package/src/map/adapter/__tests__/stream-extensions.test.ts +494 -0
- package/src/map/adapter/extensions/index.ts +36 -0
- package/src/map/adapter/extensions/streams.ts +839 -0
- package/src/map/adapter/index.ts +5 -0
- package/src/map/adapter/types.ts +8 -1
- package/src/mcp/mcp-server.ts +14 -3
- package/src/mcp/tools/done.ts +19 -0
- package/src/roles/builtin/coordinator.ts +2 -0
- package/src/roles/builtin/integrator.ts +2 -0
- package/src/roles/builtin/worker.ts +3 -0
- package/src/roles/capabilities.ts +11 -0
- package/src/roles/config-loader.ts +3 -2
- package/src/roles/types.ts +7 -0
- package/src/server/combined-server.ts +15 -1
- package/src/store/__tests__/event-store-oob.test.ts +109 -0
- package/src/store/event-store.ts +13 -3
- package/src/store/instance.ts +2 -2
- package/src/store/types/agents.ts +5 -0
- package/src/task/backend/__tests__/memory-pull-mode.test.ts +153 -0
- package/src/task/backend/opentasks/daemon-manager.ts +4 -1
- package/src/teams/CLAUDE.md +180 -0
- package/src/teams/__tests__/e2e/workspace-isolation.e2e.test.ts +1263 -0
- package/src/teams/__tests__/team-manager.test.ts +814 -0
- package/src/teams/__tests__/team-system.test.ts +1291 -8
- package/src/teams/index.ts +21 -3
- package/src/teams/seed-defaults.ts +79 -0
- package/src/teams/team-loader.ts +200 -234
- package/src/teams/team-manager.ts +387 -0
- package/src/teams/team-runtime.ts +590 -121
- package/src/teams/types.ts +99 -200
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamManager Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the central team instance lifecycle manager: starting/stopping teams,
|
|
5
|
+
* composite dispatch of interceptors/filters/validators, and agent-to-team mapping.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import { TeamManager } from "../team-manager.js";
|
|
11
|
+
import type { TeamServices } from "../team-runtime.js";
|
|
12
|
+
import { DefaultRoleRegistry } from "../../roles/registry.js";
|
|
13
|
+
import type { AgentManager, SpawnInterceptor } from "../../agent/agent-manager.js";
|
|
14
|
+
import type { MessageRouter } from "../../router/message-router.js";
|
|
15
|
+
import type { EventStore } from "../../store/event-store.js";
|
|
16
|
+
import type { SpawnAgentOptions } from "../../agent/types.js";
|
|
17
|
+
import type { AgentId, Event } from "../../store/types/index.js";
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Helpers
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
const PROJECT_ROOT = path.resolve(import.meta.dirname, "../../..");
|
|
24
|
+
|
|
25
|
+
let spawnCounter = 0;
|
|
26
|
+
let capturedInterceptor: SpawnInterceptor | null = null;
|
|
27
|
+
let interceptedSpawnOptions: SpawnAgentOptions[] = [];
|
|
28
|
+
let lifecycleCallbacks: Array<(event: any) => void> = [];
|
|
29
|
+
|
|
30
|
+
function createMockEventStore(): EventStore {
|
|
31
|
+
const events: Event[] = [];
|
|
32
|
+
return {
|
|
33
|
+
emit: vi.fn((input: Record<string, unknown>) => {
|
|
34
|
+
const event = {
|
|
35
|
+
id: `evt_${events.length}`,
|
|
36
|
+
type: input.type,
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
source: input.source,
|
|
39
|
+
target: input.target,
|
|
40
|
+
payload: input.payload,
|
|
41
|
+
} as unknown as Event;
|
|
42
|
+
events.push(event);
|
|
43
|
+
return event;
|
|
44
|
+
}),
|
|
45
|
+
persist: vi.fn().mockResolvedValue(undefined),
|
|
46
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
47
|
+
query: vi.fn().mockReturnValue([]),
|
|
48
|
+
getAgent: vi.fn().mockReturnValue(null),
|
|
49
|
+
getTask: vi.fn().mockReturnValue(null),
|
|
50
|
+
listAgents: vi.fn().mockReturnValue([]),
|
|
51
|
+
onAgentChange: vi.fn(),
|
|
52
|
+
onTaskChange: vi.fn(),
|
|
53
|
+
updateAgentMetadata: vi.fn(),
|
|
54
|
+
instanceId: "test-instance",
|
|
55
|
+
_events: events,
|
|
56
|
+
} as unknown as EventStore & { _events: Event[] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createMockMessageRouter(): MessageRouter {
|
|
60
|
+
return {
|
|
61
|
+
sendToAddress: vi.fn().mockResolvedValue({ delivered: true }),
|
|
62
|
+
emitStatus: vi.fn(),
|
|
63
|
+
getMessages: vi.fn().mockReturnValue([]),
|
|
64
|
+
subscribe: vi.fn(),
|
|
65
|
+
unsubscribe: vi.fn(),
|
|
66
|
+
getSubscriptions: vi.fn().mockReturnValue([]),
|
|
67
|
+
setupDefaultSubscriptions: vi.fn(),
|
|
68
|
+
setSignalFilter: vi.fn(),
|
|
69
|
+
setEmissionValidator: vi.fn(),
|
|
70
|
+
} as unknown as MessageRouter;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createMockAgentManager(roleRegistry: DefaultRoleRegistry): AgentManager {
|
|
74
|
+
capturedInterceptor = null;
|
|
75
|
+
spawnCounter = 0;
|
|
76
|
+
interceptedSpawnOptions = [];
|
|
77
|
+
lifecycleCallbacks = [];
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
spawn: vi.fn(async (options: SpawnAgentOptions) => {
|
|
81
|
+
const opts = capturedInterceptor ? await capturedInterceptor(options) : options;
|
|
82
|
+
interceptedSpawnOptions.push(opts);
|
|
83
|
+
const id = `agent_${spawnCounter++}`;
|
|
84
|
+
return {
|
|
85
|
+
id,
|
|
86
|
+
session_id: `session_${id}`,
|
|
87
|
+
task: opts.task ?? "test",
|
|
88
|
+
state: "running" as const,
|
|
89
|
+
created_at: Date.now(),
|
|
90
|
+
parent: opts.parent ?? null,
|
|
91
|
+
role: opts.role,
|
|
92
|
+
config: opts.config,
|
|
93
|
+
};
|
|
94
|
+
}),
|
|
95
|
+
terminate: vi.fn().mockResolvedValue(undefined),
|
|
96
|
+
get: vi.fn().mockReturnValue(null),
|
|
97
|
+
list: vi.fn().mockReturnValue([]),
|
|
98
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
99
|
+
getHierarchy: vi.fn().mockReturnValue(null),
|
|
100
|
+
getSession: vi.fn().mockReturnValue(null),
|
|
101
|
+
hasActiveSession: vi.fn().mockReturnValue(false),
|
|
102
|
+
setSpawnInterceptor: vi.fn((interceptor: SpawnInterceptor | null) => {
|
|
103
|
+
capturedInterceptor = interceptor;
|
|
104
|
+
}),
|
|
105
|
+
getRoleRegistry: vi.fn(() => roleRegistry),
|
|
106
|
+
onLifecycleEvent: vi.fn((callback: (event: any) => void) => {
|
|
107
|
+
lifecycleCallbacks.push(callback);
|
|
108
|
+
return vi.fn(); // unsubscribe
|
|
109
|
+
}),
|
|
110
|
+
continueAgent: vi.fn().mockResolvedValue({ id: "continued_0" }),
|
|
111
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
112
|
+
getOrCreateHeadManager: vi.fn(),
|
|
113
|
+
prompt: vi.fn(),
|
|
114
|
+
isPrompting: vi.fn().mockReturnValue(false),
|
|
115
|
+
} as unknown as AgentManager;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Tests
|
|
120
|
+
// =============================================================================
|
|
121
|
+
|
|
122
|
+
describe("TeamManager", () => {
|
|
123
|
+
let roleRegistry: DefaultRoleRegistry;
|
|
124
|
+
let agentManager: AgentManager;
|
|
125
|
+
let messageRouter: MessageRouter;
|
|
126
|
+
let eventStore: EventStore;
|
|
127
|
+
let services: TeamServices;
|
|
128
|
+
|
|
129
|
+
beforeEach(() => {
|
|
130
|
+
roleRegistry = new DefaultRoleRegistry();
|
|
131
|
+
eventStore = createMockEventStore();
|
|
132
|
+
messageRouter = createMockMessageRouter();
|
|
133
|
+
agentManager = createMockAgentManager(roleRegistry);
|
|
134
|
+
services = { agentManager, messageRouter, eventStore };
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("startTeam()", () => {
|
|
138
|
+
it("loads template, creates runtime, initializes and bootstraps", async () => {
|
|
139
|
+
const manager = new TeamManager(services);
|
|
140
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
141
|
+
|
|
142
|
+
expect(instance.id).toBe("self-driving-1");
|
|
143
|
+
expect(instance.templateName).toBe("self-driving");
|
|
144
|
+
expect(instance.result.rootId).toBeDefined();
|
|
145
|
+
expect(instance.result.companionIds).toHaveLength(1);
|
|
146
|
+
|
|
147
|
+
// Root (planner) + companion (judge) spawned
|
|
148
|
+
expect(agentManager.spawn).toHaveBeenCalledTimes(2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("emits team_config event during initialization", async () => {
|
|
152
|
+
const manager = new TeamManager(services);
|
|
153
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
154
|
+
|
|
155
|
+
expect(eventStore.emit).toHaveBeenCalledWith(
|
|
156
|
+
expect.objectContaining({
|
|
157
|
+
type: "status",
|
|
158
|
+
payload: expect.objectContaining({
|
|
159
|
+
team_config: expect.objectContaining({
|
|
160
|
+
teamName: "self-driving",
|
|
161
|
+
}),
|
|
162
|
+
}),
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("allows multiple concurrent teams", async () => {
|
|
168
|
+
const manager = new TeamManager(services);
|
|
169
|
+
const first = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
170
|
+
const second = await manager.startTeam("structured", PROJECT_ROOT);
|
|
171
|
+
|
|
172
|
+
expect(manager.getInstances()).toHaveLength(2);
|
|
173
|
+
expect(manager.getInstance(first.id)).toBe(first);
|
|
174
|
+
expect(manager.getInstance(second.id)).toBe(second);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("generates sequential instance IDs", async () => {
|
|
178
|
+
const manager = new TeamManager(services);
|
|
179
|
+
const first = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
180
|
+
expect(first.id).toBe("self-driving-1");
|
|
181
|
+
|
|
182
|
+
const second = await manager.startTeam("structured", PROJECT_ROOT);
|
|
183
|
+
expect(second.id).toBe("structured-2");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("maps bootstrap agents to the team instance", async () => {
|
|
187
|
+
const manager = new TeamManager(services);
|
|
188
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
189
|
+
|
|
190
|
+
const rootTeam = manager.getTeamForAgent(instance.result.rootId);
|
|
191
|
+
expect(rootTeam).toBe(instance);
|
|
192
|
+
|
|
193
|
+
const companionTeam = manager.getTeamForAgent(instance.result.companionIds[0]);
|
|
194
|
+
expect(companionTeam).toBe(instance);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("tags bootstrap agents with team_instance in EventStore", async () => {
|
|
198
|
+
const manager = new TeamManager(services);
|
|
199
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
200
|
+
|
|
201
|
+
// Root + companion agents should be tagged
|
|
202
|
+
const allAgentIds = [instance.result.rootId, ...instance.result.companionIds];
|
|
203
|
+
for (const agentId of allAgentIds) {
|
|
204
|
+
expect(eventStore.updateAgentMetadata).toHaveBeenCalledWith(
|
|
205
|
+
agentId,
|
|
206
|
+
{ team_instance: instance.id },
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("stopTeam()", () => {
|
|
213
|
+
it("tears down the runtime and removes from map", async () => {
|
|
214
|
+
const manager = new TeamManager(services);
|
|
215
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
216
|
+
|
|
217
|
+
await manager.stopTeam(instance.id);
|
|
218
|
+
|
|
219
|
+
expect(manager.hasActiveTeam()).toBe(false);
|
|
220
|
+
expect(manager.getInstance(instance.id)).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("clears agent-to-team mapping for stopped team", async () => {
|
|
224
|
+
const manager = new TeamManager(services);
|
|
225
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
226
|
+
const rootId = instance.result.rootId;
|
|
227
|
+
|
|
228
|
+
await manager.stopTeam(instance.id);
|
|
229
|
+
|
|
230
|
+
expect(manager.getTeamForAgent(rootId)).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("throws for non-existent instance ID", async () => {
|
|
234
|
+
const manager = new TeamManager(services);
|
|
235
|
+
|
|
236
|
+
await expect(
|
|
237
|
+
manager.stopTeam("nonexistent-1")
|
|
238
|
+
).rejects.toThrow(/No team instance 'nonexistent-1' found/);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("allows starting a new team after stopping", async () => {
|
|
242
|
+
const manager = new TeamManager(services);
|
|
243
|
+
const first = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
244
|
+
await manager.stopTeam(first.id);
|
|
245
|
+
|
|
246
|
+
// Should not throw
|
|
247
|
+
const second = await manager.startTeam("structured", PROJECT_ROOT);
|
|
248
|
+
expect(second.templateName).toBe("structured");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("teardownAll()", () => {
|
|
253
|
+
it("stops all running instances", async () => {
|
|
254
|
+
const manager = new TeamManager(services);
|
|
255
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
256
|
+
|
|
257
|
+
await manager.teardownAll();
|
|
258
|
+
|
|
259
|
+
expect(manager.hasActiveTeam()).toBe(false);
|
|
260
|
+
expect(manager.getInstances()).toEqual([]);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("is safe to call with no running instances", async () => {
|
|
264
|
+
const manager = new TeamManager(services);
|
|
265
|
+
await manager.teardownAll(); // Should not throw
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("getters", () => {
|
|
270
|
+
it("getInstance() returns instance by ID", async () => {
|
|
271
|
+
const manager = new TeamManager(services);
|
|
272
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
273
|
+
|
|
274
|
+
expect(manager.getInstance(instance.id)).toBe(instance);
|
|
275
|
+
expect(manager.getInstance("nonexistent")).toBeUndefined();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("getInstances() returns all active instances", async () => {
|
|
279
|
+
const manager = new TeamManager(services);
|
|
280
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
281
|
+
|
|
282
|
+
const instances = manager.getInstances();
|
|
283
|
+
expect(instances).toHaveLength(1);
|
|
284
|
+
expect(instances[0]).toBe(instance);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("hasActiveTeam() reflects state correctly", async () => {
|
|
288
|
+
const manager = new TeamManager(services);
|
|
289
|
+
expect(manager.hasActiveTeam()).toBe(false);
|
|
290
|
+
|
|
291
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
292
|
+
expect(manager.hasActiveTeam()).toBe(true);
|
|
293
|
+
|
|
294
|
+
await manager.stopTeam(instance.id);
|
|
295
|
+
expect(manager.hasActiveTeam()).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe("install() — composite interceptor", () => {
|
|
300
|
+
it("installs composite spawn interceptor on agent manager", async () => {
|
|
301
|
+
const manager = new TeamManager(services);
|
|
302
|
+
manager.install();
|
|
303
|
+
|
|
304
|
+
expect(agentManager.setSpawnInterceptor).toHaveBeenCalledWith(
|
|
305
|
+
expect.any(Function)
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("interceptor passes through agents with no parent", async () => {
|
|
310
|
+
const manager = new TeamManager(services);
|
|
311
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
312
|
+
manager.install();
|
|
313
|
+
|
|
314
|
+
// Spawn with no parent — should pass through unchanged
|
|
315
|
+
const original: SpawnAgentOptions = {
|
|
316
|
+
task: "standalone task",
|
|
317
|
+
role: "grinder",
|
|
318
|
+
parent: null,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
await agentManager.spawn(original);
|
|
322
|
+
const lastOpts = interceptedSpawnOptions.at(-1)!;
|
|
323
|
+
|
|
324
|
+
// No team context injected (parent is null)
|
|
325
|
+
expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBeUndefined();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("interceptor injects team context for child agents", async () => {
|
|
329
|
+
const manager = new TeamManager(services);
|
|
330
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
331
|
+
manager.install();
|
|
332
|
+
|
|
333
|
+
// Spawn a grinder as child of root (planner)
|
|
334
|
+
await agentManager.spawn({
|
|
335
|
+
task: "grinder task",
|
|
336
|
+
role: "grinder",
|
|
337
|
+
parent: instance.result.rootId,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const lastOpts = interceptedSpawnOptions.at(-1)!;
|
|
341
|
+
expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBe("self-driving");
|
|
342
|
+
expect(lastOpts.config?.env?.MACRO_TASK_MODE).toBe("pull");
|
|
343
|
+
expect(lastOpts.topics).toContain("work_coordination");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("interceptor sets team_instance on child agents", async () => {
|
|
347
|
+
const manager = new TeamManager(services);
|
|
348
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
349
|
+
manager.install();
|
|
350
|
+
|
|
351
|
+
await agentManager.spawn({
|
|
352
|
+
task: "grinder task",
|
|
353
|
+
role: "grinder",
|
|
354
|
+
parent: instance.result.rootId,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const lastOpts = interceptedSpawnOptions.at(-1)!;
|
|
358
|
+
expect(lastOpts.team_instance).toBe(instance.id);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("interceptor passes through agents with non-team parent", async () => {
|
|
362
|
+
const manager = new TeamManager(services);
|
|
363
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
364
|
+
manager.install();
|
|
365
|
+
|
|
366
|
+
// Spawn with a parent that's not in the team
|
|
367
|
+
await agentManager.spawn({
|
|
368
|
+
task: "orphan task",
|
|
369
|
+
role: "worker",
|
|
370
|
+
parent: "non_team_parent",
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const lastOpts = interceptedSpawnOptions.at(-1)!;
|
|
374
|
+
expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBeUndefined();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe("install() — composite signal filter", () => {
|
|
379
|
+
it("installs signal filter on message router", async () => {
|
|
380
|
+
const manager = new TeamManager(services);
|
|
381
|
+
manager.install();
|
|
382
|
+
|
|
383
|
+
expect(messageRouter.setSignalFilter).toHaveBeenCalledWith(
|
|
384
|
+
expect.any(Function)
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("delegates filtering to correct team instance", async () => {
|
|
389
|
+
const manager = new TeamManager(services);
|
|
390
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
391
|
+
manager.install();
|
|
392
|
+
|
|
393
|
+
const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
|
|
394
|
+
from: string, to: string, signal: string | undefined
|
|
395
|
+
) => boolean;
|
|
396
|
+
|
|
397
|
+
const rootId = instance.result.rootId;
|
|
398
|
+
const companionId = instance.result.companionIds[0];
|
|
399
|
+
|
|
400
|
+
// judge→planner has peer filter: [FIXUP_CREATED, GREEN_SNAPSHOT]
|
|
401
|
+
expect(filterFn(companionId, rootId, "FIXUP_CREATED")).toBe(true);
|
|
402
|
+
expect(filterFn(companionId, rootId, "WORKER_DONE")).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("allows all signals for non-team agents", async () => {
|
|
406
|
+
const manager = new TeamManager(services);
|
|
407
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
408
|
+
manager.install();
|
|
409
|
+
|
|
410
|
+
const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
|
|
411
|
+
from: string, to: string, signal: string | undefined
|
|
412
|
+
) => boolean;
|
|
413
|
+
|
|
414
|
+
// Non-team agents — should pass through
|
|
415
|
+
expect(filterFn("unknown_from", "unknown_to", "ANY_SIGNAL")).toBe(true);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("applies recipient's team filter for cross-team messages", async () => {
|
|
419
|
+
const manager = new TeamManager(services);
|
|
420
|
+
const teamA = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
421
|
+
const teamB = await manager.startTeam("structured", PROJECT_ROOT);
|
|
422
|
+
manager.install();
|
|
423
|
+
|
|
424
|
+
// Register a grinder in team A and a developer in team B
|
|
425
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
426
|
+
spawnCallback({
|
|
427
|
+
type: "spawned",
|
|
428
|
+
agent: { id: "grinder_1" as AgentId, parent: teamA.result.rootId as AgentId, role: "grinder", state: "running" },
|
|
429
|
+
});
|
|
430
|
+
spawnCallback({
|
|
431
|
+
type: "spawned",
|
|
432
|
+
agent: { id: "dev_1" as AgentId, parent: teamB.result.rootId as AgentId, role: "developer", state: "running" },
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
|
|
436
|
+
from: string, to: string, signal: string | undefined
|
|
437
|
+
) => boolean;
|
|
438
|
+
|
|
439
|
+
// grinder (team A) → developer (team B): recipient's team filter applies
|
|
440
|
+
// developer's allowed signals from structured subscriptions: { TASK_ASSIGNED }
|
|
441
|
+
expect(filterFn("grinder_1", "dev_1", "TASK_ASSIGNED")).toBe(true);
|
|
442
|
+
expect(filterFn("grinder_1", "dev_1", "WORK_ASSIGNED")).toBe(false);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("applies sender's team filter when recipient is non-team", async () => {
|
|
446
|
+
const manager = new TeamManager(services);
|
|
447
|
+
const teamA = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
448
|
+
manager.install();
|
|
449
|
+
|
|
450
|
+
// Register a grinder in team A
|
|
451
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
452
|
+
spawnCallback({
|
|
453
|
+
type: "spawned",
|
|
454
|
+
agent: { id: "grinder_1" as AgentId, parent: teamA.result.rootId as AgentId, role: "grinder", state: "running" },
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
|
|
458
|
+
from: string, to: string, signal: string | undefined
|
|
459
|
+
) => boolean;
|
|
460
|
+
|
|
461
|
+
// grinder (team A) → non-team agent: sender's team filter is used
|
|
462
|
+
// Non-team recipient has no role in team A's agentRoleMap → no filter → allow
|
|
463
|
+
expect(filterFn("grinder_1", "outsider", "ANYTHING")).toBe(true);
|
|
464
|
+
expect(filterFn("grinder_1", "outsider", "WORK_ASSIGNED")).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("applies recipient's team filter when sender is non-team", async () => {
|
|
468
|
+
const manager = new TeamManager(services);
|
|
469
|
+
const teamB = await manager.startTeam("structured", PROJECT_ROOT);
|
|
470
|
+
manager.install();
|
|
471
|
+
|
|
472
|
+
// Register a developer in team B
|
|
473
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
474
|
+
spawnCallback({
|
|
475
|
+
type: "spawned",
|
|
476
|
+
agent: { id: "dev_1" as AgentId, parent: teamB.result.rootId as AgentId, role: "developer", state: "running" },
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
|
|
480
|
+
from: string, to: string, signal: string | undefined
|
|
481
|
+
) => boolean;
|
|
482
|
+
|
|
483
|
+
// non-team → developer (team B): recipient's team filter applies
|
|
484
|
+
// developer's allowed signals: { TASK_ASSIGNED }
|
|
485
|
+
expect(filterFn("outsider", "dev_1", "TASK_ASSIGNED")).toBe(true);
|
|
486
|
+
expect(filterFn("outsider", "dev_1", "WORK_ASSIGNED")).toBe(false);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("recipient's team takes precedence over sender's team", async () => {
|
|
490
|
+
const manager = new TeamManager(services);
|
|
491
|
+
const teamA = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
492
|
+
const teamB = await manager.startTeam("structured", PROJECT_ROOT);
|
|
493
|
+
manager.install();
|
|
494
|
+
|
|
495
|
+
// Register restricted-filter agents in each team
|
|
496
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
497
|
+
spawnCallback({
|
|
498
|
+
type: "spawned",
|
|
499
|
+
agent: { id: "grinder_1" as AgentId, parent: teamA.result.rootId as AgentId, role: "grinder", state: "running" },
|
|
500
|
+
});
|
|
501
|
+
spawnCallback({
|
|
502
|
+
type: "spawned",
|
|
503
|
+
agent: { id: "dev_1" as AgentId, parent: teamB.result.rootId as AgentId, role: "developer", state: "running" },
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
|
|
507
|
+
from: string, to: string, signal: string | undefined
|
|
508
|
+
) => boolean;
|
|
509
|
+
|
|
510
|
+
// grinder → developer: recipient (developer in structured) filter applies
|
|
511
|
+
// developer allows: { TASK_ASSIGNED }, grinder allows: { WORK_ASSIGNED }
|
|
512
|
+
expect(filterFn("grinder_1", "dev_1", "TASK_ASSIGNED")).toBe(true);
|
|
513
|
+
expect(filterFn("grinder_1", "dev_1", "WORK_ASSIGNED")).toBe(false);
|
|
514
|
+
|
|
515
|
+
// Reverse: developer → grinder: recipient (grinder in self-driving) filter applies
|
|
516
|
+
// grinder allows: { WORK_ASSIGNED }
|
|
517
|
+
expect(filterFn("dev_1", "grinder_1", "WORK_ASSIGNED")).toBe(true);
|
|
518
|
+
expect(filterFn("dev_1", "grinder_1", "TASK_ASSIGNED")).toBe(false);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe("install() — composite emission validator", () => {
|
|
523
|
+
it("installs emission validator on message router", async () => {
|
|
524
|
+
const manager = new TeamManager(services);
|
|
525
|
+
manager.install();
|
|
526
|
+
|
|
527
|
+
expect(messageRouter.setEmissionValidator).toHaveBeenCalledWith(
|
|
528
|
+
expect.any(Function)
|
|
529
|
+
);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("delegates validation to correct team instance", async () => {
|
|
533
|
+
const manager = new TeamManager(services);
|
|
534
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
535
|
+
manager.install();
|
|
536
|
+
|
|
537
|
+
const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls.at(-1)![0] as (
|
|
538
|
+
agentId: string, signal: string | undefined
|
|
539
|
+
) => { action: string; message?: string };
|
|
540
|
+
|
|
541
|
+
const rootId = instance.result.rootId;
|
|
542
|
+
|
|
543
|
+
// planner's allowed emissions: [TASK_CREATED, WORK_ASSIGNED]
|
|
544
|
+
expect(validatorFn(rootId, "TASK_CREATED").action).toBe("allow");
|
|
545
|
+
// Default enforcement is permissive — disallowed signals get "warn"
|
|
546
|
+
expect(validatorFn(rootId, "FORBIDDEN_SIGNAL").action).toBe("warn");
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it("allows emissions for non-team agents", async () => {
|
|
550
|
+
const manager = new TeamManager(services);
|
|
551
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
552
|
+
manager.install();
|
|
553
|
+
|
|
554
|
+
const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls.at(-1)![0] as (
|
|
555
|
+
agentId: string, signal: string | undefined
|
|
556
|
+
) => { action: string; message?: string };
|
|
557
|
+
|
|
558
|
+
expect(validatorFn("unknown_agent", "ANYTHING").action).toBe("allow");
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
describe("install() — lifecycle listener", () => {
|
|
563
|
+
it("sets up lifecycle listener for spawn tracking", async () => {
|
|
564
|
+
const manager = new TeamManager(services);
|
|
565
|
+
manager.install();
|
|
566
|
+
|
|
567
|
+
expect(agentManager.onLifecycleEvent).toHaveBeenCalledWith(
|
|
568
|
+
expect.any(Function)
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it("auto-registers child agents in parent's team", async () => {
|
|
573
|
+
const manager = new TeamManager(services);
|
|
574
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
575
|
+
manager.install();
|
|
576
|
+
|
|
577
|
+
// Simulate a grinder being spawned as child of root
|
|
578
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
579
|
+
spawnCallback({
|
|
580
|
+
type: "spawned",
|
|
581
|
+
agent: {
|
|
582
|
+
id: "grinder_1" as AgentId,
|
|
583
|
+
parent: instance.result.rootId as AgentId,
|
|
584
|
+
role: "grinder",
|
|
585
|
+
state: "running",
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// grinder should be in the same team as root
|
|
590
|
+
const grinderTeam = manager.getTeamForAgent("grinder_1");
|
|
591
|
+
expect(grinderTeam).toBe(instance);
|
|
592
|
+
|
|
593
|
+
// Also registered in runtime's agent role map
|
|
594
|
+
expect(instance.runtime.hasAgent("grinder_1")).toBe(true);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it("ignores spawns with no parent", async () => {
|
|
598
|
+
const manager = new TeamManager(services);
|
|
599
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
600
|
+
manager.install();
|
|
601
|
+
|
|
602
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
603
|
+
spawnCallback({
|
|
604
|
+
type: "spawned",
|
|
605
|
+
agent: {
|
|
606
|
+
id: "orphan_1" as AgentId,
|
|
607
|
+
parent: null,
|
|
608
|
+
role: "worker",
|
|
609
|
+
state: "running",
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
expect(manager.getTeamForAgent("orphan_1")).toBeUndefined();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it("ignores spawns from non-team parents", async () => {
|
|
617
|
+
const manager = new TeamManager(services);
|
|
618
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
619
|
+
manager.install();
|
|
620
|
+
|
|
621
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
622
|
+
spawnCallback({
|
|
623
|
+
type: "spawned",
|
|
624
|
+
agent: {
|
|
625
|
+
id: "child_1" as AgentId,
|
|
626
|
+
parent: "non_team_parent" as AgentId,
|
|
627
|
+
role: "worker",
|
|
628
|
+
state: "running",
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
expect(manager.getTeamForAgent("child_1")).toBeUndefined();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("ignores non-spawn lifecycle events", async () => {
|
|
636
|
+
const manager = new TeamManager(services);
|
|
637
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
638
|
+
manager.install();
|
|
639
|
+
|
|
640
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
641
|
+
spawnCallback({
|
|
642
|
+
type: "stopped",
|
|
643
|
+
agent: {
|
|
644
|
+
id: instance.result.rootId as AgentId,
|
|
645
|
+
parent: null,
|
|
646
|
+
role: "planner",
|
|
647
|
+
state: "stopped",
|
|
648
|
+
},
|
|
649
|
+
reason: "completed",
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Should not throw or change mappings
|
|
653
|
+
expect(manager.getTeamForAgent(instance.result.rootId)).toBe(instance);
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
describe("uninstall()", () => {
|
|
658
|
+
it("clears spawn interceptor from agent manager", async () => {
|
|
659
|
+
const manager = new TeamManager(services);
|
|
660
|
+
manager.install();
|
|
661
|
+
manager.uninstall();
|
|
662
|
+
|
|
663
|
+
expect(agentManager.setSpawnInterceptor).toHaveBeenLastCalledWith(null);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it("unsubscribes lifecycle listener", async () => {
|
|
667
|
+
const manager = new TeamManager(services);
|
|
668
|
+
manager.install();
|
|
669
|
+
|
|
670
|
+
const unsubscribeFn = vi.mocked(agentManager.onLifecycleEvent).mock.results[0].value;
|
|
671
|
+
|
|
672
|
+
manager.uninstall();
|
|
673
|
+
|
|
674
|
+
expect(unsubscribeFn).toHaveBeenCalled();
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
describe("structured team", () => {
|
|
679
|
+
it("starts structured team with push mode", async () => {
|
|
680
|
+
const manager = new TeamManager(services);
|
|
681
|
+
const instance = await manager.startTeam("structured", PROJECT_ROOT);
|
|
682
|
+
|
|
683
|
+
expect(instance.templateName).toBe("structured");
|
|
684
|
+
expect(instance.runtime.getTaskMode()).toBe("push");
|
|
685
|
+
expect(instance.runtime.getStrategyName()).toBe("queue");
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
describe("install() — spawn rules defense-in-depth", () => {
|
|
690
|
+
it("rejects spawn when child role is not in parent's spawn_rules", async () => {
|
|
691
|
+
const manager = new TeamManager(services);
|
|
692
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
693
|
+
manager.install();
|
|
694
|
+
|
|
695
|
+
// self-driving spawn_rules: planner: [grinder, planner], judge: [], grinder: []
|
|
696
|
+
// judge cannot spawn anything — attempt should throw
|
|
697
|
+
await expect(
|
|
698
|
+
agentManager.spawn({
|
|
699
|
+
task: "disallowed child",
|
|
700
|
+
role: "grinder",
|
|
701
|
+
parent: instance.result.companionIds[0], // judge
|
|
702
|
+
})
|
|
703
|
+
).rejects.toThrow(/Spawn rules violation: role 'judge' cannot spawn 'grinder'/);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it("allows spawn when child role is in parent's spawn_rules", async () => {
|
|
707
|
+
const manager = new TeamManager(services);
|
|
708
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
709
|
+
manager.install();
|
|
710
|
+
|
|
711
|
+
// planner can spawn grinder — should succeed
|
|
712
|
+
await expect(
|
|
713
|
+
agentManager.spawn({
|
|
714
|
+
task: "allowed child",
|
|
715
|
+
role: "grinder",
|
|
716
|
+
parent: instance.result.rootId, // planner
|
|
717
|
+
})
|
|
718
|
+
).resolves.toBeDefined();
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it("passes through spawn when parent role has no spawn_rules entry", async () => {
|
|
722
|
+
const manager = new TeamManager(services);
|
|
723
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
724
|
+
manager.install();
|
|
725
|
+
|
|
726
|
+
// Simulate a dynamically spawned agent with a role not in spawn_rules
|
|
727
|
+
const spawnCallback = lifecycleCallbacks.at(-1)!;
|
|
728
|
+
spawnCallback({
|
|
729
|
+
type: "spawned",
|
|
730
|
+
agent: { id: "dynamic_1" as AgentId, parent: instance.result.rootId as AgentId, role: "custom_role", state: "running" },
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// custom_role is not in spawn_rules — should pass through (no restriction)
|
|
734
|
+
await expect(
|
|
735
|
+
agentManager.spawn({
|
|
736
|
+
task: "dynamic child",
|
|
737
|
+
role: "some_child",
|
|
738
|
+
parent: "dynamic_1",
|
|
739
|
+
})
|
|
740
|
+
).resolves.toBeDefined();
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it("passes through spawn when no role is specified on child", async () => {
|
|
744
|
+
const manager = new TeamManager(services);
|
|
745
|
+
const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
746
|
+
manager.install();
|
|
747
|
+
|
|
748
|
+
// No role specified — spawn rules check is skipped
|
|
749
|
+
await expect(
|
|
750
|
+
agentManager.spawn({
|
|
751
|
+
task: "roleless child",
|
|
752
|
+
parent: instance.result.rootId,
|
|
753
|
+
})
|
|
754
|
+
).resolves.toBeDefined();
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
describe("role conflict detection", () => {
|
|
759
|
+
it("warns when two teams register the same role name with different capabilities", async () => {
|
|
760
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
761
|
+
|
|
762
|
+
const manager = new TeamManager(services);
|
|
763
|
+
// Both self-driving and structured inherit from built-in roles,
|
|
764
|
+
// but register custom role definitions. Starting the same template twice
|
|
765
|
+
// should not warn (same capabilities). Let's start two different templates
|
|
766
|
+
// that share no custom role names — no warning expected.
|
|
767
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
768
|
+
await manager.startTeam("structured", PROJECT_ROOT);
|
|
769
|
+
|
|
770
|
+
const conflictWarnings = warnSpy.mock.calls.filter(
|
|
771
|
+
call => typeof call[0] === "string" && call[0].includes("Role") && call[0].includes("conflict")
|
|
772
|
+
);
|
|
773
|
+
expect(conflictWarnings).toHaveLength(0);
|
|
774
|
+
|
|
775
|
+
warnSpy.mockRestore();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it("does not warn when same team is started twice (identical capabilities)", async () => {
|
|
779
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
780
|
+
|
|
781
|
+
const manager = new TeamManager(services);
|
|
782
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
783
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
784
|
+
|
|
785
|
+
const conflictWarnings = warnSpy.mock.calls.filter(
|
|
786
|
+
call => typeof call[0] === "string" && call[0].includes("conflict")
|
|
787
|
+
);
|
|
788
|
+
expect(conflictWarnings).toHaveLength(0);
|
|
789
|
+
|
|
790
|
+
warnSpy.mockRestore();
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("warns when a role is re-registered with different capabilities", async () => {
|
|
794
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
795
|
+
|
|
796
|
+
// Pre-register a role with specific capabilities
|
|
797
|
+
roleRegistry.registerRole({
|
|
798
|
+
name: "grinder",
|
|
799
|
+
capabilities: ["file.read", "file.write"],
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const manager = new TeamManager(services);
|
|
803
|
+
// self-driving team registers grinder with its own capabilities
|
|
804
|
+
await manager.startTeam("self-driving", PROJECT_ROOT);
|
|
805
|
+
|
|
806
|
+
const conflictWarnings = warnSpy.mock.calls.filter(
|
|
807
|
+
call => typeof call[0] === "string" && call[0].includes("Role 'grinder' conflict")
|
|
808
|
+
);
|
|
809
|
+
expect(conflictWarnings).toHaveLength(1);
|
|
810
|
+
|
|
811
|
+
warnSpy.mockRestore();
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
});
|