@ctliz/agent-intercom-claude 0.12.0-connect.3

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.
Files changed (59) hide show
  1. package/.claude-plugin/plugin.json +26 -0
  2. package/.mcp.json +10 -0
  3. package/CHANGELOG.md +62 -0
  4. package/COPYRIGHT +6 -0
  5. package/LICENSE +661 -0
  6. package/LICENSE_TRANSITION.md +25 -0
  7. package/README.md +506 -0
  8. package/THIRD_PARTY_NOTICES.md +25 -0
  9. package/broker/access-credential.ts +76 -0
  10. package/broker/access-registry.ts +460 -0
  11. package/broker/audit.ts +60 -0
  12. package/broker/authorization.ts +125 -0
  13. package/broker/boss-contracts.ts +267 -0
  14. package/broker/broker.ts +2028 -0
  15. package/broker/client.ts +1048 -0
  16. package/broker/framing.ts +87 -0
  17. package/broker/ownership.ts +61 -0
  18. package/broker/paths.ts +161 -0
  19. package/broker/spawn.ts +463 -0
  20. package/broker/validation.ts +132 -0
  21. package/claude/cci.ts +512 -0
  22. package/claude/ccim.ts +31 -0
  23. package/claude/cli-runner.ts +220 -0
  24. package/claude/contact.ts +33 -0
  25. package/claude/inbox-monitor.ts +49 -0
  26. package/claude/inbox.ts +77 -0
  27. package/claude/mcp-protocol.ts +253 -0
  28. package/claude/native-bridge.ts +219 -0
  29. package/claude/native-protocol.ts +346 -0
  30. package/claude/permission-policy.ts +287 -0
  31. package/claude/runtime.ts +510 -0
  32. package/claude/server.ts +69 -0
  33. package/claude/team.ts +348 -0
  34. package/claude/transport.ts +97 -0
  35. package/claude/worker-config.ts +198 -0
  36. package/claude/worker-daemon.ts +418 -0
  37. package/commands/intercom-id.md +9 -0
  38. package/commands/intercom.md +8 -0
  39. package/config.ts +195 -0
  40. package/dist/broker.mjs +2530 -0
  41. package/dist/cci.mjs +3607 -0
  42. package/dist/ccim.mjs +3621 -0
  43. package/dist/claude-server.mjs +2730 -0
  44. package/dist/inbox-monitor.mjs +68 -0
  45. package/dist/worker-daemon.mjs +2797 -0
  46. package/durable-json.ts +25 -0
  47. package/licenses/MIT-pi-claude-link.txt +21 -0
  48. package/licenses/MIT-pi-intercom.txt +21 -0
  49. package/monitors/monitors.json +8 -0
  50. package/outbound-outbox.ts +116 -0
  51. package/package.json +93 -0
  52. package/protocol-v4/contract.ts +45 -0
  53. package/provider/protected-service.ts +162 -0
  54. package/provider/provider.mjs +34 -0
  55. package/scripts/build.mjs +71 -0
  56. package/scripts/verify-core-provenance.mjs +64 -0
  57. package/scripts/verify-packed-runtime.mjs +140 -0
  58. package/skills/claude-intercom/SKILL.md +90 -0
  59. package/types.ts +212 -0
