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
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Bridge Extensions (_macro/mcp/*)
|
|
3
|
+
*
|
|
4
|
+
* Server-side extension handlers that mirror each MCP tool.
|
|
5
|
+
* When MCP subprocesses run in thin-client mode, their tool calls
|
|
6
|
+
* are routed here via ephemeral MAP WebSocket connections.
|
|
7
|
+
*
|
|
8
|
+
* Each handler receives agent context via `params.context` since the
|
|
9
|
+
* MAP ExtensionContext only has participant info, not agent identity.
|
|
10
|
+
*/
|
|
11
|
+
import { RPCError } from "../rpc-handler.js";
|
|
12
|
+
import { ulid } from "ulid";
|
|
13
|
+
import { createDoneHandler } from "../../../mcp/tools/done.js";
|
|
14
|
+
import { createInjectContextHandler } from "../../../mcp/tools/inject_context.js";
|
|
15
|
+
import { createWaitForActivityHandler, } from "../../../mcp/tools/wait_for_activity.js";
|
|
16
|
+
import { createClaimTaskHandler } from "../../../mcp/tools/claim_task.js";
|
|
17
|
+
import { createUnclaimTaskHandler } from "../../../mcp/tools/unclaim_task.js";
|
|
18
|
+
import { createListClaimableTasksHandler } from "../../../mcp/tools/list_claimable_tasks.js";
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Agent Token Validation (module-level, set by registerMCPBridgeExtensions)
|
|
21
|
+
// =============================================================================
|
|
22
|
+
let _agentTokenManager;
|
|
23
|
+
/**
|
|
24
|
+
* Extract and validate agent context from params.
|
|
25
|
+
*/
|
|
26
|
+
function extractContext(params) {
|
|
27
|
+
const p = (params ?? {});
|
|
28
|
+
const context = p.context;
|
|
29
|
+
if (!context?.agent_id) {
|
|
30
|
+
throw RPCError.invalidParams("params.context.agent_id is required");
|
|
31
|
+
}
|
|
32
|
+
// Validate per-agent token if token manager is configured
|
|
33
|
+
if (_agentTokenManager) {
|
|
34
|
+
if (!context.agent_token || !_agentTokenManager.verifyToken(context.agent_id, context.agent_token)) {
|
|
35
|
+
throw RPCError.invalidParams("Invalid or missing agent token");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Return everything except context as args
|
|
39
|
+
const { context: _, ...args } = p;
|
|
40
|
+
return { context, args };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build a ToolContext from the agent context.
|
|
44
|
+
*/
|
|
45
|
+
function toToolContext(ctx) {
|
|
46
|
+
return {
|
|
47
|
+
agent_id: ctx.agent_id,
|
|
48
|
+
session_id: ctx.session_id,
|
|
49
|
+
task_id: ctx.task_id,
|
|
50
|
+
lineage: ctx.lineage ?? [],
|
|
51
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Handler Implementations
|
|
56
|
+
// =============================================================================
|
|
57
|
+
function createSpawnAgentBridge(services, emitMAPEvent) {
|
|
58
|
+
return async (_extCtx, params) => {
|
|
59
|
+
const { context, args } = extractContext(params);
|
|
60
|
+
const task = args.task;
|
|
61
|
+
const spawned = await services.agentManager.spawn({
|
|
62
|
+
task,
|
|
63
|
+
parent: context.agent_id,
|
|
64
|
+
subscribeParent: args.subscribe_parent ?? true,
|
|
65
|
+
topics: args.topics ?? [],
|
|
66
|
+
config: args.config,
|
|
67
|
+
cwd: args.cwd ?? context.cwd,
|
|
68
|
+
permissionMode: args.permission_mode,
|
|
69
|
+
});
|
|
70
|
+
// Fire-and-forget initial prompt so the agent starts working on its task.
|
|
71
|
+
// Without this, the agent process is running but idle — waiting for a message.
|
|
72
|
+
if (task) {
|
|
73
|
+
(async () => {
|
|
74
|
+
// Accumulate assistant response parts for history persistence
|
|
75
|
+
const buffer = { parts: [] };
|
|
76
|
+
const toolInfoCache = new Map();
|
|
77
|
+
// Emit user message event so TUI clients can show the task prompt
|
|
78
|
+
emitMAPEvent({
|
|
79
|
+
eventId: ulid(),
|
|
80
|
+
type: "session_user_message",
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
agentId: spawned.id,
|
|
83
|
+
data: {
|
|
84
|
+
agentId: spawned.id,
|
|
85
|
+
sessionId: spawned.session_id,
|
|
86
|
+
content: task,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
for await (const update of services.agentManager.prompt(spawned.id, task)) {
|
|
91
|
+
// Accumulate content for turn recording (mirrors acp-over-map.ts handlePrompt)
|
|
92
|
+
const u = update;
|
|
93
|
+
const updateType = u.sessionUpdate ?? u.type;
|
|
94
|
+
if (updateType === "agent_message_chunk") {
|
|
95
|
+
const content = u.content;
|
|
96
|
+
if (content?.text) {
|
|
97
|
+
const last = buffer.parts[buffer.parts.length - 1];
|
|
98
|
+
if (last && last.type === "text") {
|
|
99
|
+
last.text += content.text;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
buffer.parts.push({ type: "text", text: content.text });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (updateType === "tool_call" || updateType === "tool_call_update") {
|
|
107
|
+
const toolCallId = u.toolCallId;
|
|
108
|
+
const status = u.status;
|
|
109
|
+
const meta = u._meta;
|
|
110
|
+
if (updateType === "tool_call" && toolCallId) {
|
|
111
|
+
toolInfoCache.set(toolCallId, {
|
|
112
|
+
title: u.title,
|
|
113
|
+
name: meta?.claudeCode?.toolName,
|
|
114
|
+
input: u.rawInput,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (status === "completed" || status === "failed") {
|
|
118
|
+
const cached = toolCallId ? toolInfoCache.get(toolCallId) : undefined;
|
|
119
|
+
const rawOutput = u.rawOutput;
|
|
120
|
+
let output;
|
|
121
|
+
if (typeof rawOutput === "string")
|
|
122
|
+
output = rawOutput;
|
|
123
|
+
else if (Array.isArray(rawOutput)) {
|
|
124
|
+
output = rawOutput
|
|
125
|
+
.filter((item) => item.type === "text" && typeof item.text === "string")
|
|
126
|
+
.map((item) => item.text)
|
|
127
|
+
.join("\n") || undefined;
|
|
128
|
+
}
|
|
129
|
+
buffer.parts.push({
|
|
130
|
+
type: "tool",
|
|
131
|
+
toolCallId,
|
|
132
|
+
title: u.title ?? cached?.title,
|
|
133
|
+
name: meta?.claudeCode?.toolName ?? cached?.name,
|
|
134
|
+
status: u.status,
|
|
135
|
+
input: u.rawInput ?? cached?.input,
|
|
136
|
+
output,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Emit each session update as a MAP event for live streaming
|
|
141
|
+
emitMAPEvent({
|
|
142
|
+
eventId: ulid(),
|
|
143
|
+
type: "session_update",
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
agentId: spawned.id,
|
|
146
|
+
data: {
|
|
147
|
+
agentId: spawned.id,
|
|
148
|
+
sessionId: spawned.session_id,
|
|
149
|
+
update,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Emit prompt done event
|
|
154
|
+
emitMAPEvent({
|
|
155
|
+
eventId: ulid(),
|
|
156
|
+
type: "session_prompt_done",
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
agentId: spawned.id,
|
|
159
|
+
data: {
|
|
160
|
+
agentId: spawned.id,
|
|
161
|
+
sessionId: spawned.session_id,
|
|
162
|
+
stopReason: "end_turn",
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// Record turns so history is available when TUI reconnects
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
const conversationId = spawned.session_id;
|
|
168
|
+
// Record user turn (the initial task prompt)
|
|
169
|
+
services.eventStore.emit({
|
|
170
|
+
type: "turn",
|
|
171
|
+
source: { agent_id: spawned.id },
|
|
172
|
+
payload: {
|
|
173
|
+
action: "recorded",
|
|
174
|
+
turn_id: `turn_user_${now}_${Math.random().toString(36).slice(2, 8)}`,
|
|
175
|
+
conversation_id: conversationId,
|
|
176
|
+
participant: "user",
|
|
177
|
+
timestamp: now,
|
|
178
|
+
content_type: "user_prompt",
|
|
179
|
+
content: task,
|
|
180
|
+
source_type: "explicit",
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
// Record assistant turn with accumulated content
|
|
184
|
+
if (buffer.parts.length > 0) {
|
|
185
|
+
services.eventStore.emit({
|
|
186
|
+
type: "turn",
|
|
187
|
+
source: { agent_id: spawned.id },
|
|
188
|
+
payload: {
|
|
189
|
+
action: "recorded",
|
|
190
|
+
turn_id: `turn_asst_${now}_${Math.random().toString(36).slice(2, 8)}`,
|
|
191
|
+
conversation_id: conversationId,
|
|
192
|
+
participant: spawned.id,
|
|
193
|
+
timestamp: now + 1,
|
|
194
|
+
content_type: "assistant_response",
|
|
195
|
+
content: { parts: buffer.parts },
|
|
196
|
+
source_type: "explicit",
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
// Emit prompt done with error so TUI stops spinner
|
|
203
|
+
emitMAPEvent({
|
|
204
|
+
eventId: ulid(),
|
|
205
|
+
type: "session_prompt_done",
|
|
206
|
+
timestamp: Date.now(),
|
|
207
|
+
agentId: spawned.id,
|
|
208
|
+
data: {
|
|
209
|
+
agentId: spawned.id,
|
|
210
|
+
sessionId: spawned.session_id,
|
|
211
|
+
stopReason: "error",
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
console.error(`[MCP Bridge] Failed to send initial prompt to spawned agent ${spawned.id}:`, err);
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
agent_id: spawned.id,
|
|
220
|
+
task_id: spawned.agent.task_id,
|
|
221
|
+
session_id: spawned.session_id,
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function createEmitStatusBridge(services, emitMAPEvent) {
|
|
226
|
+
return async (_extCtx, params) => {
|
|
227
|
+
const { context, args } = extractContext(params);
|
|
228
|
+
const event = services.eventStore.emit({
|
|
229
|
+
type: "status",
|
|
230
|
+
source: { agent_id: context.agent_id },
|
|
231
|
+
payload: {
|
|
232
|
+
status_type: args.status_type,
|
|
233
|
+
summary: args.summary,
|
|
234
|
+
details: args.details,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
// Emit MAP event so TUI subscribers see the status update
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
emitMAPEvent({
|
|
240
|
+
eventId: ulid(),
|
|
241
|
+
type: "status_emitted",
|
|
242
|
+
timestamp: now,
|
|
243
|
+
agentId: context.agent_id,
|
|
244
|
+
data: {
|
|
245
|
+
id: event.id,
|
|
246
|
+
agentId: context.agent_id,
|
|
247
|
+
taskId: context.task_id,
|
|
248
|
+
statusType: args.status_type,
|
|
249
|
+
summary: args.summary,
|
|
250
|
+
details: args.details,
|
|
251
|
+
timestamp: now,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
let taskUpdated = false;
|
|
255
|
+
if (args.complete_task && args.status_type === "completed" && context.task_id) {
|
|
256
|
+
try {
|
|
257
|
+
services.taskManager.updateStatus(context.task_id, "completed");
|
|
258
|
+
taskUpdated = true;
|
|
259
|
+
}
|
|
260
|
+
catch { /* Task may not exist */ }
|
|
261
|
+
}
|
|
262
|
+
if (args.complete_task && args.status_type === "failed" && context.task_id) {
|
|
263
|
+
try {
|
|
264
|
+
services.taskManager.updateStatus(context.task_id, "failed");
|
|
265
|
+
taskUpdated = true;
|
|
266
|
+
}
|
|
267
|
+
catch { /* Task may not exist */ }
|
|
268
|
+
}
|
|
269
|
+
return { event_id: event.id, task_updated: taskUpdated };
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function createSendMessageBridge(services) {
|
|
273
|
+
return async (_extCtx, params) => {
|
|
274
|
+
const { context, args } = extractContext(params);
|
|
275
|
+
const to = args.to;
|
|
276
|
+
if (!to) {
|
|
277
|
+
throw RPCError.invalidParams("params.to is required");
|
|
278
|
+
}
|
|
279
|
+
const address = to.agent_id
|
|
280
|
+
? { agent: to.agent_id }
|
|
281
|
+
: to.task_id
|
|
282
|
+
? { task: to.task_id }
|
|
283
|
+
: to.topic
|
|
284
|
+
? { scope: to.topic }
|
|
285
|
+
: null;
|
|
286
|
+
if (!address) {
|
|
287
|
+
throw RPCError.invalidParams("Must specify one of: agent_id, task_id, or topic");
|
|
288
|
+
}
|
|
289
|
+
const result = await services.messageRouter.sendToAddress({
|
|
290
|
+
from: context.agent_id,
|
|
291
|
+
to: address,
|
|
292
|
+
content: args.content,
|
|
293
|
+
options: args.correlation_id ? { correlationId: args.correlation_id } : undefined,
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
message_id: result.id,
|
|
297
|
+
delivered_to: result.delivered.length,
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function createCheckMessagesBridge(services) {
|
|
302
|
+
return async (_extCtx, params) => {
|
|
303
|
+
const { context, args } = extractContext(params);
|
|
304
|
+
const limit = args.limit ?? 10;
|
|
305
|
+
const internalMessages = services.messageRouter.getMessages(context.agent_id, {
|
|
306
|
+
limit,
|
|
307
|
+
includeAcknowledged: args.include_acknowledged ?? false,
|
|
308
|
+
});
|
|
309
|
+
const formattedInternalMessages = internalMessages.map((msg) => ({
|
|
310
|
+
id: msg.id,
|
|
311
|
+
from: `agent:${msg.from.agent_id}`,
|
|
312
|
+
content: msg.content.length > 500 ? msg.content.substring(0, 500) : msg.content,
|
|
313
|
+
timestamp: msg.timestamp,
|
|
314
|
+
truncated: msg.truncated || msg.content.length > 500,
|
|
315
|
+
correlation_id: msg.correlation_id,
|
|
316
|
+
}));
|
|
317
|
+
// Get peer messages if peerManager is available
|
|
318
|
+
let formattedPeerMessages = [];
|
|
319
|
+
if (services.peerManager) {
|
|
320
|
+
const peerMessages = services.peerManager.getPeerMessages(context.agent_id);
|
|
321
|
+
formattedPeerMessages = peerMessages.map((msg) => ({
|
|
322
|
+
id: msg.id,
|
|
323
|
+
from: msg.from,
|
|
324
|
+
content: typeof msg.payload === "string"
|
|
325
|
+
? msg.payload.length > 500 ? msg.payload.substring(0, 500) : msg.payload
|
|
326
|
+
: JSON.stringify(msg.payload).substring(0, 500),
|
|
327
|
+
timestamp: msg.timestamp,
|
|
328
|
+
truncated: typeof msg.payload === "string" ? msg.payload.length > 500 : false,
|
|
329
|
+
correlation_id: msg.correlationId,
|
|
330
|
+
is_request: msg.isRequest,
|
|
331
|
+
request_id: msg.requestId,
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
const allFormattedMessages = [...formattedInternalMessages, ...formattedPeerMessages]
|
|
335
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
336
|
+
.slice(0, limit);
|
|
337
|
+
const allInternalMessages = services.messageRouter.getMessages(context.agent_id, {
|
|
338
|
+
limit: 1000,
|
|
339
|
+
includeAcknowledged: false,
|
|
340
|
+
});
|
|
341
|
+
const allPeerMessages = services.peerManager ? services.peerManager.getPeerMessages(context.agent_id) : [];
|
|
342
|
+
return {
|
|
343
|
+
messages: allFormattedMessages,
|
|
344
|
+
total_pending: allInternalMessages.length + allPeerMessages.length,
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function createQueryIndexBridge(services) {
|
|
349
|
+
return async (_extCtx, params) => {
|
|
350
|
+
const { context: _context, args } = extractContext(params);
|
|
351
|
+
const entries = [];
|
|
352
|
+
const limit = args.limit ?? 20;
|
|
353
|
+
const offset = args.offset ?? 0;
|
|
354
|
+
const search = args.search?.toLowerCase();
|
|
355
|
+
const filter = args.filter;
|
|
356
|
+
const queryType = args.type;
|
|
357
|
+
if (queryType === "agents" || queryType === "all") {
|
|
358
|
+
let agents = services.agentManager.list();
|
|
359
|
+
if (filter?.state) {
|
|
360
|
+
agents = agents.filter((a) => a.state === filter.state);
|
|
361
|
+
}
|
|
362
|
+
if (filter?.parent !== undefined) {
|
|
363
|
+
agents = agents.filter((a) => a.parent === filter.parent);
|
|
364
|
+
}
|
|
365
|
+
if (search) {
|
|
366
|
+
agents = agents.filter((a) => a.id.toLowerCase().includes(search) ||
|
|
367
|
+
a.task?.toLowerCase().includes(search));
|
|
368
|
+
}
|
|
369
|
+
for (const agent of agents) {
|
|
370
|
+
entries.push({
|
|
371
|
+
type: "agent",
|
|
372
|
+
id: agent.id,
|
|
373
|
+
summary: agent.task ?? "No task description",
|
|
374
|
+
state: agent.state,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (queryType === "tasks" || queryType === "all") {
|
|
379
|
+
let tasks = services.taskManager.list();
|
|
380
|
+
if (filter?.status) {
|
|
381
|
+
tasks = tasks.filter((t) => t.status === filter.status);
|
|
382
|
+
}
|
|
383
|
+
if (search) {
|
|
384
|
+
tasks = tasks.filter((t) => t.id.toLowerCase().includes(search) ||
|
|
385
|
+
t.description.toLowerCase().includes(search));
|
|
386
|
+
}
|
|
387
|
+
for (const task of tasks) {
|
|
388
|
+
entries.push({
|
|
389
|
+
type: "task",
|
|
390
|
+
id: task.id,
|
|
391
|
+
summary: task.description,
|
|
392
|
+
status: task.status,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const paginatedEntries = entries.slice(offset, offset + limit);
|
|
397
|
+
return {
|
|
398
|
+
entries: paginatedEntries,
|
|
399
|
+
total: entries.length,
|
|
400
|
+
has_more: offset + limit < entries.length,
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function createGetHierarchyBridge(services) {
|
|
405
|
+
return async (_extCtx, params) => {
|
|
406
|
+
const { context, args } = extractContext(params);
|
|
407
|
+
let rootId;
|
|
408
|
+
if (args.root) {
|
|
409
|
+
rootId = args.root;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
rootId = context.lineage.length > 0 ? context.lineage[0] : context.agent_id;
|
|
413
|
+
}
|
|
414
|
+
const hierarchy = services.agentManager.getHierarchy(rootId);
|
|
415
|
+
if (!hierarchy) {
|
|
416
|
+
throw RPCError.notFound("agent", rootId);
|
|
417
|
+
}
|
|
418
|
+
function buildNode(node, currentDepth) {
|
|
419
|
+
const shouldIncludeChildren = args.depth === undefined || currentDepth < args.depth;
|
|
420
|
+
return {
|
|
421
|
+
agent_id: node.agent.id,
|
|
422
|
+
task: node.agent.task ?? "No task",
|
|
423
|
+
state: node.agent.state,
|
|
424
|
+
children: shouldIncludeChildren
|
|
425
|
+
? node.children.map((c) => buildNode(c, currentDepth + 1))
|
|
426
|
+
: [],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const tree = buildNode(hierarchy.root, 1);
|
|
430
|
+
return {
|
|
431
|
+
tree,
|
|
432
|
+
depth: hierarchy.depth,
|
|
433
|
+
total_agents: hierarchy.totalAgents,
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function createGetAgentSummaryBridge(services) {
|
|
438
|
+
return async (_extCtx, params) => {
|
|
439
|
+
const { context: _context, args } = extractContext(params);
|
|
440
|
+
const agentId = args.agent_id;
|
|
441
|
+
const agent = services.agentManager.get(agentId);
|
|
442
|
+
if (!agent) {
|
|
443
|
+
throw RPCError.notFound("agent", agentId);
|
|
444
|
+
}
|
|
445
|
+
const children = services.agentManager.getChildren(agentId);
|
|
446
|
+
const statusEvents = services.eventStore.query({
|
|
447
|
+
type: "status",
|
|
448
|
+
source_agent_id: agentId,
|
|
449
|
+
limit: 1,
|
|
450
|
+
});
|
|
451
|
+
const recentStatus = statusEvents.length > 0
|
|
452
|
+
? {
|
|
453
|
+
type: statusEvents[0].payload.status_type,
|
|
454
|
+
summary: statusEvents[0].payload.summary,
|
|
455
|
+
timestamp: statusEvents[0].timestamp,
|
|
456
|
+
}
|
|
457
|
+
: undefined;
|
|
458
|
+
const lastActivity = agent.stopped_at ?? agent.started_at ?? agent.created_at;
|
|
459
|
+
return {
|
|
460
|
+
id: agent.id,
|
|
461
|
+
session_id: agent.session_id,
|
|
462
|
+
task: agent.task ?? "No task",
|
|
463
|
+
state: agent.state,
|
|
464
|
+
parent: agent.parent,
|
|
465
|
+
children_count: children.length,
|
|
466
|
+
last_activity: lastActivity,
|
|
467
|
+
recent_status: recentStatus,
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function createStopAgentBridge(services) {
|
|
472
|
+
return async (_extCtx, params) => {
|
|
473
|
+
const { context, args } = extractContext(params);
|
|
474
|
+
const targetAgentId = args.agent_id;
|
|
475
|
+
const reason = (args.reason ?? "cancelled");
|
|
476
|
+
const targetAgent = services.agentManager.get(targetAgentId);
|
|
477
|
+
if (!targetAgent) {
|
|
478
|
+
throw RPCError.notFound("agent", targetAgentId);
|
|
479
|
+
}
|
|
480
|
+
// Check subtree authorization
|
|
481
|
+
const isInSubtree = targetAgentId === context.agent_id ||
|
|
482
|
+
targetAgent.lineage?.includes(context.agent_id);
|
|
483
|
+
if (!isInSubtree) {
|
|
484
|
+
throw RPCError.permissionDenied(`Cannot stop agent outside your subtree: ${targetAgentId}`);
|
|
485
|
+
}
|
|
486
|
+
const stoppedAgents = [];
|
|
487
|
+
async function stopRecursive(agentId) {
|
|
488
|
+
const agent = services.agentManager.get(agentId);
|
|
489
|
+
if (!agent || agent.state === "stopped")
|
|
490
|
+
return;
|
|
491
|
+
const children = services.agentManager.getChildren(agentId);
|
|
492
|
+
for (const child of children) {
|
|
493
|
+
await stopRecursive(child.id);
|
|
494
|
+
}
|
|
495
|
+
await services.agentManager.terminate(agentId, reason);
|
|
496
|
+
stoppedAgents.push(agentId);
|
|
497
|
+
}
|
|
498
|
+
await stopRecursive(targetAgentId);
|
|
499
|
+
return {
|
|
500
|
+
success: true,
|
|
501
|
+
stopped_agents: stoppedAgents,
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function createDoneBridge(services) {
|
|
506
|
+
return async (_extCtx, params) => {
|
|
507
|
+
const { context, args } = extractContext(params);
|
|
508
|
+
const toolContext = toToolContext(context);
|
|
509
|
+
const doneDeps = {
|
|
510
|
+
eventStore: services.eventStore,
|
|
511
|
+
agentManager: services.agentManager,
|
|
512
|
+
messageRouter: services.messageRouter,
|
|
513
|
+
taskManager: services.taskManager,
|
|
514
|
+
roleRegistry: services.roleRegistry,
|
|
515
|
+
integrationStrategy: services.integrationStrategy,
|
|
516
|
+
};
|
|
517
|
+
const doneHandler = createDoneHandler(toolContext, doneDeps);
|
|
518
|
+
const result = await doneHandler(args);
|
|
519
|
+
// Handle termination if needed
|
|
520
|
+
if (result.shouldTerminate) {
|
|
521
|
+
setImmediate(async () => {
|
|
522
|
+
try {
|
|
523
|
+
await services.agentManager.terminate(context.agent_id, "completed");
|
|
524
|
+
}
|
|
525
|
+
catch { /* ignore */ }
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return result;
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function createInjectContextBridge(services) {
|
|
532
|
+
return async (_extCtx, params) => {
|
|
533
|
+
const { context, args } = extractContext(params);
|
|
534
|
+
const handler = createInjectContextHandler({ agentManager: services.agentManager, messageRouter: services.messageRouter }, context.agent_id);
|
|
535
|
+
return handler(args);
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function createWaitForActivityBridge(services) {
|
|
539
|
+
return async (_extCtx, params) => {
|
|
540
|
+
const { context, args } = extractContext(params);
|
|
541
|
+
if (!services.activityWatcher) {
|
|
542
|
+
throw RPCError.internalError("ActivityWatcher not available");
|
|
543
|
+
}
|
|
544
|
+
const toolContext = toToolContext(context);
|
|
545
|
+
const handler = createWaitForActivityHandler(toolContext, {
|
|
546
|
+
activityWatcher: services.activityWatcher,
|
|
547
|
+
});
|
|
548
|
+
return handler(args);
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function createClaimTaskBridge(services) {
|
|
552
|
+
return async (_extCtx, params) => {
|
|
553
|
+
const { context, args } = extractContext(params);
|
|
554
|
+
if (!services.taskBackend) {
|
|
555
|
+
throw RPCError.internalError("Task backend not available");
|
|
556
|
+
}
|
|
557
|
+
const toolContext = toToolContext(context);
|
|
558
|
+
const handler = createClaimTaskHandler(toolContext, {
|
|
559
|
+
taskBackend: services.taskBackend,
|
|
560
|
+
});
|
|
561
|
+
return handler(args);
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function createUnclaimTaskBridge(services) {
|
|
565
|
+
return async (_extCtx, params) => {
|
|
566
|
+
const { context, args } = extractContext(params);
|
|
567
|
+
if (!services.taskBackend) {
|
|
568
|
+
throw RPCError.internalError("Task backend not available");
|
|
569
|
+
}
|
|
570
|
+
const toolContext = toToolContext(context);
|
|
571
|
+
const handler = createUnclaimTaskHandler(toolContext, {
|
|
572
|
+
taskBackend: services.taskBackend,
|
|
573
|
+
});
|
|
574
|
+
return handler(args);
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function createListClaimableTasksBridge(services) {
|
|
578
|
+
return async (_extCtx, params) => {
|
|
579
|
+
const { context, args } = extractContext(params);
|
|
580
|
+
if (!services.taskBackend) {
|
|
581
|
+
throw RPCError.internalError("Task backend not available");
|
|
582
|
+
}
|
|
583
|
+
const toolContext = toToolContext(context);
|
|
584
|
+
const handler = createListClaimableTasksHandler(toolContext, {
|
|
585
|
+
taskBackend: services.taskBackend,
|
|
586
|
+
});
|
|
587
|
+
return handler(args);
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
// Peer communication bridges
|
|
591
|
+
function createSendPeerMessageBridge(services) {
|
|
592
|
+
return async (_extCtx, params) => {
|
|
593
|
+
const { context, args } = extractContext(params);
|
|
594
|
+
if (!services.peerManager || !services.peerManager.hasTransport()) {
|
|
595
|
+
throw RPCError.internalError("Peer communication not available");
|
|
596
|
+
}
|
|
597
|
+
await services.peerManager.sendMessage(context.agent_id, args.to, {
|
|
598
|
+
type: args.type,
|
|
599
|
+
payload: args.payload,
|
|
600
|
+
metadata: args.correlation_id ? { correlationId: args.correlation_id } : undefined,
|
|
601
|
+
});
|
|
602
|
+
return { success: true, timestamp: Date.now() };
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function createSendPeerRequestBridge(services) {
|
|
606
|
+
return async (_extCtx, params) => {
|
|
607
|
+
const { context, args } = extractContext(params);
|
|
608
|
+
if (!services.peerManager || !services.peerManager.hasTransport()) {
|
|
609
|
+
throw RPCError.internalError("Peer communication not available");
|
|
610
|
+
}
|
|
611
|
+
return services.peerManager.sendRequest(context.agent_id, args.to, {
|
|
612
|
+
method: args.method,
|
|
613
|
+
params: args.params,
|
|
614
|
+
timeout: args.timeout,
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
function createRespondToPeerRequestBridge(services) {
|
|
619
|
+
return async (_extCtx, params) => {
|
|
620
|
+
const { context, args } = extractContext(params);
|
|
621
|
+
if (!services.peerManager) {
|
|
622
|
+
throw RPCError.internalError("Peer communication not available");
|
|
623
|
+
}
|
|
624
|
+
services.peerManager.respondToRequest(context.agent_id, args.request_id, {
|
|
625
|
+
result: args.result,
|
|
626
|
+
error: args.error,
|
|
627
|
+
});
|
|
628
|
+
return { success: true };
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
// =============================================================================
|
|
632
|
+
// Dynamic Task Tool Bridge
|
|
633
|
+
// =============================================================================
|
|
634
|
+
/**
|
|
635
|
+
* Creates a bridge handler for a dynamic task tool from the TaskToolProvider.
|
|
636
|
+
* The handler extracts agent context from params and delegates to the tool's
|
|
637
|
+
* handler. The TaskToolProvider's getContext() is backed by a mutable holder
|
|
638
|
+
* that we update here before each call (safe since Node.js is single-threaded).
|
|
639
|
+
*/
|
|
640
|
+
function createTaskToolBridge(tool, contextHolder) {
|
|
641
|
+
return async (_extCtx, params) => {
|
|
642
|
+
const { context, args } = extractContext(params);
|
|
643
|
+
// Set the calling agent's ID so the tool provider's getContext() returns it
|
|
644
|
+
contextHolder.agent_id = context.agent_id;
|
|
645
|
+
// The tool handler receives the params directly (or unwrapped from the
|
|
646
|
+
// `params` envelope that the thin-client MCP server wraps them in)
|
|
647
|
+
const toolParams = args.params ?? args;
|
|
648
|
+
// The tool handler now calls backend.syncExternalTransition() internally
|
|
649
|
+
// after successful opentasks transitions, which updates the EventStore
|
|
650
|
+
// and triggers MAP events via onTaskChange.
|
|
651
|
+
return await tool.handler(toolParams);
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
// =============================================================================
|
|
655
|
+
// Registration
|
|
656
|
+
// =============================================================================
|
|
657
|
+
/**
|
|
658
|
+
* All MCP bridge extension method names.
|
|
659
|
+
*/
|
|
660
|
+
export const MCP_BRIDGE_METHODS = [
|
|
661
|
+
"_macro/mcp/spawn_agent",
|
|
662
|
+
"_macro/mcp/emit_status",
|
|
663
|
+
"_macro/mcp/send_message",
|
|
664
|
+
"_macro/mcp/check_messages",
|
|
665
|
+
"_macro/mcp/query_index",
|
|
666
|
+
"_macro/mcp/get_hierarchy",
|
|
667
|
+
"_macro/mcp/get_agent_summary",
|
|
668
|
+
"_macro/mcp/stop_agent",
|
|
669
|
+
"_macro/mcp/done",
|
|
670
|
+
"_macro/mcp/inject_context",
|
|
671
|
+
"_macro/mcp/wait_for_activity",
|
|
672
|
+
"_macro/mcp/claim_task",
|
|
673
|
+
"_macro/mcp/unclaim_task",
|
|
674
|
+
"_macro/mcp/list_claimable_tasks",
|
|
675
|
+
"_macro/mcp/send_peer_message",
|
|
676
|
+
"_macro/mcp/send_peer_request",
|
|
677
|
+
"_macro/mcp/respond_to_peer_request",
|
|
678
|
+
"_macro/mcp/task_tools_list",
|
|
679
|
+
];
|
|
680
|
+
/**
|
|
681
|
+
* Register all MCP bridge extension methods with the MAP adapter.
|
|
682
|
+
*
|
|
683
|
+
* These handlers mirror each MCP tool, allowing thin-client MCP subprocesses
|
|
684
|
+
* to execute tool logic on the main server via ephemeral WebSocket calls.
|
|
685
|
+
*/
|
|
686
|
+
export function registerMCPBridgeExtensions(adapter, services) {
|
|
687
|
+
// Set module-level agent token manager for extractContext() validation
|
|
688
|
+
_agentTokenManager = services.agentTokenManager;
|
|
689
|
+
const emitMAPEvent = (event) => adapter.emitEvent(event);
|
|
690
|
+
adapter.registerExtension("_macro/mcp/spawn_agent", createSpawnAgentBridge(services, emitMAPEvent));
|
|
691
|
+
adapter.registerExtension("_macro/mcp/emit_status", createEmitStatusBridge(services, emitMAPEvent));
|
|
692
|
+
adapter.registerExtension("_macro/mcp/send_message", createSendMessageBridge(services));
|
|
693
|
+
adapter.registerExtension("_macro/mcp/check_messages", createCheckMessagesBridge(services));
|
|
694
|
+
adapter.registerExtension("_macro/mcp/query_index", createQueryIndexBridge(services));
|
|
695
|
+
adapter.registerExtension("_macro/mcp/get_hierarchy", createGetHierarchyBridge(services));
|
|
696
|
+
adapter.registerExtension("_macro/mcp/get_agent_summary", createGetAgentSummaryBridge(services));
|
|
697
|
+
adapter.registerExtension("_macro/mcp/stop_agent", createStopAgentBridge(services));
|
|
698
|
+
adapter.registerExtension("_macro/mcp/done", createDoneBridge(services));
|
|
699
|
+
adapter.registerExtension("_macro/mcp/inject_context", createInjectContextBridge(services));
|
|
700
|
+
if (services.activityWatcher) {
|
|
701
|
+
adapter.registerExtension("_macro/mcp/wait_for_activity", createWaitForActivityBridge(services));
|
|
702
|
+
}
|
|
703
|
+
if (services.taskBackend) {
|
|
704
|
+
adapter.registerExtension("_macro/mcp/claim_task", createClaimTaskBridge(services));
|
|
705
|
+
adapter.registerExtension("_macro/mcp/unclaim_task", createUnclaimTaskBridge(services));
|
|
706
|
+
adapter.registerExtension("_macro/mcp/list_claimable_tasks", createListClaimableTasksBridge(services));
|
|
707
|
+
}
|
|
708
|
+
if (services.peerManager) {
|
|
709
|
+
adapter.registerExtension("_macro/mcp/send_peer_message", createSendPeerMessageBridge(services));
|
|
710
|
+
adapter.registerExtension("_macro/mcp/send_peer_request", createSendPeerRequestBridge(services));
|
|
711
|
+
adapter.registerExtension("_macro/mcp/respond_to_peer_request", createRespondToPeerRequestBridge(services));
|
|
712
|
+
}
|
|
713
|
+
// Register dynamic task tool bridges from the TaskToolProvider
|
|
714
|
+
if (services.taskToolProvider) {
|
|
715
|
+
const contextHolder = services.taskToolContext ?? { agent_id: "" };
|
|
716
|
+
const tools = services.taskToolProvider.getTools();
|
|
717
|
+
for (const tool of tools) {
|
|
718
|
+
const method = `_macro/mcp/task_tool/${tool.name}`;
|
|
719
|
+
adapter.registerExtension(method, createTaskToolBridge(tool, contextHolder));
|
|
720
|
+
}
|
|
721
|
+
// Discovery endpoint: returns list of available task tool names so
|
|
722
|
+
// thin-client MCP subprocesses only register tools the server supports
|
|
723
|
+
adapter.registerExtension("_macro/mcp/task_tools_list", async () => {
|
|
724
|
+
return { tools: tools.map((t) => ({ name: t.name, description: t.description })) };
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
// No task tools available — return empty list
|
|
729
|
+
adapter.registerExtension("_macro/mcp/task_tools_list", async () => {
|
|
730
|
+
return { tools: [] };
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Unregister all MCP bridge extension methods.
|
|
736
|
+
*/
|
|
737
|
+
export function unregisterMCPBridgeExtensions(adapter) {
|
|
738
|
+
for (const method of MCP_BRIDGE_METHODS) {
|
|
739
|
+
try {
|
|
740
|
+
adapter.unregisterExtension(method);
|
|
741
|
+
}
|
|
742
|
+
catch { /* ignore */ }
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
//# sourceMappingURL=mcp-bridge.js.map
|