macro-agent 0.1.0 → 0.1.1
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/.claude/settings.local.json +3 -1
- package/.sudocode/specs.jsonl +4 -0
- package/CLAUDE.md +16 -14
- package/README.md +11 -29
- package/dist/acp/macro-agent.d.ts +15 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +131 -35
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/types.d.ts +32 -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 +65 -1
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +464 -183
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +37 -6
- package/dist/api/server.js.map +1 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/token.d.ts +41 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +73 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/cli/acp.d.ts +2 -23
- package/dist/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +127 -61
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +147 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +268 -181
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/parse-args.d.ts +20 -0
- package/dist/cli/parse-args.d.ts.map +1 -0
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli/parse-args.js.map +1 -0
- package/dist/cli/stable-instance-id.d.ts +8 -0
- package/dist/cli/stable-instance-id.d.ts.map +1 -0
- package/dist/cli/stable-instance-id.js +14 -0
- package/dist/cli/stable-instance-id.js.map +1 -0
- package/dist/config/project-config.d.ts +74 -7
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +123 -20
- package/dist/config/project-config.js.map +1 -1
- package/dist/map/adapter/acp-over-map.d.ts +17 -0
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
- package/dist/map/adapter/acp-over-map.js +384 -23
- package/dist/map/adapter/acp-over-map.js.map +1 -1
- package/dist/map/adapter/connection-manager.d.ts.map +1 -1
- package/dist/map/adapter/connection-manager.js +3 -0
- package/dist/map/adapter/connection-manager.js.map +1 -1
- package/dist/map/adapter/event-log.d.ts +87 -0
- package/dist/map/adapter/event-log.d.ts.map +1 -0
- package/dist/map/adapter/event-log.js +122 -0
- package/dist/map/adapter/event-log.js.map +1 -0
- package/dist/map/adapter/event-translator.js +6 -6
- package/dist/map/adapter/event-translator.js.map +1 -1
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
- package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
- package/dist/map/adapter/extensions/agent-lifecycle.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 +34 -0
- package/dist/map/adapter/extensions/index.js.map +1 -1
- package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
- package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
- package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
- package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
- package/dist/map/adapter/extensions/rename.d.ts +29 -0
- package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
- package/dist/map/adapter/extensions/rename.js +49 -0
- package/dist/map/adapter/extensions/rename.js.map +1 -0
- package/dist/map/adapter/extensions/task.d.ts.map +1 -1
- package/dist/map/adapter/extensions/task.js +10 -0
- package/dist/map/adapter/extensions/task.js.map +1 -1
- package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
- package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
- package/dist/map/adapter/extensions/update-metadata.js +67 -0
- package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
- package/dist/map/adapter/index.d.ts +2 -1
- package/dist/map/adapter/index.d.ts.map +1 -1
- package/dist/map/adapter/index.js +8 -2
- package/dist/map/adapter/index.js.map +1 -1
- package/dist/map/adapter/interface.d.ts +2 -0
- package/dist/map/adapter/interface.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.d.ts +3 -0
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +258 -35
- package/dist/map/adapter/map-adapter.js.map +1 -1
- package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
- package/dist/map/adapter/subscription-manager.js +5 -1
- package/dist/map/adapter/subscription-manager.js.map +1 -1
- package/dist/map/adapter/types.d.ts +2 -0
- package/dist/map/adapter/types.d.ts.map +1 -1
- package/dist/mcp/map-client.d.ts +39 -0
- package/dist/mcp/map-client.d.ts.map +1 -0
- package/dist/mcp/map-client.js +129 -0
- package/dist/mcp/map-client.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +14 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +113 -85
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/types.d.ts +9 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/metrics/metrics.js +1 -1
- package/dist/metrics/metrics.js.map +1 -1
- package/dist/roles/capabilities.d.ts +3 -1
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +17 -7
- package/dist/roles/capabilities.js.map +1 -1
- package/dist/roles/config-loader.d.ts +6 -6
- package/dist/roles/config-loader.d.ts.map +1 -1
- package/dist/roles/config-loader.js +6 -6
- package/dist/roles/config-loader.js.map +1 -1
- package/dist/roles/registry.d.ts +2 -2
- package/dist/roles/registry.js +2 -2
- package/dist/server/combined-server.d.ts +20 -0
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +107 -8
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.d.ts +2 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +69 -20
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/types/agents.d.ts +18 -0
- package/dist/store/types/agents.d.ts.map +1 -1
- package/dist/store/types/events.d.ts +1 -1
- package/dist/store/types/events.d.ts.map +1 -1
- package/dist/task/backend/index.d.ts +47 -29
- package/dist/task/backend/index.d.ts.map +1 -1
- package/dist/task/backend/index.js +109 -71
- package/dist/task/backend/index.js.map +1 -1
- package/dist/task/backend/memory.d.ts +1 -0
- package/dist/task/backend/memory.d.ts.map +1 -1
- package/dist/task/backend/memory.js +3 -0
- package/dist/task/backend/memory.js.map +1 -1
- package/dist/task/backend/opentasks/backend.d.ts +140 -0
- package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
- package/dist/task/backend/opentasks/backend.js +1023 -0
- package/dist/task/backend/opentasks/backend.js.map +1 -0
- package/dist/task/backend/opentasks/client.d.ts +337 -0
- package/dist/task/backend/opentasks/client.d.ts.map +1 -0
- package/dist/task/backend/opentasks/client.js +225 -0
- package/dist/task/backend/opentasks/client.js.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.js +195 -0
- package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
- package/dist/task/backend/opentasks/index.d.ts +21 -0
- package/dist/task/backend/opentasks/index.d.ts.map +1 -0
- package/dist/task/backend/opentasks/index.js +21 -0
- package/dist/task/backend/opentasks/index.js.map +1 -0
- package/dist/task/backend/opentasks/mapping.d.ts +48 -0
- package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
- package/dist/task/backend/opentasks/mapping.js +77 -0
- package/dist/task/backend/opentasks/mapping.js.map +1 -0
- package/dist/task/backend/types.d.ts +33 -53
- package/dist/task/backend/types.d.ts.map +1 -1
- package/dist/task/backend/types.js +7 -11
- package/dist/task/backend/types.js.map +1 -1
- package/dist/task/backend/unified-tool-provider.d.ts +57 -0
- package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
- package/dist/task/backend/unified-tool-provider.js +623 -0
- package/dist/task/backend/unified-tool-provider.js.map +1 -0
- package/dist/teams/team-loader.d.ts +2 -2
- package/dist/teams/team-loader.js +3 -3
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-runtime.d.ts.map +1 -1
- package/dist/teams/team-runtime.js +2 -0
- package/dist/teams/team-runtime.js.map +1 -1
- package/docs/architecture.md +7 -6
- package/docs/configuration.md +26 -62
- package/docs/implementation-details.md +5 -5
- package/docs/implementation-summary.md +17 -17
- package/docs/plan-self-driving-support.md +4 -4
- package/docs/spec-self-driving-support.md +10 -10
- package/docs/team-templates.md +2 -2
- package/docs/teams.md +3 -3
- package/docs/troubleshooting.md +10 -11
- package/package.json +6 -4
- package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
- package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
- package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
- package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
- package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
- package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
- package/src/acp/__tests__/integration.test.ts +56 -31
- package/src/acp/__tests__/macro-agent.test.ts +16 -7
- package/src/acp/macro-agent.ts +170 -36
- package/src/acp/types.ts +46 -1
- package/src/agent/__tests__/agent-manager.test.ts +228 -2
- package/src/agent/agent-manager.ts +714 -261
- package/src/agent/types.ts +3 -1
- package/src/api/server.ts +41 -7
- package/src/auth/__tests__/token.test.ts +100 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/token.ts +82 -0
- package/src/cli/__tests__/acp.test.ts +1 -1
- package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
- package/src/cli/acp.ts +130 -72
- package/src/cli/index.ts +120 -14
- package/src/cli/mcp.ts +311 -207
- package/src/cli/parse-args.ts +54 -0
- package/src/cli/stable-instance-id.ts +14 -0
- package/src/config/project-config.ts +190 -27
- package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
- package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
- package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
- package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
- package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
- package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
- package/src/map/adapter/__tests__/event-log.test.ts +527 -0
- package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
- package/src/map/adapter/__tests__/extensions.test.ts +408 -0
- package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
- package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
- package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
- package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
- package/src/map/adapter/acp-over-map.ts +678 -66
- package/src/map/adapter/connection-manager.ts +3 -0
- package/src/map/adapter/event-log.ts +208 -0
- package/src/map/adapter/event-translator.ts +6 -6
- package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
- package/src/map/adapter/extensions/index.ts +60 -0
- package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
- package/src/map/adapter/extensions/task.ts +11 -0
- package/src/map/adapter/extensions/update-metadata.ts +126 -0
- package/src/map/adapter/index.ts +28 -0
- package/src/map/adapter/interface.ts +2 -0
- package/src/map/adapter/map-adapter.ts +312 -47
- package/src/map/adapter/subscription-manager.ts +5 -1
- package/src/map/adapter/types.ts +2 -0
- package/src/mcp/__tests__/map-client.test.ts +386 -0
- package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
- package/src/mcp/__tests__/mcp-server.test.ts +100 -1
- package/src/mcp/map-client.ts +177 -0
- package/src/mcp/mcp-server.ts +191 -100
- package/src/mcp/types.ts +6 -1
- package/src/metrics/metrics.ts +1 -1
- package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
- package/src/roles/__tests__/config-loader.test.ts +7 -7
- package/src/roles/capabilities.ts +17 -7
- package/src/roles/config-loader.ts +6 -6
- package/src/roles/registry.ts +2 -2
- package/src/server/__tests__/combined-server.test.ts +94 -21
- package/src/server/combined-server.ts +189 -33
- package/src/steering/__tests__/steering-integration.test.ts +1 -1
- package/src/store/__tests__/event-store.test.ts +196 -1
- package/src/store/__tests__/instance.test.ts +3 -3
- package/src/store/event-store.ts +80 -21
- package/src/store/types/agents.ts +15 -0
- package/src/store/types/events.ts +1 -1
- package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
- package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
- package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
- package/src/task/backend/index.ts +156 -106
- package/src/task/backend/memory.ts +4 -0
- package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
- package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
- package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
- package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
- package/src/task/backend/opentasks/backend.ts +1323 -0
- package/src/task/backend/opentasks/client.ts +652 -0
- package/src/task/backend/opentasks/daemon-manager.ts +253 -0
- package/src/task/backend/opentasks/index.ts +69 -0
- package/src/task/backend/opentasks/mapping.ts +94 -0
- package/src/task/backend/types.ts +42 -66
- package/src/task/backend/unified-tool-provider.ts +779 -0
- package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
- package/src/teams/team-loader.ts +3 -3
- package/src/teams/team-runtime.ts +2 -0
- package/test_fixtures/README.md +2 -3
- package/test_fixtures/fixtures/index.ts +0 -3
- package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
- package/test_fixtures/fixtures/repos/index.ts +1 -3
- package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
- package/test_fixtures/fixtures/repos/types.ts +0 -11
- package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
- package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
- package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
- package/vitest.config.ts +1 -1
- package/vitest.e2e.config.ts +1 -1
- package/vitest.setup.ts +1 -30
- package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
- package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
- package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
- package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
- package/.macro-agent/teams/self-driving/team.yaml +0 -103
- package/.macro-agent/teams/structured/prompts/developer.md +0 -26
- package/.macro-agent/teams/structured/prompts/lead.md +0 -25
- package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
- package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
- package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
- package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
- package/.macro-agent/teams/structured/team.yaml +0 -89
- package/docs/sudocode-integration.md +0 -383
- package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
- package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
- package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
- package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
- package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
- package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
- package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
- package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
- package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
- package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
- package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
- package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
- package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
- package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
- package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
- package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
- package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
- package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
- package/src/task/backend/sudocode/backend.ts +0 -1237
- package/src/task/backend/sudocode/client.ts +0 -515
- package/src/task/backend/sudocode/index.ts +0 -120
- package/src/task/backend/sudocode/mapping.ts +0 -93
- package/src/task/backend/sudocode/server-client.ts +0 -522
- package/src/task/backend/sudocode/standalone-client.ts +0 -623
- package/src/task/backend/sudocode/sync-policy.ts +0 -387
- package/src/task/backend/sudocode/tools.ts +0 -896
- package/src/task/backend/tool-provider.ts +0 -506
- package/test_fixtures/fixtures/sudocode/index.ts +0 -29
- package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
- package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
package/.sudocode/specs.jsonl
CHANGED
|
@@ -46,3 +46,7 @@
|
|
|
46
46
|
{"id":"s-5w36","uuid":"2e027264-1fcb-4943-bab6-4b2e35a5c2a0","title":"Session Continuations (Phase 4)","file_path":"specs/s-5w36_session_continuations_phase_4.md","content":"\n# Session Continuations (Phase 4)\n\nPersist agent session history so long-running agents can be resumed across process restarts.\n\n## Status: COMPLETE (untested continuation monitoring)\n\n## Components\n\n### continueAgent() (`src/agent/agent-manager.ts`, lines 1384-1450)\n1. Load original agent from EventStore\n2. Query status events (up to `maxMessages`, default 50)\n3. Format event summaries as \"Prior Session Context\" markdown\n4. Spawn new agent with same role/parent + resume context as `customPrompt`\n5. Emit continuation event (`continuation_of: agentId`)\n\n```typescript\ninterface ContinueAgentOptions {\n maxMessages?: number; // History depth (default: 50)\n task?: string; // Override task description\n additionalContext?: string; // Extra context to prepend\n}\n```\n\n### Continuation monitoring (`src/teams/team-runtime.ts`)\n- `monitorContinuations()` watches agent lifecycle events via `onLifecycleEvent()`\n- Monitors root and companion agent IDs\n- On unexpected stop (not \"completed\" or \"cancelled\"): wait 1s → `continueAgent()` → update tracking\n- Only active when `lifecycle.continuations.enabled === true` in team manifest\n- Unsubscribes on teardown\n\n## Known Gap\n- **No test coverage** for `monitorContinuations()` — edge cases untested (multiple lifecycle events, spawn failure, state consistency)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-07 21:26:08","updated_at":"2026-02-07 21:26:08","parent_id":"s-1z9o","parent_uuid":"251030e1-ea2a-4857-95b3-2d94619f0b69","relationships":[{"from":"s-5w36","from_type":"spec","to":"s-1z9o","to_type":"spec","type":"implements"}],"tags":["continuations","phase-4","self-driving","teams"]}
|
|
47
47
|
{"id":"s-ir97","uuid":"9d8a27cd-2a68-4664-8384-a5c1bb657f71","title":"Autonomous Observability (Phase 5)","file_path":"specs/s-ir97_autonomous_observability_phase_5.md","content":"\n# Autonomous Observability (Phase 5)\n\nMetrics primitives for monitoring throughput, error rates, and agent utilization during long-running multi-agent runs.\n\n## Status: COMPLETE\n\n## Components\n\n### Metrics functions (`src/metrics/metrics.ts`, 280 LOC)\n\n| Function | Queries | Returns |\n|----------|---------|---------|\n| `getThroughputMetrics(store, windowMs)` | Task events | tasksCompleted, tasksFailed, tasksCreated, completedPerMinute, avgCompletionTimeMs |\n| `getUtilizationMetrics(store, windowMs)` | Agent spawn/terminate | activeAgents, totalSpawned, totalStopped, agentsByRole, agentsByState |\n| `getErrorMetrics(store, windowMs, limit)` | Failed status + task events | totalErrors, errorsByType, recentErrors |\n\nAll functions query EventStore directly — no new event types or materialized views needed.\n\n### REST API endpoints (`src/api/server.ts`)\n\n| Endpoint | Query Params | Description |\n|----------|-------------|-------------|\n| `GET /api/metrics/throughput` | `window_ms` (default: 5min) | Task completion rates |\n| `GET /api/metrics/utilization` | — | Agent counts by role and state |\n| `GET /api/metrics/errors` | `window_ms` (default: 30min), `limit` (default: 20) | Error counts and recent failures |\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-07 21:26:16","updated_at":"2026-02-07 21:26:16","parent_id":"s-1z9o","parent_uuid":"251030e1-ea2a-4857-95b3-2d94619f0b69","relationships":[{"from":"s-ir97","from_type":"spec","to":"s-1z9o","to_type":"spec","type":"implements"}],"tags":["metrics","phase-5","self-driving","teams"]}
|
|
48
48
|
{"id":"s-29pg","uuid":"7e8bbb6d-51df-403b-8929-5b2793603367","title":"Communication Topology Gaps","file_path":"specs/s-29pg_communication_topology_gaps.md","content":"\n# Communication Topology Gaps\n\nFeatures where YAML config was loaded and validated but not wired into runtime behavior. Identified in the post-implementation audit.\n\n## Status: COMPLETE\n\nAll 5 gaps resolved. Implementation feedback attached to each closed issue.\n\n## Gap 1: Signal Filtering — IMPLEMENTED\n\n**Issue**: `i-3o8g` (closed)\n\n`SignalFilter` callback on MessageRouter, installed by TeamRuntime. Checks two sources:\n1. Peer connection filters (per-agent-pair, directional) from `routing.peers[].signals`\n2. Channel subscription filters (per-role) from `subscriptions[role][].signals`\n\nSignal name carried in `details.signal` field. Untagged events always pass through.\n\n## Gap 2: Peer Routing from Config — IMPLEMENTED\n\n**Issue**: `i-96f6` (closed)\n\n`wirePeerRoutes()` in TeamRuntime reads `routing.peers`, maps `via` to subscription type:\n- `direct` → subtree subscription (directional)\n- `topic` → shared named topic for both agents\n- `scope` → role-based subscription\n\nDeferred wiring for roles not spawned at bootstrap. Signal filters stored per-peer.\n\n## Gap 3: Wake Logic for Status Delivery — IMPLEMENTED\n\n**Issue**: `i-4dh7` (closed)\n\nBoth `routeStatusToSubtreeSubscribers()` and `routeStatusToTopicSubscribers()` now call `wakeHandler` with `priority: \"normal\"` (wakes idle agents, queues for busy).\n\n## Gap 4: Emission Restrictions — IMPLEMENTED\n\n**Issue**: `i-1zso` (closed)\n\n`EmissionValidator` callback on MessageRouter. Checks agent role against `communication.emissions` allowlist. Enforcement mode determines action.\n\n## Gap 5: Enforcement Mode — IMPLEMENTED\n\n**Issue**: `i-1zso` (closed)\n\nBranches on enforcement mode in emission validator:\n- `strict` → reject (block emission)\n- `permissive` → warn (allow through)\n- `audit` → record `emission_violation` event in EventStore, allow through\n","priority":2,"archived":0,"archived_at":null,"created_at":"2026-02-07 21:26:38","updated_at":"2026-02-09 08:17:22","parent_id":"s-1z9o","parent_uuid":"251030e1-ea2a-4857-95b3-2d94619f0b69","relationships":[{"from":"s-29pg","from_type":"spec","to":"s-1z9o","to_type":"spec","type":"implements"}],"tags":["communication","gaps","self-driving","teams"]}
|
|
49
|
+
{"id":"s-7umt","uuid":"d8cca27a-4005-43dd-9732-33dde3ac85d5","title":"Multi-Team Support: Concurrent Teams on Single Server","file_path":"specs/s-7umt_multi_team_support_concurrent_teams_on_single_serv.md","content":"\n## Overview\n\nEnable multiple concurrent teams to coexist on a single macro-agent server instance. Currently the system supports exactly one team at a time due to global singleton callback slots (spawn interceptor, signal filter, emission validator) and a single `team_config` event in EventStore.\n\n## Key Design Decision: Multiplexer Pattern\n\nRather than making every global service team-aware (which would be invasive and break the clean separation of concerns), introduce a **TeamManager** that sits between the global services and multiple TeamRuntime instances. The TeamManager installs a single composite callback on each global service that dispatches to the correct TeamRuntime based on agent membership.\n\n## Requirements\n\n1. Multiple TeamRuntime instances coexist in same process\n2. Each team has isolated spawn interceptor logic, signal filters, emission validators\n3. Agents are associated with a specific team (persisted)\n4. Teams can be started/stopped independently\n5. Teams don't interfere with each other's communication topology\n6. Backward compatible: single team still works identically\n7. Agents not in any team continue to work normally (no team context applied)\n8. Cross-team messaging is blocked by default (agents can only communicate within their team)\n\n## Architecture\n\n```\nCLI (--team alpha --team beta)\n │\n ▼\n TeamManager\n ├── TeamRuntime(\"alpha\")\n │ ├── roleAgentMap\n │ ├── agentRoleMap\n │ ├── peerSignalFilters\n │ └── integrationStrategy\n ├── TeamRuntime(\"beta\")\n │ └── ...\n ├── compositeSpawnInterceptor → AgentManager.setSpawnInterceptor()\n ├── compositeSignalFilter → MessageRouter.setSignalFilter()\n └── compositeEmissionValidator → MessageRouter.setEmissionValidator()\n```\n\n## Phases\n\n### Phase 1: Agent Team Binding (Foundation)\n- Add `team_id` field to Agent record in EventStore\n- Pass `team_id` through spawn event payload\n- Update `applySpawnEvent` to persist team_id\n- Update `rowToAgent` to read team_id\n\n### Phase 2: TeamManager Core\n- New `src/teams/team-manager.ts` class\n- Registry of active TeamRuntime instances\n- Agent-to-team lookup via EventStore (team_id field)\n- Composite spawn interceptor that dispatches by parent team\n- Composite signal filter that dispatches by agent team\n- Composite emission validator that dispatches by agent team\n\n### Phase 3: EventStore Multi-Team Config\n- Change team_config event to include team_name in payload (already present)\n- Add query method to find team_config by team_name\n- MCP subprocess reads correct team_config for its agent's team\n\n### Phase 4: RoleRegistry Namespacing\n- Add optional team scope to custom role registration\n- Resolution: team-scoped custom > global custom > project > builtin\n- Prevent cross-team role name collisions\n\n### Phase 5: CLI and Config Changes\n- Support multiple `--team` flags or comma-separated values\n- Support `teams` array in config.json\n- Loop: load each team, add to TeamManager\n- Shutdown: teardown all teams\n\n### Phase 6: API and Observability\n- `GET /api/teams` endpoint listing all active teams\n- `GET /api/team/:name` endpoint for specific team info\n- Team-scoped agent/task filtering\n- Metrics per team\n\n## Cross-Team Messaging Policy\n\nBy default, cross-team messaging is blocked:\n- The composite signal filter returns `false` for messages between agents in different teams\n- Agents not in any team can communicate freely with each other\n- A future extension could add explicit cross-team channels\n\n## Error Cases\n\n- **Team name conflict**: Reject addTeam() if name already registered\n- **Team teardown with running agents**: Stop team agents before removing from registry\n- **Agent spawned by non-team parent for team role**: Spawn interceptor checks parent team membership\n- **MCP subprocess for agent in unknown team**: Falls back to no team context (backward compat)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 09:33:32","updated_at":"2026-02-18 09:33:32","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","multi-team","multiplexer"]}
|
|
50
|
+
{"id":"s-931n","uuid":"2d8ea3d7-c317-4d2e-8bb7-bd47d4a2affe","title":"Runtime Configuration Manager","file_path":"specs/s-931n_runtime_configuration_manager.md","content":"\n# Runtime Configuration Manager\n\n## Overview\n\nAdd a `ConfigManager` service that holds runtime configuration in memory, exposes typed getters/setters, and notifies subscribers of changes via an EventEmitter pattern. API endpoints write to ConfigManager; subscribing services (TeamRuntime, RoleRegistry) react to changes and update their own behavior accordingly.\n\n## Design Principles\n\n- **API-first**: Config changes flow through API endpoints, not file edits\n- **Decoupled**: ConfigManager doesn't know about MessageRouter, AgentManager, etc. — subscribers own the wiring to their services\n- **Layered**: File-based config is the base layer; runtime changes are an overlay that takes precedence\n- **Optional persistence**: Some changes write back to files (roles), others are ephemeral (tuning knobs reset to file defaults on restart)\n\n## Configuration Tiers\n\n### Tier 1: Infrastructure (static, load-once)\n**NOT managed by ConfigManager.** Stays as-is in `loadMergedConfig()`.\n- `port`, `host`\n- `auth.disabled`, `auth.secret`\n- `task.backend`, `task.opentasks.socket_path`\n\n### Tier 2: Templates (runtime-writable, affects future spawns)\n- **Role definitions** — capabilities, workspace, lifecycle, tools, prompts\n- **Team role definitions** — team-specific role overrides\n\n### Tier 3: Tuning (runtime-writable, affects running system)\n- `communication.enforcement` — strict | permissive | audit\n- `macro_agent.task_assignment.pull.*` — idle_timeout_s, claim_retry_delay_ms, max_concurrent_per_agent\n- `macro_agent.lifecycle.scaling.*` — min_workers, max_workers, idle_drain\n- `macro_agent.observability.*` — metrics_window_s, snapshot_interval_s\n- `macro_agent.integration.config.*` — max_retries, conflict_action (strategy params, not strategy choice)\n\n## ConfigManager Interface\n\n```typescript\ninterface ConfigManager extends EventEmitter {\n // ─── Read ────────────────────────────────────────────────\n /** Get a config value by dot-path. Returns runtime override if set, else file-based default. */\n get<T>(path: string): T | undefined;\n\n /** Get full section (e.g., \"roles\", \"tuning.scaling\") */\n getSection<T>(section: string): T | undefined;\n\n /** Get effective config: file base merged with runtime overrides */\n getEffective(): RuntimeConfig;\n\n // ─── Write ───────────────────────────────────────────────\n /** Set a runtime override. Emits \"config:<section>\" event. */\n set(path: string, value: unknown): void;\n\n /** Merge a partial config into a section. Emits \"config:<section>\" event. */\n merge(section: string, partial: Record<string, unknown>): void;\n\n /** Remove a runtime override, reverting to file-based default. */\n unset(path: string): void;\n\n // ─── Persistence ─────────────────────────────────────────\n /** Persist current runtime overrides to file (for sections that support it) */\n persist(section?: string): Promise<void>;\n\n // ─── Lifecycle ───────────────────────────────────────────\n /** Initialize from file-based config */\n initialize(fileConfig: MultiagentConfig, teamManifest?: TeamManifest): void;\n}\n```\n\n## Config Sections & Events\n\n| Section | Event name | Subscriber | Action on change |\n|---------|-----------|------------|-----------------|\n| `roles.<name>` | `config:roles` | RoleRegistry | `registerRole()` with updated definition |\n| `enforcement` | `config:enforcement` | TeamRuntime | Re-install emission validator via `setEmissionValidator()` |\n| `tuning.scaling` | `config:tuning.scaling` | TeamRuntime | Update scaling params (affects future spawn decisions) |\n| `tuning.task_assignment` | `config:tuning.task_assignment` | TeamRuntime | Update spawn interceptor with new pull-mode params |\n| `tuning.observability` | `config:tuning.observability` | Metrics service | Update metrics window/snapshot interval |\n| `tuning.integration` | `config:tuning.integration` | IntegrationStrategy | Update strategy params (max_retries, conflict_action) |\n\n## RuntimeConfig Schema\n\n```typescript\ninterface RuntimeConfig {\n /** Role overrides (Tier 2) */\n roles: Record<string, Partial<RoleDefinition>>;\n\n /** Communication enforcement level (Tier 3) */\n enforcement?: \"strict\" | \"permissive\" | \"audit\";\n\n /** Tuning knobs (Tier 3) */\n tuning: {\n scaling?: {\n min_workers?: number;\n max_workers?: number;\n idle_drain?: boolean;\n };\n task_assignment?: {\n idle_timeout_s?: number;\n claim_retry_delay_ms?: number;\n max_concurrent_per_agent?: number;\n };\n observability?: {\n metrics_window_s?: number;\n snapshot_interval_s?: number;\n };\n integration?: {\n max_retries?: number;\n conflict_action?: string;\n [key: string]: unknown; // strategy-specific params\n };\n };\n}\n```\n\n## API Endpoints\n\n```\nGET /api/config → full effective config (file base + runtime overrides)\nGET /api/config/:section → single section (e.g., /api/config/enforcement)\nPUT /api/config/:section → replace a section\nPATCH /api/config/:section → merge into a section\nDELETE /api/config/:section/:key → unset a runtime override (revert to file default)\nPOST /api/config/persist → write runtime overrides to disk (optional, section filter)\n```\n\n### Example requests\n\n```\nPATCH /api/config/enforcement\n{ \"value\": \"permissive\" }\n\nPATCH /api/config/tuning/scaling\n{ \"max_workers\": 8, \"idle_drain\": true }\n\nPUT /api/config/roles/worker\n{ \"capabilities\": [\"file.read\", \"file.write\", \"git.commit\", \"exec.command\"] }\n\nDELETE /api/config/tuning/scaling/max_workers\n(reverts to team.yaml default)\n```\n\n## Subscriber Wiring\n\nSubscribers register during initialization, not inside ConfigManager:\n\n```typescript\n// In TeamRuntime.initialize():\nconfigManager.on(\"config:enforcement\", (value) => {\n const newValidator = this.createEmissionValidator(value);\n this.messageRouter.setEmissionValidator(newValidator);\n});\n\nconfigManager.on(\"config:tuning.task_assignment\", (value) => {\n // Rebuild spawn interceptor with updated pull-mode params\n this.agentManager.setSpawnInterceptor(this.createSpawnInterceptor());\n});\n\n// In RoleRegistry (or wherever roles are managed):\nconfigManager.on(\"config:roles\", (roles) => {\n for (const [name, definition] of Object.entries(roles)) {\n registry.registerRole({ name, ...definition });\n }\n});\n```\n\n## Cross-Process Visibility\n\nWhen ConfigManager applies a change, it optionally emits a `config_update` event to EventStore:\n\n```typescript\neventStore.emit({\n type: \"config_update\",\n source: { agent_id: \"system\" },\n payload: {\n section: \"enforcement\",\n value: \"permissive\",\n timestamp: Date.now()\n }\n});\n```\n\nMCP subprocesses that need to read current config can query EventStore for the latest `config_update` events. This is secondary to the in-memory path — not all config changes need cross-process visibility.\n\n## Persistence Strategy\n\n| Section | Persists to | On restart |\n|---------|------------|-----------|\n| `roles.*` | `.multiagent/roles.json` | Roles survive restart |\n| `enforcement` | Not persisted | Reverts to `team.yaml` default |\n| `tuning.*` | Not persisted | Reverts to `team.yaml` default |\n\nRationale: Role changes are definitional (you want them to stick). Tuning knobs are operational (the YAML file is the source of truth for defaults; runtime tweaks are temporary adjustments).\n\nUsers can explicitly call `POST /api/config/persist` to write tuning overrides to file if they want them to survive restarts.\n\n## Initialization Flow\n\n```\nServer startup:\n 1. loadMergedConfig() → infrastructure config (port, host, auth, task)\n 2. new ConfigManager()\n 3. configManager.initialize(mergedConfig, teamManifest)\n → loads file-based defaults for roles, team tuning\n → runtime overlay starts empty\n 4. TeamRuntime.initialize(configManager)\n → subscribes to config:enforcement, config:tuning.*\n 5. RoleRegistry loaded, subscribes to config:roles\n 6. API server started with configManager reference\n```\n\n## File Layout\n\n```\nsrc/config/\n├── project-config.ts # Existing: static config loader (unchanged)\n├── config-manager.ts # NEW: runtime config manager\n├── types.ts # NEW: RuntimeConfig, ConfigSection types\n└── index.ts # Updated: export ConfigManager\n```\n\n## Out of Scope\n\n- Changing topology at runtime (root, companions, spawn rules, channels, subscriptions)\n- Swapping integration strategy (queue → trunk)\n- Changing task backend (memory → opentasks)\n- Config validation schemas (can add later)\n- Config diffing / rollback\n- WebSocket config change notifications to clients (can add later)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 23:35:39","updated_at":"2026-02-18 23:35:39","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["api","config","runtime"]}
|
|
51
|
+
{"id":"s-573k","uuid":"68b7357d-feaa-4200-abad-c689f9208ed9","title":"Recursive Teams: Composable Multi-Team Support","file_path":"specs/s-573k_recursive_teams_composable_multi_team_support.md","content":"## Overview\n\nEnable teams to be **composable and recursive** -- a team's topology can reference sub-teams, whose roots become children of the parent team's agents. This enables hierarchical multi-agent structures while keeping each team config self-contained and reusable.\n\n## Design Model\n\nTeams nest via the agent hierarchy. A sub-team's root agent is spawned as a **child** of a parent team agent. Each team is autonomous internally (own roles, communication, integration strategy). Cross-team communication flows through the hierarchy (parent-child subtree subscriptions) and optionally through shared topics.\n\n```\n[coordinator] team: project ← parent team's root\n├── [backend.lead] team: backend ← sub-team root (child of coordinator)\n│ ├── [worker] team: backend\n│ └── [reviewer] team: backend\n├── [frontend.lead] team: frontend ← sub-team root (child of coordinator)\n│ ├── [designer] team: frontend\n│ └── [implementer] team: frontend\n└── [qa-monitor] team: project ← parent team's companion\n```\n\n### Key Principles\n\n1. **Teams are self-contained**: A team.yaml doesn't know or care whether it's used standalone or as a sub-team\n2. **Nesting uses agent hierarchy**: Sub-team roots become children, not peers. Communication flows through parent-child relationships naturally\n3. **Each team governs itself**: Roles, communication channels, signal filters, emission validation, and integration strategy are scoped to each team's own agents\n4. **Cross-team communication**: Flows through hierarchy (subtree subscriptions) or shared topics bridged via the parent team\n\n## Config Schema\n\n### Sub-Team Reference (v1 - minimal)\n\nNew field `topology.teams` on team.yaml:\n\n```yaml\n# .multiagent/teams/project/team.yaml\nname: project\nroles: [coordinator, qa-monitor]\n\ntopology:\n root:\n role: coordinator\n companions:\n - role: qa-monitor\n teams: # NEW\n - name: backend # loads .multiagent/teams/backend/team.yaml\n - name: coding-team # can reuse same template twice\n as: frontend # instance name (becomes team_id)\n parent: root # which topology node owns this sub-team\n topics: [coordination] # parent-level topics sub-team root subscribes to\n\ncommunication:\n channels:\n - name: coordination\n signals: [status_update, blocking_issue, handoff]\n subscriptions:\n coordinator:\n - channel: coordination\n```\n\n### Sub-Team Reference Fields\n\n| Field | Required | Default | Description |\n|-------|----------|---------|-------------|\n| `name` | yes | — | Team template directory name under `.multiagent/teams/` |\n| `as` | no | same as `name` | Instance name. Becomes `team_id` for agents. Required when same template used twice. |\n| `parent` | no | `root` | Role name of the topology node that spawns this sub-team's root as its child |\n| `topics` | no | `[]` | Parent-level topic names the sub-team root auto-subscribes to |\n\n### Sub-team template (standalone, reusable)\n\n```yaml\n# .multiagent/teams/backend/team.yaml\nname: backend\nroles: [lead, worker, reviewer]\n\ntopology:\n root:\n role: lead\n companions:\n - role: worker\n - role: reviewer\n\ncommunication:\n channels:\n - name: work_coordination\n signals: [task_assigned, review_request, task_completed]\n subscriptions:\n lead:\n - channel: work_coordination\n worker:\n - channel: work_coordination\n reviewer:\n - channel: work_coordination\n```\n\nThis template works identically whether used standalone (`--team backend`) or as a sub-team of another team. When used as a sub-team, the only difference is its root agent gets a parent (instead of `parent: null`).\n\n### Recursion\n\nSub-teams can themselves reference sub-teams:\n\n```yaml\n# .multiagent/teams/backend/team.yaml\nname: backend\ntopology:\n root:\n role: lead\n companions:\n - role: reviewer\n teams:\n - name: api-team\n - name: database-team\n```\n\nThis composes arbitrarily deep.\n\n## Agent Records: `team_id`\n\nEvery agent gets a `team_id` field identifying which team it belongs to:\n\n- Set at spawn time (via spawn event payload)\n- Inherited from team context during bootstrap, or injected by spawn interceptor for dynamically spawned children\n- Persisted in EventStore agent materialized view\n- Used by TeamManager to dispatch callbacks to the correct TeamRuntime\n\n## TeamManager (Multiplexer)\n\nA flat registry of all active TeamRuntime instances (parent + all sub-teams). Installs single composite callbacks on global services:\n\n### Composite Spawn Interceptor\n1. Look up parent agent's `team_id` from EventStore\n2. If parent is in a team → delegate to that team's spawn interceptor\n3. Inject `team_id` into child spawn options (team membership inherits)\n4. For bootstrap spawns (`parent: null`), `team_id` is set explicitly\n\n### Composite Signal Filter\n1. Look up source and target agents' `team_id`\n2. Same team → delegate to that team's signal filter\n3. Cross-team → allow (the hierarchy handles visibility via subtree subscriptions)\n4. Unaffiliated → allow (no filtering for non-team agents)\n\n### Composite Emission Validator\n1. Look up emitting agent's `team_id`\n2. If in a team → delegate to that team's emission validator\n3. Unaffiliated → allow\n\n## Recursive Bootstrap Flow\n\n1. Parent team bootstraps normally (spawn root + companions)\n2. For each sub-team reference in `topology.teams`:\n a. Resolve the `parent` role → get parent agent ID from roleAgentMap\n b. Create child TeamRuntime from pre-loaded sub-team manifest\n c. Register child TeamRuntime with TeamManager\n d. Bootstrap child with `parentAgentId` set (root spawned as child, not peer)\n e. Subscribe sub-team root to bridged `topics`\n3. Sub-team may recursively bootstrap its own sub-teams (same process)\n\n## Role Namespacing\n\nWhen multiple teams define the same role name (e.g., both \"backend\" and \"frontend\" define \"lead\"):\n\n- **Internal to each team**: roles use short names (\"lead\", \"worker\")\n- **At parent level**: auto-prefixed with team instance name (\"backend.lead\", \"frontend.lead\")\n- **RoleRegistry**: team-scoped registration (`registerTeamRole(teamName, role)`)\n- **Resolution**: `resolveRole(\"lead\", teamName: \"backend\")` → backend's lead definition\n\n## Cross-Team Communication Patterns\n\n### Through Hierarchy (default)\nParent agent (coordinator) subscribes to sub-team root's subtree. Sees all events. Can relay messages between sub-teams.\n\n### Through Shared Topics (explicit)\nSub-team refs specify `topics: [coordination]`. Sub-team roots subscribe to parent-level topic channels. Enables direct pub/sub between sub-teams on parent-defined channels.\n\n### Direct Addressing (always available)\nAny agent can send to any other agent by ID. Team boundaries don't block direct messages -- they only scope signal filters and emission validators.\n\n## Backward Compatibility\n\n- No `topology.teams` → identical behavior to today\n- Existing team YAMLs → valid as-is, usable both standalone and as sub-team templates\n- Agents without `team_id` → unaffiliated, unchanged behavior\n- Single `--team` flag → TeamManager wraps the single team transparently\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 23:38:34","updated_at":"2026-02-18 23:38:34","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","multi-team","recursive","teams"]}
|
|
52
|
+
{"id":"s-8bbq","uuid":"b03dc85d-f7f9-4f1b-9933-1fa945bfc9c4","title":"Auto-Start OpenTasks Daemon with Central + Per-Project Connection Model","file_path":"specs/s-8bbq_auto_start_opentasks_daemon_with_central_per_proje.md","content":"## Context\n\nThe macro-agent server orchestrates agents across arbitrary working directories. Each project may have its own `.opentasks/` graph (git-tracked task/spec data). The macro-agent needs a unified task view for orchestration while preserving per-project task ownership.\n\nCurrently, the opentasks backend requires a **pre-running external daemon** — if the daemon isn't available, macro-agent falls back to an in-memory task backend. This spec defines how macro-agent auto-starts and manages opentasks daemons.\n\n## Key Design Constraint: Task Provider Lifecycle\n\n**Daemon spawning is part of the task provider lifecycle.** The `createTaskBackend()` factory owns daemon start/stop — callers (CLI entry points) don't need to manage daemon lifecycle separately. This keeps daemon management encapsulated within the task backend module.\n\n```\ncreateTaskBackend({ backend: { type: \"opentasks\" } })\n └── DaemonManager.ensureDaemon()\n ├── checkExistingDaemon(centralPath)\n │ ├── Running → connect to existing socket\n │ └── Not running → createDaemonWithStore() + start()\n └── Return { client, socketPath, ownsDaemon }\n\nTaskBackendResult.shutdown()\n └── DaemonManager.shutdown()\n ├── ownsDaemon=true → daemon.stop()\n └── ownsDaemon=false → client.disconnect() only\n```\n\n## Architecture: Central Daemon + Per-Project Connections\n\n```\n~/.multiagent/\n├── config.json # global config (layered config system)\n├── opentasks/ # central opentasks location\n│ ├── graph.jsonl # orchestration-level tasks\n│ ├── config.json # location identity + connections list\n│ ├── daemon.sock # IPC socket\n│ ├── daemon.lock # exclusive lock\n│ └── cache.db # SQLite query cache\n└── <instanceId>/ # EventStore data (per-instance)\n └── events.db\n\n/projects/frontend/.opentasks/ # pre-existing project opentasks\n├── graph.jsonl # project tasks (git-tracked)\n└── config.json # connected to central as child\n\n/projects/backend/.opentasks/ # another project\n├── graph.jsonl\n└── config.json\n```\n\n### Central Daemon\n\n- Lives at `~/.multiagent/opentasks/`\n- Auto-started by `createTaskBackend()` when opentasks backend is configured\n- Owns orchestration-level tasks (cross-project coordination)\n- Maintains connections to per-project `.opentasks/` locations\n- Provides federated queries across all connected locations\n- Stopped via `TaskBackendResult.shutdown()` (only if we started it)\n\n### Per-Project Connections\n\n- When an agent is spawned into a cwd that has `.opentasks/`, the central daemon auto-connects to it\n- Connect-only model: macro-agent does **not** auto-init `.opentasks/` in new directories\n- Connections are declared in the central daemon's `config.json` via the opentasks connections API\n- Cross-project task references use URI notation: `opentasks://<project-hash>/t-abc`\n\n## Implementation\n\n### New Module: `src/task/backend/opentasks/daemon-manager.ts`\n\nInternal to the opentasks backend module — not exposed as a standalone service.\n\n```typescript\ninterface DaemonManagerConfig {\n /** Central daemon location (default: ~/.multiagent/opentasks) */\n centralPath?: string;\n /** Whether to auto-connect project .opentasks/ on connectProject() */\n connectOnSpawn?: boolean;\n}\n\ninterface DaemonManagerResult {\n /** Connected OpenTasks client */\n client: OpenTasksClient;\n /** Daemon socket path */\n socketPath: string;\n /** Whether we started the daemon (vs connecting to existing) */\n ownsDaemon: boolean;\n}\n\ninterface DaemonManager {\n /** Start or connect to central daemon */\n ensureDaemon(): Promise<DaemonManagerResult>;\n /** Connect a project's .opentasks/ to the central daemon */\n connectProject(projectPath: string): Promise<void>;\n /** Check if a project is already connected */\n isProjectConnected(projectPath: string): boolean;\n /** Stop the daemon if we started it, disconnect client */\n shutdown(): Promise<void>;\n}\n```\n\n### Updated `TaskBackendResult`\n\n```typescript\ninterface TaskBackendResult {\n backend: TaskBackend;\n openTasksClient?: OpenTasksClient;\n /** Shutdown function — stops daemon if we started it */\n shutdown?: () => Promise<void>;\n}\n```\n\n### Updated `createTaskBackend()` Flow\n\nWhen `backendConfig.type === \"opentasks\"`:\n\n```\n1. Create DaemonManager with config\n2. Call daemonManager.ensureDaemon()\n ├── Determine centralPath: config.centralPath ?? ~/.multiagent/opentasks/\n ├── mkdir -p centralPath (ensure directory exists)\n ├── checkExistingDaemon(centralPath)\n │ ├── running=true → connect client to existing socketPath\n │ └── running=false →\n │ a. createDaemonWithStore({ locationPath: centralPath, version })\n │ b. await daemon.start()\n │ c. connect client to daemon.socketPath\n │ d. Set ownsDaemon=true\n └── Return { client, socketPath, ownsDaemon }\n3. Create OpenTasksTaskBackend with client\n4. Return { backend, openTasksClient, shutdown: () => daemonManager.shutdown() }\n```\n\n### Config Integration\n\nThe `.multiagent/config.json` (layered config system) controls behavior:\n\n```json\n{\n \"task\": {\n \"backend\": \"opentasks\",\n \"opentasks\": {\n \"auto_start\": true,\n \"central_path\": \"~/.multiagent/opentasks\",\n \"connect_on_spawn\": true\n }\n }\n}\n```\n\n| Config Key | Default | Description |\n| --- | --- | --- |\n| `task.opentasks.auto_start` | `true` | Auto-start central daemon in createTaskBackend() |\n| `task.opentasks.central_path` | `~/.multiagent/opentasks` | Central daemon location |\n| `task.opentasks.connect_on_spawn` | `true` | Auto-connect project .opentasks/ on agent spawn |\n\n### CLI Entry Point Changes\n\nMinimal — daemon lifecycle is encapsulated in the backend:\n\n```typescript\n// In acp.ts / index.ts:\nconst result = await createTaskBackend(taskConfig, eventStore);\ntaskBackend = result.backend;\n// ...\nconst cleanup = async () => {\n await result.shutdown?.(); // Stops daemon if we started it\n await eventStore.close();\n};\n```\n\n### Connect-on-Spawn (Phase 2)\n\nStill wired via `agent-manager.ts` but calls into the daemon manager:\n\n```typescript\n// agent-manager.ts\nagentManager.onLifecycleEvent((event) => {\n if (event.type === \"spawned\" && daemonManager) {\n daemonManager.connectProject(event.agent.cwd);\n }\n});\n```\n\n## opentasks Package APIs Used\n\n| Operation | API | Import Path |\n| --- | --- | --- |\n| Check existing daemon | `checkExistingDaemon(locationPath)` | `opentasks` (dynamic) |\n| Create daemon with store | `createDaemonWithStore({ locationPath, version })` | `opentasks` (dynamic) |\n| Start/stop daemon | `daemon.start()` / `daemon.stop()` | Daemon instance |\n| Connect client | `IPCOpenTasksClient({ socketPath })` | local client.ts |\n| Create connection | `createConnection(targetPath, role, basePath)` | `opentasks` (dynamic) |\n| Discover locations | `discoverLocations(startDir, { direction })` | `opentasks` (dynamic) |\n\nAll opentasks imports are dynamic (`await import(\"opentasks\")`) to avoid hard dependencies.\n\n## Edge Cases\n\n### Multiple macro-agent instances on same machine\n\n- `checkExistingDaemon()` detects pre-existing daemon at `~/.multiagent/opentasks/`\n- Second instance connects to existing daemon (shared central graph)\n- Only the instance that started the daemon stops it (tracked via `ownsDaemon` flag)\n\n### opentasks package not installed\n\n- Dynamic import fails → fall back to memory backend (existing behavior in CLI entry points)\n- Log warning: \"opentasks package not available, using memory backend\"\n\n### Daemon crashes mid-session\n\n- Client operations throw `CONNECTION_FAILED`\n- For v1: fall back to memory backend on daemon loss (existing try/catch in CLI entry points)\n- Future: reconnect logic in daemon manager\n\n### Agent spawned in subdirectory of project with .opentasks/\n\n- Walk up from agent cwd to find nearest `.opentasks/`\n- Use `discoverLocations(cwd, { direction: 'up' })` from opentasks API\n- Connect the discovered location, not the agent's exact cwd\n\n## Phases\n\n### Phase 1: Auto-start central daemon (within createTaskBackend)\n\n- Create `DaemonManager` module at `src/task/backend/opentasks/daemon-manager.ts`\n- Integrate into `createTaskBackend()` factory\n- Add `shutdown` to `TaskBackendResult`\n- Update CLI cleanup to call `result.shutdown?.()`\n- Fall back to memory on failure (existing behavior)\n\n### Phase 2: Connect-on-spawn\n\n- Hook into agent spawn lifecycle via agent-manager\n- Auto-discover and connect project `.opentasks/` directories\n- Surface connected projects in API/health endpoint\n\n### Phase 3: Federated queries\n\n- Ensure `query({ ready: {} })` works across connected locations\n- Surface project origin in task metadata\n- Cross-project edge creation via URI references\n\n## Non-Goals\n\n- Auto-initializing `.opentasks/` in new project directories (connect-only model)\n- Running separate daemons per project (single central daemon)\n- Migrating existing EventStore tasks to opentasks (dual persistence continues)\n- Replacing the memory backend fallback (always available as safety net)","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 23:58:49","updated_at":"2026-02-19 00:52:32","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","config","daemon","opentasks"]}
|
package/CLAUDE.md
CHANGED
|
@@ -10,7 +10,7 @@ macro-agent enables coordinated work across multiple AI agents with:
|
|
|
10
10
|
- **Pluggable integration strategies** (queue, trunk, optimistic)
|
|
11
11
|
- **Workspace isolation** via git worktrees
|
|
12
12
|
- **Merge queue** for serialized integration
|
|
13
|
-
- **Task backend** abstraction (memory or
|
|
13
|
+
- **Task backend** abstraction (memory or opentasks) with push and pull modes
|
|
14
14
|
- **In-flight steering** via context injection
|
|
15
15
|
- **Signal filtering and emission enforcement** for communication topology
|
|
16
16
|
- **Session continuations** for long-running daemon agents
|
|
@@ -46,7 +46,7 @@ macro-agent enables coordinated work across multiple AI agents with:
|
|
|
46
46
|
│ Roles │ │ Workspace │ │ Tasks │
|
|
47
47
|
│ Built-in + │ │ Bare Repo │ │ Backend │
|
|
48
48
|
│ Team-defined│ │ Worktrees │ │ (memory/ │
|
|
49
|
-
│ (via YAML) │ │ Strategies │ │
|
|
49
|
+
│ (via YAML) │ │ Strategies │ │ opentasks) │
|
|
50
50
|
│ │ │ (queue/ │ │ Push/Pull │
|
|
51
51
|
│ │ │ trunk/opt) │ │ modes │
|
|
52
52
|
└──────────────┘ └──────────────┘ └──────────────┘
|
|
@@ -92,7 +92,7 @@ src/
|
|
|
92
92
|
│ └── index.ts # CLI commands (start, chat, status, --team)
|
|
93
93
|
│
|
|
94
94
|
├── config/ # Project configuration
|
|
95
|
-
│ └── project-config.ts # .
|
|
95
|
+
│ └── project-config.ts # .multiagent/config.json loader
|
|
96
96
|
│
|
|
97
97
|
├── lifecycle/ # Agent lifecycle management
|
|
98
98
|
│ ├── handlers/ # Role-specific done() handlers
|
|
@@ -152,8 +152,8 @@ src/
|
|
|
152
152
|
│ └── backend/ # Pluggable task backends
|
|
153
153
|
│ ├── types.ts # TaskBackend interface (+ claim/unclaim/listClaimable)
|
|
154
154
|
│ ├── memory.ts # InMemoryTaskBackend (push + pull)
|
|
155
|
-
│ ├── tool-provider.ts #
|
|
156
|
-
│ └──
|
|
155
|
+
│ ├── unified-tool-provider.ts # Unified task MCP tool provider (7 tools)
|
|
156
|
+
│ └── opentasks/ # OpenTasks integration
|
|
157
157
|
│
|
|
158
158
|
├── teams/ # Team template system
|
|
159
159
|
│ ├── types.ts # TeamManifest, TeamTopology, TeamCommunication
|
|
@@ -219,9 +219,12 @@ Each worker gets an isolated git worktree:
|
|
|
219
219
|
|
|
220
220
|
Two backends available:
|
|
221
221
|
- **memory**: In-memory tasks with EventStore persistence (supports push + pull modes)
|
|
222
|
-
- **
|
|
222
|
+
- **opentasks**: External issue tracking with dependency management (supports push + pull modes)
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
The unified task tool provider exposes 7 MCP tools:
|
|
225
|
+
- Always available: `create_task`, `get_task`, `list_tasks`, `assign_task`
|
|
226
|
+
- When OpenTasks client available: `task` (upsert), `link`, `annotate`
|
|
227
|
+
- Pull mode adds: `claim_task`, `unclaim_task`, `list_claimable_tasks` (gated by `task.claim` capability)
|
|
225
228
|
|
|
226
229
|
### Communication Topology
|
|
227
230
|
|
|
@@ -301,23 +304,23 @@ npm run test:e2e # E2E tests (requires RUN_E2E_TESTS=true)
|
|
|
301
304
|
|
|
302
305
|
### Adding a Team Role (via YAML)
|
|
303
306
|
|
|
304
|
-
1. Create `.
|
|
307
|
+
1. Create `.multiagent/teams/<team>/roles/<role>.yaml` with `extends` base role
|
|
305
308
|
2. Add `capabilities_add`/`capabilities_remove` as needed
|
|
306
|
-
3. Create `.
|
|
309
|
+
3. Create `.multiagent/teams/<team>/prompts/<role>.md` for custom prompt
|
|
307
310
|
4. Reference the role in `team.yaml` topology and communication sections
|
|
308
311
|
|
|
309
312
|
### Modifying Task Backend
|
|
310
313
|
|
|
311
314
|
1. Update interface in `src/task/backend/types.ts`
|
|
312
|
-
2. Implement in both `memory.ts` and `
|
|
313
|
-
3. Update tool provider if adding new operations
|
|
315
|
+
2. Implement in both `memory.ts` and `opentasks/`
|
|
316
|
+
3. Update unified tool provider if adding new operations
|
|
314
317
|
|
|
315
318
|
## Environment Variables
|
|
316
319
|
|
|
317
320
|
| Variable | Description | Default |
|
|
318
321
|
|----------|-------------|---------|
|
|
319
|
-
| `MACRO_TASK_BACKEND` | Task backend: `memory` or `
|
|
320
|
-
| `
|
|
322
|
+
| `MACRO_TASK_BACKEND` | Task backend: `memory` or `opentasks` | `opentasks` |
|
|
323
|
+
| `OPENTASKS_SOCKET_PATH` | Path to OpenTasks socket | — |
|
|
321
324
|
| `MACRO_WORKSPACE_POOL_SIZE` | Max concurrent workspaces | `10` |
|
|
322
325
|
| `MACRO_MERGE_QUEUE_DB` | Merge queue SQLite path | `:memory:` |
|
|
323
326
|
| `MACRO_TEAM_NAME` | Team name (injected into agent env by team runtime) | — |
|
|
@@ -333,4 +336,3 @@ npm run test:e2e # E2E tests (requires RUN_E2E_TESTS=true)
|
|
|
333
336
|
- [docs/configuration.md](docs/configuration.md) - Configuration reference
|
|
334
337
|
- [docs/teams.md](docs/teams.md) - Team template schema reference
|
|
335
338
|
- [docs/team-templates.md](docs/team-templates.md) - Team template format and examples
|
|
336
|
-
- [docs/sudocode-integration.md](docs/sudocode-integration.md) - Sudocode backend details
|
package/README.md
CHANGED
|
@@ -19,28 +19,7 @@ A multi-agent orchestration system for spawning and managing hierarchical Claude
|
|
|
19
19
|
- **Context Injection** - Push context into running agents without waiting for message checks
|
|
20
20
|
- **Session Continuations** - Auto-resume long-running agents across process restarts
|
|
21
21
|
- **Observability** - Throughput, utilization, and error metrics via REST API
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
## Sudocode Integration
|
|
25
|
-
|
|
26
|
-
macro-agent can integrate with [sudocode](https://github.com/sudocode-ai/sudocode) for external issue tracking:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
# Enable sudocode backend
|
|
30
|
-
export MACRO_TASK_BACKEND=sudocode
|
|
31
|
-
export SUDOCODE_PROJECT_PATH=/path/to/project
|
|
32
|
-
|
|
33
|
-
# Start macro-agent server (full mode with ACP + MAP + REST)
|
|
34
|
-
npx multiagent
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
With sudocode enabled:
|
|
38
|
-
- Tasks are bound to sudocode issues via `external_id`
|
|
39
|
-
- Blocking relationships come from sudocode's issue links
|
|
40
|
-
- `listReady()` returns only tasks with no incomplete blockers
|
|
41
|
-
- Task status can sync with issue status
|
|
42
|
-
|
|
43
|
-
See [docs/sudocode-integration.md](docs/sudocode-integration.md) for full documentation.
|
|
22
|
+
- **OpenTasks Integration** - Optional issue tracking with dependency management
|
|
44
23
|
|
|
45
24
|
## Team Templates
|
|
46
25
|
|
|
@@ -50,15 +29,15 @@ Teams define multi-agent topologies as YAML configuration. A team template speci
|
|
|
50
29
|
# Start with a team template
|
|
51
30
|
npx multiagent --team self-driving
|
|
52
31
|
|
|
53
|
-
# Or set in project config (.
|
|
54
|
-
echo '{ "team": "self-driving" }' > .
|
|
32
|
+
# Or set in project config (.multiagent/config.json)
|
|
33
|
+
echo '{ "team": "self-driving" }' > .multiagent/config.json
|
|
55
34
|
npx multiagent
|
|
56
35
|
```
|
|
57
36
|
|
|
58
|
-
Teams are stored in `.
|
|
37
|
+
Teams are stored in `.multiagent/teams/<name>/`:
|
|
59
38
|
|
|
60
39
|
```
|
|
61
|
-
.
|
|
40
|
+
.multiagent/teams/self-driving/
|
|
62
41
|
├── team.yaml # Team manifest (topology, communication, strategy)
|
|
63
42
|
├── roles/
|
|
64
43
|
│ ├── planner.yaml # Custom role (extends coordinator)
|
|
@@ -295,7 +274,11 @@ Fallback chain: `inject()` → `interruptWith()` → high-priority message
|
|
|
295
274
|
| `stop_agent` | Terminate an agent |
|
|
296
275
|
| `create_task` | Create a new task |
|
|
297
276
|
| `get_task` | Get task details |
|
|
298
|
-
| `
|
|
277
|
+
| `list_tasks` | List tasks with optional filters |
|
|
278
|
+
| `assign_task` | Assign task to agent |
|
|
279
|
+
| `task` | Upsert task (OpenTasks only) |
|
|
280
|
+
| `link` | Link tasks (OpenTasks only) |
|
|
281
|
+
| `annotate` | Add annotations (OpenTasks only) |
|
|
299
282
|
| `claim_task` | Claim next available task (pull mode) |
|
|
300
283
|
| `unclaim_task` | Return claimed task to pool (pull mode) |
|
|
301
284
|
| `list_claimable_tasks` | List claimable tasks (pull mode) |
|
|
@@ -332,7 +315,7 @@ npx multiagent --acp --cwd /path/to/project
|
|
|
332
315
|
| `--port <port>` | Server port (default: 3001) |
|
|
333
316
|
| `--host <host>` | Server host (default: localhost) |
|
|
334
317
|
| `--cwd <path>` | Working directory for agents |
|
|
335
|
-
| `--team <name>` | Load team template from `.
|
|
318
|
+
| `--team <name>` | Load team template from `.multiagent/teams/<name>/` |
|
|
336
319
|
| `--acp` | Stdio ACP-only mode (for embedded use with acp-factory) |
|
|
337
320
|
|
|
338
321
|
### Multi-Client Architecture
|
|
@@ -454,7 +437,6 @@ RUN_E2E_TESTS=true ANTHROPIC_API_KEY=xxx npm run test:e2e
|
|
|
454
437
|
- [Configuration Reference](docs/configuration.md) - Environment variables and config options
|
|
455
438
|
- [Team Templates](docs/team-templates.md) - Team template format and examples
|
|
456
439
|
- [Team Schema Reference](docs/teams.md) - Full YAML schema reference
|
|
457
|
-
- [Sudocode Integration](docs/sudocode-integration.md) - External issue tracking
|
|
458
440
|
- [Troubleshooting Guide](docs/troubleshooting.md) - Common issues and solutions
|
|
459
441
|
|
|
460
442
|
## License
|
|
@@ -173,6 +173,10 @@ export declare class MacroAgent implements Agent {
|
|
|
173
173
|
* then calls the agent manager to cancel the pending permission.
|
|
174
174
|
*/
|
|
175
175
|
private handleCancelPermission;
|
|
176
|
+
/**
|
|
177
|
+
* Change the permission mode for a running agent at runtime
|
|
178
|
+
*/
|
|
179
|
+
private handleSetPermissionMode;
|
|
176
180
|
/**
|
|
177
181
|
* Resume a stopped/failed agent
|
|
178
182
|
*/
|
|
@@ -229,6 +233,17 @@ export declare class MacroAgent implements Agent {
|
|
|
229
233
|
* Record user and assistant turns after a prompt completes.
|
|
230
234
|
*/
|
|
231
235
|
private recordPromptTurns;
|
|
236
|
+
/**
|
|
237
|
+
* Handle _macro/getModels extension — returns the session's available models.
|
|
238
|
+
* Claude Code populates models asynchronously after session creation
|
|
239
|
+
* (via _model_state_update notification), so this allows the TUI to poll
|
|
240
|
+
* for the model list once it's available.
|
|
241
|
+
*
|
|
242
|
+
* Returns full model info (modelId + name) since Claude Code uses shorthand
|
|
243
|
+
* model IDs ("default", "sonnet") that don't match models.dev. The name
|
|
244
|
+
* field (e.g., "Claude Sonnet 4") enables better model registry matching.
|
|
245
|
+
*/
|
|
246
|
+
private handleGetModels;
|
|
232
247
|
/**
|
|
233
248
|
* Handle _macro/getHistory extension — returns conversation turns for a session
|
|
234
249
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"macro-agent.d.ts","sourceRoot":"","sources":["../../src/acp/macro-agent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,KAAK,EACL,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,kBAAkB,
|
|
1
|
+
{"version":3,"file":"macro-agent.d.ts","sourceRoot":"","sources":["../../src/acp/macro-agent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,KAAK,EACL,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EACV,YAAY,EAYZ,oBAAoB,EA6BrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAoDlE,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,YAAY,EAAE,YAAY,CAAC;IAE3B,iCAAiC;IACjC,UAAU,EAAE,UAAU,CAAC;IAEvB,sCAAsC;IACtC,WAAW,EAAE,WAAW,CAAC;IAEzB,iEAAiE;IACjE,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B,kEAAkE;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAEtC,+FAA+F;IAC/F,YAAY,CAAC,EAAE,YAAY,CAAC;IAE5B,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAsBD;;;GAGG;AACH,qBAAa,UAAW,YAAW,KAAK;IACtC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAS;IAE3B,4CAA4C;IAC5C,OAAO,CAAC,UAAU,CAA4B;IAE9C,8DAA8D;IAC9D,OAAO,CAAC,uBAAuB,CACnB;IAEZ,2FAA2F;IAC3F,OAAO,CAAC,aAAa,CAGP;IAEd,sFAAsF;IACtF,OAAO,CAAC,cAAc,CAGR;gBAEF,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAE,gBAAgB;IAwBrE;;;;;OAKG;IACG,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0BxE;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAkCxE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAoF3E;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IAKhC;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAwE5D;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCvD;;OAEG;IACG,SAAS,CACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAmHnC;;OAEG;YACW,gBAAgB;IAmE9B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAqB1B;;OAEG;YACW,kBAAkB;IAsChC;;OAEG;YACW,aAAa;IAgB3B;;;;;OAKG;YACW,gBAAgB;IAoC9B;;;;;OAKG;YACW,eAAe;IA8D7B;;;;OAIG;YACW,qBAAqB;IA0CnC;;;;;OAKG;YACW,qBAAqB;IAyCnC;;;;;OAKG;YACW,wBAAwB;IA6BtC;;;;;;OAMG;YACW,wBAAwB;IA6BtC;;OAEG;YACW,qBAAqB;IAsBnC;;OAEG;YACW,sBAAsB;IAoBpC;;OAEG;YACW,qBAAqB;IAqBnC;;OAEG;YACW,qBAAqB;IAsBnC;;;;;OAKG;YACW,yBAAyB;IAkCvC;;;;;OAKG;YACW,sBAAsB;IA8BpC;;OAEG;YACW,uBAAuB;IAgCrC;;OAEG;YACW,iBAAiB;IAqC/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiB7B;;;;;;;;;;;;;;;;;OAiBG;YACW,oBAAoB;IAmOlC;;;OAGG;YACW,eAAe;IAyB7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAqC3B;;OAEG;IACH,OAAO,CAAC,aAAa;IAuBrB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA8B1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA4DzB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;IAoCvB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA2BxB;;OAEG;IACH,gBAAgB,IAAI,aAAa;IAIjC;;OAEG;IACH,gBAAgB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,GAAG,SAAS;IAIjE;;OAEG;IACH,aAAa,IAAI,oBAAoB;CAGtC"}
|
package/dist/acp/macro-agent.js
CHANGED
|
@@ -43,10 +43,27 @@ const SUPPORTED_EXTENSIONS = [
|
|
|
43
43
|
"_macro/checkCapability",
|
|
44
44
|
"_macro/respondToPermission",
|
|
45
45
|
"_macro/cancelPermission",
|
|
46
|
+
"_macro/setPermissionMode",
|
|
46
47
|
"_macro/resume",
|
|
47
48
|
"_macro/getHistory",
|
|
49
|
+
"_macro/getModels",
|
|
48
50
|
];
|
|
49
51
|
// ─────────────────────────────────────────────────────────────────
|
|
52
|
+
// Helpers
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Build a SessionModelState from the acp-factory Session.models (string[]).
|
|
56
|
+
* Returns null if the models array is empty or missing.
|
|
57
|
+
*/
|
|
58
|
+
function buildModelState(models) {
|
|
59
|
+
if (!models || models.length === 0)
|
|
60
|
+
return null;
|
|
61
|
+
return {
|
|
62
|
+
currentModelId: models[0],
|
|
63
|
+
availableModels: models.map((id) => ({ modelId: id, name: id })),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────
|
|
50
67
|
// MacroAgent Implementation
|
|
51
68
|
// ─────────────────────────────────────────────────────────────────
|
|
52
69
|
/**
|
|
@@ -145,6 +162,7 @@ export class MacroAgent {
|
|
|
145
162
|
await this.emitSessionInfo(acpSessionId);
|
|
146
163
|
return {
|
|
147
164
|
sessionId: acpSessionId,
|
|
165
|
+
models: buildModelState(spawned.session.models),
|
|
148
166
|
};
|
|
149
167
|
}
|
|
150
168
|
/**
|
|
@@ -153,6 +171,7 @@ export class MacroAgent {
|
|
|
153
171
|
async loadSession(params) {
|
|
154
172
|
let acpSessionId = params.sessionId;
|
|
155
173
|
const cwd = params.cwd ?? this.defaultCwd;
|
|
174
|
+
// DEBUG: Log loadSession params
|
|
156
175
|
// Extension: If _meta.agentId provided, look up session from agent record
|
|
157
176
|
// This allows resuming a stopped head manager by MAP agent ID
|
|
158
177
|
// when the TUI doesn't know the ACP session ID
|
|
@@ -180,30 +199,40 @@ export class MacroAgent {
|
|
|
180
199
|
}
|
|
181
200
|
this.ensureConversation(acpSessionId, existing.id);
|
|
182
201
|
await this.emitSessionInfo(acpSessionId);
|
|
183
|
-
|
|
202
|
+
const activeSession = this.agentManager.getSession(existing.id);
|
|
203
|
+
return {
|
|
204
|
+
models: activeSession ? buildModelState(activeSession.models) : null,
|
|
205
|
+
};
|
|
184
206
|
}
|
|
185
207
|
// Agent exists but no active session - resume it
|
|
186
208
|
console.log(`[MacroAgent] loadSession: Resuming stopped agent ${existing.id}`);
|
|
187
|
-
const
|
|
209
|
+
const defaultConfig = this.initConfig.defaultSubAgentConfig;
|
|
210
|
+
const spawned = await this.agentManager.resume(existing.id, defaultConfig?.permissionMode);
|
|
188
211
|
// Create session mapping
|
|
189
212
|
this.sessionMapper.createMapping(acpSessionId, spawned.id);
|
|
190
213
|
this.cancellationControllers.set(acpSessionId, new AbortController());
|
|
191
214
|
this.ensureConversation(acpSessionId, spawned.id);
|
|
192
215
|
await this.emitSessionInfo(acpSessionId);
|
|
193
|
-
return {
|
|
216
|
+
return {
|
|
217
|
+
models: buildModelState(spawned.session.models),
|
|
218
|
+
};
|
|
194
219
|
}
|
|
195
220
|
// No existing agent found - try to get or create with the specific session ID
|
|
196
221
|
console.log(`[MacroAgent] loadSession: No existing agent for session ${acpSessionId}, creating new`);
|
|
222
|
+
const defaultConfig = this.initConfig.defaultSubAgentConfig;
|
|
197
223
|
const spawned = await this.agentManager.getOrCreateHeadManager({
|
|
198
224
|
cwd,
|
|
199
225
|
sessionId: acpSessionId,
|
|
226
|
+
permissionMode: defaultConfig?.permissionMode,
|
|
200
227
|
});
|
|
201
228
|
// Create session mapping
|
|
202
229
|
this.sessionMapper.createMapping(acpSessionId, spawned.id);
|
|
203
230
|
this.cancellationControllers.set(acpSessionId, new AbortController());
|
|
204
231
|
this.ensureConversation(acpSessionId, spawned.id);
|
|
205
232
|
await this.emitSessionInfo(acpSessionId);
|
|
206
|
-
return {
|
|
233
|
+
return {
|
|
234
|
+
models: buildModelState(spawned.session.models),
|
|
235
|
+
};
|
|
207
236
|
}
|
|
208
237
|
/**
|
|
209
238
|
* Authenticate - macro-agent doesn't require authentication
|
|
@@ -342,10 +371,14 @@ export class MacroAgent {
|
|
|
342
371
|
return this.handleRespondToPermission(params);
|
|
343
372
|
case "_macro/cancelPermission":
|
|
344
373
|
return this.handleCancelPermission(params);
|
|
374
|
+
case "_macro/setPermissionMode":
|
|
375
|
+
return this.handleSetPermissionMode(params);
|
|
345
376
|
case "_macro/resume":
|
|
346
377
|
return this.handleResumeAgent(params);
|
|
347
378
|
case "_macro/getHistory":
|
|
348
379
|
return this.handleGetHistory(params);
|
|
380
|
+
case "_macro/getModels":
|
|
381
|
+
return this.handleGetModels(params);
|
|
349
382
|
default:
|
|
350
383
|
throw new ACPError(`Unknown extension method: ${method}`, "INVALID_EXTENSION");
|
|
351
384
|
}
|
|
@@ -515,40 +548,42 @@ export class MacroAgent {
|
|
|
515
548
|
* Uses native fork if available, otherwise falls back to loadSession.
|
|
516
549
|
*/
|
|
517
550
|
async handleForkAgent(params) {
|
|
518
|
-
const { agentId, name } = params;
|
|
519
|
-
//
|
|
551
|
+
const { agentId, name, prompt, cwd } = params;
|
|
552
|
+
// Validate source agent exists
|
|
520
553
|
const originalAgent = this.agentManager.get(agentId);
|
|
521
554
|
if (!originalAgent) {
|
|
522
555
|
throw new ACPError(`Agent not found: ${agentId}`, "AGENT_NOT_FOUND", {
|
|
523
556
|
agentId,
|
|
524
557
|
});
|
|
525
558
|
}
|
|
526
|
-
// Check
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
throw new ACPError(`Agent has no
|
|
530
|
-
}
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
// Since acp-factory doesn't expose these yet, we create a new agent
|
|
536
|
-
// with the same task description as a simplified fork
|
|
537
|
-
const taskDescription = name
|
|
538
|
-
? `[Fork of ${agentId}] ${name}`
|
|
539
|
-
: `[Fork of ${agentId}] ${originalAgent.task ?? "Forked task"}`;
|
|
540
|
-
const spawned = await this.agentManager.spawn({
|
|
541
|
-
task: taskDescription,
|
|
542
|
-
parent: originalAgent.parent ?? null,
|
|
543
|
-
cwd: this.defaultCwd,
|
|
544
|
-
subscribeParent: false,
|
|
559
|
+
// Check forkable: needs active session or persisted provider_session_id
|
|
560
|
+
if (!this.agentManager.hasActiveSession(agentId) &&
|
|
561
|
+
!originalAgent.provider_session_id) {
|
|
562
|
+
throw new ACPError(`Agent has no session to fork: ${agentId}`, "FORK_NOT_SUPPORTED", { agentId });
|
|
563
|
+
}
|
|
564
|
+
// Fork via AgentManager (handles forkWithFlush + new process + loadSession)
|
|
565
|
+
const forked = await this.agentManager.forkAgent(agentId, {
|
|
566
|
+
name,
|
|
567
|
+
cwd: cwd ?? originalAgent.cwd ?? this.defaultCwd,
|
|
545
568
|
});
|
|
546
|
-
//
|
|
547
|
-
|
|
569
|
+
// Fire-and-forget initial prompt if provided
|
|
570
|
+
if (prompt) {
|
|
571
|
+
(async () => {
|
|
572
|
+
try {
|
|
573
|
+
for await (const _update of this.agentManager.prompt(forked.id, prompt)) {
|
|
574
|
+
// Drain the async iterable
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch (err) {
|
|
578
|
+
console.warn(`[MacroAgent] Failed to send initial prompt to forked agent ${forked.id}:`, err);
|
|
579
|
+
}
|
|
580
|
+
})();
|
|
581
|
+
}
|
|
548
582
|
return {
|
|
549
|
-
newAgentId:
|
|
550
|
-
newSessionId:
|
|
583
|
+
newAgentId: forked.id,
|
|
584
|
+
newSessionId: forked.session_id,
|
|
551
585
|
originalAgentId: agentId,
|
|
586
|
+
providerSessionId: forked.session.id,
|
|
552
587
|
};
|
|
553
588
|
}
|
|
554
589
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -774,6 +809,29 @@ export class MacroAgent {
|
|
|
774
809
|
};
|
|
775
810
|
}
|
|
776
811
|
}
|
|
812
|
+
/**
|
|
813
|
+
* Change the permission mode for a running agent at runtime
|
|
814
|
+
*/
|
|
815
|
+
async handleSetPermissionMode(params) {
|
|
816
|
+
const { agentId, permissionMode } = params;
|
|
817
|
+
// Get the current mode before changing
|
|
818
|
+
const previousMode = this.agentManager.getPermissionMode(agentId);
|
|
819
|
+
// Set the new mode via agent manager
|
|
820
|
+
const success = this.agentManager.setPermissionMode(agentId, permissionMode);
|
|
821
|
+
if (success) {
|
|
822
|
+
console.log(`[MacroAgent] Set permission mode for agent ${agentId} from ${previousMode} to ${permissionMode}`);
|
|
823
|
+
return {
|
|
824
|
+
success: true,
|
|
825
|
+
previousMode: previousMode ?? undefined,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
return {
|
|
830
|
+
success: false,
|
|
831
|
+
error: `No active session found for agent ${agentId}`,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
777
835
|
/**
|
|
778
836
|
* Resume a stopped/failed agent
|
|
779
837
|
*/
|
|
@@ -873,17 +931,20 @@ export class MacroAgent {
|
|
|
873
931
|
}
|
|
874
932
|
case "permission_request": {
|
|
875
933
|
// Handle permission_request specially - ACP SDK doesn't recognize it as a session update
|
|
876
|
-
// We need to call requestPermission on the connection to forward to
|
|
934
|
+
// We need to call requestPermission on the connection to forward to the client
|
|
877
935
|
// Extract permission request data
|
|
878
936
|
const permReq = sessionUpdate;
|
|
879
|
-
//
|
|
880
|
-
|
|
937
|
+
// Look up agent ID using the ACP session ID (from the client connection),
|
|
938
|
+
// NOT permReq.sessionId which is the agent's internal session ID.
|
|
939
|
+
// The session mapper maps ACP session IDs → agent IDs, so using the
|
|
940
|
+
// internal session ID would fail silently and drop the permission request.
|
|
941
|
+
const agentId = this.sessionMapper.getAgentId(acpSessionId);
|
|
881
942
|
if (!agentId) {
|
|
882
|
-
console.warn(`[MacroAgent] No agent found for session ${
|
|
943
|
+
console.warn(`[MacroAgent] No agent found for ACP session ${acpSessionId}, cannot forward permission request`);
|
|
883
944
|
return;
|
|
884
945
|
}
|
|
885
|
-
// Forward
|
|
886
|
-
// This will trigger
|
|
946
|
+
// Forward via requestPermission RPC
|
|
947
|
+
// This will trigger the client's handler which will show the prompt
|
|
887
948
|
try {
|
|
888
949
|
const response = await this.connection.requestPermission({
|
|
889
950
|
sessionId: acpSessionId,
|
|
@@ -1164,6 +1225,41 @@ export class MacroAgent {
|
|
|
1164
1225
|
this.toolInfoCaches.delete(acpSessionId);
|
|
1165
1226
|
}
|
|
1166
1227
|
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Handle _macro/getModels extension — returns the session's available models.
|
|
1230
|
+
* Claude Code populates models asynchronously after session creation
|
|
1231
|
+
* (via _model_state_update notification), so this allows the TUI to poll
|
|
1232
|
+
* for the model list once it's available.
|
|
1233
|
+
*
|
|
1234
|
+
* Returns full model info (modelId + name) since Claude Code uses shorthand
|
|
1235
|
+
* model IDs ("default", "sonnet") that don't match models.dev. The name
|
|
1236
|
+
* field (e.g., "Claude Sonnet 4") enables better model registry matching.
|
|
1237
|
+
*/
|
|
1238
|
+
handleGetModels(params) {
|
|
1239
|
+
const { sessionId } = params;
|
|
1240
|
+
const agentId = this.sessionMapper.getAgentId(sessionId);
|
|
1241
|
+
if (!agentId) {
|
|
1242
|
+
return { currentModelId: null, availableModels: [] };
|
|
1243
|
+
}
|
|
1244
|
+
const session = this.agentManager.getSession(agentId);
|
|
1245
|
+
if (!session) {
|
|
1246
|
+
return { currentModelId: null, availableModels: [] };
|
|
1247
|
+
}
|
|
1248
|
+
// Try clientHandler's model info store first (from _model_state_update notification)
|
|
1249
|
+
const clientHandler = session.clientHandler;
|
|
1250
|
+
const modelInfo = clientHandler?.getSessionModelInfo?.(session.id);
|
|
1251
|
+
if (modelInfo && modelInfo.availableModels.length > 0) {
|
|
1252
|
+
return modelInfo;
|
|
1253
|
+
}
|
|
1254
|
+
// Fall back to Session.models (from initial session response — just IDs)
|
|
1255
|
+
if (session.models && session.models.length > 0) {
|
|
1256
|
+
return {
|
|
1257
|
+
currentModelId: session.models[0],
|
|
1258
|
+
availableModels: session.models.map((id) => ({ modelId: id, name: id })),
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
return { currentModelId: null, availableModels: [] };
|
|
1262
|
+
}
|
|
1167
1263
|
/**
|
|
1168
1264
|
* Handle _macro/getHistory extension — returns conversation turns for a session
|
|
1169
1265
|
*/
|