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
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Client Event Broadcast E2E Test
|
|
3
|
+
*
|
|
4
|
+
* Spins up a REAL CombinedServer with in-memory EventStore, connects
|
|
5
|
+
* two WebSocket clients, and verifies that events emitted by one client's
|
|
6
|
+
* ACP activity are broadcast to the other client's subscription.
|
|
7
|
+
*
|
|
8
|
+
* This tests the full pipeline:
|
|
9
|
+
* Client B sends map/send (ACP envelope)
|
|
10
|
+
* → handleSend → handleACPOverMAP
|
|
11
|
+
* → emitEvent(message_sent)
|
|
12
|
+
* → processRequest → emitNotification → emitEvent(message_delivered)
|
|
13
|
+
* → emitEvent(message_delivered) [final response]
|
|
14
|
+
* → subscription match → sendToSession → WebSocket.send()
|
|
15
|
+
* → Client A receives map/event notification
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
19
|
+
import { WebSocket } from "ws";
|
|
20
|
+
import { createEventStore, type EventStore } from "../../../store/event-store.js";
|
|
21
|
+
import {
|
|
22
|
+
createAgentManager,
|
|
23
|
+
type AgentManager,
|
|
24
|
+
} from "../../../agent/agent-manager.js";
|
|
25
|
+
import { createTaskManager, type TaskManager } from "../../../task/task-manager.js";
|
|
26
|
+
import {
|
|
27
|
+
createMessageRouter,
|
|
28
|
+
type MessageRouter,
|
|
29
|
+
} from "../../../router/message-router.js";
|
|
30
|
+
import {
|
|
31
|
+
createCombinedServer,
|
|
32
|
+
type CombinedServer,
|
|
33
|
+
type CombinedServerServices,
|
|
34
|
+
} from "../../../server/combined-server.js";
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Helpers
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
function getPortFromUrl(url: string): number {
|
|
41
|
+
return parseInt(new URL(url).port, 10);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface JsonRpcMessage {
|
|
45
|
+
jsonrpc: "2.0";
|
|
46
|
+
id?: number;
|
|
47
|
+
method?: string;
|
|
48
|
+
params?: unknown;
|
|
49
|
+
result?: unknown;
|
|
50
|
+
error?: { code: number; message: string; data?: unknown };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* WebSocket MAP client that handles both RPC responses AND notifications.
|
|
55
|
+
*/
|
|
56
|
+
class MAPTestClient {
|
|
57
|
+
private ws!: WebSocket;
|
|
58
|
+
private waiters: Map<
|
|
59
|
+
number,
|
|
60
|
+
{ resolve: (r: JsonRpcMessage) => void; reject: (e: Error) => void }
|
|
61
|
+
> = new Map();
|
|
62
|
+
private nextId = 1;
|
|
63
|
+
private url: string;
|
|
64
|
+
|
|
65
|
+
/** All received notifications (no `id` field) */
|
|
66
|
+
readonly notifications: JsonRpcMessage[] = [];
|
|
67
|
+
|
|
68
|
+
/** Resolvers waiting for a notification matching some predicate */
|
|
69
|
+
private notificationWaiters: Array<{
|
|
70
|
+
check: (msg: JsonRpcMessage) => boolean;
|
|
71
|
+
resolve: (msg: JsonRpcMessage) => void;
|
|
72
|
+
reject: (e: Error) => void;
|
|
73
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
74
|
+
}> = [];
|
|
75
|
+
|
|
76
|
+
constructor(url: string) {
|
|
77
|
+
this.url = url;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async connect(): Promise<void> {
|
|
81
|
+
this.ws = new WebSocket(this.url);
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const timeout = setTimeout(
|
|
84
|
+
() => reject(new Error("Connection timeout")),
|
|
85
|
+
5000,
|
|
86
|
+
);
|
|
87
|
+
this.ws.on("open", () => {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
resolve();
|
|
90
|
+
});
|
|
91
|
+
this.ws.on("error", (err) => {
|
|
92
|
+
clearTimeout(timeout);
|
|
93
|
+
reject(err);
|
|
94
|
+
});
|
|
95
|
+
this.ws.on("message", (data: Buffer) => {
|
|
96
|
+
try {
|
|
97
|
+
const msg = JSON.parse(data.toString()) as JsonRpcMessage;
|
|
98
|
+
|
|
99
|
+
if (msg.id != null) {
|
|
100
|
+
// RPC response
|
|
101
|
+
const waiter = this.waiters.get(msg.id);
|
|
102
|
+
if (waiter) {
|
|
103
|
+
this.waiters.delete(msg.id);
|
|
104
|
+
waiter.resolve(msg);
|
|
105
|
+
}
|
|
106
|
+
} else if (msg.method) {
|
|
107
|
+
// Notification (no id)
|
|
108
|
+
this.notifications.push(msg);
|
|
109
|
+
|
|
110
|
+
// Check pending notification waiters
|
|
111
|
+
for (let i = this.notificationWaiters.length - 1; i >= 0; i--) {
|
|
112
|
+
const w = this.notificationWaiters[i];
|
|
113
|
+
if (w.check(msg)) {
|
|
114
|
+
clearTimeout(w.timeout);
|
|
115
|
+
this.notificationWaiters.splice(i, 1);
|
|
116
|
+
w.resolve(msg);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// ignore parse errors
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async request(method: string, params?: unknown): Promise<JsonRpcMessage> {
|
|
128
|
+
const id = this.nextId++;
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const timeout = setTimeout(() => {
|
|
131
|
+
this.waiters.delete(id);
|
|
132
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
133
|
+
}, 30000);
|
|
134
|
+
this.waiters.set(id, {
|
|
135
|
+
resolve: (r) => {
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
resolve(r);
|
|
138
|
+
},
|
|
139
|
+
reject: (e) => {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
reject(e);
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
this.ws.send(JSON.stringify({ jsonrpc: "2.0", method, params, id }));
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Wait for a notification matching the predicate.
|
|
150
|
+
* Checks already-received notifications first.
|
|
151
|
+
*/
|
|
152
|
+
waitForNotification(
|
|
153
|
+
check: (msg: JsonRpcMessage) => boolean,
|
|
154
|
+
timeoutMs = 15000,
|
|
155
|
+
): Promise<JsonRpcMessage> {
|
|
156
|
+
const existing = this.notifications.find(check);
|
|
157
|
+
if (existing) return Promise.resolve(existing);
|
|
158
|
+
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const timeout = setTimeout(() => {
|
|
161
|
+
const idx = this.notificationWaiters.findIndex(
|
|
162
|
+
(w) => w.resolve === resolve,
|
|
163
|
+
);
|
|
164
|
+
if (idx >= 0) this.notificationWaiters.splice(idx, 1);
|
|
165
|
+
reject(
|
|
166
|
+
new Error(
|
|
167
|
+
`Timeout waiting for notification. Received ${this.notifications.length} notifications:\n` +
|
|
168
|
+
this.notifications
|
|
169
|
+
.map((n) => {
|
|
170
|
+
const p = n.params as Record<string, unknown> | undefined;
|
|
171
|
+
const evt = p?.event as Record<string, unknown> | undefined;
|
|
172
|
+
return ` ${n.method} → type=${evt?.type}`;
|
|
173
|
+
})
|
|
174
|
+
.join("\n"),
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
}, timeoutMs);
|
|
178
|
+
this.notificationWaiters.push({ check, resolve, reject, timeout });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
close(): void {
|
|
183
|
+
// Clean up pending waiters
|
|
184
|
+
for (const w of this.notificationWaiters) {
|
|
185
|
+
clearTimeout(w.timeout);
|
|
186
|
+
}
|
|
187
|
+
this.notificationWaiters.length = 0;
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
this.ws?.close();
|
|
191
|
+
} catch {
|
|
192
|
+
// ignore
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// =============================================================================
|
|
198
|
+
// Tests
|
|
199
|
+
// =============================================================================
|
|
200
|
+
|
|
201
|
+
describe("Multi-client event broadcast E2E", () => {
|
|
202
|
+
let eventStore: EventStore;
|
|
203
|
+
let agentManager: AgentManager;
|
|
204
|
+
let taskManager: TaskManager;
|
|
205
|
+
let messageRouter: MessageRouter;
|
|
206
|
+
let server: CombinedServer;
|
|
207
|
+
let port: number;
|
|
208
|
+
const clients: MAPTestClient[] = [];
|
|
209
|
+
|
|
210
|
+
beforeEach(async () => {
|
|
211
|
+
eventStore = await createEventStore({ inMemory: true });
|
|
212
|
+
messageRouter = createMessageRouter(eventStore);
|
|
213
|
+
taskManager = createTaskManager(eventStore);
|
|
214
|
+
agentManager = createAgentManager(eventStore, messageRouter, {
|
|
215
|
+
defaultPermissionMode: "auto-approve",
|
|
216
|
+
defaultCwd: process.cwd(),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const services: CombinedServerServices = {
|
|
220
|
+
eventStore,
|
|
221
|
+
agentManager,
|
|
222
|
+
taskManager,
|
|
223
|
+
messageRouter,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
server = createCombinedServer(services, { port: 0, host: "localhost" });
|
|
227
|
+
await server.start();
|
|
228
|
+
port = getPortFromUrl(server.getUrl());
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
afterEach(async () => {
|
|
232
|
+
for (const c of clients) {
|
|
233
|
+
c.close();
|
|
234
|
+
}
|
|
235
|
+
clients.length = 0;
|
|
236
|
+
await server.stop().catch(() => {});
|
|
237
|
+
await agentManager.close();
|
|
238
|
+
await eventStore.close();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
function createClient(): MAPTestClient {
|
|
242
|
+
const client = new MAPTestClient(`ws://localhost:${port}/map?token=${server.serverToken}`);
|
|
243
|
+
clients.push(client);
|
|
244
|
+
return client;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Helper to check if a notification is a map/event with a specific type
|
|
248
|
+
function isEventOfType(type: string) {
|
|
249
|
+
return (msg: JsonRpcMessage) => {
|
|
250
|
+
if (msg.method !== "map/event") return false;
|
|
251
|
+
const params = msg.params as Record<string, unknown> | undefined;
|
|
252
|
+
const event = params?.event as Record<string, unknown> | undefined;
|
|
253
|
+
return event?.type === type;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Send ACP initialize + session/new via map/send, return the response. */
|
|
258
|
+
async function initializeAndCreateSession(
|
|
259
|
+
client: MAPTestClient,
|
|
260
|
+
streamId: string,
|
|
261
|
+
): Promise<JsonRpcMessage> {
|
|
262
|
+
// ACP requires initialize before session/new
|
|
263
|
+
await client.request("map/send", {
|
|
264
|
+
to: { agent: "default" },
|
|
265
|
+
payload: {
|
|
266
|
+
acp: {
|
|
267
|
+
jsonrpc: "2.0",
|
|
268
|
+
id: 1,
|
|
269
|
+
method: "initialize",
|
|
270
|
+
params: {
|
|
271
|
+
clientInfo: { name: "test-client", version: "0.1.0" },
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
acpContext: { streamId, direction: "client-to-agent" },
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return client.request("map/send", {
|
|
279
|
+
to: { agent: "default" },
|
|
280
|
+
payload: {
|
|
281
|
+
acp: {
|
|
282
|
+
jsonrpc: "2.0",
|
|
283
|
+
id: 2,
|
|
284
|
+
method: "session/new",
|
|
285
|
+
params: {},
|
|
286
|
+
},
|
|
287
|
+
acpContext: { streamId, direction: "client-to-agent" },
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
it("Client A receives agent_registered when Client B creates a session", { timeout: 30_000 }, async () => {
|
|
293
|
+
// Connect Client A and subscribe
|
|
294
|
+
const clientA = createClient();
|
|
295
|
+
await clientA.connect();
|
|
296
|
+
const subRes = await clientA.request("map/subscribe", {
|
|
297
|
+
filter: {
|
|
298
|
+
eventTypes: [
|
|
299
|
+
"agent_registered",
|
|
300
|
+
"message_sent",
|
|
301
|
+
"message_delivered",
|
|
302
|
+
"agent_state_changed",
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
expect(subRes.error).toBeUndefined();
|
|
307
|
+
expect(subRes.result).toHaveProperty("subscriptionId");
|
|
308
|
+
|
|
309
|
+
// Connect Client B
|
|
310
|
+
const clientB = createClient();
|
|
311
|
+
await clientB.connect();
|
|
312
|
+
|
|
313
|
+
// Client B initializes ACP stream and creates a session
|
|
314
|
+
const streamId = `stream-${Date.now()}`;
|
|
315
|
+
const sessionNewRes = await initializeAndCreateSession(clientB, streamId);
|
|
316
|
+
console.log("session/new response:", JSON.stringify(sessionNewRes, null, 2));
|
|
317
|
+
|
|
318
|
+
// Client A should receive agent_registered notification
|
|
319
|
+
const agentEvent = await clientA.waitForNotification(
|
|
320
|
+
isEventOfType("agent_registered"),
|
|
321
|
+
10000,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(agentEvent.method).toBe("map/event");
|
|
325
|
+
const params = agentEvent.params as Record<string, unknown>;
|
|
326
|
+
const event = params.event as Record<string, unknown>;
|
|
327
|
+
expect(event.type).toBe("agent_registered");
|
|
328
|
+
|
|
329
|
+
const data = event.data as Record<string, unknown>;
|
|
330
|
+
expect(data.agentId).toBeDefined();
|
|
331
|
+
console.log("Agent registered event data:", JSON.stringify(data, null, 2));
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("Client A receives message_sent when Client B sends ACP request", { timeout: 30_000 }, async () => {
|
|
335
|
+
// Connect and subscribe Client A
|
|
336
|
+
const clientA = createClient();
|
|
337
|
+
await clientA.connect();
|
|
338
|
+
await clientA.request("map/subscribe", {
|
|
339
|
+
filter: {
|
|
340
|
+
eventTypes: [
|
|
341
|
+
"agent_registered",
|
|
342
|
+
"message_sent",
|
|
343
|
+
"message_delivered",
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Connect Client B and create a session first
|
|
349
|
+
const clientB = createClient();
|
|
350
|
+
await clientB.connect();
|
|
351
|
+
|
|
352
|
+
const streamId = `stream-${Date.now()}`;
|
|
353
|
+
|
|
354
|
+
// Initialize ACP stream
|
|
355
|
+
await clientB.request("map/send", {
|
|
356
|
+
to: { agent: "default" },
|
|
357
|
+
payload: {
|
|
358
|
+
acp: {
|
|
359
|
+
jsonrpc: "2.0",
|
|
360
|
+
id: 1,
|
|
361
|
+
method: "initialize",
|
|
362
|
+
params: {
|
|
363
|
+
clientInfo: { name: "test-client-b", version: "0.1.0" },
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
acpContext: {
|
|
367
|
+
streamId,
|
|
368
|
+
direction: "client-to-agent",
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Create new session (this also triggers agent_registered)
|
|
374
|
+
const sessionNewRes = await clientB.request("map/send", {
|
|
375
|
+
to: { agent: "default" },
|
|
376
|
+
payload: {
|
|
377
|
+
acp: {
|
|
378
|
+
jsonrpc: "2.0",
|
|
379
|
+
id: 2,
|
|
380
|
+
method: "session/new",
|
|
381
|
+
params: {},
|
|
382
|
+
},
|
|
383
|
+
acpContext: {
|
|
384
|
+
streamId,
|
|
385
|
+
direction: "client-to-agent",
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
console.log("session/new response:", JSON.stringify(sessionNewRes, null, 2));
|
|
391
|
+
|
|
392
|
+
// Wait for agent_registered first (confirms agent was created)
|
|
393
|
+
const agentEvent = await clientA.waitForNotification(
|
|
394
|
+
isEventOfType("agent_registered"),
|
|
395
|
+
10000,
|
|
396
|
+
);
|
|
397
|
+
const agentData = (
|
|
398
|
+
(agentEvent.params as Record<string, unknown>).event as Record<
|
|
399
|
+
string,
|
|
400
|
+
unknown
|
|
401
|
+
>
|
|
402
|
+
).data as Record<string, unknown>;
|
|
403
|
+
const agentId = agentData.agentId as string;
|
|
404
|
+
console.log("Created agent:", agentId);
|
|
405
|
+
|
|
406
|
+
// Client A should have received message_sent events for the ACP requests
|
|
407
|
+
// (initialize and session/new both emit message_sent)
|
|
408
|
+
const messageSentEvents = clientA.notifications.filter(
|
|
409
|
+
isEventOfType("message_sent"),
|
|
410
|
+
);
|
|
411
|
+
console.log(
|
|
412
|
+
`Client A received ${messageSentEvents.length} message_sent events`,
|
|
413
|
+
);
|
|
414
|
+
expect(messageSentEvents.length).toBeGreaterThanOrEqual(1);
|
|
415
|
+
|
|
416
|
+
// Verify the message_sent event has the ACP envelope in the data
|
|
417
|
+
const sentParams = messageSentEvents[0].params as Record<string, unknown>;
|
|
418
|
+
const sentEvent = sentParams.event as Record<string, unknown>;
|
|
419
|
+
const sentData = sentEvent.data as Record<string, unknown>;
|
|
420
|
+
expect(sentData.from).toBeDefined();
|
|
421
|
+
expect(sentData.to).toBeDefined();
|
|
422
|
+
expect(sentData.message).toBeDefined();
|
|
423
|
+
|
|
424
|
+
const message = sentData.message as Record<string, unknown>;
|
|
425
|
+
expect(message.payload).toBeDefined();
|
|
426
|
+
|
|
427
|
+
// The payload should be a valid ACP envelope
|
|
428
|
+
const payload = message.payload as Record<string, unknown>;
|
|
429
|
+
expect(payload.acp).toBeDefined();
|
|
430
|
+
expect(payload.acpContext).toBeDefined();
|
|
431
|
+
|
|
432
|
+
console.log("message_sent event data:", JSON.stringify(sentData, null, 2));
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("Client A receives message_delivered for ACP streaming updates", { timeout: 30_000 }, async () => {
|
|
436
|
+
// Connect and subscribe Client A
|
|
437
|
+
const clientA = createClient();
|
|
438
|
+
await clientA.connect();
|
|
439
|
+
await clientA.request("map/subscribe", {
|
|
440
|
+
filter: {
|
|
441
|
+
eventTypes: [
|
|
442
|
+
"agent_registered",
|
|
443
|
+
"message_sent",
|
|
444
|
+
"message_delivered",
|
|
445
|
+
],
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Connect Client B
|
|
450
|
+
const clientB = createClient();
|
|
451
|
+
await clientB.connect();
|
|
452
|
+
|
|
453
|
+
const streamId = `stream-${Date.now()}`;
|
|
454
|
+
|
|
455
|
+
// Initialize
|
|
456
|
+
await clientB.request("map/send", {
|
|
457
|
+
to: { agent: "default" },
|
|
458
|
+
payload: {
|
|
459
|
+
acp: {
|
|
460
|
+
jsonrpc: "2.0",
|
|
461
|
+
id: 1,
|
|
462
|
+
method: "initialize",
|
|
463
|
+
params: {
|
|
464
|
+
clientInfo: { name: "test-client-b", version: "0.1.0" },
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
acpContext: {
|
|
468
|
+
streamId,
|
|
469
|
+
direction: "client-to-agent",
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Create session
|
|
475
|
+
const sessionRes = await clientB.request("map/send", {
|
|
476
|
+
to: { agent: "default" },
|
|
477
|
+
payload: {
|
|
478
|
+
acp: {
|
|
479
|
+
jsonrpc: "2.0",
|
|
480
|
+
id: 2,
|
|
481
|
+
method: "session/new",
|
|
482
|
+
params: {},
|
|
483
|
+
},
|
|
484
|
+
acpContext: {
|
|
485
|
+
streamId,
|
|
486
|
+
direction: "client-to-agent",
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
console.log("session/new response:", JSON.stringify(sessionRes, null, 2));
|
|
492
|
+
|
|
493
|
+
// Wait for agent_registered first
|
|
494
|
+
const agentEvent = await clientA.waitForNotification(
|
|
495
|
+
isEventOfType("agent_registered"),
|
|
496
|
+
10000,
|
|
497
|
+
);
|
|
498
|
+
const agentData = (
|
|
499
|
+
(agentEvent.params as Record<string, unknown>).event as Record<
|
|
500
|
+
string,
|
|
501
|
+
unknown
|
|
502
|
+
>
|
|
503
|
+
).data as Record<string, unknown>;
|
|
504
|
+
const agentId = agentData.agentId as string;
|
|
505
|
+
|
|
506
|
+
// Extract session ID from the session/new response
|
|
507
|
+
const sessionResult =
|
|
508
|
+
sessionRes.result as Record<string, unknown> | undefined;
|
|
509
|
+
let sessionId: string | undefined;
|
|
510
|
+
if (sessionResult?.delivered) {
|
|
511
|
+
// The response comes from sendMessage — extract sessionId from
|
|
512
|
+
// message_delivered events that should be in Client A's notifications
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Check message_delivered events — session/new should emit session info
|
|
516
|
+
// Wait a moment for streaming updates
|
|
517
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
518
|
+
|
|
519
|
+
const deliveredEvents = clientA.notifications.filter(
|
|
520
|
+
isEventOfType("message_delivered"),
|
|
521
|
+
);
|
|
522
|
+
console.log(
|
|
523
|
+
`Client A received ${deliveredEvents.length} message_delivered events`,
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// There should be at least one message_delivered event
|
|
527
|
+
// (session info notification emitted during session/new processing)
|
|
528
|
+
expect(deliveredEvents.length).toBeGreaterThanOrEqual(1);
|
|
529
|
+
|
|
530
|
+
// Verify the delivered event has the proper structure
|
|
531
|
+
for (const evt of deliveredEvents) {
|
|
532
|
+
const p = evt.params as Record<string, unknown>;
|
|
533
|
+
const e = p.event as Record<string, unknown>;
|
|
534
|
+
const d = e.data as Record<string, unknown>;
|
|
535
|
+
expect(d.from).toBeDefined();
|
|
536
|
+
expect(d.to).toBeDefined();
|
|
537
|
+
expect(d.message).toBeDefined();
|
|
538
|
+
|
|
539
|
+
const msg = d.message as Record<string, unknown>;
|
|
540
|
+
expect(msg.payload).toBeDefined();
|
|
541
|
+
|
|
542
|
+
const payload = msg.payload as Record<string, unknown>;
|
|
543
|
+
expect(payload.acp).toBeDefined();
|
|
544
|
+
expect(payload.acpContext).toBeDefined();
|
|
545
|
+
|
|
546
|
+
console.log(
|
|
547
|
+
` message_delivered: method=${(payload.acp as Record<string, unknown>).method ?? "(response)"}`,
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("Client A does NOT receive events for non-subscribed types", { timeout: 30_000 }, async () => {
|
|
553
|
+
// Client A subscribes only to agent_registered
|
|
554
|
+
const clientA = createClient();
|
|
555
|
+
await clientA.connect();
|
|
556
|
+
await clientA.request("map/subscribe", {
|
|
557
|
+
filter: { eventTypes: ["agent_registered"] },
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Client B sends ACP messages
|
|
561
|
+
const clientB = createClient();
|
|
562
|
+
await clientB.connect();
|
|
563
|
+
|
|
564
|
+
const streamId = `stream-${Date.now()}`;
|
|
565
|
+
await clientB.request("map/send", {
|
|
566
|
+
to: { agent: "default" },
|
|
567
|
+
payload: {
|
|
568
|
+
acp: {
|
|
569
|
+
jsonrpc: "2.0",
|
|
570
|
+
id: 1,
|
|
571
|
+
method: "initialize",
|
|
572
|
+
params: {
|
|
573
|
+
clientInfo: { name: "test-client-b", version: "0.1.0" },
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
acpContext: {
|
|
577
|
+
streamId,
|
|
578
|
+
direction: "client-to-agent",
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
await clientB.request("map/send", {
|
|
584
|
+
to: { agent: "default" },
|
|
585
|
+
payload: {
|
|
586
|
+
acp: {
|
|
587
|
+
jsonrpc: "2.0",
|
|
588
|
+
id: 2,
|
|
589
|
+
method: "session/new",
|
|
590
|
+
params: {},
|
|
591
|
+
},
|
|
592
|
+
acpContext: {
|
|
593
|
+
streamId,
|
|
594
|
+
direction: "client-to-agent",
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Wait for agent_registered (should arrive)
|
|
600
|
+
await clientA.waitForNotification(
|
|
601
|
+
isEventOfType("agent_registered"),
|
|
602
|
+
10000,
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
// Give time for any other events to arrive
|
|
606
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
607
|
+
|
|
608
|
+
// Should NOT have received message_sent or message_delivered
|
|
609
|
+
const messageSent = clientA.notifications.filter(
|
|
610
|
+
isEventOfType("message_sent"),
|
|
611
|
+
);
|
|
612
|
+
const messageDelivered = clientA.notifications.filter(
|
|
613
|
+
isEventOfType("message_delivered"),
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
expect(messageSent).toHaveLength(0);
|
|
617
|
+
expect(messageDelivered).toHaveLength(0);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it("Both clients receive events when both are subscribed", { timeout: 30_000 }, async () => {
|
|
621
|
+
const clientA = createClient();
|
|
622
|
+
await clientA.connect();
|
|
623
|
+
await clientA.request("map/subscribe", {
|
|
624
|
+
filter: { eventTypes: ["agent_registered", "message_sent"] },
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const clientB = createClient();
|
|
628
|
+
await clientB.connect();
|
|
629
|
+
await clientB.request("map/subscribe", {
|
|
630
|
+
filter: { eventTypes: ["agent_registered", "message_sent"] },
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
const streamId = `stream-${Date.now()}`;
|
|
634
|
+
|
|
635
|
+
// Client B initializes and creates session — both should see the events
|
|
636
|
+
await initializeAndCreateSession(clientB, streamId);
|
|
637
|
+
|
|
638
|
+
// Both should receive agent_registered
|
|
639
|
+
const eventA = await clientA.waitForNotification(
|
|
640
|
+
isEventOfType("agent_registered"),
|
|
641
|
+
10000,
|
|
642
|
+
);
|
|
643
|
+
const eventB = await clientB.waitForNotification(
|
|
644
|
+
isEventOfType("agent_registered"),
|
|
645
|
+
10000,
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
expect(eventA.method).toBe("map/event");
|
|
649
|
+
expect(eventB.method).toBe("map/event");
|
|
650
|
+
|
|
651
|
+
console.log(
|
|
652
|
+
`Client A notifications: ${clientA.notifications.length}`,
|
|
653
|
+
clientA.notifications.map((n) => {
|
|
654
|
+
const p = n.params as Record<string, unknown>;
|
|
655
|
+
const e = p?.event as Record<string, unknown>;
|
|
656
|
+
return e?.type;
|
|
657
|
+
}),
|
|
658
|
+
);
|
|
659
|
+
console.log(
|
|
660
|
+
`Client B notifications: ${clientB.notifications.length}`,
|
|
661
|
+
clientB.notifications.map((n) => {
|
|
662
|
+
const p = n.params as Record<string, unknown>;
|
|
663
|
+
const e = p?.event as Record<string, unknown>;
|
|
664
|
+
return e?.type;
|
|
665
|
+
}),
|
|
666
|
+
);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it("map/replay returns historical events", { timeout: 30_000 }, async () => {
|
|
670
|
+
// Client B creates an agent (generates events)
|
|
671
|
+
const clientB = createClient();
|
|
672
|
+
await clientB.connect();
|
|
673
|
+
|
|
674
|
+
const streamId = `stream-${Date.now()}`;
|
|
675
|
+
await initializeAndCreateSession(clientB, streamId);
|
|
676
|
+
|
|
677
|
+
// Wait for the request to be processed
|
|
678
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
679
|
+
|
|
680
|
+
// NOW Client A connects and replays history
|
|
681
|
+
const clientA = createClient();
|
|
682
|
+
await clientA.connect();
|
|
683
|
+
|
|
684
|
+
const replayRes = await clientA.request("map/replay", {
|
|
685
|
+
filter: {
|
|
686
|
+
eventTypes: [
|
|
687
|
+
"agent_registered",
|
|
688
|
+
"message_sent",
|
|
689
|
+
"message_delivered",
|
|
690
|
+
],
|
|
691
|
+
},
|
|
692
|
+
limit: 100,
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
expect(replayRes.error).toBeUndefined();
|
|
696
|
+
const result = replayRes.result as {
|
|
697
|
+
events: Array<{ event: { type: string } }>;
|
|
698
|
+
hasMore: boolean;
|
|
699
|
+
};
|
|
700
|
+
expect(result.events).toBeDefined();
|
|
701
|
+
expect(result.events.length).toBeGreaterThan(0);
|
|
702
|
+
|
|
703
|
+
const eventTypes = result.events.map((e) => e.event.type);
|
|
704
|
+
console.log("Replayed event types:", eventTypes);
|
|
705
|
+
|
|
706
|
+
// Should include agent_registered from the session/new
|
|
707
|
+
expect(eventTypes).toContain("agent_registered");
|
|
708
|
+
// Should include message_sent (for the session/new ACP request)
|
|
709
|
+
expect(eventTypes).toContain("message_sent");
|
|
710
|
+
});
|
|
711
|
+
});
|