@@ -0,0 +1,2530 @@
1
+ // broker/broker.ts
2
+ import net from "net";
3
+ import { existsSync as existsSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
4
+ import { join as join2 } from "path";
5
+ import { randomUUID as randomUUID3 } from "crypto";
6
+ import { authorize, POLICY_SEMANTICS_HASH, POLICY_SEMANTICS_VERSION } from "@ctliz/agent-intercom-core";
7
+
8
+ // protocol-v4/contract.ts
9
+ import {
10
+ INTERCOM_PROTOCOL_NAME,
11
+ INTERCOM_PROTOCOL_V4_SEMANTICS_HASH,
12
+ INTERCOM_PROTOCOL_V4_VECTOR_SCHEMA_VERSION,
13
+ INTERCOM_PROTOCOL_V4_VECTORS,
14
+ INTERCOM_PROTOCOL_VERSION,
15
+ INTERCOM_SCOPE_ENV,
16
+ INTERCOM_SCOPE_ID_PATTERN,
17
+ INTERCOM_SCOPE_ID_PATTERN_SOURCE,
18
+ intercomScopeIdFromEnv,
19
+ parseIntercomScopeId,
20
+ sameIntercomScope
21
+ } from "@ctliz/agent-intercom-core/protocol-v4";
22
+ var INTERCOM_REGISTRATION_METADATA_ERROR = "Registration metadata is invalid";
23
+ function parseIntercomScopeIdForRegistration(value) {
24
+ try {
25
+ return parseIntercomScopeId(value, "registrationMetadata");
26
+ } catch {
27
+ throw new Error(INTERCOM_REGISTRATION_METADATA_ERROR);
28
+ }
29
+ }
30
+
31
+ // broker/framing.ts
32
+ var MAX_FRAME_BYTES = 1024 * 1024;
33
+ function writeMessage(socket, msg) {
34
+ const json = JSON.stringify(msg);
35
+ const payload = Buffer.from(json, "utf-8");
36
+ const header = Buffer.alloc(4);
37
+ header.writeUInt32BE(payload.length, 0);
38
+ socket.write(Buffer.concat([header, payload]));
39
+ }
40
+ function createMessageReader(onMessage, onError, maxFrameBytes = MAX_FRAME_BYTES) {
41
+ let buffer = Buffer.alloc(0);
42
+ function reportMessage(payload) {
43
+ let msg;
44
+ try {
45
+ msg = JSON.parse(payload.toString("utf-8"));
46
+ } catch (error) {
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error }));
49
+ return false;
50
+ }
51
+ try {
52
+ onMessage(msg);
53
+ return true;
54
+ } catch (error) {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ onError(new Error(`Failed to handle intercom message: ${message}`, { cause: error }));
57
+ return false;
58
+ }
59
+ }
60
+ return (data) => {
61
+ let remaining = data;
62
+ while (remaining.length > 0) {
63
+ if (buffer.length < 4) {
64
+ const headerBytes = Math.min(4 - buffer.length, remaining.length);
65
+ buffer = Buffer.concat([buffer, remaining.subarray(0, headerBytes)]);
66
+ remaining = remaining.subarray(headerBytes);
67
+ if (buffer.length < 4) {
68
+ return;
69
+ }
70
+ }
71
+ const length = buffer.readUInt32BE(0);
72
+ if (length > maxFrameBytes) {
73
+ buffer = Buffer.alloc(0);
74
+ onError(new Error(`Intercom frame length ${length} exceeds maximum ${maxFrameBytes} bytes`));
75
+ return;
76
+ }
77
+ const missingPayloadBytes = length - Math.max(0, buffer.length - 4);
78
+ const payloadBytes = Math.min(missingPayloadBytes, remaining.length);
79
+ if (payloadBytes > 0) {
80
+ buffer = Buffer.concat([buffer, remaining.subarray(0, payloadBytes)]);
81
+ remaining = remaining.subarray(payloadBytes);
82
+ }
83
+ if (buffer.length < 4 + length) {
84
+ return;
85
+ }
86
+ const payload = buffer.subarray(4, 4 + length);
87
+ buffer = Buffer.alloc(0);
88
+ if (!reportMessage(payload)) {
89
+ return;
90
+ }
91
+ }
92
+ };
93
+ }
94
+
95
+ // broker/paths.ts
96
+ import { chmodSync, mkdirSync, readFileSync } from "fs";
97
+ import { isAbsolute, join, resolve } from "path";
98
+ import { homedir } from "os";
99
+ var INTERCOM_DIR_MODE = 448;
100
+ var INTERCOM_RUNTIME_FILE_MODE = 384;
101
+ var INTERCOM_TCP_HOST = "127.0.0.1";
102
+ var INTERCOM_PROTOCOL_NAME2 = "pi-intercom";
103
+ var INTERCOM_PROTOCOL_VERSION2 = 4;
104
+ function sanitizePipeSegment(value) {
105
+ return value.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "default";
106
+ }
107
+ function getAgentDirPath(env = process.env, homeDir = homedir(), cwd = process.cwd()) {
108
+ const configured = env.PI_CODING_AGENT_DIR?.trim();
109
+ if (!configured) {
110
+ return join(homeDir, ".pi/agent");
111
+ }
112
+ return isAbsolute(configured) ? configured : resolve(cwd, configured);
113
+ }
114
+ function getIntercomDirPath(agentDir = getAgentDirPath()) {
115
+ return join(agentDir, "intercom");
116
+ }
117
+ function shouldUseWindowsTcpTransport(platform = process.platform, env = process.env) {
118
+ if (platform !== "win32") {
119
+ return false;
120
+ }
121
+ const transport = env.PI_INTERCOM_TRANSPORT?.trim().toLowerCase();
122
+ if (transport === "tcp") {
123
+ return true;
124
+ }
125
+ const legacyOptIn = env.PI_INTERCOM_TCP?.trim().toLowerCase();
126
+ return legacyOptIn === "1" || legacyOptIn === "true";
127
+ }
128
+ function getBrokerPortFilePath(intercomDir = getIntercomDirPath()) {
129
+ return join(intercomDir, "broker.port.json");
130
+ }
131
+ function getBrokerAskStateFilePath(intercomDir = getIntercomDirPath()) {
132
+ return join(intercomDir, "broker-asks.json");
133
+ }
134
+ function getBrokerAccessStateFilePath(intercomDir = getIntercomDirPath()) {
135
+ return join(intercomDir, "broker-access.json");
136
+ }
137
+ function getBrokerAdminCredentialFilePath(intercomDir = getIntercomDirPath()) {
138
+ return join(intercomDir, "broker-admin.json");
139
+ }
140
+ function getBrokerAuditFilePath(intercomDir = getIntercomDirPath()) {
141
+ return join(intercomDir, "broker-audit.jsonl");
142
+ }
143
+ function getRemoteGatewaySocketPath(platform = process.platform, agentDir = getAgentDirPath()) {
144
+ if (platform === "win32") {
145
+ return `\\\\.\\pipe\\pi-intercom-remote-${sanitizePipeSegment(agentDir)}`;
146
+ }
147
+ return join(getIntercomDirPath(agentDir), "remote-gateway.sock");
148
+ }
149
+ function getBrokerSocketPath(platform = process.platform, agentDir = getAgentDirPath()) {
150
+ if (platform === "win32") {
151
+ return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(agentDir)}`;
152
+ }
153
+ return join(getIntercomDirPath(agentDir), "broker.sock");
154
+ }
155
+ function getBrokerListenTarget(platform = process.platform, env = process.env) {
156
+ if (shouldUseWindowsTcpTransport(platform, env)) {
157
+ return { transport: "tcp", host: INTERCOM_TCP_HOST, port: 0 };
158
+ }
159
+ return getBrokerSocketPath(platform, getAgentDirPath(env));
160
+ }
161
+ function ensureIntercomRuntimeDir(intercomDir = getIntercomDirPath(), platform = process.platform) {
162
+ mkdirSync(intercomDir, { recursive: true, mode: INTERCOM_DIR_MODE });
163
+ if (platform !== "win32") {
164
+ chmodSync(intercomDir, INTERCOM_DIR_MODE);
165
+ }
166
+ }
167
+ function restrictIntercomRuntimeFile(filePath, platform = process.platform) {
168
+ if (platform !== "win32") {
169
+ chmodSync(filePath, INTERCOM_RUNTIME_FILE_MODE);
170
+ }
171
+ }
172
+
173
+ // config.ts
174
+ var DEFAULT_ASK_TIMEOUT_MS = 45 * 1e3;
175
+ var MAX_ASK_TIMEOUT_MS = 120 * 1e3;
176
+ function validateAskTimeoutMs(value, name = "timeout_ms") {
177
+ if (!Number.isSafeInteger(value) || typeof value !== "number" || value <= 0) {
178
+ throw new Error(`${name} must be a positive integer number of milliseconds`);
179
+ }
180
+ if (value > MAX_ASK_TIMEOUT_MS) {
181
+ throw new Error(`${name} must be ${MAX_ASK_TIMEOUT_MS} ms or less; use intercom_send plus intercom_pending for longer-running work`);
182
+ }
183
+ return value;
184
+ }
185
+ function getAskTimeoutMs() {
186
+ const raw = process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
187
+ if (raw === void 0 || raw.trim() === "") {
188
+ return DEFAULT_ASK_TIMEOUT_MS;
189
+ }
190
+ const value = Number(raw);
191
+ return validateAskTimeoutMs(value, "PI_INTERCOM_ASK_TIMEOUT_MS");
192
+ }
193
+
194
+ // durable-json.ts
195
+ import { randomUUID } from "crypto";
196
+ import { closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "fs";
197
+ import { dirname } from "path";
198
+ function writeDurableJson(filePath, value) {
199
+ const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
200
+ writeFileSync(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE });
201
+ const fileDescriptor = openSync(temporaryPath, "r");
202
+ try {
203
+ fsyncSync(fileDescriptor);
204
+ } finally {
205
+ closeSync(fileDescriptor);
206
+ }
207
+ renameSync(temporaryPath, filePath);
208
+ restrictIntercomRuntimeFile(filePath);
209
+ if (process.platform !== "win32") {
210
+ const directoryDescriptor = openSync(dirname(filePath), "r");
211
+ try {
212
+ fsyncSync(directoryDescriptor);
213
+ } finally {
214
+ closeSync(directoryDescriptor);
215
+ }
216
+ }
217
+ }
218
+
219
+ // broker/ownership.ts
220
+ import { closeSync as closeSync2, constants, openSync as openSync2, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
221
+ function ownerPid(path) {
222
+ try {
223
+ const pid = Number.parseInt(readFileSync2(path, "utf8").trim(), 10);
224
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
225
+ } catch {
226
+ return null;
227
+ }
228
+ }
229
+ function pidIsAlive(pid) {
230
+ try {
231
+ process.kill(pid, 0);
232
+ return true;
233
+ } catch (error) {
234
+ return error.code !== "ESRCH";
235
+ }
236
+ }
237
+ function acquireBrokerOwnership(path, pid = process.pid) {
238
+ for (let attempt = 0; attempt < 2; attempt += 1) {
239
+ try {
240
+ const fd = openSync2(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, INTERCOM_RUNTIME_FILE_MODE);
241
+ try {
242
+ writeFileSync2(fd, String(pid));
243
+ } finally {
244
+ closeSync2(fd);
245
+ }
246
+ restrictIntercomRuntimeFile(path);
247
+ return;
248
+ } catch (error) {
249
+ if (error.code !== "EEXIST") throw error;
250
+ const existingPid = ownerPid(path);
251
+ if (existingPid !== null && pidIsAlive(existingPid)) {
252
+ throw new Error(`Intercom broker already owned by live process ${existingPid}`);
253
+ }
254
+ try {
255
+ unlinkSync(path);
256
+ } catch (unlinkError) {
257
+ if (unlinkError.code !== "ENOENT") throw unlinkError;
258
+ }
259
+ }
260
+ }
261
+ throw new Error("Could not acquire intercom broker ownership");
262
+ }
263
+ function releaseBrokerOwnership(path, pid = process.pid) {
264
+ if (ownerPid(path) !== pid) return;
265
+ try {
266
+ unlinkSync(path);
267
+ } catch (error) {
268
+ if (error.code !== "ENOENT") throw error;
269
+ }
270
+ }
271
+ function hasBrokerOwnership(path, pid = process.pid) {
272
+ return ownerPid(path) === pid;
273
+ }
274
+
275
+ // broker/access-registry.ts
276
+ import { createHash, randomBytes, randomUUID as randomUUID2, timingSafeEqual } from "crypto";
277
+ import { existsSync, readFileSync as readFileSync3 } from "fs";
278
+ var REMOTE_ACCESS_STATE_VERSION = 2;
279
+ var REMOTE_ACCESS_CREDENTIAL_VERSION = 1;
280
+ var DEFAULT_ENROLLMENT_TTL_MS = 10 * 60 * 1e3;
281
+ var DEFAULT_PRINCIPAL_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
282
+ var RemoteAccessError = class extends Error {
283
+ constructor(code, message) {
284
+ super(message);
285
+ this.code = code;
286
+ this.name = "RemoteAccessError";
287
+ }
288
+ code;
289
+ };
290
+ function emptyState() {
291
+ return { version: REMOTE_ACCESS_STATE_VERSION, principals: {}, enrollments: {} };
292
+ }
293
+ function hashSecret(secret) {
294
+ return createHash("sha256").update(secret, "utf8").digest("hex");
295
+ }
296
+ function secretsMatch(secret, expectedHash) {
297
+ const actual = Buffer.from(hashSecret(secret), "hex");
298
+ const expected = Buffer.from(expectedHash, "hex");
299
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
300
+ }
301
+ function newSecret() {
302
+ return randomBytes(32).toString("base64url");
303
+ }
304
+ function requireText(value, field, maxLength = 512) {
305
+ const normalized = value.trim();
306
+ if (!normalized || normalized.length > maxLength || /[\u0000-\u001f\u007f]/.test(normalized)) {
307
+ throw new Error(`Invalid ${field}`);
308
+ }
309
+ return normalized;
310
+ }
311
+ function boundedInteger(value, fallback, field, minimum, maximum) {
312
+ const resolved = value ?? fallback;
313
+ if (!Number.isSafeInteger(resolved) || resolved < minimum || resolved > maximum) throw new Error(`Invalid ${field}`);
314
+ return resolved;
315
+ }
316
+ function parseState(raw) {
317
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error("expected object");
318
+ const state = raw;
319
+ if (state.version !== 1 && state.version !== REMOTE_ACCESS_STATE_VERSION) throw new Error("unsupported version");
320
+ if (typeof state.principals !== "object" || state.principals === null || Array.isArray(state.principals)) throw new Error("invalid principals");
321
+ if (typeof state.enrollments !== "object" || state.enrollments === null || Array.isArray(state.enrollments)) throw new Error("invalid enrollments");
322
+ if (state.adminCredentialHash !== void 0 && typeof state.adminCredentialHash !== "string") throw new Error("invalid admin credential hash");
323
+ const principals = {};
324
+ for (const [id, value] of Object.entries(state.principals)) {
325
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`invalid principal ${id}`);
326
+ const principal = value;
327
+ if (principal.id !== id || typeof principal.name !== "string" || typeof principal.credentialHash !== "string" || typeof principal.parentSessionId !== "string" || typeof principal.rootSessionId !== "string" || typeof principal.remoteHostId !== "string" || typeof principal.generation !== "number" || principal.state !== "active" && principal.state !== "revoked" || typeof principal.expiresAt !== "number" || typeof principal.createdAt !== "number" || typeof principal.updatedAt !== "number") throw new Error(`invalid principal ${id}`);
328
+ const depth = boundedInteger(principal.depth, 1, "principal depth", 1, 32);
329
+ const maxDepth = boundedInteger(principal.maxDepth, depth, "maximum delegation depth", depth, 32);
330
+ const maxChildren = boundedInteger(principal.maxChildren, 0, "maximum child count", 0, 128);
331
+ principals[id] = {
332
+ ...principal,
333
+ policy: "remote-tree",
334
+ canDelegate: principal.canDelegate === true,
335
+ depth,
336
+ maxDepth,
337
+ maxChildren
338
+ };
339
+ }
340
+ const enrollments = {};
341
+ for (const [hash, value] of Object.entries(state.enrollments)) {
342
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`invalid enrollment ${hash}`);
343
+ const enrollment = value;
344
+ const template = enrollment.template;
345
+ if (!template || typeof template !== "object") throw new Error(`invalid enrollment ${hash}`);
346
+ const depth = boundedInteger(template.depth, 1, "enrollment depth", 1, 32);
347
+ enrollments[hash] = {
348
+ ...enrollment,
349
+ template: {
350
+ ...template,
351
+ canDelegate: template.canDelegate === true,
352
+ depth,
353
+ maxDepth: boundedInteger(template.maxDepth, depth, "enrollment maximum depth", depth, 32),
354
+ maxChildren: boundedInteger(template.maxChildren, 0, "enrollment maximum children", 0, 128)
355
+ }
356
+ };
357
+ }
358
+ return {
359
+ version: REMOTE_ACCESS_STATE_VERSION,
360
+ ...typeof state.adminCredentialHash === "string" ? { adminCredentialHash: state.adminCredentialHash } : {},
361
+ principals,
362
+ enrollments
363
+ };
364
+ }
365
+ var RemoteAccessRegistry = class {
366
+ constructor(statePath, now = Date.now) {
367
+ this.statePath = statePath;
368
+ this.now = now;
369
+ this.state = this.load();
370
+ }
371
+ statePath;
372
+ now;
373
+ state;
374
+ snapshot() {
375
+ return structuredClone(this.state);
376
+ }
377
+ ensureAdminCredential(credentialPath) {
378
+ if (existsSync(credentialPath)) {
379
+ const parsed = JSON.parse(readFileSync3(credentialPath, "utf8"));
380
+ if (parsed.version === REMOTE_ACCESS_CREDENTIAL_VERSION && typeof parsed.adminToken === "string" && parsed.adminToken.length >= 32) {
381
+ const hash = hashSecret(parsed.adminToken);
382
+ if (this.state.adminCredentialHash !== hash) {
383
+ this.state.adminCredentialHash = hash;
384
+ this.persist();
385
+ }
386
+ restrictIntercomRuntimeFile(credentialPath);
387
+ return parsed.adminToken;
388
+ }
389
+ }
390
+ const adminToken = newSecret();
391
+ writeDurableJson(credentialPath, { version: REMOTE_ACCESS_CREDENTIAL_VERSION, adminToken });
392
+ this.state.adminCredentialHash = hashSecret(adminToken);
393
+ this.persist();
394
+ return adminToken;
395
+ }
396
+ authenticateAdmin(adminToken) {
397
+ return typeof this.state.adminCredentialHash === "string" && secretsMatch(adminToken, this.state.adminCredentialHash);
398
+ }
399
+ issueEnrollment(template, ttlMs = DEFAULT_ENROLLMENT_TTL_MS) {
400
+ if (!Number.isSafeInteger(ttlMs) || ttlMs <= 0 || ttlMs > 24 * 60 * 60 * 1e3) throw new Error("Invalid enrollment TTL");
401
+ const now = this.now();
402
+ const enrollmentToken = newSecret();
403
+ const tokenHash = hashSecret(enrollmentToken);
404
+ const expiresAt = now + ttlMs;
405
+ const principalExpiresAt = template.expiresAt ?? now + DEFAULT_PRINCIPAL_TTL_MS;
406
+ if (!Number.isSafeInteger(principalExpiresAt) || principalExpiresAt <= now) throw new Error("Invalid principal expiry");
407
+ const depth = boundedInteger(template.depth, 1, "principal depth", 1, 32);
408
+ const maxDepth = boundedInteger(template.maxDepth, depth, "maximum delegation depth", depth, 32);
409
+ const maxChildren = boundedInteger(template.maxChildren, 0, "maximum child count", 0, 128);
410
+ const canDelegate = template.canDelegate === true;
411
+ if (canDelegate && (maxDepth <= depth || maxChildren === 0)) throw new Error("Delegating principals require remaining depth and child capacity");
412
+ const normalized = {
413
+ name: requireText(template.name, "principal name", 256),
414
+ parentSessionId: requireText(template.parentSessionId, "parent session ID"),
415
+ rootSessionId: requireText(template.rootSessionId, "root session ID"),
416
+ remoteHostId: requireText(template.remoteHostId, "remote host ID", 256),
417
+ expiresAt: principalExpiresAt,
418
+ canDelegate,
419
+ depth,
420
+ maxDepth,
421
+ maxChildren
422
+ };
423
+ this.pruneExpiredEnrollments(now);
424
+ this.state.enrollments[tokenHash] = { tokenHash, template: normalized, expiresAt, createdAt: now };
425
+ this.persist();
426
+ return { enrollmentToken, expiresAt };
427
+ }
428
+ issueChildEnrollment(parentSessionId, parentGeneration, request, ttlMs = DEFAULT_ENROLLMENT_TTL_MS) {
429
+ const parent = this.validatePrincipal(parentSessionId, parentGeneration);
430
+ if (!parent.canDelegate) throw new RemoteAccessError("INVALID_ENROLLMENT", "Parent principal cannot delegate children");
431
+ const now = this.now();
432
+ const activeChildren = Object.values(this.state.principals).filter(
433
+ (principal) => principal.parentSessionId === parent.id && principal.state === "active" && principal.expiresAt > now
434
+ ).length;
435
+ const pendingChildren = Object.values(this.state.enrollments).filter(
436
+ (enrollment) => enrollment.template.parentSessionId === parent.id && enrollment.expiresAt > now
437
+ ).length;
438
+ if (activeChildren + pendingChildren >= parent.maxChildren) throw new RemoteAccessError("INVALID_ENROLLMENT", "Parent child limit is exhausted");
439
+ const depth = parent.depth + 1;
440
+ if (depth > parent.maxDepth) throw new RemoteAccessError("INVALID_ENROLLMENT", "Parent delegation depth is exhausted");
441
+ const maxDepth = boundedInteger(request.maxDepth, depth, "child maximum depth", depth, parent.maxDepth);
442
+ const maxChildren = boundedInteger(request.maxChildren, 0, "child maximum count", 0, parent.maxChildren);
443
+ const canDelegate = request.canDelegate === true;
444
+ if (canDelegate && (maxDepth <= depth || maxChildren === 0)) throw new Error("Delegating child requires remaining depth and child capacity");
445
+ const expiresAt = request.expiresAt ?? parent.expiresAt;
446
+ if (!Number.isSafeInteger(expiresAt) || expiresAt <= this.now() || expiresAt > parent.expiresAt) {
447
+ throw new Error("Child expiry must not exceed the parent expiry");
448
+ }
449
+ return this.issueEnrollment({
450
+ name: request.name,
451
+ parentSessionId: parent.id,
452
+ rootSessionId: parent.rootSessionId,
453
+ remoteHostId: parent.remoteHostId,
454
+ expiresAt,
455
+ canDelegate,
456
+ depth,
457
+ maxDepth,
458
+ maxChildren
459
+ }, ttlMs);
460
+ }
461
+ consumeEnrollment(enrollmentToken) {
462
+ const now = this.now();
463
+ const tokenHash = hashSecret(enrollmentToken);
464
+ const enrollment = this.state.enrollments[tokenHash];
465
+ if (!enrollment) throw new RemoteAccessError("INVALID_ENROLLMENT", "Enrollment credential is invalid or already consumed");
466
+ delete this.state.enrollments[tokenHash];
467
+ if (enrollment.expiresAt <= now) {
468
+ this.persist();
469
+ throw new RemoteAccessError("INVALID_ENROLLMENT", "Enrollment credential has expired");
470
+ }
471
+ const sessionCredential = newSecret();
472
+ const id = randomUUID2();
473
+ const principal = {
474
+ id,
475
+ name: enrollment.template.name,
476
+ credentialHash: hashSecret(sessionCredential),
477
+ parentSessionId: enrollment.template.parentSessionId,
478
+ rootSessionId: enrollment.template.rootSessionId,
479
+ remoteHostId: enrollment.template.remoteHostId,
480
+ generation: 1,
481
+ policy: "remote-tree",
482
+ canDelegate: enrollment.template.canDelegate === true,
483
+ depth: enrollment.template.depth,
484
+ maxDepth: enrollment.template.maxDepth,
485
+ maxChildren: enrollment.template.maxChildren,
486
+ state: "active",
487
+ expiresAt: enrollment.template.expiresAt,
488
+ createdAt: now,
489
+ updatedAt: now
490
+ };
491
+ this.state.principals[id] = principal;
492
+ this.persist();
493
+ return { principal: structuredClone(principal), sessionCredential };
494
+ }
495
+ authenticateSession(sessionId, generation, sessionCredential) {
496
+ const principal = this.state.principals[sessionId];
497
+ if (!principal || !secretsMatch(sessionCredential, principal.credentialHash)) {
498
+ throw new RemoteAccessError("INVALID_CREDENTIAL", "Session credential is invalid");
499
+ }
500
+ return this.validatePrincipal(sessionId, generation);
501
+ }
502
+ validatePrincipal(sessionId, generation) {
503
+ const principal = this.state.principals[sessionId];
504
+ if (!principal) throw new RemoteAccessError("INVALID_CREDENTIAL", "Remote principal does not exist");
505
+ if (principal.state !== "active") throw new RemoteAccessError("REVOKED_CREDENTIAL", "Session credential is revoked");
506
+ if (principal.expiresAt <= this.now()) throw new RemoteAccessError("EXPIRED_CREDENTIAL", "Session credential has expired");
507
+ if (principal.generation !== generation) throw new RemoteAccessError("STALE_GENERATION", "Session credential generation is stale");
508
+ return structuredClone(principal);
509
+ }
510
+ inspectSubtree(principalId) {
511
+ const result = [];
512
+ const queue = [principalId];
513
+ const seen = /* @__PURE__ */ new Set();
514
+ while (queue.length) {
515
+ const id = queue.shift();
516
+ if (seen.has(id)) continue;
517
+ seen.add(id);
518
+ const principal = this.state.principals[id];
519
+ if (!principal) continue;
520
+ const { credentialHash: _credentialHash, ...metadata } = principal;
521
+ result.push(structuredClone(metadata));
522
+ for (const candidate of Object.values(this.state.principals)) {
523
+ if (candidate.parentSessionId === id) queue.push(candidate.id);
524
+ }
525
+ }
526
+ return result;
527
+ }
528
+ adoptSubtree(principalId, newParentSessionId, newRootSessionId) {
529
+ const principal = this.state.principals[principalId];
530
+ if (!principal) throw new Error("Unknown adopted principal");
531
+ if (principalId === newParentSessionId) throw new Error("Adoption would create an ownership cycle");
532
+ let ancestor = this.state.principals[newParentSessionId];
533
+ const seen = /* @__PURE__ */ new Set();
534
+ while (ancestor && !seen.has(ancestor.id)) {
535
+ if (ancestor.id === principalId) throw new Error("Adoption would create an ownership cycle");
536
+ seen.add(ancestor.id);
537
+ ancestor = this.state.principals[ancestor.parentSessionId];
538
+ }
539
+ const ids = this.subtreePrincipalIds(principalId);
540
+ const now = this.now();
541
+ principal.parentSessionId = requireText(newParentSessionId, "new parent session ID");
542
+ for (const id of ids) {
543
+ const changed = this.state.principals[id];
544
+ changed.rootSessionId = requireText(newRootSessionId, "new root session ID");
545
+ changed.generation += 1;
546
+ changed.updatedAt = now;
547
+ }
548
+ const changedIds = new Set(ids);
549
+ for (const [hash, enrollment] of Object.entries(this.state.enrollments)) {
550
+ if (changedIds.has(enrollment.template.parentSessionId)) delete this.state.enrollments[hash];
551
+ }
552
+ this.persist();
553
+ return ids.map((id) => structuredClone(this.state.principals[id]));
554
+ }
555
+ expirePrincipals(now = this.now()) {
556
+ const changed = /* @__PURE__ */ new Map();
557
+ const expiredRoots = Object.values(this.state.principals).filter((principal) => principal.state === "active" && principal.expiresAt <= now).sort((left, right) => left.depth - right.depth);
558
+ for (const principal of expiredRoots) {
559
+ if (changed.has(principal.id)) continue;
560
+ for (const revoked of this.revoke(principal.id)) changed.set(revoked.id, revoked);
561
+ }
562
+ return [...changed.values()];
563
+ }
564
+ revoke(principalId) {
565
+ const now = this.now();
566
+ const queue = [principalId];
567
+ const seen = /* @__PURE__ */ new Set();
568
+ const changed = [];
569
+ while (queue.length) {
570
+ const id = queue.shift();
571
+ if (seen.has(id)) continue;
572
+ seen.add(id);
573
+ const principal = this.state.principals[id];
574
+ if (!principal) continue;
575
+ principal.state = "revoked";
576
+ principal.generation += 1;
577
+ principal.updatedAt = now;
578
+ changed.push(structuredClone(principal));
579
+ for (const candidate of Object.values(this.state.principals)) {
580
+ if (candidate.parentSessionId === id) queue.push(candidate.id);
581
+ }
582
+ }
583
+ if (changed.length) this.persist();
584
+ return changed;
585
+ }
586
+ subtreePrincipalIds(principalId) {
587
+ const result = [];
588
+ const queue = [principalId];
589
+ const seen = /* @__PURE__ */ new Set();
590
+ while (queue.length) {
591
+ const id = queue.shift();
592
+ if (seen.has(id) || !this.state.principals[id]) continue;
593
+ seen.add(id);
594
+ result.push(id);
595
+ for (const candidate of Object.values(this.state.principals)) {
596
+ if (candidate.parentSessionId === id) queue.push(candidate.id);
597
+ }
598
+ }
599
+ return result;
600
+ }
601
+ pruneExpiredEnrollments(now = this.now()) {
602
+ for (const [hash, enrollment] of Object.entries(this.state.enrollments)) {
603
+ if (enrollment.expiresAt <= now) delete this.state.enrollments[hash];
604
+ }
605
+ }
606
+ load() {
607
+ if (!existsSync(this.statePath)) return emptyState();
608
+ try {
609
+ return parseState(JSON.parse(readFileSync3(this.statePath, "utf8")));
610
+ } catch (error) {
611
+ throw new Error(`Invalid remote access registry at ${this.statePath}: ${error instanceof Error ? error.message : String(error)}`);
612
+ }
613
+ }
614
+ persist() {
615
+ writeDurableJson(this.statePath, this.state);
616
+ }
617
+ };
618
+
619
+ // broker/authorization.ts
620
+ import {
621
+ authorizeFeatureAware
622
+ } from "@ctliz/agent-intercom-core/boss";
623
+
624
+ // broker/boss-contracts.ts
625
+ import {
626
+ BOSS_CAPABILITY_FEATURE_DIGEST,
627
+ BOSS_POLICY_SEMANTICS_HASH,
628
+ BOSS_RUN_FEATURE,
629
+ BOSS_RUN_FEATURE_CONTRACT,
630
+ INTERCOM_BASE_PROTOCOL_VERSION,
631
+ evaluateBrokerCompatibility,
632
+ parseBossControlEnvelope,
633
+ parseBossParticipantBinding,
634
+ parseBossParticipantCredentialEnvelope,
635
+ parseBossPolicyPrincipal,
636
+ parseBossRunFeatureContract,
637
+ parseBrokerCapabilityAdvertisement,
638
+ isTerminalParticipantState,
639
+ parseParticipantState,
640
+ parseWorkerIdentityV2
641
+ } from "@ctliz/agent-intercom-core/boss";
642
+ import {
643
+ ContractValidationError,
644
+ assertExactKeys,
645
+ assertRecord,
646
+ readHexDigest
647
+ } from "@ctliz/agent-intercom-core/canonical";
648
+
649
+ // broker/validation.ts
650
+ import { types as nodeUtilTypes } from "node:util";
651
+ function isPlainDataRecord(value) {
652
+ if (typeof value !== "object" || value === null || nodeUtilTypes.isProxy(value) || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
653
+ return false;
654
+ }
655
+ try {
656
+ return Reflect.ownKeys(value).every((key) => {
657
+ if (typeof key !== "string") return false;
658
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
659
+ return descriptor !== void 0 && descriptor.enumerable && Object.hasOwn(descriptor, "value");
660
+ });
661
+ } catch {
662
+ return false;
663
+ }
664
+ }
665
+ function hasExactDataKeys(value, required, optional = []) {
666
+ if (!isPlainDataRecord(value)) return false;
667
+ const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
668
+ const keys = Reflect.ownKeys(value);
669
+ return keys.length >= required.length && keys.every((key) => typeof key === "string" && allowed.has(key)) && required.every((key) => Object.hasOwn(value, key));
670
+ }
671
+ function assertPlainDataGraph(value, path = "$", seen = /* @__PURE__ */ new WeakSet()) {
672
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return;
673
+ if (nodeUtilTypes.isProxy(value)) {
674
+ throw new Error(`${path} must not contain a Proxy`);
675
+ }
676
+ if (seen.has(value)) {
677
+ throw new Error(`${path} must not contain a cycle or repeated object reference`);
678
+ }
679
+ seen.add(value);
680
+ if (Array.isArray(value)) {
681
+ if (Object.getPrototypeOf(value) !== Array.prototype) {
682
+ throw new Error(`${path} must use the exact Array prototype`);
683
+ }
684
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
685
+ if (lengthDescriptor === void 0 || !Object.hasOwn(lengthDescriptor, "value")) {
686
+ throw new Error(`${path}.length must be an own data property`);
687
+ }
688
+ const length = lengthDescriptor.value;
689
+ const descriptors = /* @__PURE__ */ new Map();
690
+ for (const key of Reflect.ownKeys(value)) {
691
+ if (key === "length") continue;
692
+ if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`);
693
+ const index = Number(key);
694
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
695
+ throw new Error(`${path} must not contain non-index properties`);
696
+ }
697
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
698
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
699
+ throw new Error(`${path}[${index}] must be an enumerable own data property`);
700
+ }
701
+ descriptors.set(index, descriptor);
702
+ }
703
+ if (descriptors.size !== length) throw new Error(`${path} must be dense`);
704
+ for (let index = 0; index < length; index += 1) {
705
+ const descriptor = descriptors.get(index);
706
+ if (descriptor === void 0) throw new Error(`${path} must be dense`);
707
+ assertPlainDataGraph(descriptor.value, `${path}[${index}]`, seen);
708
+ }
709
+ return;
710
+ }
711
+ if (Object.getPrototypeOf(value) !== Object.prototype) {
712
+ throw new Error(`${path} must be a plain data object`);
713
+ }
714
+ for (const key of Reflect.ownKeys(value)) {
715
+ if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`);
716
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
717
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
718
+ throw new Error(`${path}.${key} must be an enumerable own data property`);
719
+ }
720
+ assertPlainDataGraph(descriptor.value, `${path}.${key}`, seen);
721
+ }
722
+ }
723
+ function isDenseArrayOf(value, predicate) {
724
+ if (typeof value !== "object" || value === null || nodeUtilTypes.isProxy(value) || !Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) return false;
725
+ try {
726
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
727
+ if (lengthDescriptor === void 0 || !Object.hasOwn(lengthDescriptor, "value")) return false;
728
+ const length = lengthDescriptor.value;
729
+ const entries = /* @__PURE__ */ new Map();
730
+ for (const key of Reflect.ownKeys(value)) {
731
+ if (key === "length") continue;
732
+ if (typeof key !== "string") return false;
733
+ const index = Number(key);
734
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) return false;
735
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
736
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) return false;
737
+ entries.set(index, descriptor.value);
738
+ }
739
+ if (entries.size !== length) return false;
740
+ for (let index = 0; index < length; index += 1) {
741
+ if (!predicate(entries.get(index), index)) return false;
742
+ }
743
+ return true;
744
+ } catch {
745
+ return false;
746
+ }
747
+ }
748
+
749
+ // broker/boss-contracts.ts
750
+ function parseBossRegistrationRequest(value) {
751
+ assertPlainDataGraph(value, "$.boss");
752
+ assertRecord(value, "$.boss");
753
+ assertExactKeys(value, ["featureContract", "capabilityDigest", "credential"], [], "$.boss");
754
+ const featureContract = parseBossRunFeatureContract(value.featureContract);
755
+ const capabilityDigest = readHexDigest(value.capabilityDigest, "$.boss.capabilityDigest");
756
+ if (featureContract.feature !== BOSS_RUN_FEATURE_CONTRACT.feature || featureContract.version !== BOSS_RUN_FEATURE_CONTRACT.version || featureContract.baseProtocolVersion !== INTERCOM_BASE_PROTOCOL_VERSION || featureContract.semanticsHash !== BOSS_RUN_FEATURE_CONTRACT.semanticsHash || featureContract.controlEnvelopeVersion !== BOSS_RUN_FEATURE_CONTRACT.controlEnvelopeVersion || capabilityDigest !== BOSS_CAPABILITY_FEATURE_DIGEST) {
757
+ throw new ContractValidationError("$.boss", "Boss registration diverges from the canonical feature contract");
758
+ }
759
+ return {
760
+ featureContract,
761
+ capabilityDigest,
762
+ credential: parseBossParticipantCredentialEnvelope(value.credential)
763
+ };
764
+ }
765
+ function parseBossSessionMetadata(value, sessionId) {
766
+ assertPlainDataGraph(value, "$.boss");
767
+ assertRecord(value, "$.boss");
768
+ assertExactKeys(value, [
769
+ "binding",
770
+ "principal",
771
+ "liveWorker",
772
+ "featureContract",
773
+ "policySemanticsHash",
774
+ "capabilityDigest",
775
+ "brokerIdentityVerified"
776
+ ], [], "$.boss");
777
+ const binding = parseBossParticipantBinding(value.binding);
778
+ const parsedPrincipal = parseBossPolicyPrincipal(value.principal);
779
+ if (parsedPrincipal.principalClass !== "boss-private") {
780
+ throw new ContractValidationError("$.boss.principal", "must be a Boss-private principal");
781
+ }
782
+ const principal = parsedPrincipal;
783
+ assertRecord(value.liveWorker, "$.boss.liveWorker");
784
+ assertExactKeys(value.liveWorker, ["identity", "state"], [], "$.boss.liveWorker");
785
+ const liveWorkerIdentity = parseWorkerIdentityV2(value.liveWorker.identity);
786
+ const liveWorkerState = parseParticipantState(value.liveWorker.state, "$.boss.liveWorker.state");
787
+ const featureContract = parseBossRunFeatureContract(value.featureContract);
788
+ const policySemanticsHash = readHexDigest(value.policySemanticsHash, "$.boss.policySemanticsHash");
789
+ const capabilityDigest = readHexDigest(value.capabilityDigest, "$.boss.capabilityDigest");
790
+ if (value.brokerIdentityVerified !== true) {
791
+ throw new ContractValidationError("$.boss.brokerIdentityVerified", "must be true for a live Boss session");
792
+ }
793
+ if (sessionId !== void 0 && binding.sessionId !== sessionId) {
794
+ throw new ContractValidationError("$.boss.binding.sessionId", "must match the registered session ID");
795
+ }
796
+ if (sessionId !== void 0 && principal.principalId !== sessionId) {
797
+ throw new ContractValidationError("$.boss.principal.principalId", "must match the registered session ID");
798
+ }
799
+ if (!("bossRunId" in liveWorkerIdentity) || sessionId !== void 0 && liveWorkerIdentity.workerId !== sessionId || liveWorkerIdentity.bossRunId !== binding.bossRunId || liveWorkerIdentity.participantId !== binding.participantId || liveWorkerIdentity.bindingEpoch !== binding.bindingEpoch || isTerminalParticipantState(liveWorkerState)) {
800
+ throw new ContractValidationError(
801
+ "$.boss.liveWorker",
802
+ "must attest the same live Boss worker identity and binding as the registered session"
803
+ );
804
+ }
805
+ if (binding.state !== "active" || principal.state !== "active" || binding.bossRunId !== principal.bossRunId || binding.participantId !== principal.participantId || binding.role !== principal.role || binding.bindingEpoch !== principal.bindingEpoch || binding.assignedManagerParticipantId !== principal.assignedManagerParticipantId) {
806
+ throw new ContractValidationError("$.boss", "binding and policy principal are not the same active participant");
807
+ }
808
+ if (featureContract.semanticsHash !== BOSS_RUN_FEATURE_CONTRACT.semanticsHash || policySemanticsHash !== BOSS_POLICY_SEMANTICS_HASH || capabilityDigest !== BOSS_CAPABILITY_FEATURE_DIGEST) {
809
+ throw new ContractValidationError("$.boss", "Boss session attestation diverges from Core");
810
+ }
811
+ return {
812
+ binding,
813
+ principal,
814
+ liveWorker: { identity: liveWorkerIdentity, state: liveWorkerState },
815
+ featureContract,
816
+ policySemanticsHash,
817
+ capabilityDigest,
818
+ brokerIdentityVerified: true
819
+ };
820
+ }
821
+ function bossControlDispatchDenial(_target) {
822
+ return {
823
+ allowed: false,
824
+ code: "CONTROL_DISPATCH_UNAVAILABLE",
825
+ reason: "Typed Boss control dispatch is unavailable without authoritative correlation and durable dispatch"
826
+ };
827
+ }
828
+ function parseCorrelatedBossControl(value, messageId) {
829
+ assertPlainDataGraph(value, "$.control");
830
+ const envelope = parseBossControlEnvelope(value);
831
+ if (messageId !== void 0 && envelope.messageId !== messageId) {
832
+ throw new ContractValidationError("$.control.messageId", "must match the transport message ID");
833
+ }
834
+ return envelope;
835
+ }
836
+
837
+ // broker/authorization.ts
838
+ function policyPrincipalForSession(session) {
839
+ if (session.boss !== void 0) {
840
+ throw new Error(`Boss session ${session.id} cannot cross the frozen legacy policy boundary`);
841
+ }
842
+ if (session.origin === "remote") {
843
+ if (!session.parentSessionId || !session.rootSessionId || !session.generation) {
844
+ throw new Error(`Remote session ${session.id} is missing broker-owned policy metadata`);
845
+ }
846
+ return {
847
+ id: session.id,
848
+ kind: "remote",
849
+ state: "active",
850
+ generation: session.generation,
851
+ policy: "remote-tree",
852
+ parentSessionId: session.parentSessionId,
853
+ rootSessionId: session.rootSessionId
854
+ };
855
+ }
856
+ return {
857
+ id: session.id,
858
+ kind: "local",
859
+ state: "active",
860
+ generation: 1,
861
+ policy: "local-public",
862
+ rootSessionId: session.id
863
+ };
864
+ }
865
+ function featureAwarePolicyStateForSessions(sessions) {
866
+ const legacy = { principals: {} };
867
+ const boss = { principals: {} };
868
+ const registrations = {};
869
+ for (const session of sessions) {
870
+ if (session.boss === void 0) {
871
+ const principal = policyPrincipalForSession(session);
872
+ legacy.principals[session.id] = principal;
873
+ registrations[session.id] = {
874
+ principalId: session.id,
875
+ principalClass: "ordinary",
876
+ state: "active"
877
+ };
878
+ continue;
879
+ }
880
+ const metadata = parseBossSessionMetadata(session.boss, session.id);
881
+ boss.principals[session.id] = metadata.principal;
882
+ registrations[session.id] = {
883
+ principalId: session.id,
884
+ principalClass: "boss-bound",
885
+ state: "active",
886
+ bossRunId: metadata.binding.bossRunId,
887
+ participantId: metadata.binding.participantId,
888
+ bindingEpoch: metadata.binding.bindingEpoch,
889
+ featureContract: metadata.featureContract,
890
+ policySemanticsHash: metadata.policySemanticsHash,
891
+ capabilityDigest: metadata.capabilityDigest,
892
+ brokerIdentityVerified: metadata.brokerIdentityVerified
893
+ };
894
+ }
895
+ return { legacy, boss, registrations };
896
+ }
897
+ function authorizeSessionAction(sessions, actorId, action, targetId, context = {}) {
898
+ const state = featureAwarePolicyStateForSessions(sessions);
899
+ const actor = state.registrations[actorId];
900
+ const target = state.registrations[targetId];
901
+ return authorizeFeatureAware(state, {
902
+ actorId,
903
+ targetId,
904
+ action,
905
+ ...actor?.principalClass === "ordinary" && target?.principalClass === "ordinary" ? {
906
+ legacyContext: {
907
+ actorGeneration: state.legacy.principals[actorId]?.generation,
908
+ targetGeneration: state.legacy.principals[targetId]?.generation
909
+ }
910
+ } : action === "control" ? {
911
+ bossContext: {
912
+ controlKind: context.controlKind,
913
+ correlated: context.correlated
914
+ }
915
+ } : {}
916
+ });
917
+ }
918
+ function visibleSessions(sessions, actorId) {
919
+ const values = Array.from(sessions);
920
+ return values.filter((target) => authorizeSessionAction(values, actorId, "discover", target.id).allowed);
921
+ }
922
+
923
+ // broker/audit.ts
924
+ import { closeSync as closeSync3, fsyncSync as fsyncSync2, openSync as openSync3, writeSync } from "fs";
925
+ var BROKER_AUDIT_VERSION = 1;
926
+ var BrokerAuditLog = class {
927
+ constructor(path, now = Date.now) {
928
+ this.path = path;
929
+ this.now = now;
930
+ }
931
+ path;
932
+ now;
933
+ record(entry) {
934
+ const line = `${JSON.stringify({
935
+ version: BROKER_AUDIT_VERSION,
936
+ timestamp: this.now(),
937
+ ...entry
938
+ })}
939
+ `;
940
+ const descriptor = openSync3(this.path, "a", INTERCOM_RUNTIME_FILE_MODE);
941
+ try {
942
+ writeSync(descriptor, line, void 0, "utf8");
943
+ fsyncSync2(descriptor);
944
+ } finally {
945
+ closeSync3(descriptor);
946
+ }
947
+ restrictIntercomRuntimeFile(this.path);
948
+ }
949
+ tryRecord(entry) {
950
+ try {
951
+ this.record(entry);
952
+ } catch (error) {
953
+ console.error("Failed to append Agent Intercom broker audit event:", error);
954
+ }
955
+ }
956
+ };
957
+
958
+ // broker/broker.ts
959
+ var INTERCOM_DIR = getIntercomDirPath();
960
+ var LISTEN_TARGET = getBrokerListenTarget();
961
+ var REMOTE_LISTEN_TARGET = getRemoteGatewaySocketPath();
962
+ var PID_PATH = join2(INTERCOM_DIR, "broker.pid");
963
+ var OWNER_PATH = join2(INTERCOM_DIR, "broker.owner");
964
+ var PORT_PATH = getBrokerPortFilePath(INTERCOM_DIR);
965
+ var ASK_STATE_PATH = getBrokerAskStateFilePath(INTERCOM_DIR);
966
+ var ACCESS_STATE_PATH = getBrokerAccessStateFilePath(INTERCOM_DIR);
967
+ var ADMIN_CREDENTIAL_PATH = getBrokerAdminCredentialFilePath(INTERCOM_DIR);
968
+ var AUDIT_PATH = getBrokerAuditFilePath(INTERCOM_DIR);
969
+ var BROKER_STATE_ID = randomUUID3();
970
+ var MAX_SESSIONS = 128;
971
+ var MAX_UNREGISTERED_CONNECTIONS = 32;
972
+ var REGISTRATION_TIMEOUT_MS = 1e3;
973
+ var RATE_LIMIT_CAPACITY = 240;
974
+ var RATE_LIMIT_REFILL_PER_SECOND = 120;
975
+ var REMOTE_RATE_LIMIT_CAPACITY = 60;
976
+ var REMOTE_RATE_LIMIT_REFILL_PER_SECOND = 30;
977
+ var REMOTE_EXPIRY_SWEEP_MS = Math.max(50, Number.parseInt(process.env.PI_INTERCOM_REMOTE_EXPIRY_SWEEP_MS ?? "1000", 10) || 1e3);
978
+ var PRESENCE_HEARTBEAT_MS = 1e3;
979
+ var DELIVERY_ACK_TIMEOUT_MS = 8e3;
980
+ var RECENT_DELIVERY_TTL_MS = 10 * 60 * 1e3;
981
+ var MAX_PENDING_DELIVERIES = 1024;
982
+ var MAX_PENDING_DELIVERIES_PER_SESSION = 64;
983
+ var MAX_PENDING_ASKS_PER_SESSION = 64;
984
+ var RATE_LIMIT_BYTES_PER_TOKEN = 8 * 1024;
985
+ var MAX_MESSAGE_TEXT_BYTES = 256 * 1024;
986
+ var MAX_ATTACHMENT_CONTENT_BYTES = 512 * 1024;
987
+ var MAX_ATTACHMENTS = 16;
988
+ var MAX_MESSAGE_ID_LENGTH = 256;
989
+ var MAX_TARGET_LENGTH = 512;
990
+ var MAX_SESSION_NAME_LENGTH = 256;
991
+ var MAX_SESSION_CWD_LENGTH = 4096;
992
+ var MAX_SESSION_MODEL_LENGTH = 512;
993
+ var MAX_SESSION_STATUS_LENGTH = 512;
994
+ var MAX_RUNTIME_INSTANCE_ID_LENGTH = 256;
995
+ function isAttachment(value) {
996
+ if (!hasExactDataKeys(value, ["type", "name", "content"], ["language"])) {
997
+ return false;
998
+ }
999
+ const attachment = value;
1000
+ if (attachment.type !== "file" && attachment.type !== "snippet" && attachment.type !== "context") {
1001
+ return false;
1002
+ }
1003
+ if (typeof attachment.name !== "string" || attachment.name.length > 256 || typeof attachment.content !== "string" || Buffer.byteLength(attachment.content, "utf-8") > MAX_ATTACHMENT_CONTENT_BYTES) {
1004
+ return false;
1005
+ }
1006
+ return attachment.language === void 0 || typeof attachment.language === "string";
1007
+ }
1008
+ function isMessage(value) {
1009
+ if (!hasExactDataKeys(value, ["id", "timestamp", "content"], ["replyTo", "expectsReply", "control"])) {
1010
+ return false;
1011
+ }
1012
+ const message = value;
1013
+ if (typeof message.id !== "string" || message.id.length === 0 || message.id.length > MAX_MESSAGE_ID_LENGTH || typeof message.timestamp !== "number" || !Number.isFinite(message.timestamp)) {
1014
+ return false;
1015
+ }
1016
+ if (message.replyTo !== void 0 && (typeof message.replyTo !== "string" || message.replyTo.length === 0 || message.replyTo.length > MAX_MESSAGE_ID_LENGTH)) {
1017
+ return false;
1018
+ }
1019
+ if (message.expectsReply !== void 0 && typeof message.expectsReply !== "boolean") {
1020
+ return false;
1021
+ }
1022
+ if (message.control !== void 0) {
1023
+ try {
1024
+ parseCorrelatedBossControl(message.control, message.id);
1025
+ } catch {
1026
+ return false;
1027
+ }
1028
+ if (message.replyTo !== void 0 || message.expectsReply !== void 0) return false;
1029
+ }
1030
+ if (!hasExactDataKeys(message.content, ["text"], ["attachments"])) {
1031
+ return false;
1032
+ }
1033
+ const content = message.content;
1034
+ if (typeof content.text !== "string" || Buffer.byteLength(content.text, "utf-8") > MAX_MESSAGE_TEXT_BYTES) {
1035
+ return false;
1036
+ }
1037
+ if (message.control !== void 0 && (content.text !== "" || content.attachments !== void 0)) {
1038
+ return false;
1039
+ }
1040
+ return content.attachments === void 0 || isDenseArrayOf(content.attachments, isAttachment) && content.attachments.length <= MAX_ATTACHMENTS;
1041
+ }
1042
+ function isSessionId(value) {
1043
+ return typeof value === "string" && value.trim().length > 0;
1044
+ }
1045
+ function isSessionRegistration(value) {
1046
+ if (!hasExactDataKeys(
1047
+ value,
1048
+ ["cwd", "model", "pid", "startedAt", "lastActivity"],
1049
+ ["name", "status", "runtimeInstanceId", "boss"]
1050
+ )) {
1051
+ return false;
1052
+ }
1053
+ const session = value;
1054
+ if (typeof session.cwd !== "string" || session.cwd.length === 0 || session.cwd.length > MAX_SESSION_CWD_LENGTH || typeof session.model !== "string" || session.model.length === 0 || session.model.length > MAX_SESSION_MODEL_LENGTH || typeof session.pid !== "number" || !Number.isFinite(session.pid) || typeof session.startedAt !== "number" || !Number.isFinite(session.startedAt) || typeof session.lastActivity !== "number" || !Number.isFinite(session.lastActivity)) {
1055
+ return false;
1056
+ }
1057
+ if (session.name !== void 0 && (typeof session.name !== "string" || session.name.length > MAX_SESSION_NAME_LENGTH)) {
1058
+ return false;
1059
+ }
1060
+ if (session.runtimeInstanceId !== void 0 && (typeof session.runtimeInstanceId !== "string" || session.runtimeInstanceId.length === 0 || session.runtimeInstanceId.length > MAX_RUNTIME_INSTANCE_ID_LENGTH)) {
1061
+ return false;
1062
+ }
1063
+ if (session.boss !== void 0) {
1064
+ try {
1065
+ parseBossRegistrationRequest(session.boss);
1066
+ } catch {
1067
+ return false;
1068
+ }
1069
+ }
1070
+ return session.status === void 0 || typeof session.status === "string" && session.status.length <= MAX_SESSION_STATUS_LENGTH;
1071
+ }
1072
+ function isSameLocalRuntime(previous, registration) {
1073
+ if (previous.runtimeInstanceId !== void 0 || registration.runtimeInstanceId !== void 0) {
1074
+ return previous.runtimeInstanceId !== void 0 && previous.runtimeInstanceId === registration.runtimeInstanceId;
1075
+ }
1076
+ return previous.info.pid === registration.pid && previous.info.startedAt === registration.startedAt;
1077
+ }
1078
+ var IntercomBroker = class {
1079
+ sessions = /* @__PURE__ */ new Map();
1080
+ askEdges = /* @__PURE__ */ new Map();
1081
+ pendingDeliveries = /* @__PURE__ */ new Map();
1082
+ pendingDeliveryKeys = /* @__PURE__ */ new Map();
1083
+ recentDeliveries = /* @__PURE__ */ new Map();
1084
+ connections = /* @__PURE__ */ new Set();
1085
+ unregisteredConnections = /* @__PURE__ */ new Set();
1086
+ server;
1087
+ remoteServer = null;
1088
+ shutdownTimer = null;
1089
+ expiryTimer = null;
1090
+ askTimeoutMs = getAskTimeoutMs();
1091
+ accessRegistry;
1092
+ audit;
1093
+ constructor() {
1094
+ ensureIntercomRuntimeDir(INTERCOM_DIR);
1095
+ acquireBrokerOwnership(OWNER_PATH);
1096
+ this.accessRegistry = new RemoteAccessRegistry(ACCESS_STATE_PATH);
1097
+ this.audit = new BrokerAuditLog(AUDIT_PATH);
1098
+ this.accessRegistry.ensureAdminCredential(ADMIN_CREDENTIAL_PATH);
1099
+ this.loadAskEdges();
1100
+ if (typeof LISTEN_TARGET === "string" && process.platform !== "win32") {
1101
+ for (const socketPath of [LISTEN_TARGET, REMOTE_LISTEN_TARGET]) {
1102
+ try {
1103
+ unlinkSync2(socketPath);
1104
+ } catch {
1105
+ }
1106
+ }
1107
+ }
1108
+ this.server = net.createServer((socket) => this.handleConnection(socket, "local"));
1109
+ if (process.platform !== "win32" && typeof LISTEN_TARGET === "string") {
1110
+ this.remoteServer = net.createServer((socket) => this.handleConnection(socket, "remote"));
1111
+ }
1112
+ }
1113
+ start() {
1114
+ let localListening = false;
1115
+ let remoteListening = this.remoteServer === null;
1116
+ const announceWhenReady = () => {
1117
+ if (!localListening || !remoteListening) return;
1118
+ writeFileSync3(PID_PATH, String(process.pid), { mode: INTERCOM_RUNTIME_FILE_MODE });
1119
+ restrictIntercomRuntimeFile(PID_PATH);
1120
+ console.log(`Intercom broker started (pid: ${process.pid}, remote-access-v1)`);
1121
+ };
1122
+ const onLocalListening = () => {
1123
+ if (typeof LISTEN_TARGET === "string") {
1124
+ restrictIntercomRuntimeFile(LISTEN_TARGET);
1125
+ } else {
1126
+ const address = this.server.address();
1127
+ if (!address || typeof address === "string") {
1128
+ throw new Error("Intercom TCP broker started without a TCP address");
1129
+ }
1130
+ const endpoint = {
1131
+ transport: "tcp",
1132
+ host: LISTEN_TARGET.host,
1133
+ port: address.port,
1134
+ stateId: BROKER_STATE_ID
1135
+ };
1136
+ writeFileSync3(PORT_PATH, `${JSON.stringify(endpoint)}
1137
+ `, { mode: INTERCOM_RUNTIME_FILE_MODE });
1138
+ restrictIntercomRuntimeFile(PORT_PATH);
1139
+ }
1140
+ localListening = true;
1141
+ announceWhenReady();
1142
+ };
1143
+ if (typeof LISTEN_TARGET === "string") {
1144
+ this.server.listen(LISTEN_TARGET, onLocalListening);
1145
+ } else {
1146
+ this.server.listen({ host: LISTEN_TARGET.host, port: LISTEN_TARGET.port }, onLocalListening);
1147
+ }
1148
+ if (this.remoteServer) {
1149
+ this.remoteServer.listen(REMOTE_LISTEN_TARGET, () => {
1150
+ restrictIntercomRuntimeFile(REMOTE_LISTEN_TARGET);
1151
+ remoteListening = true;
1152
+ announceWhenReady();
1153
+ });
1154
+ }
1155
+ this.expiryTimer = setInterval(() => this.reconcileExpiredPrincipals(), REMOTE_EXPIRY_SWEEP_MS);
1156
+ this.expiryTimer.unref?.();
1157
+ process.on("SIGTERM", () => this.shutdown());
1158
+ process.on("SIGINT", () => this.shutdown());
1159
+ }
1160
+ handleConnection(socket, origin) {
1161
+ this.connections.add(socket);
1162
+ let sessionId = null;
1163
+ let registrationTimeout = null;
1164
+ const armRegistrationTimeout = () => {
1165
+ if (registrationTimeout) {
1166
+ clearTimeout(registrationTimeout);
1167
+ }
1168
+ this.unregisteredConnections.delete(socket);
1169
+ this.unregisteredConnections.add(socket);
1170
+ this.evictOldestUnregisteredConnections(socket);
1171
+ registrationTimeout = setTimeout(() => {
1172
+ if (!sessionId) {
1173
+ socket.destroy();
1174
+ }
1175
+ }, REGISTRATION_TIMEOUT_MS);
1176
+ registrationTimeout.unref?.();
1177
+ };
1178
+ const clearRegistrationTimeout = () => {
1179
+ if (registrationTimeout) {
1180
+ clearTimeout(registrationTimeout);
1181
+ registrationTimeout = null;
1182
+ }
1183
+ this.unregisteredConnections.delete(socket);
1184
+ };
1185
+ armRegistrationTimeout();
1186
+ const connection = {
1187
+ socket,
1188
+ origin,
1189
+ tokens: origin === "remote" ? REMOTE_RATE_LIMIT_CAPACITY : RATE_LIMIT_CAPACITY,
1190
+ refillPerSecond: origin === "remote" ? REMOTE_RATE_LIMIT_REFILL_PER_SECOND : RATE_LIMIT_REFILL_PER_SECOND,
1191
+ lastRefillAt: Date.now()
1192
+ };
1193
+ const reader = createMessageReader((msg) => {
1194
+ const byteCost = Math.max(1, Math.ceil(Buffer.byteLength(JSON.stringify(msg), "utf-8") / RATE_LIMIT_BYTES_PER_TOKEN));
1195
+ if (!this.consumeToken(connection, byteCost)) {
1196
+ this.sendError(socket, "RATE_LIMITED", "Intercom broker rate limit exceeded");
1197
+ socket.destroy(new Error("Intercom broker rate limit exceeded"));
1198
+ return;
1199
+ }
1200
+ try {
1201
+ this.handleMessage(socket, origin, msg, sessionId, (id) => {
1202
+ sessionId = id;
1203
+ if (id) {
1204
+ clearRegistrationTimeout();
1205
+ } else {
1206
+ armRegistrationTimeout();
1207
+ }
1208
+ });
1209
+ } catch (error) {
1210
+ const reason = error instanceof Error ? error.message : String(error);
1211
+ if (reason === "Invalid intercom TCP endpoint credentials") {
1212
+ socket.destroy();
1213
+ return;
1214
+ }
1215
+ this.sendError(socket, "INVALID_REQUEST", reason);
1216
+ socket.end();
1217
+ }
1218
+ }, (error) => {
1219
+ socket.destroy(error);
1220
+ });
1221
+ socket.on("data", reader);
1222
+ socket.on("close", () => {
1223
+ clearRegistrationTimeout();
1224
+ this.connections.delete(socket);
1225
+ if (sessionId) {
1226
+ const existing = this.sessions.get(sessionId);
1227
+ if (existing?.socket === socket) {
1228
+ if (existing.info.origin === "remote") {
1229
+ this.audit.tryRecord({
1230
+ event: "remote_disconnect",
1231
+ outcome: "observed",
1232
+ actorId: sessionId,
1233
+ remoteHostId: existing.info.remoteHostId,
1234
+ generation: existing.info.generation,
1235
+ reason: "SOCKET_CLOSED"
1236
+ });
1237
+ }
1238
+ this.broadcastLifecycle({ type: "session_left", sessionId }, existing, sessionId);
1239
+ this.sessions.delete(sessionId);
1240
+ this.clearPendingDeliveriesForSession(sessionId, socket);
1241
+ this.deferAskEdgesForSession(sessionId);
1242
+ this.scheduleShutdownCheck();
1243
+ }
1244
+ }
1245
+ });
1246
+ socket.on("error", (error) => {
1247
+ console.error("Socket error:", error);
1248
+ });
1249
+ }
1250
+ evictOldestUnregisteredConnections(currentSocket) {
1251
+ while (this.unregisteredConnections.size > MAX_UNREGISTERED_CONNECTIONS) {
1252
+ const [oldest] = this.unregisteredConnections;
1253
+ if (!oldest) {
1254
+ return;
1255
+ }
1256
+ if (oldest === currentSocket && this.unregisteredConnections.size === 1) {
1257
+ return;
1258
+ }
1259
+ this.unregisteredConnections.delete(oldest);
1260
+ oldest.destroy();
1261
+ }
1262
+ }
1263
+ consumeToken(connection, cost = 1, now = Date.now()) {
1264
+ const elapsedMs = now - connection.lastRefillAt;
1265
+ if (elapsedMs > 0) {
1266
+ connection.tokens = Math.min(
1267
+ connection.origin === "remote" ? REMOTE_RATE_LIMIT_CAPACITY : RATE_LIMIT_CAPACITY,
1268
+ connection.tokens + elapsedMs * connection.refillPerSecond / 1e3
1269
+ );
1270
+ connection.lastRefillAt = now;
1271
+ }
1272
+ if (connection.tokens < cost) {
1273
+ return false;
1274
+ }
1275
+ connection.tokens -= cost;
1276
+ return true;
1277
+ }
1278
+ sendError(socket, code, error) {
1279
+ writeMessage(socket, { type: "error", code, error });
1280
+ }
1281
+ sendDeliveryFailure(socket, messageId, accepted, code, reason) {
1282
+ writeMessage(socket, { type: "delivery_failed", messageId, accepted, code, reason });
1283
+ }
1284
+ scheduleShutdownCheck() {
1285
+ if (this.shutdownTimer) return;
1286
+ this.shutdownTimer = setTimeout(() => {
1287
+ this.shutdownTimer = null;
1288
+ if (this.sessions.size === 0) {
1289
+ console.log("No sessions connected, shutting down");
1290
+ this.shutdown();
1291
+ }
1292
+ }, 5e3);
1293
+ }
1294
+ handleMessage(socket, origin, msg, currentId, setId) {
1295
+ if (!isPlainDataRecord(msg) || typeof msg.type !== "string") {
1296
+ throw new Error("Invalid client message");
1297
+ }
1298
+ const clientMessage = msg;
1299
+ const requiresEndpointAuth = typeof LISTEN_TARGET !== "string";
1300
+ const hasEndpointAuth = clientMessage.stateId === BROKER_STATE_ID;
1301
+ if (clientMessage.type === "health") {
1302
+ if (!hasExactDataKeys(clientMessage, ["type", "requestId"], ["stateId"]) || typeof clientMessage.requestId !== "string") {
1303
+ throw new Error("Invalid health message");
1304
+ }
1305
+ if (requiresEndpointAuth && !hasEndpointAuth) {
1306
+ throw new Error("Invalid intercom TCP endpoint credentials");
1307
+ }
1308
+ writeMessage(socket, {
1309
+ type: "health_ok",
1310
+ requestId: clientMessage.requestId,
1311
+ protocol: INTERCOM_PROTOCOL_NAME2,
1312
+ version: INTERCOM_PROTOCOL_VERSION2,
1313
+ endpoint: origin,
1314
+ remoteAccess: this.remoteAccessContract()
1315
+ });
1316
+ return;
1317
+ }
1318
+ if (clientMessage.type === "access_control") {
1319
+ if (currentId !== null) {
1320
+ this.sendError(socket, "ACCESS_DENIED", "Remote access control requires a short-lived control connection");
1321
+ socket.end();
1322
+ return;
1323
+ }
1324
+ if (origin === "local") this.handleAccessControl(socket, clientMessage);
1325
+ else this.handleRemoteAccessControl(socket, clientMessage);
1326
+ return;
1327
+ }
1328
+ if (requiresEndpointAuth && clientMessage.type === "register" && !hasEndpointAuth) {
1329
+ throw new Error("Invalid intercom TCP endpoint credentials");
1330
+ }
1331
+ if (currentId === null && clientMessage.type !== "register") {
1332
+ throw new Error(`Received ${clientMessage.type} before register`);
1333
+ }
1334
+ if (currentId) {
1335
+ const bound = this.sessions.get(currentId);
1336
+ if (bound?.socket !== socket) {
1337
+ socket.destroy();
1338
+ return;
1339
+ }
1340
+ if (!this.isCurrentPrincipal(currentId)) {
1341
+ this.sendError(socket, "ACCESS_DENIED", "Remote session authorization is no longer valid");
1342
+ socket.destroy();
1343
+ return;
1344
+ }
1345
+ }
1346
+ switch (clientMessage.type) {
1347
+ case "register": {
1348
+ if (!hasExactDataKeys(
1349
+ clientMessage,
1350
+ ["type", "protocol", "version", "session"],
1351
+ origin === "remote" ? ["sessionId", "stateId", "access", "scopeId"] : ["sessionId", "stateId", "scopeId"]
1352
+ )) {
1353
+ throw new Error("Invalid register message");
1354
+ }
1355
+ if (clientMessage.protocol !== INTERCOM_PROTOCOL_NAME2 || clientMessage.version !== INTERCOM_PROTOCOL_VERSION2) {
1356
+ this.sendError(
1357
+ socket,
1358
+ "PROTOCOL_MISMATCH",
1359
+ `Unsupported intercom protocol; expected ${INTERCOM_PROTOCOL_NAME2} v${INTERCOM_PROTOCOL_VERSION2}`
1360
+ );
1361
+ socket.end();
1362
+ break;
1363
+ }
1364
+ let scopeId;
1365
+ try {
1366
+ scopeId = parseIntercomScopeIdForRegistration(clientMessage.scopeId);
1367
+ } catch {
1368
+ this.sendError(socket, "INVALID_REQUEST", INTERCOM_REGISTRATION_METADATA_ERROR);
1369
+ socket.end();
1370
+ break;
1371
+ }
1372
+ if (!isSessionRegistration(clientMessage.session)) {
1373
+ throw new Error("Invalid register message");
1374
+ }
1375
+ if (currentId) {
1376
+ throw new Error("Received duplicate register message");
1377
+ }
1378
+ if (clientMessage.session.boss !== void 0) {
1379
+ this.sendError(
1380
+ socket,
1381
+ "BOSS_FEATURE_UNAVAILABLE",
1382
+ "boss-run-v1 is dormant until the protected broker lockstep readiness contract is complete"
1383
+ );
1384
+ socket.end();
1385
+ break;
1386
+ }
1387
+ let id;
1388
+ let remotePrincipal;
1389
+ let issuedSessionCredential;
1390
+ let enrollmentConsumed = false;
1391
+ let previousLocal;
1392
+ if (origin === "remote") {
1393
+ if (this.sessions.size >= MAX_SESSIONS) {
1394
+ this.sendError(socket, "TOO_MANY_SESSIONS", "Too many registered intercom sessions");
1395
+ socket.destroy();
1396
+ break;
1397
+ }
1398
+ const access = clientMessage.access;
1399
+ if (!isPlainDataRecord(access)) {
1400
+ this.audit.tryRecord({ event: "remote_registration_denied", outcome: "denied", reason: "MISSING_CREDENTIAL" });
1401
+ this.sendError(socket, "ACCESS_DENIED", "Remote registration requires an access credential");
1402
+ socket.end();
1403
+ break;
1404
+ }
1405
+ const fields = access;
1406
+ try {
1407
+ if (typeof fields.enrollmentToken === "string") {
1408
+ const consumed = this.accessRegistry.consumeEnrollment(fields.enrollmentToken);
1409
+ remotePrincipal = consumed.principal;
1410
+ issuedSessionCredential = consumed.sessionCredential;
1411
+ enrollmentConsumed = true;
1412
+ } else if (typeof fields.sessionCredential === "string" && typeof fields.sessionId === "string" && typeof fields.generation === "number" && Number.isSafeInteger(fields.generation)) {
1413
+ remotePrincipal = this.accessRegistry.authenticateSession(fields.sessionId, fields.generation, fields.sessionCredential);
1414
+ } else {
1415
+ throw new Error("Invalid remote access credential shape");
1416
+ }
1417
+ } catch {
1418
+ this.audit.tryRecord({ event: "remote_registration_denied", outcome: "denied", reason: "INVALID_CREDENTIAL" });
1419
+ this.sendError(socket, "ACCESS_DENIED", "Remote registration credential was rejected");
1420
+ socket.end();
1421
+ break;
1422
+ }
1423
+ id = remotePrincipal.id;
1424
+ if (this.sessions.has(id)) {
1425
+ this.audit.tryRecord({
1426
+ event: "credential_reuse_denied",
1427
+ outcome: "denied",
1428
+ actorId: id,
1429
+ remoteHostId: remotePrincipal.remoteHostId,
1430
+ generation: remotePrincipal.generation,
1431
+ reason: "ALREADY_ACTIVE"
1432
+ });
1433
+ this.sendError(socket, "ACCESS_DENIED", "Remote session credential is already active");
1434
+ socket.end();
1435
+ break;
1436
+ }
1437
+ } else {
1438
+ id = randomUUID3();
1439
+ if (clientMessage.sessionId !== void 0) {
1440
+ if (!isSessionId(clientMessage.sessionId)) {
1441
+ throw new Error("Invalid register sessionId");
1442
+ }
1443
+ id = clientMessage.sessionId;
1444
+ }
1445
+ const previous = this.sessions.get(id);
1446
+ if (!previous && this.sessions.size >= MAX_SESSIONS) {
1447
+ this.sendError(socket, "TOO_MANY_SESSIONS", "Too many registered intercom sessions");
1448
+ socket.destroy();
1449
+ break;
1450
+ }
1451
+ if (previous && !isSameLocalRuntime(previous, clientMessage.session)) {
1452
+ this.sendError(
1453
+ socket,
1454
+ "SESSION_ID_IN_USE",
1455
+ `Session ID "${id}" is already active in another local runtime; close the existing session or use a different session ID`
1456
+ );
1457
+ socket.end();
1458
+ break;
1459
+ }
1460
+ previousLocal = previous;
1461
+ }
1462
+ setId(id);
1463
+ const session = clientMessage.session;
1464
+ const info = remotePrincipal ? {
1465
+ id,
1466
+ name: remotePrincipal.name,
1467
+ cwd: session.cwd,
1468
+ model: session.model,
1469
+ pid: session.pid,
1470
+ startedAt: session.startedAt,
1471
+ lastActivity: session.lastActivity,
1472
+ ...session.status !== void 0 ? { status: session.status } : {},
1473
+ trustedLocal: false,
1474
+ origin: "remote",
1475
+ remoteHostId: remotePrincipal.remoteHostId,
1476
+ parentSessionId: remotePrincipal.parentSessionId,
1477
+ rootSessionId: remotePrincipal.rootSessionId,
1478
+ generation: remotePrincipal.generation,
1479
+ canDelegate: remotePrincipal.canDelegate,
1480
+ depth: remotePrincipal.depth,
1481
+ maxDepth: remotePrincipal.maxDepth,
1482
+ maxChildren: remotePrincipal.maxChildren
1483
+ } : {
1484
+ id,
1485
+ ...session.name !== void 0 ? { name: session.name } : {},
1486
+ cwd: session.cwd,
1487
+ model: session.model,
1488
+ pid: session.pid,
1489
+ startedAt: session.startedAt,
1490
+ lastActivity: session.lastActivity,
1491
+ ...session.status !== void 0 ? { status: session.status } : {},
1492
+ trustedLocal: typeof LISTEN_TARGET === "string" && process.platform !== "win32",
1493
+ origin: "local"
1494
+ };
1495
+ if (remotePrincipal) {
1496
+ this.audit.record({
1497
+ event: enrollmentConsumed ? "enrollment_consumed" : "remote_reconnect",
1498
+ outcome: "allowed",
1499
+ actorId: id,
1500
+ targetId: remotePrincipal.parentSessionId,
1501
+ remoteHostId: remotePrincipal.remoteHostId,
1502
+ generation: remotePrincipal.generation
1503
+ });
1504
+ this.audit.record({
1505
+ event: "remote_connect",
1506
+ outcome: "allowed",
1507
+ actorId: id,
1508
+ targetId: remotePrincipal.parentSessionId,
1509
+ remoteHostId: remotePrincipal.remoteHostId,
1510
+ generation: remotePrincipal.generation
1511
+ });
1512
+ }
1513
+ const connected = {
1514
+ socket,
1515
+ info,
1516
+ ...scopeId ? { scopeId } : {},
1517
+ ...!remotePrincipal && clientMessage.session.runtimeInstanceId ? { runtimeInstanceId: clientMessage.session.runtimeInstanceId } : {},
1518
+ lastPresenceBroadcastAt: Date.now()
1519
+ };
1520
+ if (previousLocal) {
1521
+ this.broadcastLifecycle({ type: "session_left", sessionId: id }, previousLocal, id);
1522
+ this.clearPendingDeliveriesForSession(id, previousLocal.socket);
1523
+ this.deferAskEdgesForSession(id);
1524
+ }
1525
+ this.sessions.set(id, connected);
1526
+ if (this.shutdownTimer) {
1527
+ clearTimeout(this.shutdownTimer);
1528
+ this.shutdownTimer = null;
1529
+ }
1530
+ writeMessage(socket, {
1531
+ type: "registered",
1532
+ sessionId: id,
1533
+ protocol: INTERCOM_PROTOCOL_NAME2,
1534
+ version: INTERCOM_PROTOCOL_VERSION2,
1535
+ ...remotePrincipal ? {
1536
+ remoteAccess: this.remoteAccessContract(),
1537
+ access: {
1538
+ origin: "remote",
1539
+ remoteHostId: remotePrincipal.remoteHostId,
1540
+ parentSessionId: remotePrincipal.parentSessionId,
1541
+ rootSessionId: remotePrincipal.rootSessionId,
1542
+ generation: remotePrincipal.generation,
1543
+ canDelegate: remotePrincipal.canDelegate,
1544
+ depth: remotePrincipal.depth,
1545
+ maxDepth: remotePrincipal.maxDepth,
1546
+ maxChildren: remotePrincipal.maxChildren,
1547
+ ...issuedSessionCredential ? { sessionCredential: issuedSessionCredential } : {}
1548
+ }
1549
+ } : {}
1550
+ });
1551
+ this.broadcastLifecycle({ type: "session_joined", session: info }, connected, id);
1552
+ previousLocal?.socket.end();
1553
+ break;
1554
+ }
1555
+ case "unregister": {
1556
+ if (!currentId) {
1557
+ throw new Error("Received unregister before register");
1558
+ }
1559
+ if (clientMessage.preserveAsks !== void 0 && typeof clientMessage.preserveAsks !== "boolean") {
1560
+ throw new Error("Invalid unregister preserveAsks value");
1561
+ }
1562
+ const existing = this.sessions.get(currentId);
1563
+ if (existing?.socket === socket) {
1564
+ if (existing.info.origin === "remote") {
1565
+ this.audit.tryRecord({
1566
+ event: "remote_disconnect",
1567
+ outcome: "observed",
1568
+ actorId: currentId,
1569
+ remoteHostId: existing.info.remoteHostId,
1570
+ generation: existing.info.generation,
1571
+ reason: "UNREGISTERED"
1572
+ });
1573
+ }
1574
+ this.broadcastLifecycle({ type: "session_left", sessionId: currentId }, existing, currentId);
1575
+ this.sessions.delete(currentId);
1576
+ this.clearPendingDeliveriesForSession(currentId, socket);
1577
+ if (clientMessage.preserveAsks) {
1578
+ this.deferAskEdgesForSession(currentId);
1579
+ } else {
1580
+ this.clearAskEdgesForSession(currentId, "session_disconnected");
1581
+ }
1582
+ this.scheduleShutdownCheck();
1583
+ }
1584
+ setId(null);
1585
+ break;
1586
+ }
1587
+ case "list": {
1588
+ if (typeof clientMessage.requestId !== "string") {
1589
+ throw new Error("Invalid list message");
1590
+ }
1591
+ const allConnected = Array.from(this.sessions.values());
1592
+ const actor = this.sessions.get(currentId);
1593
+ const sameScopeConnected = actor ? allConnected.filter((candidate) => sameIntercomScope(actor.scopeId, candidate.scopeId)) : [];
1594
+ const sameScopeInfos = sameScopeConnected.map((s) => s.info);
1595
+ const sessions = visibleSessions(sameScopeInfos, currentId);
1596
+ if (actor?.info.origin === "remote" && sessions.length < allConnected.length) {
1597
+ this.audit.tryRecord({
1598
+ event: "remote_visibility_filtered",
1599
+ outcome: "observed",
1600
+ actorId: currentId,
1601
+ remoteHostId: actor.info.remoteHostId,
1602
+ generation: actor.info.generation,
1603
+ visibleCount: sessions.length,
1604
+ hiddenCount: allConnected.length - sessions.length
1605
+ });
1606
+ }
1607
+ writeMessage(socket, { type: "sessions", requestId: clientMessage.requestId, sessions });
1608
+ break;
1609
+ }
1610
+ case "send": {
1611
+ if (!currentId) {
1612
+ throw new Error("Received send before register");
1613
+ }
1614
+ const message = clientMessage.message;
1615
+ const messageId = isPlainDataRecord(message) && typeof message.id === "string" && message.id.length > 0 && message.id.length <= MAX_MESSAGE_ID_LENGTH ? message.id : "unknown";
1616
+ if (typeof clientMessage.to !== "string" || clientMessage.to.length === 0 || clientMessage.to.length > MAX_TARGET_LENGTH || !isMessage(message)) {
1617
+ this.sendDeliveryFailure(socket, messageId, false, "INVALID_MESSAGE", "Invalid message format");
1618
+ break;
1619
+ }
1620
+ const fromSession = this.sessions.get(currentId);
1621
+ if (message.control !== void 0) {
1622
+ const denial = bossControlDispatchDenial(clientMessage.to);
1623
+ this.sendDeliveryFailure(socket, message.id, false, denial.code, denial.reason);
1624
+ break;
1625
+ }
1626
+ const action = message.replyTo ? "reply" : message.expectsReply ? "ask" : "send";
1627
+ const authorizationContext = {};
1628
+ this.pruneRecentDeliveries();
1629
+ const deliveryKey = this.deliveryKey(currentId, message.id);
1630
+ const fingerprint = JSON.stringify({
1631
+ to: clientMessage.to,
1632
+ replyTo: message.replyTo,
1633
+ expectsReply: message.expectsReply,
1634
+ control: message.control,
1635
+ content: message.content
1636
+ });
1637
+ const recent = this.recentDeliveries.get(deliveryKey);
1638
+ if (recent) {
1639
+ if (recent.fingerprint !== fingerprint) {
1640
+ this.sendDeliveryFailure(socket, message.id, false, "DUPLICATE_MESSAGE_ID", "Message ID was already used with a different payload");
1641
+ break;
1642
+ }
1643
+ const actor = this.sessions.get(currentId);
1644
+ const target = this.sessions.get(recent.to);
1645
+ const authorizationStillValid = Boolean(
1646
+ actor && target && this.authorizationEpoch(actor.info) === recent.fromGeneration && this.authorizationEpoch(target.info) === recent.toGeneration && this.isAuthorized(currentId, recent.action, recent.to, recent.controlKind === void 0 ? {} : { controlKind: recent.controlKind, correlated: false })
1647
+ );
1648
+ if (recent.retryable || !authorizationStillValid) {
1649
+ this.recentDeliveries.delete(deliveryKey);
1650
+ } else {
1651
+ if (recent.response.type === "delivered") {
1652
+ writeMessage(socket, {
1653
+ type: "delivery_accepted",
1654
+ messageId: message.id,
1655
+ deliveryId: recent.response.deliveryId
1656
+ });
1657
+ }
1658
+ writeMessage(socket, recent.response);
1659
+ break;
1660
+ }
1661
+ }
1662
+ const existingDeliveryId = this.pendingDeliveryKeys.get(deliveryKey);
1663
+ if (existingDeliveryId) {
1664
+ const existing = this.pendingDeliveries.get(existingDeliveryId);
1665
+ if (!existing || existing.fingerprint !== fingerprint) {
1666
+ this.sendDeliveryFailure(socket, message.id, false, "DUPLICATE_MESSAGE_ID", "Message ID is already pending with a different payload");
1667
+ break;
1668
+ }
1669
+ const actor = this.sessions.get(existing.from);
1670
+ const target = this.sessions.get(existing.to);
1671
+ if (actor && target && this.authorizationEpoch(actor.info) === existing.fromGeneration && this.authorizationEpoch(target.info) === existing.toGeneration && this.isAuthorized(existing.from, existing.action, existing.to, existing.controlKind === void 0 ? {} : { controlKind: existing.controlKind, correlated: false })) {
1672
+ writeMessage(socket, { type: "delivery_accepted", messageId: message.id, deliveryId: existing.id });
1673
+ break;
1674
+ }
1675
+ this.failPendingDelivery(existing.id, "SESSION_NOT_FOUND", "Delivery authorization changed while pending");
1676
+ }
1677
+ if (this.pendingDeliveries.size >= MAX_PENDING_DELIVERIES || this.countPendingDeliveriesFrom(currentId) >= MAX_PENDING_DELIVERIES_PER_SESSION) {
1678
+ this.sendDeliveryFailure(socket, message.id, false, "TOO_MANY_PENDING_DELIVERIES", "Too many messages are waiting for receiver acknowledgement");
1679
+ break;
1680
+ }
1681
+ const candidates = this.findSessions(currentId, clientMessage.to);
1682
+ const targets = candidates.filter((target) => this.isAuthorized(currentId, action, target.info.id, authorizationContext));
1683
+ if (candidates.length > 0 && targets.length === 0) {
1684
+ const actor = this.sessions.get(currentId);
1685
+ this.audit.tryRecord({
1686
+ event: "remote_delivery_denied",
1687
+ outcome: "denied",
1688
+ actorId: currentId,
1689
+ targetId: candidates.length === 1 ? candidates[0].info.id : void 0,
1690
+ remoteHostId: actor?.info.remoteHostId ?? candidates.find((candidate) => candidate.info.remoteHostId)?.info.remoteHostId,
1691
+ generation: actor?.info.generation,
1692
+ reason: "POLICY_DENIED"
1693
+ });
1694
+ }
1695
+ if (targets.length === 1) {
1696
+ if (!fromSession || fromSession.socket !== socket) {
1697
+ this.sendDeliveryFailure(socket, message.id, false, "SENDER_NOT_FOUND", "Sender session not found");
1698
+ break;
1699
+ }
1700
+ const target = targets[0];
1701
+ const replyEdge = message.replyTo ? this.askEdges.get(this.askKey(target.info.id, message.replyTo)) : void 0;
1702
+ if (message.replyTo && !replyEdge) {
1703
+ this.sendDeliveryFailure(socket, message.id, false, "INVALID_REPLY_TARGET", "Reply target does not match a pending ask");
1704
+ break;
1705
+ }
1706
+ if (replyEdge && (replyEdge.to !== currentId || replyEdge.from !== target.info.id)) {
1707
+ this.sendDeliveryFailure(socket, message.id, false, "INVALID_REPLY_TARGET", "Reply target does not match the pending ask");
1708
+ break;
1709
+ }
1710
+ if (message.expectsReply) {
1711
+ const existingAsk = Array.from(this.askEdges.values()).find(
1712
+ (edge) => edge.from === currentId && edge.to === target.info.id
1713
+ );
1714
+ if (existingAsk) {
1715
+ this.sendDeliveryFailure(socket, message.id, false, "ASK_ALREADY_PENDING", "Another ask to this session is still unresolved. Wait for its reply or use intercom_send for a non-blocking follow-up.");
1716
+ break;
1717
+ }
1718
+ const reverseEdge = Array.from(this.askEdges.values()).find(
1719
+ (edge) => edge.state === "blocking" && !(message.replyTo === edge.messageId && target.info.id === edge.from) && edge.from === target.info.id && edge.to === currentId
1720
+ );
1721
+ if (reverseEdge) {
1722
+ this.sendDeliveryFailure(socket, message.id, false, "MUTUAL_ASK", "Mutual ask refused: target session is already waiting for a reply from this session.");
1723
+ break;
1724
+ }
1725
+ if (this.countAskEdgesFrom(currentId) >= MAX_PENDING_ASKS_PER_SESSION) {
1726
+ this.sendDeliveryFailure(socket, message.id, false, "TOO_MANY_PENDING_ASKS", "Too many asks are already waiting for replies");
1727
+ break;
1728
+ }
1729
+ this.addAskEdge(message.id, currentId, target.info.id);
1730
+ }
1731
+ const deliveryId = randomUUID3();
1732
+ const timeout = setTimeout(() => {
1733
+ this.failPendingDelivery(deliveryId, "DELIVERY_TIMEOUT", "Recipient did not acknowledge the message in time");
1734
+ }, DELIVERY_ACK_TIMEOUT_MS);
1735
+ timeout.unref?.();
1736
+ const pending = {
1737
+ id: deliveryId,
1738
+ key: deliveryKey,
1739
+ fingerprint,
1740
+ message,
1741
+ from: currentId,
1742
+ to: target.info.id,
1743
+ senderSocket: socket,
1744
+ recipientSocket: target.socket,
1745
+ action,
1746
+ fromGeneration: this.authorizationEpoch(fromSession.info),
1747
+ toGeneration: this.authorizationEpoch(target.info),
1748
+ timeout
1749
+ };
1750
+ this.pendingDeliveries.set(deliveryId, pending);
1751
+ this.pendingDeliveryKeys.set(deliveryKey, deliveryId);
1752
+ writeMessage(socket, { type: "delivery_accepted", messageId: message.id, deliveryId });
1753
+ writeMessage(target.socket, {
1754
+ type: "message",
1755
+ deliveryId,
1756
+ from: fromSession.info,
1757
+ message
1758
+ });
1759
+ break;
1760
+ }
1761
+ if (targets.length > 1) {
1762
+ this.sendDeliveryFailure(socket, message.id, false, "AMBIGUOUS_TARGET", `Multiple sessions named "${clientMessage.to}" are connected. Use the session ID instead.`);
1763
+ break;
1764
+ }
1765
+ this.sendDeliveryFailure(socket, message.id, false, "SESSION_NOT_FOUND", "Session not found");
1766
+ break;
1767
+ }
1768
+ case "message_received": {
1769
+ if (!currentId) {
1770
+ throw new Error("Received message_received before register");
1771
+ }
1772
+ if (typeof clientMessage.deliveryId !== "string") {
1773
+ throw new Error("Invalid message_received message");
1774
+ }
1775
+ this.acknowledgePendingDelivery(clientMessage.deliveryId, currentId, socket);
1776
+ break;
1777
+ }
1778
+ case "message_rejected": {
1779
+ if (!currentId) {
1780
+ throw new Error("Received message_rejected before register");
1781
+ }
1782
+ if (typeof clientMessage.deliveryId !== "string" || clientMessage.code !== "CONFLICTING_MESSAGE_ID" || typeof clientMessage.reason !== "string" || clientMessage.reason.length > 1024) {
1783
+ throw new Error("Invalid message_rejected message");
1784
+ }
1785
+ const pending = this.pendingDeliveries.get(clientMessage.deliveryId);
1786
+ if (pending?.to === currentId && pending.recipientSocket === socket) {
1787
+ this.failPendingDelivery(clientMessage.deliveryId, clientMessage.code, clientMessage.reason);
1788
+ }
1789
+ break;
1790
+ }
1791
+ case "defer_ask": {
1792
+ if (!currentId) {
1793
+ throw new Error("Received defer_ask before register");
1794
+ }
1795
+ if (typeof clientMessage.messageId !== "string" || clientMessage.messageId.length > MAX_MESSAGE_ID_LENGTH || typeof clientMessage.requestId !== "string" || clientMessage.requestId.length > MAX_MESSAGE_ID_LENGTH) {
1796
+ throw new Error("Invalid defer_ask message");
1797
+ }
1798
+ const session = this.sessions.get(currentId);
1799
+ const edge = this.askEdges.get(this.askKey(currentId, clientMessage.messageId));
1800
+ const applied = Boolean(session?.socket === socket && edge?.from === currentId);
1801
+ if (applied && edge?.state === "blocking") {
1802
+ edge.state = "deferred";
1803
+ this.persistAskEdges();
1804
+ this.notifyAskDeferred(edge);
1805
+ }
1806
+ writeMessage(socket, { type: "ask_control_result", requestId: clientMessage.requestId, action: "defer", messageId: clientMessage.messageId, applied });
1807
+ break;
1808
+ }
1809
+ case "cancel_ask": {
1810
+ if (!currentId) {
1811
+ throw new Error("Received cancel_ask before register");
1812
+ }
1813
+ if (typeof clientMessage.messageId !== "string" || clientMessage.messageId.length > MAX_MESSAGE_ID_LENGTH || typeof clientMessage.requestId !== "string" || clientMessage.requestId.length > MAX_MESSAGE_ID_LENGTH) {
1814
+ throw new Error("Invalid cancel_ask message");
1815
+ }
1816
+ const session = this.sessions.get(currentId);
1817
+ const edgeKey = this.askKey(currentId, clientMessage.messageId);
1818
+ const edge = this.askEdges.get(edgeKey);
1819
+ const applied = Boolean(session?.socket === socket && edge?.from === currentId);
1820
+ if (applied) {
1821
+ this.removeAskEdge(edgeKey, "cancelled", true);
1822
+ }
1823
+ writeMessage(socket, { type: "ask_control_result", requestId: clientMessage.requestId, action: "cancel", messageId: clientMessage.messageId, applied });
1824
+ break;
1825
+ }
1826
+ case "presence": {
1827
+ if (!currentId) {
1828
+ throw new Error("Received presence before register");
1829
+ }
1830
+ const session = this.sessions.get(currentId);
1831
+ if (session?.socket === socket) {
1832
+ let changed = false;
1833
+ if (clientMessage.name !== void 0) {
1834
+ if (typeof clientMessage.name !== "string" || clientMessage.name.length > MAX_SESSION_NAME_LENGTH) {
1835
+ throw new Error("Invalid presence name");
1836
+ }
1837
+ if (session.info.origin !== "remote" && session.info.name !== clientMessage.name) {
1838
+ session.info.name = clientMessage.name;
1839
+ changed = true;
1840
+ }
1841
+ }
1842
+ if (clientMessage.status !== void 0) {
1843
+ if (typeof clientMessage.status !== "string" || clientMessage.status.length > MAX_SESSION_STATUS_LENGTH) {
1844
+ throw new Error("Invalid presence status");
1845
+ }
1846
+ if (session.info.status !== clientMessage.status) {
1847
+ session.info.status = clientMessage.status;
1848
+ changed = true;
1849
+ }
1850
+ }
1851
+ if (clientMessage.model !== void 0) {
1852
+ if (typeof clientMessage.model !== "string" || clientMessage.model.length > MAX_SESSION_MODEL_LENGTH) {
1853
+ throw new Error("Invalid presence model");
1854
+ }
1855
+ if (session.info.model !== clientMessage.model) {
1856
+ session.info.model = clientMessage.model;
1857
+ changed = true;
1858
+ }
1859
+ }
1860
+ const now = Date.now();
1861
+ session.info.lastActivity = now;
1862
+ if (changed || now - session.lastPresenceBroadcastAt >= PRESENCE_HEARTBEAT_MS) {
1863
+ session.lastPresenceBroadcastAt = now;
1864
+ this.broadcastLifecycle({ type: "presence_update", session: session.info }, session, currentId);
1865
+ }
1866
+ }
1867
+ break;
1868
+ }
1869
+ default:
1870
+ throw new Error(`Unknown client message type: ${clientMessage.type}`);
1871
+ }
1872
+ }
1873
+ remoteAccessContract() {
1874
+ return {
1875
+ feature: "remote-access-v1",
1876
+ policySemanticsVersion: POLICY_SEMANTICS_VERSION,
1877
+ policySemanticsHash: POLICY_SEMANTICS_HASH
1878
+ };
1879
+ }
1880
+ handleAccessControl(socket, message) {
1881
+ if (typeof message.requestId !== "string" || message.requestId.length > MAX_MESSAGE_ID_LENGTH || typeof message.adminToken !== "string" || !this.accessRegistry.authenticateAdmin(message.adminToken)) {
1882
+ this.sendError(socket, "ACCESS_DENIED", "Remote access control credential or request was rejected");
1883
+ socket.end();
1884
+ return;
1885
+ }
1886
+ if (message.action === "inspect_tree") {
1887
+ if (typeof message.principalId !== "string" || !isSessionId(message.principalId)) {
1888
+ this.sendError(socket, "INVALID_REQUEST", "Invalid remote principal ID");
1889
+ socket.end();
1890
+ return;
1891
+ }
1892
+ const principals = this.accessRegistry.inspectSubtree(message.principalId).map((principal) => this.principalSummary(principal));
1893
+ this.audit.record({
1894
+ event: "tree_inspected",
1895
+ outcome: "allowed",
1896
+ actorId: "local-admin",
1897
+ targetId: message.principalId,
1898
+ visibleCount: principals.length
1899
+ });
1900
+ writeMessage(socket, { type: "access_control_result", requestId: message.requestId, action: "inspect_tree", principals });
1901
+ socket.end();
1902
+ return;
1903
+ }
1904
+ if (message.action === "adopt_subtree") {
1905
+ if (typeof message.principalId !== "string" || !isSessionId(message.principalId) || typeof message.newParentSessionId !== "string" || !isSessionId(message.newParentSessionId)) {
1906
+ this.sendError(socket, "INVALID_REQUEST", "Invalid adoption request");
1907
+ socket.end();
1908
+ return;
1909
+ }
1910
+ const localParent = this.sessions.get(message.newParentSessionId);
1911
+ const remoteParent = this.accessRegistry.snapshot().principals[message.newParentSessionId];
1912
+ if ((!localParent || localParent.info.origin === "remote") && (!remoteParent || remoteParent.state !== "active")) {
1913
+ this.sendError(socket, "ACCESS_DENIED", "Adoption parent must be an active local or remote principal");
1914
+ socket.end();
1915
+ return;
1916
+ }
1917
+ const newRootSessionId = localParent?.info.origin === "local" ? localParent.info.id : remoteParent.rootSessionId;
1918
+ const priorSessions = Array.from(this.sessions.values(), (session) => session.info);
1919
+ let changed;
1920
+ try {
1921
+ changed = this.accessRegistry.adoptSubtree(message.principalId, message.newParentSessionId, newRootSessionId);
1922
+ } catch {
1923
+ this.sendError(socket, "ACCESS_DENIED", "Adoption would violate the ownership tree");
1924
+ socket.end();
1925
+ return;
1926
+ }
1927
+ this.disconnectTransitionedPrincipals(changed, priorSessions, "principal_adopted");
1928
+ writeMessage(socket, {
1929
+ type: "access_control_result",
1930
+ requestId: message.requestId,
1931
+ action: "adopt_subtree",
1932
+ principals: changed.map((principal) => this.principalSummary(principal))
1933
+ });
1934
+ socket.end();
1935
+ return;
1936
+ }
1937
+ if (message.action === "revoke_subtree") {
1938
+ if (typeof message.principalId !== "string" || !isSessionId(message.principalId)) {
1939
+ this.sendError(socket, "INVALID_REQUEST", "Invalid remote principal ID");
1940
+ socket.end();
1941
+ return;
1942
+ }
1943
+ const priorSessions = Array.from(this.sessions.values(), (session) => session.info);
1944
+ const changed = this.accessRegistry.revoke(message.principalId);
1945
+ this.disconnectTransitionedPrincipals(changed, priorSessions);
1946
+ writeMessage(socket, {
1947
+ type: "access_control_result",
1948
+ requestId: message.requestId,
1949
+ action: "revoke_subtree",
1950
+ changedPrincipalIds: changed.map((principal) => principal.id)
1951
+ });
1952
+ socket.end();
1953
+ return;
1954
+ }
1955
+ if (message.action !== "issue_enrollment" || typeof message.enrollment !== "object" || message.enrollment === null || Array.isArray(message.enrollment)) {
1956
+ this.sendError(socket, "INVALID_REQUEST", "Unknown remote access control action");
1957
+ socket.end();
1958
+ return;
1959
+ }
1960
+ const enrollment = message.enrollment;
1961
+ if (typeof enrollment.name !== "string" || typeof enrollment.parentSessionId !== "string" || typeof enrollment.rootSessionId !== "string" || typeof enrollment.remoteHostId !== "string" || enrollment.ttlMs !== void 0 && (typeof enrollment.ttlMs !== "number" || !Number.isSafeInteger(enrollment.ttlMs)) || enrollment.expiresAt !== void 0 && (typeof enrollment.expiresAt !== "number" || !Number.isSafeInteger(enrollment.expiresAt)) || enrollment.canDelegate !== void 0 && typeof enrollment.canDelegate !== "boolean" || enrollment.maxDepth !== void 0 && (typeof enrollment.maxDepth !== "number" || !Number.isSafeInteger(enrollment.maxDepth)) || enrollment.maxChildren !== void 0 && (typeof enrollment.maxChildren !== "number" || !Number.isSafeInteger(enrollment.maxChildren))) {
1962
+ this.sendError(socket, "INVALID_REQUEST", "Invalid remote enrollment request");
1963
+ socket.end();
1964
+ return;
1965
+ }
1966
+ const parent = this.sessions.get(enrollment.parentSessionId);
1967
+ if (!parent || parent.info.origin === "remote" || enrollment.rootSessionId !== parent.info.id) {
1968
+ this.sendError(socket, "ACCESS_DENIED", "Enrollment parent must be an active local root session");
1969
+ socket.end();
1970
+ return;
1971
+ }
1972
+ const issued = this.accessRegistry.issueEnrollment({
1973
+ name: enrollment.name,
1974
+ parentSessionId: parent.info.id,
1975
+ rootSessionId: parent.info.id,
1976
+ remoteHostId: enrollment.remoteHostId,
1977
+ ...enrollment.expiresAt !== void 0 ? { expiresAt: enrollment.expiresAt } : {},
1978
+ ...enrollment.canDelegate !== void 0 ? { canDelegate: enrollment.canDelegate } : {},
1979
+ ...enrollment.maxDepth !== void 0 ? { maxDepth: enrollment.maxDepth } : {},
1980
+ ...enrollment.maxChildren !== void 0 ? { maxChildren: enrollment.maxChildren } : {}
1981
+ }, enrollment.ttlMs);
1982
+ this.audit.record({
1983
+ event: "enrollment_issued",
1984
+ outcome: "allowed",
1985
+ actorId: parent.info.id,
1986
+ targetId: enrollment.name,
1987
+ remoteHostId: enrollment.remoteHostId,
1988
+ reason: `expires:${issued.expiresAt}`
1989
+ });
1990
+ writeMessage(socket, {
1991
+ type: "access_control_result",
1992
+ requestId: message.requestId,
1993
+ action: "issue_enrollment",
1994
+ enrollmentToken: issued.enrollmentToken,
1995
+ expiresAt: issued.expiresAt
1996
+ });
1997
+ socket.end();
1998
+ }
1999
+ handleRemoteAccessControl(socket, message) {
2000
+ if (typeof message.requestId !== "string" || message.requestId.length > MAX_MESSAGE_ID_LENGTH || typeof message.access !== "object" || message.access === null || Array.isArray(message.access)) {
2001
+ this.sendError(socket, "ACCESS_DENIED", "Remote control request was rejected");
2002
+ socket.end();
2003
+ return;
2004
+ }
2005
+ const access = message.access;
2006
+ if (typeof access.sessionCredential !== "string" || typeof access.sessionId !== "string" || typeof access.generation !== "number" || !Number.isSafeInteger(access.generation)) {
2007
+ this.sendError(socket, "ACCESS_DENIED", "Remote control credential was rejected");
2008
+ socket.end();
2009
+ return;
2010
+ }
2011
+ let parent;
2012
+ try {
2013
+ parent = this.accessRegistry.authenticateSession(access.sessionId, access.generation, access.sessionCredential);
2014
+ } catch {
2015
+ this.audit.tryRecord({ event: "remote_registration_denied", outcome: "denied", reason: "INVALID_CONTROL_CREDENTIAL" });
2016
+ this.sendError(socket, "ACCESS_DENIED", "Remote control credential was rejected");
2017
+ socket.end();
2018
+ return;
2019
+ }
2020
+ const policyState = this.registryPolicyState();
2021
+ if (message.action === "inspect_tree") {
2022
+ const targetId = typeof message.principalId === "string" ? message.principalId : parent.id;
2023
+ const inspection = authorize(policyState, parent.id, "inspect_tree", targetId, { actorGeneration: parent.generation });
2024
+ if (!inspection.allowed) {
2025
+ this.sendError(socket, "ACCESS_DENIED", "Remote tree inspection policy denied the request");
2026
+ socket.end();
2027
+ return;
2028
+ }
2029
+ const principals = this.accessRegistry.inspectSubtree(targetId).filter((candidate) => authorize(policyState, parent.id, "inspect_tree", candidate.id, { actorGeneration: parent.generation }).allowed).map((candidate) => this.principalSummary(candidate));
2030
+ this.audit.record({
2031
+ event: "tree_inspected",
2032
+ outcome: "allowed",
2033
+ actorId: parent.id,
2034
+ targetId,
2035
+ remoteHostId: parent.remoteHostId,
2036
+ generation: parent.generation,
2037
+ visibleCount: principals.length
2038
+ });
2039
+ writeMessage(socket, { type: "access_control_result", requestId: message.requestId, action: "inspect_tree", principals });
2040
+ socket.end();
2041
+ return;
2042
+ }
2043
+ if (message.action !== "issue_child_enrollment" || typeof message.enrollment !== "object" || message.enrollment === null || Array.isArray(message.enrollment)) {
2044
+ this.sendError(socket, "ACCESS_DENIED", "Remote control action was rejected");
2045
+ socket.end();
2046
+ return;
2047
+ }
2048
+ const enrollment = message.enrollment;
2049
+ if (typeof enrollment.name !== "string" || enrollment.ttlMs !== void 0 && (typeof enrollment.ttlMs !== "number" || !Number.isSafeInteger(enrollment.ttlMs)) || enrollment.expiresAt !== void 0 && (typeof enrollment.expiresAt !== "number" || !Number.isSafeInteger(enrollment.expiresAt)) || enrollment.canDelegate !== void 0 && typeof enrollment.canDelegate !== "boolean" || enrollment.maxDepth !== void 0 && (typeof enrollment.maxDepth !== "number" || !Number.isSafeInteger(enrollment.maxDepth)) || enrollment.maxChildren !== void 0 && (typeof enrollment.maxChildren !== "number" || !Number.isSafeInteger(enrollment.maxChildren))) {
2050
+ this.sendError(socket, "ACCESS_DENIED", "Remote delegation request was rejected");
2051
+ socket.end();
2052
+ return;
2053
+ }
2054
+ const delegation = authorize(policyState, parent.id, "delegate_child", parent.id, {
2055
+ actorGeneration: parent.generation,
2056
+ targetGeneration: parent.generation
2057
+ });
2058
+ if (!delegation.allowed) {
2059
+ this.sendError(socket, "ACCESS_DENIED", "Remote delegation policy denied the request");
2060
+ socket.end();
2061
+ return;
2062
+ }
2063
+ let issued;
2064
+ try {
2065
+ issued = this.accessRegistry.issueChildEnrollment(parent.id, parent.generation, {
2066
+ name: enrollment.name,
2067
+ ...enrollment.expiresAt !== void 0 ? { expiresAt: enrollment.expiresAt } : {},
2068
+ ...enrollment.canDelegate !== void 0 ? { canDelegate: enrollment.canDelegate } : {},
2069
+ ...enrollment.maxDepth !== void 0 ? { maxDepth: enrollment.maxDepth } : {},
2070
+ ...enrollment.maxChildren !== void 0 ? { maxChildren: enrollment.maxChildren } : {}
2071
+ }, enrollment.ttlMs);
2072
+ } catch {
2073
+ this.sendError(socket, "ACCESS_DENIED", "Remote delegation limits denied the request");
2074
+ socket.end();
2075
+ return;
2076
+ }
2077
+ this.audit.record({
2078
+ event: "enrollment_issued",
2079
+ outcome: "allowed",
2080
+ actorId: parent.id,
2081
+ targetId: enrollment.name,
2082
+ remoteHostId: parent.remoteHostId,
2083
+ generation: parent.generation,
2084
+ reason: `delegated-expires:${issued.expiresAt}`
2085
+ });
2086
+ writeMessage(socket, {
2087
+ type: "access_control_result",
2088
+ requestId: message.requestId,
2089
+ action: "issue_child_enrollment",
2090
+ enrollmentToken: issued.enrollmentToken,
2091
+ expiresAt: issued.expiresAt,
2092
+ parentSessionId: parent.id
2093
+ });
2094
+ socket.end();
2095
+ }
2096
+ registryPolicyState() {
2097
+ const records = this.accessRegistry.snapshot().principals;
2098
+ const principals = {};
2099
+ for (const record of Object.values(records)) {
2100
+ principals[record.id] = {
2101
+ id: record.id,
2102
+ kind: "remote",
2103
+ state: record.state,
2104
+ generation: record.generation,
2105
+ policy: "remote-tree",
2106
+ parentSessionId: record.parentSessionId,
2107
+ rootSessionId: record.rootSessionId
2108
+ };
2109
+ if (!principals[record.rootSessionId]) {
2110
+ principals[record.rootSessionId] = {
2111
+ id: record.rootSessionId,
2112
+ kind: "local",
2113
+ state: "active",
2114
+ generation: 1,
2115
+ policy: "local-public",
2116
+ rootSessionId: record.rootSessionId
2117
+ };
2118
+ }
2119
+ }
2120
+ return { principals };
2121
+ }
2122
+ principalSummary(principal) {
2123
+ return { ...principal, connected: this.sessions.has(principal.id) };
2124
+ }
2125
+ reconcileExpiredPrincipals() {
2126
+ const priorSessions = Array.from(this.sessions.values(), (session) => session.info);
2127
+ const changed = this.accessRegistry.expirePrincipals();
2128
+ if (changed.length > 0) this.disconnectTransitionedPrincipals(changed, priorSessions, "principal_expired");
2129
+ }
2130
+ disconnectTransitionedPrincipals(changed, priorSessions, auditEvent = "principal_revoked") {
2131
+ const changedIds = new Set(changed.map((principal) => principal.id));
2132
+ for (const principal of changed) {
2133
+ const live = this.sessions.get(principal.id);
2134
+ if (!live) {
2135
+ this.audit.record({
2136
+ event: auditEvent,
2137
+ outcome: "allowed",
2138
+ actorId: principal.id,
2139
+ remoteHostId: principal.remoteHostId,
2140
+ generation: principal.generation,
2141
+ reason: "OFFLINE"
2142
+ });
2143
+ continue;
2144
+ }
2145
+ const subject = priorSessions.find((session) => session.id === principal.id) ?? live.info;
2146
+ for (const [recipientId, recipient] of this.sessions) {
2147
+ if (recipientId !== principal.id && !changedIds.has(recipientId) && sameIntercomScope(recipient.scopeId, live.scopeId) && authorizeSessionAction(priorSessions, recipientId, "discover", principal.id).allowed) {
2148
+ writeMessage(recipient.socket, { type: "session_left", sessionId: principal.id });
2149
+ }
2150
+ }
2151
+ this.clearPendingDeliveriesForSession(principal.id, live.socket);
2152
+ this.clearAskEdgesForSession(principal.id, "authorization_revoked");
2153
+ this.sessions.delete(principal.id);
2154
+ for (const [key, recent] of this.recentDeliveries) {
2155
+ if (recent.from === principal.id || recent.to === principal.id) this.recentDeliveries.delete(key);
2156
+ }
2157
+ this.audit.record({
2158
+ event: auditEvent,
2159
+ outcome: "allowed",
2160
+ actorId: principal.id,
2161
+ targetId: subject.parentSessionId,
2162
+ remoteHostId: principal.remoteHostId,
2163
+ generation: principal.generation,
2164
+ reason: "DISCONNECTED"
2165
+ });
2166
+ live.socket.destroy();
2167
+ }
2168
+ if (changed.length > 0) this.scheduleShutdownCheck();
2169
+ }
2170
+ isCurrentPrincipal(sessionId) {
2171
+ const session = this.sessions.get(sessionId);
2172
+ if (!session) return false;
2173
+ if (session.info.origin !== "remote") return true;
2174
+ try {
2175
+ this.accessRegistry.validatePrincipal(sessionId, session.info.generation ?? 0);
2176
+ return true;
2177
+ } catch {
2178
+ return false;
2179
+ }
2180
+ }
2181
+ authorizationEpoch(session) {
2182
+ return Number(session.boss?.binding.bindingEpoch ?? session.generation ?? 1);
2183
+ }
2184
+ isAuthorized(actorId, action, targetId, context = {}) {
2185
+ if (!this.isCurrentPrincipal(actorId) || !this.isCurrentPrincipal(targetId)) return false;
2186
+ return authorizeSessionAction(
2187
+ Array.from(this.sessions.values(), (session) => session.info),
2188
+ actorId,
2189
+ action,
2190
+ targetId,
2191
+ context
2192
+ ).allowed;
2193
+ }
2194
+ broadcastLifecycle(message, subject, exclude) {
2195
+ for (const [id, session] of this.sessions) {
2196
+ if (id !== exclude && sameIntercomScope(session.scopeId, subject.scopeId) && this.isAuthorized(id, "discover", subject.info.id)) {
2197
+ writeMessage(session.socket, message);
2198
+ }
2199
+ }
2200
+ }
2201
+ askKey(fromSessionId, messageId) {
2202
+ return `${fromSessionId}\0${messageId}`;
2203
+ }
2204
+ deliveryKey(fromSessionId, messageId) {
2205
+ return `${fromSessionId}\0${messageId}`;
2206
+ }
2207
+ addAskEdge(messageId, from, to) {
2208
+ const key = this.askKey(from, messageId);
2209
+ const previous = this.askEdges.get(key);
2210
+ if (previous) {
2211
+ clearTimeout(previous.timeout);
2212
+ }
2213
+ const createdAt = Date.now();
2214
+ const expiresAt = createdAt + this.askTimeoutMs;
2215
+ this.askEdges.set(key, {
2216
+ messageId,
2217
+ from,
2218
+ to,
2219
+ createdAt,
2220
+ expiresAt,
2221
+ state: "blocking",
2222
+ timeout: this.scheduleAskExpiry(key, expiresAt)
2223
+ });
2224
+ this.persistAskEdges();
2225
+ }
2226
+ removeAskEdge(key, reason, notifyRecipient = false) {
2227
+ const edge = this.askEdges.get(key);
2228
+ if (!edge) {
2229
+ return;
2230
+ }
2231
+ clearTimeout(edge.timeout);
2232
+ this.askEdges.delete(key);
2233
+ this.persistAskEdges();
2234
+ if (reason && notifyRecipient) {
2235
+ this.notifyAskCancelled(edge.to, edge.messageId, edge.from, reason);
2236
+ }
2237
+ }
2238
+ notifyAskDeferred(edge) {
2239
+ const recipient = this.sessions.get(edge.to);
2240
+ if (recipient) {
2241
+ writeMessage(recipient.socket, {
2242
+ type: "ask_deferred",
2243
+ messageId: edge.messageId,
2244
+ fromSessionId: edge.from
2245
+ });
2246
+ }
2247
+ }
2248
+ notifyAskCancelled(sessionId, messageId, fromSessionId, reason) {
2249
+ const session = this.sessions.get(sessionId);
2250
+ if (session) {
2251
+ writeMessage(session.socket, { type: "ask_cancelled", messageId, fromSessionId, reason });
2252
+ }
2253
+ }
2254
+ clearAskEdgesForSession(sessionId, reason) {
2255
+ let changed = false;
2256
+ for (const [key, edge] of this.askEdges) {
2257
+ if (edge.from === sessionId || edge.to === sessionId) {
2258
+ clearTimeout(edge.timeout);
2259
+ this.askEdges.delete(key);
2260
+ changed = true;
2261
+ if (edge.from === sessionId) {
2262
+ this.notifyAskCancelled(edge.to, edge.messageId, edge.from, reason);
2263
+ } else {
2264
+ this.notifyAskCancelled(edge.from, edge.messageId, edge.to, reason);
2265
+ }
2266
+ }
2267
+ }
2268
+ if (changed) {
2269
+ this.persistAskEdges();
2270
+ }
2271
+ }
2272
+ deferAskEdgesForSession(sessionId) {
2273
+ let changed = false;
2274
+ for (const edge of this.askEdges.values()) {
2275
+ if ((edge.from === sessionId || edge.to === sessionId) && edge.state === "blocking") {
2276
+ edge.state = "deferred";
2277
+ changed = true;
2278
+ if (edge.from === sessionId) {
2279
+ this.notifyAskDeferred(edge);
2280
+ }
2281
+ }
2282
+ }
2283
+ if (changed) {
2284
+ this.persistAskEdges();
2285
+ }
2286
+ }
2287
+ scheduleAskExpiry(key, expiresAt) {
2288
+ const delay = Math.max(1, Math.min(expiresAt - Date.now(), 2147483647));
2289
+ const timeout = setTimeout(() => {
2290
+ if (expiresAt > Date.now()) {
2291
+ const edge = this.askEdges.get(key);
2292
+ if (edge) {
2293
+ clearTimeout(edge.timeout);
2294
+ edge.timeout = this.scheduleAskExpiry(key, expiresAt);
2295
+ }
2296
+ return;
2297
+ }
2298
+ this.removeAskEdge(key, "expired", true);
2299
+ }, delay);
2300
+ timeout.unref?.();
2301
+ return timeout;
2302
+ }
2303
+ loadAskEdges() {
2304
+ if (!existsSync2(ASK_STATE_PATH)) {
2305
+ return;
2306
+ }
2307
+ try {
2308
+ const parsed = JSON.parse(readFileSync4(ASK_STATE_PATH, "utf-8"));
2309
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2310
+ throw new Error("expected an object");
2311
+ }
2312
+ const state = parsed;
2313
+ if (state.version !== 1) {
2314
+ throw new Error("unsupported state version");
2315
+ }
2316
+ const edges = state.edges;
2317
+ if (!Array.isArray(edges)) {
2318
+ throw new Error("expected an edges array");
2319
+ }
2320
+ const now = Date.now();
2321
+ for (const candidate of edges) {
2322
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) {
2323
+ continue;
2324
+ }
2325
+ const edge = candidate;
2326
+ if (typeof edge.messageId !== "string" || edge.messageId.length === 0 || edge.messageId.length > MAX_MESSAGE_ID_LENGTH || !isSessionId(edge.from) || !isSessionId(edge.to) || typeof edge.createdAt !== "number" || !Number.isFinite(edge.createdAt) || typeof edge.expiresAt !== "number" || !Number.isFinite(edge.expiresAt) || edge.expiresAt <= now || edge.state !== "blocking" && edge.state !== "deferred") {
2327
+ continue;
2328
+ }
2329
+ const key = this.askKey(edge.from, edge.messageId);
2330
+ this.askEdges.set(key, {
2331
+ messageId: edge.messageId,
2332
+ from: edge.from,
2333
+ to: edge.to,
2334
+ createdAt: edge.createdAt,
2335
+ expiresAt: edge.expiresAt,
2336
+ state: "deferred",
2337
+ timeout: this.scheduleAskExpiry(key, edge.expiresAt)
2338
+ });
2339
+ }
2340
+ this.persistAskEdges();
2341
+ } catch (error) {
2342
+ console.error(`Failed to load persisted ask state at ${ASK_STATE_PATH}:`, error);
2343
+ for (const edge of this.askEdges.values()) {
2344
+ clearTimeout(edge.timeout);
2345
+ }
2346
+ this.askEdges.clear();
2347
+ try {
2348
+ const corruptPath = `${ASK_STATE_PATH}.corrupt-${Date.now()}`;
2349
+ renameSync2(ASK_STATE_PATH, corruptPath);
2350
+ restrictIntercomRuntimeFile(corruptPath);
2351
+ } catch {
2352
+ }
2353
+ }
2354
+ }
2355
+ persistAskEdges() {
2356
+ const edges = Array.from(this.askEdges.values(), (edge) => ({
2357
+ messageId: edge.messageId,
2358
+ from: edge.from,
2359
+ to: edge.to,
2360
+ createdAt: edge.createdAt,
2361
+ expiresAt: edge.expiresAt,
2362
+ state: edge.state
2363
+ }));
2364
+ writeDurableJson(ASK_STATE_PATH, { version: 1, edges });
2365
+ }
2366
+ countAskEdgesFrom(sessionId) {
2367
+ let count = 0;
2368
+ for (const edge of this.askEdges.values()) {
2369
+ if (edge.from === sessionId) {
2370
+ count += 1;
2371
+ }
2372
+ }
2373
+ return count;
2374
+ }
2375
+ countPendingDeliveriesFrom(sessionId) {
2376
+ let count = 0;
2377
+ for (const delivery of this.pendingDeliveries.values()) {
2378
+ if (delivery.from === sessionId) {
2379
+ count += 1;
2380
+ }
2381
+ }
2382
+ return count;
2383
+ }
2384
+ acknowledgePendingDelivery(deliveryId, sessionId, socket) {
2385
+ const pending = this.pendingDeliveries.get(deliveryId);
2386
+ if (!pending || pending.to !== sessionId || pending.recipientSocket !== socket) {
2387
+ return;
2388
+ }
2389
+ const sender = this.sessions.get(pending.from);
2390
+ const recipient = this.sessions.get(pending.to);
2391
+ if (!sender || !recipient || this.authorizationEpoch(sender.info) !== pending.fromGeneration || this.authorizationEpoch(recipient.info) !== pending.toGeneration || !this.isAuthorized(pending.from, pending.action, pending.to, pending.controlKind === void 0 ? {} : { controlKind: pending.controlKind, correlated: false })) {
2392
+ this.failPendingDelivery(deliveryId, "SESSION_NOT_FOUND", "Delivery authorization changed before acknowledgement");
2393
+ return;
2394
+ }
2395
+ clearTimeout(pending.timeout);
2396
+ this.pendingDeliveries.delete(deliveryId);
2397
+ this.pendingDeliveryKeys.delete(pending.key);
2398
+ if (pending.message.replyTo) {
2399
+ this.removeAskEdge(this.askKey(pending.to, pending.message.replyTo));
2400
+ }
2401
+ const response = { type: "delivered", messageId: pending.message.id, deliveryId };
2402
+ this.recentDeliveries.set(pending.key, {
2403
+ fingerprint: pending.fingerprint,
2404
+ from: pending.from,
2405
+ to: pending.to,
2406
+ action: pending.action,
2407
+ ...pending.controlKind === void 0 ? {} : { controlKind: pending.controlKind },
2408
+ fromGeneration: pending.fromGeneration,
2409
+ toGeneration: pending.toGeneration,
2410
+ retryable: false,
2411
+ response,
2412
+ expiresAt: Date.now() + RECENT_DELIVERY_TTL_MS
2413
+ });
2414
+ if (sender.socket === pending.senderSocket) {
2415
+ writeMessage(sender.socket, response);
2416
+ }
2417
+ }
2418
+ failPendingDelivery(deliveryId, code, reason) {
2419
+ const pending = this.pendingDeliveries.get(deliveryId);
2420
+ if (!pending) {
2421
+ return;
2422
+ }
2423
+ clearTimeout(pending.timeout);
2424
+ this.pendingDeliveries.delete(deliveryId);
2425
+ this.pendingDeliveryKeys.delete(pending.key);
2426
+ if (pending.message.expectsReply) {
2427
+ this.removeAskEdge(this.askKey(pending.from, pending.message.id), "delivery_failed", true);
2428
+ }
2429
+ const response = {
2430
+ type: "delivery_failed",
2431
+ messageId: pending.message.id,
2432
+ accepted: true,
2433
+ code,
2434
+ reason
2435
+ };
2436
+ this.recentDeliveries.set(pending.key, {
2437
+ fingerprint: pending.fingerprint,
2438
+ from: pending.from,
2439
+ to: pending.to,
2440
+ action: pending.action,
2441
+ ...pending.controlKind === void 0 ? {} : { controlKind: pending.controlKind },
2442
+ fromGeneration: pending.fromGeneration,
2443
+ toGeneration: pending.toGeneration,
2444
+ retryable: true,
2445
+ response,
2446
+ expiresAt: Date.now() + RECENT_DELIVERY_TTL_MS
2447
+ });
2448
+ const sender = this.sessions.get(pending.from);
2449
+ if (sender?.socket === pending.senderSocket) {
2450
+ writeMessage(sender.socket, response);
2451
+ }
2452
+ }
2453
+ clearPendingDeliveriesForSession(sessionId, socket) {
2454
+ for (const delivery of Array.from(this.pendingDeliveries.values())) {
2455
+ if (delivery.to === sessionId && delivery.recipientSocket === socket) {
2456
+ this.failPendingDelivery(delivery.id, "RECIPIENT_DISCONNECTED", "Recipient disconnected before acknowledging the message");
2457
+ } else if (delivery.from === sessionId && delivery.senderSocket === socket) {
2458
+ this.failPendingDelivery(delivery.id, "SENDER_DISCONNECTED", "Sender disconnected before delivery was acknowledged");
2459
+ }
2460
+ }
2461
+ }
2462
+ pruneRecentDeliveries(now = Date.now()) {
2463
+ for (const [key, delivery] of this.recentDeliveries) {
2464
+ if (delivery.expiresAt <= now) {
2465
+ this.recentDeliveries.delete(key);
2466
+ }
2467
+ }
2468
+ }
2469
+ findSessions(actorId, nameOrId) {
2470
+ const byId = this.sessions.get(nameOrId);
2471
+ if (byId) {
2472
+ return [byId];
2473
+ }
2474
+ const actor = this.sessions.get(actorId);
2475
+ if (!actor) return [];
2476
+ const sameScope = Array.from(this.sessions.values()).filter(
2477
+ (session) => sameIntercomScope(actor.scopeId, session.scopeId)
2478
+ );
2479
+ const lowerName = nameOrId.toLowerCase();
2480
+ const byName = sameScope.filter((session) => session.info.name?.toLowerCase() === lowerName);
2481
+ if (byName.length > 0) {
2482
+ return byName;
2483
+ }
2484
+ return sameScope.filter((session) => session.info.id.startsWith(nameOrId));
2485
+ }
2486
+ shutdown() {
2487
+ console.log("Broker shutting down");
2488
+ if (this.expiryTimer) {
2489
+ clearInterval(this.expiryTimer);
2490
+ this.expiryTimer = null;
2491
+ }
2492
+ for (const session of this.sessions.values()) {
2493
+ session.socket.end();
2494
+ }
2495
+ this.sessions.clear();
2496
+ for (const delivery of this.pendingDeliveries.values()) {
2497
+ clearTimeout(delivery.timeout);
2498
+ }
2499
+ this.pendingDeliveries.clear();
2500
+ this.pendingDeliveryKeys.clear();
2501
+ for (const edge of this.askEdges.values()) {
2502
+ clearTimeout(edge.timeout);
2503
+ }
2504
+ this.askEdges.clear();
2505
+ const ownsBroker = hasBrokerOwnership(OWNER_PATH);
2506
+ if (ownsBroker && typeof LISTEN_TARGET === "string" && process.platform !== "win32") {
2507
+ for (const socketPath of [LISTEN_TARGET, REMOTE_LISTEN_TARGET]) {
2508
+ try {
2509
+ unlinkSync2(socketPath);
2510
+ } catch {
2511
+ }
2512
+ }
2513
+ }
2514
+ if (ownsBroker) {
2515
+ try {
2516
+ unlinkSync2(PORT_PATH);
2517
+ } catch {
2518
+ }
2519
+ try {
2520
+ unlinkSync2(PID_PATH);
2521
+ } catch {
2522
+ }
2523
+ releaseBrokerOwnership(OWNER_PATH);
2524
+ }
2525
+ this.server.close();
2526
+ this.remoteServer?.close();
2527
+ process.exit(0);
2528
+ }
2529
+ };
2530
+ new IntercomBroker().start();