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,573 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as crypto from "node:crypto";
|
|
6
|
+
import type {
|
|
7
|
+
FederatedAddress,
|
|
8
|
+
FederationConfig,
|
|
9
|
+
FederationLink,
|
|
10
|
+
FederationPeerConfig,
|
|
11
|
+
Message,
|
|
12
|
+
SystemId,
|
|
13
|
+
} from "../types.js";
|
|
14
|
+
import type {
|
|
15
|
+
MapConnection,
|
|
16
|
+
MapAgentConnectionClass,
|
|
17
|
+
IncomingMapMessage,
|
|
18
|
+
MeshPeerLike,
|
|
19
|
+
MeshFederationGateway,
|
|
20
|
+
} from "../map/map-client.js";
|
|
21
|
+
import type { MeshConnector } from "../mesh/mesh-connector.js";
|
|
22
|
+
import { inboxMessageToMap } from "../mesh/type-mapper.js";
|
|
23
|
+
import { RoutingEngine } from "./routing-engine.js";
|
|
24
|
+
import { DeliveryQueue } from "./delivery-queue.js";
|
|
25
|
+
import { TrustManager } from "./trust.js";
|
|
26
|
+
import { parseAddress, isRemoteAddress } from "./address.js";
|
|
27
|
+
|
|
28
|
+
export interface DeliveryResult {
|
|
29
|
+
delivered: boolean;
|
|
30
|
+
peerId?: string;
|
|
31
|
+
queued?: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Callback for handling incoming federation messages.
|
|
37
|
+
* Wired to router.routeMessage() by index.ts.
|
|
38
|
+
*/
|
|
39
|
+
export type IncomingMessageHandler = (incoming: {
|
|
40
|
+
from: string;
|
|
41
|
+
peerId: string;
|
|
42
|
+
payload: unknown;
|
|
43
|
+
meta?: Record<string, unknown>;
|
|
44
|
+
}) => void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Manages MAP connections and federation peer links.
|
|
48
|
+
*
|
|
49
|
+
* Coordinates routing, delivery queuing, and trust enforcement
|
|
50
|
+
* for cross-system messaging. When an SDK connect function is provided,
|
|
51
|
+
* opens real MAP connections to federation peers for actual message transport.
|
|
52
|
+
*/
|
|
53
|
+
export class ConnectionManager {
|
|
54
|
+
private peers = new Map<string, FederationLink>();
|
|
55
|
+
private connections = new Map<string, MapConnection>();
|
|
56
|
+
private gateways = new Map<string, MeshFederationGateway>();
|
|
57
|
+
private systemId: SystemId;
|
|
58
|
+
private sdkClass: MapAgentConnectionClass | null;
|
|
59
|
+
private meshConnector: MeshConnector | null;
|
|
60
|
+
private meshPeer: MeshPeerLike | null;
|
|
61
|
+
private onIncoming: IncomingMessageHandler | null;
|
|
62
|
+
readonly routing: RoutingEngine;
|
|
63
|
+
readonly queue: DeliveryQueue;
|
|
64
|
+
readonly trust: TrustManager;
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
private events: EventEmitter,
|
|
68
|
+
private config: FederationConfig = {},
|
|
69
|
+
opts?: {
|
|
70
|
+
sdkClass?: MapAgentConnectionClass;
|
|
71
|
+
meshConnector?: MeshConnector;
|
|
72
|
+
/** Full MeshPeer for Phase 2 FederationGateway delegation */
|
|
73
|
+
meshPeer?: MeshPeerLike;
|
|
74
|
+
onIncomingMessage?: IncomingMessageHandler;
|
|
75
|
+
}
|
|
76
|
+
) {
|
|
77
|
+
this.systemId = this.resolveSystemId();
|
|
78
|
+
this.routing = new RoutingEngine(events, config.routing);
|
|
79
|
+
this.queue = new DeliveryQueue(events, config.deliveryQueue);
|
|
80
|
+
this.trust = new TrustManager(config.trust);
|
|
81
|
+
this.sdkClass = opts?.sdkClass ?? null;
|
|
82
|
+
this.meshConnector = opts?.meshConnector ?? null;
|
|
83
|
+
this.meshPeer = opts?.meshPeer ?? null;
|
|
84
|
+
this.onIncoming = opts?.onIncomingMessage ?? null;
|
|
85
|
+
|
|
86
|
+
// Wire up flush-on-reconnect
|
|
87
|
+
this.events.on("federation.connected", (peerId: string) => {
|
|
88
|
+
if (config.deliveryQueue?.flushOnReconnect !== false) {
|
|
89
|
+
const queued = this.queue.flush(peerId);
|
|
90
|
+
if (queued.length > 0) {
|
|
91
|
+
this.events.emit("federation.flushing", {
|
|
92
|
+
peerId,
|
|
93
|
+
count: queued.length,
|
|
94
|
+
});
|
|
95
|
+
for (const entry of queued) {
|
|
96
|
+
this.route(entry.message).catch(() => {
|
|
97
|
+
// Re-queue if delivery still fails
|
|
98
|
+
this.queue.enqueue(peerId, entry.message);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the resolved system ID for this Agent Inbox instance.
|
|
108
|
+
*/
|
|
109
|
+
getSystemId(): SystemId {
|
|
110
|
+
return this.systemId;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Establish federation with a peer. Uses MAP federation/connect protocol.
|
|
115
|
+
* If an SDK class was injected, opens a real MAP connection to the peer.
|
|
116
|
+
* Returns the federation link, or throws if trust check fails.
|
|
117
|
+
*/
|
|
118
|
+
async federate(peer: FederationPeerConfig): Promise<FederationLink> {
|
|
119
|
+
if (!peer.url && !peer.meshPeerId) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Federation peer "${peer.systemId}" must have either url or meshPeerId`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Trust check
|
|
126
|
+
if (!this.trust.canConnect(peer.systemId)) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Federation denied: system "${peer.systemId}" not in allowed servers list`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let conn: MapConnection | undefined;
|
|
133
|
+
let transport: "websocket" | "mesh" = "websocket";
|
|
134
|
+
|
|
135
|
+
if (peer.meshPeerId && this.meshPeer) {
|
|
136
|
+
// Phase 2: Full MeshPeer integration with FederationGateway (v0.2.0+)
|
|
137
|
+
transport = "mesh";
|
|
138
|
+
try {
|
|
139
|
+
// Try to get an existing gateway first (e.g. pre-connected in test setup),
|
|
140
|
+
// otherwise create one via federateWith()
|
|
141
|
+
let gateway: MeshFederationGateway | undefined =
|
|
142
|
+
this.meshPeer.getFederationGateway(peer.systemId);
|
|
143
|
+
if (!gateway) {
|
|
144
|
+
gateway = await this.meshPeer.federateWith(peer.systemId, {
|
|
145
|
+
localSystemId: this.meshPeer.peerId,
|
|
146
|
+
remoteSystemId: peer.systemId,
|
|
147
|
+
buffer: { enabled: true, maxMessages: 1000 },
|
|
148
|
+
routing: { maxHops: 5, trackPath: true },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.gateways.set(peer.systemId, gateway);
|
|
153
|
+
|
|
154
|
+
// Listen for incoming federated messages
|
|
155
|
+
gateway.on("message:received", (envelope: unknown) => {
|
|
156
|
+
const env = envelope as { payload?: { from?: string; payload?: unknown; meta?: Record<string, unknown> } };
|
|
157
|
+
if (env.payload) {
|
|
158
|
+
this.handlePeerMessage(peer.systemId, {
|
|
159
|
+
from: env.payload.from ?? peer.systemId,
|
|
160
|
+
payload: env.payload.payload,
|
|
161
|
+
meta: env.payload.meta,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
} catch (err) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Mesh federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
} else if (peer.meshPeerId && this.meshConnector) {
|
|
171
|
+
// Phase 1 fallback: Mesh transport via raw channel (no MeshPeer)
|
|
172
|
+
transport = "mesh";
|
|
173
|
+
try {
|
|
174
|
+
conn = await this.meshConnector.connect(peer.meshPeerId);
|
|
175
|
+
|
|
176
|
+
conn.onMessage((msg: IncomingMapMessage) => {
|
|
177
|
+
this.handlePeerMessage(peer.systemId, msg);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this.connections.set(peer.systemId, conn);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Mesh federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
} else if (peer.url && this.sdkClass) {
|
|
187
|
+
// WebSocket transport — connect via MAP SDK
|
|
188
|
+
try {
|
|
189
|
+
conn = await this.sdkClass.connect(peer.url, {
|
|
190
|
+
name: this.systemId.id,
|
|
191
|
+
role: "gateway",
|
|
192
|
+
scopes: ["federation"],
|
|
193
|
+
capabilities: { trajectory: { canReport: false } },
|
|
194
|
+
metadata: {
|
|
195
|
+
systemId: this.systemId.id,
|
|
196
|
+
type: "federation-gateway",
|
|
197
|
+
peerSystemId: peer.systemId,
|
|
198
|
+
},
|
|
199
|
+
reconnection: {
|
|
200
|
+
enabled: true,
|
|
201
|
+
maxRetries: 5,
|
|
202
|
+
baseDelayMs: 1000,
|
|
203
|
+
maxDelayMs: 30000,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
conn.onMessage((msg: IncomingMapMessage) => {
|
|
208
|
+
this.handlePeerMessage(peer.systemId, msg);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
this.connections.set(peer.systemId, conn);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const link: FederationLink = {
|
|
220
|
+
peerId: peer.systemId,
|
|
221
|
+
sessionId: crypto.randomUUID(),
|
|
222
|
+
status: "connected",
|
|
223
|
+
exposure: peer.exposure ?? { agents: "all" },
|
|
224
|
+
url: peer.url ?? "",
|
|
225
|
+
transport,
|
|
226
|
+
connectedAt: new Date().toISOString(),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
this.peers.set(peer.systemId, link);
|
|
230
|
+
this.events.emit("federation.connected", peer.systemId);
|
|
231
|
+
return link;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Disconnect from a federation peer.
|
|
236
|
+
* Closes the real MAP connection if one exists.
|
|
237
|
+
*/
|
|
238
|
+
async disconnect(peerId: string): Promise<void> {
|
|
239
|
+
const link = this.peers.get(peerId);
|
|
240
|
+
if (!link) return;
|
|
241
|
+
|
|
242
|
+
// Close real connection
|
|
243
|
+
const conn = this.connections.get(peerId);
|
|
244
|
+
if (conn) {
|
|
245
|
+
try {
|
|
246
|
+
await conn.disconnect();
|
|
247
|
+
} catch {
|
|
248
|
+
// Best-effort disconnect
|
|
249
|
+
}
|
|
250
|
+
this.connections.delete(peerId);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
link.status = "disconnected";
|
|
254
|
+
this.routing.removePeer(peerId);
|
|
255
|
+
this.peers.delete(peerId);
|
|
256
|
+
this.events.emit("federation.disconnected", peerId);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Route a message to the correct federation peer.
|
|
261
|
+
* Resolves the address, checks trust, and delivers or queues.
|
|
262
|
+
* If a real connection exists, sends via conn.send(); otherwise emits event.
|
|
263
|
+
*/
|
|
264
|
+
async route(message: Message): Promise<DeliveryResult> {
|
|
265
|
+
// Determine target for each recipient
|
|
266
|
+
for (const recipient of message.recipients) {
|
|
267
|
+
const addr = parseAddress(recipient.agent_id);
|
|
268
|
+
if (!isRemoteAddress(addr)) continue;
|
|
269
|
+
|
|
270
|
+
const peerId = this.routing.resolveRoute(addr);
|
|
271
|
+
if (!peerId) {
|
|
272
|
+
// Route unknown — try strategies
|
|
273
|
+
return this.handleUnknownRoute(addr, message);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check trust
|
|
277
|
+
if (!this.trust.canRoute(peerId, message.scope)) {
|
|
278
|
+
return {
|
|
279
|
+
delivered: false,
|
|
280
|
+
peerId,
|
|
281
|
+
error: `Trust policy denies routing to scope "${message.scope}" from system "${peerId}"`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const link = this.peers.get(peerId);
|
|
286
|
+
if (!link || link.status !== "connected") {
|
|
287
|
+
// No direct peer link — try broadcast/hierarchical strategies before queuing.
|
|
288
|
+
// This handles cases like system-qualified addresses (bob@system-2) targeting
|
|
289
|
+
// a system we're not directly connected to but could reach via relay or hub.
|
|
290
|
+
const strategy = this.routing.getStrategy();
|
|
291
|
+
if (strategy !== "table") {
|
|
292
|
+
return this.handleUnknownRoute(addr, message);
|
|
293
|
+
}
|
|
294
|
+
this.queue.enqueue(peerId, message);
|
|
295
|
+
return { delivered: false, peerId, queued: true };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Deliver — use real connection if available, otherwise emit event
|
|
299
|
+
const sendResult = await this.sendToPeer(peerId, addr, message);
|
|
300
|
+
if (sendResult) {
|
|
301
|
+
recipient.delivered_at = new Date().toISOString();
|
|
302
|
+
return { delivered: true, peerId };
|
|
303
|
+
}
|
|
304
|
+
// Send failed — queue for retry
|
|
305
|
+
this.queue.enqueue(peerId, message);
|
|
306
|
+
return { delivered: false, peerId, queued: true };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { delivered: false, error: "No remote recipients found" };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Send a message to a peer via FederationGateway, real connection, or event.
|
|
314
|
+
* Returns true if send succeeded (or event was emitted).
|
|
315
|
+
*/
|
|
316
|
+
private async sendToPeer(
|
|
317
|
+
peerId: string,
|
|
318
|
+
addr: FederatedAddress,
|
|
319
|
+
message: Message
|
|
320
|
+
): Promise<boolean> {
|
|
321
|
+
// Phase 2: Try FederationGateway first (provides envelope, hop/loop detection)
|
|
322
|
+
const gateway = this.gateways.get(peerId);
|
|
323
|
+
if (gateway) {
|
|
324
|
+
try {
|
|
325
|
+
const mapMsg = inboxMessageToMap(message);
|
|
326
|
+
// Build a MAP-compatible message for the gateway.
|
|
327
|
+
// Use local agent address (not federated) since the gateway
|
|
328
|
+
// already handles cross-system routing. A federated `to` like
|
|
329
|
+
// { system: "system-b", agent: "bob" } would cause the remote
|
|
330
|
+
// MapServer to call routeToFederation() instead of deliverToAgent().
|
|
331
|
+
const targetAgentIds = addr.agent ? [addr.agent] : [];
|
|
332
|
+
const localTo =
|
|
333
|
+
targetAgentIds.length === 1
|
|
334
|
+
? { agent: targetAgentIds[0] }
|
|
335
|
+
: targetAgentIds.length > 1
|
|
336
|
+
? { agents: targetAgentIds }
|
|
337
|
+
: mapMsg.to;
|
|
338
|
+
const gatewayMessage = {
|
|
339
|
+
id: message.id,
|
|
340
|
+
from: message.sender_id,
|
|
341
|
+
to: localTo,
|
|
342
|
+
timestamp: Date.now(),
|
|
343
|
+
payload: mapMsg.payload,
|
|
344
|
+
meta: mapMsg.meta,
|
|
345
|
+
};
|
|
346
|
+
const routed = await gateway.route(gatewayMessage, targetAgentIds);
|
|
347
|
+
if (routed) return true;
|
|
348
|
+
// Fall through to direct connection if gateway route fails
|
|
349
|
+
} catch {
|
|
350
|
+
// Fall through to direct connection
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const conn = this.connections.get(peerId);
|
|
355
|
+
if (conn) {
|
|
356
|
+
// Real transport — send via MAP SDK or mesh channel connection
|
|
357
|
+
try {
|
|
358
|
+
await conn.send(
|
|
359
|
+
{ agentId: addr.agent, scope: addr.scope },
|
|
360
|
+
message.content,
|
|
361
|
+
{
|
|
362
|
+
messageId: message.id,
|
|
363
|
+
senderId: message.sender_id,
|
|
364
|
+
sourceSystem: this.systemId.id,
|
|
365
|
+
targetAgent: addr.agent,
|
|
366
|
+
importance: message.importance,
|
|
367
|
+
...(message.subject ? { subject: message.subject } : {}),
|
|
368
|
+
...(message.thread_tag ? { threadTag: message.thread_tag } : {}),
|
|
369
|
+
...(message.in_reply_to ? { inReplyTo: message.in_reply_to } : {}),
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
return true;
|
|
373
|
+
} catch {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// No real connection — emit event (for testing / event-based mode)
|
|
379
|
+
this.events.emit("federation.route", { peerId, message });
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Handle an incoming message from a federation peer connection.
|
|
385
|
+
* Delegates to the injected message handler (wired to router.routeMessage).
|
|
386
|
+
*/
|
|
387
|
+
private handlePeerMessage(peerId: string, msg: IncomingMapMessage): void {
|
|
388
|
+
this.events.emit("federation.message.received", { peerId, message: msg });
|
|
389
|
+
|
|
390
|
+
if (this.onIncoming) {
|
|
391
|
+
this.onIncoming({
|
|
392
|
+
from: msg.from,
|
|
393
|
+
peerId,
|
|
394
|
+
payload: msg.payload,
|
|
395
|
+
meta: msg.meta,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Handle routing to an unknown agent. Behavior depends on strategy.
|
|
402
|
+
*/
|
|
403
|
+
private async handleUnknownRoute(
|
|
404
|
+
addr: FederatedAddress,
|
|
405
|
+
message: Message
|
|
406
|
+
): Promise<DeliveryResult> {
|
|
407
|
+
const strategy = this.routing.getStrategy();
|
|
408
|
+
|
|
409
|
+
if (strategy === "broadcast") {
|
|
410
|
+
// Forward to all connected peers
|
|
411
|
+
const connectedPeers = this.getConnectedPeers();
|
|
412
|
+
if (connectedPeers.length === 0) {
|
|
413
|
+
return { delivered: false, error: "No connected peers for broadcast" };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Try real connections first, fall back to event
|
|
417
|
+
for (const peer of connectedPeers) {
|
|
418
|
+
await this.sendToPeer(peer.peerId, addr, message);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.events.emit("federation.broadcast", {
|
|
422
|
+
message,
|
|
423
|
+
peers: connectedPeers.map((p) => p.peerId),
|
|
424
|
+
timeout: this.routing.getBroadcastTimeout(),
|
|
425
|
+
});
|
|
426
|
+
return { delivered: true };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (strategy === "hierarchical") {
|
|
430
|
+
// Delegate to upstream hubs
|
|
431
|
+
const upstream = this.routing.getUpstream();
|
|
432
|
+
for (const hubId of upstream) {
|
|
433
|
+
const link = this.peers.get(hubId);
|
|
434
|
+
if (link?.status === "connected") {
|
|
435
|
+
const sent = await this.sendToPeer(hubId, addr, message);
|
|
436
|
+
if (sent) {
|
|
437
|
+
return { delivered: true, peerId: hubId };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return { delivered: false, error: "No reachable upstream hubs" };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Table strategy with refreshOnMiss — emit event for transport layer
|
|
445
|
+
if (this.routing.shouldRefreshOnMiss()) {
|
|
446
|
+
this.events.emit("federation.refresh", { address: addr });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Queue for later if we have a system hint
|
|
450
|
+
if (addr.system) {
|
|
451
|
+
this.queue.enqueue(addr.system, message);
|
|
452
|
+
return { delivered: false, peerId: addr.system, queued: true };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
delivered: false,
|
|
457
|
+
error: `No route to agent "${addr.agent}"`,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get all federation peer links.
|
|
463
|
+
*/
|
|
464
|
+
getPeers(): FederationLink[] {
|
|
465
|
+
return Array.from(this.peers.values());
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get only connected peers.
|
|
470
|
+
*/
|
|
471
|
+
getConnectedPeers(): FederationLink[] {
|
|
472
|
+
return Array.from(this.peers.values()).filter(
|
|
473
|
+
(p) => p.status === "connected"
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Get a specific peer link.
|
|
479
|
+
*/
|
|
480
|
+
getPeer(peerId: string): FederationLink | undefined {
|
|
481
|
+
return this.peers.get(peerId);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Check if a peer is connected.
|
|
486
|
+
*/
|
|
487
|
+
isConnected(peerId: string): boolean {
|
|
488
|
+
return this.peers.get(peerId)?.status === "connected";
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Check if a real MAP SDK connection exists for a peer.
|
|
493
|
+
*/
|
|
494
|
+
hasTransport(peerId: string): boolean {
|
|
495
|
+
return this.connections.has(peerId);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Clean up all state. Call on shutdown.
|
|
500
|
+
*/
|
|
501
|
+
/**
|
|
502
|
+
* Check if a FederationGateway exists for a peer.
|
|
503
|
+
*/
|
|
504
|
+
hasGateway(peerId: string): boolean {
|
|
505
|
+
return this.gateways.has(peerId);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async destroy(): Promise<void> {
|
|
509
|
+
// Close all real connections
|
|
510
|
+
for (const [peerId, conn] of this.connections) {
|
|
511
|
+
try {
|
|
512
|
+
await conn.disconnect();
|
|
513
|
+
} catch {
|
|
514
|
+
// Best-effort
|
|
515
|
+
}
|
|
516
|
+
this.connections.delete(peerId);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
this.gateways.clear();
|
|
520
|
+
this.routing.destroy();
|
|
521
|
+
this.queue.destroy();
|
|
522
|
+
this.peers.clear();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Resolve the system ID using tiered precedence:
|
|
527
|
+
* 1. Explicit config (INBOX_SYSTEM_ID)
|
|
528
|
+
* 2. Auto-generated (persisted to file for stability across restarts)
|
|
529
|
+
*
|
|
530
|
+
* Note: Tier 2 (MAP systemInfo) is handled after MAP connection is established.
|
|
531
|
+
*/
|
|
532
|
+
private resolveSystemId(): SystemId {
|
|
533
|
+
// Tier 1: Explicit config
|
|
534
|
+
if (this.config.systemId) {
|
|
535
|
+
return { id: this.config.systemId, source: "config" };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Tier 3: Auto-generated (with persistence)
|
|
539
|
+
const idFile = path.join(
|
|
540
|
+
os.homedir(),
|
|
541
|
+
".claude",
|
|
542
|
+
"agent-inbox",
|
|
543
|
+
"system-id"
|
|
544
|
+
);
|
|
545
|
+
try {
|
|
546
|
+
const existing = fs.readFileSync(idFile, "utf-8").trim();
|
|
547
|
+
if (existing) {
|
|
548
|
+
return { id: existing, source: "auto" };
|
|
549
|
+
}
|
|
550
|
+
} catch {
|
|
551
|
+
// File doesn't exist — generate new ID
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const newId = `inbox-${crypto.randomBytes(4).toString("hex")}`;
|
|
555
|
+
try {
|
|
556
|
+
fs.mkdirSync(path.dirname(idFile), { recursive: true });
|
|
557
|
+
fs.writeFileSync(idFile, newId);
|
|
558
|
+
} catch {
|
|
559
|
+
// Best-effort persistence
|
|
560
|
+
}
|
|
561
|
+
return { id: newId, source: "auto" };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Update system ID from MAP server's systemInfo (Tier 2).
|
|
566
|
+
* Only used if no explicit config was set.
|
|
567
|
+
*/
|
|
568
|
+
updateSystemIdFromMap(mapSystemName: string): void {
|
|
569
|
+
if (this.systemId.source === "config") return; // Explicit config takes precedence
|
|
570
|
+
this.systemId = { id: mapSystemName, source: "map" };
|
|
571
|
+
this.events.emit("system.id.updated", this.systemId);
|
|
572
|
+
}
|
|
573
|
+
}
|