macro-agent 0.0.17 → 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 +17 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +183 -55
- 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 +23 -0
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
- package/dist/map/adapter/acp-over-map.js +482 -55
- 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 +4 -0
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +302 -30
- 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 +7 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +91 -8
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/types/agents.d.ts +23 -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__/history.test.ts +8 -4
- 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 +230 -62
- 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 +820 -0
- package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
- package/src/map/adapter/__tests__/acp-over-map-history.test.ts +724 -2
- 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 +777 -92
- 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 +373 -38
- 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 +236 -1
- package/src/store/__tests__/instance.test.ts +3 -3
- package/src/store/event-store.ts +109 -8
- package/src/store/types/agents.ts +16 -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
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - MessageRouter for subscription setup
|
|
8
8
|
*/
|
|
9
9
|
import { nanoid } from "nanoid";
|
|
10
|
+
import { uniqueNamesGenerator, adjectives, animals, } from "unique-names-generator";
|
|
10
11
|
import { AgentFactory, } from "acp-factory";
|
|
11
12
|
import { AgentManagerError } from "./types.js";
|
|
12
13
|
import { AGENT_CAPABILITIES } from "../roles/capabilities.js";
|
|
@@ -45,7 +46,9 @@ function getSpawnCapability(childRole) {
|
|
|
45
46
|
// AgentManager Implementation
|
|
46
47
|
// ─────────────────────────────────────────────────────────────────
|
|
47
48
|
export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
48
|
-
const { defaultPermissionMode = "auto-approve", defaultAgentType = "claude-code", defaultCwd = process.cwd(), workspaceManager, roleRegistry = new DefaultRoleRegistry(), healthCheckService, mailService: initialMailService, conversationMap: initialConversationMap, } = config;
|
|
49
|
+
const { defaultPermissionMode = "auto-approve", defaultAgentType = "claude-code", defaultCwd = process.cwd(), workspaceManager, roleRegistry = new DefaultRoleRegistry(), healthCheckService, mailService: initialMailService, conversationMap: initialConversationMap, serverUrl, serverToken, agentTokenManager, taskBackend: configTaskBackend, openTasksSocketPath: initialOpenTasksSocketPath, } = config;
|
|
50
|
+
// Mutable OpenTasks socket path (support late binding via setOpenTasksSocketPath)
|
|
51
|
+
let configOpenTasksSocketPath = initialOpenTasksSocketPath;
|
|
49
52
|
// Mutable mail services (support late binding via setMailServices)
|
|
50
53
|
let mailService = initialMailService;
|
|
51
54
|
let conversationMap = initialConversationMap;
|
|
@@ -57,10 +60,66 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
57
60
|
const agentWorkspaces = new Map();
|
|
58
61
|
// Lifecycle event listeners
|
|
59
62
|
const lifecycleListeners = new Set();
|
|
63
|
+
// Shutdown guard — prevents spawns during close()
|
|
64
|
+
let isShuttingDown = false;
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────
|
|
66
|
+
// MCP Server Config
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Build the macro-agent MCP server config for a Claude Code agent session.
|
|
70
|
+
* Used by spawn(), resume(), and forkAgent() to ensure every agent gets
|
|
71
|
+
* access to the macro-agent coordination tools.
|
|
72
|
+
*/
|
|
73
|
+
function buildMacroAgentMcp(opts) {
|
|
74
|
+
// Common env vars for both thin-client and legacy modes
|
|
75
|
+
const env = [
|
|
76
|
+
{ name: "MACRO_AGENT_ID", value: opts.agentId },
|
|
77
|
+
{ name: "MACRO_PARENT_ID", value: opts.parentId },
|
|
78
|
+
{ name: "MACRO_TASK_ID", value: opts.taskId },
|
|
79
|
+
{ name: "MACRO_AGENT_CWD", value: opts.cwd },
|
|
80
|
+
{ name: "MACRO_PERMISSION_MODE", value: opts.permissionMode },
|
|
81
|
+
{
|
|
82
|
+
name: "MACRO_TASK_BACKEND",
|
|
83
|
+
value: configTaskBackend ?? process.env.MACRO_TASK_BACKEND ?? "",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "OPENTASKS_SOCKET_PATH",
|
|
87
|
+
value: configOpenTasksSocketPath ?? process.env.OPENTASKS_SOCKET_PATH ?? "",
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
if (serverUrl) {
|
|
91
|
+
// Thin-client mode: forward tool calls to main server via MAP WebSocket
|
|
92
|
+
env.push({ name: "MACRO_SERVER_URL", value: serverUrl }, {
|
|
93
|
+
name: "MACRO_AGENT_LINEAGE",
|
|
94
|
+
value: JSON.stringify(opts.lineage ?? []),
|
|
95
|
+
}, { name: "MACRO_SESSION_ID", value: opts.sessionId ?? "" });
|
|
96
|
+
// Auth tokens for thin-client connections
|
|
97
|
+
if (serverToken) {
|
|
98
|
+
env.push({ name: "MACRO_SERVER_TOKEN", value: serverToken });
|
|
99
|
+
}
|
|
100
|
+
if (agentTokenManager) {
|
|
101
|
+
const agentToken = agentTokenManager.createToken(opts.agentId);
|
|
102
|
+
env.push({ name: "MACRO_AGENT_TOKEN", value: agentToken });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Legacy mode: create local service stack with shared SQLite
|
|
107
|
+
env.push({ name: "MACRO_INSTANCE_ID", value: eventStore.instanceId }, { name: "MACRO_BASE_DIR", value: eventStore.baseDir });
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
name: "macro-agent",
|
|
111
|
+
command: "npx",
|
|
112
|
+
args: ["multiagent-mcp"],
|
|
113
|
+
env,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
60
116
|
// ─────────────────────────────────────────────────────────────────
|
|
61
117
|
// Lifecycle
|
|
62
118
|
// ─────────────────────────────────────────────────────────────────
|
|
63
119
|
async function spawn(rawOptions) {
|
|
120
|
+
if (isShuttingDown) {
|
|
121
|
+
throw new AgentManagerError("Cannot spawn agent during shutdown", "SHUTDOWN_IN_PROGRESS");
|
|
122
|
+
}
|
|
64
123
|
// Apply spawn interceptor if set (used by TeamRuntime for team context injection)
|
|
65
124
|
const options = spawnInterceptor
|
|
66
125
|
? await spawnInterceptor(rawOptions)
|
|
@@ -144,6 +203,13 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
144
203
|
cwd,
|
|
145
204
|
},
|
|
146
205
|
});
|
|
206
|
+
// Generate a human-readable default name
|
|
207
|
+
const generatedName = uniqueNamesGenerator({
|
|
208
|
+
dictionaries: [adjectives, animals],
|
|
209
|
+
separator: "-",
|
|
210
|
+
length: 2,
|
|
211
|
+
});
|
|
212
|
+
eventStore.updateAgentMetadata(agentId, { name: generatedName });
|
|
147
213
|
// Persist immediately so MCP server subprocess can read the agent
|
|
148
214
|
await eventStore.persist();
|
|
149
215
|
// Verify the agent is now in the store
|
|
@@ -157,162 +223,177 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
157
223
|
permissionMode,
|
|
158
224
|
env: agentConfig?.env,
|
|
159
225
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
try {
|
|
227
|
+
const macroAgentMcp = buildMacroAgentMcp({
|
|
228
|
+
agentId,
|
|
229
|
+
parentId: parent ?? "",
|
|
230
|
+
taskId,
|
|
231
|
+
cwd,
|
|
232
|
+
permissionMode,
|
|
233
|
+
lineage: parentAgent?.lineage
|
|
234
|
+
? [...parentAgent.lineage, parent]
|
|
235
|
+
: [],
|
|
236
|
+
sessionId,
|
|
237
|
+
});
|
|
238
|
+
// Combine with any user-provided MCP servers
|
|
239
|
+
// Note: Like macroAgentMcp, user MCP servers use stdio (no 'type' field)
|
|
240
|
+
const userMcpServers = agentConfig?.mcpServers?.map((s) => ({
|
|
241
|
+
name: s.name,
|
|
242
|
+
command: s.command,
|
|
243
|
+
args: s.args ?? [],
|
|
244
|
+
env: s.env
|
|
245
|
+
? Object.entries(s.env).map(([name, value]) => ({ name, value }))
|
|
246
|
+
: [],
|
|
247
|
+
})) ?? [];
|
|
248
|
+
// Create session with MCP servers
|
|
249
|
+
// Note: The MCP server subprocess will start here and look for the agent
|
|
250
|
+
// in EventStore. We already persisted the spawn event above.
|
|
251
|
+
//
|
|
252
|
+
// When permissionMode is "interactive", we strip settingSources so that
|
|
253
|
+
// the Claude Code subprocess doesn't read pre-approved tool rules from
|
|
254
|
+
// the user's ~/.claude/settings.local.json. This ensures ALL tool calls
|
|
255
|
+
// go through the canUseTool → requestPermission ACP flow.
|
|
256
|
+
const agentMeta = permissionMode === "interactive"
|
|
257
|
+
? { claudeCode: { options: { settingSources: [] } } }
|
|
258
|
+
: undefined;
|
|
259
|
+
const session = await handle.createSession(cwd, {
|
|
260
|
+
mcpServers: [macroAgentMcp, ...userMcpServers],
|
|
261
|
+
...(agentMeta && { agentMeta }),
|
|
262
|
+
});
|
|
263
|
+
// Emit started status (session is ready)
|
|
264
|
+
// Include the provider's session ID (e.g., Claude Code UUID) so
|
|
265
|
+
// it can be used for handle.loadSession() during resume
|
|
266
|
+
eventStore.emit({
|
|
267
|
+
type: "status",
|
|
268
|
+
source: { agent_id: agentId },
|
|
269
|
+
payload: {
|
|
270
|
+
status_type: "started",
|
|
271
|
+
summary: "Agent session started",
|
|
272
|
+
provider_session_id: session.id,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
// Persist the status event
|
|
276
|
+
await eventStore.persist();
|
|
277
|
+
// Set up default subscriptions via MessageRouter
|
|
278
|
+
messageRouter.setupDefaultSubscriptions({
|
|
279
|
+
agent_id: agentId,
|
|
280
|
+
parent_id: parent ?? undefined,
|
|
281
|
+
task_id: taskId,
|
|
282
|
+
subscribe_parent: subscribeParent,
|
|
283
|
+
additional_topics: topics,
|
|
284
|
+
role: role ?? undefined,
|
|
285
|
+
});
|
|
286
|
+
// ─────────────────────────────────────────────────────────────────
|
|
287
|
+
// Mail: Create task conversation for this agent
|
|
288
|
+
// ─────────────────────────────────────────────────────────────────
|
|
289
|
+
if (mailService && conversationMap) {
|
|
290
|
+
try {
|
|
291
|
+
const parentConversationId = parent
|
|
292
|
+
? (conversationMap.getAgentConversation(parent) ??
|
|
293
|
+
conversationMap.getSessionConversation(parent))
|
|
294
|
+
: undefined;
|
|
295
|
+
const { conversationId: taskConvId } = mailService.createConversation({
|
|
296
|
+
type: "task",
|
|
297
|
+
subject: task?.slice(0, 80),
|
|
298
|
+
createdBy: parent ?? agentId,
|
|
299
|
+
parentConversationId: parentConversationId,
|
|
300
|
+
});
|
|
301
|
+
// Join parent and child as participants
|
|
302
|
+
if (parent) {
|
|
303
|
+
mailService.joinConversation({
|
|
304
|
+
conversationId: taskConvId,
|
|
305
|
+
participantId: parent,
|
|
306
|
+
participantType: "agent",
|
|
307
|
+
role: "initiator",
|
|
308
|
+
agentId: parent,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
232
311
|
mailService.joinConversation({
|
|
233
312
|
conversationId: taskConvId,
|
|
234
|
-
participantId:
|
|
313
|
+
participantId: agentId,
|
|
235
314
|
participantType: "agent",
|
|
236
|
-
role: "
|
|
237
|
-
agentId
|
|
315
|
+
role: "worker",
|
|
316
|
+
agentId,
|
|
238
317
|
});
|
|
318
|
+
conversationMap.setAgentConversation(agentId, taskConvId);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
// Never fail spawn due to mail errors
|
|
322
|
+
console.warn(`[AgentManager] Failed to create task conversation for ${agentId}:`, err);
|
|
239
323
|
}
|
|
240
|
-
mailService.joinConversation({
|
|
241
|
-
conversationId: taskConvId,
|
|
242
|
-
participantId: agentId,
|
|
243
|
-
participantType: "agent",
|
|
244
|
-
role: "worker",
|
|
245
|
-
agentId,
|
|
246
|
-
});
|
|
247
|
-
conversationMap.setAgentConversation(agentId, taskConvId);
|
|
248
|
-
}
|
|
249
|
-
catch (err) {
|
|
250
|
-
// Never fail spawn due to mail errors
|
|
251
|
-
console.warn(`[AgentManager] Failed to create task conversation for ${agentId}:`, err);
|
|
252
324
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
workspaceManager.registerChildWorkspace(parent, agentId, workspace.path);
|
|
325
|
+
// Track active session
|
|
326
|
+
const activeSession = {
|
|
327
|
+
agentId,
|
|
328
|
+
handle,
|
|
329
|
+
session,
|
|
330
|
+
createdAt: Date.now(),
|
|
331
|
+
isPrompting: false,
|
|
332
|
+
};
|
|
333
|
+
activeSessions.set(agentId, activeSession);
|
|
334
|
+
// Get the agent from materialized view
|
|
335
|
+
const agent = eventStore.getAgent(agentId);
|
|
336
|
+
// ─────────────────────────────────────────────────────────────────
|
|
337
|
+
// Workspace Creation (Phase 2)
|
|
338
|
+
// ─────────────────────────────────────────────────────────────────
|
|
339
|
+
let workspace;
|
|
340
|
+
let resolvedStreamId = streamId;
|
|
341
|
+
if (workspaceManager && role) {
|
|
342
|
+
try {
|
|
343
|
+
workspace = await createWorkspaceForRole(workspaceManager, agentId, role, {
|
|
344
|
+
streamId,
|
|
345
|
+
streamConfig,
|
|
346
|
+
dataplaneTaskId,
|
|
347
|
+
cwd,
|
|
348
|
+
});
|
|
349
|
+
if (workspace) {
|
|
350
|
+
agentWorkspaces.set(agentId, workspace);
|
|
351
|
+
resolvedStreamId = workspace.streamId;
|
|
352
|
+
// Register with parent coordinator if applicable
|
|
353
|
+
if (parent && (role === "worker" || role === "integrator")) {
|
|
354
|
+
const parentWorkspace = agentWorkspaces.get(parent);
|
|
355
|
+
if (parentWorkspace?.role === "coordinator") {
|
|
356
|
+
workspaceManager.registerChildWorkspace(parent, agentId, workspace.path);
|
|
357
|
+
}
|
|
287
358
|
}
|
|
288
359
|
}
|
|
289
360
|
}
|
|
361
|
+
catch (wsError) {
|
|
362
|
+
console.error(`[AgentManager] Failed to create workspace for ${agentId}: ${wsError}`);
|
|
363
|
+
// Continue without workspace - don't fail the spawn
|
|
364
|
+
}
|
|
290
365
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
366
|
+
// Notify lifecycle listeners
|
|
367
|
+
notifyLifecycle({ type: "spawned", agent });
|
|
368
|
+
notifyLifecycle({ type: "started", agent });
|
|
369
|
+
// Start health monitoring for coordinators
|
|
370
|
+
if (healthCheckService && role === "coordinator") {
|
|
371
|
+
healthCheckService.startForCoordinator(agentId);
|
|
294
372
|
}
|
|
373
|
+
return {
|
|
374
|
+
id: agentId,
|
|
375
|
+
session_id: sessionId, // Macro-agent's own session ID for ACP protocol mapping
|
|
376
|
+
agent,
|
|
377
|
+
session,
|
|
378
|
+
workspace,
|
|
379
|
+
streamId: resolvedStreamId,
|
|
380
|
+
};
|
|
295
381
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
382
|
+
catch (handleError) {
|
|
383
|
+
// Close the spawned process to prevent orphaning
|
|
384
|
+
try {
|
|
385
|
+
await handle.close();
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// Ignore errors during cleanup
|
|
389
|
+
}
|
|
390
|
+
throw handleError;
|
|
302
391
|
}
|
|
303
|
-
return {
|
|
304
|
-
id: agentId,
|
|
305
|
-
session_id: sessionId, // Macro-agent's own session ID for ACP protocol mapping
|
|
306
|
-
agent,
|
|
307
|
-
session,
|
|
308
|
-
workspace,
|
|
309
|
-
streamId: resolvedStreamId,
|
|
310
|
-
};
|
|
311
392
|
}
|
|
312
393
|
catch (error) {
|
|
313
394
|
// Clean up the spawn event we already emitted
|
|
314
395
|
eventStore.emit({
|
|
315
|
-
type: "
|
|
396
|
+
type: "stop",
|
|
316
397
|
source: { agent_id: agentId },
|
|
317
398
|
payload: {
|
|
318
399
|
reason: "failed",
|
|
@@ -355,6 +436,10 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
355
436
|
// Continue with termination even if workspace cleanup fails
|
|
356
437
|
}
|
|
357
438
|
}
|
|
439
|
+
// Revoke agent authentication token
|
|
440
|
+
if (agentTokenManager) {
|
|
441
|
+
agentTokenManager.revokeToken(agentId);
|
|
442
|
+
}
|
|
358
443
|
// ─────────────────────────────────────────────────────────────────
|
|
359
444
|
// Mail: Close task conversation on terminate
|
|
360
445
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -383,9 +468,9 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
383
468
|
console.warn(`[AgentManager] Failed to close conversation for ${agentId}:`, err);
|
|
384
469
|
}
|
|
385
470
|
}
|
|
386
|
-
// Emit
|
|
471
|
+
// Emit stop event
|
|
387
472
|
eventStore.emit({
|
|
388
|
-
type: "
|
|
473
|
+
type: "stop",
|
|
389
474
|
source: { agent_id: agentId },
|
|
390
475
|
payload: {
|
|
391
476
|
agent_id: agentId,
|
|
@@ -442,7 +527,10 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
442
527
|
}
|
|
443
528
|
}
|
|
444
529
|
}
|
|
445
|
-
async function resume(agentId) {
|
|
530
|
+
async function resume(agentId, overridePermissionMode) {
|
|
531
|
+
if (isShuttingDown) {
|
|
532
|
+
throw new AgentManagerError("Cannot resume agent during shutdown", "SHUTDOWN_IN_PROGRESS", agentId);
|
|
533
|
+
}
|
|
446
534
|
const agent = eventStore.getAgent(agentId);
|
|
447
535
|
if (!agent) {
|
|
448
536
|
throw new AgentManagerError(`Agent not found: ${agentId}`, "AGENT_NOT_FOUND", agentId);
|
|
@@ -451,57 +539,216 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
451
539
|
if (activeSessions.has(agentId)) {
|
|
452
540
|
throw new AgentManagerError(`Agent already has active session: ${agentId}`, "ALREADY_RUNNING", agentId);
|
|
453
541
|
}
|
|
542
|
+
const permissionMode = overridePermissionMode ?? defaultPermissionMode;
|
|
454
543
|
// Spawn new process
|
|
455
544
|
const handle = await AgentFactory.spawn(defaultAgentType, {
|
|
456
|
-
permissionMode
|
|
545
|
+
permissionMode,
|
|
457
546
|
});
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
//
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
547
|
+
try {
|
|
548
|
+
const agentCwd = agent.cwd ?? defaultCwd;
|
|
549
|
+
let session;
|
|
550
|
+
// When interactive mode, strip settings to prevent auto-approval
|
|
551
|
+
const resumeAgentMeta = permissionMode === "interactive"
|
|
552
|
+
? { claudeCode: { options: { settingSources: [] } } }
|
|
553
|
+
: undefined;
|
|
554
|
+
const macroAgentMcp = buildMacroAgentMcp({
|
|
555
|
+
agentId,
|
|
556
|
+
parentId: agent.parent ?? "",
|
|
557
|
+
taskId: agent.task_id ?? "",
|
|
558
|
+
cwd: agentCwd,
|
|
559
|
+
permissionMode,
|
|
560
|
+
lineage: agent.lineage ?? [],
|
|
561
|
+
sessionId: agent.session_id ?? "",
|
|
562
|
+
});
|
|
563
|
+
const mcpServers = [macroAgentMcp];
|
|
564
|
+
if (agent.provider_session_id) {
|
|
565
|
+
// Load existing session using the provider's session ID (e.g., Claude Code UUID)
|
|
566
|
+
// Note: loadSession's TS type for mcpServers is { name, uri }[] but
|
|
567
|
+
// the underlying ACP protocol accepts full McpServerStdio. The JS
|
|
568
|
+
// implementation passes mcpServers through to the connection unchanged.
|
|
569
|
+
session = await handle.loadSession(agent.provider_session_id, agentCwd, mcpServers, resumeAgentMeta ? { agentMeta: resumeAgentMeta } : undefined);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
// No provider session ID available (agent predates this feature or wasn't persisted).
|
|
573
|
+
// Create a new session instead of loading with the macro-agent session_id
|
|
574
|
+
// which is not a valid provider session ID (e.g., Claude Code expects UUIDs).
|
|
575
|
+
session = await handle.createSession(agentCwd, {
|
|
576
|
+
mcpServers,
|
|
577
|
+
...(resumeAgentMeta && { agentMeta: resumeAgentMeta }),
|
|
578
|
+
});
|
|
579
|
+
// Store the provider session ID for future resumes
|
|
580
|
+
eventStore.emit({
|
|
581
|
+
type: "status",
|
|
582
|
+
source: { agent_id: agentId },
|
|
583
|
+
payload: {
|
|
584
|
+
status_type: "started",
|
|
585
|
+
summary: "Agent session created (no provider session to resume)",
|
|
586
|
+
provider_session_id: session.id,
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
// Track active session
|
|
591
|
+
const activeSession = {
|
|
592
|
+
agentId,
|
|
593
|
+
handle,
|
|
594
|
+
session,
|
|
595
|
+
createdAt: Date.now(),
|
|
596
|
+
isPrompting: false,
|
|
597
|
+
};
|
|
598
|
+
activeSessions.set(agentId, activeSession);
|
|
599
|
+
// Emit status event for resume
|
|
470
600
|
eventStore.emit({
|
|
471
601
|
type: "status",
|
|
472
602
|
source: { agent_id: agentId },
|
|
473
603
|
payload: {
|
|
474
604
|
status_type: "started",
|
|
475
|
-
summary: "Agent session
|
|
605
|
+
summary: "Agent session resumed",
|
|
476
606
|
provider_session_id: session.id,
|
|
477
607
|
},
|
|
478
608
|
});
|
|
609
|
+
return {
|
|
610
|
+
id: agentId,
|
|
611
|
+
session_id: agent.session_id, // Macro-agent's own session ID
|
|
612
|
+
agent: eventStore.getAgent(agentId),
|
|
613
|
+
session,
|
|
614
|
+
};
|
|
479
615
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
616
|
+
catch (handleError) {
|
|
617
|
+
// Close the spawned process to prevent orphaning
|
|
618
|
+
try {
|
|
619
|
+
await handle.close();
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
// Ignore errors during cleanup
|
|
623
|
+
}
|
|
624
|
+
throw handleError;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// ─────────────────────────────────────────────────────────────────
|
|
628
|
+
// Fork
|
|
629
|
+
// ─────────────────────────────────────────────────────────────────
|
|
630
|
+
async function forkAgent(sourceAgentId, options) {
|
|
631
|
+
if (isShuttingDown) {
|
|
632
|
+
throw new AgentManagerError("Cannot fork agent during shutdown", "SHUTDOWN_IN_PROGRESS", sourceAgentId);
|
|
633
|
+
}
|
|
634
|
+
const sourceAgent = eventStore.getAgent(sourceAgentId);
|
|
635
|
+
if (!sourceAgent) {
|
|
636
|
+
throw new AgentManagerError(`Agent not found: ${sourceAgentId}`, "AGENT_NOT_FOUND", sourceAgentId);
|
|
637
|
+
}
|
|
638
|
+
// Need either an active session or a persisted provider_session_id
|
|
639
|
+
const activeSession = activeSessions.get(sourceAgentId);
|
|
640
|
+
if (!activeSession && !sourceAgent.provider_session_id) {
|
|
641
|
+
throw new AgentManagerError(`Agent has no session to fork: ${sourceAgentId}`, "FORK_NOT_SUPPORTED", sourceAgentId);
|
|
642
|
+
}
|
|
643
|
+
// Generate new IDs
|
|
644
|
+
const agentId = `agent_${nanoid(12)}`;
|
|
645
|
+
const taskId = `task_${nanoid(12)}`;
|
|
646
|
+
const sessionId = `session_${nanoid(12)}`;
|
|
647
|
+
const cwd = options?.cwd ?? sourceAgent.cwd ?? defaultCwd;
|
|
648
|
+
// Emit spawn event with fork metadata
|
|
490
649
|
eventStore.emit({
|
|
491
|
-
type: "
|
|
492
|
-
source: { agent_id:
|
|
650
|
+
type: "spawn",
|
|
651
|
+
source: { agent_id: sourceAgentId },
|
|
493
652
|
payload: {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
653
|
+
agent_id: agentId,
|
|
654
|
+
session_id: sessionId,
|
|
655
|
+
task: options?.name ?? `[Fork of ${sourceAgentId}]`,
|
|
656
|
+
task_id: taskId,
|
|
657
|
+
parent: sourceAgent.parent ?? null,
|
|
658
|
+
role: sourceAgent.role ?? undefined,
|
|
659
|
+
config: {},
|
|
660
|
+
cwd,
|
|
661
|
+
metadata: { fork_of: sourceAgentId },
|
|
497
662
|
},
|
|
498
663
|
});
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
};
|
|
664
|
+
// Generate a human-readable name
|
|
665
|
+
const generatedName = uniqueNamesGenerator({
|
|
666
|
+
dictionaries: [adjectives, animals],
|
|
667
|
+
separator: "-",
|
|
668
|
+
length: 2,
|
|
669
|
+
});
|
|
670
|
+
eventStore.updateAgentMetadata(agentId, { name: generatedName });
|
|
671
|
+
await eventStore.persist();
|
|
672
|
+
// Get the provider session ID to fork from
|
|
673
|
+
let forkedProviderSessionId;
|
|
674
|
+
if (activeSession) {
|
|
675
|
+
// Active session: fork with flush to ensure data is persisted
|
|
676
|
+
const forkedSession = await activeSession.session.forkWithFlush();
|
|
677
|
+
forkedProviderSessionId = forkedSession.id;
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
// Stopped agent: use the persisted provider session ID directly
|
|
681
|
+
forkedProviderSessionId = sourceAgent.provider_session_id;
|
|
682
|
+
}
|
|
683
|
+
// Spawn a new process
|
|
684
|
+
const handle = await AgentFactory.spawn(defaultAgentType, {
|
|
685
|
+
permissionMode: defaultPermissionMode,
|
|
686
|
+
});
|
|
687
|
+
try {
|
|
688
|
+
const macroAgentMcp = buildMacroAgentMcp({
|
|
689
|
+
agentId,
|
|
690
|
+
parentId: sourceAgent.parent ?? "",
|
|
691
|
+
taskId,
|
|
692
|
+
cwd,
|
|
693
|
+
permissionMode: defaultPermissionMode,
|
|
694
|
+
lineage: sourceAgent.lineage ?? [],
|
|
695
|
+
sessionId,
|
|
696
|
+
});
|
|
697
|
+
// Load the forked session on the new process with correct MCP config.
|
|
698
|
+
// Note: loadSession's TS type for mcpServers is { name, uri }[] but
|
|
699
|
+
// the underlying ACP protocol accepts full McpServerStdio. The JS
|
|
700
|
+
// implementation passes mcpServers through to the connection unchanged.
|
|
701
|
+
const session = await handle.loadSession(forkedProviderSessionId, cwd, [
|
|
702
|
+
macroAgentMcp,
|
|
703
|
+
]);
|
|
704
|
+
// Emit started status with provider session ID
|
|
705
|
+
eventStore.emit({
|
|
706
|
+
type: "status",
|
|
707
|
+
source: { agent_id: agentId },
|
|
708
|
+
payload: {
|
|
709
|
+
status_type: "started",
|
|
710
|
+
summary: "Agent session started (forked)",
|
|
711
|
+
provider_session_id: session.id,
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
await eventStore.persist();
|
|
715
|
+
// Set up message router subscriptions
|
|
716
|
+
messageRouter.setupDefaultSubscriptions({
|
|
717
|
+
agent_id: agentId,
|
|
718
|
+
parent_id: sourceAgent.parent ?? undefined,
|
|
719
|
+
task_id: taskId,
|
|
720
|
+
subscribe_parent: false,
|
|
721
|
+
additional_topics: [],
|
|
722
|
+
role: sourceAgent.role ?? undefined,
|
|
723
|
+
});
|
|
724
|
+
// Track active session
|
|
725
|
+
const newActiveSession = {
|
|
726
|
+
agentId,
|
|
727
|
+
handle,
|
|
728
|
+
session,
|
|
729
|
+
createdAt: Date.now(),
|
|
730
|
+
isPrompting: false,
|
|
731
|
+
};
|
|
732
|
+
activeSessions.set(agentId, newActiveSession);
|
|
733
|
+
const agent = eventStore.getAgent(agentId);
|
|
734
|
+
notifyLifecycle({ type: "spawned", agent });
|
|
735
|
+
notifyLifecycle({ type: "started", agent });
|
|
736
|
+
return {
|
|
737
|
+
id: agentId,
|
|
738
|
+
session_id: sessionId,
|
|
739
|
+
agent,
|
|
740
|
+
session,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
catch (handleError) {
|
|
744
|
+
try {
|
|
745
|
+
await handle.close();
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// Ignore errors during cleanup
|
|
749
|
+
}
|
|
750
|
+
throw handleError;
|
|
751
|
+
}
|
|
505
752
|
}
|
|
506
753
|
// ─────────────────────────────────────────────────────────────────
|
|
507
754
|
// Queries
|
|
@@ -654,11 +901,12 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
|
|
|
654
901
|
const agentCompletedStatus = statusEvents.find((e) => e.source?.agent_id === agentId &&
|
|
655
902
|
(e.payload?.status_type === "completed" ||
|
|
656
903
|
e.payload?.status_type === "failed" ||
|
|
657
|
-
e.payload?.details?.signal ===
|
|
904
|
+
e.payload?.details?.signal ===
|
|
905
|
+
"WORKER_DONE"));
|
|
658
906
|
if (agentCompletedStatus) {
|
|
659
907
|
return {
|
|
660
908
|
called: true,
|
|
661
|
-
status: agentCompletedStatus.payload?.status_type
|
|
909
|
+
status: agentCompletedStatus.payload?.status_type,
|
|
662
910
|
};
|
|
663
911
|
}
|
|
664
912
|
return { called: false };
|
|
@@ -789,6 +1037,29 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
789
1037
|
return false;
|
|
790
1038
|
}
|
|
791
1039
|
}
|
|
1040
|
+
function setPermissionMode(agentId, mode) {
|
|
1041
|
+
const activeSession = activeSessions.get(agentId);
|
|
1042
|
+
if (!activeSession) {
|
|
1043
|
+
console.warn(`[AgentManager] Cannot set permission mode: no active session for agent ${agentId}`);
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
try {
|
|
1047
|
+
activeSession.handle.setPermissionMode(mode);
|
|
1048
|
+
console.log(`[AgentManager] Set permission mode for agent ${agentId} to ${mode}`);
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
catch (err) {
|
|
1052
|
+
console.error(`[AgentManager] Error setting permission mode for agent ${agentId}:`, err);
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function getPermissionMode(agentId) {
|
|
1057
|
+
const activeSession = activeSessions.get(agentId);
|
|
1058
|
+
if (!activeSession) {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
return activeSession.handle.getPermissionMode();
|
|
1062
|
+
}
|
|
792
1063
|
// ─────────────────────────────────────────────────────────────────
|
|
793
1064
|
// Lifecycle Callbacks
|
|
794
1065
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -807,6 +1078,12 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
807
1078
|
}
|
|
808
1079
|
}
|
|
809
1080
|
// ─────────────────────────────────────────────────────────────────
|
|
1081
|
+
// OpenTasks Socket Path (Late Binding)
|
|
1082
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1083
|
+
function setOpenTasksSocketPath(socketPath) {
|
|
1084
|
+
configOpenTasksSocketPath = socketPath;
|
|
1085
|
+
}
|
|
1086
|
+
// ─────────────────────────────────────────────────────────────────
|
|
810
1087
|
// Mail Services (Late Binding)
|
|
811
1088
|
// ─────────────────────────────────────────────────────────────────
|
|
812
1089
|
function setMailServices(ms, cm) {
|
|
@@ -817,6 +1094,8 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
817
1094
|
// Cleanup
|
|
818
1095
|
// ─────────────────────────────────────────────────────────────────
|
|
819
1096
|
async function close() {
|
|
1097
|
+
// Prevent new spawns/resumes from racing with cleanup
|
|
1098
|
+
isShuttingDown = true;
|
|
820
1099
|
// Stop all health checks
|
|
821
1100
|
if (healthCheckService) {
|
|
822
1101
|
healthCheckService.stopAll();
|
|
@@ -876,9 +1155,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
876
1155
|
}
|
|
877
1156
|
const resumeContext = contextLines.join("\n");
|
|
878
1157
|
// Spawn a continuation agent with same role, task, and context
|
|
879
|
-
const taskDescription = options?.task ??
|
|
880
|
-
agent.task ??
|
|
881
|
-
`Continue work from ${agentId}`;
|
|
1158
|
+
const taskDescription = options?.task ?? agent.task ?? `Continue work from ${agentId}`;
|
|
882
1159
|
const newAgent = await spawn({
|
|
883
1160
|
task: taskDescription,
|
|
884
1161
|
role: agent.role,
|
|
@@ -903,6 +1180,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
903
1180
|
terminate,
|
|
904
1181
|
resume,
|
|
905
1182
|
continueAgent,
|
|
1183
|
+
forkAgent,
|
|
906
1184
|
get,
|
|
907
1185
|
list,
|
|
908
1186
|
getChildren,
|
|
@@ -918,9 +1196,12 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
918
1196
|
isProcessRunning,
|
|
919
1197
|
respondToPermission,
|
|
920
1198
|
cancelPermission,
|
|
1199
|
+
setPermissionMode,
|
|
1200
|
+
getPermissionMode,
|
|
921
1201
|
onLifecycleEvent,
|
|
922
1202
|
setSpawnInterceptor,
|
|
923
1203
|
getRoleRegistry,
|
|
1204
|
+
setOpenTasksSocketPath,
|
|
924
1205
|
setMailServices,
|
|
925
1206
|
close,
|
|
926
1207
|
};
|