claude-code-swarm 0.3.3 → 0.3.5
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-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +22 -1
- package/.claude-plugin/run-agent-inbox-mcp.sh +76 -0
- package/.claude-plugin/run-minimem-mcp.sh +98 -0
- package/.claude-plugin/run-opentasks-mcp.sh +65 -0
- package/CLAUDE.md +200 -36
- package/README.md +65 -0
- package/e2e/helpers/cleanup.mjs +17 -3
- package/e2e/helpers/map-mock-server.mjs +201 -25
- package/e2e/helpers/sidecar.mjs +222 -0
- package/e2e/helpers/workspace.mjs +2 -1
- package/e2e/tier5-sidecar-inbox.test.mjs +900 -0
- package/e2e/tier6-inbox-mcp.test.mjs +173 -0
- package/e2e/tier6-live-agent.test.mjs +759 -0
- package/e2e/vitest.config.e2e.mjs +1 -1
- package/hooks/hooks.json +15 -8
- package/package.json +13 -1
- package/references/agent-inbox/CLAUDE.md +151 -0
- package/references/agent-inbox/README.md +238 -0
- package/references/agent-inbox/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
- package/references/agent-inbox/docs/DESIGN.md +1156 -0
- package/references/agent-inbox/hooks/inbox-hook.mjs +119 -0
- package/references/agent-inbox/hooks/register-hook.mjs +69 -0
- package/references/agent-inbox/package-lock.json +3347 -0
- package/references/agent-inbox/package.json +58 -0
- package/references/agent-inbox/rules/agent-inbox.md +78 -0
- package/references/agent-inbox/src/federation/address.ts +61 -0
- package/references/agent-inbox/src/federation/connection-manager.ts +573 -0
- package/references/agent-inbox/src/federation/delivery-queue.ts +222 -0
- package/references/agent-inbox/src/federation/index.ts +6 -0
- package/references/agent-inbox/src/federation/routing-engine.ts +188 -0
- package/references/agent-inbox/src/federation/trust.ts +71 -0
- package/references/agent-inbox/src/index.ts +390 -0
- package/references/agent-inbox/src/ipc/ipc-server.ts +207 -0
- package/references/agent-inbox/src/jsonrpc/mail-server.ts +382 -0
- package/references/agent-inbox/src/map/map-client.ts +414 -0
- package/references/agent-inbox/src/mcp/mcp-server.ts +272 -0
- package/references/agent-inbox/src/mesh/delivery-bridge.ts +110 -0
- package/references/agent-inbox/src/mesh/mesh-connector.ts +41 -0
- package/references/agent-inbox/src/mesh/mesh-transport.ts +157 -0
- package/references/agent-inbox/src/mesh/type-mapper.ts +239 -0
- package/references/agent-inbox/src/push/notifier.ts +233 -0
- package/references/agent-inbox/src/registry/warm-registry.ts +255 -0
- package/references/agent-inbox/src/router/message-router.ts +175 -0
- package/references/agent-inbox/src/storage/interface.ts +48 -0
- package/references/agent-inbox/src/storage/memory.ts +145 -0
- package/references/agent-inbox/src/storage/sqlite.ts +671 -0
- package/references/agent-inbox/src/traceability/traceability.ts +183 -0
- package/references/agent-inbox/src/types.ts +303 -0
- package/references/agent-inbox/test/federation/address.test.ts +101 -0
- package/references/agent-inbox/test/federation/connection-manager.test.ts +546 -0
- package/references/agent-inbox/test/federation/delivery-queue.test.ts +159 -0
- package/references/agent-inbox/test/federation/integration.test.ts +857 -0
- package/references/agent-inbox/test/federation/routing-engine.test.ts +117 -0
- package/references/agent-inbox/test/federation/sdk-integration.test.ts +744 -0
- package/references/agent-inbox/test/federation/trust.test.ts +89 -0
- package/references/agent-inbox/test/ipc-jsonrpc.test.ts +113 -0
- package/references/agent-inbox/test/ipc-server.test.ts +197 -0
- package/references/agent-inbox/test/mail-server.test.ts +285 -0
- package/references/agent-inbox/test/map-client.test.ts +408 -0
- package/references/agent-inbox/test/mesh/delivery-bridge.test.ts +178 -0
- package/references/agent-inbox/test/mesh/e2e-mesh.test.ts +527 -0
- package/references/agent-inbox/test/mesh/e2e-real-meshpeer.test.ts +629 -0
- package/references/agent-inbox/test/mesh/federation-mesh.test.ts +269 -0
- package/references/agent-inbox/test/mesh/mesh-connector.test.ts +66 -0
- package/references/agent-inbox/test/mesh/mesh-transport.test.ts +191 -0
- package/references/agent-inbox/test/mesh/meshpeer-integration.test.ts +442 -0
- package/references/agent-inbox/test/mesh/mock-mesh.ts +125 -0
- package/references/agent-inbox/test/mesh/mock-meshpeer.ts +266 -0
- package/references/agent-inbox/test/mesh/type-mapper.test.ts +226 -0
- package/references/agent-inbox/test/message-router.test.ts +184 -0
- package/references/agent-inbox/test/push-notifier.test.ts +139 -0
- package/references/agent-inbox/test/registry/warm-registry.test.ts +171 -0
- package/references/agent-inbox/test/sqlite-prefix.test.ts +192 -0
- package/references/agent-inbox/test/sqlite-storage.test.ts +243 -0
- package/references/agent-inbox/test/storage.test.ts +196 -0
- package/references/agent-inbox/test/traceability.test.ts +123 -0
- package/references/agent-inbox/test/wake.test.ts +330 -0
- package/references/agent-inbox/tsconfig.json +20 -0
- package/references/agent-inbox/tsup.config.ts +10 -0
- package/references/agent-inbox/vitest.config.ts +8 -0
- package/references/minimem/.claude/settings.json +7 -0
- package/references/minimem/.sudocode/issues.jsonl +18 -0
- package/references/minimem/.sudocode/specs.jsonl +1 -0
- package/references/minimem/CLAUDE.md +329 -0
- package/references/minimem/README.md +565 -0
- package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
- package/references/minimem/claude-plugin/.mcp.json +7 -0
- package/references/minimem/claude-plugin/README.md +158 -0
- package/references/minimem/claude-plugin/commands/recall.md +47 -0
- package/references/minimem/claude-plugin/commands/remember.md +41 -0
- package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
- package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
- package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
- package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
- package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
- package/references/minimem/media/banner.png +0 -0
- package/references/minimem/package-lock.json +5373 -0
- package/references/minimem/package.json +76 -0
- package/references/minimem/scripts/postbuild.js +49 -0
- package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
- package/references/minimem/src/__tests__/errors.test.ts +265 -0
- package/references/minimem/src/__tests__/helpers.ts +199 -0
- package/references/minimem/src/__tests__/internal.test.ts +407 -0
- package/references/minimem/src/__tests__/knowledge-frontmatter.test.ts +148 -0
- package/references/minimem/src/__tests__/knowledge.test.ts +148 -0
- package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
- package/references/minimem/src/__tests__/session.test.ts +190 -0
- package/references/minimem/src/cli/__tests__/commands.test.ts +760 -0
- package/references/minimem/src/cli/__tests__/contained-layout.test.ts +286 -0
- package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
- package/references/minimem/src/cli/commands/append.ts +76 -0
- package/references/minimem/src/cli/commands/config.ts +262 -0
- package/references/minimem/src/cli/commands/conflicts.ts +415 -0
- package/references/minimem/src/cli/commands/daemon.ts +169 -0
- package/references/minimem/src/cli/commands/index.ts +12 -0
- package/references/minimem/src/cli/commands/init.ts +166 -0
- package/references/minimem/src/cli/commands/mcp.ts +221 -0
- package/references/minimem/src/cli/commands/push-pull.ts +213 -0
- package/references/minimem/src/cli/commands/search.ts +223 -0
- package/references/minimem/src/cli/commands/status.ts +84 -0
- package/references/minimem/src/cli/commands/store.ts +189 -0
- package/references/minimem/src/cli/commands/sync-init.ts +290 -0
- package/references/minimem/src/cli/commands/sync.ts +70 -0
- package/references/minimem/src/cli/commands/upsert.ts +197 -0
- package/references/minimem/src/cli/config.ts +611 -0
- package/references/minimem/src/cli/index.ts +299 -0
- package/references/minimem/src/cli/shared.ts +189 -0
- package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
- package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
- package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
- package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
- package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
- package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
- package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
- package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
- package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
- package/references/minimem/src/cli/sync/central.ts +292 -0
- package/references/minimem/src/cli/sync/conflicts.ts +205 -0
- package/references/minimem/src/cli/sync/daemon.ts +407 -0
- package/references/minimem/src/cli/sync/detection.ts +138 -0
- package/references/minimem/src/cli/sync/index.ts +107 -0
- package/references/minimem/src/cli/sync/operations.ts +373 -0
- package/references/minimem/src/cli/sync/registry.ts +279 -0
- package/references/minimem/src/cli/sync/state.ts +358 -0
- package/references/minimem/src/cli/sync/validation.ts +206 -0
- package/references/minimem/src/cli/sync/watcher.ts +237 -0
- package/references/minimem/src/cli/version.ts +34 -0
- package/references/minimem/src/core/index.ts +9 -0
- package/references/minimem/src/core/indexer.ts +628 -0
- package/references/minimem/src/core/searcher.ts +221 -0
- package/references/minimem/src/db/schema.ts +183 -0
- package/references/minimem/src/db/sqlite-vec.ts +24 -0
- package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
- package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
- package/references/minimem/src/embeddings/batch-openai.ts +409 -0
- package/references/minimem/src/embeddings/embeddings.ts +434 -0
- package/references/minimem/src/index.ts +132 -0
- package/references/minimem/src/internal.ts +299 -0
- package/references/minimem/src/minimem.ts +1291 -0
- package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
- package/references/minimem/src/search/graph.ts +234 -0
- package/references/minimem/src/search/hybrid.ts +151 -0
- package/references/minimem/src/search/search.ts +256 -0
- package/references/minimem/src/server/__tests__/mcp.test.ts +347 -0
- package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
- package/references/minimem/src/server/mcp.ts +326 -0
- package/references/minimem/src/server/tools.ts +720 -0
- package/references/minimem/src/session.ts +460 -0
- package/references/minimem/src/store/__tests__/manifest.test.ts +177 -0
- package/references/minimem/src/store/__tests__/materialize.test.ts +52 -0
- package/references/minimem/src/store/__tests__/store-graph.test.ts +228 -0
- package/references/minimem/src/store/index.ts +27 -0
- package/references/minimem/src/store/manifest.ts +203 -0
- package/references/minimem/src/store/materialize.ts +185 -0
- package/references/minimem/src/store/store-graph.ts +252 -0
- package/references/minimem/tsconfig.json +19 -0
- package/references/minimem/tsup.config.ts +26 -0
- package/references/minimem/vitest.config.ts +29 -0
- package/references/openteams/src/cli/generate.ts +23 -1
- package/references/openteams/src/generators/agent-prompt-generator.test.ts +94 -0
- package/references/openteams/src/generators/agent-prompt-generator.ts +42 -13
- package/references/openteams/src/generators/package-generator.ts +9 -1
- package/references/openteams/src/generators/skill-generator.test.ts +28 -0
- package/references/openteams/src/generators/skill-generator.ts +10 -4
- package/references/skill-tree/.claude/settings.json +6 -0
- package/references/skill-tree/.sudocode/issues.jsonl +19 -0
- package/references/skill-tree/.sudocode/specs.jsonl +3 -0
- package/references/skill-tree/CLAUDE.md +132 -0
- package/references/skill-tree/README.md +396 -0
- package/references/skill-tree/docs/GAPS_v1.md +221 -0
- package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
- package/references/skill-tree/docs/TODOS.md +91 -0
- package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
- package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
- package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
- package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
- package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
- package/references/skill-tree/docs/scraper/README.md +170 -0
- package/references/skill-tree/examples/basic-usage.ts +157 -0
- package/references/skill-tree/package-lock.json +1852 -0
- package/references/skill-tree/package.json +66 -0
- package/references/skill-tree/plan.md +78 -0
- package/references/skill-tree/scraper/README.md +123 -0
- package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
- package/references/skill-tree/scraper/docs/PLAN.md +336 -0
- package/references/skill-tree/scraper/drizzle.config.ts +10 -0
- package/references/skill-tree/scraper/package-lock.json +6329 -0
- package/references/skill-tree/scraper/package.json +68 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
- package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
- package/references/skill-tree/scraper/tsup.config.ts +14 -0
- package/references/skill-tree/scraper/vitest.config.ts +17 -0
- package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
- package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
- package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
- package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
- package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
- package/references/skill-tree/test/run-all.ts +106 -0
- package/references/skill-tree/test/utils.ts +128 -0
- package/references/skill-tree/vitest.config.ts +16 -0
- package/references/swarmkit/src/commands/init/phases/configure.ts +0 -22
- package/references/swarmkit/src/commands/init/phases/global-setup.ts +5 -3
- package/references/swarmkit/src/commands/init/wizard.ts +2 -2
- package/references/swarmkit/src/packages/setup.test.ts +53 -7
- package/references/swarmkit/src/packages/setup.ts +37 -1
- package/scripts/bootstrap.mjs +26 -1
- package/scripts/generate-agents.mjs +5 -1
- package/scripts/map-hook.mjs +97 -64
- package/scripts/map-sidecar.mjs +179 -25
- package/scripts/team-loader.mjs +12 -41
- package/skills/swarm/SKILL.md +89 -25
- package/src/__tests__/agent-generator.test.mjs +6 -13
- package/src/__tests__/bootstrap.test.mjs +124 -1
- package/src/__tests__/config.test.mjs +200 -27
- package/src/__tests__/e2e-live-map.test.mjs +536 -0
- package/src/__tests__/e2e-mesh-sidecar.test.mjs +570 -0
- package/src/__tests__/e2e-native-task-hooks.test.mjs +376 -0
- package/src/__tests__/e2e-sidecar-bridge.test.mjs +477 -0
- package/src/__tests__/helpers.mjs +13 -0
- package/src/__tests__/inbox.test.mjs +22 -89
- package/src/__tests__/index.test.mjs +35 -9
- package/src/__tests__/integration.test.mjs +513 -0
- package/src/__tests__/map-events.test.mjs +514 -150
- package/src/__tests__/mesh-connection.test.mjs +308 -0
- package/src/__tests__/opentasks-client.test.mjs +517 -0
- package/src/__tests__/paths.test.mjs +185 -41
- package/src/__tests__/sidecar-client.test.mjs +35 -0
- package/src/__tests__/sidecar-server.test.mjs +124 -0
- package/src/__tests__/skilltree-client.test.mjs +80 -0
- package/src/agent-generator.mjs +104 -33
- package/src/bootstrap.mjs +150 -10
- package/src/config.mjs +81 -17
- package/src/context-output.mjs +58 -8
- package/src/inbox.mjs +9 -54
- package/src/index.mjs +39 -8
- package/src/map-connection.mjs +4 -3
- package/src/map-events.mjs +350 -80
- package/src/mesh-connection.mjs +148 -0
- package/src/opentasks-client.mjs +269 -0
- package/src/paths.mjs +182 -27
- package/src/sessionlog.mjs +14 -9
- package/src/sidecar-client.mjs +81 -27
- package/src/sidecar-server.mjs +175 -16
- package/src/skilltree-client.mjs +173 -0
- package/src/template.mjs +68 -4
- package/vitest.config.mjs +1 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end tests: Two real agent-inbox instances communicating
|
|
3
|
+
* through real agentic-mesh MeshPeer federation.
|
|
4
|
+
*
|
|
5
|
+
* Unlike the mock-based e2e tests, these use real agentic-mesh components:
|
|
6
|
+
* - MeshPeer.createEmbedded() for in-process MAP servers
|
|
7
|
+
* - Real FederationGateway with JSON-RPC handshake + envelope routing
|
|
8
|
+
* - Real MapServer agent registration + DeliveryHandler dispatch
|
|
9
|
+
* - InMemoryMapStream pairs simulate the transport layer
|
|
10
|
+
*
|
|
11
|
+
* The only mock is the transport: instead of real networking (Nebula/Tailscale),
|
|
12
|
+
* we use linked in-memory MapStream pairs so frames written on one side appear
|
|
13
|
+
* instantly on the other. Everything above the transport is real code.
|
|
14
|
+
*
|
|
15
|
+
* Message flow:
|
|
16
|
+
* Agent on System-A → router.routeMessage()
|
|
17
|
+
* → ConnectionManager.route() → inboxMessageToMap()
|
|
18
|
+
* → real FederationGateway.route() → federation envelope → MapStream
|
|
19
|
+
* → [in-memory linked stream] →
|
|
20
|
+
* → real FederationGateway.processIncomingEnvelope()
|
|
21
|
+
* → real MapServer.MessageRouter.send()
|
|
22
|
+
* → DeliveryBridge.deliverToAgent()
|
|
23
|
+
* → mapMessageToInbox() → storage.putMessage()
|
|
24
|
+
* → events.emit("message.created") → traceability
|
|
25
|
+
*
|
|
26
|
+
* Note: The real FederationGateway prefixes sender IDs with the source
|
|
27
|
+
* system, e.g. "alice" becomes "system-a:alice" when delivered to system-b.
|
|
28
|
+
* This is standard MAP federation behavior.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
32
|
+
import { EventEmitter } from "node:events";
|
|
33
|
+
import { MeshPeer, type MapStream, type MapFrame } from "agentic-mesh";
|
|
34
|
+
import { InMemoryStorage } from "../../src/storage/memory.js";
|
|
35
|
+
import { MessageRouter } from "../../src/router/message-router.js";
|
|
36
|
+
import { TraceabilityLayer } from "../../src/traceability/traceability.js";
|
|
37
|
+
import { MapClient } from "../../src/map/map-client.js";
|
|
38
|
+
import type { MeshPeerLike } from "../../src/map/map-client.js";
|
|
39
|
+
import { ConnectionManager } from "../../src/federation/connection-manager.js";
|
|
40
|
+
import { DeliveryBridge } from "../../src/mesh/delivery-bridge.js";
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// InMemoryMapStream: linked pair for in-process federation
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
class InMemoryMapStream extends EventEmitter implements MapStream {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
private _isOpen = false;
|
|
49
|
+
private _state: "connecting" | "connected" | "disconnecting" | "disconnected" | "error" =
|
|
50
|
+
"disconnected";
|
|
51
|
+
private _frameQueue: MapFrame[] = [];
|
|
52
|
+
private _waitingReaders: Array<(frame: MapFrame | null) => void> = [];
|
|
53
|
+
private _peer: InMemoryMapStream | null = null;
|
|
54
|
+
|
|
55
|
+
constructor(id: string) {
|
|
56
|
+
super();
|
|
57
|
+
this.id = id;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get isOpen(): boolean {
|
|
61
|
+
return this._isOpen;
|
|
62
|
+
}
|
|
63
|
+
get state(): "connecting" | "connected" | "disconnecting" | "disconnected" | "error" {
|
|
64
|
+
return this._state;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
link(peer: InMemoryMapStream): void {
|
|
68
|
+
this._peer = peer;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
open(): void {
|
|
72
|
+
this._isOpen = true;
|
|
73
|
+
this._state = "connected";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async write(frame: MapFrame): Promise<void> {
|
|
77
|
+
if (!this._isOpen) throw new Error("Stream closed");
|
|
78
|
+
if (this._peer) {
|
|
79
|
+
this._peer._enqueue(frame);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_enqueue(frame: MapFrame): void {
|
|
84
|
+
if (this._waitingReaders.length > 0) {
|
|
85
|
+
const resolve = this._waitingReaders.shift()!;
|
|
86
|
+
resolve(frame);
|
|
87
|
+
} else {
|
|
88
|
+
this._frameQueue.push(frame);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async close(): Promise<void> {
|
|
93
|
+
this._isOpen = false;
|
|
94
|
+
this._state = "disconnected";
|
|
95
|
+
for (const resolve of this._waitingReaders) {
|
|
96
|
+
resolve(null);
|
|
97
|
+
}
|
|
98
|
+
this._waitingReaders = [];
|
|
99
|
+
this.emit("close");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[Symbol.asyncIterator](): AsyncIterator<MapFrame> {
|
|
103
|
+
const self = this;
|
|
104
|
+
return {
|
|
105
|
+
async next(): Promise<IteratorResult<MapFrame>> {
|
|
106
|
+
if (!self._isOpen && self._frameQueue.length === 0) {
|
|
107
|
+
return { done: true, value: undefined };
|
|
108
|
+
}
|
|
109
|
+
if (self._frameQueue.length > 0) {
|
|
110
|
+
return { done: false, value: self._frameQueue.shift()! };
|
|
111
|
+
}
|
|
112
|
+
const frame = await new Promise<MapFrame | null>((resolve) => {
|
|
113
|
+
self._waitingReaders.push(resolve);
|
|
114
|
+
});
|
|
115
|
+
if (frame === null) return { done: true, value: undefined };
|
|
116
|
+
return { done: false, value: frame };
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function createLinkedMapStreamPair(): [InMemoryMapStream, InMemoryMapStream] {
|
|
123
|
+
const a = new InMemoryMapStream("stream-a");
|
|
124
|
+
const b = new InMemoryMapStream("stream-b");
|
|
125
|
+
a.link(b);
|
|
126
|
+
b.link(a);
|
|
127
|
+
a.open();
|
|
128
|
+
b.open();
|
|
129
|
+
return [a, b];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Helper: wire up a full agent-inbox stack on a real MeshPeer
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
interface InboxStack {
|
|
137
|
+
peerId: string;
|
|
138
|
+
systemId: string;
|
|
139
|
+
storage: InMemoryStorage;
|
|
140
|
+
events: EventEmitter;
|
|
141
|
+
router: MessageRouter;
|
|
142
|
+
traceability: TraceabilityLayer;
|
|
143
|
+
mapClient: MapClient;
|
|
144
|
+
federation: ConnectionManager;
|
|
145
|
+
meshPeer: MeshPeer;
|
|
146
|
+
stop(): Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function createInboxOnMeshPeer(
|
|
150
|
+
meshPeer: MeshPeer,
|
|
151
|
+
scope = "default"
|
|
152
|
+
): Promise<InboxStack> {
|
|
153
|
+
const storage = new InMemoryStorage();
|
|
154
|
+
const events = new EventEmitter();
|
|
155
|
+
const router = new MessageRouter(storage, events, scope);
|
|
156
|
+
const traceability = new TraceabilityLayer(storage, events);
|
|
157
|
+
|
|
158
|
+
const mapClient = new MapClient(storage, router, events);
|
|
159
|
+
const meshSystemId = await mapClient.connectViaMesh(
|
|
160
|
+
meshPeer as unknown as MeshPeerLike
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Install DeliveryBridge on the real MapServer
|
|
164
|
+
const bridge = new DeliveryBridge(storage, events, scope);
|
|
165
|
+
meshPeer.server.setDeliveryHandler(bridge);
|
|
166
|
+
|
|
167
|
+
// Set up federation (ConnectionManager will use meshPeer.federateWith())
|
|
168
|
+
const federation = new ConnectionManager(
|
|
169
|
+
events,
|
|
170
|
+
{
|
|
171
|
+
systemId: meshSystemId,
|
|
172
|
+
trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
|
|
173
|
+
},
|
|
174
|
+
{ meshPeer: meshPeer as unknown as MeshPeerLike }
|
|
175
|
+
);
|
|
176
|
+
router.setFederation(federation);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
peerId: meshPeer.peerId,
|
|
180
|
+
systemId: meshSystemId,
|
|
181
|
+
storage,
|
|
182
|
+
events,
|
|
183
|
+
router,
|
|
184
|
+
traceability,
|
|
185
|
+
mapClient,
|
|
186
|
+
federation,
|
|
187
|
+
meshPeer,
|
|
188
|
+
async stop() {
|
|
189
|
+
await federation.destroy();
|
|
190
|
+
await mapClient.disconnect();
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Tests
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
describe("E2E: Two agent-inboxes over real agentic-mesh MeshPeers", () => {
|
|
200
|
+
let peerA: MeshPeer;
|
|
201
|
+
let peerB: MeshPeer;
|
|
202
|
+
let streams: InMemoryMapStream[];
|
|
203
|
+
let inboxA: InboxStack;
|
|
204
|
+
let inboxB: InboxStack;
|
|
205
|
+
|
|
206
|
+
beforeEach(async () => {
|
|
207
|
+
// Create two real embedded MeshPeers (in-process, no networking)
|
|
208
|
+
peerA = MeshPeer.createEmbedded({ peerId: "system-a" });
|
|
209
|
+
peerB = MeshPeer.createEmbedded({ peerId: "system-b" });
|
|
210
|
+
await peerA.start();
|
|
211
|
+
await peerB.start();
|
|
212
|
+
|
|
213
|
+
// Create linked MapStream pairs and connect FederationGateways.
|
|
214
|
+
// This must happen BEFORE wiring agent-inbox, so that when
|
|
215
|
+
// ConnectionManager.federate() calls federateWith(), the gateway
|
|
216
|
+
// already exists and is connected.
|
|
217
|
+
const [streamAtoB, streamBtoA] = createLinkedMapStreamPair();
|
|
218
|
+
streams = [streamAtoB, streamBtoA];
|
|
219
|
+
const gwA = await peerA.federateWith("system-b");
|
|
220
|
+
const gwB = await peerB.federateWith("system-a");
|
|
221
|
+
|
|
222
|
+
// Connect concurrently — each gateway sends a JSON-RPC handshake
|
|
223
|
+
// that the other must respond to
|
|
224
|
+
await Promise.all([gwA.connect(streamAtoB), gwB.connect(streamBtoA)]);
|
|
225
|
+
|
|
226
|
+
// Wire up full agent-inbox stacks on each peer
|
|
227
|
+
inboxA = await createInboxOnMeshPeer(peerA);
|
|
228
|
+
inboxB = await createInboxOnMeshPeer(peerB);
|
|
229
|
+
|
|
230
|
+
// Register local agents on each system.
|
|
231
|
+
// Agents must be registered BOTH on the real MapServer (so the real
|
|
232
|
+
// MessageRouter can resolve addresses) AND in inbox storage (so
|
|
233
|
+
// agent-inbox knows they're local).
|
|
234
|
+
await peerA.createAgent({ agentId: "alice", name: "Alice" });
|
|
235
|
+
inboxA.storage.putAgent({
|
|
236
|
+
agent_id: "alice",
|
|
237
|
+
scope: "default",
|
|
238
|
+
status: "active",
|
|
239
|
+
metadata: {},
|
|
240
|
+
registered_at: new Date().toISOString(),
|
|
241
|
+
last_active_at: new Date().toISOString(),
|
|
242
|
+
});
|
|
243
|
+
await peerB.createAgent({ agentId: "bob", name: "Bob" });
|
|
244
|
+
inboxB.storage.putAgent({
|
|
245
|
+
agent_id: "bob",
|
|
246
|
+
scope: "default",
|
|
247
|
+
status: "active",
|
|
248
|
+
metadata: {},
|
|
249
|
+
registered_at: new Date().toISOString(),
|
|
250
|
+
last_active_at: new Date().toISOString(),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Federate — ConnectionManager picks up the already-connected gateways
|
|
254
|
+
await inboxA.federation.federate({
|
|
255
|
+
systemId: "system-b",
|
|
256
|
+
meshPeerId: "system-b",
|
|
257
|
+
});
|
|
258
|
+
await inboxB.federation.federate({
|
|
259
|
+
systemId: "system-a",
|
|
260
|
+
meshPeerId: "system-a",
|
|
261
|
+
});
|
|
262
|
+
}, 15000);
|
|
263
|
+
|
|
264
|
+
afterEach(async () => {
|
|
265
|
+
await inboxA.stop();
|
|
266
|
+
await inboxB.stop();
|
|
267
|
+
// Close streams BEFORE stopping peers to unblock async iterators
|
|
268
|
+
for (const stream of streams) {
|
|
269
|
+
await stream.close();
|
|
270
|
+
}
|
|
271
|
+
await peerA.stop();
|
|
272
|
+
await peerB.stop();
|
|
273
|
+
}, 15000);
|
|
274
|
+
|
|
275
|
+
// -----------------------------------------------------------------------
|
|
276
|
+
// Basic message delivery
|
|
277
|
+
// -----------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
it("should deliver a message from alice@system-a to bob@system-b", async () => {
|
|
280
|
+
await inboxA.router.routeMessage({
|
|
281
|
+
from: "alice",
|
|
282
|
+
to: "bob@system-b",
|
|
283
|
+
payload: "Hello Bob, this is Alice!",
|
|
284
|
+
importance: "normal",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
288
|
+
|
|
289
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
290
|
+
expect(bobInbox).toHaveLength(1);
|
|
291
|
+
expect(bobInbox[0].content).toEqual({
|
|
292
|
+
type: "text",
|
|
293
|
+
text: "Hello Bob, this is Alice!",
|
|
294
|
+
});
|
|
295
|
+
// Real FederationGateway prefixes sender with source system
|
|
296
|
+
expect(bobInbox[0].sender_id).toContain("alice");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("should deliver a message from bob@system-b to alice@system-a", async () => {
|
|
300
|
+
await inboxB.router.routeMessage({
|
|
301
|
+
from: "bob",
|
|
302
|
+
to: "alice@system-a",
|
|
303
|
+
payload: "Hey Alice!",
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
307
|
+
|
|
308
|
+
const aliceInbox = inboxA.storage.getInbox("alice");
|
|
309
|
+
expect(aliceInbox).toHaveLength(1);
|
|
310
|
+
expect(aliceInbox[0].content).toEqual({ type: "text", text: "Hey Alice!" });
|
|
311
|
+
expect(aliceInbox[0].sender_id).toContain("bob");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// -----------------------------------------------------------------------
|
|
315
|
+
// Reply chain
|
|
316
|
+
// -----------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
it("should support a full reply chain: A→B→A", async () => {
|
|
319
|
+
// Alice sends to Bob
|
|
320
|
+
await inboxA.router.routeMessage({
|
|
321
|
+
from: "alice",
|
|
322
|
+
to: "bob@system-b",
|
|
323
|
+
payload: "Can you review PR #42?",
|
|
324
|
+
subject: "PR Review",
|
|
325
|
+
threadTag: "pr-42",
|
|
326
|
+
importance: "high",
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
330
|
+
|
|
331
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
332
|
+
expect(bobInbox).toHaveLength(1);
|
|
333
|
+
expect(bobInbox[0].subject).toBe("PR Review");
|
|
334
|
+
expect(bobInbox[0].importance).toBe("high");
|
|
335
|
+
|
|
336
|
+
// Bob replies to Alice
|
|
337
|
+
await inboxB.router.routeMessage({
|
|
338
|
+
from: "bob",
|
|
339
|
+
to: "alice@system-a",
|
|
340
|
+
payload: "LGTM, approved!",
|
|
341
|
+
subject: "PR Review",
|
|
342
|
+
threadTag: "pr-42",
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
346
|
+
|
|
347
|
+
const aliceInbox = inboxA.storage.getInbox("alice");
|
|
348
|
+
expect(aliceInbox).toHaveLength(1);
|
|
349
|
+
expect(aliceInbox[0].content).toEqual({
|
|
350
|
+
type: "text",
|
|
351
|
+
text: "LGTM, approved!",
|
|
352
|
+
});
|
|
353
|
+
expect(aliceInbox[0].subject).toBe("PR Review");
|
|
354
|
+
expect(aliceInbox[0].thread_tag).toBe("pr-42");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// -----------------------------------------------------------------------
|
|
358
|
+
// Subject / thread_tag / importance preservation
|
|
359
|
+
// -----------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
it("should preserve subject, thread_tag, and importance through real federation", async () => {
|
|
362
|
+
await inboxA.router.routeMessage({
|
|
363
|
+
from: "alice",
|
|
364
|
+
to: "bob@system-b",
|
|
365
|
+
payload: {
|
|
366
|
+
type: "data",
|
|
367
|
+
schema: "review",
|
|
368
|
+
data: { pr: 42, status: "open" },
|
|
369
|
+
},
|
|
370
|
+
subject: "Urgent: production hotfix",
|
|
371
|
+
threadTag: "hotfix-2024",
|
|
372
|
+
importance: "urgent",
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
376
|
+
|
|
377
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
378
|
+
expect(bobInbox).toHaveLength(1);
|
|
379
|
+
const msg = bobInbox[0];
|
|
380
|
+
expect(msg.subject).toBe("Urgent: production hotfix");
|
|
381
|
+
expect(msg.thread_tag).toBe("hotfix-2024");
|
|
382
|
+
expect(msg.importance).toBe("urgent");
|
|
383
|
+
expect(msg.content).toEqual({
|
|
384
|
+
type: "data",
|
|
385
|
+
schema: "review",
|
|
386
|
+
data: { pr: 42, status: "open" },
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// -----------------------------------------------------------------------
|
|
391
|
+
// Multiple agents
|
|
392
|
+
// -----------------------------------------------------------------------
|
|
393
|
+
|
|
394
|
+
it("should handle multiple agents sending messages in both directions", async () => {
|
|
395
|
+
inboxA.storage.putAgent({
|
|
396
|
+
agent_id: "carol",
|
|
397
|
+
scope: "default",
|
|
398
|
+
status: "active",
|
|
399
|
+
metadata: {},
|
|
400
|
+
registered_at: new Date().toISOString(),
|
|
401
|
+
last_active_at: new Date().toISOString(),
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
await inboxA.router.routeMessage({
|
|
405
|
+
from: "alice",
|
|
406
|
+
to: "bob@system-b",
|
|
407
|
+
payload: "Message 1 from alice",
|
|
408
|
+
});
|
|
409
|
+
await inboxA.router.routeMessage({
|
|
410
|
+
from: "carol",
|
|
411
|
+
to: "bob@system-b",
|
|
412
|
+
payload: "Message 2 from carol",
|
|
413
|
+
});
|
|
414
|
+
await inboxB.router.routeMessage({
|
|
415
|
+
from: "bob",
|
|
416
|
+
to: "alice@system-a",
|
|
417
|
+
payload: "Reply from bob",
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
421
|
+
|
|
422
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
423
|
+
expect(bobInbox).toHaveLength(2);
|
|
424
|
+
|
|
425
|
+
const aliceInbox = inboxA.storage.getInbox("alice");
|
|
426
|
+
expect(aliceInbox).toHaveLength(1);
|
|
427
|
+
expect(aliceInbox[0].sender_id).toContain("bob");
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// -----------------------------------------------------------------------
|
|
431
|
+
// Traceability
|
|
432
|
+
// -----------------------------------------------------------------------
|
|
433
|
+
|
|
434
|
+
it("should trigger traceability on both sides via real federation", async () => {
|
|
435
|
+
const traceSpy = vi.fn();
|
|
436
|
+
inboxB.events.on("message.created", traceSpy);
|
|
437
|
+
|
|
438
|
+
await inboxA.router.routeMessage({
|
|
439
|
+
from: "alice",
|
|
440
|
+
to: "bob@system-b",
|
|
441
|
+
payload: "traceability test",
|
|
442
|
+
threadTag: "trace-thread",
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
446
|
+
|
|
447
|
+
expect(traceSpy).toHaveBeenCalledTimes(1);
|
|
448
|
+
const delivered = traceSpy.mock.calls[0][0] as Message;
|
|
449
|
+
expect(delivered.sender_id).toContain("alice");
|
|
450
|
+
|
|
451
|
+
const conversations = inboxB.storage.listConversations("default");
|
|
452
|
+
expect(conversations.length).toBeGreaterThan(0);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// -----------------------------------------------------------------------
|
|
456
|
+
// Content types
|
|
457
|
+
// -----------------------------------------------------------------------
|
|
458
|
+
|
|
459
|
+
it("should deliver structured content types through real federation", async () => {
|
|
460
|
+
await inboxA.router.routeMessage({
|
|
461
|
+
from: "alice",
|
|
462
|
+
to: "bob@system-b",
|
|
463
|
+
payload: {
|
|
464
|
+
type: "event",
|
|
465
|
+
event: "deployment.started",
|
|
466
|
+
data: { version: "1.2.3" },
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
471
|
+
|
|
472
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
473
|
+
expect(bobInbox).toHaveLength(1);
|
|
474
|
+
expect(bobInbox[0].content).toEqual({
|
|
475
|
+
type: "event",
|
|
476
|
+
event: "deployment.started",
|
|
477
|
+
data: { version: "1.2.3" },
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// -----------------------------------------------------------------------
|
|
482
|
+
// Concurrent bidirectional messaging
|
|
483
|
+
// -----------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
it("should handle concurrent bidirectional messaging", async () => {
|
|
486
|
+
await Promise.all([
|
|
487
|
+
inboxA.router.routeMessage({
|
|
488
|
+
from: "alice",
|
|
489
|
+
to: "bob@system-b",
|
|
490
|
+
payload: "from A",
|
|
491
|
+
}),
|
|
492
|
+
inboxB.router.routeMessage({
|
|
493
|
+
from: "bob",
|
|
494
|
+
to: "alice@system-a",
|
|
495
|
+
payload: "from B",
|
|
496
|
+
}),
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
500
|
+
|
|
501
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
502
|
+
expect(bobInbox).toHaveLength(1);
|
|
503
|
+
expect(bobInbox[0].content).toEqual({ type: "text", text: "from A" });
|
|
504
|
+
|
|
505
|
+
const aliceInbox = inboxA.storage.getInbox("alice");
|
|
506
|
+
expect(aliceInbox).toHaveLength(1);
|
|
507
|
+
expect(aliceInbox[0].content).toEqual({ type: "text", text: "from B" });
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// -----------------------------------------------------------------------
|
|
511
|
+
// Importance levels
|
|
512
|
+
// -----------------------------------------------------------------------
|
|
513
|
+
|
|
514
|
+
it("should preserve all importance levels through real federation", async () => {
|
|
515
|
+
const levels = ["low", "normal", "high", "urgent"] as const;
|
|
516
|
+
|
|
517
|
+
for (const level of levels) {
|
|
518
|
+
await inboxA.router.routeMessage({
|
|
519
|
+
from: "alice",
|
|
520
|
+
to: "bob@system-b",
|
|
521
|
+
payload: `${level} message`,
|
|
522
|
+
importance: level,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
527
|
+
|
|
528
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
529
|
+
expect(bobInbox).toHaveLength(4);
|
|
530
|
+
|
|
531
|
+
for (const level of levels) {
|
|
532
|
+
const msg = bobInbox.find(
|
|
533
|
+
(m) => m.content.type === "text" && m.content.text === `${level} message`
|
|
534
|
+
);
|
|
535
|
+
expect(msg).toBeDefined();
|
|
536
|
+
expect(msg!.importance).toBe(level);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// -----------------------------------------------------------------------
|
|
541
|
+
// Custom metadata
|
|
542
|
+
// -----------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
it("should preserve custom metadata through real federation", async () => {
|
|
545
|
+
await inboxA.router.routeMessage({
|
|
546
|
+
from: "alice",
|
|
547
|
+
to: "bob@system-b",
|
|
548
|
+
payload: "metadata test",
|
|
549
|
+
metadata: {
|
|
550
|
+
customTag: "important",
|
|
551
|
+
priority_override: true,
|
|
552
|
+
nested: { key: "value" },
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
557
|
+
|
|
558
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
559
|
+
expect(bobInbox).toHaveLength(1);
|
|
560
|
+
const meta = bobInbox[0].metadata;
|
|
561
|
+
expect(meta.customTag).toBe("important");
|
|
562
|
+
expect(meta.priority_override).toBe(true);
|
|
563
|
+
expect(meta.nested).toEqual({ key: "value" });
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// -----------------------------------------------------------------------
|
|
567
|
+
// Rapid-fire messages
|
|
568
|
+
// -----------------------------------------------------------------------
|
|
569
|
+
|
|
570
|
+
it("should handle rapid sequential messages through real federation", async () => {
|
|
571
|
+
const count = 10;
|
|
572
|
+
for (let i = 0; i < count; i++) {
|
|
573
|
+
await inboxA.router.routeMessage({
|
|
574
|
+
from: "alice",
|
|
575
|
+
to: "bob@system-b",
|
|
576
|
+
payload: `message ${i}`,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
581
|
+
|
|
582
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
583
|
+
expect(bobInbox).toHaveLength(count);
|
|
584
|
+
|
|
585
|
+
const texts = bobInbox
|
|
586
|
+
.map((m) => (m.content.type === "text" ? m.content.text : ""))
|
|
587
|
+
.sort();
|
|
588
|
+
for (let i = 0; i < count; i++) {
|
|
589
|
+
expect(texts).toContain(`message ${i}`);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// -----------------------------------------------------------------------
|
|
594
|
+
// Gateway state
|
|
595
|
+
// -----------------------------------------------------------------------
|
|
596
|
+
|
|
597
|
+
it("should report gateways as connected on both sides", () => {
|
|
598
|
+
expect(inboxA.federation.hasGateway("system-b")).toBe(true);
|
|
599
|
+
expect(inboxB.federation.hasGateway("system-a")).toBe(true);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// -----------------------------------------------------------------------
|
|
603
|
+
// Local messages bypass federation
|
|
604
|
+
// -----------------------------------------------------------------------
|
|
605
|
+
|
|
606
|
+
it("should deliver local messages without federation", async () => {
|
|
607
|
+
inboxA.storage.putAgent({
|
|
608
|
+
agent_id: "carol",
|
|
609
|
+
scope: "default",
|
|
610
|
+
status: "active",
|
|
611
|
+
metadata: {},
|
|
612
|
+
registered_at: new Date().toISOString(),
|
|
613
|
+
last_active_at: new Date().toISOString(),
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
await inboxA.router.routeMessage({
|
|
617
|
+
from: "alice",
|
|
618
|
+
to: "carol",
|
|
619
|
+
payload: "local message",
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
const carolInbox = inboxA.storage.getInbox("carol");
|
|
623
|
+
expect(carolInbox).toHaveLength(1);
|
|
624
|
+
expect(carolInbox[0].sender_id).toBe("alice");
|
|
625
|
+
|
|
626
|
+
const bobInbox = inboxB.storage.getInbox("bob");
|
|
627
|
+
expect(bobInbox).toHaveLength(0);
|
|
628
|
+
});
|
|
629
|
+
});
|