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
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { nanoid } from "nanoid";
|
|
11
|
+
import {
|
|
12
|
+
uniqueNamesGenerator,
|
|
13
|
+
adjectives,
|
|
14
|
+
animals,
|
|
15
|
+
} from "unique-names-generator";
|
|
11
16
|
import {
|
|
12
17
|
AgentFactory,
|
|
13
18
|
type Session,
|
|
@@ -50,6 +55,7 @@ import {
|
|
|
50
55
|
type CascadeAgentManager,
|
|
51
56
|
} from "../lifecycle/cascade.js";
|
|
52
57
|
import type { HealthCheckService } from "../monitor/health-check-service.js";
|
|
58
|
+
import { AgentTokenManager } from "../auth/token.js";
|
|
53
59
|
|
|
54
60
|
// ─────────────────────────────────────────────────────────────────
|
|
55
61
|
// Helper Functions
|
|
@@ -103,8 +109,12 @@ export interface AgentManager {
|
|
|
103
109
|
|
|
104
110
|
/**
|
|
105
111
|
* Resume a stopped agent by loading its existing session.
|
|
112
|
+
* @param permissionMode - Optional permission mode override (defaults to the agent manager's default)
|
|
106
113
|
*/
|
|
107
|
-
resume(
|
|
114
|
+
resume(
|
|
115
|
+
agentId: AgentId,
|
|
116
|
+
permissionMode?: PermissionMode,
|
|
117
|
+
): Promise<SpawnedAgent>;
|
|
108
118
|
|
|
109
119
|
/**
|
|
110
120
|
* Continue a terminated agent by spawning a new agent with the same
|
|
@@ -117,7 +127,20 @@ export interface AgentManager {
|
|
|
117
127
|
*/
|
|
118
128
|
continueAgent(
|
|
119
129
|
agentId: AgentId,
|
|
120
|
-
options?: ContinueAgentOptions
|
|
130
|
+
options?: ContinueAgentOptions,
|
|
131
|
+
): Promise<SpawnedAgent>;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Fork an agent's session, creating a new agent with the same
|
|
135
|
+
* conversation history. Uses forkWithFlush for active sessions
|
|
136
|
+
* or loadSession for stopped agents with persisted sessions.
|
|
137
|
+
*
|
|
138
|
+
* @param sourceAgentId - ID of the agent to fork from
|
|
139
|
+
* @param options - Fork options (name, prompt, cwd)
|
|
140
|
+
*/
|
|
141
|
+
forkAgent(
|
|
142
|
+
sourceAgentId: AgentId,
|
|
143
|
+
options?: { name?: string; prompt?: string; cwd?: string },
|
|
121
144
|
): Promise<SpawnedAgent>;
|
|
122
145
|
|
|
123
146
|
// ── Queries ────────────────────────────────────────────────────
|
|
@@ -143,7 +166,7 @@ export interface AgentManager {
|
|
|
143
166
|
*/
|
|
144
167
|
getHierarchy(
|
|
145
168
|
agentId: AgentId,
|
|
146
|
-
options?: HierarchyOptions
|
|
169
|
+
options?: HierarchyOptions,
|
|
147
170
|
): AgentHierarchy | null;
|
|
148
171
|
|
|
149
172
|
// ── Head Manager ───────────────────────────────────────────────
|
|
@@ -166,7 +189,7 @@ export interface AgentManager {
|
|
|
166
189
|
*/
|
|
167
190
|
prompt(
|
|
168
191
|
agentId: AgentId,
|
|
169
|
-
message: string
|
|
192
|
+
message: string,
|
|
170
193
|
): AsyncIterable<ExtendedSessionUpdate>;
|
|
171
194
|
|
|
172
195
|
/**
|
|
@@ -186,7 +209,7 @@ export interface AgentManager {
|
|
|
186
209
|
maxFollowUps?: number;
|
|
187
210
|
/** Callback for each update during prompting */
|
|
188
211
|
onUpdate?: (update: ExtendedSessionUpdate) => void;
|
|
189
|
-
}
|
|
212
|
+
},
|
|
190
213
|
): Promise<{
|
|
191
214
|
doneCalled: boolean;
|
|
192
215
|
doneStatus?: string;
|
|
@@ -235,7 +258,7 @@ export interface AgentManager {
|
|
|
235
258
|
respondToPermission(
|
|
236
259
|
agentId: AgentId,
|
|
237
260
|
requestId: string,
|
|
238
|
-
optionId: string
|
|
261
|
+
optionId: string,
|
|
239
262
|
): boolean;
|
|
240
263
|
|
|
241
264
|
/**
|
|
@@ -247,6 +270,24 @@ export interface AgentManager {
|
|
|
247
270
|
*/
|
|
248
271
|
cancelPermission(agentId: AgentId, requestId: string): boolean;
|
|
249
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Change the permission mode for a running agent at runtime.
|
|
275
|
+
* Takes effect on the next permission request.
|
|
276
|
+
*
|
|
277
|
+
* @param agentId - Agent ID to change permission mode for
|
|
278
|
+
* @param mode - New permission mode
|
|
279
|
+
* @returns true if the mode was changed successfully
|
|
280
|
+
*/
|
|
281
|
+
setPermissionMode(agentId: AgentId, mode: PermissionMode): boolean;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get the current permission mode for a running agent.
|
|
285
|
+
*
|
|
286
|
+
* @param agentId - Agent ID to query
|
|
287
|
+
* @returns The current permission mode, or null if no active session
|
|
288
|
+
*/
|
|
289
|
+
getPermissionMode(agentId: AgentId): PermissionMode | null;
|
|
290
|
+
|
|
250
291
|
// ── Lifecycle Callbacks ────────────────────────────────────────
|
|
251
292
|
|
|
252
293
|
/**
|
|
@@ -260,15 +301,22 @@ export interface AgentManager {
|
|
|
260
301
|
* Set a spawn interceptor that transforms SpawnAgentOptions before spawning.
|
|
261
302
|
* Used by TeamRuntime to inject team topics, prompts, MCP servers, and env vars.
|
|
262
303
|
*/
|
|
263
|
-
setSpawnInterceptor(
|
|
264
|
-
interceptor: SpawnInterceptor | null
|
|
265
|
-
): void;
|
|
304
|
+
setSpawnInterceptor(interceptor: SpawnInterceptor | null): void;
|
|
266
305
|
|
|
267
306
|
/**
|
|
268
307
|
* Get the RoleRegistry used by this AgentManager.
|
|
269
308
|
*/
|
|
270
309
|
getRoleRegistry(): RoleRegistry;
|
|
271
310
|
|
|
311
|
+
// ── OpenTasks Socket Path (Late Binding) ─────────────────────
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Set the runtime OpenTasks socket path for propagation to child agents.
|
|
315
|
+
* Used when createTaskBackend() discovers the daemon socket path after
|
|
316
|
+
* AgentManager is already created.
|
|
317
|
+
*/
|
|
318
|
+
setOpenTasksSocketPath(socketPath: string): void;
|
|
319
|
+
|
|
272
320
|
// ── Mail Services (Late Binding) ─────────────────────────────
|
|
273
321
|
|
|
274
322
|
/**
|
|
@@ -277,7 +325,7 @@ export interface AgentManager {
|
|
|
277
325
|
*/
|
|
278
326
|
setMailServices(
|
|
279
327
|
mailService: import("../mail/mail-service.js").MailService,
|
|
280
|
-
conversationMap: import("../mail/conversation-map.js").ConversationMap
|
|
328
|
+
conversationMap: import("../mail/conversation-map.js").ConversationMap,
|
|
281
329
|
): void;
|
|
282
330
|
|
|
283
331
|
// ── Cleanup ────────────────────────────────────────────────────
|
|
@@ -335,6 +383,38 @@ export interface AgentManagerConfig {
|
|
|
335
383
|
* Required when mailService is provided.
|
|
336
384
|
*/
|
|
337
385
|
conversationMap?: import("../mail/conversation-map.js").ConversationMap;
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Optional server URL for MCP thin-client mode.
|
|
389
|
+
* When set, spawned agents use ephemeral MAP WebSocket calls instead
|
|
390
|
+
* of creating local service stacks.
|
|
391
|
+
*/
|
|
392
|
+
serverUrl?: string;
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Server authentication token for MCP thin-client connections.
|
|
396
|
+
* Passed to spawned agents as MACRO_SERVER_TOKEN env var.
|
|
397
|
+
*/
|
|
398
|
+
serverToken?: string;
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Per-agent token manager for MCP bridge authentication.
|
|
402
|
+
* When provided, generates a unique token per agent at spawn time
|
|
403
|
+
* and revokes it on terminate.
|
|
404
|
+
*/
|
|
405
|
+
agentTokenManager?: AgentTokenManager;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Task backend type to propagate to child MCP subprocesses.
|
|
409
|
+
* Sourced from merged config. Falls back to MACRO_TASK_BACKEND env var.
|
|
410
|
+
*/
|
|
411
|
+
taskBackend?: string;
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* OpenTasks socket path to propagate to child MCP subprocesses.
|
|
415
|
+
* Sourced from merged config. Falls back to OPENTASKS_SOCKET_PATH env var.
|
|
416
|
+
*/
|
|
417
|
+
openTasksSocketPath?: string;
|
|
338
418
|
}
|
|
339
419
|
|
|
340
420
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -346,7 +426,7 @@ export interface AgentManagerConfig {
|
|
|
346
426
|
* Used by TeamRuntime to inject team-specific configuration.
|
|
347
427
|
*/
|
|
348
428
|
export type SpawnInterceptor = (
|
|
349
|
-
options: SpawnAgentOptions
|
|
429
|
+
options: SpawnAgentOptions,
|
|
350
430
|
) => SpawnAgentOptions | Promise<SpawnAgentOptions>;
|
|
351
431
|
|
|
352
432
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -356,7 +436,7 @@ export type SpawnInterceptor = (
|
|
|
356
436
|
export function createAgentManager(
|
|
357
437
|
eventStore: EventStore,
|
|
358
438
|
messageRouter: MessageRouter,
|
|
359
|
-
config: AgentManagerConfig = {}
|
|
439
|
+
config: AgentManagerConfig = {},
|
|
360
440
|
): AgentManager {
|
|
361
441
|
const {
|
|
362
442
|
defaultPermissionMode = "auto-approve",
|
|
@@ -367,8 +447,16 @@ export function createAgentManager(
|
|
|
367
447
|
healthCheckService,
|
|
368
448
|
mailService: initialMailService,
|
|
369
449
|
conversationMap: initialConversationMap,
|
|
450
|
+
serverUrl,
|
|
451
|
+
serverToken,
|
|
452
|
+
agentTokenManager,
|
|
453
|
+
taskBackend: configTaskBackend,
|
|
454
|
+
openTasksSocketPath: initialOpenTasksSocketPath,
|
|
370
455
|
} = config;
|
|
371
456
|
|
|
457
|
+
// Mutable OpenTasks socket path (support late binding via setOpenTasksSocketPath)
|
|
458
|
+
let configOpenTasksSocketPath = initialOpenTasksSocketPath;
|
|
459
|
+
|
|
372
460
|
// Mutable mail services (support late binding via setMailServices)
|
|
373
461
|
let mailService = initialMailService;
|
|
374
462
|
let conversationMap = initialConversationMap;
|
|
@@ -385,11 +473,91 @@ export function createAgentManager(
|
|
|
385
473
|
// Lifecycle event listeners
|
|
386
474
|
const lifecycleListeners = new Set<AgentLifecycleCallback>();
|
|
387
475
|
|
|
476
|
+
// Shutdown guard — prevents spawns during close()
|
|
477
|
+
let isShuttingDown = false;
|
|
478
|
+
|
|
479
|
+
// ─────────────────────────────────────────────────────────────────
|
|
480
|
+
// MCP Server Config
|
|
481
|
+
// ─────────────────────────────────────────────────────────────────
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Build the macro-agent MCP server config for a Claude Code agent session.
|
|
485
|
+
* Used by spawn(), resume(), and forkAgent() to ensure every agent gets
|
|
486
|
+
* access to the macro-agent coordination tools.
|
|
487
|
+
*/
|
|
488
|
+
function buildMacroAgentMcp(opts: {
|
|
489
|
+
agentId: string;
|
|
490
|
+
parentId: string;
|
|
491
|
+
taskId: string;
|
|
492
|
+
cwd: string;
|
|
493
|
+
permissionMode: string;
|
|
494
|
+
lineage?: string[];
|
|
495
|
+
sessionId?: string;
|
|
496
|
+
}) {
|
|
497
|
+
// Common env vars for both thin-client and legacy modes
|
|
498
|
+
const env = [
|
|
499
|
+
{ name: "MACRO_AGENT_ID", value: opts.agentId },
|
|
500
|
+
{ name: "MACRO_PARENT_ID", value: opts.parentId },
|
|
501
|
+
{ name: "MACRO_TASK_ID", value: opts.taskId },
|
|
502
|
+
{ name: "MACRO_AGENT_CWD", value: opts.cwd },
|
|
503
|
+
{ name: "MACRO_PERMISSION_MODE", value: opts.permissionMode },
|
|
504
|
+
{
|
|
505
|
+
name: "MACRO_TASK_BACKEND",
|
|
506
|
+
value: configTaskBackend ?? process.env.MACRO_TASK_BACKEND ?? "",
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: "OPENTASKS_SOCKET_PATH",
|
|
510
|
+
value: configOpenTasksSocketPath ?? process.env.OPENTASKS_SOCKET_PATH ?? "",
|
|
511
|
+
},
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
if (serverUrl) {
|
|
515
|
+
// Thin-client mode: forward tool calls to main server via MAP WebSocket
|
|
516
|
+
env.push(
|
|
517
|
+
{ name: "MACRO_SERVER_URL", value: serverUrl },
|
|
518
|
+
{
|
|
519
|
+
name: "MACRO_AGENT_LINEAGE",
|
|
520
|
+
value: JSON.stringify(opts.lineage ?? []),
|
|
521
|
+
},
|
|
522
|
+
{ name: "MACRO_SESSION_ID", value: opts.sessionId ?? "" },
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
// Auth tokens for thin-client connections
|
|
526
|
+
if (serverToken) {
|
|
527
|
+
env.push({ name: "MACRO_SERVER_TOKEN", value: serverToken });
|
|
528
|
+
}
|
|
529
|
+
if (agentTokenManager) {
|
|
530
|
+
const agentToken = agentTokenManager.createToken(opts.agentId);
|
|
531
|
+
env.push({ name: "MACRO_AGENT_TOKEN", value: agentToken });
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
// Legacy mode: create local service stack with shared SQLite
|
|
535
|
+
env.push(
|
|
536
|
+
{ name: "MACRO_INSTANCE_ID", value: eventStore.instanceId },
|
|
537
|
+
{ name: "MACRO_BASE_DIR", value: eventStore.baseDir },
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
name: "macro-agent",
|
|
543
|
+
command: "npx",
|
|
544
|
+
args: ["multiagent-mcp"],
|
|
545
|
+
env,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
388
549
|
// ─────────────────────────────────────────────────────────────────
|
|
389
550
|
// Lifecycle
|
|
390
551
|
// ─────────────────────────────────────────────────────────────────
|
|
391
552
|
|
|
392
553
|
async function spawn(rawOptions: SpawnAgentOptions): Promise<SpawnedAgent> {
|
|
554
|
+
if (isShuttingDown) {
|
|
555
|
+
throw new AgentManagerError(
|
|
556
|
+
"Cannot spawn agent during shutdown",
|
|
557
|
+
"SHUTDOWN_IN_PROGRESS",
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
393
561
|
// Apply spawn interceptor if set (used by TeamRuntime for team context injection)
|
|
394
562
|
const options = spawnInterceptor
|
|
395
563
|
? await spawnInterceptor(rawOptions)
|
|
@@ -426,7 +594,7 @@ export function createAgentManager(
|
|
|
426
594
|
throw new AgentManagerError(
|
|
427
595
|
`Parent agent not found: ${parent}`,
|
|
428
596
|
"AGENT_NOT_FOUND",
|
|
429
|
-
parent
|
|
597
|
+
parent,
|
|
430
598
|
);
|
|
431
599
|
}
|
|
432
600
|
|
|
@@ -437,8 +605,12 @@ export function createAgentManager(
|
|
|
437
605
|
|
|
438
606
|
// Accept either the specific capability (e.g., agent.spawn.grinder)
|
|
439
607
|
// or the generic agent.spawn.custom as a fallback for non-built-in roles
|
|
440
|
-
const hasSpecific = roleRegistry.hasCapability(
|
|
441
|
-
|
|
608
|
+
const hasSpecific = roleRegistry.hasCapability(
|
|
609
|
+
parentRole,
|
|
610
|
+
requiredCapability,
|
|
611
|
+
);
|
|
612
|
+
const hasGeneric =
|
|
613
|
+
requiredCapability !== AGENT_CAPABILITIES.SPAWN_CUSTOM &&
|
|
442
614
|
roleRegistry.hasCapability(parentRole, AGENT_CAPABILITIES.SPAWN_CUSTOM);
|
|
443
615
|
|
|
444
616
|
if (!hasSpecific && !hasGeneric) {
|
|
@@ -446,7 +618,7 @@ export function createAgentManager(
|
|
|
446
618
|
`Parent agent with role '${parentRole}' does not have capability to spawn '${childRole}' agents. ` +
|
|
447
619
|
`Required capability: ${requiredCapability}`,
|
|
448
620
|
"CAPABILITY_DENIED",
|
|
449
|
-
parent
|
|
621
|
+
parent,
|
|
450
622
|
);
|
|
451
623
|
}
|
|
452
624
|
}
|
|
@@ -507,6 +679,14 @@ export function createAgentManager(
|
|
|
507
679
|
},
|
|
508
680
|
});
|
|
509
681
|
|
|
682
|
+
// Generate a human-readable default name
|
|
683
|
+
const generatedName = uniqueNamesGenerator({
|
|
684
|
+
dictionaries: [adjectives, animals],
|
|
685
|
+
separator: "-",
|
|
686
|
+
length: 2,
|
|
687
|
+
});
|
|
688
|
+
eventStore.updateAgentMetadata(agentId as AgentId, { name: generatedName });
|
|
689
|
+
|
|
510
690
|
// Persist immediately so MCP server subprocess can read the agent
|
|
511
691
|
await eventStore.persist();
|
|
512
692
|
|
|
@@ -514,10 +694,10 @@ export function createAgentManager(
|
|
|
514
694
|
const verifyAgent = eventStore.getAgent(agentId);
|
|
515
695
|
const allAgents = eventStore.listAgents();
|
|
516
696
|
console.error(
|
|
517
|
-
`[AgentManager] After persist: agent ${agentId} exists = ${!!verifyAgent}, total agents = ${allAgents.length}, instancePath = ${eventStore.instancePath}
|
|
697
|
+
`[AgentManager] After persist: agent ${agentId} exists = ${!!verifyAgent}, total agents = ${allAgents.length}, instancePath = ${eventStore.instancePath}`,
|
|
518
698
|
);
|
|
519
699
|
console.error(
|
|
520
|
-
`[AgentManager] All agent IDs: ${allAgents.map((a) => a.id).join(", ")}
|
|
700
|
+
`[AgentManager] All agent IDs: ${allAgents.map((a) => a.id).join(", ")}`,
|
|
521
701
|
);
|
|
522
702
|
|
|
523
703
|
try {
|
|
@@ -527,195 +707,207 @@ export function createAgentManager(
|
|
|
527
707
|
env: agentConfig?.env,
|
|
528
708
|
});
|
|
529
709
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
{ name: "MACRO_PARENT_ID", value: parent ?? "" },
|
|
540
|
-
{ name: "MACRO_TASK_ID", value: taskId },
|
|
541
|
-
{ name: "MACRO_AGENT_CWD", value: cwd },
|
|
542
|
-
{ name: "MACRO_INSTANCE_ID", value: eventStore.instanceId },
|
|
543
|
-
{ name: "MACRO_BASE_DIR", value: eventStore.baseDir },
|
|
544
|
-
],
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
// Combine with any user-provided MCP servers
|
|
548
|
-
// Note: Like macroAgentMcp, user MCP servers use stdio (no 'type' field)
|
|
549
|
-
const userMcpServers =
|
|
550
|
-
agentConfig?.mcpServers?.map((s) => ({
|
|
551
|
-
name: s.name,
|
|
552
|
-
command: s.command,
|
|
553
|
-
args: s.args ?? [],
|
|
554
|
-
env: s.env
|
|
555
|
-
? Object.entries(s.env).map(([name, value]) => ({ name, value }))
|
|
710
|
+
try {
|
|
711
|
+
const macroAgentMcp = buildMacroAgentMcp({
|
|
712
|
+
agentId,
|
|
713
|
+
parentId: parent ?? "",
|
|
714
|
+
taskId,
|
|
715
|
+
cwd,
|
|
716
|
+
permissionMode,
|
|
717
|
+
lineage: parentAgent?.lineage
|
|
718
|
+
? [...parentAgent.lineage, parent!]
|
|
556
719
|
: [],
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
// Create session with MCP servers
|
|
560
|
-
// Note: The MCP server subprocess will start here and look for the agent
|
|
561
|
-
// in EventStore. We already persisted the spawn event above.
|
|
562
|
-
const session = await handle.createSession(cwd, {
|
|
563
|
-
mcpServers: [macroAgentMcp, ...userMcpServers],
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
// Emit started status (session is ready)
|
|
567
|
-
// Include the provider's session ID (e.g., Claude Code UUID) so
|
|
568
|
-
// it can be used for handle.loadSession() during resume
|
|
569
|
-
eventStore.emit({
|
|
570
|
-
type: "status",
|
|
571
|
-
source: { agent_id: agentId },
|
|
572
|
-
payload: {
|
|
573
|
-
status_type: "started",
|
|
574
|
-
summary: "Agent session started",
|
|
575
|
-
provider_session_id: session.id,
|
|
576
|
-
},
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
// Persist the status event
|
|
580
|
-
await eventStore.persist();
|
|
581
|
-
|
|
582
|
-
// Set up default subscriptions via MessageRouter
|
|
583
|
-
messageRouter.setupDefaultSubscriptions({
|
|
584
|
-
agent_id: agentId,
|
|
585
|
-
parent_id: parent ?? undefined,
|
|
586
|
-
task_id: taskId,
|
|
587
|
-
subscribe_parent: subscribeParent,
|
|
588
|
-
additional_topics: topics,
|
|
589
|
-
role: role ?? undefined,
|
|
590
|
-
});
|
|
720
|
+
sessionId,
|
|
721
|
+
});
|
|
591
722
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
723
|
+
// Combine with any user-provided MCP servers
|
|
724
|
+
// Note: Like macroAgentMcp, user MCP servers use stdio (no 'type' field)
|
|
725
|
+
const userMcpServers =
|
|
726
|
+
agentConfig?.mcpServers?.map((s) => ({
|
|
727
|
+
name: s.name,
|
|
728
|
+
command: s.command,
|
|
729
|
+
args: s.args ?? [],
|
|
730
|
+
env: s.env
|
|
731
|
+
? Object.entries(s.env).map(([name, value]) => ({ name, value }))
|
|
732
|
+
: [],
|
|
733
|
+
})) ?? [];
|
|
734
|
+
|
|
735
|
+
// Create session with MCP servers
|
|
736
|
+
// Note: The MCP server subprocess will start here and look for the agent
|
|
737
|
+
// in EventStore. We already persisted the spawn event above.
|
|
738
|
+
//
|
|
739
|
+
// When permissionMode is "interactive", we strip settingSources so that
|
|
740
|
+
// the Claude Code subprocess doesn't read pre-approved tool rules from
|
|
741
|
+
// the user's ~/.claude/settings.local.json. This ensures ALL tool calls
|
|
742
|
+
// go through the canUseTool → requestPermission ACP flow.
|
|
743
|
+
const agentMeta =
|
|
744
|
+
permissionMode === "interactive"
|
|
745
|
+
? { claudeCode: { options: { settingSources: [] } } }
|
|
600
746
|
: undefined;
|
|
747
|
+
const session = await handle.createSession(cwd, {
|
|
748
|
+
mcpServers: [macroAgentMcp, ...userMcpServers],
|
|
749
|
+
...(agentMeta && { agentMeta }),
|
|
750
|
+
});
|
|
601
751
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
752
|
+
// Emit started status (session is ready)
|
|
753
|
+
// Include the provider's session ID (e.g., Claude Code UUID) so
|
|
754
|
+
// it can be used for handle.loadSession() during resume
|
|
755
|
+
eventStore.emit({
|
|
756
|
+
type: "status",
|
|
757
|
+
source: { agent_id: agentId },
|
|
758
|
+
payload: {
|
|
759
|
+
status_type: "started",
|
|
760
|
+
summary: "Agent session started",
|
|
761
|
+
provider_session_id: session.id,
|
|
762
|
+
},
|
|
763
|
+
});
|
|
609
764
|
|
|
610
|
-
|
|
611
|
-
|
|
765
|
+
// Persist the status event
|
|
766
|
+
await eventStore.persist();
|
|
767
|
+
|
|
768
|
+
// Set up default subscriptions via MessageRouter
|
|
769
|
+
messageRouter.setupDefaultSubscriptions({
|
|
770
|
+
agent_id: agentId,
|
|
771
|
+
parent_id: parent ?? undefined,
|
|
772
|
+
task_id: taskId,
|
|
773
|
+
subscribe_parent: subscribeParent,
|
|
774
|
+
additional_topics: topics,
|
|
775
|
+
role: role ?? undefined,
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// ─────────────────────────────────────────────────────────────────
|
|
779
|
+
// Mail: Create task conversation for this agent
|
|
780
|
+
// ─────────────────────────────────────────────────────────────────
|
|
781
|
+
if (mailService && conversationMap) {
|
|
782
|
+
try {
|
|
783
|
+
const parentConversationId = parent
|
|
784
|
+
? (conversationMap.getAgentConversation(parent) ??
|
|
785
|
+
conversationMap.getSessionConversation(parent))
|
|
786
|
+
: undefined;
|
|
787
|
+
|
|
788
|
+
const { conversationId: taskConvId } =
|
|
789
|
+
mailService.createConversation({
|
|
790
|
+
type: "task",
|
|
791
|
+
subject: task?.slice(0, 80),
|
|
792
|
+
createdBy: parent ?? agentId,
|
|
793
|
+
parentConversationId: parentConversationId,
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
// Join parent and child as participants
|
|
797
|
+
if (parent) {
|
|
798
|
+
mailService.joinConversation({
|
|
799
|
+
conversationId: taskConvId,
|
|
800
|
+
participantId: parent,
|
|
801
|
+
participantType: "agent",
|
|
802
|
+
role: "initiator",
|
|
803
|
+
agentId: parent,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
612
806
|
mailService.joinConversation({
|
|
613
807
|
conversationId: taskConvId,
|
|
614
|
-
participantId:
|
|
808
|
+
participantId: agentId,
|
|
615
809
|
participantType: "agent",
|
|
616
|
-
role: "
|
|
617
|
-
agentId
|
|
810
|
+
role: "worker",
|
|
811
|
+
agentId,
|
|
618
812
|
});
|
|
619
|
-
}
|
|
620
|
-
mailService.joinConversation({
|
|
621
|
-
conversationId: taskConvId,
|
|
622
|
-
participantId: agentId,
|
|
623
|
-
participantType: "agent",
|
|
624
|
-
role: "worker",
|
|
625
|
-
agentId,
|
|
626
|
-
});
|
|
627
813
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
814
|
+
conversationMap.setAgentConversation(agentId, taskConvId);
|
|
815
|
+
} catch (err) {
|
|
816
|
+
// Never fail spawn due to mail errors
|
|
817
|
+
console.warn(
|
|
818
|
+
`[AgentManager] Failed to create task conversation for ${agentId}:`,
|
|
819
|
+
err,
|
|
820
|
+
);
|
|
821
|
+
}
|
|
635
822
|
}
|
|
636
|
-
}
|
|
637
823
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
824
|
+
// Track active session
|
|
825
|
+
const activeSession: ActiveSession = {
|
|
826
|
+
agentId,
|
|
827
|
+
handle,
|
|
828
|
+
session,
|
|
829
|
+
createdAt: Date.now(),
|
|
830
|
+
isPrompting: false,
|
|
831
|
+
};
|
|
832
|
+
activeSessions.set(agentId, activeSession);
|
|
647
833
|
|
|
648
|
-
|
|
649
|
-
|
|
834
|
+
// Get the agent from materialized view
|
|
835
|
+
const agent = eventStore.getAgent(agentId)!;
|
|
650
836
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
837
|
+
// ─────────────────────────────────────────────────────────────────
|
|
838
|
+
// Workspace Creation (Phase 2)
|
|
839
|
+
// ─────────────────────────────────────────────────────────────────
|
|
840
|
+
let workspace: Workspace | undefined;
|
|
841
|
+
let resolvedStreamId = streamId;
|
|
656
842
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
workspace.path
|
|
686
|
-
);
|
|
843
|
+
if (workspaceManager && role) {
|
|
844
|
+
try {
|
|
845
|
+
workspace = await createWorkspaceForRole(
|
|
846
|
+
workspaceManager,
|
|
847
|
+
agentId,
|
|
848
|
+
role,
|
|
849
|
+
{
|
|
850
|
+
streamId,
|
|
851
|
+
streamConfig,
|
|
852
|
+
dataplaneTaskId,
|
|
853
|
+
cwd,
|
|
854
|
+
},
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
if (workspace) {
|
|
858
|
+
agentWorkspaces.set(agentId, workspace);
|
|
859
|
+
resolvedStreamId = workspace.streamId;
|
|
860
|
+
|
|
861
|
+
// Register with parent coordinator if applicable
|
|
862
|
+
if (parent && (role === "worker" || role === "integrator")) {
|
|
863
|
+
const parentWorkspace = agentWorkspaces.get(parent);
|
|
864
|
+
if (parentWorkspace?.role === "coordinator") {
|
|
865
|
+
workspaceManager.registerChildWorkspace(
|
|
866
|
+
parent,
|
|
867
|
+
agentId,
|
|
868
|
+
workspace.path,
|
|
869
|
+
);
|
|
870
|
+
}
|
|
687
871
|
}
|
|
688
872
|
}
|
|
873
|
+
} catch (wsError) {
|
|
874
|
+
console.error(
|
|
875
|
+
`[AgentManager] Failed to create workspace for ${agentId}: ${wsError}`,
|
|
876
|
+
);
|
|
877
|
+
// Continue without workspace - don't fail the spawn
|
|
689
878
|
}
|
|
690
|
-
} catch (wsError) {
|
|
691
|
-
console.error(
|
|
692
|
-
`[AgentManager] Failed to create workspace for ${agentId}: ${wsError}`
|
|
693
|
-
);
|
|
694
|
-
// Continue without workspace - don't fail the spawn
|
|
695
879
|
}
|
|
696
|
-
}
|
|
697
880
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
881
|
+
// Notify lifecycle listeners
|
|
882
|
+
notifyLifecycle({ type: "spawned", agent });
|
|
883
|
+
notifyLifecycle({ type: "started", agent });
|
|
701
884
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
885
|
+
// Start health monitoring for coordinators
|
|
886
|
+
if (healthCheckService && role === "coordinator") {
|
|
887
|
+
healthCheckService.startForCoordinator(agentId);
|
|
888
|
+
}
|
|
706
889
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
890
|
+
return {
|
|
891
|
+
id: agentId,
|
|
892
|
+
session_id: sessionId, // Macro-agent's own session ID for ACP protocol mapping
|
|
893
|
+
agent,
|
|
894
|
+
session,
|
|
895
|
+
workspace,
|
|
896
|
+
streamId: resolvedStreamId,
|
|
897
|
+
};
|
|
898
|
+
} catch (handleError) {
|
|
899
|
+
// Close the spawned process to prevent orphaning
|
|
900
|
+
try {
|
|
901
|
+
await handle.close();
|
|
902
|
+
} catch {
|
|
903
|
+
// Ignore errors during cleanup
|
|
904
|
+
}
|
|
905
|
+
throw handleError;
|
|
906
|
+
}
|
|
715
907
|
} catch (error) {
|
|
716
908
|
// Clean up the spawn event we already emitted
|
|
717
909
|
eventStore.emit({
|
|
718
|
-
type: "
|
|
910
|
+
type: "stop",
|
|
719
911
|
source: { agent_id: agentId },
|
|
720
912
|
payload: {
|
|
721
913
|
reason: "failed",
|
|
@@ -726,21 +918,21 @@ export function createAgentManager(
|
|
|
726
918
|
throw new AgentManagerError(
|
|
727
919
|
`Failed to spawn agent: ${error}`,
|
|
728
920
|
"SPAWN_FAILED",
|
|
729
|
-
agentId
|
|
921
|
+
agentId,
|
|
730
922
|
);
|
|
731
923
|
}
|
|
732
924
|
}
|
|
733
925
|
|
|
734
926
|
async function terminate(
|
|
735
927
|
agentId: AgentId,
|
|
736
|
-
reason: AgentStopReason
|
|
928
|
+
reason: AgentStopReason,
|
|
737
929
|
): Promise<void> {
|
|
738
930
|
const agent = eventStore.getAgent(agentId);
|
|
739
931
|
if (!agent) {
|
|
740
932
|
throw new AgentManagerError(
|
|
741
933
|
`Agent not found: ${agentId}`,
|
|
742
934
|
"AGENT_NOT_FOUND",
|
|
743
|
-
agentId
|
|
935
|
+
agentId,
|
|
744
936
|
);
|
|
745
937
|
}
|
|
746
938
|
|
|
@@ -769,12 +961,17 @@ export function createAgentManager(
|
|
|
769
961
|
agentWorkspaces.delete(agentId);
|
|
770
962
|
} catch (wsError) {
|
|
771
963
|
console.error(
|
|
772
|
-
`[AgentManager] Failed to deallocate workspace for ${agentId}: ${wsError}
|
|
964
|
+
`[AgentManager] Failed to deallocate workspace for ${agentId}: ${wsError}`,
|
|
773
965
|
);
|
|
774
966
|
// Continue with termination even if workspace cleanup fails
|
|
775
967
|
}
|
|
776
968
|
}
|
|
777
969
|
|
|
970
|
+
// Revoke agent authentication token
|
|
971
|
+
if (agentTokenManager) {
|
|
972
|
+
agentTokenManager.revokeToken(agentId);
|
|
973
|
+
}
|
|
974
|
+
|
|
778
975
|
// ─────────────────────────────────────────────────────────────────
|
|
779
976
|
// Mail: Close task conversation on terminate
|
|
780
977
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -789,8 +986,7 @@ export function createAgentManager(
|
|
|
789
986
|
});
|
|
790
987
|
}
|
|
791
988
|
// Close any peer conversations
|
|
792
|
-
const peerConvIds =
|
|
793
|
-
conversationMap.closePeerConversationsFor(agentId);
|
|
989
|
+
const peerConvIds = conversationMap.closePeerConversationsFor(agentId);
|
|
794
990
|
for (const peerConvId of peerConvIds) {
|
|
795
991
|
mailService.closeConversation({
|
|
796
992
|
conversationId: peerConvId,
|
|
@@ -802,14 +998,14 @@ export function createAgentManager(
|
|
|
802
998
|
} catch (err) {
|
|
803
999
|
console.warn(
|
|
804
1000
|
`[AgentManager] Failed to close conversation for ${agentId}:`,
|
|
805
|
-
err
|
|
1001
|
+
err,
|
|
806
1002
|
);
|
|
807
1003
|
}
|
|
808
1004
|
}
|
|
809
1005
|
|
|
810
|
-
// Emit
|
|
1006
|
+
// Emit stop event
|
|
811
1007
|
eventStore.emit({
|
|
812
|
-
type: "
|
|
1008
|
+
type: "stop",
|
|
813
1009
|
source: { agent_id: agentId },
|
|
814
1010
|
payload: {
|
|
815
1011
|
agent_id: agentId,
|
|
@@ -876,19 +1072,30 @@ export function createAgentManager(
|
|
|
876
1072
|
child.id,
|
|
877
1073
|
agentId,
|
|
878
1074
|
cascadeAdapter,
|
|
879
|
-
workspaceProvider
|
|
1075
|
+
workspaceProvider,
|
|
880
1076
|
);
|
|
881
1077
|
}
|
|
882
1078
|
}
|
|
883
1079
|
}
|
|
884
1080
|
|
|
885
|
-
async function resume(
|
|
1081
|
+
async function resume(
|
|
1082
|
+
agentId: AgentId,
|
|
1083
|
+
overridePermissionMode?: PermissionMode,
|
|
1084
|
+
): Promise<SpawnedAgent> {
|
|
1085
|
+
if (isShuttingDown) {
|
|
1086
|
+
throw new AgentManagerError(
|
|
1087
|
+
"Cannot resume agent during shutdown",
|
|
1088
|
+
"SHUTDOWN_IN_PROGRESS",
|
|
1089
|
+
agentId,
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
886
1093
|
const agent = eventStore.getAgent(agentId);
|
|
887
1094
|
if (!agent) {
|
|
888
1095
|
throw new AgentManagerError(
|
|
889
1096
|
`Agent not found: ${agentId}`,
|
|
890
1097
|
"AGENT_NOT_FOUND",
|
|
891
|
-
agentId
|
|
1098
|
+
agentId,
|
|
892
1099
|
);
|
|
893
1100
|
}
|
|
894
1101
|
|
|
@@ -897,66 +1104,260 @@ export function createAgentManager(
|
|
|
897
1104
|
throw new AgentManagerError(
|
|
898
1105
|
`Agent already has active session: ${agentId}`,
|
|
899
1106
|
"ALREADY_RUNNING",
|
|
900
|
-
agentId
|
|
1107
|
+
agentId,
|
|
901
1108
|
);
|
|
902
1109
|
}
|
|
903
1110
|
|
|
1111
|
+
const permissionMode = overridePermissionMode ?? defaultPermissionMode;
|
|
1112
|
+
|
|
904
1113
|
// Spawn new process
|
|
905
1114
|
const handle = await AgentFactory.spawn(defaultAgentType, {
|
|
906
|
-
permissionMode
|
|
1115
|
+
permissionMode,
|
|
907
1116
|
});
|
|
908
1117
|
|
|
909
|
-
|
|
910
|
-
|
|
1118
|
+
try {
|
|
1119
|
+
const agentCwd = agent.cwd ?? defaultCwd;
|
|
1120
|
+
let session;
|
|
911
1121
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1122
|
+
// When interactive mode, strip settings to prevent auto-approval
|
|
1123
|
+
const resumeAgentMeta =
|
|
1124
|
+
permissionMode === "interactive"
|
|
1125
|
+
? { claudeCode: { options: { settingSources: [] } } }
|
|
1126
|
+
: undefined;
|
|
1127
|
+
|
|
1128
|
+
const macroAgentMcp = buildMacroAgentMcp({
|
|
1129
|
+
agentId,
|
|
1130
|
+
parentId: agent.parent ?? "",
|
|
1131
|
+
taskId: agent.task_id ?? "",
|
|
1132
|
+
cwd: agentCwd,
|
|
1133
|
+
permissionMode,
|
|
1134
|
+
lineage: agent.lineage ?? [],
|
|
1135
|
+
sessionId: agent.session_id ?? "",
|
|
1136
|
+
});
|
|
1137
|
+
const mcpServers = [macroAgentMcp];
|
|
1138
|
+
|
|
1139
|
+
if (agent.provider_session_id) {
|
|
1140
|
+
// Load existing session using the provider's session ID (e.g., Claude Code UUID)
|
|
1141
|
+
// Note: loadSession's TS type for mcpServers is { name, uri }[] but
|
|
1142
|
+
// the underlying ACP protocol accepts full McpServerStdio. The JS
|
|
1143
|
+
// implementation passes mcpServers through to the connection unchanged.
|
|
1144
|
+
session = await handle.loadSession(
|
|
1145
|
+
agent.provider_session_id,
|
|
1146
|
+
agentCwd,
|
|
1147
|
+
mcpServers as any,
|
|
1148
|
+
resumeAgentMeta ? { agentMeta: resumeAgentMeta } : undefined,
|
|
1149
|
+
);
|
|
1150
|
+
} else {
|
|
1151
|
+
// No provider session ID available (agent predates this feature or wasn't persisted).
|
|
1152
|
+
// Create a new session instead of loading with the macro-agent session_id
|
|
1153
|
+
// which is not a valid provider session ID (e.g., Claude Code expects UUIDs).
|
|
1154
|
+
session = await handle.createSession(agentCwd, {
|
|
1155
|
+
mcpServers,
|
|
1156
|
+
...(resumeAgentMeta && { agentMeta: resumeAgentMeta }),
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// Store the provider session ID for future resumes
|
|
1160
|
+
eventStore.emit({
|
|
1161
|
+
type: "status",
|
|
1162
|
+
source: { agent_id: agentId },
|
|
1163
|
+
payload: {
|
|
1164
|
+
status_type: "started",
|
|
1165
|
+
summary: "Agent session created (no provider session to resume)",
|
|
1166
|
+
provider_session_id: session.id,
|
|
1167
|
+
},
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Track active session
|
|
1172
|
+
const activeSession: ActiveSession = {
|
|
1173
|
+
agentId,
|
|
1174
|
+
handle,
|
|
1175
|
+
session,
|
|
1176
|
+
createdAt: Date.now(),
|
|
1177
|
+
isPrompting: false,
|
|
1178
|
+
};
|
|
1179
|
+
activeSessions.set(agentId, activeSession);
|
|
920
1180
|
|
|
921
|
-
//
|
|
1181
|
+
// Emit status event for resume
|
|
922
1182
|
eventStore.emit({
|
|
923
1183
|
type: "status",
|
|
924
1184
|
source: { agent_id: agentId },
|
|
925
1185
|
payload: {
|
|
926
1186
|
status_type: "started",
|
|
927
|
-
summary: "Agent session
|
|
1187
|
+
summary: "Agent session resumed",
|
|
928
1188
|
provider_session_id: session.id,
|
|
929
1189
|
},
|
|
930
1190
|
});
|
|
1191
|
+
|
|
1192
|
+
return {
|
|
1193
|
+
id: agentId,
|
|
1194
|
+
session_id: agent.session_id, // Macro-agent's own session ID
|
|
1195
|
+
agent: eventStore.getAgent(agentId)!,
|
|
1196
|
+
session,
|
|
1197
|
+
};
|
|
1198
|
+
} catch (handleError) {
|
|
1199
|
+
// Close the spawned process to prevent orphaning
|
|
1200
|
+
try {
|
|
1201
|
+
await handle.close();
|
|
1202
|
+
} catch {
|
|
1203
|
+
// Ignore errors during cleanup
|
|
1204
|
+
}
|
|
1205
|
+
throw handleError;
|
|
931
1206
|
}
|
|
1207
|
+
}
|
|
932
1208
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1209
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1210
|
+
// Fork
|
|
1211
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1212
|
+
|
|
1213
|
+
async function forkAgent(
|
|
1214
|
+
sourceAgentId: AgentId,
|
|
1215
|
+
options?: { name?: string; prompt?: string; cwd?: string },
|
|
1216
|
+
): Promise<SpawnedAgent> {
|
|
1217
|
+
if (isShuttingDown) {
|
|
1218
|
+
throw new AgentManagerError(
|
|
1219
|
+
"Cannot fork agent during shutdown",
|
|
1220
|
+
"SHUTDOWN_IN_PROGRESS",
|
|
1221
|
+
sourceAgentId,
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const sourceAgent = eventStore.getAgent(sourceAgentId);
|
|
1226
|
+
if (!sourceAgent) {
|
|
1227
|
+
throw new AgentManagerError(
|
|
1228
|
+
`Agent not found: ${sourceAgentId}`,
|
|
1229
|
+
"AGENT_NOT_FOUND",
|
|
1230
|
+
sourceAgentId,
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
942
1233
|
|
|
943
|
-
//
|
|
1234
|
+
// Need either an active session or a persisted provider_session_id
|
|
1235
|
+
const activeSession = activeSessions.get(sourceAgentId);
|
|
1236
|
+
if (!activeSession && !sourceAgent.provider_session_id) {
|
|
1237
|
+
throw new AgentManagerError(
|
|
1238
|
+
`Agent has no session to fork: ${sourceAgentId}`,
|
|
1239
|
+
"FORK_NOT_SUPPORTED",
|
|
1240
|
+
sourceAgentId,
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Generate new IDs
|
|
1245
|
+
const agentId = `agent_${nanoid(12)}`;
|
|
1246
|
+
const taskId = `task_${nanoid(12)}`;
|
|
1247
|
+
const sessionId = `session_${nanoid(12)}`;
|
|
1248
|
+
const cwd = options?.cwd ?? sourceAgent.cwd ?? defaultCwd;
|
|
1249
|
+
|
|
1250
|
+
// Emit spawn event with fork metadata
|
|
944
1251
|
eventStore.emit({
|
|
945
|
-
type: "
|
|
946
|
-
source: { agent_id:
|
|
1252
|
+
type: "spawn",
|
|
1253
|
+
source: { agent_id: sourceAgentId },
|
|
947
1254
|
payload: {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1255
|
+
agent_id: agentId,
|
|
1256
|
+
session_id: sessionId,
|
|
1257
|
+
task: options?.name ?? `[Fork of ${sourceAgentId}]`,
|
|
1258
|
+
task_id: taskId,
|
|
1259
|
+
parent: sourceAgent.parent ?? null,
|
|
1260
|
+
role: sourceAgent.role ?? undefined,
|
|
1261
|
+
config: {},
|
|
1262
|
+
cwd,
|
|
1263
|
+
metadata: { fork_of: sourceAgentId },
|
|
951
1264
|
},
|
|
952
1265
|
});
|
|
953
1266
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
};
|
|
1267
|
+
// Generate a human-readable name
|
|
1268
|
+
const generatedName = uniqueNamesGenerator({
|
|
1269
|
+
dictionaries: [adjectives, animals],
|
|
1270
|
+
separator: "-",
|
|
1271
|
+
length: 2,
|
|
1272
|
+
});
|
|
1273
|
+
eventStore.updateAgentMetadata(agentId as AgentId, { name: generatedName });
|
|
1274
|
+
await eventStore.persist();
|
|
1275
|
+
|
|
1276
|
+
// Get the provider session ID to fork from
|
|
1277
|
+
let forkedProviderSessionId: string;
|
|
1278
|
+
if (activeSession) {
|
|
1279
|
+
// Active session: fork with flush to ensure data is persisted
|
|
1280
|
+
const forkedSession = await activeSession.session.forkWithFlush();
|
|
1281
|
+
forkedProviderSessionId = forkedSession.id;
|
|
1282
|
+
} else {
|
|
1283
|
+
// Stopped agent: use the persisted provider session ID directly
|
|
1284
|
+
forkedProviderSessionId = sourceAgent.provider_session_id!;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Spawn a new process
|
|
1288
|
+
const handle = await AgentFactory.spawn(defaultAgentType, {
|
|
1289
|
+
permissionMode: defaultPermissionMode,
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
try {
|
|
1293
|
+
const macroAgentMcp = buildMacroAgentMcp({
|
|
1294
|
+
agentId,
|
|
1295
|
+
parentId: sourceAgent.parent ?? "",
|
|
1296
|
+
taskId,
|
|
1297
|
+
cwd,
|
|
1298
|
+
permissionMode: defaultPermissionMode,
|
|
1299
|
+
lineage: sourceAgent.lineage ?? [],
|
|
1300
|
+
sessionId,
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
// Load the forked session on the new process with correct MCP config.
|
|
1304
|
+
// Note: loadSession's TS type for mcpServers is { name, uri }[] but
|
|
1305
|
+
// the underlying ACP protocol accepts full McpServerStdio. The JS
|
|
1306
|
+
// implementation passes mcpServers through to the connection unchanged.
|
|
1307
|
+
const session = await handle.loadSession(forkedProviderSessionId, cwd, [
|
|
1308
|
+
macroAgentMcp,
|
|
1309
|
+
] as any);
|
|
1310
|
+
|
|
1311
|
+
// Emit started status with provider session ID
|
|
1312
|
+
eventStore.emit({
|
|
1313
|
+
type: "status",
|
|
1314
|
+
source: { agent_id: agentId },
|
|
1315
|
+
payload: {
|
|
1316
|
+
status_type: "started",
|
|
1317
|
+
summary: "Agent session started (forked)",
|
|
1318
|
+
provider_session_id: session.id,
|
|
1319
|
+
},
|
|
1320
|
+
});
|
|
1321
|
+
await eventStore.persist();
|
|
1322
|
+
|
|
1323
|
+
// Set up message router subscriptions
|
|
1324
|
+
messageRouter.setupDefaultSubscriptions({
|
|
1325
|
+
agent_id: agentId,
|
|
1326
|
+
parent_id: sourceAgent.parent ?? undefined,
|
|
1327
|
+
task_id: taskId,
|
|
1328
|
+
subscribe_parent: false,
|
|
1329
|
+
additional_topics: [],
|
|
1330
|
+
role: sourceAgent.role ?? undefined,
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// Track active session
|
|
1334
|
+
const newActiveSession: ActiveSession = {
|
|
1335
|
+
agentId,
|
|
1336
|
+
handle,
|
|
1337
|
+
session,
|
|
1338
|
+
createdAt: Date.now(),
|
|
1339
|
+
isPrompting: false,
|
|
1340
|
+
};
|
|
1341
|
+
activeSessions.set(agentId, newActiveSession);
|
|
1342
|
+
|
|
1343
|
+
const agent = eventStore.getAgent(agentId)!;
|
|
1344
|
+
notifyLifecycle({ type: "spawned", agent });
|
|
1345
|
+
notifyLifecycle({ type: "started", agent });
|
|
1346
|
+
|
|
1347
|
+
return {
|
|
1348
|
+
id: agentId,
|
|
1349
|
+
session_id: sessionId,
|
|
1350
|
+
agent,
|
|
1351
|
+
session,
|
|
1352
|
+
};
|
|
1353
|
+
} catch (handleError) {
|
|
1354
|
+
try {
|
|
1355
|
+
await handle.close();
|
|
1356
|
+
} catch {
|
|
1357
|
+
// Ignore errors during cleanup
|
|
1358
|
+
}
|
|
1359
|
+
throw handleError;
|
|
1360
|
+
}
|
|
960
1361
|
}
|
|
961
1362
|
|
|
962
1363
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -994,7 +1395,7 @@ export function createAgentManager(
|
|
|
994
1395
|
|
|
995
1396
|
function getHierarchy(
|
|
996
1397
|
agentId: AgentId,
|
|
997
|
-
options?: HierarchyOptions
|
|
1398
|
+
options?: HierarchyOptions,
|
|
998
1399
|
): AgentHierarchy | null {
|
|
999
1400
|
const agent = eventStore.getAgent(agentId);
|
|
1000
1401
|
if (!agent) return null;
|
|
@@ -1035,7 +1436,7 @@ export function createAgentManager(
|
|
|
1035
1436
|
// ─────────────────────────────────────────────────────────────────
|
|
1036
1437
|
|
|
1037
1438
|
async function getOrCreateHeadManager(
|
|
1038
|
-
options: HeadManagerOptions
|
|
1439
|
+
options: HeadManagerOptions,
|
|
1039
1440
|
): Promise<SpawnedAgent> {
|
|
1040
1441
|
const {
|
|
1041
1442
|
cwd,
|
|
@@ -1107,14 +1508,14 @@ export function createAgentManager(
|
|
|
1107
1508
|
|
|
1108
1509
|
async function* prompt(
|
|
1109
1510
|
agentId: AgentId,
|
|
1110
|
-
message: string
|
|
1511
|
+
message: string,
|
|
1111
1512
|
): AsyncIterable<ExtendedSessionUpdate> {
|
|
1112
1513
|
const activeSession = activeSessions.get(agentId);
|
|
1113
1514
|
if (!activeSession) {
|
|
1114
1515
|
throw new AgentManagerError(
|
|
1115
1516
|
`No active session for agent: ${agentId}`,
|
|
1116
1517
|
"SESSION_NOT_FOUND",
|
|
1117
|
-
agentId
|
|
1518
|
+
agentId,
|
|
1118
1519
|
);
|
|
1119
1520
|
}
|
|
1120
1521
|
|
|
@@ -1139,7 +1540,7 @@ export function createAgentManager(
|
|
|
1139
1540
|
maxFollowUps?: number;
|
|
1140
1541
|
throwOnMaxExceeded?: boolean;
|
|
1141
1542
|
onUpdate?: (update: ExtendedSessionUpdate) => void;
|
|
1142
|
-
}
|
|
1543
|
+
},
|
|
1143
1544
|
): Promise<{
|
|
1144
1545
|
doneCalled: boolean;
|
|
1145
1546
|
doneStatus?: string;
|
|
@@ -1156,7 +1557,10 @@ export function createAgentManager(
|
|
|
1156
1557
|
// Helper to check if done() was called by looking for status events
|
|
1157
1558
|
// The done() MCP tool emits status events with status_type completed/failed
|
|
1158
1559
|
// and includes signal: "WORKER_DONE" in the details
|
|
1159
|
-
const checkDoneCalled = async (): Promise<{
|
|
1560
|
+
const checkDoneCalled = async (): Promise<{
|
|
1561
|
+
called: boolean;
|
|
1562
|
+
status?: string;
|
|
1563
|
+
}> => {
|
|
1160
1564
|
// Reload from disk to see events from MCP subprocess
|
|
1161
1565
|
await eventStore.reload();
|
|
1162
1566
|
const statusEvents = eventStore.query({ type: "status" });
|
|
@@ -1166,14 +1570,15 @@ export function createAgentManager(
|
|
|
1166
1570
|
(e) =>
|
|
1167
1571
|
e.source?.agent_id === agentId &&
|
|
1168
1572
|
(e.payload?.status_type === "completed" ||
|
|
1169
|
-
|
|
1170
|
-
|
|
1573
|
+
e.payload?.status_type === "failed" ||
|
|
1574
|
+
(e.payload?.details as Record<string, unknown>)?.signal ===
|
|
1575
|
+
"WORKER_DONE"),
|
|
1171
1576
|
);
|
|
1172
1577
|
|
|
1173
1578
|
if (agentCompletedStatus) {
|
|
1174
1579
|
return {
|
|
1175
1580
|
called: true,
|
|
1176
|
-
status: agentCompletedStatus.payload?.status_type as string
|
|
1581
|
+
status: agentCompletedStatus.payload?.status_type as string,
|
|
1177
1582
|
};
|
|
1178
1583
|
}
|
|
1179
1584
|
|
|
@@ -1213,7 +1618,8 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1213
1618
|
|
|
1214
1619
|
for (let i = 0; i < maxFollowUps; i++) {
|
|
1215
1620
|
followUpCount++;
|
|
1216
|
-
const followUpMessage =
|
|
1621
|
+
const followUpMessage =
|
|
1622
|
+
followUpMessages[Math.min(i, followUpMessages.length - 1)];
|
|
1217
1623
|
|
|
1218
1624
|
// Send follow-up prompt
|
|
1219
1625
|
for await (const update of prompt(agentId, followUpMessage)) {
|
|
@@ -1238,7 +1644,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1238
1644
|
if (throwOnMaxExceeded) {
|
|
1239
1645
|
throw new Error(
|
|
1240
1646
|
`Agent ${agentId} did not call done() after ${maxFollowUps} follow-up attempts. ` +
|
|
1241
|
-
|
|
1647
|
+
`Total prompts sent: ${1 + followUpCount}. Consider increasing maxFollowUps or investigating agent behavior.`,
|
|
1242
1648
|
);
|
|
1243
1649
|
}
|
|
1244
1650
|
|
|
@@ -1293,12 +1699,12 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1293
1699
|
function respondToPermission(
|
|
1294
1700
|
agentId: AgentId,
|
|
1295
1701
|
requestId: string,
|
|
1296
|
-
optionId: string
|
|
1702
|
+
optionId: string,
|
|
1297
1703
|
): boolean {
|
|
1298
1704
|
const activeSession = activeSessions.get(agentId);
|
|
1299
1705
|
if (!activeSession) {
|
|
1300
1706
|
console.warn(
|
|
1301
|
-
`[AgentManager] Cannot respond to permission: no active session for agent ${agentId}
|
|
1707
|
+
`[AgentManager] Cannot respond to permission: no active session for agent ${agentId}`,
|
|
1302
1708
|
);
|
|
1303
1709
|
return false;
|
|
1304
1710
|
}
|
|
@@ -1306,13 +1712,13 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1306
1712
|
try {
|
|
1307
1713
|
activeSession.session.respondToPermission(requestId, optionId);
|
|
1308
1714
|
console.log(
|
|
1309
|
-
`[AgentManager] Responded to permission ${requestId} for agent ${agentId} with ${optionId}
|
|
1715
|
+
`[AgentManager] Responded to permission ${requestId} for agent ${agentId} with ${optionId}`,
|
|
1310
1716
|
);
|
|
1311
1717
|
return true;
|
|
1312
1718
|
} catch (err) {
|
|
1313
1719
|
console.error(
|
|
1314
1720
|
`[AgentManager] Error responding to permission ${requestId}:`,
|
|
1315
|
-
err
|
|
1721
|
+
err,
|
|
1316
1722
|
);
|
|
1317
1723
|
return false;
|
|
1318
1724
|
}
|
|
@@ -1322,7 +1728,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1322
1728
|
const activeSession = activeSessions.get(agentId);
|
|
1323
1729
|
if (!activeSession) {
|
|
1324
1730
|
console.warn(
|
|
1325
|
-
`[AgentManager] Cannot cancel permission: no active session for agent ${agentId}
|
|
1731
|
+
`[AgentManager] Cannot cancel permission: no active session for agent ${agentId}`,
|
|
1326
1732
|
);
|
|
1327
1733
|
return false;
|
|
1328
1734
|
}
|
|
@@ -1330,18 +1736,50 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1330
1736
|
try {
|
|
1331
1737
|
activeSession.session.cancelPermission(requestId);
|
|
1332
1738
|
console.log(
|
|
1333
|
-
`[AgentManager] Cancelled permission ${requestId} for agent ${agentId}
|
|
1739
|
+
`[AgentManager] Cancelled permission ${requestId} for agent ${agentId}`,
|
|
1334
1740
|
);
|
|
1335
1741
|
return true;
|
|
1336
1742
|
} catch (err) {
|
|
1337
1743
|
console.error(
|
|
1338
1744
|
`[AgentManager] Error cancelling permission ${requestId}:`,
|
|
1339
|
-
err
|
|
1745
|
+
err,
|
|
1746
|
+
);
|
|
1747
|
+
return false;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function setPermissionMode(agentId: AgentId, mode: PermissionMode): boolean {
|
|
1752
|
+
const activeSession = activeSessions.get(agentId);
|
|
1753
|
+
if (!activeSession) {
|
|
1754
|
+
console.warn(
|
|
1755
|
+
`[AgentManager] Cannot set permission mode: no active session for agent ${agentId}`,
|
|
1756
|
+
);
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
try {
|
|
1761
|
+
activeSession.handle.setPermissionMode(mode);
|
|
1762
|
+
console.log(
|
|
1763
|
+
`[AgentManager] Set permission mode for agent ${agentId} to ${mode}`,
|
|
1764
|
+
);
|
|
1765
|
+
return true;
|
|
1766
|
+
} catch (err) {
|
|
1767
|
+
console.error(
|
|
1768
|
+
`[AgentManager] Error setting permission mode for agent ${agentId}:`,
|
|
1769
|
+
err,
|
|
1340
1770
|
);
|
|
1341
1771
|
return false;
|
|
1342
1772
|
}
|
|
1343
1773
|
}
|
|
1344
1774
|
|
|
1775
|
+
function getPermissionMode(agentId: AgentId): PermissionMode | null {
|
|
1776
|
+
const activeSession = activeSessions.get(agentId);
|
|
1777
|
+
if (!activeSession) {
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1780
|
+
return activeSession.handle.getPermissionMode();
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1345
1783
|
// ─────────────────────────────────────────────────────────────────
|
|
1346
1784
|
// Lifecycle Callbacks
|
|
1347
1785
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -1361,13 +1799,21 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1361
1799
|
}
|
|
1362
1800
|
}
|
|
1363
1801
|
|
|
1802
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1803
|
+
// OpenTasks Socket Path (Late Binding)
|
|
1804
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1805
|
+
|
|
1806
|
+
function setOpenTasksSocketPath(socketPath: string): void {
|
|
1807
|
+
configOpenTasksSocketPath = socketPath;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1364
1810
|
// ─────────────────────────────────────────────────────────────────
|
|
1365
1811
|
// Mail Services (Late Binding)
|
|
1366
1812
|
// ─────────────────────────────────────────────────────────────────
|
|
1367
1813
|
|
|
1368
1814
|
function setMailServices(
|
|
1369
1815
|
ms: import("../mail/mail-service.js").MailService,
|
|
1370
|
-
cm: import("../mail/conversation-map.js").ConversationMap
|
|
1816
|
+
cm: import("../mail/conversation-map.js").ConversationMap,
|
|
1371
1817
|
): void {
|
|
1372
1818
|
mailService = ms;
|
|
1373
1819
|
conversationMap = cm;
|
|
@@ -1378,6 +1824,9 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1378
1824
|
// ─────────────────────────────────────────────────────────────────
|
|
1379
1825
|
|
|
1380
1826
|
async function close(): Promise<void> {
|
|
1827
|
+
// Prevent new spawns/resumes from racing with cleanup
|
|
1828
|
+
isShuttingDown = true;
|
|
1829
|
+
|
|
1381
1830
|
// Stop all health checks
|
|
1382
1831
|
if (healthCheckService) {
|
|
1383
1832
|
healthCheckService.stopAll();
|
|
@@ -1393,7 +1842,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1393
1842
|
} catch {
|
|
1394
1843
|
// Ignore errors during cleanup
|
|
1395
1844
|
}
|
|
1396
|
-
})()
|
|
1845
|
+
})(),
|
|
1397
1846
|
);
|
|
1398
1847
|
}
|
|
1399
1848
|
|
|
@@ -1416,14 +1865,14 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1416
1865
|
*/
|
|
1417
1866
|
async function continueAgent(
|
|
1418
1867
|
agentId: AgentId,
|
|
1419
|
-
options?: ContinueAgentOptions
|
|
1868
|
+
options?: ContinueAgentOptions,
|
|
1420
1869
|
): Promise<SpawnedAgent> {
|
|
1421
1870
|
const agent = eventStore.getAgent(agentId);
|
|
1422
1871
|
if (!agent) {
|
|
1423
1872
|
throw new AgentManagerError(
|
|
1424
1873
|
`Agent not found: ${agentId}`,
|
|
1425
1874
|
"AGENT_NOT_FOUND",
|
|
1426
|
-
agentId
|
|
1875
|
+
agentId,
|
|
1427
1876
|
);
|
|
1428
1877
|
}
|
|
1429
1878
|
|
|
@@ -1443,7 +1892,9 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1443
1892
|
|
|
1444
1893
|
if (events.length > 0) {
|
|
1445
1894
|
contextLines.push("## Prior Session Context");
|
|
1446
|
-
contextLines.push(
|
|
1895
|
+
contextLines.push(
|
|
1896
|
+
`Continuing from agent ${agentId} (${events.length} events).`,
|
|
1897
|
+
);
|
|
1447
1898
|
for (const event of events.slice(-20)) {
|
|
1448
1899
|
const summary = event.payload?.summary;
|
|
1449
1900
|
if (summary && typeof summary === "string") {
|
|
@@ -1456,9 +1907,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1456
1907
|
|
|
1457
1908
|
// Spawn a continuation agent with same role, task, and context
|
|
1458
1909
|
const taskDescription =
|
|
1459
|
-
options?.task ??
|
|
1460
|
-
agent.task ??
|
|
1461
|
-
`Continue work from ${agentId}`;
|
|
1910
|
+
options?.task ?? agent.task ?? `Continue work from ${agentId}`;
|
|
1462
1911
|
|
|
1463
1912
|
const newAgent = await spawn({
|
|
1464
1913
|
task: taskDescription,
|
|
@@ -1487,6 +1936,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1487
1936
|
terminate,
|
|
1488
1937
|
resume,
|
|
1489
1938
|
continueAgent,
|
|
1939
|
+
forkAgent,
|
|
1490
1940
|
get,
|
|
1491
1941
|
list,
|
|
1492
1942
|
getChildren,
|
|
@@ -1502,9 +1952,12 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
|
|
|
1502
1952
|
isProcessRunning,
|
|
1503
1953
|
respondToPermission,
|
|
1504
1954
|
cancelPermission,
|
|
1955
|
+
setPermissionMode,
|
|
1956
|
+
getPermissionMode,
|
|
1505
1957
|
onLifecycleEvent,
|
|
1506
1958
|
setSpawnInterceptor,
|
|
1507
1959
|
getRoleRegistry,
|
|
1960
|
+
setOpenTasksSocketPath,
|
|
1508
1961
|
setMailServices,
|
|
1509
1962
|
close,
|
|
1510
1963
|
};
|
|
@@ -1534,7 +1987,7 @@ async function createWorkspaceForRole(
|
|
|
1534
1987
|
workspaceManager: WorkspaceManager,
|
|
1535
1988
|
agentId: AgentId,
|
|
1536
1989
|
role: string,
|
|
1537
|
-
options: CreateWorkspaceOptions
|
|
1990
|
+
options: CreateWorkspaceOptions,
|
|
1538
1991
|
): Promise<Workspace | undefined> {
|
|
1539
1992
|
const { streamId, streamConfig, dataplaneTaskId } = options;
|
|
1540
1993
|
|
|
@@ -1543,14 +1996,14 @@ async function createWorkspaceForRole(
|
|
|
1543
1996
|
// Coordinators create a new integration stream
|
|
1544
1997
|
if (!streamConfig) {
|
|
1545
1998
|
console.warn(
|
|
1546
|
-
`[AgentManager] Coordinator ${agentId} spawn missing streamConfig, skipping workspace
|
|
1999
|
+
`[AgentManager] Coordinator ${agentId} spawn missing streamConfig, skipping workspace`,
|
|
1547
2000
|
);
|
|
1548
2001
|
return undefined;
|
|
1549
2002
|
}
|
|
1550
2003
|
|
|
1551
2004
|
const newStreamId = workspaceManager.createIntegrationStream(
|
|
1552
2005
|
agentId,
|
|
1553
|
-
streamConfig
|
|
2006
|
+
streamConfig,
|
|
1554
2007
|
);
|
|
1555
2008
|
|
|
1556
2009
|
return workspaceManager.createCoordinatorWorkspace(agentId, newStreamId);
|
|
@@ -1560,7 +2013,7 @@ async function createWorkspaceForRole(
|
|
|
1560
2013
|
// Integrators join an existing stream
|
|
1561
2014
|
if (!streamId) {
|
|
1562
2015
|
console.warn(
|
|
1563
|
-
`[AgentManager] Integrator ${agentId} spawn missing streamId, skipping workspace
|
|
2016
|
+
`[AgentManager] Integrator ${agentId} spawn missing streamId, skipping workspace`,
|
|
1564
2017
|
);
|
|
1565
2018
|
return undefined;
|
|
1566
2019
|
}
|
|
@@ -1573,7 +2026,7 @@ async function createWorkspaceForRole(
|
|
|
1573
2026
|
// Workers need streamId and either dataplaneTaskId or create a new task
|
|
1574
2027
|
if (!streamId) {
|
|
1575
2028
|
console.warn(
|
|
1576
|
-
`[AgentManager] Worker ${agentId} spawn missing streamId, skipping workspace
|
|
2029
|
+
`[AgentManager] Worker ${agentId} spawn missing streamId, skipping workspace`,
|
|
1577
2030
|
);
|
|
1578
2031
|
return undefined;
|
|
1579
2032
|
}
|
|
@@ -1582,7 +2035,7 @@ async function createWorkspaceForRole(
|
|
|
1582
2035
|
const taskId = dataplaneTaskId;
|
|
1583
2036
|
if (!taskId) {
|
|
1584
2037
|
console.warn(
|
|
1585
|
-
`[AgentManager] Worker ${agentId} spawn missing dataplaneTaskId, skipping workspace
|
|
2038
|
+
`[AgentManager] Worker ${agentId} spawn missing dataplaneTaskId, skipping workspace`,
|
|
1586
2039
|
);
|
|
1587
2040
|
return undefined;
|
|
1588
2041
|
}
|