agent-comms 8.15.9 → 8.15.10
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 +1 -1
- package/dist/bridges/user/web/server.d.ts.map +1 -1
- package/dist/bridges/user/web/server.js +44 -24
- package/dist/bridges/user/web/server.js.map +1 -1
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts +5 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js +90 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts +2 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js +75 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js.map +1 -0
- package/dist/bridges/user/web/web-assets.d.ts +28 -0
- package/dist/bridges/user/web/web-assets.d.ts.map +1 -0
- package/dist/bridges/user/web/web-assets.js +67 -0
- package/dist/bridges/user/web/web-assets.js.map +1 -0
- package/package.json +8 -2
- package/src/account-cli.ts +195 -0
- package/src/account-join-cli.ts +191 -0
- package/src/bridges/cc-peer/approval-commands.ts +82 -0
- package/src/bridges/cc-peer/bridge.ts +138 -0
- package/src/bridges/cc-peer/default-front.ts +58 -0
- package/src/bridges/cc-peer/front-controller.ts +160 -0
- package/src/bridges/cc-peer/front-relay.ts +191 -0
- package/src/bridges/cc-peer/front-runtime.ts +167 -0
- package/src/bridges/cc-peer/front.ts +59 -0
- package/src/bridges/cc-peer/reply-aliases.ts +90 -0
- package/src/bridges/cc-peer/run.ts +100 -0
- package/src/bridges/cc-peer/shared-peer.ts +28 -0
- package/src/bridges/claude-code/channel.ts +318 -0
- package/src/bridges/codex/tool.ts +132 -0
- package/src/bridges/mcp/server.ts +137 -0
- package/src/bridges/opencode/plugin.ts +130 -0
- package/src/bridges/pi/extension.ts +330 -0
- package/src/bridges/registry.ts +17 -0
- package/src/bridges/user/cli.ts +87 -0
- package/src/bridges/user/controller.ts +345 -0
- package/src/bridges/user/tui.ts +300 -0
- package/src/bridges/user/web/actions.ts +173 -0
- package/src/bridges/user/web/contract.ts +222 -0
- package/src/bridges/user/web/dist/bundle.js +49 -0
- package/src/bridges/user/web/dist/icons/icon-192x192.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-512x512.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-96x96.svg +9 -0
- package/src/bridges/user/web/dist/index.html +21 -0
- package/src/bridges/user/web/dist/manifest.json +26 -0
- package/src/bridges/user/web/dist/mesh-worker.js +64 -0
- package/src/bridges/user/web/dist/sw.js +1 -0
- package/src/bridges/user/web/event-publisher.ts +114 -0
- package/src/bridges/user/web/port-discovery.ts +49 -0
- package/src/bridges/user/web/reads.ts +35 -0
- package/src/bridges/user/web/router.ts +158 -0
- package/src/bridges/user/web/server.ts +782 -0
- package/src/bridges/user/web/vite.config.ts +71 -0
- package/src/bridges/user/web/web-access.ts +404 -0
- package/src/bridges/user/web/web-assets.ts +90 -0
- package/src/bridges/user/web/web-beacon.ts +69 -0
- package/src/bridges/user/web/web-console-static.ts +90 -0
- package/src/cli-main.ts +635 -0
- package/src/cli.ts +13 -0
- package/src/core/account-bundle.ts +264 -0
- package/src/core/account-join.ts +289 -0
- package/src/core/account-ledger-crypto.ts +154 -0
- package/src/core/account-ledger-store.ts +66 -0
- package/src/core/account-ledger.ts +385 -0
- package/src/core/agent-details.ts +216 -0
- package/src/core/agent-list-format.ts +97 -0
- package/src/core/agent-registry.ts +282 -0
- package/src/core/agent-table.ts +58 -0
- package/src/core/atomic-file.ts +46 -0
- package/src/core/bind-retry.ts +38 -0
- package/src/core/bridge-mesh.ts +176 -0
- package/src/core/bridge.ts +718 -0
- package/src/core/capability-ask.ts +162 -0
- package/src/core/capability-bubble-up.ts +237 -0
- package/src/core/comms-store.ts +130 -0
- package/src/core/connection-approval.ts +134 -0
- package/src/core/connection-code-tool.ts +92 -0
- package/src/core/connection-code.ts +296 -0
- package/src/core/coordinator-role.ts +224 -0
- package/src/core/data-directory.ts +32 -0
- package/src/core/data-frame-routing.ts +309 -0
- package/src/core/default-command.ts +12 -0
- package/src/core/delegation-policy.ts +41 -0
- package/src/core/delivery-engine.ts +649 -0
- package/src/core/device-membership-verification.ts +125 -0
- package/src/core/device-membership.ts +130 -0
- package/src/core/directory-admission.ts +155 -0
- package/src/core/discovery-mdns.ts +247 -0
- package/src/core/discovery-tailscale.ts +224 -0
- package/src/core/discovery.ts +303 -0
- package/src/core/display-name.ts +73 -0
- package/src/core/dm-grant-actions.ts +72 -0
- package/src/core/dm-send-delegation.ts +73 -0
- package/src/core/dm-token-verification.ts +76 -0
- package/src/core/election-sessions.ts +135 -0
- package/src/core/error-reporting.ts +85 -0
- package/src/core/first-contact.ts +242 -0
- package/src/core/gateway-trust-actions.ts +184 -0
- package/src/core/gateway-trust.ts +305 -0
- package/src/core/gossip-directory.ts +276 -0
- package/src/core/gossip-extensions.ts +123 -0
- package/src/core/group-proofs.ts +205 -0
- package/src/core/handshake.ts +354 -0
- package/src/core/hub-forwarding.ts +24 -0
- package/src/core/hub-link.ts +176 -0
- package/src/core/hub-session.ts +374 -0
- package/src/core/identity-store.ts +715 -0
- package/src/core/identity.ts +489 -0
- package/src/core/index.ts +65 -0
- package/src/core/issuer-grouping.ts +165 -0
- package/src/core/issuer-identity-file.ts +187 -0
- package/src/core/lan-web-actions.ts +34 -0
- package/src/core/lan-web-uis.ts +55 -0
- package/src/core/ledger-lock.ts +81 -0
- package/src/core/listener-registry.ts +133 -0
- package/src/core/loopback-address.ts +22 -0
- package/src/core/machine-identity.ts +77 -0
- package/src/core/machine-list-groups.ts +53 -0
- package/src/core/mcp-registry-retry.ts +69 -0
- package/src/core/mcp-registry-version.ts +18 -0
- package/src/core/membership-proof.ts +139 -0
- package/src/core/membership-proofs.ts +149 -0
- package/src/core/mesh-graph-trace-tool.ts +81 -0
- package/src/core/mesh-graph.ts +89 -0
- package/src/core/mesh-network-actions.ts +183 -0
- package/src/core/mesh-store-options.ts +23 -0
- package/src/core/mesh-store-shared.ts +134 -0
- package/src/core/mesh-store.ts +1114 -0
- package/src/core/name-claim.ts +145 -0
- package/src/core/naming-actions.ts +90 -0
- package/src/core/naming.ts +138 -0
- package/src/core/nanoid.ts +20 -0
- package/src/core/network-interfaces.ts +24 -0
- package/src/core/package-version.ts +34 -0
- package/src/core/peer-lifecycle.ts +377 -0
- package/src/core/peer-link-error.ts +41 -0
- package/src/core/peer-versions.ts +98 -0
- package/src/core/pending-connection.ts +53 -0
- package/src/core/petnames.ts +121 -0
- package/src/core/pgp-keyserver.ts +32 -0
- package/src/core/pid-lock-file.ts +122 -0
- package/src/core/push-manager.ts +76 -0
- package/src/core/random-id.ts +9 -0
- package/src/core/receiver-set.ts +42 -0
- package/src/core/relay-role.ts +182 -0
- package/src/core/relay-selection.ts +120 -0
- package/src/core/relay-server.ts +198 -0
- package/src/core/relay-uplink.ts +78 -0
- package/src/core/request-timeouts.ts +37 -0
- package/src/core/room-discovery.ts +48 -0
- package/src/core/room-lifecycle.ts +838 -0
- package/src/core/room-lookup.ts +41 -0
- package/src/core/room-messaging.ts +247 -0
- package/src/core/room-notice-log.ts +61 -0
- package/src/core/room-path.ts +84 -0
- package/src/core/room-protocol.ts +893 -0
- package/src/core/room-router.ts +180 -0
- package/src/core/room-token-verification.ts +85 -0
- package/src/core/room-wire-extensions.ts +60 -0
- package/src/core/self-name-claim.ts +67 -0
- package/src/core/send-outcome.ts +69 -0
- package/src/core/shutdown-signals.ts +57 -0
- package/src/core/stale-agent-checker.ts +108 -0
- package/src/core/store.ts +704 -0
- package/src/core/token-text.ts +40 -0
- package/src/core/tool.ts +853 -0
- package/src/core/transport-events.ts +73 -0
- package/src/core/transport-queries.ts +54 -0
- package/src/core/transport.ts +367 -0
- package/src/core/types.ts +550 -0
- package/src/core/user-identity.ts +290 -0
- package/src/core/vapid.ts +81 -0
- package/src/core/version-check.ts +147 -0
- package/src/core/version-report-actions.ts +79 -0
- package/src/core/web-beacon.ts +31 -0
- package/src/core/web-push.ts +322 -0
- package/src/core/wire-mesh-core-version.ts +25 -0
- package/src/core/wire-mesh-identity.ts +36 -0
- package/src/core/wire-mesh-transport-options.ts +82 -0
- package/src/core/wire-mesh-transport.ts +1007 -0
- package/src/core/wire-protocol.ts +153 -0
- package/src/core/ws-dial.ts +191 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Comms — shared bridge helpers.
|
|
3
|
+
*
|
|
4
|
+
* Every bridge needs the same three things:
|
|
5
|
+
* 1. Parse tool parameters into a CommsAction (via Zod schema)
|
|
6
|
+
* 2. Format a DeliveryEvent as human-readable text
|
|
7
|
+
* 3. Register (or recover) an agent identity
|
|
8
|
+
*
|
|
9
|
+
* Extracted here so each bridge only wires up its harness-specific
|
|
10
|
+
* push mechanism and tool registration.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import type { CommsStore } from "./comms-store.js";
|
|
15
|
+
import type {
|
|
16
|
+
CommsAction,
|
|
17
|
+
DeliveryEvent,
|
|
18
|
+
StreamingBehavior,
|
|
19
|
+
Visibility,
|
|
20
|
+
} from "./types.js";
|
|
21
|
+
import { ownerNamedRoomPath, slugRoomName } from "./room-path.js";
|
|
22
|
+
import { describeDeliveryFor } from "./send-outcome.js";
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Tool parameter schema — single source of truth for MCP input
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
const VisibilityEnum = z.enum(["visible", "hidden", "ghost"]);
|
|
30
|
+
const StatusEnum = z.enum(["active", "idle", "busy"]);
|
|
31
|
+
const RoomTypeEnum = z.enum(["public", "private", "secret"]);
|
|
32
|
+
|
|
33
|
+
const MeshVisibilityEnum = z.enum(["discoverable", "quiet", "dark"]);
|
|
34
|
+
|
|
35
|
+
export const MCP_TOOL_PARAMS = z.object({
|
|
36
|
+
action: z.enum([
|
|
37
|
+
"register",
|
|
38
|
+
"update",
|
|
39
|
+
"whoami",
|
|
40
|
+
"web_url",
|
|
41
|
+
"create_room",
|
|
42
|
+
"list_rooms",
|
|
43
|
+
"join_room",
|
|
44
|
+
"leave_room",
|
|
45
|
+
"send",
|
|
46
|
+
"dm",
|
|
47
|
+
"list_agents",
|
|
48
|
+
"read_room",
|
|
49
|
+
"invite",
|
|
50
|
+
"decline_invite",
|
|
51
|
+
"kick",
|
|
52
|
+
"destroy_room",
|
|
53
|
+
"mesh_connect",
|
|
54
|
+
"mesh_accept",
|
|
55
|
+
"mesh_reject",
|
|
56
|
+
"mesh_pending",
|
|
57
|
+
"room_accept",
|
|
58
|
+
"room_reject",
|
|
59
|
+
"room_pending",
|
|
60
|
+
"capability_accept",
|
|
61
|
+
"capability_reject",
|
|
62
|
+
"capability_pending",
|
|
63
|
+
"mesh_discover",
|
|
64
|
+
"mesh_advertise",
|
|
65
|
+
"mesh_unadvertise",
|
|
66
|
+
"mesh_interfaces",
|
|
67
|
+
"mesh_listen",
|
|
68
|
+
"mesh_unlisten",
|
|
69
|
+
"mesh_listeners",
|
|
70
|
+
"mesh_set_visibility",
|
|
71
|
+
"mesh_get_visibility",
|
|
72
|
+
"gateway_trust",
|
|
73
|
+
"gateway_untrust",
|
|
74
|
+
"gateway_list_trusted",
|
|
75
|
+
"petname_set",
|
|
76
|
+
"petname_clear",
|
|
77
|
+
"petname_list",
|
|
78
|
+
"machine_name",
|
|
79
|
+
"principal_name",
|
|
80
|
+
"dm_admit",
|
|
81
|
+
"dm_use_grant",
|
|
82
|
+
"dm_revoke",
|
|
83
|
+
"gateway_generate_connection_code",
|
|
84
|
+
"gateway_redeem_connection_code",
|
|
85
|
+
"query_version",
|
|
86
|
+
"mesh_graph",
|
|
87
|
+
"mesh_trace",
|
|
88
|
+
]),
|
|
89
|
+
name: z.string().optional(),
|
|
90
|
+
visibility: VisibilityEnum.optional(),
|
|
91
|
+
tags: z.array(z.string()).optional(),
|
|
92
|
+
status: StatusEnum.optional(),
|
|
93
|
+
room: z.string().optional(),
|
|
94
|
+
type: RoomTypeEnum.optional(),
|
|
95
|
+
description: z.string().optional(),
|
|
96
|
+
target: z.string().optional(),
|
|
97
|
+
content: z.string().optional(),
|
|
98
|
+
agent: z.string().optional(),
|
|
99
|
+
since: z.string().optional(),
|
|
100
|
+
replyTo: z.string().optional(),
|
|
101
|
+
reason: z.string().optional(),
|
|
102
|
+
method: z.string().optional(),
|
|
103
|
+
host: z.string().optional(),
|
|
104
|
+
port: z.number().optional(),
|
|
105
|
+
policy: z.string().optional(),
|
|
106
|
+
adapter: z.string().optional(),
|
|
107
|
+
id: z.string().optional(),
|
|
108
|
+
meshVisibility: MeshVisibilityEnum.optional(),
|
|
109
|
+
connectionId: z.string().optional(),
|
|
110
|
+
requesterId: z.string().optional(),
|
|
111
|
+
streamingBehavior: z.enum(["steer", "followUp", "info"]).optional(),
|
|
112
|
+
requestId: z.string().optional(),
|
|
113
|
+
capability: z.string().optional(),
|
|
114
|
+
expires: z.number().optional(),
|
|
115
|
+
delegationsRemaining: z.number().optional(),
|
|
116
|
+
/** A remote gateway's own device-id (hex), for gateway_trust/gateway_untrust, for gateway_redeem_connection_code (the code's own embedded deviceId field), and the device-id petname_set/petname_clear label. */
|
|
117
|
+
device: z.string().optional(),
|
|
118
|
+
/** The connection code's own nonce (gateway_redeem_connection_code). */
|
|
119
|
+
code: z.string().optional(),
|
|
120
|
+
/** ISO-8601 expiry for gateway_generate_connection_code's ttlMs override, or the redeemed code's own expiresAt field for gateway_redeem_connection_code. */
|
|
121
|
+
expiresAt: z.string().optional(),
|
|
122
|
+
/** ttlMs override for gateway_generate_connection_code; defaults to DEFAULT_CONNECTION_CODE_TTL_MS when omitted. */
|
|
123
|
+
ttlMs: z.number().optional(),
|
|
124
|
+
/** Armored PGP private key to sign a generated code with (gateway_generate_connection_code), or armored PGP public key to verify a redeemed code's signature against (gateway_redeem_connection_code). Both are supplied per call -- agent-comms never generates, stores, or manages PGP key material itself. */
|
|
125
|
+
privateKey: z.string().optional(),
|
|
126
|
+
publicKey: z.string().optional(),
|
|
127
|
+
/** Passphrase for privateKey, if it is passphrase-protected (gateway_generate_connection_code). */
|
|
128
|
+
passphrase: z.string().optional(),
|
|
129
|
+
/** The redeemed code's own signature field (gateway_redeem_connection_code), or a PGP fingerprint the caller already independently trusts, used either to pin a supplied publicKey or to fetch one from a keyserver when publicKey is omitted. */
|
|
130
|
+
signature: z.string().optional(),
|
|
131
|
+
fingerprint: z.string().optional(),
|
|
132
|
+
/** For gateway_trust/gateway_untrust (and dm_admit, where `target` names it): when true, the id names a user principal (agent-comms#187) rather than a bare remote device-id (agent-comms#193). */
|
|
133
|
+
principal: z.boolean().optional(),
|
|
134
|
+
/** For gateway_trust/gateway_untrust: when true, the id names a machine (agent-comms#343), whose devices (every bridge one OS account runs on that host) are trusted or revoked at once. */
|
|
135
|
+
machine: z.boolean().optional(),
|
|
136
|
+
/** The dm:send grant text dm_admit returned, presented by dm_use_grant. */
|
|
137
|
+
grant: z.string().optional(),
|
|
138
|
+
/** How long mesh_trace waits for a path.trace response before giving up (agent-comms#199). */
|
|
139
|
+
timeoutMs: z.number().optional(),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export type ToolParams = z.infer<typeof MCP_TOOL_PARAMS>;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Zod schema for the MCP tool inputSchema field.
|
|
146
|
+
* This is the same schema exposed directly for the MCP SDK.
|
|
147
|
+
*/
|
|
148
|
+
// Removed MCP_TOOL_SCHEMA alias — use MCP_TOOL_PARAMS directly
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// buildAction — parsed params → typed CommsAction
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
class BuildActionError extends Error {
|
|
155
|
+
constructor(
|
|
156
|
+
public readonly action: string,
|
|
157
|
+
field: string,
|
|
158
|
+
) {
|
|
159
|
+
super(`Missing required field "${field}" for action "${action}"`);
|
|
160
|
+
this.name = "BuildActionError";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function buildAction(params: Record<string, unknown>): CommsAction {
|
|
165
|
+
const parsed = MCP_TOOL_PARAMS.safeParse(params);
|
|
166
|
+
if (!parsed.success) return { action: "whoami" };
|
|
167
|
+
const p = parsed.data;
|
|
168
|
+
|
|
169
|
+
switch (p.action) {
|
|
170
|
+
case "register":
|
|
171
|
+
if (p.name === undefined) throw new BuildActionError("register", "name");
|
|
172
|
+
return {
|
|
173
|
+
action: "register",
|
|
174
|
+
name: p.name,
|
|
175
|
+
visibility: p.visibility ?? "visible",
|
|
176
|
+
tags: p.tags ?? [],
|
|
177
|
+
};
|
|
178
|
+
case "update": {
|
|
179
|
+
const update: CommsAction & { action: "update" } = { action: "update" };
|
|
180
|
+
if (p.visibility !== undefined) update.visibility = p.visibility;
|
|
181
|
+
if (p.status !== undefined) update.status = p.status;
|
|
182
|
+
if (p.name !== undefined) update.name = p.name;
|
|
183
|
+
if (p.tags !== undefined) update.tags = p.tags;
|
|
184
|
+
return update;
|
|
185
|
+
}
|
|
186
|
+
case "whoami":
|
|
187
|
+
return { action: "whoami" };
|
|
188
|
+
case "web_url":
|
|
189
|
+
return { action: "web_url" };
|
|
190
|
+
case "create_room":
|
|
191
|
+
if (p.room === undefined)
|
|
192
|
+
throw new BuildActionError("create_room", "room");
|
|
193
|
+
return {
|
|
194
|
+
action: "create_room",
|
|
195
|
+
name: p.room,
|
|
196
|
+
type: p.type ?? "public",
|
|
197
|
+
description: p.description ?? "",
|
|
198
|
+
};
|
|
199
|
+
case "list_rooms":
|
|
200
|
+
return { action: "list_rooms" };
|
|
201
|
+
case "join_room":
|
|
202
|
+
if (p.room === undefined) throw new BuildActionError("join_room", "room");
|
|
203
|
+
return { action: "join_room", room: p.room };
|
|
204
|
+
case "leave_room":
|
|
205
|
+
if (p.room === undefined)
|
|
206
|
+
throw new BuildActionError("leave_room", "room");
|
|
207
|
+
return { action: "leave_room", room: p.room };
|
|
208
|
+
case "send": {
|
|
209
|
+
if (p.content === undefined)
|
|
210
|
+
throw new BuildActionError("send", "content");
|
|
211
|
+
if (p.target !== undefined) {
|
|
212
|
+
const send: CommsAction & { action: "send" } = {
|
|
213
|
+
action: "send",
|
|
214
|
+
target: p.target,
|
|
215
|
+
content: p.content,
|
|
216
|
+
};
|
|
217
|
+
if (p.replyTo !== undefined) send.replyTo = p.replyTo;
|
|
218
|
+
if (p.streamingBehavior !== undefined)
|
|
219
|
+
send.streamingBehavior = p.streamingBehavior;
|
|
220
|
+
return send;
|
|
221
|
+
}
|
|
222
|
+
if (p.room !== undefined) {
|
|
223
|
+
const send: CommsAction & { action: "send" } = {
|
|
224
|
+
action: "send",
|
|
225
|
+
target: p.room,
|
|
226
|
+
content: p.content,
|
|
227
|
+
};
|
|
228
|
+
if (p.replyTo !== undefined) send.replyTo = p.replyTo;
|
|
229
|
+
if (p.streamingBehavior !== undefined)
|
|
230
|
+
send.streamingBehavior = p.streamingBehavior;
|
|
231
|
+
return send;
|
|
232
|
+
}
|
|
233
|
+
throw new BuildActionError("send", "target");
|
|
234
|
+
}
|
|
235
|
+
case "dm": {
|
|
236
|
+
if (p.content === undefined) throw new BuildActionError("dm", "content");
|
|
237
|
+
if (p.target !== undefined) {
|
|
238
|
+
const dm: CommsAction & { action: "dm" } = {
|
|
239
|
+
action: "dm",
|
|
240
|
+
target: p.target,
|
|
241
|
+
content: p.content,
|
|
242
|
+
};
|
|
243
|
+
if (p.streamingBehavior !== undefined)
|
|
244
|
+
dm.streamingBehavior = p.streamingBehavior;
|
|
245
|
+
return dm;
|
|
246
|
+
}
|
|
247
|
+
if (p.agent !== undefined) {
|
|
248
|
+
const dm: CommsAction & { action: "dm" } = {
|
|
249
|
+
action: "dm",
|
|
250
|
+
target: p.agent,
|
|
251
|
+
content: p.content,
|
|
252
|
+
};
|
|
253
|
+
if (p.streamingBehavior !== undefined)
|
|
254
|
+
dm.streamingBehavior = p.streamingBehavior;
|
|
255
|
+
return dm;
|
|
256
|
+
}
|
|
257
|
+
throw new BuildActionError("dm", "target");
|
|
258
|
+
}
|
|
259
|
+
case "list_agents":
|
|
260
|
+
return { action: "list_agents" };
|
|
261
|
+
case "read_room":
|
|
262
|
+
if (p.room === undefined) throw new BuildActionError("read_room", "room");
|
|
263
|
+
return {
|
|
264
|
+
action: "read_room",
|
|
265
|
+
room: p.room,
|
|
266
|
+
...(p.since !== undefined && { since: p.since }),
|
|
267
|
+
};
|
|
268
|
+
case "invite":
|
|
269
|
+
if (p.room === undefined) throw new BuildActionError("invite", "room");
|
|
270
|
+
if (p.agent === undefined) throw new BuildActionError("invite", "agent");
|
|
271
|
+
return { action: "invite", room: p.room, agent: p.agent };
|
|
272
|
+
case "decline_invite":
|
|
273
|
+
if (p.room === undefined)
|
|
274
|
+
throw new BuildActionError("decline_invite", "room");
|
|
275
|
+
if (p.reason === undefined)
|
|
276
|
+
throw new BuildActionError("decline_invite", "reason");
|
|
277
|
+
return { action: "decline_invite", room: p.room, reason: p.reason };
|
|
278
|
+
case "kick":
|
|
279
|
+
if (p.room === undefined) throw new BuildActionError("kick", "room");
|
|
280
|
+
if (p.agent === undefined) throw new BuildActionError("kick", "agent");
|
|
281
|
+
return { action: "kick", room: p.room, agent: p.agent };
|
|
282
|
+
case "destroy_room":
|
|
283
|
+
if (p.room === undefined)
|
|
284
|
+
throw new BuildActionError("destroy_room", "room");
|
|
285
|
+
return { action: "destroy_room", room: p.room };
|
|
286
|
+
case "mesh_connect": {
|
|
287
|
+
if (p.host === undefined)
|
|
288
|
+
throw new BuildActionError("mesh_connect", "host");
|
|
289
|
+
if (p.port === undefined)
|
|
290
|
+
throw new BuildActionError("mesh_connect", "port");
|
|
291
|
+
return {
|
|
292
|
+
action: "mesh_connect",
|
|
293
|
+
host: p.host,
|
|
294
|
+
port: p.port,
|
|
295
|
+
...(p.policy !== undefined && { policy: p.policy }),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
case "mesh_accept": {
|
|
299
|
+
if (p.connectionId === undefined)
|
|
300
|
+
throw new BuildActionError("mesh_accept", "connectionId");
|
|
301
|
+
return { action: "mesh_accept", connectionId: p.connectionId };
|
|
302
|
+
}
|
|
303
|
+
case "mesh_reject": {
|
|
304
|
+
if (p.connectionId === undefined)
|
|
305
|
+
throw new BuildActionError("mesh_reject", "connectionId");
|
|
306
|
+
if (p.reason === undefined)
|
|
307
|
+
throw new BuildActionError("mesh_reject", "reason");
|
|
308
|
+
return {
|
|
309
|
+
action: "mesh_reject",
|
|
310
|
+
connectionId: p.connectionId,
|
|
311
|
+
reason: p.reason,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
case "mesh_pending":
|
|
315
|
+
return { action: "mesh_pending" };
|
|
316
|
+
case "room_accept": {
|
|
317
|
+
if (p.room === undefined)
|
|
318
|
+
throw new BuildActionError("room_accept", "room");
|
|
319
|
+
if (p.requesterId === undefined)
|
|
320
|
+
throw new BuildActionError("room_accept", "requesterId");
|
|
321
|
+
return {
|
|
322
|
+
action: "room_accept",
|
|
323
|
+
room: p.room,
|
|
324
|
+
requesterId: p.requesterId,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
case "room_reject": {
|
|
328
|
+
if (p.room === undefined)
|
|
329
|
+
throw new BuildActionError("room_reject", "room");
|
|
330
|
+
if (p.requesterId === undefined)
|
|
331
|
+
throw new BuildActionError("room_reject", "requesterId");
|
|
332
|
+
return {
|
|
333
|
+
action: "room_reject",
|
|
334
|
+
room: p.room,
|
|
335
|
+
requesterId: p.requesterId,
|
|
336
|
+
...(p.reason !== undefined && { reason: p.reason }),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
case "room_pending":
|
|
340
|
+
return { action: "room_pending" };
|
|
341
|
+
case "capability_accept": {
|
|
342
|
+
if (p.requestId === undefined)
|
|
343
|
+
throw new BuildActionError("capability_accept", "requestId");
|
|
344
|
+
if (p.expires === undefined)
|
|
345
|
+
throw new BuildActionError("capability_accept", "expires");
|
|
346
|
+
return {
|
|
347
|
+
action: "capability_accept",
|
|
348
|
+
requestId: p.requestId,
|
|
349
|
+
expires: p.expires,
|
|
350
|
+
...(p.delegationsRemaining !== undefined && {
|
|
351
|
+
delegationsRemaining: p.delegationsRemaining,
|
|
352
|
+
}),
|
|
353
|
+
...(p.capability !== undefined && { capability: p.capability }),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
case "capability_reject": {
|
|
357
|
+
if (p.requestId === undefined)
|
|
358
|
+
throw new BuildActionError("capability_reject", "requestId");
|
|
359
|
+
return {
|
|
360
|
+
action: "capability_reject",
|
|
361
|
+
requestId: p.requestId,
|
|
362
|
+
...(p.reason !== undefined && { reason: p.reason }),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
case "capability_pending":
|
|
366
|
+
return { action: "capability_pending" };
|
|
367
|
+
case "mesh_discover": {
|
|
368
|
+
const discover: CommsAction & { action: "mesh_discover" } = {
|
|
369
|
+
action: "mesh_discover",
|
|
370
|
+
};
|
|
371
|
+
if (p.method !== undefined) discover.method = p.method;
|
|
372
|
+
return discover;
|
|
373
|
+
}
|
|
374
|
+
case "mesh_advertise":
|
|
375
|
+
if (p.name === undefined)
|
|
376
|
+
throw new BuildActionError("mesh_advertise", "name");
|
|
377
|
+
if (p.method === undefined)
|
|
378
|
+
throw new BuildActionError("mesh_advertise", "method");
|
|
379
|
+
return {
|
|
380
|
+
action: "mesh_advertise",
|
|
381
|
+
method: p.method,
|
|
382
|
+
name: p.name,
|
|
383
|
+
...(p.port !== undefined && { port: p.port }),
|
|
384
|
+
...(p.adapter !== undefined && { adapter: p.adapter }),
|
|
385
|
+
};
|
|
386
|
+
case "mesh_unadvertise":
|
|
387
|
+
if (p.id === undefined)
|
|
388
|
+
throw new BuildActionError("mesh_unadvertise", "id");
|
|
389
|
+
return { action: "mesh_unadvertise", id: p.id };
|
|
390
|
+
case "mesh_interfaces":
|
|
391
|
+
return { action: "mesh_interfaces" };
|
|
392
|
+
case "mesh_listen":
|
|
393
|
+
if (p.host === undefined)
|
|
394
|
+
throw new BuildActionError("mesh_listen", "host");
|
|
395
|
+
return {
|
|
396
|
+
action: "mesh_listen",
|
|
397
|
+
host: p.host,
|
|
398
|
+
...(p.port !== undefined && { port: p.port }),
|
|
399
|
+
...(p.policy !== undefined && { policy: p.policy }),
|
|
400
|
+
};
|
|
401
|
+
case "mesh_unlisten":
|
|
402
|
+
if (p.id === undefined) throw new BuildActionError("mesh_unlisten", "id");
|
|
403
|
+
return { action: "mesh_unlisten", id: p.id };
|
|
404
|
+
case "mesh_listeners":
|
|
405
|
+
return { action: "mesh_listeners" };
|
|
406
|
+
case "mesh_set_visibility": {
|
|
407
|
+
if (p.meshVisibility === undefined) {
|
|
408
|
+
throw new BuildActionError("mesh_set_visibility", "meshVisibility");
|
|
409
|
+
}
|
|
410
|
+
const result: CommsAction & { action: "mesh_set_visibility" } = {
|
|
411
|
+
action: "mesh_set_visibility",
|
|
412
|
+
visibility: p.meshVisibility,
|
|
413
|
+
};
|
|
414
|
+
if (p.adapter !== undefined) result.adapter = p.adapter;
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
case "mesh_get_visibility": {
|
|
418
|
+
const result: CommsAction & { action: "mesh_get_visibility" } = {
|
|
419
|
+
action: "mesh_get_visibility",
|
|
420
|
+
};
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
case "gateway_trust":
|
|
424
|
+
if (p.device === undefined)
|
|
425
|
+
throw new BuildActionError("gateway_trust", "device");
|
|
426
|
+
return {
|
|
427
|
+
action: "gateway_trust",
|
|
428
|
+
device: p.device,
|
|
429
|
+
...(p.principal !== undefined && { principal: p.principal }),
|
|
430
|
+
...(p.machine !== undefined && { machine: p.machine }),
|
|
431
|
+
};
|
|
432
|
+
case "gateway_untrust":
|
|
433
|
+
if (p.device === undefined)
|
|
434
|
+
throw new BuildActionError("gateway_untrust", "device");
|
|
435
|
+
return {
|
|
436
|
+
action: "gateway_untrust",
|
|
437
|
+
device: p.device,
|
|
438
|
+
...(p.principal !== undefined && { principal: p.principal }),
|
|
439
|
+
...(p.machine !== undefined && { machine: p.machine }),
|
|
440
|
+
};
|
|
441
|
+
case "gateway_list_trusted":
|
|
442
|
+
return { action: "gateway_list_trusted" };
|
|
443
|
+
case "petname_set":
|
|
444
|
+
if (p.device === undefined)
|
|
445
|
+
throw new BuildActionError("petname_set", "device");
|
|
446
|
+
if (p.name === undefined)
|
|
447
|
+
throw new BuildActionError("petname_set", "name");
|
|
448
|
+
return { action: "petname_set", device: p.device, name: p.name };
|
|
449
|
+
case "petname_clear":
|
|
450
|
+
if (p.device === undefined)
|
|
451
|
+
throw new BuildActionError("petname_clear", "device");
|
|
452
|
+
return { action: "petname_clear", device: p.device };
|
|
453
|
+
case "petname_list":
|
|
454
|
+
return { action: "petname_list" };
|
|
455
|
+
case "machine_name":
|
|
456
|
+
return {
|
|
457
|
+
action: "machine_name",
|
|
458
|
+
...(p.name !== undefined && { name: p.name }),
|
|
459
|
+
};
|
|
460
|
+
case "principal_name":
|
|
461
|
+
return {
|
|
462
|
+
action: "principal_name",
|
|
463
|
+
...(p.name !== undefined && { name: p.name }),
|
|
464
|
+
};
|
|
465
|
+
case "dm_admit":
|
|
466
|
+
if (p.target === undefined)
|
|
467
|
+
throw new BuildActionError("dm_admit", "target");
|
|
468
|
+
return {
|
|
469
|
+
action: "dm_admit",
|
|
470
|
+
target: p.target,
|
|
471
|
+
...(p.principal !== undefined && { principal: p.principal }),
|
|
472
|
+
};
|
|
473
|
+
case "dm_use_grant":
|
|
474
|
+
if (p.target === undefined)
|
|
475
|
+
throw new BuildActionError("dm_use_grant", "target");
|
|
476
|
+
if (p.grant === undefined)
|
|
477
|
+
throw new BuildActionError("dm_use_grant", "grant");
|
|
478
|
+
return { action: "dm_use_grant", target: p.target, grant: p.grant };
|
|
479
|
+
case "dm_revoke":
|
|
480
|
+
if (p.target === undefined)
|
|
481
|
+
throw new BuildActionError("dm_revoke", "target");
|
|
482
|
+
return { action: "dm_revoke", target: p.target };
|
|
483
|
+
case "gateway_generate_connection_code":
|
|
484
|
+
return {
|
|
485
|
+
action: "gateway_generate_connection_code",
|
|
486
|
+
...(p.ttlMs !== undefined && { ttlMs: p.ttlMs }),
|
|
487
|
+
...(p.privateKey !== undefined && { privateKey: p.privateKey }),
|
|
488
|
+
...(p.passphrase !== undefined && { passphrase: p.passphrase }),
|
|
489
|
+
};
|
|
490
|
+
case "gateway_redeem_connection_code":
|
|
491
|
+
if (p.code === undefined)
|
|
492
|
+
throw new BuildActionError("gateway_redeem_connection_code", "code");
|
|
493
|
+
if (p.expiresAt === undefined)
|
|
494
|
+
throw new BuildActionError(
|
|
495
|
+
"gateway_redeem_connection_code",
|
|
496
|
+
"expiresAt",
|
|
497
|
+
);
|
|
498
|
+
if (p.device === undefined)
|
|
499
|
+
throw new BuildActionError("gateway_redeem_connection_code", "device");
|
|
500
|
+
return {
|
|
501
|
+
action: "gateway_redeem_connection_code",
|
|
502
|
+
code: p.code,
|
|
503
|
+
expiresAt: p.expiresAt,
|
|
504
|
+
device: p.device,
|
|
505
|
+
...(p.signature !== undefined && { signature: p.signature }),
|
|
506
|
+
...(p.publicKey !== undefined && { publicKey: p.publicKey }),
|
|
507
|
+
...(p.fingerprint !== undefined && { fingerprint: p.fingerprint }),
|
|
508
|
+
};
|
|
509
|
+
case "query_version":
|
|
510
|
+
if (p.device === undefined)
|
|
511
|
+
throw new BuildActionError("query_version", "device");
|
|
512
|
+
return { action: "query_version", device: p.device };
|
|
513
|
+
case "mesh_graph":
|
|
514
|
+
return { action: "mesh_graph" };
|
|
515
|
+
case "mesh_trace":
|
|
516
|
+
if (p.target === undefined)
|
|
517
|
+
throw new BuildActionError("mesh_trace", "target");
|
|
518
|
+
return {
|
|
519
|
+
action: "mesh_trace",
|
|
520
|
+
target: p.target,
|
|
521
|
+
...(p.timeoutMs !== undefined && { timeoutMs: p.timeoutMs }),
|
|
522
|
+
};
|
|
523
|
+
default:
|
|
524
|
+
return p.action satisfies never;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
// extractStreamingBehavior — pull hint from message envelope if present
|
|
530
|
+
// ---------------------------------------------------------------------------
|
|
531
|
+
|
|
532
|
+
export function extractStreamingBehavior(
|
|
533
|
+
event: DeliveryEvent,
|
|
534
|
+
): StreamingBehavior | undefined {
|
|
535
|
+
if (event.type === "dm" || event.type === "room_message") {
|
|
536
|
+
return event.message.streamingBehavior;
|
|
537
|
+
}
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ---------------------------------------------------------------------------
|
|
542
|
+
// formatDeliveryEvent — DeliveryEvent → human-readable string
|
|
543
|
+
// ---------------------------------------------------------------------------
|
|
544
|
+
|
|
545
|
+
export function formatDeliveryEvent(event: DeliveryEvent): string {
|
|
546
|
+
const hint = extractStreamingBehavior(event);
|
|
547
|
+
const pfx =
|
|
548
|
+
hint === "steer" ? "[STEER] " : hint === "followUp" ? "[FOLLOWUP] " : "";
|
|
549
|
+
switch (event.type) {
|
|
550
|
+
case "room_message":
|
|
551
|
+
return `${pfx}[${event.message.room}] ${event.message.from}: ${event.message.content}`;
|
|
552
|
+
case "dm":
|
|
553
|
+
return `${pfx}DM from ${event.message.from}: ${event.message.content}`;
|
|
554
|
+
case "room_invite": {
|
|
555
|
+
const desc = event.roomDescription ? ` — ${event.roomDescription}` : "";
|
|
556
|
+
const who = event.fromCwd
|
|
557
|
+
? `${event.fromName} (${event.fromCwd})`
|
|
558
|
+
: event.fromName;
|
|
559
|
+
return `${who} invited you to room "${event.room}"${desc}`;
|
|
560
|
+
}
|
|
561
|
+
case "member_joined":
|
|
562
|
+
return `${event.agent} joined ${event.room}`;
|
|
563
|
+
case "member_left":
|
|
564
|
+
return `${event.agent} left ${event.room}`;
|
|
565
|
+
case "room_members": {
|
|
566
|
+
const names = event.members
|
|
567
|
+
.map((m) => `${m.name} (${m.status})`)
|
|
568
|
+
.join(", ");
|
|
569
|
+
return `Room ${event.room} members: ${names}`;
|
|
570
|
+
}
|
|
571
|
+
case "member_status":
|
|
572
|
+
return `${event.agent} is now ${event.status} in ${event.room}`;
|
|
573
|
+
case "delivery_status":
|
|
574
|
+
return `Message ${event.messageId} ${describeDeliveryFor(event.delivery, event.agent)}${event.room !== undefined ? ` in ${event.room}` : ""}`;
|
|
575
|
+
case "invite_declined":
|
|
576
|
+
return `${event.agentName} declined invite to ${event.room}: "${event.reason}"`;
|
|
577
|
+
case "name_changed":
|
|
578
|
+
return `${event.oldName} is now known as ${event.newName}`;
|
|
579
|
+
case "connection_request":
|
|
580
|
+
return `Connection request from ${event.peerId} (${event.name}) fingerprint ${event.fingerprint}`;
|
|
581
|
+
case "capability_request":
|
|
582
|
+
return `${event.requesterDevice} is asking for "${event.capability}" (${event.requestId})`;
|
|
583
|
+
case "room_join_request":
|
|
584
|
+
return `${event.requesterId} is asking to join ${event.room}: room_accept or room_reject to answer`;
|
|
585
|
+
default:
|
|
586
|
+
return event satisfies never;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
// Event classification — actionable vs informational
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Classify a delivery event as actionable (requires model attention)
|
|
596
|
+
* or informational (can be buffered and returned with next tool call).
|
|
597
|
+
*
|
|
598
|
+
* Actionable: DMs, room messages, room invites, and room join requests awaiting its decision — the model may need to respond, and a requester may be blocked until it does.
|
|
599
|
+
* Informational: status changes, joins/leaves, renames, read receipts —
|
|
600
|
+
* the model may need to know eventually but never needs to act immediately.
|
|
601
|
+
*/
|
|
602
|
+
export function isActionableEvent(event: DeliveryEvent): boolean {
|
|
603
|
+
switch (event.type) {
|
|
604
|
+
case "dm":
|
|
605
|
+
case "room_message":
|
|
606
|
+
case "room_invite":
|
|
607
|
+
case "room_join_request":
|
|
608
|
+
return true;
|
|
609
|
+
case "member_joined":
|
|
610
|
+
case "member_left":
|
|
611
|
+
case "room_members":
|
|
612
|
+
case "member_status":
|
|
613
|
+
case "delivery_status":
|
|
614
|
+
case "invite_declined":
|
|
615
|
+
case "name_changed":
|
|
616
|
+
case "connection_request":
|
|
617
|
+
case "capability_request":
|
|
618
|
+
return false;
|
|
619
|
+
default:
|
|
620
|
+
return event satisfies never;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ---------------------------------------------------------------------------
|
|
625
|
+
// ensureRegistered — recover or create an agent identity
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
|
|
628
|
+
export interface RegistrationResult {
|
|
629
|
+
agentId: string;
|
|
630
|
+
store: CommsStore;
|
|
631
|
+
isNew: boolean;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Recover an existing identity (from `identity.json`) or register a new agent.
|
|
636
|
+
* Returns the agent ID and whether this was a fresh registration.
|
|
637
|
+
*/
|
|
638
|
+
export async function ensureRegistered(opts: {
|
|
639
|
+
store: CommsStore;
|
|
640
|
+
harness: string;
|
|
641
|
+
cwd: string;
|
|
642
|
+
defaultName: string;
|
|
643
|
+
visibility?: Visibility;
|
|
644
|
+
tags?: string[];
|
|
645
|
+
}): Promise<RegistrationResult> {
|
|
646
|
+
await opts.store.init();
|
|
647
|
+
|
|
648
|
+
const identity = await opts.store.readIdentity(opts.harness, opts.cwd);
|
|
649
|
+
if (identity) {
|
|
650
|
+
await opts.store.updateAgent(identity.id, {
|
|
651
|
+
status: "active",
|
|
652
|
+
pid: process.pid,
|
|
653
|
+
});
|
|
654
|
+
await ensureProjectRoom(opts.store, identity.id, opts.cwd);
|
|
655
|
+
return { agentId: identity.id, store: opts.store, isNew: false };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const agent = await opts.store.registerAgent({
|
|
659
|
+
name: opts.defaultName,
|
|
660
|
+
harness: opts.harness,
|
|
661
|
+
cwd: opts.cwd,
|
|
662
|
+
pid: process.pid,
|
|
663
|
+
visibility: opts.visibility ?? "visible",
|
|
664
|
+
tags: opts.tags ?? [],
|
|
665
|
+
});
|
|
666
|
+
await ensureProjectRoom(opts.store, agent.id, opts.cwd);
|
|
667
|
+
return { agentId: agent.id, store: opts.store, isNew: true };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
// ensureProjectRoom — auto-create a room for the agent's working directory
|
|
672
|
+
// ---------------------------------------------------------------------------
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Ensure a project room exists for the given cwd and the agent is a member. Creates the room if needed and joins the agent. No-op if already a member. Returns the room ID.
|
|
676
|
+
*
|
|
677
|
+
* Every agent computes an owner-rooted path with itself as owner, so two agents in the same cwd do not currently converge on one shared room -- a real, deliberately deferred regression from the pre-core/room behaviour (see the design doc's OQ-2: deterministic lowest-device-id ownership is P3.4's own responsibility, once gossiped presence data is wired up here).
|
|
678
|
+
*/
|
|
679
|
+
export async function ensureProjectRoom(
|
|
680
|
+
store: Readonly<CommsStore>,
|
|
681
|
+
agentId: string,
|
|
682
|
+
cwd: string,
|
|
683
|
+
): Promise<string> {
|
|
684
|
+
const localName = slugRoomName(path.basename(cwd));
|
|
685
|
+
const id = ownerNamedRoomPath(agentId, localName);
|
|
686
|
+
|
|
687
|
+
// Check if the room already exists
|
|
688
|
+
const existing = await store.getRoom(id);
|
|
689
|
+
if (existing) {
|
|
690
|
+
// Room exists — verify it's a project room (description matches pattern)
|
|
691
|
+
if (!existing.members.includes(agentId)) {
|
|
692
|
+
await store.joinRoom(id, agentId);
|
|
693
|
+
}
|
|
694
|
+
return id;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Create — createRoom auto-joins the owner
|
|
698
|
+
await store.createRoom({
|
|
699
|
+
name: localName,
|
|
700
|
+
type: "public",
|
|
701
|
+
owner: agentId,
|
|
702
|
+
description: `Project room for ${cwd}`,
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
return id;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
709
|
+
// drainAndFormat — drain delivery queue, return formatted lines
|
|
710
|
+
// ---------------------------------------------------------------------------
|
|
711
|
+
|
|
712
|
+
export async function drainAndFormat(
|
|
713
|
+
store: Readonly<CommsStore>,
|
|
714
|
+
agentId: string,
|
|
715
|
+
): Promise<string[]> {
|
|
716
|
+
const events = await store.drainDelivery(agentId);
|
|
717
|
+
return events.map(formatDeliveryEvent);
|
|
718
|
+
}
|