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
|
@@ -1078,6 +1078,269 @@ describe("ACP-over-MAP history persistence", () => {
|
|
|
1078
1078
|
expect(result.plan[0].content).toBe("Persistent task");
|
|
1079
1079
|
});
|
|
1080
1080
|
|
|
1081
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1082
|
+
// Fork history tests
|
|
1083
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1084
|
+
|
|
1085
|
+
/** Register a forked agent with fork_of metadata */
|
|
1086
|
+
function registerForkedAgent(
|
|
1087
|
+
agentId: string,
|
|
1088
|
+
sessionId: string,
|
|
1089
|
+
sourceAgentId: string,
|
|
1090
|
+
createdAt?: number,
|
|
1091
|
+
): void {
|
|
1092
|
+
eventStore.emit({
|
|
1093
|
+
type: "spawn",
|
|
1094
|
+
source: { agent_id: sourceAgentId },
|
|
1095
|
+
payload: {
|
|
1096
|
+
agent_id: agentId,
|
|
1097
|
+
session_id: sessionId,
|
|
1098
|
+
task: `[Fork of ${sourceAgentId}]`,
|
|
1099
|
+
task_id: "task-fork",
|
|
1100
|
+
cwd: "/test/cwd",
|
|
1101
|
+
},
|
|
1102
|
+
});
|
|
1103
|
+
// Set fork_of metadata (mirrors what agent-manager.ts does)
|
|
1104
|
+
eventStore.updateAgentMetadata(agentId as AgentId, {
|
|
1105
|
+
metadata: { fork_of: sourceAgentId },
|
|
1106
|
+
});
|
|
1107
|
+
// Backdate created_at if specified (for timestamp filtering tests)
|
|
1108
|
+
if (createdAt !== undefined) {
|
|
1109
|
+
// Overwrite the agent row's created_at by re-emitting a lifecycle event
|
|
1110
|
+
// at the desired time — but since we can't change created_at directly,
|
|
1111
|
+
// we rely on the spawn event timestamp. For testing, we'll emit the
|
|
1112
|
+
// spawn before recording turns to ensure correct ordering.
|
|
1113
|
+
}
|
|
1114
|
+
// Mark as running
|
|
1115
|
+
eventStore.emit({
|
|
1116
|
+
type: "lifecycle",
|
|
1117
|
+
source: { agent_id: agentId },
|
|
1118
|
+
payload: {
|
|
1119
|
+
agent_id: agentId,
|
|
1120
|
+
action: "started",
|
|
1121
|
+
},
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/** Record a turn directly in the event store (bypasses prompt flow) */
|
|
1126
|
+
function recordTurn(
|
|
1127
|
+
agentId: string,
|
|
1128
|
+
conversationId: string,
|
|
1129
|
+
role: "user" | "assistant",
|
|
1130
|
+
content: unknown,
|
|
1131
|
+
): void {
|
|
1132
|
+
const now = Date.now();
|
|
1133
|
+
eventStore.emit({
|
|
1134
|
+
type: "turn",
|
|
1135
|
+
source: { agent_id: agentId },
|
|
1136
|
+
payload: {
|
|
1137
|
+
action: "recorded",
|
|
1138
|
+
turn_id: `turn_${role}_${now}_${Math.random().toString(36).slice(2, 8)}`,
|
|
1139
|
+
conversation_id: conversationId,
|
|
1140
|
+
participant: role,
|
|
1141
|
+
timestamp: now,
|
|
1142
|
+
content_type: role === "user" ? "user_prompt" : "assistant_response",
|
|
1143
|
+
content,
|
|
1144
|
+
source_type: "acp",
|
|
1145
|
+
},
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
it("should include source agent history for forked agents", async () => {
|
|
1150
|
+
await setup();
|
|
1151
|
+
|
|
1152
|
+
const sourceAgentId = "agent-source" as AgentId;
|
|
1153
|
+
const sourceSessionId = "session-source";
|
|
1154
|
+
const forkedAgentId = "agent-forked" as AgentId;
|
|
1155
|
+
const forkedSessionId = "session-forked";
|
|
1156
|
+
|
|
1157
|
+
// Register source agent and record turns
|
|
1158
|
+
registerAgent(sourceAgentId, sourceSessionId);
|
|
1159
|
+
recordTurn(sourceAgentId, sourceSessionId, "user", "Hello source");
|
|
1160
|
+
recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Hi from source" }] });
|
|
1161
|
+
|
|
1162
|
+
// Register forked agent with fork_of metadata
|
|
1163
|
+
registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
|
|
1164
|
+
|
|
1165
|
+
// Initialize stream for forked agent
|
|
1166
|
+
const streamId = "fork-history-stream";
|
|
1167
|
+
await handler.processRequest(
|
|
1168
|
+
forkedAgentId,
|
|
1169
|
+
envelope(streamId, "initialize", {
|
|
1170
|
+
protocolVersion: 1,
|
|
1171
|
+
capabilities: {},
|
|
1172
|
+
clientInfo: { name: "test", version: "1.0" },
|
|
1173
|
+
}),
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
// Query history for forked agent
|
|
1177
|
+
const historyResult = await handler.processRequest(
|
|
1178
|
+
forkedAgentId,
|
|
1179
|
+
envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
|
|
1180
|
+
);
|
|
1181
|
+
|
|
1182
|
+
const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
|
|
1183
|
+
|
|
1184
|
+
// Should include source agent's 2 turns
|
|
1185
|
+
expect(turns).toHaveLength(2);
|
|
1186
|
+
expect(turns[0].role).toBe("user");
|
|
1187
|
+
expect(turns[0].content).toBe("Hello source");
|
|
1188
|
+
expect(turns[1].role).toBe("assistant");
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
it("should combine source and forked agent turns in order", async () => {
|
|
1192
|
+
await setup();
|
|
1193
|
+
|
|
1194
|
+
const sourceAgentId = "agent-src" as AgentId;
|
|
1195
|
+
const sourceSessionId = "session-src";
|
|
1196
|
+
const forkedAgentId = "agent-fork" as AgentId;
|
|
1197
|
+
const forkedSessionId = "session-fork";
|
|
1198
|
+
|
|
1199
|
+
// Source agent conversation
|
|
1200
|
+
registerAgent(sourceAgentId, sourceSessionId);
|
|
1201
|
+
recordTurn(sourceAgentId, sourceSessionId, "user", "Question 1");
|
|
1202
|
+
recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Answer 1" }] });
|
|
1203
|
+
|
|
1204
|
+
// Fork the agent
|
|
1205
|
+
registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
|
|
1206
|
+
|
|
1207
|
+
// Forked agent has its own turns
|
|
1208
|
+
recordTurn(forkedAgentId, forkedSessionId, "user", "Question 2 (forked)");
|
|
1209
|
+
recordTurn(forkedAgentId, forkedSessionId, "assistant", { parts: [{ type: "text", text: "Answer 2 (forked)" }] });
|
|
1210
|
+
|
|
1211
|
+
const streamId = "fork-combined-stream";
|
|
1212
|
+
await handler.processRequest(
|
|
1213
|
+
forkedAgentId,
|
|
1214
|
+
envelope(streamId, "initialize", {
|
|
1215
|
+
protocolVersion: 1,
|
|
1216
|
+
capabilities: {},
|
|
1217
|
+
clientInfo: { name: "test", version: "1.0" },
|
|
1218
|
+
}),
|
|
1219
|
+
);
|
|
1220
|
+
|
|
1221
|
+
const historyResult = await handler.processRequest(
|
|
1222
|
+
forkedAgentId,
|
|
1223
|
+
envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
|
|
1227
|
+
|
|
1228
|
+
// Source turns (2) + forked turns (2) = 4
|
|
1229
|
+
expect(turns).toHaveLength(4);
|
|
1230
|
+
expect(turns[0].content).toBe("Question 1");
|
|
1231
|
+
expect(turns[2].content).toBe("Question 2 (forked)");
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
it("should not include source turns recorded after the fork", async () => {
|
|
1235
|
+
await setup();
|
|
1236
|
+
|
|
1237
|
+
const sourceAgentId = "agent-pre" as AgentId;
|
|
1238
|
+
const sourceSessionId = "session-pre";
|
|
1239
|
+
const forkedAgentId = "agent-post" as AgentId;
|
|
1240
|
+
const forkedSessionId = "session-post";
|
|
1241
|
+
|
|
1242
|
+
// Source agent: record a turn before the fork
|
|
1243
|
+
registerAgent(sourceAgentId, sourceSessionId);
|
|
1244
|
+
recordTurn(sourceAgentId, sourceSessionId, "user", "Before fork");
|
|
1245
|
+
recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Pre-fork reply" }] });
|
|
1246
|
+
|
|
1247
|
+
// Small delay to ensure fork timestamp is after source turns
|
|
1248
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1249
|
+
|
|
1250
|
+
// Fork the agent
|
|
1251
|
+
registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
|
|
1252
|
+
|
|
1253
|
+
// Small delay to ensure post-fork turn timestamp is after fork
|
|
1254
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1255
|
+
|
|
1256
|
+
// Source agent continues after fork — these should NOT appear in forked history
|
|
1257
|
+
recordTurn(sourceAgentId, sourceSessionId, "user", "After fork on source");
|
|
1258
|
+
recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Post-fork source reply" }] });
|
|
1259
|
+
|
|
1260
|
+
const streamId = "fork-filter-stream";
|
|
1261
|
+
await handler.processRequest(
|
|
1262
|
+
forkedAgentId,
|
|
1263
|
+
envelope(streamId, "initialize", {
|
|
1264
|
+
protocolVersion: 1,
|
|
1265
|
+
capabilities: {},
|
|
1266
|
+
clientInfo: { name: "test", version: "1.0" },
|
|
1267
|
+
}),
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
const historyResult = await handler.processRequest(
|
|
1271
|
+
forkedAgentId,
|
|
1272
|
+
envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
|
|
1276
|
+
|
|
1277
|
+
// Should only include the 2 pre-fork turns, not the 2 post-fork ones
|
|
1278
|
+
expect(turns).toHaveLength(2);
|
|
1279
|
+
expect(turns[0].content).toBe("Before fork");
|
|
1280
|
+
expect(turns.find((t: { content: unknown }) => t.content === "After fork on source")).toBeUndefined();
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
it("should return empty history for forked agent when source has no turns", async () => {
|
|
1284
|
+
await setup();
|
|
1285
|
+
|
|
1286
|
+
const sourceAgentId = "agent-empty-src" as AgentId;
|
|
1287
|
+
const sourceSessionId = "session-empty-src";
|
|
1288
|
+
const forkedAgentId = "agent-empty-fork" as AgentId;
|
|
1289
|
+
const forkedSessionId = "session-empty-fork";
|
|
1290
|
+
|
|
1291
|
+
// Source agent exists but has no conversation turns
|
|
1292
|
+
registerAgent(sourceAgentId, sourceSessionId);
|
|
1293
|
+
registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
|
|
1294
|
+
|
|
1295
|
+
const streamId = "fork-empty-stream";
|
|
1296
|
+
await handler.processRequest(
|
|
1297
|
+
forkedAgentId,
|
|
1298
|
+
envelope(streamId, "initialize", {
|
|
1299
|
+
protocolVersion: 1,
|
|
1300
|
+
capabilities: {},
|
|
1301
|
+
clientInfo: { name: "test", version: "1.0" },
|
|
1302
|
+
}),
|
|
1303
|
+
);
|
|
1304
|
+
|
|
1305
|
+
const historyResult = await handler.processRequest(
|
|
1306
|
+
forkedAgentId,
|
|
1307
|
+
envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
|
|
1308
|
+
);
|
|
1309
|
+
|
|
1310
|
+
const turns = (historyResult.acp.result as { turns: unknown[] }).turns;
|
|
1311
|
+
expect(turns).toEqual([]);
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
it("should not affect non-forked agent history queries", async () => {
|
|
1315
|
+
await setup();
|
|
1316
|
+
|
|
1317
|
+
const agentId = "agent-normal" as AgentId;
|
|
1318
|
+
const sessionId = "session-normal";
|
|
1319
|
+
|
|
1320
|
+
registerAgent(agentId, sessionId);
|
|
1321
|
+
recordTurn(agentId, sessionId, "user", "Normal question");
|
|
1322
|
+
recordTurn(agentId, sessionId, "assistant", { parts: [{ type: "text", text: "Normal reply" }] });
|
|
1323
|
+
|
|
1324
|
+
const streamId = "normal-history-stream";
|
|
1325
|
+
await handler.processRequest(
|
|
1326
|
+
agentId,
|
|
1327
|
+
envelope(streamId, "initialize", {
|
|
1328
|
+
protocolVersion: 1,
|
|
1329
|
+
capabilities: {},
|
|
1330
|
+
clientInfo: { name: "test", version: "1.0" },
|
|
1331
|
+
}),
|
|
1332
|
+
);
|
|
1333
|
+
|
|
1334
|
+
const historyResult = await handler.processRequest(
|
|
1335
|
+
agentId,
|
|
1336
|
+
envelope(streamId, "_macro/getHistory", { agentId }),
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
|
|
1340
|
+
expect(turns).toHaveLength(2);
|
|
1341
|
+
expect(turns[0].content).toBe("Normal question");
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1081
1344
|
it("should include both plan and cwd together in getHistory response", async () => {
|
|
1082
1345
|
await setup([
|
|
1083
1346
|
{
|
|
@@ -208,7 +208,7 @@ describe("ACP-over-MAP history persistence (E2E with file-backed store)", () =>
|
|
|
208
208
|
title: "ListFiles",
|
|
209
209
|
status: "completed",
|
|
210
210
|
rawInput: { path: "/src" },
|
|
211
|
-
|
|
211
|
+
rawOutput: "index.ts\napp.ts",
|
|
212
212
|
},
|
|
213
213
|
]),
|
|
214
214
|
eventStore: eventStore1,
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for multi-client event broadcast.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that when events are emitted via emitEvent(), they are correctly
|
|
5
|
+
* delivered to subscribed participants through their WebSocket streams.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
createMAPAdapter,
|
|
11
|
+
MAPAdapterImpl,
|
|
12
|
+
type MAPAdapterServices,
|
|
13
|
+
} from "../map-adapter.js";
|
|
14
|
+
import type { MAPAdapter, MAPAdapterConfig, Stream } from "../interface.js";
|
|
15
|
+
import type { ParticipantId, EventNotification } from "../types.js";
|
|
16
|
+
import type { AgentId } from "../../../store/types/index.js";
|
|
17
|
+
import type { MAPEventType } from "../subscription-manager.js";
|
|
18
|
+
import { ulid } from "ulid";
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Helpers
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a mock stream pair that captures messages written by the server.
|
|
26
|
+
* - `serverStream`: pass to adapter.acceptConnection()
|
|
27
|
+
* - `clientMessages`: array of messages the adapter has written to the client
|
|
28
|
+
* - `sendToServer`: simulate client sending a message to the server
|
|
29
|
+
*/
|
|
30
|
+
function createMockStreamPair(): {
|
|
31
|
+
serverStream: Stream;
|
|
32
|
+
clientMessages: unknown[];
|
|
33
|
+
sendToServer: (msg: unknown) => void;
|
|
34
|
+
} {
|
|
35
|
+
const clientMessages: unknown[] = [];
|
|
36
|
+
|
|
37
|
+
let clientResolve: ((msg: unknown) => void) | null = null;
|
|
38
|
+
let serverResolve: ((msg: unknown) => void) | null = null;
|
|
39
|
+
|
|
40
|
+
// Server reads from this (client → server)
|
|
41
|
+
const serverReadable = new ReadableStream<unknown>({
|
|
42
|
+
start(controller) {
|
|
43
|
+
serverResolve = (msg) => {
|
|
44
|
+
controller.enqueue(msg);
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Server writes to this (server → client)
|
|
50
|
+
const serverWritable = new WritableStream<unknown>({
|
|
51
|
+
write(chunk) {
|
|
52
|
+
clientMessages.push(chunk);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
serverStream: { readable: serverReadable, writable: serverWritable },
|
|
58
|
+
clientMessages,
|
|
59
|
+
sendToServer: (msg) => serverResolve?.(msg),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wait for async writes to flush (emitEvent fires sendToSession without await).
|
|
65
|
+
*/
|
|
66
|
+
async function flushAsync(ms = 50): Promise<void> {
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Tests
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
describe("Multi-client event broadcast", () => {
|
|
75
|
+
let adapter: MAPAdapter;
|
|
76
|
+
let config: MAPAdapterConfig;
|
|
77
|
+
let services: MAPAdapterServices;
|
|
78
|
+
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
config = {
|
|
81
|
+
name: "test-broadcast",
|
|
82
|
+
version: "1.0.0",
|
|
83
|
+
limits: {
|
|
84
|
+
maxConnections: 10,
|
|
85
|
+
maxSubscriptionsPerConnection: 5,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
services = {
|
|
90
|
+
getAgent: vi.fn(),
|
|
91
|
+
listAgents: vi.fn().mockReturnValue([]),
|
|
92
|
+
sendMessage: vi.fn().mockResolvedValue({ delivered: [] }),
|
|
93
|
+
getAncestors: vi.fn().mockReturnValue([]),
|
|
94
|
+
getDescendants: vi.fn().mockReturnValue([]),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
adapter = createMAPAdapter(config, services);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(async () => {
|
|
101
|
+
if (adapter.isRunning()) {
|
|
102
|
+
await adapter.stop();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("broadcasts event to subscribed participant", async () => {
|
|
107
|
+
await adapter.start();
|
|
108
|
+
|
|
109
|
+
// Connect Client A with subscription
|
|
110
|
+
const pairA = createMockStreamPair();
|
|
111
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
112
|
+
await adapter.createSubscription(clientA.id, {
|
|
113
|
+
eventTypes: [
|
|
114
|
+
"message_sent" as MAPEventType,
|
|
115
|
+
"message_delivered" as MAPEventType,
|
|
116
|
+
"agent_registered" as MAPEventType,
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Connect Client B (no subscription)
|
|
121
|
+
const pairB = createMockStreamPair();
|
|
122
|
+
await adapter.acceptConnection(pairB.serverStream);
|
|
123
|
+
|
|
124
|
+
// Emit a message_delivered event (simulates ACP response broadcast)
|
|
125
|
+
adapter.emitEvent({
|
|
126
|
+
eventId: ulid(),
|
|
127
|
+
type: "message_delivered" as MAPEventType,
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
agentId: "agent-1" as AgentId,
|
|
130
|
+
data: {
|
|
131
|
+
from: "agent-1",
|
|
132
|
+
to: "p-client-b",
|
|
133
|
+
message: {
|
|
134
|
+
id: `acp-notif-${Date.now()}`,
|
|
135
|
+
from: "agent-1",
|
|
136
|
+
payload: { type: "session/update", data: { text: "hello" } },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
} as EventNotification);
|
|
140
|
+
|
|
141
|
+
await flushAsync();
|
|
142
|
+
|
|
143
|
+
// Client A should have received the notification
|
|
144
|
+
expect(pairA.clientMessages.length).toBeGreaterThan(0);
|
|
145
|
+
|
|
146
|
+
const notification = pairA.clientMessages[0] as {
|
|
147
|
+
jsonrpc: string;
|
|
148
|
+
method: string;
|
|
149
|
+
params: {
|
|
150
|
+
subscriptionId: string;
|
|
151
|
+
sequenceNumber: number;
|
|
152
|
+
event: { type: string; data: unknown };
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
expect(notification.method).toBe("map/event");
|
|
156
|
+
expect(notification.params.event.type).toBe("message_delivered");
|
|
157
|
+
expect(notification.params.subscriptionId).toMatch(/^sub-/);
|
|
158
|
+
|
|
159
|
+
// Client B should NOT have received the notification (no subscription)
|
|
160
|
+
expect(pairB.clientMessages).toHaveLength(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("broadcasts agent_registered event to subscribed participant", async () => {
|
|
164
|
+
await adapter.start();
|
|
165
|
+
|
|
166
|
+
const pairA = createMockStreamPair();
|
|
167
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
168
|
+
await adapter.createSubscription(clientA.id, {
|
|
169
|
+
eventTypes: ["agent_registered" as MAPEventType],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Emit agent_registered (simulates onAgentRegistered callback)
|
|
173
|
+
adapter.emitEvent({
|
|
174
|
+
eventId: ulid(),
|
|
175
|
+
type: "agent_registered" as MAPEventType,
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
agentId: "agent-new" as AgentId,
|
|
178
|
+
data: {
|
|
179
|
+
agentId: "agent-new",
|
|
180
|
+
name: "New Agent",
|
|
181
|
+
role: "assistant",
|
|
182
|
+
},
|
|
183
|
+
} as EventNotification);
|
|
184
|
+
|
|
185
|
+
await flushAsync();
|
|
186
|
+
|
|
187
|
+
expect(pairA.clientMessages.length).toBeGreaterThan(0);
|
|
188
|
+
const notification = pairA.clientMessages[0] as {
|
|
189
|
+
method: string;
|
|
190
|
+
params: { event: { type: string; data: { agentId: string } } };
|
|
191
|
+
};
|
|
192
|
+
expect(notification.method).toBe("map/event");
|
|
193
|
+
expect(notification.params.event.type).toBe("agent_registered");
|
|
194
|
+
expect(notification.params.event.data.agentId).toBe("agent-new");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("broadcasts message_sent event to subscribed participant", async () => {
|
|
198
|
+
await adapter.start();
|
|
199
|
+
|
|
200
|
+
const pairA = createMockStreamPair();
|
|
201
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
202
|
+
await adapter.createSubscription(clientA.id, {
|
|
203
|
+
eventTypes: ["message_sent" as MAPEventType],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Emit message_sent (simulates client→agent ACP request broadcast)
|
|
207
|
+
adapter.emitEvent({
|
|
208
|
+
eventId: ulid(),
|
|
209
|
+
type: "message_sent" as MAPEventType,
|
|
210
|
+
timestamp: Date.now(),
|
|
211
|
+
agentId: "agent-1" as AgentId,
|
|
212
|
+
data: {
|
|
213
|
+
from: "p-client-b",
|
|
214
|
+
to: "agent-1",
|
|
215
|
+
message: {
|
|
216
|
+
id: `acp-req-${Date.now()}`,
|
|
217
|
+
from: "p-client-b",
|
|
218
|
+
to: "agent-1",
|
|
219
|
+
payload: {
|
|
220
|
+
type: "session/prompt",
|
|
221
|
+
data: { text: "hello agent" },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
} as EventNotification);
|
|
226
|
+
|
|
227
|
+
await flushAsync();
|
|
228
|
+
|
|
229
|
+
expect(pairA.clientMessages.length).toBeGreaterThan(0);
|
|
230
|
+
const notification = pairA.clientMessages[0] as {
|
|
231
|
+
method: string;
|
|
232
|
+
params: { event: { type: string; data: { from: string; to: string } } };
|
|
233
|
+
};
|
|
234
|
+
expect(notification.method).toBe("map/event");
|
|
235
|
+
expect(notification.params.event.type).toBe("message_sent");
|
|
236
|
+
expect(notification.params.event.data.from).toBe("p-client-b");
|
|
237
|
+
expect(notification.params.event.data.to).toBe("agent-1");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("both clients receive events when both are subscribed", async () => {
|
|
241
|
+
await adapter.start();
|
|
242
|
+
|
|
243
|
+
const pairA = createMockStreamPair();
|
|
244
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
245
|
+
await adapter.createSubscription(clientA.id, {
|
|
246
|
+
eventTypes: ["message_delivered" as MAPEventType],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const pairB = createMockStreamPair();
|
|
250
|
+
const clientB = await adapter.acceptConnection(pairB.serverStream);
|
|
251
|
+
await adapter.createSubscription(clientB.id, {
|
|
252
|
+
eventTypes: ["message_delivered" as MAPEventType],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
adapter.emitEvent({
|
|
256
|
+
eventId: ulid(),
|
|
257
|
+
type: "message_delivered" as MAPEventType,
|
|
258
|
+
timestamp: Date.now(),
|
|
259
|
+
agentId: "agent-1" as AgentId,
|
|
260
|
+
data: { from: "agent-1", to: "some-client", message: {} },
|
|
261
|
+
} as EventNotification);
|
|
262
|
+
|
|
263
|
+
await flushAsync();
|
|
264
|
+
|
|
265
|
+
// Both should receive
|
|
266
|
+
expect(pairA.clientMessages.length).toBeGreaterThan(0);
|
|
267
|
+
expect(pairB.clientMessages.length).toBeGreaterThan(0);
|
|
268
|
+
|
|
269
|
+
const notifA = pairA.clientMessages[0] as { method: string };
|
|
270
|
+
const notifB = pairB.clientMessages[0] as { method: string };
|
|
271
|
+
expect(notifA.method).toBe("map/event");
|
|
272
|
+
expect(notifB.method).toBe("map/event");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("does NOT broadcast to participant with non-matching filter", async () => {
|
|
276
|
+
await adapter.start();
|
|
277
|
+
|
|
278
|
+
const pairA = createMockStreamPair();
|
|
279
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
280
|
+
// Subscribe only to agent_registered
|
|
281
|
+
await adapter.createSubscription(clientA.id, {
|
|
282
|
+
eventTypes: ["agent_registered" as MAPEventType],
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Emit message_delivered — should NOT match
|
|
286
|
+
adapter.emitEvent({
|
|
287
|
+
eventId: ulid(),
|
|
288
|
+
type: "message_delivered" as MAPEventType,
|
|
289
|
+
timestamp: Date.now(),
|
|
290
|
+
agentId: "agent-1" as AgentId,
|
|
291
|
+
data: { from: "agent-1", to: "some-client", message: {} },
|
|
292
|
+
} as EventNotification);
|
|
293
|
+
|
|
294
|
+
await flushAsync();
|
|
295
|
+
|
|
296
|
+
expect(pairA.clientMessages).toHaveLength(0);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("dot format event types match underscore subscriptions via normalization", async () => {
|
|
300
|
+
await adapter.start();
|
|
301
|
+
|
|
302
|
+
const pairA = createMockStreamPair();
|
|
303
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
304
|
+
// Subscribe with underscore format (what SDK sends)
|
|
305
|
+
await adapter.createSubscription(clientA.id, {
|
|
306
|
+
eventTypes: ["agent_registered" as MAPEventType],
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Emit with DOT format — SHOULD match after normalization
|
|
310
|
+
adapter.emitEvent({
|
|
311
|
+
eventId: ulid(),
|
|
312
|
+
type: "agent.registered" as MAPEventType,
|
|
313
|
+
timestamp: Date.now(),
|
|
314
|
+
agentId: "agent-1" as AgentId,
|
|
315
|
+
data: { agentId: "agent-1" },
|
|
316
|
+
} as EventNotification);
|
|
317
|
+
|
|
318
|
+
await flushAsync();
|
|
319
|
+
|
|
320
|
+
// Should receive — normalization converts dots to underscores for matching
|
|
321
|
+
expect(pairA.clientMessages.length).toBeGreaterThan(0);
|
|
322
|
+
const notification = pairA.clientMessages[0] as { method: string };
|
|
323
|
+
expect(notification.method).toBe("map/event");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("underscore format event types DO match underscore subscriptions", async () => {
|
|
327
|
+
await adapter.start();
|
|
328
|
+
|
|
329
|
+
const pairA = createMockStreamPair();
|
|
330
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
331
|
+
await adapter.createSubscription(clientA.id, {
|
|
332
|
+
eventTypes: ["agent_registered" as MAPEventType],
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Emit with UNDERSCORE format — SHOULD match
|
|
336
|
+
adapter.emitEvent({
|
|
337
|
+
eventId: ulid(),
|
|
338
|
+
type: "agent_registered" as MAPEventType,
|
|
339
|
+
timestamp: Date.now(),
|
|
340
|
+
agentId: "agent-1" as AgentId,
|
|
341
|
+
data: { agentId: "agent-1" },
|
|
342
|
+
} as EventNotification);
|
|
343
|
+
|
|
344
|
+
await flushAsync();
|
|
345
|
+
|
|
346
|
+
expect(pairA.clientMessages.length).toBeGreaterThan(0);
|
|
347
|
+
const notification = pairA.clientMessages[0] as { method: string };
|
|
348
|
+
expect(notification.method).toBe("map/event");
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("sequence numbers increment per subscription", async () => {
|
|
352
|
+
await adapter.start();
|
|
353
|
+
|
|
354
|
+
const pairA = createMockStreamPair();
|
|
355
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
356
|
+
await adapter.createSubscription(clientA.id, {
|
|
357
|
+
eventTypes: ["message_delivered" as MAPEventType],
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Emit three events
|
|
361
|
+
for (let i = 0; i < 3; i++) {
|
|
362
|
+
adapter.emitEvent({
|
|
363
|
+
eventId: ulid(),
|
|
364
|
+
type: "message_delivered" as MAPEventType,
|
|
365
|
+
timestamp: Date.now(),
|
|
366
|
+
agentId: "agent-1" as AgentId,
|
|
367
|
+
data: { index: i },
|
|
368
|
+
} as EventNotification);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
await flushAsync();
|
|
372
|
+
|
|
373
|
+
expect(pairA.clientMessages).toHaveLength(3);
|
|
374
|
+
|
|
375
|
+
const seqNums = pairA.clientMessages.map(
|
|
376
|
+
(m) => (m as { params: { sequenceNumber: number } }).params.sequenceNumber,
|
|
377
|
+
);
|
|
378
|
+
expect(seqNums).toEqual([0, 1, 2]);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("paused subscription does not receive events", async () => {
|
|
382
|
+
await adapter.start();
|
|
383
|
+
|
|
384
|
+
const pairA = createMockStreamPair();
|
|
385
|
+
const clientA = await adapter.acceptConnection(pairA.serverStream);
|
|
386
|
+
const subId = await adapter.createSubscription(clientA.id, {
|
|
387
|
+
eventTypes: ["message_delivered" as MAPEventType],
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Pause the subscription
|
|
391
|
+
await adapter.pauseSubscription(subId);
|
|
392
|
+
|
|
393
|
+
adapter.emitEvent({
|
|
394
|
+
eventId: ulid(),
|
|
395
|
+
type: "message_delivered" as MAPEventType,
|
|
396
|
+
timestamp: Date.now(),
|
|
397
|
+
agentId: "agent-1" as AgentId,
|
|
398
|
+
data: {},
|
|
399
|
+
} as EventNotification);
|
|
400
|
+
|
|
401
|
+
await flushAsync();
|
|
402
|
+
|
|
403
|
+
expect(pairA.clientMessages).toHaveLength(0);
|
|
404
|
+
|
|
405
|
+
// Resume and emit again
|
|
406
|
+
await adapter.resumeSubscription(subId);
|
|
407
|
+
|
|
408
|
+
adapter.emitEvent({
|
|
409
|
+
eventId: ulid(),
|
|
410
|
+
type: "message_delivered" as MAPEventType,
|
|
411
|
+
timestamp: Date.now(),
|
|
412
|
+
agentId: "agent-1" as AgentId,
|
|
413
|
+
data: {},
|
|
414
|
+
} as EventNotification);
|
|
415
|
+
|
|
416
|
+
await flushAsync();
|
|
417
|
+
|
|
418
|
+
expect(pairA.clientMessages).toHaveLength(1);
|
|
419
|
+
});
|
|
420
|
+
});
|