@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,2730 @@
1
+ #!/usr/bin/env node
2
+
3
+ // claude/server.ts
4
+ import readline from "node:readline";
5
+ import { stdin, stdout } from "node:process";
6
+
7
+ // claude/runtime.ts
8
+ import { randomUUID as randomUUID4, createHash as createHash2 } from "crypto";
9
+ import { spawnSync } from "child_process";
10
+ import { basename } from "path";
11
+ import { cwd as processCwd } from "process";
12
+
13
+ // broker/client.ts
14
+ import { EventEmitter } from "events";
15
+ import net from "net";
16
+ import { randomUUID as randomUUID2 } from "crypto";
17
+ import { POLICY_SEMANTICS_HASH, POLICY_SEMANTICS_VERSION } from "@ctliz/agent-intercom-core";
18
+
19
+ // broker/framing.ts
20
+ var MAX_FRAME_BYTES = 1024 * 1024;
21
+ function writeMessage(socket, msg) {
22
+ const json = JSON.stringify(msg);
23
+ const payload = Buffer.from(json, "utf-8");
24
+ const header = Buffer.alloc(4);
25
+ header.writeUInt32BE(payload.length, 0);
26
+ socket.write(Buffer.concat([header, payload]));
27
+ }
28
+ function createMessageReader(onMessage, onError, maxFrameBytes = MAX_FRAME_BYTES) {
29
+ let buffer = Buffer.alloc(0);
30
+ function reportMessage(payload) {
31
+ let msg;
32
+ try {
33
+ msg = JSON.parse(payload.toString("utf-8"));
34
+ } catch (error2) {
35
+ const message = error2 instanceof Error ? error2.message : String(error2);
36
+ onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error2 }));
37
+ return false;
38
+ }
39
+ try {
40
+ onMessage(msg);
41
+ return true;
42
+ } catch (error2) {
43
+ const message = error2 instanceof Error ? error2.message : String(error2);
44
+ onError(new Error(`Failed to handle intercom message: ${message}`, { cause: error2 }));
45
+ return false;
46
+ }
47
+ }
48
+ return (data) => {
49
+ let remaining = data;
50
+ while (remaining.length > 0) {
51
+ if (buffer.length < 4) {
52
+ const headerBytes = Math.min(4 - buffer.length, remaining.length);
53
+ buffer = Buffer.concat([buffer, remaining.subarray(0, headerBytes)]);
54
+ remaining = remaining.subarray(headerBytes);
55
+ if (buffer.length < 4) {
56
+ return;
57
+ }
58
+ }
59
+ const length = buffer.readUInt32BE(0);
60
+ if (length > maxFrameBytes) {
61
+ buffer = Buffer.alloc(0);
62
+ onError(new Error(`Intercom frame length ${length} exceeds maximum ${maxFrameBytes} bytes`));
63
+ return;
64
+ }
65
+ const missingPayloadBytes = length - Math.max(0, buffer.length - 4);
66
+ const payloadBytes = Math.min(missingPayloadBytes, remaining.length);
67
+ if (payloadBytes > 0) {
68
+ buffer = Buffer.concat([buffer, remaining.subarray(0, payloadBytes)]);
69
+ remaining = remaining.subarray(payloadBytes);
70
+ }
71
+ if (buffer.length < 4 + length) {
72
+ return;
73
+ }
74
+ const payload = buffer.subarray(4, 4 + length);
75
+ buffer = Buffer.alloc(0);
76
+ if (!reportMessage(payload)) {
77
+ return;
78
+ }
79
+ }
80
+ };
81
+ }
82
+
83
+ // outbound-outbox.ts
84
+ import { createHash } from "crypto";
85
+ import { chmodSync as chmodSync2, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2 } from "fs";
86
+ import { join as join2 } from "path";
87
+
88
+ // broker/paths.ts
89
+ import { chmodSync, mkdirSync, readFileSync } from "fs";
90
+ import { isAbsolute, join, resolve } from "path";
91
+ import { homedir } from "os";
92
+ var INTERCOM_DIR_MODE = 448;
93
+ var INTERCOM_RUNTIME_FILE_MODE = 384;
94
+ var INTERCOM_TCP_HOST = "127.0.0.1";
95
+ var INTERCOM_PROTOCOL_NAME = "pi-intercom";
96
+ var INTERCOM_PROTOCOL_VERSION = 4;
97
+ function sanitizePipeSegment(value) {
98
+ return value.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "default";
99
+ }
100
+ function getAgentDirPath(env = process.env, homeDir = homedir(), cwd = process.cwd()) {
101
+ const configured = env.PI_CODING_AGENT_DIR?.trim();
102
+ if (!configured) {
103
+ return join(homeDir, ".pi/agent");
104
+ }
105
+ return isAbsolute(configured) ? configured : resolve(cwd, configured);
106
+ }
107
+ function getIntercomDirPath(agentDir = getAgentDirPath()) {
108
+ return join(agentDir, "intercom");
109
+ }
110
+ function shouldUseWindowsTcpTransport(platform = process.platform, env = process.env) {
111
+ if (platform !== "win32") {
112
+ return false;
113
+ }
114
+ const transport = env.PI_INTERCOM_TRANSPORT?.trim().toLowerCase();
115
+ if (transport === "tcp") {
116
+ return true;
117
+ }
118
+ const legacyOptIn = env.PI_INTERCOM_TCP?.trim().toLowerCase();
119
+ return legacyOptIn === "1" || legacyOptIn === "true";
120
+ }
121
+ function getBrokerPortFilePath(intercomDir = getIntercomDirPath()) {
122
+ return join(intercomDir, "broker.port.json");
123
+ }
124
+ function getBrokerSocketPath(platform = process.platform, agentDir = getAgentDirPath()) {
125
+ if (platform === "win32") {
126
+ return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(agentDir)}`;
127
+ }
128
+ return join(getIntercomDirPath(agentDir), "broker.sock");
129
+ }
130
+ function getBrokerConnectTarget(platform = process.platform, env = process.env, intercomDir = getIntercomDirPath(getAgentDirPath(env))) {
131
+ if (shouldUseWindowsTcpTransport(platform, env)) {
132
+ const endpointFile = getBrokerPortFilePath(intercomDir);
133
+ const raw = readFileSync(endpointFile, "utf-8");
134
+ const parsed = JSON.parse(raw);
135
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
136
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}: expected a JSON object`);
137
+ }
138
+ const endpoint = parsed;
139
+ if (endpoint.transport !== "tcp" || endpoint.host !== INTERCOM_TCP_HOST || typeof endpoint.port !== "number" || !Number.isSafeInteger(endpoint.port) || endpoint.port <= 0 || endpoint.port > 65535 || typeof endpoint.stateId !== "string" || endpoint.stateId.length === 0) {
140
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}`);
141
+ }
142
+ return { transport: "tcp", host: endpoint.host, port: endpoint.port, stateId: endpoint.stateId };
143
+ }
144
+ return getBrokerSocketPath(platform, getAgentDirPath(env));
145
+ }
146
+ function ensureIntercomRuntimeDir(intercomDir = getIntercomDirPath(), platform = process.platform) {
147
+ mkdirSync(intercomDir, { recursive: true, mode: INTERCOM_DIR_MODE });
148
+ if (platform !== "win32") {
149
+ chmodSync(intercomDir, INTERCOM_DIR_MODE);
150
+ }
151
+ }
152
+ function restrictIntercomRuntimeFile(filePath, platform = process.platform) {
153
+ if (platform !== "win32") {
154
+ chmodSync(filePath, INTERCOM_RUNTIME_FILE_MODE);
155
+ }
156
+ }
157
+
158
+ // durable-json.ts
159
+ import { randomUUID } from "crypto";
160
+ import { closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "fs";
161
+ import { dirname } from "path";
162
+ function writeDurableJson(filePath, value) {
163
+ const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
164
+ writeFileSync(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE });
165
+ const fileDescriptor = openSync(temporaryPath, "r");
166
+ try {
167
+ fsyncSync(fileDescriptor);
168
+ } finally {
169
+ closeSync(fileDescriptor);
170
+ }
171
+ renameSync(temporaryPath, filePath);
172
+ restrictIntercomRuntimeFile(filePath);
173
+ if (process.platform !== "win32") {
174
+ const directoryDescriptor = openSync(dirname(filePath), "r");
175
+ try {
176
+ fsyncSync(directoryDescriptor);
177
+ } finally {
178
+ closeSync(directoryDescriptor);
179
+ }
180
+ }
181
+ }
182
+
183
+ // outbound-outbox.ts
184
+ var OUTBOX_STATE_VERSION = 1;
185
+ var MAX_OUTBOX_MESSAGES = 256;
186
+ function fingerprint(entry) {
187
+ return JSON.stringify({
188
+ to: entry.to,
189
+ replyTo: entry.message.replyTo,
190
+ expectsReply: entry.message.expectsReply,
191
+ content: entry.message.content
192
+ });
193
+ }
194
+ function isStoredOutboundMessage(value) {
195
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
196
+ const entry = value;
197
+ if (typeof entry.to !== "string" || typeof entry.queuedAt !== "number") return false;
198
+ if (typeof entry.message !== "object" || entry.message === null || Array.isArray(entry.message)) return false;
199
+ const message = entry.message;
200
+ return typeof message.id === "string" && typeof message.timestamp === "number" && typeof message.content === "object" && message.content !== null && typeof message.content.text === "string";
201
+ }
202
+ function fileName(sessionId) {
203
+ return `${createHash("sha256").update(sessionId).digest("hex")}.json`;
204
+ }
205
+ var PersistentOutboundOutbox = class {
206
+ directory;
207
+ filePath;
208
+ state;
209
+ constructor(sessionId, intercomDir = getIntercomDirPath()) {
210
+ ensureIntercomRuntimeDir(intercomDir);
211
+ this.directory = join2(intercomDir, "outbox");
212
+ mkdirSync2(this.directory, { recursive: true, mode: INTERCOM_DIR_MODE });
213
+ if (process.platform !== "win32") chmodSync2(this.directory, INTERCOM_DIR_MODE);
214
+ this.filePath = join2(this.directory, fileName(sessionId));
215
+ this.state = this.load();
216
+ }
217
+ list() {
218
+ return this.state.entries.map((entry) => ({ ...entry, message: { ...entry.message, content: { ...entry.message.content } } }));
219
+ }
220
+ enqueue(to, message) {
221
+ const existing = this.state.entries.find((entry) => entry.message.id === message.id);
222
+ if (existing) {
223
+ if (fingerprint(existing) !== fingerprint({ to, message })) {
224
+ throw new Error(`Message ID ${message.id} is already queued with a different payload`);
225
+ }
226
+ return "existing";
227
+ }
228
+ if (this.state.entries.length >= MAX_OUTBOX_MESSAGES) {
229
+ throw new Error(`Durable outbox is full (${MAX_OUTBOX_MESSAGES} messages)`);
230
+ }
231
+ this.state.entries.push({ to, message, queuedAt: Date.now() });
232
+ this.persist();
233
+ return "added";
234
+ }
235
+ remove(messageId) {
236
+ const remaining = this.state.entries.filter((entry) => entry.message.id !== messageId);
237
+ if (remaining.length === this.state.entries.length) return;
238
+ this.state.entries = remaining;
239
+ this.persist();
240
+ }
241
+ clear() {
242
+ if (this.state.entries.length === 0) return;
243
+ this.state.entries = [];
244
+ this.persist();
245
+ }
246
+ load() {
247
+ if (!existsSync(this.filePath)) return { version: OUTBOX_STATE_VERSION, entries: [] };
248
+ try {
249
+ const parsed = JSON.parse(readFileSync2(this.filePath, "utf-8"));
250
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("expected object");
251
+ const state = parsed;
252
+ if (state.version !== OUTBOX_STATE_VERSION || !Array.isArray(state.entries) || !state.entries.every(isStoredOutboundMessage)) {
253
+ throw new Error("invalid outbox state");
254
+ }
255
+ return { version: OUTBOX_STATE_VERSION, entries: state.entries };
256
+ } catch {
257
+ const corruptPath = `${this.filePath}.corrupt-${Date.now()}`;
258
+ renameSync2(this.filePath, corruptPath);
259
+ restrictIntercomRuntimeFile(corruptPath);
260
+ return { version: OUTBOX_STATE_VERSION, entries: [] };
261
+ }
262
+ }
263
+ persist() {
264
+ writeDurableJson(this.filePath, this.state);
265
+ }
266
+ };
267
+
268
+ // broker/access-credential.ts
269
+ import { readFileSync as readFileSync3 } from "fs";
270
+ var ACCESS_CREDENTIAL_ENV = "AGENT_INTERCOM_ACCESS_CREDENTIAL_PATH";
271
+ var ACCESS_CREDENTIAL_VERSION = 1;
272
+ function nonEmptyString(value) {
273
+ return typeof value === "string" && value.length > 0 && !value.includes("\0");
274
+ }
275
+ function loadRemoteAccessCredential(env = process.env) {
276
+ const path = env[ACCESS_CREDENTIAL_ENV]?.trim();
277
+ if (!path) return void 0;
278
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
279
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
280
+ throw new Error(`Invalid Agent Intercom access credential at ${path}`);
281
+ }
282
+ const credential = parsed;
283
+ if (nonEmptyString(credential.enrollmentToken)) {
284
+ return { path, access: { enrollmentToken: credential.enrollmentToken }, enrollment: true };
285
+ }
286
+ if (credential.version === ACCESS_CREDENTIAL_VERSION && nonEmptyString(credential.sessionCredential) && nonEmptyString(credential.sessionId) && typeof credential.generation === "number" && Number.isSafeInteger(credential.generation) && credential.generation > 0) {
287
+ return {
288
+ path,
289
+ access: {
290
+ sessionCredential: credential.sessionCredential,
291
+ sessionId: credential.sessionId,
292
+ generation: credential.generation
293
+ },
294
+ enrollment: false
295
+ };
296
+ }
297
+ throw new Error(`Invalid Agent Intercom access credential at ${path}`);
298
+ }
299
+ function writeRemoteSessionCredential(path, sessionId, metadata) {
300
+ if (!metadata.sessionCredential) {
301
+ throw new Error("Remote enrollment response omitted the session credential");
302
+ }
303
+ writeDurableJson(path, {
304
+ version: ACCESS_CREDENTIAL_VERSION,
305
+ sessionCredential: metadata.sessionCredential,
306
+ sessionId,
307
+ generation: metadata.generation
308
+ });
309
+ }
310
+
311
+ // protocol-v4/contract.ts
312
+ import {
313
+ INTERCOM_PROTOCOL_NAME as INTERCOM_PROTOCOL_NAME2,
314
+ INTERCOM_PROTOCOL_V4_SEMANTICS_HASH,
315
+ INTERCOM_PROTOCOL_V4_VECTOR_SCHEMA_VERSION,
316
+ INTERCOM_PROTOCOL_V4_VECTORS,
317
+ INTERCOM_PROTOCOL_VERSION as INTERCOM_PROTOCOL_VERSION2,
318
+ INTERCOM_SCOPE_ENV,
319
+ INTERCOM_SCOPE_ID_PATTERN,
320
+ INTERCOM_SCOPE_ID_PATTERN_SOURCE,
321
+ intercomScopeIdFromEnv,
322
+ parseIntercomScopeId,
323
+ sameIntercomScope
324
+ } from "@ctliz/agent-intercom-core/protocol-v4";
325
+ var INTERCOM_REGISTRATION_METADATA_ERROR = "Registration metadata is invalid";
326
+ function parseIntercomScopeIdForRegistration(value) {
327
+ try {
328
+ return parseIntercomScopeId(value, "registrationMetadata");
329
+ } catch {
330
+ throw new Error(INTERCOM_REGISTRATION_METADATA_ERROR);
331
+ }
332
+ }
333
+ function intercomScopeIdFromEnvForRegistration(env = process.env) {
334
+ try {
335
+ return intercomScopeIdFromEnv(env);
336
+ } catch {
337
+ throw new Error(INTERCOM_REGISTRATION_METADATA_ERROR);
338
+ }
339
+ }
340
+
341
+ // types.ts
342
+ var DELIVERY_FAILURE_CODES = [
343
+ "INVALID_MESSAGE",
344
+ "SESSION_NOT_FOUND",
345
+ "AMBIGUOUS_TARGET",
346
+ "SENDER_NOT_FOUND",
347
+ "INVALID_REPLY_TARGET",
348
+ "MUTUAL_ASK",
349
+ "ASK_ALREADY_PENDING",
350
+ "DUPLICATE_MESSAGE_ID",
351
+ "CONFLICTING_MESSAGE_ID",
352
+ "TOO_MANY_PENDING_DELIVERIES",
353
+ "TOO_MANY_PENDING_ASKS",
354
+ "RECIPIENT_DISCONNECTED",
355
+ "SENDER_DISCONNECTED",
356
+ "DELIVERY_TIMEOUT",
357
+ "CONTROL_DISPATCH_UNAVAILABLE"
358
+ ];
359
+
360
+ // broker/boss-contracts.ts
361
+ import {
362
+ BOSS_CAPABILITY_FEATURE_DIGEST,
363
+ BOSS_POLICY_SEMANTICS_HASH,
364
+ BOSS_RUN_FEATURE,
365
+ BOSS_RUN_FEATURE_CONTRACT,
366
+ INTERCOM_BASE_PROTOCOL_VERSION,
367
+ evaluateBrokerCompatibility,
368
+ parseBossControlEnvelope,
369
+ parseBossParticipantBinding,
370
+ parseBossParticipantCredentialEnvelope,
371
+ parseBossPolicyPrincipal,
372
+ parseBossRunFeatureContract,
373
+ parseBrokerCapabilityAdvertisement,
374
+ isTerminalParticipantState,
375
+ parseParticipantState,
376
+ parseWorkerIdentityV2
377
+ } from "@ctliz/agent-intercom-core/boss";
378
+ import {
379
+ ContractValidationError,
380
+ assertExactKeys,
381
+ assertRecord,
382
+ readHexDigest
383
+ } from "@ctliz/agent-intercom-core/canonical";
384
+
385
+ // broker/validation.ts
386
+ import { types as nodeUtilTypes } from "node:util";
387
+ function isPlainDataRecord(value) {
388
+ if (typeof value !== "object" || value === null || nodeUtilTypes.isProxy(value) || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
389
+ return false;
390
+ }
391
+ try {
392
+ return Reflect.ownKeys(value).every((key) => {
393
+ if (typeof key !== "string") return false;
394
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
395
+ return descriptor !== void 0 && descriptor.enumerable && Object.hasOwn(descriptor, "value");
396
+ });
397
+ } catch {
398
+ return false;
399
+ }
400
+ }
401
+ function hasExactDataKeys(value, required, optional = []) {
402
+ if (!isPlainDataRecord(value)) return false;
403
+ const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
404
+ const keys = Reflect.ownKeys(value);
405
+ return keys.length >= required.length && keys.every((key) => typeof key === "string" && allowed.has(key)) && required.every((key) => Object.hasOwn(value, key));
406
+ }
407
+ function assertPlainDataGraph(value, path = "$", seen = /* @__PURE__ */ new WeakSet()) {
408
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return;
409
+ if (nodeUtilTypes.isProxy(value)) {
410
+ throw new Error(`${path} must not contain a Proxy`);
411
+ }
412
+ if (seen.has(value)) {
413
+ throw new Error(`${path} must not contain a cycle or repeated object reference`);
414
+ }
415
+ seen.add(value);
416
+ if (Array.isArray(value)) {
417
+ if (Object.getPrototypeOf(value) !== Array.prototype) {
418
+ throw new Error(`${path} must use the exact Array prototype`);
419
+ }
420
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
421
+ if (lengthDescriptor === void 0 || !Object.hasOwn(lengthDescriptor, "value")) {
422
+ throw new Error(`${path}.length must be an own data property`);
423
+ }
424
+ const length = lengthDescriptor.value;
425
+ const descriptors = /* @__PURE__ */ new Map();
426
+ for (const key of Reflect.ownKeys(value)) {
427
+ if (key === "length") continue;
428
+ if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`);
429
+ const index = Number(key);
430
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
431
+ throw new Error(`${path} must not contain non-index properties`);
432
+ }
433
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
434
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
435
+ throw new Error(`${path}[${index}] must be an enumerable own data property`);
436
+ }
437
+ descriptors.set(index, descriptor);
438
+ }
439
+ if (descriptors.size !== length) throw new Error(`${path} must be dense`);
440
+ for (let index = 0; index < length; index += 1) {
441
+ const descriptor = descriptors.get(index);
442
+ if (descriptor === void 0) throw new Error(`${path} must be dense`);
443
+ assertPlainDataGraph(descriptor.value, `${path}[${index}]`, seen);
444
+ }
445
+ return;
446
+ }
447
+ if (Object.getPrototypeOf(value) !== Object.prototype) {
448
+ throw new Error(`${path} must be a plain data object`);
449
+ }
450
+ for (const key of Reflect.ownKeys(value)) {
451
+ if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`);
452
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
453
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
454
+ throw new Error(`${path}.${key} must be an enumerable own data property`);
455
+ }
456
+ assertPlainDataGraph(descriptor.value, `${path}.${key}`, seen);
457
+ }
458
+ }
459
+ function isDenseArrayOf(value, predicate) {
460
+ if (typeof value !== "object" || value === null || nodeUtilTypes.isProxy(value) || !Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) return false;
461
+ try {
462
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
463
+ if (lengthDescriptor === void 0 || !Object.hasOwn(lengthDescriptor, "value")) return false;
464
+ const length = lengthDescriptor.value;
465
+ const entries = /* @__PURE__ */ new Map();
466
+ for (const key of Reflect.ownKeys(value)) {
467
+ if (key === "length") continue;
468
+ if (typeof key !== "string") return false;
469
+ const index = Number(key);
470
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) return false;
471
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
472
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) return false;
473
+ entries.set(index, descriptor.value);
474
+ }
475
+ if (entries.size !== length) return false;
476
+ for (let index = 0; index < length; index += 1) {
477
+ if (!predicate(entries.get(index), index)) return false;
478
+ }
479
+ return true;
480
+ } catch {
481
+ return false;
482
+ }
483
+ }
484
+
485
+ // broker/boss-contracts.ts
486
+ function assertCompatibleOrdinaryAdvertisement(value) {
487
+ assertPlainDataGraph(value, "$.capabilities");
488
+ const advertisement = parseBrokerCapabilityAdvertisement(value);
489
+ const decision = evaluateBrokerCompatibility({
490
+ clientKind: "ordinary",
491
+ supportedBaseProtocolVersions: [INTERCOM_BASE_PROTOCOL_VERSION]
492
+ }, advertisement);
493
+ if (!decision.compatible || decision.mode !== "ordinary") {
494
+ throw new ContractValidationError("$.capabilities", "broker does not support exact base protocol v3");
495
+ }
496
+ return advertisement;
497
+ }
498
+ function assertCompatibleBossAdvertisement(value) {
499
+ assertPlainDataGraph(value, "$.capabilities");
500
+ const advertisement = parseBrokerCapabilityAdvertisement(value);
501
+ if (advertisement.baseProtocolVersion !== INTERCOM_BASE_PROTOCOL_VERSION || !advertisement.features.some((feature) => feature.feature === BOSS_RUN_FEATURE)) {
502
+ throw new ContractValidationError("$.capabilities", "broker did not negotiate the exact required Boss feature");
503
+ }
504
+ return advertisement;
505
+ }
506
+ function parseBossRegistrationRequest(value) {
507
+ assertPlainDataGraph(value, "$.boss");
508
+ assertRecord(value, "$.boss");
509
+ assertExactKeys(value, ["featureContract", "capabilityDigest", "credential"], [], "$.boss");
510
+ const featureContract = parseBossRunFeatureContract(value.featureContract);
511
+ const capabilityDigest = readHexDigest(value.capabilityDigest, "$.boss.capabilityDigest");
512
+ 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) {
513
+ throw new ContractValidationError("$.boss", "Boss registration diverges from the canonical feature contract");
514
+ }
515
+ return {
516
+ featureContract,
517
+ capabilityDigest,
518
+ credential: parseBossParticipantCredentialEnvelope(value.credential)
519
+ };
520
+ }
521
+ function parseBossSessionMetadata(value, sessionId) {
522
+ assertPlainDataGraph(value, "$.boss");
523
+ assertRecord(value, "$.boss");
524
+ assertExactKeys(value, [
525
+ "binding",
526
+ "principal",
527
+ "liveWorker",
528
+ "featureContract",
529
+ "policySemanticsHash",
530
+ "capabilityDigest",
531
+ "brokerIdentityVerified"
532
+ ], [], "$.boss");
533
+ const binding = parseBossParticipantBinding(value.binding);
534
+ const parsedPrincipal = parseBossPolicyPrincipal(value.principal);
535
+ if (parsedPrincipal.principalClass !== "boss-private") {
536
+ throw new ContractValidationError("$.boss.principal", "must be a Boss-private principal");
537
+ }
538
+ const principal = parsedPrincipal;
539
+ assertRecord(value.liveWorker, "$.boss.liveWorker");
540
+ assertExactKeys(value.liveWorker, ["identity", "state"], [], "$.boss.liveWorker");
541
+ const liveWorkerIdentity = parseWorkerIdentityV2(value.liveWorker.identity);
542
+ const liveWorkerState = parseParticipantState(value.liveWorker.state, "$.boss.liveWorker.state");
543
+ const featureContract = parseBossRunFeatureContract(value.featureContract);
544
+ const policySemanticsHash = readHexDigest(value.policySemanticsHash, "$.boss.policySemanticsHash");
545
+ const capabilityDigest = readHexDigest(value.capabilityDigest, "$.boss.capabilityDigest");
546
+ if (value.brokerIdentityVerified !== true) {
547
+ throw new ContractValidationError("$.boss.brokerIdentityVerified", "must be true for a live Boss session");
548
+ }
549
+ if (sessionId !== void 0 && binding.sessionId !== sessionId) {
550
+ throw new ContractValidationError("$.boss.binding.sessionId", "must match the registered session ID");
551
+ }
552
+ if (sessionId !== void 0 && principal.principalId !== sessionId) {
553
+ throw new ContractValidationError("$.boss.principal.principalId", "must match the registered session ID");
554
+ }
555
+ if (!("bossRunId" in liveWorkerIdentity) || sessionId !== void 0 && liveWorkerIdentity.workerId !== sessionId || liveWorkerIdentity.bossRunId !== binding.bossRunId || liveWorkerIdentity.participantId !== binding.participantId || liveWorkerIdentity.bindingEpoch !== binding.bindingEpoch || isTerminalParticipantState(liveWorkerState)) {
556
+ throw new ContractValidationError(
557
+ "$.boss.liveWorker",
558
+ "must attest the same live Boss worker identity and binding as the registered session"
559
+ );
560
+ }
561
+ 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) {
562
+ throw new ContractValidationError("$.boss", "binding and policy principal are not the same active participant");
563
+ }
564
+ if (featureContract.semanticsHash !== BOSS_RUN_FEATURE_CONTRACT.semanticsHash || policySemanticsHash !== BOSS_POLICY_SEMANTICS_HASH || capabilityDigest !== BOSS_CAPABILITY_FEATURE_DIGEST) {
565
+ throw new ContractValidationError("$.boss", "Boss session attestation diverges from Core");
566
+ }
567
+ return {
568
+ binding,
569
+ principal,
570
+ liveWorker: { identity: liveWorkerIdentity, state: liveWorkerState },
571
+ featureContract,
572
+ policySemanticsHash,
573
+ capabilityDigest,
574
+ brokerIdentityVerified: true
575
+ };
576
+ }
577
+ function assertBossRegistrationEcho(requestValue, metadataValue, sessionId) {
578
+ const request = parseBossRegistrationRequest(requestValue);
579
+ const metadata = parseBossSessionMetadata(metadataValue, sessionId);
580
+ const credential = request.credential;
581
+ const binding = metadata.binding;
582
+ if (credential.bossRunId !== binding.bossRunId || credential.participantId !== binding.participantId || credential.role !== binding.role || credential.communicationProfile !== binding.communicationProfile || credential.bindingEpoch !== binding.bindingEpoch || request.featureContract.feature !== metadata.featureContract.feature || request.featureContract.version !== metadata.featureContract.version || request.featureContract.baseProtocolVersion !== metadata.featureContract.baseProtocolVersion || request.featureContract.semanticsHash !== metadata.featureContract.semanticsHash || request.featureContract.controlEnvelopeVersion !== metadata.featureContract.controlEnvelopeVersion || request.capabilityDigest !== metadata.capabilityDigest) {
583
+ throw new ContractValidationError("$.boss", "broker-owned Boss registration echo does not match the requested participant binding");
584
+ }
585
+ return metadata;
586
+ }
587
+ function parseCorrelatedBossControl(value, messageId) {
588
+ assertPlainDataGraph(value, "$.control");
589
+ const envelope = parseBossControlEnvelope(value);
590
+ if (messageId !== void 0 && envelope.messageId !== messageId) {
591
+ throw new ContractValidationError("$.control.messageId", "must match the transport message ID");
592
+ }
593
+ return envelope;
594
+ }
595
+
596
+ // broker/client.ts
597
+ function toError(error2) {
598
+ return error2 instanceof Error ? error2 : new Error(String(error2));
599
+ }
600
+ function connectToBrokerTarget(target) {
601
+ return typeof target === "string" ? net.connect(target) : net.connect({ host: target.host, port: target.port });
602
+ }
603
+ function isAttachment(value) {
604
+ if (!hasExactDataKeys(value, ["type", "name", "content"], ["language"])) {
605
+ return false;
606
+ }
607
+ const attachment = value;
608
+ if (attachment.type !== "file" && attachment.type !== "snippet" && attachment.type !== "context") {
609
+ return false;
610
+ }
611
+ if (typeof attachment.name !== "string" || typeof attachment.content !== "string") {
612
+ return false;
613
+ }
614
+ return attachment.language === void 0 || typeof attachment.language === "string";
615
+ }
616
+ function isMessage(value) {
617
+ if (!hasExactDataKeys(value, ["id", "timestamp", "content"], ["replyTo", "expectsReply", "control"])) {
618
+ return false;
619
+ }
620
+ const message = value;
621
+ if (typeof message.id !== "string" || typeof message.timestamp !== "number") {
622
+ return false;
623
+ }
624
+ if (message.replyTo !== void 0 && typeof message.replyTo !== "string") {
625
+ return false;
626
+ }
627
+ if (message.expectsReply !== void 0 && typeof message.expectsReply !== "boolean") {
628
+ return false;
629
+ }
630
+ if (message.control !== void 0) {
631
+ try {
632
+ parseCorrelatedBossControl(message.control, message.id);
633
+ } catch {
634
+ return false;
635
+ }
636
+ if (message.replyTo !== void 0 || message.expectsReply !== void 0) return false;
637
+ }
638
+ if (!hasExactDataKeys(message.content, ["text"], ["attachments"])) {
639
+ return false;
640
+ }
641
+ const content = message.content;
642
+ if (typeof content.text !== "string") {
643
+ return false;
644
+ }
645
+ if (message.control !== void 0 && (content.text !== "" || content.attachments !== void 0)) return false;
646
+ return content.attachments === void 0 || isDenseArrayOf(content.attachments, isAttachment);
647
+ }
648
+ function isSessionInfo(value) {
649
+ if (!hasExactDataKeys(
650
+ value,
651
+ ["id", "cwd", "model", "pid", "startedAt", "lastActivity"],
652
+ [
653
+ "name",
654
+ "status",
655
+ "peerUid",
656
+ "trustedLocal",
657
+ "origin",
658
+ "remoteHostId",
659
+ "parentSessionId",
660
+ "rootSessionId",
661
+ "generation",
662
+ "canDelegate",
663
+ "depth",
664
+ "maxDepth",
665
+ "maxChildren",
666
+ "boss"
667
+ ]
668
+ )) {
669
+ return false;
670
+ }
671
+ const session = value;
672
+ if (typeof session.id !== "string" || typeof session.cwd !== "string" || typeof session.model !== "string" || typeof session.pid !== "number" || typeof session.startedAt !== "number" || typeof session.lastActivity !== "number") {
673
+ return false;
674
+ }
675
+ if (session.name !== void 0 && typeof session.name !== "string") {
676
+ return false;
677
+ }
678
+ if (session.status !== void 0 && typeof session.status !== "string") {
679
+ return false;
680
+ }
681
+ if (session.peerUid !== void 0 && typeof session.peerUid !== "number") {
682
+ return false;
683
+ }
684
+ if (session.trustedLocal !== void 0 && typeof session.trustedLocal !== "boolean") return false;
685
+ if (session.origin !== void 0 && session.origin !== "local" && session.origin !== "remote") return false;
686
+ if (session.remoteHostId !== void 0 && typeof session.remoteHostId !== "string") return false;
687
+ if (session.parentSessionId !== void 0 && typeof session.parentSessionId !== "string") return false;
688
+ if (session.rootSessionId !== void 0 && typeof session.rootSessionId !== "string") return false;
689
+ if (session.generation !== void 0 && (typeof session.generation !== "number" || !Number.isSafeInteger(session.generation))) return false;
690
+ if (session.canDelegate !== void 0 && typeof session.canDelegate !== "boolean") return false;
691
+ for (const field of ["depth", "maxDepth", "maxChildren"]) {
692
+ if (session[field] !== void 0 && (typeof session[field] !== "number" || !Number.isSafeInteger(session[field]))) return false;
693
+ }
694
+ if (session.boss !== void 0) {
695
+ try {
696
+ parseBossSessionMetadata(session.boss, session.id);
697
+ } catch {
698
+ return false;
699
+ }
700
+ }
701
+ return true;
702
+ }
703
+ function isRemoteAccessMetadata(value) {
704
+ if (!hasExactDataKeys(
705
+ value,
706
+ ["origin", "remoteHostId", "parentSessionId", "rootSessionId", "generation", "canDelegate", "depth", "maxDepth", "maxChildren"],
707
+ ["sessionCredential"]
708
+ )) return false;
709
+ const access = value;
710
+ return access.origin === "remote" && typeof access.remoteHostId === "string" && typeof access.parentSessionId === "string" && typeof access.rootSessionId === "string" && typeof access.generation === "number" && Number.isSafeInteger(access.generation) && access.generation > 0 && typeof access.canDelegate === "boolean" && typeof access.depth === "number" && Number.isSafeInteger(access.depth) && typeof access.maxDepth === "number" && Number.isSafeInteger(access.maxDepth) && typeof access.maxChildren === "number" && Number.isSafeInteger(access.maxChildren) && (access.sessionCredential === void 0 || typeof access.sessionCredential === "string");
711
+ }
712
+ function isDeliveryFailureCode(value) {
713
+ return typeof value === "string" && DELIVERY_FAILURE_CODES.includes(value);
714
+ }
715
+ var IntercomClient = class extends EventEmitter {
716
+ constructor(options = {}) {
717
+ super();
718
+ this.options = options;
719
+ this.scopeId = options.scopeId === void 0 ? intercomScopeIdFromEnvForRegistration(options.env ?? process.env) : parseIntercomScopeIdForRegistration(options.scopeId);
720
+ }
721
+ options;
722
+ socket = null;
723
+ scopeId;
724
+ _sessionId = null;
725
+ pendingSends = /* @__PURE__ */ new Map();
726
+ lateDeliveryAcceptances = /* @__PURE__ */ new Map();
727
+ pendingLists = /* @__PURE__ */ new Map();
728
+ pendingAskControls = /* @__PURE__ */ new Map();
729
+ outbox = null;
730
+ remoteAccessCredential;
731
+ requestedBossRegistration;
732
+ bossSessionMetadata;
733
+ disconnecting = false;
734
+ disconnectError = null;
735
+ failPending(error2) {
736
+ for (const pending of this.pendingSends.values()) {
737
+ pending.reject(error2);
738
+ }
739
+ this.pendingSends.clear();
740
+ for (const pending of this.pendingLists.values()) {
741
+ pending.reject(error2);
742
+ }
743
+ this.pendingLists.clear();
744
+ for (const pending of this.pendingAskControls.values()) {
745
+ clearTimeout(pending.timeout);
746
+ pending.resolve(false);
747
+ }
748
+ this.pendingAskControls.clear();
749
+ this.lateDeliveryAcceptances.clear();
750
+ }
751
+ hasQueuedOutboundMessage(messageId) {
752
+ return this.outbox?.list().some((entry) => entry.message.id === messageId) ?? false;
753
+ }
754
+ get sessionId() {
755
+ return this._sessionId;
756
+ }
757
+ get outboxSize() {
758
+ return this.outbox?.list().length ?? 0;
759
+ }
760
+ isConnected() {
761
+ const socket = this.socket;
762
+ return Boolean(socket && this._sessionId && !this.disconnecting && !socket.destroyed && !socket.writableEnded && socket.writable);
763
+ }
764
+ requireActiveSocket() {
765
+ if (this.disconnecting) {
766
+ throw new Error("Client disconnecting");
767
+ }
768
+ const socket = this.socket;
769
+ if (!socket || !this._sessionId) {
770
+ throw new Error("Not connected");
771
+ }
772
+ if (socket.destroyed || socket.writableEnded || !socket.writable) {
773
+ throw new Error("Client disconnected");
774
+ }
775
+ return socket;
776
+ }
777
+ connect(session, sessionId) {
778
+ if (this.socket) {
779
+ return Promise.reject(new Error("Already connected"));
780
+ }
781
+ return new Promise((resolve3, reject) => {
782
+ let socket;
783
+ let target;
784
+ try {
785
+ this.requestedBossRegistration = session.boss === void 0 ? void 0 : parseBossRegistrationRequest(session.boss);
786
+ target = getBrokerConnectTarget();
787
+ this.remoteAccessCredential = loadRemoteAccessCredential();
788
+ socket = connectToBrokerTarget(target);
789
+ } catch (error2) {
790
+ this.requestedBossRegistration = void 0;
791
+ reject(toError(error2));
792
+ return;
793
+ }
794
+ this.socket = socket;
795
+ this.disconnectError = null;
796
+ let settled = false;
797
+ const timeout = setTimeout(() => {
798
+ if (!this._sessionId) {
799
+ cleanupConnectionAttempt();
800
+ cleanupSocketListeners();
801
+ if (this.socket === socket) {
802
+ this.socket = null;
803
+ }
804
+ this.requestedBossRegistration = void 0;
805
+ socket.destroy();
806
+ reject(new Error("Connection timeout"));
807
+ }
808
+ }, 1e4);
809
+ let connectionEstablished = false;
810
+ const onRegistered = () => {
811
+ settled = true;
812
+ connectionEstablished = true;
813
+ cleanupConnectionAttempt();
814
+ resolve3();
815
+ };
816
+ const onError = (err) => {
817
+ settled = true;
818
+ cleanupConnectionAttempt();
819
+ cleanupSocketListeners();
820
+ if (this.socket === socket) {
821
+ this.socket = null;
822
+ }
823
+ this.requestedBossRegistration = void 0;
824
+ socket.destroy();
825
+ reject(err);
826
+ };
827
+ const onClose = () => {
828
+ const wasConnecting = !settled && !this._sessionId;
829
+ const wasDisconnecting = this.disconnecting;
830
+ const disconnectError = this.disconnectError ?? new Error("Client disconnected");
831
+ this.disconnecting = false;
832
+ cleanupConnectionAttempt();
833
+ cleanupSocketListeners();
834
+ this.failPending(disconnectError);
835
+ if (this.socket === socket) {
836
+ this.socket = null;
837
+ }
838
+ this._sessionId = null;
839
+ this.requestedBossRegistration = void 0;
840
+ this.bossSessionMetadata = void 0;
841
+ this.disconnectError = null;
842
+ if (connectionEstablished && !wasDisconnecting) {
843
+ this.emit("disconnected", disconnectError);
844
+ }
845
+ if (wasConnecting) {
846
+ reject(new Error("Connection closed before registration"));
847
+ }
848
+ };
849
+ const onSocketError = (err) => {
850
+ if (connectionEstablished) {
851
+ this.disconnectError = err;
852
+ this.emit("error", err);
853
+ }
854
+ };
855
+ const onReaderError = (error2) => {
856
+ const protocolError = new Error(`Intercom protocol error: ${error2.message}`, { cause: error2 });
857
+ if (!connectionEstablished) {
858
+ onError(protocolError);
859
+ return;
860
+ }
861
+ this.disconnectError = protocolError;
862
+ this.emit("error", protocolError);
863
+ socket.destroy();
864
+ };
865
+ const reader = createMessageReader((msg) => {
866
+ this.handleBrokerMessage(msg);
867
+ }, onReaderError);
868
+ const cleanupConnectionAttempt = () => {
869
+ this.off("_registered", onRegistered);
870
+ socket.off("error", onError);
871
+ clearTimeout(timeout);
872
+ };
873
+ const cleanupSocketListeners = () => {
874
+ socket.off("data", reader);
875
+ socket.off("error", onSocketError);
876
+ socket.off("close", onClose);
877
+ };
878
+ socket.on("data", reader);
879
+ socket.on("error", onError);
880
+ socket.on("close", onClose);
881
+ socket.on("error", onSocketError);
882
+ this.once("_registered", onRegistered);
883
+ try {
884
+ writeMessage(socket, {
885
+ type: "register",
886
+ protocol: INTERCOM_PROTOCOL_NAME,
887
+ version: INTERCOM_PROTOCOL_VERSION,
888
+ session,
889
+ ...!this.remoteAccessCredential && sessionId ? { sessionId } : {},
890
+ ...this.remoteAccessCredential ? { access: this.remoteAccessCredential.access } : {},
891
+ ...this.scopeId ? { scopeId: this.scopeId } : {},
892
+ ...typeof target === "string" ? {} : { stateId: target.stateId }
893
+ });
894
+ } catch (error2) {
895
+ cleanupConnectionAttempt();
896
+ cleanupSocketListeners();
897
+ if (this.socket === socket) {
898
+ this.socket = null;
899
+ }
900
+ this.requestedBossRegistration = void 0;
901
+ socket.destroy();
902
+ reject(toError(error2));
903
+ }
904
+ });
905
+ }
906
+ handleBrokerMessage(msg) {
907
+ if (!isPlainDataRecord(msg) || typeof msg.type !== "string") {
908
+ throw new Error("Invalid broker message");
909
+ }
910
+ const brokerMessage = msg;
911
+ if (this._sessionId === null && brokerMessage.type !== "registered" && brokerMessage.type !== "error") {
912
+ throw new Error(`Received ${brokerMessage.type} before registered`);
913
+ }
914
+ switch (brokerMessage.type) {
915
+ case "registered": {
916
+ const baseKeys = ["type", "sessionId", "protocol", "version"];
917
+ if (this.requestedBossRegistration !== void 0 && (brokerMessage.capabilities === void 0 || brokerMessage.boss === void 0)) {
918
+ throw new Error("Boss registration was downgraded to an ordinary session");
919
+ }
920
+ const exactKeys = this.requestedBossRegistration !== void 0 ? [...baseKeys, "capabilities", "boss"] : this.remoteAccessCredential ? [...baseKeys, "remoteAccess", "access"] : baseKeys;
921
+ const optionalKeys = this.requestedBossRegistration === void 0 ? ["capabilities"] : [];
922
+ if (!hasExactDataKeys(brokerMessage, exactKeys, optionalKeys) || typeof brokerMessage.sessionId !== "string" || brokerMessage.protocol !== INTERCOM_PROTOCOL_NAME || brokerMessage.version !== INTERCOM_PROTOCOL_VERSION) {
923
+ throw new Error("Invalid registered message");
924
+ }
925
+ if (this._sessionId !== null) {
926
+ throw new Error("Received duplicate registered message");
927
+ }
928
+ if (this.requestedBossRegistration !== void 0) {
929
+ assertCompatibleBossAdvertisement(brokerMessage.capabilities);
930
+ this.bossSessionMetadata = assertBossRegistrationEcho(
931
+ this.requestedBossRegistration,
932
+ brokerMessage.boss,
933
+ brokerMessage.sessionId
934
+ );
935
+ this.requestedBossRegistration = void 0;
936
+ } else {
937
+ if (brokerMessage.boss !== void 0) {
938
+ throw new Error("Ordinary registration received unsolicited Boss authority metadata");
939
+ }
940
+ if (brokerMessage.capabilities !== void 0) {
941
+ assertCompatibleOrdinaryAdvertisement(brokerMessage.capabilities);
942
+ }
943
+ this.bossSessionMetadata = void 0;
944
+ }
945
+ if (this.remoteAccessCredential) {
946
+ const contract = brokerMessage.remoteAccess;
947
+ const contractFields = hasExactDataKeys(
948
+ contract,
949
+ ["feature", "policySemanticsVersion", "policySemanticsHash"]
950
+ ) ? contract : void 0;
951
+ if (!contractFields || contractFields.feature !== "remote-access-v1" || contractFields.policySemanticsVersion !== POLICY_SEMANTICS_VERSION || contractFields.policySemanticsHash !== POLICY_SEMANTICS_HASH) {
952
+ throw new Error("Remote Intercom policy contract is absent or incompatible");
953
+ }
954
+ if (!isRemoteAccessMetadata(brokerMessage.access)) {
955
+ throw new Error("Remote Intercom registration omitted broker-owned provenance");
956
+ }
957
+ if (this.remoteAccessCredential.enrollment) {
958
+ writeRemoteSessionCredential(this.remoteAccessCredential.path, brokerMessage.sessionId, brokerMessage.access);
959
+ } else {
960
+ const reconnect = this.remoteAccessCredential.access;
961
+ if (!isPlainDataRecord(reconnect) || reconnect.sessionId !== brokerMessage.sessionId || reconnect.generation !== brokerMessage.access.generation) {
962
+ throw new Error("Remote Intercom reconnect identity or generation changed unexpectedly");
963
+ }
964
+ }
965
+ }
966
+ this._sessionId = brokerMessage.sessionId;
967
+ this.outbox = new PersistentOutboundOutbox(brokerMessage.sessionId);
968
+ this.lateDeliveryAcceptances.clear();
969
+ this.replayOutbox();
970
+ this.emit("_registered", { type: "registered", sessionId: brokerMessage.sessionId });
971
+ break;
972
+ }
973
+ case "sessions": {
974
+ const { requestId, sessions } = brokerMessage;
975
+ if (!hasExactDataKeys(brokerMessage, ["type", "requestId", "sessions"]) || typeof requestId !== "string" || !isDenseArrayOf(sessions, isSessionInfo)) {
976
+ throw new Error("Invalid sessions message");
977
+ }
978
+ const pending = this.pendingLists.get(requestId);
979
+ if (!pending) {
980
+ return;
981
+ }
982
+ this.pendingLists.delete(requestId);
983
+ pending.resolve(sessions);
984
+ break;
985
+ }
986
+ case "message": {
987
+ const { deliveryId, from, message } = brokerMessage;
988
+ if (!hasExactDataKeys(brokerMessage, ["type", "deliveryId", "from", "message"]) || typeof deliveryId !== "string" || !isSessionInfo(from) || !isMessage(message)) {
989
+ throw new Error("Invalid message event");
990
+ }
991
+ if (message.control !== void 0) {
992
+ if (this.bossSessionMetadata === void 0) {
993
+ throw new Error("Ordinary session received unavailable Boss control traffic");
994
+ }
995
+ this.emit("control", from, message.control, deliveryId);
996
+ } else {
997
+ this.emit("message", from, message, deliveryId);
998
+ }
999
+ break;
1000
+ }
1001
+ case "delivery_accepted": {
1002
+ const { deliveryId, messageId } = brokerMessage;
1003
+ if (!hasExactDataKeys(brokerMessage, ["type", "messageId", "deliveryId"]) || typeof deliveryId !== "string" || typeof messageId !== "string") {
1004
+ throw new Error("Invalid delivery_accepted message");
1005
+ }
1006
+ const pending = this.pendingSends.get(messageId);
1007
+ if (pending) {
1008
+ if (pending.accepted) {
1009
+ throw new Error("Duplicate delivery_accepted message");
1010
+ }
1011
+ pending.accepted = true;
1012
+ pending.deliveryId = deliveryId;
1013
+ this.emit("delivery_accepted", messageId, deliveryId);
1014
+ break;
1015
+ }
1016
+ if (!this.hasQueuedOutboundMessage(messageId)) {
1017
+ throw new Error("Unexpected delivery_accepted message without a pending send or queued outbox message");
1018
+ }
1019
+ if (this.lateDeliveryAcceptances.has(messageId)) {
1020
+ throw new Error("Duplicate delivery_accepted message");
1021
+ }
1022
+ this.lateDeliveryAcceptances.set(messageId, deliveryId);
1023
+ this.emit("delivery_accepted", messageId, deliveryId);
1024
+ break;
1025
+ }
1026
+ case "delivered": {
1027
+ const { deliveryId, messageId } = brokerMessage;
1028
+ if (!hasExactDataKeys(brokerMessage, ["type", "messageId", "deliveryId"]) || typeof deliveryId !== "string" || typeof messageId !== "string") {
1029
+ throw new Error("Invalid delivered message");
1030
+ }
1031
+ const pending = this.pendingSends.get(messageId);
1032
+ if (pending) {
1033
+ if (!pending.accepted) {
1034
+ throw new Error("Delivered message arrived before delivery acceptance");
1035
+ }
1036
+ if (pending.deliveryId !== deliveryId) {
1037
+ throw new Error("Delivered message used a mismatched delivery ID");
1038
+ }
1039
+ this.outbox?.remove(messageId);
1040
+ this.pendingSends.delete(messageId);
1041
+ this.lateDeliveryAcceptances.delete(messageId);
1042
+ pending.resolve({ id: messageId, accepted: true, delivered: true, deliveryId });
1043
+ break;
1044
+ }
1045
+ if (!this.hasQueuedOutboundMessage(messageId)) {
1046
+ throw new Error("Unexpected delivered message without a pending send or queued outbox message");
1047
+ }
1048
+ const acceptedDeliveryId = this.lateDeliveryAcceptances.get(messageId);
1049
+ if (acceptedDeliveryId === void 0) {
1050
+ throw new Error("Delivered message arrived before delivery acceptance");
1051
+ }
1052
+ if (acceptedDeliveryId !== deliveryId) {
1053
+ throw new Error("Delivered message used a mismatched delivery ID");
1054
+ }
1055
+ this.outbox?.remove(messageId);
1056
+ this.lateDeliveryAcceptances.delete(messageId);
1057
+ this.emit("outbox_delivered", messageId, deliveryId);
1058
+ break;
1059
+ }
1060
+ case "delivery_failed": {
1061
+ const { accepted, code, messageId, reason } = brokerMessage;
1062
+ if (!hasExactDataKeys(brokerMessage, ["type", "messageId", "accepted", "code", "reason"]) || typeof accepted !== "boolean" || !isDeliveryFailureCode(code) || typeof messageId !== "string" || typeof reason !== "string") {
1063
+ throw new Error("Invalid delivery_failed message");
1064
+ }
1065
+ const pending = this.pendingSends.get(messageId);
1066
+ if (pending) {
1067
+ if (accepted !== pending.accepted) {
1068
+ throw new Error("Delivery failure contradicted the accepted delivery state");
1069
+ }
1070
+ this.outbox?.remove(messageId);
1071
+ this.pendingSends.delete(messageId);
1072
+ this.lateDeliveryAcceptances.delete(messageId);
1073
+ pending.resolve({
1074
+ id: messageId,
1075
+ accepted,
1076
+ delivered: false,
1077
+ code,
1078
+ reason,
1079
+ ...pending.deliveryId !== void 0 ? { deliveryId: pending.deliveryId } : {}
1080
+ });
1081
+ break;
1082
+ }
1083
+ if (!this.hasQueuedOutboundMessage(messageId)) {
1084
+ throw new Error("Unexpected delivery_failed message without a pending send or queued outbox message");
1085
+ }
1086
+ if (accepted !== this.lateDeliveryAcceptances.has(messageId)) {
1087
+ throw new Error("Delivery failure contradicted the accepted delivery state");
1088
+ }
1089
+ this.outbox?.remove(messageId);
1090
+ this.lateDeliveryAcceptances.delete(messageId);
1091
+ this.emit("outbox_failed", messageId, code, reason);
1092
+ break;
1093
+ }
1094
+ case "ask_deferred": {
1095
+ const { fromSessionId, messageId } = brokerMessage;
1096
+ if (typeof fromSessionId !== "string" || typeof messageId !== "string") {
1097
+ throw new Error("Invalid ask_deferred message");
1098
+ }
1099
+ this.emit("ask_deferred", messageId, fromSessionId);
1100
+ break;
1101
+ }
1102
+ case "ask_cancelled": {
1103
+ const { fromSessionId, messageId, reason } = brokerMessage;
1104
+ if (typeof fromSessionId !== "string" || typeof messageId !== "string" || typeof reason !== "string") {
1105
+ throw new Error("Invalid ask_cancelled message");
1106
+ }
1107
+ this.emit("ask_cancelled", messageId, fromSessionId, reason);
1108
+ break;
1109
+ }
1110
+ case "ask_control_result": {
1111
+ const { action, applied, messageId, requestId } = brokerMessage;
1112
+ if (action !== "defer" && action !== "cancel" || typeof applied !== "boolean" || typeof messageId !== "string" || typeof requestId !== "string") {
1113
+ throw new Error("Invalid ask_control_result message");
1114
+ }
1115
+ const pending = this.pendingAskControls.get(requestId);
1116
+ if (!pending) return;
1117
+ clearTimeout(pending.timeout);
1118
+ this.pendingAskControls.delete(requestId);
1119
+ pending.resolve(applied);
1120
+ break;
1121
+ }
1122
+ case "session_joined": {
1123
+ if (!isSessionInfo(brokerMessage.session)) {
1124
+ throw new Error("Invalid session_joined message");
1125
+ }
1126
+ this.emit("session_joined", brokerMessage.session);
1127
+ break;
1128
+ }
1129
+ case "session_left": {
1130
+ if (typeof brokerMessage.sessionId !== "string") {
1131
+ throw new Error("Invalid session_left message");
1132
+ }
1133
+ this.emit("session_left", brokerMessage.sessionId);
1134
+ break;
1135
+ }
1136
+ case "presence_update": {
1137
+ if (!isSessionInfo(brokerMessage.session)) {
1138
+ throw new Error("Invalid presence_update message");
1139
+ }
1140
+ this.emit("presence_update", brokerMessage.session);
1141
+ break;
1142
+ }
1143
+ case "error": {
1144
+ if (typeof brokerMessage.code !== "string" || typeof brokerMessage.error !== "string") {
1145
+ throw new Error("Invalid error message");
1146
+ }
1147
+ if (this._sessionId === null) {
1148
+ const error3 = new Error(brokerMessage.error);
1149
+ error3.code = brokerMessage.code;
1150
+ throw error3;
1151
+ }
1152
+ const error2 = new Error(brokerMessage.error);
1153
+ error2.code = brokerMessage.code;
1154
+ this.emit("error", error2);
1155
+ break;
1156
+ }
1157
+ default:
1158
+ throw new Error(`Unknown broker message type: ${brokerMessage.type}`);
1159
+ }
1160
+ }
1161
+ async disconnect(preserveAsks = false) {
1162
+ const socket = this.socket;
1163
+ if (!socket) {
1164
+ return;
1165
+ }
1166
+ this.disconnecting = true;
1167
+ this.disconnectError = null;
1168
+ this.failPending(new Error("Client disconnected"));
1169
+ if (!preserveAsks) this.outbox?.clear();
1170
+ await new Promise((resolve3) => {
1171
+ let settled = false;
1172
+ const finish = () => {
1173
+ if (settled) {
1174
+ return;
1175
+ }
1176
+ settled = true;
1177
+ clearTimeout(timeout);
1178
+ socket.off("close", onClose);
1179
+ socket.off("error", onError);
1180
+ resolve3();
1181
+ };
1182
+ const onClose = () => finish();
1183
+ const onError = () => {
1184
+ socket.destroy();
1185
+ };
1186
+ const timeout = setTimeout(() => {
1187
+ socket.destroy();
1188
+ }, 2e3);
1189
+ socket.once("close", onClose);
1190
+ socket.once("error", onError);
1191
+ try {
1192
+ writeMessage(socket, { type: "unregister", ...preserveAsks ? { preserveAsks: true } : {} });
1193
+ socket.end();
1194
+ } catch {
1195
+ socket.destroy();
1196
+ }
1197
+ });
1198
+ }
1199
+ listSessions() {
1200
+ let socket;
1201
+ try {
1202
+ socket = this.requireActiveSocket();
1203
+ } catch (error2) {
1204
+ return Promise.reject(toError(error2));
1205
+ }
1206
+ return new Promise((resolve3, reject) => {
1207
+ const requestId = randomUUID2();
1208
+ const wrappedResolve = (sessions) => {
1209
+ clearTimeout(timeout);
1210
+ resolve3(sessions);
1211
+ };
1212
+ const wrappedReject = (error2) => {
1213
+ clearTimeout(timeout);
1214
+ reject(error2);
1215
+ };
1216
+ const timeout = setTimeout(() => {
1217
+ if (this.pendingLists.has(requestId)) {
1218
+ this.pendingLists.delete(requestId);
1219
+ wrappedReject(new Error("List sessions timeout"));
1220
+ }
1221
+ }, 5e3);
1222
+ this.pendingLists.set(requestId, { resolve: wrappedResolve, reject: wrappedReject });
1223
+ try {
1224
+ writeMessage(socket, { type: "list", requestId });
1225
+ } catch (error2) {
1226
+ clearTimeout(timeout);
1227
+ this.pendingLists.delete(requestId);
1228
+ reject(toError(error2));
1229
+ }
1230
+ });
1231
+ }
1232
+ send(to, options) {
1233
+ return this.sendInternal(to, options);
1234
+ }
1235
+ sendControl(to, controlValue) {
1236
+ const control = parseCorrelatedBossControl(controlValue);
1237
+ return this.sendInternal(to, {
1238
+ text: "",
1239
+ messageId: control.messageId,
1240
+ control
1241
+ });
1242
+ }
1243
+ sendInternal(to, options) {
1244
+ let socket;
1245
+ try {
1246
+ socket = this.requireActiveSocket();
1247
+ } catch (error2) {
1248
+ return Promise.reject(toError(error2));
1249
+ }
1250
+ const messageId = options.messageId ?? randomUUID2();
1251
+ if (this.pendingSends.has(messageId)) {
1252
+ return Promise.resolve({
1253
+ id: messageId,
1254
+ accepted: false,
1255
+ delivered: false,
1256
+ code: "DUPLICATE_MESSAGE_ID",
1257
+ reason: `Message ID ${messageId} is already pending`
1258
+ });
1259
+ }
1260
+ const message = {
1261
+ id: messageId,
1262
+ timestamp: Date.now(),
1263
+ replyTo: options.replyTo,
1264
+ expectsReply: options.expectsReply,
1265
+ ...options.control === void 0 ? {} : { control: options.control },
1266
+ content: {
1267
+ text: options.text,
1268
+ ...options.attachments === void 0 ? {} : { attachments: options.attachments }
1269
+ }
1270
+ };
1271
+ try {
1272
+ this.outbox?.enqueue(to, message);
1273
+ } catch (error2) {
1274
+ return Promise.reject(toError(error2));
1275
+ }
1276
+ return new Promise((resolve3, reject) => {
1277
+ const wrappedResolve = (result) => {
1278
+ clearTimeout(timeout);
1279
+ resolve3(result);
1280
+ };
1281
+ const wrappedReject = (error2) => {
1282
+ clearTimeout(timeout);
1283
+ reject(error2);
1284
+ };
1285
+ const timeout = setTimeout(() => {
1286
+ const pending = this.pendingSends.get(messageId);
1287
+ if (pending) {
1288
+ if (pending.accepted && pending.deliveryId !== void 0 && this.hasQueuedOutboundMessage(messageId)) {
1289
+ this.lateDeliveryAcceptances.set(messageId, pending.deliveryId);
1290
+ }
1291
+ this.pendingSends.delete(messageId);
1292
+ wrappedReject(new Error("Send timeout"));
1293
+ }
1294
+ }, 1e4);
1295
+ this.pendingSends.set(messageId, {
1296
+ accepted: false,
1297
+ resolve: wrappedResolve,
1298
+ reject: wrappedReject
1299
+ });
1300
+ try {
1301
+ writeMessage(socket, { type: "send", to, message });
1302
+ } catch (error2) {
1303
+ clearTimeout(timeout);
1304
+ this.pendingSends.delete(messageId);
1305
+ reject(toError(error2));
1306
+ }
1307
+ });
1308
+ }
1309
+ acknowledgeMessage(deliveryId) {
1310
+ return this.writeControlMessage({ type: "message_received", deliveryId });
1311
+ }
1312
+ rejectMessage(deliveryId, reason) {
1313
+ return this.writeControlMessage({ type: "message_rejected", deliveryId, code: "CONFLICTING_MESSAGE_ID", reason });
1314
+ }
1315
+ deferAsk(messageId) {
1316
+ return this.sendAskControl("defer", messageId);
1317
+ }
1318
+ cancelAsk(messageId) {
1319
+ return this.sendAskControl("cancel", messageId);
1320
+ }
1321
+ sendAskControl(action, messageId) {
1322
+ const requestId = randomUUID2();
1323
+ return new Promise((resolve3) => {
1324
+ const timeout = setTimeout(() => {
1325
+ this.pendingAskControls.delete(requestId);
1326
+ resolve3(false);
1327
+ }, 2e3);
1328
+ timeout.unref?.();
1329
+ this.pendingAskControls.set(requestId, { resolve: resolve3, timeout });
1330
+ if (!this.writeControlMessage({ type: action === "defer" ? "defer_ask" : "cancel_ask", requestId, messageId })) {
1331
+ clearTimeout(timeout);
1332
+ this.pendingAskControls.delete(requestId);
1333
+ resolve3(false);
1334
+ }
1335
+ });
1336
+ }
1337
+ writeControlMessage(message) {
1338
+ if (this.disconnecting) {
1339
+ return false;
1340
+ }
1341
+ const socket = this.socket;
1342
+ if (!socket || !this._sessionId || socket.destroyed || socket.writableEnded || !socket.writable) {
1343
+ return false;
1344
+ }
1345
+ try {
1346
+ writeMessage(socket, message);
1347
+ return true;
1348
+ } catch {
1349
+ return false;
1350
+ }
1351
+ }
1352
+ replayOutbox() {
1353
+ const socket = this.socket;
1354
+ if (!socket || !this._sessionId || socket.destroyed || socket.writableEnded || !socket.writable) return;
1355
+ for (const entry of this.outbox?.list() ?? []) {
1356
+ if (this.pendingSends.has(entry.message.id)) continue;
1357
+ this.lateDeliveryAcceptances.delete(entry.message.id);
1358
+ this.pendingSends.set(entry.message.id, {
1359
+ accepted: false,
1360
+ resolve: (result) => {
1361
+ if (result.delivered && result.deliveryId) {
1362
+ this.emit("outbox_delivered", result.id, result.deliveryId);
1363
+ } else {
1364
+ this.emit("outbox_failed", result.id, result.code, result.reason);
1365
+ }
1366
+ },
1367
+ reject: () => {
1368
+ }
1369
+ });
1370
+ try {
1371
+ writeMessage(socket, { type: "send", to: entry.to, message: entry.message });
1372
+ } catch {
1373
+ this.pendingSends.delete(entry.message.id);
1374
+ return;
1375
+ }
1376
+ }
1377
+ }
1378
+ updatePresence(updates) {
1379
+ if (this.disconnecting) {
1380
+ return;
1381
+ }
1382
+ const socket = this.socket;
1383
+ if (!socket || !this._sessionId || socket.destroyed || socket.writableEnded || !socket.writable) {
1384
+ return;
1385
+ }
1386
+ writeMessage(socket, { type: "presence", ...updates });
1387
+ }
1388
+ };
1389
+
1390
+ // broker/spawn.ts
1391
+ import { spawn } from "child_process";
1392
+ import { existsSync as existsSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
1393
+ import { join as join3, dirname as dirname2 } from "path";
1394
+ import { fileURLToPath } from "url";
1395
+ import { createRequire } from "module";
1396
+ import net2 from "net";
1397
+ import { randomUUID as randomUUID3 } from "crypto";
1398
+ import { POLICY_SEMANTICS_HASH as POLICY_SEMANTICS_HASH2, POLICY_SEMANTICS_VERSION as POLICY_SEMANTICS_VERSION2 } from "@ctliz/agent-intercom-core";
1399
+ var INTERCOM_DIR = getIntercomDirPath();
1400
+ var EXTENSION_DIR = join3(dirname2(fileURLToPath(import.meta.url)), "..");
1401
+ var BROKER_PID = join3(INTERCOM_DIR, "broker.pid");
1402
+ var BROKER_SPAWN_LOCK = join3(INTERCOM_DIR, "broker.spawn.lock");
1403
+ function sleep(ms) {
1404
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
1405
+ }
1406
+ function getBrokerEntryPath(moduleUrl = import.meta.url) {
1407
+ const moduleDir = dirname2(fileURLToPath(moduleUrl));
1408
+ const bundledBroker = join3(moduleDir, "broker.mjs");
1409
+ return existsSync2(bundledBroker) ? bundledBroker : join3(moduleDir, "broker.ts");
1410
+ }
1411
+ function getTsxCliPath(extensionDir = EXTENSION_DIR) {
1412
+ try {
1413
+ const requireFromExtension = createRequire(import.meta.url);
1414
+ const tsxMain = requireFromExtension.resolve("tsx");
1415
+ return join3(dirname2(tsxMain), "cli.mjs");
1416
+ } catch {
1417
+ return join3(extensionDir, "node_modules", "tsx", "dist", "cli.mjs");
1418
+ }
1419
+ }
1420
+ function quoteWindowsArg(value) {
1421
+ return `"${value.replace(/"/g, '""')}"`;
1422
+ }
1423
+ function getWindowsHiddenLauncherPath(intercomDir = INTERCOM_DIR) {
1424
+ return join3(intercomDir, "broker-launch.vbs");
1425
+ }
1426
+ function usesDefaultBrokerCommand(brokerCommand, brokerArgs) {
1427
+ return brokerCommand === "npx" && brokerArgs.length === 2 && brokerArgs[0] === "--no-install" && brokerArgs[1] === "tsx";
1428
+ }
1429
+ function getWindowsBrokerCommandLine(brokerPath, extensionDir = EXTENSION_DIR, nodePath = process.execPath, brokerCommand = "npx", brokerArgs = ["--no-install", "tsx"]) {
1430
+ if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
1431
+ return [quoteWindowsArg(nodePath), quoteWindowsArg(getTsxCliPath(extensionDir)), quoteWindowsArg(brokerPath)].join(" ");
1432
+ }
1433
+ return [quoteWindowsArg(brokerCommand), ...brokerArgs.map(quoteWindowsArg), quoteWindowsArg(brokerPath)].join(" ");
1434
+ }
1435
+ function getWindowsHiddenLauncherScript(commandLine) {
1436
+ return [
1437
+ 'Set WshShell = CreateObject("WScript.Shell")',
1438
+ `WshShell.Run "${commandLine.replace(/"/g, '""')}", 0, False`,
1439
+ "Set WshShell = Nothing",
1440
+ ""
1441
+ ].join("\r\n");
1442
+ }
1443
+ function isBrokerHealthOkMessage(message, requestId) {
1444
+ if (!hasExactDataKeys(
1445
+ message,
1446
+ ["type", "requestId", "protocol", "version", "endpoint", "remoteAccess"],
1447
+ ["capabilities"]
1448
+ )) return false;
1449
+ const response = message;
1450
+ if (response.type !== "health_ok" || response.requestId !== requestId || response.protocol !== INTERCOM_PROTOCOL_NAME || response.version !== INTERCOM_PROTOCOL_VERSION || response.endpoint !== "local") return false;
1451
+ const remoteAccess = response.remoteAccess;
1452
+ if (!hasExactDataKeys(remoteAccess, ["feature", "policySemanticsVersion", "policySemanticsHash"])) return false;
1453
+ const contract = remoteAccess;
1454
+ const legacyCompatible = contract.feature === "remote-access-v1" && contract.policySemanticsVersion === POLICY_SEMANTICS_VERSION2 && contract.policySemanticsHash === POLICY_SEMANTICS_HASH2;
1455
+ if (!legacyCompatible) return false;
1456
+ if (response.capabilities !== void 0) {
1457
+ try {
1458
+ assertCompatibleOrdinaryAdvertisement(response.capabilities);
1459
+ } catch {
1460
+ return false;
1461
+ }
1462
+ }
1463
+ return true;
1464
+ }
1465
+ function writeWindowsHiddenLauncher(commandLine, launcherPath = getWindowsHiddenLauncherPath()) {
1466
+ ensureIntercomRuntimeDir(dirname2(launcherPath));
1467
+ writeFileSync2(launcherPath, getWindowsHiddenLauncherScript(commandLine), {
1468
+ encoding: "utf-8",
1469
+ mode: INTERCOM_RUNTIME_FILE_MODE
1470
+ });
1471
+ restrictIntercomRuntimeFile(launcherPath);
1472
+ return launcherPath;
1473
+ }
1474
+ function getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs, extensionDir = EXTENSION_DIR, platform = process.platform, intercomDir = INTERCOM_DIR, nodePath = process.execPath) {
1475
+ if (platform === "win32") {
1476
+ const launcherPath = getWindowsHiddenLauncherPath(intercomDir);
1477
+ return {
1478
+ kind: "windows-launcher",
1479
+ command: "wscript.exe",
1480
+ args: [launcherPath],
1481
+ launcherPath,
1482
+ launcherCommandLine: getWindowsBrokerCommandLine(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs)
1483
+ };
1484
+ }
1485
+ if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
1486
+ return {
1487
+ kind: "direct",
1488
+ command: nodePath,
1489
+ args: [getTsxCliPath(extensionDir), brokerPath]
1490
+ };
1491
+ }
1492
+ return {
1493
+ kind: "direct",
1494
+ command: brokerCommand,
1495
+ args: [...brokerArgs, brokerPath]
1496
+ };
1497
+ }
1498
+ function getBrokerSpawnOptions(extensionDir = EXTENSION_DIR, env = process.env) {
1499
+ return {
1500
+ detached: true,
1501
+ stdio: "ignore",
1502
+ cwd: extensionDir,
1503
+ env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
1504
+ windowsHide: true
1505
+ };
1506
+ }
1507
+ function toError2(error2) {
1508
+ return error2 instanceof Error ? error2 : new Error(String(error2));
1509
+ }
1510
+ async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
1511
+ ensureIntercomRuntimeDir(INTERCOM_DIR);
1512
+ if (await isBrokerRunning()) {
1513
+ return;
1514
+ }
1515
+ const ownsLock = acquireSpawnLock();
1516
+ if (!ownsLock) {
1517
+ await waitForBroker();
1518
+ return;
1519
+ }
1520
+ try {
1521
+ if (await isBrokerRunning()) {
1522
+ return;
1523
+ }
1524
+ if (await checkBrokerHealth() === "incompatible") {
1525
+ throw new Error(`Incompatible live intercom broker; expected ${INTERCOM_PROTOCOL_NAME} v${INTERCOM_PROTOCOL_VERSION}. Stop it explicitly during a coordinated migration.`);
1526
+ }
1527
+ const brokerPath = getBrokerEntryPath();
1528
+ const launch = getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs);
1529
+ if (launch.kind === "windows-launcher") {
1530
+ writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
1531
+ }
1532
+ const child = spawn(launch.command, launch.args, getBrokerSpawnOptions());
1533
+ child.unref();
1534
+ await new Promise((resolve3, reject) => {
1535
+ const cleanup = () => {
1536
+ child.off("error", onError);
1537
+ child.off("exit", onExit);
1538
+ };
1539
+ const onError = (error2) => {
1540
+ cleanup();
1541
+ reject(new Error(`Failed to spawn intercom broker: ${error2.message}`, { cause: error2 }));
1542
+ };
1543
+ const onExit = (code, signal) => {
1544
+ if (launch.kind === "windows-launcher" && code === 0 && signal === null) {
1545
+ return;
1546
+ }
1547
+ cleanup();
1548
+ if (signal) {
1549
+ reject(new Error(`Intercom broker exited before startup with signal ${signal}`));
1550
+ return;
1551
+ }
1552
+ reject(new Error(`Intercom broker exited before startup with code ${code ?? "unknown"}`));
1553
+ };
1554
+ child.once("error", onError);
1555
+ child.once("exit", onExit);
1556
+ waitForBroker().then(() => {
1557
+ cleanup();
1558
+ resolve3();
1559
+ }, (error2) => {
1560
+ cleanup();
1561
+ reject(toError2(error2));
1562
+ });
1563
+ });
1564
+ } finally {
1565
+ releaseSpawnLock();
1566
+ }
1567
+ }
1568
+ async function isBrokerRunning() {
1569
+ if (await checkSocketConnectable()) {
1570
+ return true;
1571
+ }
1572
+ if (!existsSync2(BROKER_PID)) return false;
1573
+ try {
1574
+ const pid = parseInt(readFileSync4(BROKER_PID, "utf-8").trim(), 10);
1575
+ if (!Number.isFinite(pid)) return false;
1576
+ process.kill(pid, 0);
1577
+ return checkSocketConnectable();
1578
+ } catch {
1579
+ return false;
1580
+ }
1581
+ }
1582
+ function connectToBrokerTarget2(target) {
1583
+ return typeof target === "string" ? net2.connect(target) : net2.connect({ host: target.host, port: target.port });
1584
+ }
1585
+ async function checkSocketConnectable() {
1586
+ return await checkBrokerHealth() === "compatible";
1587
+ }
1588
+ function checkBrokerHealth() {
1589
+ return new Promise((resolve3) => {
1590
+ let target;
1591
+ try {
1592
+ target = getBrokerConnectTarget();
1593
+ } catch {
1594
+ resolve3("unreachable");
1595
+ return;
1596
+ }
1597
+ const socket = connectToBrokerTarget2(target);
1598
+ const requestId = randomUUID3();
1599
+ const expectedStateId = typeof target === "string" ? void 0 : target.stateId;
1600
+ let settled = false;
1601
+ const finish = (health) => {
1602
+ if (settled) {
1603
+ return;
1604
+ }
1605
+ settled = true;
1606
+ clearTimeout(timeout);
1607
+ socket.off("connect", onConnect);
1608
+ socket.off("error", onError);
1609
+ socket.off("data", reader);
1610
+ socket.destroy();
1611
+ resolve3(health);
1612
+ };
1613
+ const onConnect = () => {
1614
+ try {
1615
+ writeMessage(socket, {
1616
+ type: "health",
1617
+ requestId,
1618
+ ...expectedStateId ? { stateId: expectedStateId } : {}
1619
+ });
1620
+ } catch {
1621
+ finish("unreachable");
1622
+ }
1623
+ };
1624
+ const onError = () => finish("unreachable");
1625
+ const reader = createMessageReader((message) => {
1626
+ if (isBrokerHealthOkMessage(message, requestId)) {
1627
+ finish("compatible");
1628
+ return;
1629
+ }
1630
+ if (isPlainDataRecord(message) && message.type === "health_ok" && message.requestId === requestId) {
1631
+ finish("incompatible");
1632
+ return;
1633
+ }
1634
+ finish("unreachable");
1635
+ }, () => finish("unreachable"));
1636
+ socket.on("connect", onConnect);
1637
+ socket.on("error", onError);
1638
+ socket.on("data", reader);
1639
+ const timeout = setTimeout(() => finish("unreachable"), 1e3);
1640
+ });
1641
+ }
1642
+ function acquireSpawnLock() {
1643
+ const maxRetries = 5;
1644
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1645
+ try {
1646
+ writeFileSync2(BROKER_SPAWN_LOCK, `${process.pid}
1647
+ ${Date.now()}
1648
+ `, {
1649
+ flag: "wx",
1650
+ mode: INTERCOM_RUNTIME_FILE_MODE
1651
+ });
1652
+ restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);
1653
+ return true;
1654
+ } catch (error2) {
1655
+ if (!(error2 instanceof Error) || error2.code !== "EEXIST") {
1656
+ throw error2;
1657
+ }
1658
+ if (isSpawnLockStale()) {
1659
+ try {
1660
+ unlinkSync(BROKER_SPAWN_LOCK);
1661
+ } catch {
1662
+ }
1663
+ continue;
1664
+ }
1665
+ return false;
1666
+ }
1667
+ }
1668
+ return false;
1669
+ }
1670
+ function isSpawnLockStale() {
1671
+ if (!existsSync2(BROKER_SPAWN_LOCK)) {
1672
+ return false;
1673
+ }
1674
+ try {
1675
+ const [pidLine = "", createdAtLine = "0"] = readFileSync4(BROKER_SPAWN_LOCK, "utf-8").trim().split("\n");
1676
+ const pid = Number.parseInt(pidLine, 10);
1677
+ const createdAt = Number.parseInt(createdAtLine, 10);
1678
+ const ageMs = Date.now() - createdAt;
1679
+ if (Number.isFinite(pid)) {
1680
+ try {
1681
+ process.kill(pid, 0);
1682
+ } catch {
1683
+ return true;
1684
+ }
1685
+ }
1686
+ return !Number.isFinite(createdAt) || ageMs > 1e4;
1687
+ } catch {
1688
+ return true;
1689
+ }
1690
+ }
1691
+ function releaseSpawnLock() {
1692
+ try {
1693
+ unlinkSync(BROKER_SPAWN_LOCK);
1694
+ } catch {
1695
+ }
1696
+ }
1697
+ async function waitForBroker(timeoutMs = 5e3) {
1698
+ const start = Date.now();
1699
+ while (Date.now() - start < timeoutMs) {
1700
+ if (await checkSocketConnectable()) {
1701
+ return;
1702
+ }
1703
+ await sleep(100);
1704
+ }
1705
+ throw new Error("Broker failed to start within timeout");
1706
+ }
1707
+
1708
+ // config.ts
1709
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
1710
+ import { join as join4, resolve as resolve2 } from "path";
1711
+ import { homedir as homedir2 } from "os";
1712
+ var DEFAULT_ASK_TIMEOUT_MS = 45 * 1e3;
1713
+ var MAX_ASK_TIMEOUT_MS = 120 * 1e3;
1714
+ function validateAskTimeoutMs(value, name = "timeout_ms") {
1715
+ if (!Number.isSafeInteger(value) || typeof value !== "number" || value <= 0) {
1716
+ throw new Error(`${name} must be a positive integer number of milliseconds`);
1717
+ }
1718
+ if (value > MAX_ASK_TIMEOUT_MS) {
1719
+ throw new Error(`${name} must be ${MAX_ASK_TIMEOUT_MS} ms or less; use intercom_send plus intercom_pending for longer-running work`);
1720
+ }
1721
+ return value;
1722
+ }
1723
+ function getAskTimeoutMs() {
1724
+ const raw = process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
1725
+ if (raw === void 0 || raw.trim() === "") {
1726
+ return DEFAULT_ASK_TIMEOUT_MS;
1727
+ }
1728
+ const value = Number(raw);
1729
+ return validateAskTimeoutMs(value, "PI_INTERCOM_ASK_TIMEOUT_MS");
1730
+ }
1731
+ function getConfigPath() {
1732
+ const agentDir = process.env.PI_CODING_AGENT_DIR ? resolve2(process.env.PI_CODING_AGENT_DIR) : join4(homedir2(), ".pi", "agent");
1733
+ return join4(agentDir, "intercom", "config.json");
1734
+ }
1735
+ var defaults = {
1736
+ brokerCommand: "npx",
1737
+ brokerArgs: ["--no-install", "tsx"],
1738
+ confirmSend: false,
1739
+ enabled: true,
1740
+ replyHint: true,
1741
+ inboundForkHandlers: {
1742
+ enabled: true,
1743
+ when: "auto",
1744
+ notify: "summary",
1745
+ triggerParentOnSummary: "auto"
1746
+ }
1747
+ };
1748
+ function loadConfig() {
1749
+ const configPath = getConfigPath();
1750
+ if (!existsSync3(configPath)) {
1751
+ return { ...defaults };
1752
+ }
1753
+ try {
1754
+ const raw = readFileSync5(configPath, "utf-8");
1755
+ const parsed = JSON.parse(raw);
1756
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1757
+ throw new Error("Config must be a JSON object");
1758
+ }
1759
+ const parsedConfig = parsed;
1760
+ const config = { ...defaults };
1761
+ if (Object.hasOwn(parsedConfig, "brokerCommand")) {
1762
+ if (typeof parsedConfig.brokerCommand !== "string") {
1763
+ throw new Error(`"brokerCommand" must be a string`);
1764
+ }
1765
+ const brokerCommand = parsedConfig.brokerCommand.trim();
1766
+ if (!brokerCommand) {
1767
+ throw new Error(`"brokerCommand" must not be empty`);
1768
+ }
1769
+ config.brokerCommand = brokerCommand;
1770
+ }
1771
+ if (Object.hasOwn(parsedConfig, "brokerArgs")) {
1772
+ if (!Array.isArray(parsedConfig.brokerArgs)) {
1773
+ throw new Error(`"brokerArgs" must be an array`);
1774
+ }
1775
+ const brokerArgs = [];
1776
+ for (const arg of parsedConfig.brokerArgs) {
1777
+ if (typeof arg !== "string") {
1778
+ throw new Error(`"brokerArgs" items must be strings`);
1779
+ }
1780
+ brokerArgs.push(arg);
1781
+ }
1782
+ config.brokerArgs = brokerArgs;
1783
+ }
1784
+ if (Object.hasOwn(parsedConfig, "confirmSend")) {
1785
+ if (typeof parsedConfig.confirmSend !== "boolean") {
1786
+ throw new Error(`"confirmSend" must be a boolean`);
1787
+ }
1788
+ config.confirmSend = parsedConfig.confirmSend;
1789
+ }
1790
+ if (Object.hasOwn(parsedConfig, "enabled")) {
1791
+ if (typeof parsedConfig.enabled !== "boolean") {
1792
+ throw new Error(`"enabled" must be a boolean`);
1793
+ }
1794
+ config.enabled = parsedConfig.enabled;
1795
+ }
1796
+ if (Object.hasOwn(parsedConfig, "replyHint")) {
1797
+ if (typeof parsedConfig.replyHint !== "boolean") {
1798
+ throw new Error(`"replyHint" must be a boolean`);
1799
+ }
1800
+ config.replyHint = parsedConfig.replyHint;
1801
+ }
1802
+ if (Object.hasOwn(parsedConfig, "status")) {
1803
+ if (typeof parsedConfig.status !== "string") {
1804
+ throw new Error(`"status" must be a string`);
1805
+ }
1806
+ config.status = parsedConfig.status;
1807
+ }
1808
+ if (Object.hasOwn(parsedConfig, "inboundForkHandlers")) {
1809
+ if (typeof parsedConfig.inboundForkHandlers !== "object" || parsedConfig.inboundForkHandlers === null || Array.isArray(parsedConfig.inboundForkHandlers)) {
1810
+ throw new Error(`"inboundForkHandlers" must be an object`);
1811
+ }
1812
+ const forkConfig = parsedConfig.inboundForkHandlers;
1813
+ config.inboundForkHandlers = { ...defaults.inboundForkHandlers };
1814
+ if (Object.hasOwn(forkConfig, "enabled")) {
1815
+ if (typeof forkConfig.enabled !== "boolean") throw new Error(`"inboundForkHandlers.enabled" must be a boolean`);
1816
+ config.inboundForkHandlers.enabled = forkConfig.enabled;
1817
+ }
1818
+ if (Object.hasOwn(forkConfig, "when")) {
1819
+ if (forkConfig.when !== "auto" && forkConfig.when !== "busy" && forkConfig.when !== "always") throw new Error(`"inboundForkHandlers.when" must be "auto", "busy", or "always"`);
1820
+ config.inboundForkHandlers.when = forkConfig.when;
1821
+ }
1822
+ if (Object.hasOwn(forkConfig, "notify")) {
1823
+ if (forkConfig.notify !== "ack-and-summary" && forkConfig.notify !== "summary" && forkConfig.notify !== "none") throw new Error(`"inboundForkHandlers.notify" must be "ack-and-summary", "summary", or "none"`);
1824
+ config.inboundForkHandlers.notify = forkConfig.notify;
1825
+ }
1826
+ if (Object.hasOwn(forkConfig, "piCommand")) {
1827
+ if (typeof forkConfig.piCommand !== "string") throw new Error(`"inboundForkHandlers.piCommand" must be a string`);
1828
+ const piCommand = forkConfig.piCommand.trim();
1829
+ if (piCommand) config.inboundForkHandlers.piCommand = piCommand;
1830
+ }
1831
+ if (Object.hasOwn(forkConfig, "triggerParentOnSummary")) {
1832
+ const triggerParentOnSummary = forkConfig.triggerParentOnSummary;
1833
+ if (typeof triggerParentOnSummary !== "boolean" && triggerParentOnSummary !== "auto") {
1834
+ throw new Error(`"inboundForkHandlers.triggerParentOnSummary" must be a boolean or "auto"`);
1835
+ }
1836
+ config.inboundForkHandlers.triggerParentOnSummary = triggerParentOnSummary;
1837
+ }
1838
+ }
1839
+ return config;
1840
+ } catch (error2) {
1841
+ console.error(`Failed to load intercom config at ${configPath}:`, error2);
1842
+ return { ...defaults };
1843
+ }
1844
+ }
1845
+
1846
+ // claude/inbox.ts
1847
+ import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
1848
+ import { dirname as dirname3, join as join5 } from "node:path";
1849
+ function toInboxEntry(from, message, now = Date.now()) {
1850
+ return {
1851
+ ts: now,
1852
+ fromId: from.id,
1853
+ ...from.name ? { fromName: from.name } : {},
1854
+ messageId: message.id,
1855
+ ...message.replyTo ? { replyTo: message.replyTo } : {},
1856
+ expectsReply: Boolean(message.expectsReply),
1857
+ text: message.content.text
1858
+ };
1859
+ }
1860
+ function appendInboxMessage(path, from, message) {
1861
+ mkdirSync3(dirname3(path), { recursive: true });
1862
+ appendFileSync(path, `${JSON.stringify(toInboxEntry(from, message))}
1863
+ `, {
1864
+ encoding: "utf-8",
1865
+ mode: INTERCOM_RUNTIME_FILE_MODE
1866
+ });
1867
+ }
1868
+
1869
+ // claude/team.ts
1870
+ import { readFile } from "node:fs/promises";
1871
+ import { join as join6 } from "node:path";
1872
+ import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
1873
+ var LEGACY_LIVE_STATES = /* @__PURE__ */ new Set(["provisioning", "running", "idle", "needs_attention", "stopping"]);
1874
+ var BOSS_NONTERMINAL_STATES = new Set(
1875
+ PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
1876
+ );
1877
+ function stringValue(value) {
1878
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1879
+ }
1880
+ function exactStringValue(value) {
1881
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1882
+ }
1883
+ function positiveInteger(value) {
1884
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
1885
+ }
1886
+ function positiveIntegerString(value) {
1887
+ if (typeof value !== "string" || !/^[1-9]\d*$/.test(value)) return void 0;
1888
+ const parsed = Number(value);
1889
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
1890
+ }
1891
+ function connectedTo(sessions, target) {
1892
+ const normalized = target.toLowerCase();
1893
+ return sessions.some((session) => session.id === target || session.name?.toLowerCase() === normalized);
1894
+ }
1895
+ async function readWorkers(agentDir) {
1896
+ try {
1897
+ const parsed = JSON.parse(await readFile(join6(agentDir, "intercom", "orchestrator", "workers.json"), "utf8"));
1898
+ return Array.isArray(parsed.workers) ? parsed.workers : [];
1899
+ } catch {
1900
+ return [];
1901
+ }
1902
+ }
1903
+ function bossSessionHasExactAuthority(session) {
1904
+ const binding = session.boss?.binding;
1905
+ const principal = session.boss?.principal;
1906
+ const liveWorker = session.boss?.liveWorker;
1907
+ const liveIdentity = liveWorker?.identity;
1908
+ return binding !== void 0 && principal !== void 0 && liveWorker !== void 0 && liveIdentity !== void 0 && binding.sessionId === session.id && binding.state === "active" && principal.principalClass === "boss-private" && principal.principalId === session.id && principal.state === "active" && principal.bossRunId === binding.bossRunId && principal.participantId === binding.participantId && principal.role === binding.role && principal.bindingEpoch === binding.bindingEpoch && principal.assignedManagerParticipantId === binding.assignedManagerParticipantId && (binding.role === "worker" || binding.role === "scout") === (binding.assignedManagerParticipantId !== void 0) && principal.role === "manager" === Array.isArray(principal.assignedParticipantIds) && (principal.role === "manager" || principal.assignedParticipantIds === void 0) && liveIdentity.workerId === session.id && "bossRunId" in liveIdentity && liveIdentity.bossRunId === binding.bossRunId && liveIdentity.participantId === binding.participantId && liveIdentity.bindingEpoch === binding.bindingEpoch && BOSS_NONTERMINAL_STATES.has(liveWorker.state);
1909
+ }
1910
+ function bossWorkerMatchesSession(worker, session) {
1911
+ const binding = session.boss?.binding;
1912
+ const liveWorker = session.boss?.liveWorker;
1913
+ const liveIdentity = liveWorker?.identity;
1914
+ const id = exactStringValue(worker.id);
1915
+ const target = exactStringValue(worker.intercomTarget);
1916
+ const storedSessionId = exactStringValue(worker.sessionId) ?? target;
1917
+ return bossSessionHasExactAuthority(session) && binding !== void 0 && liveWorker !== void 0 && liveIdentity !== void 0 && worker.owned === true && id !== void 0 && target === id && storedSessionId === id && session.id === id && binding.sessionId === session.id && binding.state === "active" && BOSS_NONTERMINAL_STATES.has(liveWorker.state) && exactStringValue(worker.state) === liveWorker.state && exactStringValue(worker.bossRunId) === binding.bossRunId && exactStringValue(worker.participantId) === binding.participantId && positiveInteger(worker.bindingEpoch) === binding.bindingEpoch && exactStringValue(worker.role) === binding.role && liveIdentity.workerId === id && liveIdentity.workerId === session.id && liveIdentity.workerIncarnationId === exactStringValue(worker.workerIncarnationId) && liveIdentity.workerGeneration === positiveInteger(worker.workerGeneration) && liveIdentity.bossRunId === binding.bossRunId && liveIdentity.participantId === binding.participantId && liveIdentity.bindingEpoch === binding.bindingEpoch;
1918
+ }
1919
+ function isReciprocallyAssignedToManager(workerSession, managerSession) {
1920
+ const workerBinding = workerSession.boss?.binding;
1921
+ const workerPrincipal = workerSession.boss?.principal;
1922
+ const managerBinding = managerSession.boss?.binding;
1923
+ const managerPrincipal = managerSession.boss?.principal;
1924
+ return bossSessionHasExactAuthority(workerSession) && bossSessionHasExactAuthority(managerSession) && (workerBinding?.role === "worker" || workerBinding?.role === "scout") && workerPrincipal?.role === workerBinding.role && managerBinding?.role === "manager" && managerPrincipal?.role === "manager" && workerBinding.bossRunId === managerBinding.bossRunId && workerBinding.assignedManagerParticipantId === managerBinding.participantId && workerPrincipal.assignedManagerParticipantId === managerPrincipal.participantId && managerPrincipal.assignedParticipantIds?.includes(workerBinding.participantId) === true;
1925
+ }
1926
+ function currentBossWorker(workers, self, env) {
1927
+ const binding = self.boss?.binding;
1928
+ const liveIdentity = self.boss?.liveWorker.identity;
1929
+ if (!binding) return void 0;
1930
+ const workerId = exactStringValue(env.AGENT_INTERCOM_WORKER_ID);
1931
+ const incarnationId = exactStringValue(env.AGENT_INTERCOM_WORKER_INCARNATION_ID);
1932
+ const deprecatedIncarnationId = exactStringValue(env.AGENT_INTERCOM_RUN_ID);
1933
+ const workerGeneration = positiveIntegerString(env.AGENT_INTERCOM_WORKER_GENERATION);
1934
+ const bindingEpoch = positiveIntegerString(env.AGENT_INTERCOM_BINDING_EPOCH);
1935
+ if (workerId === void 0 || incarnationId === void 0 || deprecatedIncarnationId !== void 0 && deprecatedIncarnationId !== incarnationId || workerGeneration === void 0 || bindingEpoch === void 0 || liveIdentity === void 0 || liveIdentity.workerId !== workerId || liveIdentity.workerIncarnationId !== incarnationId || liveIdentity.workerGeneration !== workerGeneration || liveIdentity.bossRunId !== binding.bossRunId || liveIdentity.participantId !== binding.participantId || liveIdentity.bindingEpoch !== binding.bindingEpoch || exactStringValue(env.AGENT_INTERCOM_BOSS_RUN_ID) !== binding.bossRunId || exactStringValue(env.AGENT_INTERCOM_PARTICIPANT_ID) !== binding.participantId || bindingEpoch !== binding.bindingEpoch) return void 0;
1936
+ return workers.find(
1937
+ (worker) => exactStringValue(worker.id) === workerId && exactStringValue(worker.workerIncarnationId) === incarnationId && positiveInteger(worker.workerGeneration) === workerGeneration && bossWorkerMatchesSession(worker, self)
1938
+ );
1939
+ }
1940
+ function teamMember(worker, session) {
1941
+ const id = stringValue(worker.id);
1942
+ return {
1943
+ id,
1944
+ target: id,
1945
+ ...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
1946
+ ...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
1947
+ ...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
1948
+ connected: session.id === id
1949
+ };
1950
+ }
1951
+ function resolveLegacyTeam(input, workers, env) {
1952
+ const workerId = stringValue(env.AGENT_INTERCOM_WORKER_ID);
1953
+ const runId = stringValue(env.AGENT_INTERCOM_RUN_ID);
1954
+ const current = workerId ? workers.find(
1955
+ (worker) => stringValue(worker.id) === workerId && (!runId || stringValue(worker.runId) === runId)
1956
+ ) : void 0;
1957
+ const managerTarget = stringValue(current?.managerSessionId) ?? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
1958
+ const teamId = managerTarget ?? input.selfId;
1959
+ const coworkers = workers.filter((worker) => worker.owned === true).filter((worker) => stringValue(worker.managerSessionId) === teamId).filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? "")).filter((worker) => stringValue(worker.id) !== workerId).map((worker) => {
1960
+ const id = stringValue(worker.id);
1961
+ if (!id) return void 0;
1962
+ const target = stringValue(worker.intercomTarget) ?? id;
1963
+ return {
1964
+ id,
1965
+ target,
1966
+ ...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
1967
+ ...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
1968
+ ...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
1969
+ connected: connectedTo(input.sessions, target)
1970
+ };
1971
+ }).filter((member) => Boolean(member));
1972
+ return {
1973
+ teamId,
1974
+ self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: !managerTarget },
1975
+ manager: managerTarget ? { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) } : { target: input.selfId, connected: true },
1976
+ coworkers
1977
+ };
1978
+ }
1979
+ async function resolveIntercomTeam(input) {
1980
+ const env = input.env ?? process.env;
1981
+ const workers = await readWorkers(input.agentDir ?? getAgentDirPath());
1982
+ const selfSession = input.sessions.find((session) => session.id === input.selfId);
1983
+ if (!selfSession?.boss) return resolveLegacyTeam(input, workers, env);
1984
+ const current = currentBossWorker(workers, selfSession, env);
1985
+ const workerId = stringValue(env.AGENT_INTERCOM_WORKER_ID);
1986
+ if (!current) {
1987
+ return {
1988
+ teamId: selfSession.boss.binding.bossRunId,
1989
+ self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
1990
+ coworkers: []
1991
+ };
1992
+ }
1993
+ const currentRole = selfSession.boss.binding.role;
1994
+ const selfIsManager = currentRole === "manager";
1995
+ const configuredManagerTarget = selfIsManager ? input.selfId : exactStringValue(current.managerSessionId);
1996
+ const managerSession = configuredManagerTarget === void 0 ? void 0 : input.sessions.find((session) => session.id === configuredManagerTarget);
1997
+ const managerWorker = managerSession === void 0 ? void 0 : workers.find((worker) => exactStringValue(worker.id) === configuredManagerTarget && bossWorkerMatchesSession(worker, managerSession));
1998
+ const managerTarget = selfIsManager ? input.selfId : managerWorker !== void 0 && managerSession?.boss?.binding.bossRunId === selfSession.boss.binding.bossRunId && isReciprocallyAssignedToManager(selfSession, managerSession) ? configuredManagerTarget : void 0;
1999
+ if (managerTarget === void 0) {
2000
+ return {
2001
+ teamId: selfSession.boss.binding.bossRunId,
2002
+ self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
2003
+ coworkers: []
2004
+ };
2005
+ }
2006
+ const coworkers = workers.filter((worker) => exactStringValue(worker.id) !== workerId).filter((worker) => exactStringValue(worker.managerSessionId) === managerTarget).map((worker) => {
2007
+ const id = exactStringValue(worker.id);
2008
+ if (!id) return void 0;
2009
+ const session = input.sessions.find((candidate) => candidate.id === id);
2010
+ if (session === void 0 || !bossWorkerMatchesSession(worker, session) || session.boss?.binding.bossRunId !== selfSession.boss.binding.bossRunId || managerSession === void 0 || !isReciprocallyAssignedToManager(session, managerSession)) return void 0;
2011
+ return teamMember(worker, session);
2012
+ }).filter((member) => Boolean(member));
2013
+ return {
2014
+ teamId: selfSession.boss.binding.bossRunId,
2015
+ self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: selfIsManager },
2016
+ manager: { target: managerTarget, connected: true },
2017
+ coworkers
2018
+ };
2019
+ }
2020
+ function formatIntercomTeam(team) {
2021
+ const lines = [
2022
+ `Manager: ${team.manager ? `${team.manager.target} [${team.manager.connected ? "connected" : "not connected"}]` : "unknown"}`,
2023
+ `You: ${team.self.id}${team.self.isManager ? " [manager]" : ""}`
2024
+ ];
2025
+ if (!team.coworkers.length) {
2026
+ lines.push("Coworkers: none");
2027
+ } else {
2028
+ lines.push("Coworkers:");
2029
+ for (const coworker of team.coworkers) {
2030
+ const metadata = [coworker.harness, coworker.role, coworker.state].filter(Boolean).join(", ");
2031
+ lines.push(`- ${coworker.id} target=${coworker.target}${metadata ? ` (${metadata})` : ""} [${coworker.connected ? "connected" : "not connected"}]`);
2032
+ }
2033
+ }
2034
+ return lines.join("\n");
2035
+ }
2036
+
2037
+ // claude/runtime.ts
2038
+ function matchesPendingSender(entry, to) {
2039
+ return entry.from.id === to || entry.from.name?.toLowerCase() === to.toLowerCase() || entry.from.id.startsWith(to);
2040
+ }
2041
+ function selectPendingAsk(entries, to, which) {
2042
+ const sorted = [...entries].sort((a, b) => a.receivedAt - b.receivedAt);
2043
+ if (sorted.length === 0) throw new Error("No matching pending ask. Call intercom_pending to inspect unresolved asks.");
2044
+ const matches = to ? sorted.filter((entry) => matchesPendingSender(entry, to)) : sorted;
2045
+ if (matches.length === 0) throw new Error(`No pending ask from "${to}".`);
2046
+ if (matches.length === 1) return matches[0];
2047
+ if (!to && new Set(matches.map((entry) => entry.from.id)).size > 1) {
2048
+ throw new Error("Multiple pending asks \u2014 specify `to` using a sender from intercom_pending.");
2049
+ }
2050
+ if (!which) {
2051
+ const sender = to ? ` from "${to}"` : "";
2052
+ throw new Error(`Multiple pending asks${sender} \u2014 specify \`which\` as \`oldest\` or \`latest\`.`);
2053
+ }
2054
+ return which === "oldest" ? matches[0] : matches[matches.length - 1];
2055
+ }
2056
+ function pendingSelector(entries, entry) {
2057
+ const sameSender = entries.filter((candidate) => candidate.from.id === entry.from.id);
2058
+ if (sameSender.length <= 1) return void 0;
2059
+ const index = sameSender.findIndex((candidate) => candidate.message.id === entry.message.id);
2060
+ if (index === 0) return "oldest";
2061
+ if (index === sameSender.length - 1) return "latest";
2062
+ return "queued";
2063
+ }
2064
+ function publicPendingEntry(entry, selector) {
2065
+ return {
2066
+ from: {
2067
+ id: entry.from.id,
2068
+ name: entry.from.name,
2069
+ origin: entry.from.origin ?? "local",
2070
+ ...entry.from.remoteHostId ? { remote_host_id: entry.from.remoteHostId } : {},
2071
+ ...entry.from.parentSessionId ? { parent_session_id: entry.from.parentSessionId } : {},
2072
+ ...entry.from.generation ? { generation: entry.from.generation } : {}
2073
+ },
2074
+ received_at: entry.receivedAt,
2075
+ read: entry.read,
2076
+ text: entry.message.content.text,
2077
+ attachments: entry.message.content.attachments,
2078
+ expects_reply: entry.message.expectsReply,
2079
+ ...selector ? { selector } : {}
2080
+ };
2081
+ }
2082
+ function shortHash(value) {
2083
+ return createHash2("sha256").update(value).digest("hex").slice(0, 8);
2084
+ }
2085
+ function buildClaudeRuntimeIdentity(env = process.env, cwd = env.PWD || processCwd(), pid = process.pid) {
2086
+ const sessionId = env.CLAUDE_INTERCOM_SESSION_ID?.trim() || env.CLAUDE_PEER_ID?.trim() || `claude-${pid}-${shortHash(cwd)}`;
2087
+ const cwdName = basename(cwd) || "workspace";
2088
+ const name = env.CLAUDE_INTERCOM_NAME?.trim() || env.CLAUDE_PEER_NAME?.trim() || `claude-${cwdName}-${pid}`;
2089
+ return {
2090
+ sessionId,
2091
+ name,
2092
+ cwd,
2093
+ model: env.CLAUDE_INTERCOM_MODEL?.trim() || env.CLAUDE_MODEL?.trim() || "claude",
2094
+ startedAt: Date.now()
2095
+ };
2096
+ }
2097
+ function formatAttachments(attachments) {
2098
+ if (!attachments?.length) return "";
2099
+ return attachments.map((attachment) => {
2100
+ if (attachment.language) {
2101
+ return `
2102
+
2103
+ ---
2104
+ Attachment: ${attachment.name}
2105
+ ~~~${attachment.language}
2106
+ ${attachment.content}
2107
+ ~~~`;
2108
+ }
2109
+ return `
2110
+
2111
+ ---
2112
+ Attachment: ${attachment.name}
2113
+ ${attachment.content}`;
2114
+ }).join("");
2115
+ }
2116
+ function resolveSessionTarget(sessions, nameOrId) {
2117
+ const byId = sessions.find((session) => session.id === nameOrId);
2118
+ if (byId) return byId.id;
2119
+ const lowerName = nameOrId.toLowerCase();
2120
+ const byName = sessions.filter((session) => session.name?.toLowerCase() === lowerName);
2121
+ if (byName.length > 1) {
2122
+ throw new Error(`Multiple sessions named "${nameOrId}" are connected. Use the session ID instead.`);
2123
+ }
2124
+ if (byName[0]) return byName[0].id;
2125
+ if (nameOrId.length >= 4) {
2126
+ const byPrefix = sessions.filter((session) => session.id.startsWith(nameOrId));
2127
+ if (byPrefix.length > 1) {
2128
+ throw new Error(`Multiple sessions match the ID prefix "${nameOrId}". Use the full session ID or a unique name.`);
2129
+ }
2130
+ if (byPrefix[0]) return byPrefix[0].id;
2131
+ }
2132
+ return null;
2133
+ }
2134
+ function formatSessionDisplay(session) {
2135
+ const name = session.name || session.id;
2136
+ return session.origin === "remote" ? `${name} [remote:${session.remoteHostId || "unknown-host"}]` : name;
2137
+ }
2138
+ function formatSessionList(sessions, currentSessionId, currentCwd) {
2139
+ if (!sessions.length) return "No intercom sessions connected.";
2140
+ return sessions.map((session) => {
2141
+ const tags = [
2142
+ session.id === currentSessionId ? "self" : void 0,
2143
+ session.cwd === currentCwd ? "same cwd" : void 0,
2144
+ session.status
2145
+ ].filter((tag) => Boolean(tag));
2146
+ const suffix = tags.length ? ` [${tags.join(", ")}]` : "";
2147
+ return `- ${formatSessionDisplay(session)} (${session.id.slice(0, 8)}) - ${session.cwd} (${session.model})${suffix}`;
2148
+ }).join("\n");
2149
+ }
2150
+ function detectGitRoot(cwd) {
2151
+ const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
2152
+ cwd,
2153
+ encoding: "utf8",
2154
+ stdio: ["ignore", "pipe", "ignore"]
2155
+ });
2156
+ if (result.status !== 0) return null;
2157
+ return result.stdout.trim() || null;
2158
+ }
2159
+ function textResult(text, structuredContent, isError = false) {
2160
+ return {
2161
+ content: [{ type: "text", text }],
2162
+ ...structuredContent ? { structuredContent } : {},
2163
+ ...isError ? { isError: true } : {}
2164
+ };
2165
+ }
2166
+ var ClaudeIntercomRuntime = class {
2167
+ client = null;
2168
+ connectPromise = null;
2169
+ reconnectTimer = null;
2170
+ reconnectAttempt = 0;
2171
+ reconnectEnabled = true;
2172
+ identity;
2173
+ unread = [];
2174
+ unresolvedAsks = /* @__PURE__ */ new Map();
2175
+ replyWaiters = /* @__PURE__ */ new Map();
2176
+ clientFactory;
2177
+ capturedScopeId;
2178
+ prepareConnection;
2179
+ reconnectDelays;
2180
+ constructor(identity = buildClaudeRuntimeIdentity(), options = {}) {
2181
+ this.identity = identity;
2182
+ this.capturedScopeId = intercomScopeIdFromEnvForRegistration(process.env);
2183
+ const scopeSnapshot = this.capturedScopeId ? { AGENT_INTERCOM_SCOPE_ID: this.capturedScopeId } : {};
2184
+ this.clientFactory = options.clientFactory ?? (() => new IntercomClient({ env: scopeSnapshot }));
2185
+ this.prepareConnection = options.prepareConnection ?? (async () => {
2186
+ const config = loadConfig();
2187
+ if (!config.enabled) throw new Error("Intercom disabled");
2188
+ await spawnBrokerIfNeeded(config.brokerCommand, config.brokerArgs);
2189
+ });
2190
+ this.reconnectDelays = options.reconnectDelays?.length ? options.reconnectDelays : [250, 500, 1e3, 2e3, 5e3];
2191
+ }
2192
+ getIdentity() {
2193
+ return this.identity;
2194
+ }
2195
+ async connect() {
2196
+ this.reconnectEnabled = true;
2197
+ this.clearReconnectTimer();
2198
+ if (this.client?.isConnected()) return this.client;
2199
+ if (this.connectPromise) return this.connectPromise;
2200
+ this.connectPromise = this.connectOnce();
2201
+ try {
2202
+ return await this.connectPromise;
2203
+ } finally {
2204
+ this.connectPromise = null;
2205
+ }
2206
+ }
2207
+ async connectOnce() {
2208
+ await this.prepareConnection();
2209
+ const client = this.clientFactory();
2210
+ client.on("message", (from, message, deliveryId) => {
2211
+ this.handleIncomingMessage(from, message);
2212
+ client.acknowledgeMessage(deliveryId);
2213
+ });
2214
+ client.on("disconnected", (error2) => {
2215
+ for (const waiter of this.replyWaiters.values()) {
2216
+ clearTimeout(waiter.timeout);
2217
+ waiter.cleanup?.();
2218
+ waiter.reject(new Error(`Disconnected while waiting for reply: ${error2.message}`, { cause: error2 }));
2219
+ }
2220
+ this.replyWaiters.clear();
2221
+ if (this.client === client) this.client = null;
2222
+ this.scheduleReconnect();
2223
+ });
2224
+ await client.connect({
2225
+ name: this.identity.name,
2226
+ cwd: this.identity.cwd,
2227
+ model: this.identity.model,
2228
+ pid: process.pid,
2229
+ startedAt: this.identity.startedAt,
2230
+ lastActivity: Date.now(),
2231
+ status: "idle"
2232
+ }, this.identity.sessionId);
2233
+ this.client = client;
2234
+ this.reconnectAttempt = 0;
2235
+ return client;
2236
+ }
2237
+ scheduleReconnect() {
2238
+ if (!this.reconnectEnabled || this.reconnectTimer) return;
2239
+ const delay = this.reconnectDelays[Math.min(this.reconnectAttempt, this.reconnectDelays.length - 1)];
2240
+ this.reconnectTimer = setTimeout(() => {
2241
+ this.reconnectTimer = null;
2242
+ void this.connect().then((client) => {
2243
+ if (!client.isConnected()) {
2244
+ this.reconnectAttempt += 1;
2245
+ this.scheduleReconnect();
2246
+ }
2247
+ }).catch(() => {
2248
+ this.reconnectAttempt += 1;
2249
+ this.scheduleReconnect();
2250
+ });
2251
+ }, delay);
2252
+ this.reconnectTimer.unref?.();
2253
+ }
2254
+ clearReconnectTimer() {
2255
+ if (!this.reconnectTimer) return;
2256
+ clearTimeout(this.reconnectTimer);
2257
+ this.reconnectTimer = null;
2258
+ }
2259
+ async disconnect() {
2260
+ this.reconnectEnabled = false;
2261
+ this.clearReconnectTimer();
2262
+ if (this.connectPromise) {
2263
+ try {
2264
+ await this.connectPromise;
2265
+ } catch {
2266
+ }
2267
+ }
2268
+ const client = this.client;
2269
+ this.client = null;
2270
+ if (client) await client.disconnect();
2271
+ }
2272
+ handleIncomingMessage(from, message) {
2273
+ const waiter = this.replyWaiters.get(message.replyTo ?? "");
2274
+ if (waiter) {
2275
+ const senderTarget = from.name || from.id;
2276
+ const fromMatches = senderTarget.toLowerCase() === waiter.from.toLowerCase() || from.id === waiter.from;
2277
+ if (fromMatches) {
2278
+ this.replyWaiters.delete(waiter.replyTo);
2279
+ clearTimeout(waiter.timeout);
2280
+ waiter.cleanup?.();
2281
+ waiter.resolve(message);
2282
+ return;
2283
+ }
2284
+ }
2285
+ const entry = { from, message, receivedAt: Date.now(), read: false };
2286
+ this.unread.push(entry);
2287
+ if (message.expectsReply) {
2288
+ this.unresolvedAsks.set(message.id, entry);
2289
+ }
2290
+ const inboxPath = process.env.CLAUDE_INTERCOM_INBOX;
2291
+ if (inboxPath) {
2292
+ try {
2293
+ appendInboxMessage(inboxPath, from, message);
2294
+ } catch {
2295
+ }
2296
+ }
2297
+ }
2298
+ waitForReply(from, replyTo, timeoutMs = getAskTimeoutMs(), signal) {
2299
+ return new Promise((resolve3, reject) => {
2300
+ if (signal?.aborted) {
2301
+ reject(new Error("intercom_ask cancelled"));
2302
+ return;
2303
+ }
2304
+ let timeout;
2305
+ const cleanup = () => {
2306
+ clearTimeout(timeout);
2307
+ signal?.removeEventListener("abort", onAbort);
2308
+ };
2309
+ const onAbort = () => {
2310
+ this.replyWaiters.delete(replyTo);
2311
+ cleanup();
2312
+ void this.client?.cancelAsk(replyTo);
2313
+ reject(new Error("intercom_ask cancelled"));
2314
+ };
2315
+ timeout = setTimeout(() => {
2316
+ this.replyWaiters.delete(replyTo);
2317
+ void this.client?.deferAsk(replyTo);
2318
+ signal?.removeEventListener("abort", onAbort);
2319
+ reject(new Error(`No reply from "${from}" within ${Math.round(timeoutMs / 1e3)} seconds`));
2320
+ }, timeoutMs);
2321
+ signal?.addEventListener("abort", onAbort, { once: true });
2322
+ this.replyWaiters.set(replyTo, { from, replyTo, resolve: resolve3, reject, timeout, cleanup });
2323
+ });
2324
+ }
2325
+ async resolveTarget(to) {
2326
+ const client = await this.connect();
2327
+ const sessions = await client.listSessions();
2328
+ return resolveSessionTarget(sessions, to) ?? to;
2329
+ }
2330
+ async whoami() {
2331
+ const client = await this.connect();
2332
+ const sessionId = client.sessionId ?? this.identity.sessionId;
2333
+ return textResult(
2334
+ `session_id: ${sessionId}
2335
+ name: ${this.identity.name}
2336
+ cwd: ${this.identity.cwd}`,
2337
+ { session_id: sessionId, name: this.identity.name, cwd: this.identity.cwd, model: this.identity.model }
2338
+ );
2339
+ }
2340
+ async team() {
2341
+ const client = await this.connect();
2342
+ const sessions = await client.listSessions();
2343
+ const team = await resolveIntercomTeam({ selfId: client.sessionId ?? this.identity.sessionId, sessions });
2344
+ return textResult(formatIntercomTeam(team), team);
2345
+ }
2346
+ async status() {
2347
+ const client = await this.connect();
2348
+ const sessions = await client.listSessions();
2349
+ return textResult(
2350
+ `Connected: ${client.isConnected() ? "Yes" : "No"}
2351
+ Session ID: ${client.sessionId ?? "unknown"}
2352
+ Active sessions: ${sessions.length}
2353
+ Unread messages: ${this.unread.filter((entry) => !entry.read).length}
2354
+ Pending asks: ${this.unresolvedAsks.size}`,
2355
+ {
2356
+ connected: client.isConnected(),
2357
+ session_id: client.sessionId,
2358
+ active_sessions: sessions.length,
2359
+ unread_messages: this.unread.filter((entry) => !entry.read).length,
2360
+ pending_asks: this.unresolvedAsks.size
2361
+ }
2362
+ );
2363
+ }
2364
+ async list(scope = "machine", includeSelf = false) {
2365
+ const client = await this.connect();
2366
+ let sessions = await client.listSessions();
2367
+ if (scope === "directory") {
2368
+ sessions = sessions.filter((session) => session.cwd === this.identity.cwd);
2369
+ } else if (scope === "repo") {
2370
+ const currentRoot = detectGitRoot(this.identity.cwd);
2371
+ sessions = currentRoot ? sessions.filter((session) => detectGitRoot(session.cwd) === currentRoot) : [];
2372
+ }
2373
+ if (!includeSelf) {
2374
+ sessions = sessions.filter((session) => session.id !== client.sessionId);
2375
+ }
2376
+ return textResult(formatSessionList(sessions, client.sessionId, this.identity.cwd), { sessions });
2377
+ }
2378
+ async setSummary(summary) {
2379
+ const client = await this.connect();
2380
+ client.updatePresence({ status: summary.trim() || "idle" });
2381
+ return textResult("Summary updated.", { ok: true, summary });
2382
+ }
2383
+ async send(to, message, attachments, replyTo) {
2384
+ const client = await this.connect();
2385
+ const sendTo = await this.resolveTarget(to);
2386
+ const result = await client.send(sendTo, { text: message, attachments, replyTo });
2387
+ if (!result.delivered) {
2388
+ return textResult(`Message to "${to}" was not delivered: ${result.reason ?? "Session may not exist or has disconnected."}`, { ok: false, message_id: result.id, reason: result.reason }, true);
2389
+ }
2390
+ if (replyTo) this.unresolvedAsks.delete(replyTo);
2391
+ return textResult(`Message sent to ${to}.`, { ok: true, message_id: result.id, to });
2392
+ }
2393
+ async ask(to, message, attachments, timeoutMs = getAskTimeoutMs(), signal) {
2394
+ const client = await this.connect();
2395
+ const sendTo = await this.resolveTarget(to);
2396
+ const questionId = randomUUID4();
2397
+ const replyPromise = this.waitForReply(sendTo, questionId, timeoutMs, signal);
2398
+ void replyPromise.catch(() => void 0);
2399
+ try {
2400
+ const result = await client.send(sendTo, {
2401
+ messageId: questionId,
2402
+ text: message,
2403
+ attachments,
2404
+ expectsReply: true
2405
+ });
2406
+ if (!result.delivered) {
2407
+ this.replyWaiters.get(questionId)?.reject(new Error(result.reason ?? "Session may not exist or has disconnected."));
2408
+ this.replyWaiters.delete(questionId);
2409
+ void client.cancelAsk(questionId);
2410
+ return textResult(`Message to "${to}" was not delivered: ${result.reason ?? "Session may not exist or has disconnected."}`, { ok: false, message_id: result.id, reason: result.reason }, true);
2411
+ }
2412
+ const reply = await replyPromise;
2413
+ const replyText = `${reply.content.text}${formatAttachments(reply.content.attachments)}`;
2414
+ return textResult(`Reply from ${to}:
2415
+ ${replyText}`, { ok: true, message_id: result.id, reply });
2416
+ } catch (error2) {
2417
+ void client.cancelAsk(questionId);
2418
+ return textResult(error2 instanceof Error ? error2.message : String(error2), { ok: false }, true);
2419
+ }
2420
+ }
2421
+ async pending(markRead = false) {
2422
+ const unreadMessages = this.unread.filter((entry) => !entry.read);
2423
+ if (markRead) {
2424
+ for (const entry of unreadMessages) entry.read = true;
2425
+ }
2426
+ const pendingAsks = Array.from(this.unresolvedAsks.values()).sort((a, b) => a.receivedAt - b.receivedAt);
2427
+ const lines = [
2428
+ unreadMessages.length ? unreadMessages.map((entry) => `- ${formatSessionDisplay(entry.from)}: ${entry.message.content.text}${formatAttachments(entry.message.content.attachments)}`).join("\n") : "No unread messages.",
2429
+ pendingAsks.length ? `
2430
+ Pending asks:
2431
+ ${pendingAsks.map((entry) => {
2432
+ const selector = pendingSelector(pendingAsks, entry);
2433
+ return `- ${formatSessionDisplay(entry.from)}${selector ? ` [${selector}]` : ""}: ${entry.message.content.text}`;
2434
+ }).join("\n")}` : ""
2435
+ ].filter(Boolean);
2436
+ return textResult(lines.join("\n"), {
2437
+ unread_messages: unreadMessages.map((entry) => publicPendingEntry(entry)),
2438
+ pending_asks: pendingAsks.map((entry) => publicPendingEntry(entry, pendingSelector(pendingAsks, entry)))
2439
+ });
2440
+ }
2441
+ async reply(message, to, which) {
2442
+ let target;
2443
+ try {
2444
+ target = selectPendingAsk(Array.from(this.unresolvedAsks.values()), to, which);
2445
+ } catch (error2) {
2446
+ return textResult(error2 instanceof Error ? error2.message : String(error2), { ok: false }, true);
2447
+ }
2448
+ const result = await this.send(target.from.id, message, void 0, target.message.id);
2449
+ if (!result.isError) {
2450
+ this.unresolvedAsks.delete(target.message.id);
2451
+ }
2452
+ return result;
2453
+ }
2454
+ };
2455
+
2456
+ // claude/mcp-protocol.ts
2457
+ var inflightToolCalls = /* @__PURE__ */ new Map();
2458
+ function asString(value, name) {
2459
+ if (typeof value !== "string" || value.trim() === "") {
2460
+ throw new Error(`${name} must be a non-empty string`);
2461
+ }
2462
+ return value;
2463
+ }
2464
+ function asBoolean(value, defaultValue) {
2465
+ return typeof value === "boolean" ? value : defaultValue;
2466
+ }
2467
+ function asOptionalPositiveInteger(value, name) {
2468
+ if (value === void 0) return void 0;
2469
+ return validateAskTimeoutMs(value, name);
2470
+ }
2471
+ function asAttachmentArray(value) {
2472
+ if (value === void 0) return void 0;
2473
+ if (!isDenseArrayOf(value, (_entry) => true)) {
2474
+ throw new Error("attachments must be an exact dense array");
2475
+ }
2476
+ return value.map((item, index) => {
2477
+ if (!hasExactDataKeys(item, ["type", "name", "content"], ["language"])) {
2478
+ throw new Error(`attachments[${index}] must be an exact object`);
2479
+ }
2480
+ const raw = item;
2481
+ const type = raw.type;
2482
+ if (type !== "file" && type !== "snippet" && type !== "context") throw new Error(`attachments[${index}].type must be file, snippet, or context`);
2483
+ const name = asString(raw.name, `attachments[${index}].name`);
2484
+ const content = asString(raw.content, `attachments[${index}].content`);
2485
+ if (raw.language !== void 0 && typeof raw.language !== "string") throw new Error(`attachments[${index}].language must be a string`);
2486
+ return { type, name, content, ...raw.language ? { language: raw.language } : {} };
2487
+ });
2488
+ }
2489
+ var attachmentsSchema = {
2490
+ type: "array",
2491
+ items: {
2492
+ type: "object",
2493
+ properties: {
2494
+ type: { type: "string", enum: ["file", "snippet", "context"] },
2495
+ name: { type: "string" },
2496
+ content: { type: "string" },
2497
+ language: { type: "string" }
2498
+ },
2499
+ required: ["type", "name", "content"],
2500
+ additionalProperties: false
2501
+ }
2502
+ };
2503
+ function buildToolDefinitions(runtime2) {
2504
+ const tools = [
2505
+ {
2506
+ name: "intercom_whoami",
2507
+ description: "Return this Claude session's intercom identity for reliable targeting.",
2508
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
2509
+ handler: async () => runtime2.whoami()
2510
+ },
2511
+ {
2512
+ name: "intercom_team",
2513
+ description: "Show your current manager and the live coworkers owned by that manager. No arguments are required.",
2514
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
2515
+ handler: async () => runtime2.team()
2516
+ },
2517
+ {
2518
+ name: "intercom_status",
2519
+ description: "Show intercom connection status, active sessions, unread messages, and pending asks.",
2520
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
2521
+ handler: async () => runtime2.status()
2522
+ },
2523
+ {
2524
+ name: "intercom_list",
2525
+ description: "List intercom-connected Pi or Claude sessions on this machine.",
2526
+ inputSchema: {
2527
+ type: "object",
2528
+ properties: {
2529
+ scope: { type: "string", enum: ["machine", "directory", "repo"], default: "machine" },
2530
+ include_self: { type: "boolean", default: false }
2531
+ },
2532
+ additionalProperties: false
2533
+ },
2534
+ handler: async (args) => runtime2.list(
2535
+ args.scope === "directory" || args.scope === "repo" ? args.scope : "machine",
2536
+ asBoolean(args.include_self, false)
2537
+ )
2538
+ },
2539
+ {
2540
+ name: "intercom_set_summary",
2541
+ description: "Publish a short status summary so other sessions can discover what this Claude session is doing.",
2542
+ inputSchema: {
2543
+ type: "object",
2544
+ properties: { summary: { type: "string", maxLength: 400 } },
2545
+ required: ["summary"],
2546
+ additionalProperties: false
2547
+ },
2548
+ handler: async (args) => runtime2.setSummary(asString(args.summary, "summary"))
2549
+ },
2550
+ {
2551
+ name: "intercom_send",
2552
+ description: "Send a non-blocking direct message to another intercom session by name, full ID, or unique ID prefix.",
2553
+ inputSchema: {
2554
+ type: "object",
2555
+ properties: {
2556
+ to: { type: "string" },
2557
+ message: { type: "string" },
2558
+ attachments: attachmentsSchema
2559
+ },
2560
+ required: ["to", "message"],
2561
+ additionalProperties: false
2562
+ },
2563
+ handler: async (args) => runtime2.send(asString(args.to, "to"), asString(args.message, "message"), asAttachmentArray(args.attachments))
2564
+ },
2565
+ {
2566
+ name: "intercom_ask",
2567
+ description: "Ask another intercom session a question only when the next step depends on its reply. Use intercom_send for assignments, progress/status checkpoints, and notifications.",
2568
+ inputSchema: {
2569
+ type: "object",
2570
+ properties: {
2571
+ to: { type: "string" },
2572
+ message: { type: "string" },
2573
+ attachments: attachmentsSchema,
2574
+ timeout_ms: { type: "integer", minimum: 1, maximum: 12e4, description: "Maximum time to wait for a reply before returning an error. Use intercom_send plus intercom_pending for longer work." }
2575
+ },
2576
+ required: ["to", "message"],
2577
+ additionalProperties: false
2578
+ },
2579
+ handler: async (args, signal) => runtime2.ask(
2580
+ asString(args.to, "to"),
2581
+ asString(args.message, "message"),
2582
+ asAttachmentArray(args.attachments),
2583
+ asOptionalPositiveInteger(args.timeout_ms, "timeout_ms"),
2584
+ signal
2585
+ )
2586
+ },
2587
+ {
2588
+ name: "intercom_pending",
2589
+ description: "Read unread inbound messages and unresolved asks for this Claude session.",
2590
+ inputSchema: {
2591
+ type: "object",
2592
+ properties: { mark_read: { type: "boolean", default: false } },
2593
+ additionalProperties: false
2594
+ },
2595
+ handler: async (args) => runtime2.pending(asBoolean(args.mark_read, false))
2596
+ },
2597
+ {
2598
+ name: "intercom_reply",
2599
+ description: "Reply to a pending inbound ask. Use to plus which=oldest/latest when one sender has multiple unresolved asks.",
2600
+ inputSchema: {
2601
+ type: "object",
2602
+ properties: {
2603
+ message: { type: "string" },
2604
+ to: { type: "string", description: "Optional sender/session selector; never a message or thread ID." },
2605
+ which: { type: "string", enum: ["oldest", "latest"], description: "Select the oldest or latest ask from the chosen sender." }
2606
+ },
2607
+ required: ["message"],
2608
+ additionalProperties: false
2609
+ },
2610
+ handler: async (args) => runtime2.reply(asString(args.message, "message"), typeof args.to === "string" ? args.to : void 0, args.which === "oldest" || args.which === "latest" ? args.which : void 0)
2611
+ }
2612
+ ];
2613
+ return tools;
2614
+ }
2615
+ function ok(id, result) {
2616
+ return { jsonrpc: "2.0", id, result };
2617
+ }
2618
+ function error(id, code, message) {
2619
+ return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
2620
+ }
2621
+ async function handleMcpRequest(request, runtime2) {
2622
+ if (!request.method) {
2623
+ return error(request.id, -32600, "Invalid request");
2624
+ }
2625
+ if (request.method === "notifications/cancelled") {
2626
+ const requestId = request.params?.requestId;
2627
+ if (typeof requestId === "string" || typeof requestId === "number") {
2628
+ inflightToolCalls.get(requestId)?.abort();
2629
+ }
2630
+ return void 0;
2631
+ }
2632
+ if (request.id === void 0 && request.method.startsWith("notifications/")) {
2633
+ return void 0;
2634
+ }
2635
+ const tools = buildToolDefinitions(runtime2);
2636
+ switch (request.method) {
2637
+ case "initialize":
2638
+ return ok(request.id, {
2639
+ protocolVersion: "2025-06-18",
2640
+ capabilities: { tools: {} },
2641
+ serverInfo: { name: "claude-intercom", version: "0.1.0" }
2642
+ });
2643
+ case "ping":
2644
+ return ok(request.id, {});
2645
+ case "tools/list":
2646
+ return ok(request.id, {
2647
+ tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }))
2648
+ });
2649
+ case "tools/call": {
2650
+ const name = request.params?.name;
2651
+ const args = request.params?.arguments;
2652
+ if (typeof name !== "string") return error(request.id, -32602, "tools/call requires params.name");
2653
+ if (args !== void 0 && (!args || typeof args !== "object" || Array.isArray(args))) {
2654
+ return error(request.id, -32602, "tools/call params.arguments must be an object");
2655
+ }
2656
+ const tool = tools.find((candidate) => candidate.name === name);
2657
+ if (!tool) return error(request.id, -32602, `Unknown tool: ${name}`);
2658
+ const requestId = request.id;
2659
+ const abortController = typeof requestId === "string" || typeof requestId === "number" ? new AbortController() : null;
2660
+ if (abortController && requestId !== void 0 && requestId !== null) inflightToolCalls.set(requestId, abortController);
2661
+ try {
2662
+ return ok(request.id, await tool.handler(args ?? {}, abortController?.signal));
2663
+ } catch (caught) {
2664
+ const message = caught instanceof Error ? caught.message : String(caught);
2665
+ return ok(request.id, { content: [{ type: "text", text: message }], isError: true });
2666
+ } finally {
2667
+ if (abortController && requestId !== void 0 && requestId !== null) inflightToolCalls.delete(requestId);
2668
+ }
2669
+ }
2670
+ default:
2671
+ return error(request.id, -32601, `Method not found: ${request.method}`);
2672
+ }
2673
+ }
2674
+
2675
+ // claude/server.ts
2676
+ var runtime = new ClaudeIntercomRuntime();
2677
+ if (process.env.CLAUDE_INTERCOM_INBOX) {
2678
+ void runtime.connect().catch((error2) => {
2679
+ process.stderr.write(`claude-intercom: eager connect failed: ${error2 instanceof Error ? error2.message : String(error2)}
2680
+ `);
2681
+ });
2682
+ }
2683
+ var rl = readline.createInterface({
2684
+ input: stdin,
2685
+ crlfDelay: Infinity
2686
+ });
2687
+ var shuttingDown = false;
2688
+ var pendingRequests = 0;
2689
+ function writeResponse(response) {
2690
+ if (!response) return;
2691
+ stdout.write(`${JSON.stringify(response)}
2692
+ `);
2693
+ }
2694
+ function maybeShutdown() {
2695
+ if (!shuttingDown || pendingRequests > 0) return;
2696
+ void runtime.disconnect().finally(() => process.exit(0));
2697
+ }
2698
+ rl.on("line", (line) => {
2699
+ const trimmed = line.trim();
2700
+ if (!trimmed) return;
2701
+ pendingRequests += 1;
2702
+ void (async () => {
2703
+ try {
2704
+ const request = JSON.parse(trimmed);
2705
+ writeResponse(await handleMcpRequest(request, runtime));
2706
+ } catch (error2) {
2707
+ writeResponse({
2708
+ jsonrpc: "2.0",
2709
+ id: null,
2710
+ error: {
2711
+ code: -32700,
2712
+ message: error2 instanceof Error ? error2.message : String(error2)
2713
+ }
2714
+ });
2715
+ }
2716
+ })().finally(() => {
2717
+ pendingRequests -= 1;
2718
+ maybeShutdown();
2719
+ });
2720
+ });
2721
+ var shutdown = () => {
2722
+ if (shuttingDown) return;
2723
+ shuttingDown = true;
2724
+ rl.close();
2725
+ maybeShutdown();
2726
+ };
2727
+ rl.on("close", shutdown);
2728
+ process.on("SIGINT", shutdown);
2729
+ process.on("SIGTERM", shutdown);
2730
+ process.on("disconnect", shutdown);