@ctliz/agent-intercom-codex 0.11.0-connect.2

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 (52) hide show
  1. package/.codex-plugin/plugin.json +39 -0
  2. package/.mcp.json +10 -0
  3. package/COPYRIGHT +6 -0
  4. package/LICENSE +661 -0
  5. package/LICENSE_TRANSITION.md +25 -0
  6. package/README.md +572 -0
  7. package/THIRD_PARTY_NOTICES.md +17 -0
  8. package/assets/logo-generated.png +0 -0
  9. package/assets/logo.svg +22 -0
  10. package/boss-control-outbox.ts +195 -0
  11. package/broker/access-credential.ts +76 -0
  12. package/broker/access-registry.ts +464 -0
  13. package/broker/audit.ts +60 -0
  14. package/broker/authorization.ts +54 -0
  15. package/broker/boss-adapter.ts +531 -0
  16. package/broker/boss-control-ledger.ts +306 -0
  17. package/broker/broker.ts +2327 -0
  18. package/broker/client.ts +1111 -0
  19. package/broker/framing.ts +87 -0
  20. package/broker/ownership.ts +61 -0
  21. package/broker/paths.ts +161 -0
  22. package/broker/spawn.ts +453 -0
  23. package/codex/app-server-client.ts +562 -0
  24. package/codex/boss-client.ts +34 -0
  25. package/codex/bridge-config.ts +282 -0
  26. package/codex/bridge-daemon.ts +913 -0
  27. package/codex/clipboard.ts +142 -0
  28. package/codex/coi.ts +802 -0
  29. package/codex/contact.ts +54 -0
  30. package/codex/mcp-protocol.ts +248 -0
  31. package/codex/runtime.ts +488 -0
  32. package/codex/server.ts +60 -0
  33. package/codex/team.ts +249 -0
  34. package/codex/tui-input.ts +174 -0
  35. package/config.ts +195 -0
  36. package/dist/bridge-daemon.mjs +3781 -0
  37. package/dist/broker.mjs +3012 -0
  38. package/dist/build-info.json +12 -0
  39. package/dist/codex-server.mjs +2890 -0
  40. package/dist/coi.mjs +4686 -0
  41. package/durable-json.ts +25 -0
  42. package/licenses/MIT-pi-intercom.txt +21 -0
  43. package/outbound-outbox.ts +116 -0
  44. package/package.json +89 -0
  45. package/protocol-v4/contract.ts +27 -0
  46. package/provider/protected-service.ts +162 -0
  47. package/provider/provider.mjs +34 -0
  48. package/scripts/build-info.mjs +58 -0
  49. package/scripts/build.mjs +55 -0
  50. package/skills/codex-intercom/SKILL.md +104 -0
  51. package/tsconfig.json +16 -0
  52. package/types.ts +225 -0
@@ -0,0 +1,3781 @@
1
+ #!/usr/bin/env node
2
+ process.stderr.write("[agent-intercom-build] package=@ctliz/agent-intercom-codex version=0.11.0-connect.2 target=bridge-daemon sourceSha256=3f81ffd19c83ad2f4db13939ce8dba9eae17d8c4b0c419e405941e0352f9c102\n");
3
+
4
+ // codex/bridge-daemon.ts
5
+ import { once } from "node:events";
6
+ import { randomUUID as randomUUID4 } from "node:crypto";
7
+ import { basename } from "node:path";
8
+
9
+ // codex/app-server-client.ts
10
+ import { EventEmitter } from "node:events";
11
+ import { spawn, spawnSync } from "node:child_process";
12
+ import { randomBytes, createHash } from "node:crypto";
13
+ import net from "node:net";
14
+ import readline from "node:readline";
15
+ import { setTimeout as delay } from "node:timers/promises";
16
+
17
+ // codex/boss-client.ts
18
+ var HARDENED_BOSS_CODEX_DEFAULTS = Object.freeze({
19
+ boss_participant: Object.freeze({ approvalPolicy: "untrusted", sandbox: "workspace-write" }),
20
+ boss_reviewer: Object.freeze({ approvalPolicy: "untrusted", sandbox: "read-only" })
21
+ });
22
+ var PROVIDER_AUTHORITY_UNAVAILABLE = "PROVIDER_AUTHORITY_UNAVAILABLE";
23
+ var ProviderAuthorityUnavailableError = class extends Error {
24
+ constructor(bossClient) {
25
+ super(`${PROVIDER_AUTHORITY_UNAVAILABLE}: ${bossClient} requires a broker-owned, artifact-attested Codex provider executable`);
26
+ this.bossClient = bossClient;
27
+ this.name = "ProviderAuthorityUnavailableError";
28
+ }
29
+ bossClient;
30
+ code = PROVIDER_AUTHORITY_UNAVAILABLE;
31
+ };
32
+ function assertHardenedBossProviderAuthority(bossClient) {
33
+ if (bossClient !== void 0) throw new ProviderAuthorityUnavailableError(bossClient);
34
+ }
35
+
36
+ // codex/app-server-client.ts
37
+ var DEFAULT_REQUEST_TIMEOUT_MS = 10 * 60 * 1e3;
38
+ var MAX_WEBSOCKET_MESSAGE_BYTES = 16 * 1024 * 1024;
39
+ var UNIX_WEBSOCKET_CONNECT_TIMEOUT_MS = 1e4;
40
+ function asError(error) {
41
+ return error instanceof Error ? error : new Error(String(error));
42
+ }
43
+ function defaultServerRequestResponse(method) {
44
+ switch (method) {
45
+ case "item/commandExecution/requestApproval":
46
+ return { decision: "decline" };
47
+ case "item/fileChange/requestApproval":
48
+ return { decision: "decline" };
49
+ case "item/permissions/requestApproval":
50
+ return { permissions: {}, scope: "turn", strictAutoReview: true };
51
+ case "item/tool/requestUserInput":
52
+ return { answers: {} };
53
+ case "mcpServer/elicitation/request":
54
+ return { action: "decline", content: null, _meta: null };
55
+ case "item/tool/call":
56
+ return { contentItems: [{ type: "text", text: "Background bridge declined tool call." }], success: false };
57
+ case "applyPatchApproval":
58
+ case "execCommandApproval":
59
+ return { decision: "denied" };
60
+ default:
61
+ throw new Error(`Unsupported app-server request: ${method}`);
62
+ }
63
+ }
64
+ var CodexAppServerClient = class extends EventEmitter {
65
+ proc = null;
66
+ socket = null;
67
+ wsDecoder = new WebSocketFrameDecoder();
68
+ rl = null;
69
+ nextId = 1;
70
+ pending = /* @__PURE__ */ new Map();
71
+ initialized = false;
72
+ options;
73
+ constructor(options = {}, protectedBossClient) {
74
+ super();
75
+ assertHardenedBossProviderAuthority(protectedBossClient);
76
+ this.options = {
77
+ command: options.command ?? "codex",
78
+ args: options.args ?? ["app-server"],
79
+ transport: options.transport ?? "stdio",
80
+ socketPath: options.socketPath ?? "",
81
+ serverRequestHandler: options.serverRequestHandler ?? defaultServerRequestResponseFromMessage,
82
+ startDaemon: options.startDaemon ?? false,
83
+ startDaemonCommand: options.startDaemonCommand ?? "codex",
84
+ startDaemonArgs: options.startDaemonArgs ?? ["app-server", "daemon", "start"],
85
+ requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
86
+ env: options.env ?? process.env
87
+ };
88
+ }
89
+ setServerRequestHandler(handler) {
90
+ this.options.serverRequestHandler = handler;
91
+ }
92
+ async connect() {
93
+ if (this.proc) return;
94
+ if (this.options.startDaemon) {
95
+ const started = spawnSync(this.options.startDaemonCommand, this.options.startDaemonArgs, {
96
+ encoding: "utf8",
97
+ env: this.options.env,
98
+ stdio: ["ignore", "pipe", "pipe"]
99
+ });
100
+ if (started.status !== 0) {
101
+ throw new Error(`Failed to start Codex app-server daemon: ${started.stderr || started.stdout || `exit ${started.status}`}`);
102
+ }
103
+ }
104
+ if (this.options.transport === "unix-websocket") {
105
+ await this.connectUnixWebSocket();
106
+ await this.initialize();
107
+ return;
108
+ }
109
+ const proc = spawn(this.options.command, this.options.args, {
110
+ stdio: ["pipe", "pipe", "pipe"],
111
+ env: this.options.env
112
+ });
113
+ this.proc = proc;
114
+ this.rl = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
115
+ this.rl.on("line", (line) => this.handleLine(line));
116
+ proc.stderr.on("data", (chunk) => this.emit("stderr", String(chunk)));
117
+ proc.once("error", (error) => this.failAll(asError(error)));
118
+ proc.once("exit", (code, signal) => {
119
+ this.failAll(new Error(`Codex app-server proxy exited (${signal ?? code ?? "unknown"})`));
120
+ this.proc = null;
121
+ this.initialized = false;
122
+ this.emit("exit", { code, signal });
123
+ });
124
+ await this.initialize();
125
+ }
126
+ async disconnect() {
127
+ const socket = this.socket;
128
+ if (socket) {
129
+ this.socket = null;
130
+ this.initialized = false;
131
+ this.failAll(new Error("Codex app-server client disconnected"));
132
+ await new Promise((resolve4) => {
133
+ const timeout = setTimeout(() => {
134
+ socket.destroy();
135
+ resolve4();
136
+ }, 1e3);
137
+ socket.once("close", () => {
138
+ clearTimeout(timeout);
139
+ resolve4();
140
+ });
141
+ if (!socket.destroyed) {
142
+ this.writeWebSocketFrame(8, Buffer.alloc(0));
143
+ socket.end();
144
+ }
145
+ });
146
+ return;
147
+ }
148
+ const proc = this.proc;
149
+ this.rl?.close();
150
+ this.rl = null;
151
+ this.proc = null;
152
+ this.initialized = false;
153
+ this.failAll(new Error("Codex app-server client disconnected"));
154
+ if (!proc) return;
155
+ await new Promise((resolve4) => {
156
+ const timeout = setTimeout(() => {
157
+ proc.kill("SIGKILL");
158
+ resolve4();
159
+ }, 2e3);
160
+ proc.once("exit", () => {
161
+ clearTimeout(timeout);
162
+ resolve4();
163
+ });
164
+ proc.stdin.end();
165
+ proc.kill("SIGTERM");
166
+ });
167
+ }
168
+ async initialize() {
169
+ if (this.initialized) return;
170
+ await this.request("initialize", {
171
+ clientInfo: {
172
+ name: "codex_intercom_bridge",
173
+ title: "Codex Intercom Bridge",
174
+ version: "0.1.0"
175
+ },
176
+ capabilities: { experimentalApi: true }
177
+ });
178
+ this.notify("initialized", {});
179
+ this.initialized = true;
180
+ }
181
+ request(method, params, timeoutMs = this.options.requestTimeoutMs) {
182
+ if (!this.canWrite()) {
183
+ return Promise.reject(new Error("Codex app-server client is not connected"));
184
+ }
185
+ const id = this.nextId++;
186
+ const payload = params === void 0 ? { id, method } : { id, method, params };
187
+ return new Promise((resolve4, reject) => {
188
+ const timeout = setTimeout(() => {
189
+ this.pending.delete(id);
190
+ reject(new Error(`Codex app-server request timed out: ${method}`));
191
+ }, timeoutMs);
192
+ this.pending.set(id, { resolve: resolve4, reject, timeout });
193
+ this.writePayload(payload);
194
+ });
195
+ }
196
+ notify(method, params) {
197
+ if (!this.canWrite()) {
198
+ throw new Error("Codex app-server client is not connected");
199
+ }
200
+ const payload = params === void 0 ? { method } : { method, params };
201
+ this.writePayload(payload);
202
+ }
203
+ respond(id, result) {
204
+ if (id === void 0 || id === null) return;
205
+ this.writePayload({ id, result });
206
+ }
207
+ respondError(id, code, message) {
208
+ if (id === void 0 || id === null) return;
209
+ this.writePayload({ id, error: { code, message } });
210
+ }
211
+ canWrite() {
212
+ if (this.options.transport === "unix-websocket") {
213
+ return Boolean(this.socket && !this.socket.destroyed && this.socket.writable);
214
+ }
215
+ return Boolean(this.proc && this.proc.stdin.writable);
216
+ }
217
+ writePayload(payload) {
218
+ const json = JSON.stringify(payload);
219
+ if (this.options.transport === "unix-websocket") {
220
+ this.writeWebSocketFrame(1, Buffer.from(json, "utf8"));
221
+ return;
222
+ }
223
+ this.proc?.stdin.write(`${json}
224
+ `);
225
+ }
226
+ async connectUnixWebSocket() {
227
+ const deadline = Date.now() + UNIX_WEBSOCKET_CONNECT_TIMEOUT_MS;
228
+ let lastError = null;
229
+ let attempt = 0;
230
+ while (Date.now() < deadline) {
231
+ try {
232
+ await this.connectUnixWebSocketOnce();
233
+ return;
234
+ } catch (error) {
235
+ lastError = asError(error);
236
+ this.socket?.destroy();
237
+ this.socket = null;
238
+ const code = lastError.code;
239
+ if (code !== "ENOENT" && code !== "ECONNREFUSED") throw lastError;
240
+ const backoffMs = Math.min(250, 25 * 2 ** attempt);
241
+ attempt += 1;
242
+ await delay(backoffMs);
243
+ }
244
+ }
245
+ throw new Error(`Codex app-server WebSocket did not become ready within ${Math.round(UNIX_WEBSOCKET_CONNECT_TIMEOUT_MS / 1e3)} seconds${lastError ? `: ${lastError.message}` : ""}`);
246
+ }
247
+ connectUnixWebSocketOnce() {
248
+ const socketPath = this.options.socketPath;
249
+ if (!socketPath) return Promise.reject(new Error("socketPath is required for unix-websocket transport"));
250
+ return new Promise((resolve4, reject) => {
251
+ const key = randomBytes(16).toString("base64");
252
+ const expectedAccept = createHash("sha1").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest("base64");
253
+ const socket = net.createConnection(socketPath);
254
+ this.socket = socket;
255
+ this.wsDecoder = new WebSocketFrameDecoder();
256
+ let handshake = Buffer.alloc(0);
257
+ let settled = false;
258
+ const fail = (error) => {
259
+ if (!settled) {
260
+ settled = true;
261
+ cleanupHandshake();
262
+ reject(error);
263
+ return;
264
+ }
265
+ this.failAll(error);
266
+ this.emit("exit", { code: null, signal: null });
267
+ };
268
+ const onData = (chunk) => {
269
+ if (settled) {
270
+ this.handleWebSocketData(chunk);
271
+ return;
272
+ }
273
+ handshake = Buffer.concat([handshake, chunk]);
274
+ const headerEnd = handshake.indexOf("\r\n\r\n");
275
+ if (headerEnd < 0) return;
276
+ const head = handshake.subarray(0, headerEnd).toString("utf8");
277
+ const rest = handshake.subarray(headerEnd + 4);
278
+ if (!/^HTTP\/1\.1 101\b/im.test(head) || !head.toLowerCase().includes(`sec-websocket-accept: ${expectedAccept.toLowerCase()}`)) {
279
+ fail(new Error(`Codex app-server WebSocket handshake failed: ${head.split("\r\n")[0] || "invalid response"}`));
280
+ return;
281
+ }
282
+ settled = true;
283
+ cleanupHandshake();
284
+ socket.on("data", (data) => this.handleWebSocketData(data));
285
+ socket.on("error", (error) => fail(error));
286
+ socket.on("close", () => fail(new Error("Codex app-server WebSocket closed")));
287
+ if (rest.length) this.handleWebSocketData(rest);
288
+ resolve4();
289
+ };
290
+ const cleanupHandshake = () => {
291
+ socket.off("data", onData);
292
+ socket.off("error", fail);
293
+ };
294
+ socket.once("connect", () => {
295
+ socket.write([
296
+ "GET / HTTP/1.1",
297
+ "Host: localhost",
298
+ "Upgrade: websocket",
299
+ "Connection: Upgrade",
300
+ `Sec-WebSocket-Key: ${key}`,
301
+ "Sec-WebSocket-Version: 13",
302
+ "\r\n"
303
+ ].join("\r\n"));
304
+ });
305
+ socket.on("data", onData);
306
+ socket.once("error", fail);
307
+ });
308
+ }
309
+ writeWebSocketFrame(opcode, payload) {
310
+ const socket = this.socket;
311
+ if (!socket || socket.destroyed || !socket.writable) return;
312
+ const length = payload.length;
313
+ const lengthBytes = length < 126 ? 0 : length <= 65535 ? 2 : 8;
314
+ const header = Buffer.alloc(2 + lengthBytes + 4);
315
+ header[0] = 128 | opcode;
316
+ if (length < 126) {
317
+ header[1] = 128 | length;
318
+ } else if (length <= 65535) {
319
+ header[1] = 128 | 126;
320
+ header.writeUInt16BE(length, 2);
321
+ } else {
322
+ header[1] = 128 | 127;
323
+ header.writeBigUInt64BE(BigInt(length), 2);
324
+ }
325
+ const maskOffset = 2 + lengthBytes;
326
+ const mask = randomBytes(4);
327
+ mask.copy(header, maskOffset);
328
+ const masked = Buffer.alloc(length);
329
+ for (let index = 0; index < length; index += 1) {
330
+ masked[index] = payload[index] ^ mask[index % 4];
331
+ }
332
+ socket.write(Buffer.concat([header, masked]));
333
+ }
334
+ handleWebSocketData(chunk) {
335
+ let frames;
336
+ try {
337
+ frames = this.wsDecoder.push(chunk);
338
+ } catch (error) {
339
+ this.emit("protocolError", asError(error));
340
+ this.socket?.destroy(asError(error));
341
+ return;
342
+ }
343
+ for (const { opcode, payload } of frames) {
344
+ if (opcode === 1) {
345
+ this.handleLine(payload.toString("utf8"));
346
+ } else if (opcode === 8) {
347
+ this.socket?.end();
348
+ } else if (opcode === 9) {
349
+ this.writeWebSocketFrame(10, payload);
350
+ }
351
+ }
352
+ }
353
+ handleLine(line) {
354
+ const trimmed = line.trim();
355
+ if (!trimmed) return;
356
+ let message;
357
+ try {
358
+ message = JSON.parse(trimmed);
359
+ } catch (error) {
360
+ this.emit("protocolError", asError(error));
361
+ return;
362
+ }
363
+ if (message.id !== void 0 && !message.method) {
364
+ const pending = this.pending.get(message.id);
365
+ if (!pending) return;
366
+ this.pending.delete(message.id);
367
+ clearTimeout(pending.timeout);
368
+ if (message.error) {
369
+ pending.reject(new Error(message.error.message));
370
+ } else {
371
+ pending.resolve(message.result);
372
+ }
373
+ return;
374
+ }
375
+ if (message.method && message.id !== void 0) {
376
+ void this.handleServerRequest(message);
377
+ this.emit("serverRequest", message);
378
+ return;
379
+ }
380
+ if (message.method) {
381
+ this.emit("notification", message);
382
+ if (message.method === "error") {
383
+ this.emit("serverError", message.params);
384
+ } else {
385
+ this.emit(message.method, message.params);
386
+ }
387
+ }
388
+ }
389
+ failAll(error) {
390
+ for (const pending of this.pending.values()) {
391
+ clearTimeout(pending.timeout);
392
+ pending.reject(error);
393
+ }
394
+ this.pending.clear();
395
+ }
396
+ async handleServerRequest(message) {
397
+ try {
398
+ if (process.env.CODEX_INTERCOM_DEBUG_TOOL_CALLS) {
399
+ process.stderr.write(`app-server request ${message.method ?? "unknown"}: ${JSON.stringify(message.params ?? {})}
400
+ `);
401
+ }
402
+ this.respond(message.id, await this.options.serverRequestHandler(message));
403
+ } catch (error) {
404
+ if (process.env.CODEX_INTERCOM_DEBUG_TOOL_CALLS) {
405
+ process.stderr.write(`app-server request failed ${message.method ?? "unknown"}: ${asError(error).message}
406
+ `);
407
+ }
408
+ this.respondError(message.id, -32601, asError(error).message);
409
+ }
410
+ }
411
+ };
412
+ function defaultServerRequestResponseFromMessage(message) {
413
+ if (!message.method) throw new Error("Unsupported app-server request");
414
+ return defaultServerRequestResponse(message.method);
415
+ }
416
+ var WebSocketFrameDecoder = class {
417
+ buffer = Buffer.alloc(0);
418
+ continuationOpcode = null;
419
+ continuationParts = [];
420
+ continuationBytes = 0;
421
+ push(chunk) {
422
+ this.buffer = Buffer.concat([this.buffer, chunk]);
423
+ const frames = [];
424
+ while (this.buffer.length >= 2) {
425
+ const first = this.buffer[0];
426
+ const second = this.buffer[1];
427
+ const fin = Boolean(first & 128);
428
+ const rsv = first & 112;
429
+ const opcode = first & 15;
430
+ const masked = Boolean(second & 128);
431
+ let length = second & 127;
432
+ let offset = 2;
433
+ if (rsv !== 0) throw new Error("Unsupported WebSocket RSV bits");
434
+ if (length === 126) {
435
+ if (this.buffer.length < offset + 2) break;
436
+ length = this.buffer.readUInt16BE(offset);
437
+ offset += 2;
438
+ } else if (length === 127) {
439
+ if (this.buffer.length < offset + 8) break;
440
+ const bigLength = this.buffer.readBigUInt64BE(offset);
441
+ if (bigLength > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("WebSocket frame too large");
442
+ length = Number(bigLength);
443
+ offset += 8;
444
+ }
445
+ const maskOffset = masked ? offset : -1;
446
+ if (masked) offset += 4;
447
+ if (this.buffer.length < offset + length) break;
448
+ const mask = masked ? Buffer.from(this.buffer.subarray(maskOffset, maskOffset + 4)) : null;
449
+ const payload = Buffer.from(this.buffer.subarray(offset, offset + length));
450
+ this.buffer = this.buffer.subarray(offset + length);
451
+ if (masked) {
452
+ for (let index = 0; index < payload.length; index += 1) {
453
+ payload[index] ^= mask[index % 4];
454
+ }
455
+ }
456
+ this.acceptFrame(frames, opcode, fin, payload);
457
+ }
458
+ return frames;
459
+ }
460
+ acceptFrame(frames, opcode, fin, payload) {
461
+ if (opcode >= 8) {
462
+ if (!fin) throw new Error("Fragmented WebSocket control frame");
463
+ if (payload.length > 125) throw new Error("Oversized WebSocket control frame");
464
+ frames.push({ opcode, payload });
465
+ return;
466
+ }
467
+ if (opcode === 0) {
468
+ if (this.continuationOpcode === null) throw new Error("Unexpected WebSocket continuation frame");
469
+ this.appendContinuation(payload);
470
+ if (fin) {
471
+ frames.push({ opcode: this.continuationOpcode, payload: Buffer.concat(this.continuationParts, this.continuationBytes) });
472
+ this.clearContinuation();
473
+ }
474
+ return;
475
+ }
476
+ if (opcode !== 1 && opcode !== 2) throw new Error(`Unsupported WebSocket opcode: ${opcode}`);
477
+ if (this.continuationOpcode !== null) throw new Error("New WebSocket data frame before continuation completed");
478
+ if (fin) {
479
+ frames.push({ opcode, payload });
480
+ return;
481
+ }
482
+ this.continuationOpcode = opcode;
483
+ this.continuationParts = [];
484
+ this.continuationBytes = 0;
485
+ this.appendContinuation(payload);
486
+ }
487
+ appendContinuation(payload) {
488
+ this.continuationBytes += payload.length;
489
+ if (this.continuationBytes > MAX_WEBSOCKET_MESSAGE_BYTES) throw new Error("WebSocket message too large");
490
+ this.continuationParts.push(payload);
491
+ }
492
+ clearContinuation() {
493
+ this.continuationOpcode = null;
494
+ this.continuationParts = [];
495
+ this.continuationBytes = 0;
496
+ }
497
+ };
498
+
499
+ // codex/bridge-config.ts
500
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
501
+ import { dirname, join as join2, parse as parsePath, resolve as resolve2 } from "node:path";
502
+ import { cwd as processCwd } from "node:process";
503
+ import { types as nodeUtilTypes2 } from "node:util";
504
+
505
+ // broker/paths.ts
506
+ import { chmodSync, mkdirSync, readFileSync } from "fs";
507
+ import { isAbsolute, join, resolve } from "path";
508
+ import { homedir } from "os";
509
+ var INTERCOM_DIR_MODE = 448;
510
+ var INTERCOM_RUNTIME_FILE_MODE = 384;
511
+ var INTERCOM_TCP_HOST = "127.0.0.1";
512
+ var INTERCOM_PROTOCOL_NAME = "pi-intercom";
513
+ var INTERCOM_PROTOCOL_VERSION = 4;
514
+ function sanitizePipeSegment(value) {
515
+ return value.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "default";
516
+ }
517
+ function getAgentDirPath(env = process.env, homeDir = homedir(), cwd = process.cwd()) {
518
+ const configured = env.PI_CODING_AGENT_DIR?.trim();
519
+ if (!configured) {
520
+ return join(homeDir, ".pi/agent");
521
+ }
522
+ return isAbsolute(configured) ? configured : resolve(cwd, configured);
523
+ }
524
+ function getIntercomDirPath(agentDir = getAgentDirPath()) {
525
+ return join(agentDir, "intercom");
526
+ }
527
+ function shouldUseWindowsTcpTransport(platform = process.platform, env = process.env) {
528
+ if (platform !== "win32") {
529
+ return false;
530
+ }
531
+ const transport = env.PI_INTERCOM_TRANSPORT?.trim().toLowerCase();
532
+ if (transport === "tcp") {
533
+ return true;
534
+ }
535
+ const legacyOptIn = env.PI_INTERCOM_TCP?.trim().toLowerCase();
536
+ return legacyOptIn === "1" || legacyOptIn === "true";
537
+ }
538
+ function getBrokerPortFilePath(intercomDir = getIntercomDirPath()) {
539
+ return join(intercomDir, "broker.port.json");
540
+ }
541
+ function getBrokerSocketPath(platform = process.platform, agentDir = getAgentDirPath()) {
542
+ if (platform === "win32") {
543
+ return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(agentDir)}`;
544
+ }
545
+ return join(getIntercomDirPath(agentDir), "broker.sock");
546
+ }
547
+ function getBrokerConnectTarget(platform = process.platform, env = process.env, intercomDir = getIntercomDirPath(getAgentDirPath(env))) {
548
+ if (shouldUseWindowsTcpTransport(platform, env)) {
549
+ const endpointFile = getBrokerPortFilePath(intercomDir);
550
+ const raw = readFileSync(endpointFile, "utf-8");
551
+ const parsed = JSON.parse(raw);
552
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
553
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}: expected a JSON object`);
554
+ }
555
+ const endpoint = parsed;
556
+ 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) {
557
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}`);
558
+ }
559
+ return { transport: "tcp", host: endpoint.host, port: endpoint.port, stateId: endpoint.stateId };
560
+ }
561
+ return getBrokerSocketPath(platform, getAgentDirPath(env));
562
+ }
563
+ function ensureIntercomRuntimeDir(intercomDir = getIntercomDirPath(), platform = process.platform) {
564
+ mkdirSync(intercomDir, { recursive: true, mode: INTERCOM_DIR_MODE });
565
+ if (platform !== "win32") {
566
+ chmodSync(intercomDir, INTERCOM_DIR_MODE);
567
+ }
568
+ }
569
+ function restrictIntercomRuntimeFile(filePath, platform = process.platform) {
570
+ if (platform !== "win32") {
571
+ chmodSync(filePath, INTERCOM_RUNTIME_FILE_MODE);
572
+ }
573
+ }
574
+
575
+ // broker/boss-adapter.ts
576
+ import {
577
+ BOSS_CAPABILITY_FEATURE_DIGEST,
578
+ BOSS_CONTROL_ENVELOPE_VERSION,
579
+ BOSS_POLICY_PRINCIPAL_VERSION,
580
+ BOSS_POLICY_SEMANTICS_HASH,
581
+ BOSS_RUN_FEATURE,
582
+ BOSS_RUN_FEATURE_CONTRACT,
583
+ BOSS_RUN_FEATURE_SEMANTICS_HASH,
584
+ BOSS_RUN_FEATURE_VERSION,
585
+ BOSS_RUN_PROTOCOL_FEATURE_CONTRACT_HASH,
586
+ BROKER_FEATURE_ATTESTATION_VERSION,
587
+ INTERCOM_BASE_PROTOCOL_VERSION,
588
+ authorizeFeatureAware,
589
+ brokerFeatureSetHash,
590
+ parseBossControlEnvelope,
591
+ parseBossParticipantBinding,
592
+ parseBossParticipantCredentialEnvelope,
593
+ parseBossRunFeatureContract,
594
+ parseBrokerCapabilityAdvertisement,
595
+ parseParticipantState,
596
+ parseWorkerIdentityV2
597
+ } from "@ctliz/agent-intercom-core/boss";
598
+ import {
599
+ ContractValidationError,
600
+ assertExactKeys,
601
+ assertRecord,
602
+ canonicalJson
603
+ } from "@ctliz/agent-intercom-core/canonical";
604
+ import { types as nodeUtilTypes } from "node:util";
605
+ var BOSS_ADVERTISEMENT_PREDICATES = [
606
+ "protectedProvider",
607
+ "brokerIdentity",
608
+ "credentialRegistry",
609
+ "authorityTransitions",
610
+ "participantHealth"
611
+ ];
612
+ var DORMANT_BOSS_ADVERTISEMENT_READINESS = Object.freeze({
613
+ protectedProvider: false,
614
+ brokerIdentity: false,
615
+ credentialRegistry: false,
616
+ authorityTransitions: false,
617
+ participantHealth: false
618
+ });
619
+ var ORDINARY_SESSION_REGISTRATION_KEYS = [
620
+ "cwd",
621
+ "model",
622
+ "pid",
623
+ "startedAt",
624
+ "lastActivity"
625
+ ];
626
+ var OPTIONAL_SESSION_REGISTRATION_KEYS = ["name", "status", "runtimeInstanceId"];
627
+ function assertBossCanonicalData(value, path = "$", seen = /* @__PURE__ */ new WeakSet()) {
628
+ if (typeof value !== "object" || value === null) return;
629
+ if (nodeUtilTypes.isProxy(value)) {
630
+ throw new ContractValidationError(path, "proxies are not supported");
631
+ }
632
+ if (seen.has(value)) throw new ContractValidationError(path, "cyclic values are not supported");
633
+ seen.add(value);
634
+ if (Array.isArray(value)) {
635
+ if (Object.getPrototypeOf(value) !== Array.prototype) {
636
+ throw new ContractValidationError(path, "must use the exact Array prototype");
637
+ }
638
+ const ownKeys = Reflect.ownKeys(value);
639
+ const expectedKeys = /* @__PURE__ */ new Set(["length"]);
640
+ for (let index = 0; index < value.length; index += 1) expectedKeys.add(String(index));
641
+ if (ownKeys.length !== expectedKeys.size || ownKeys.some((key) => !expectedKeys.has(key))) {
642
+ throw new ContractValidationError(path, "must be a dense array without symbols or extra properties");
643
+ }
644
+ for (let index = 0; index < value.length; index += 1) {
645
+ if (!Object.hasOwn(value, index)) {
646
+ throw new ContractValidationError(`${path}[${index}]`, "sparse array holes are not supported");
647
+ }
648
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
649
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
650
+ throw new ContractValidationError(`${path}[${index}]`, "must be an own enumerable data property");
651
+ }
652
+ assertBossCanonicalData(descriptor.value, `${path}[${index}]`, seen);
653
+ }
654
+ return;
655
+ }
656
+ if (Object.getPrototypeOf(value) !== Object.prototype) {
657
+ throw new ContractValidationError(path, "must use the exact Object prototype");
658
+ }
659
+ for (const key of Reflect.ownKeys(value)) {
660
+ if (typeof key !== "string") throw new ContractValidationError(path, "symbol properties are not supported");
661
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
662
+ if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
663
+ throw new ContractValidationError(`${path}.${key}`, "must be an own enumerable data property");
664
+ }
665
+ assertBossCanonicalData(descriptor.value, `${path}.${key}`, seen);
666
+ }
667
+ }
668
+ function parseBossAdvertisementReadiness(value) {
669
+ assertBossCanonicalData(value, "$.readiness");
670
+ assertRecord(value);
671
+ assertExactKeys(value, BOSS_ADVERTISEMENT_PREDICATES);
672
+ const parsed = {};
673
+ for (const predicate of BOSS_ADVERTISEMENT_PREDICATES) {
674
+ const enabled = ownDataValue(value, predicate);
675
+ if (typeof enabled !== "boolean") {
676
+ throw new ContractValidationError(`$.readiness.${predicate}`, "must be a boolean");
677
+ }
678
+ parsed[predicate] = enabled;
679
+ }
680
+ return parsed;
681
+ }
682
+ function missingBossAdvertisementPredicates(readiness = DORMANT_BOSS_ADVERTISEMENT_READINESS) {
683
+ const parsed = parseBossAdvertisementReadiness(readiness);
684
+ return BOSS_ADVERTISEMENT_PREDICATES.filter((predicate) => parsed[predicate] !== true);
685
+ }
686
+ function bossCapabilityAdvertisement(readiness = DORMANT_BOSS_ADVERTISEMENT_READINESS) {
687
+ if (missingBossAdvertisementPredicates(readiness).length > 0) return void 0;
688
+ const features = [{
689
+ version: BROKER_FEATURE_ATTESTATION_VERSION,
690
+ feature: BOSS_RUN_FEATURE,
691
+ featureVersion: BOSS_RUN_FEATURE_VERSION,
692
+ semanticsHash: BOSS_RUN_FEATURE_SEMANTICS_HASH,
693
+ controlEnvelopeVersion: BOSS_CONTROL_ENVELOPE_VERSION,
694
+ capabilityDigest: BOSS_CAPABILITY_FEATURE_DIGEST
695
+ }];
696
+ return parseBrokerCapabilityAdvertisement({
697
+ baseProtocolVersion: INTERCOM_BASE_PROTOCOL_VERSION,
698
+ features,
699
+ protocolFeatureContractHash: BOSS_RUN_PROTOCOL_FEATURE_CONTRACT_HASH,
700
+ featureSetHash: brokerFeatureSetHash(features),
701
+ controlEnvelopeVersion: BOSS_CONTROL_ENVELOPE_VERSION,
702
+ capabilityDigest: BOSS_CAPABILITY_FEATURE_DIGEST
703
+ });
704
+ }
705
+ function optionalOwnDataValue(value, key) {
706
+ assertRecord(value);
707
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
708
+ if (descriptor === void 0) return void 0;
709
+ if (!descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
710
+ throw new ContractValidationError(`$.${key}`, "must be an own enumerable data property");
711
+ }
712
+ return descriptor.value;
713
+ }
714
+ function ownDataValue(value, key) {
715
+ const result = optionalOwnDataValue(value, key);
716
+ if (result === void 0) throw new ContractValidationError(`$.${key}`, "is required");
717
+ return result;
718
+ }
719
+ function parseBossParticipantRegistrationMetadata(value) {
720
+ assertBossCanonicalData(value);
721
+ assertRecord(value);
722
+ assertExactKeys(value, ["featureContract", "credential"]);
723
+ const featureContract = parseBossRunFeatureContract(ownDataValue(value, "featureContract"));
724
+ if (featureContract.baseProtocolVersion !== INTERCOM_BASE_PROTOCOL_VERSION || canonicalJson(featureContract) !== canonicalJson(BOSS_RUN_FEATURE_CONTRACT)) {
725
+ throw new ContractValidationError("$.featureContract", "must exactly negotiate boss-run-v1 over base protocol v3");
726
+ }
727
+ const credential = parseBossParticipantCredentialEnvelope(ownDataValue(value, "credential"));
728
+ if (credential.namespace !== featureContract.feature) {
729
+ throw new ContractValidationError("$.credential.namespace", "must match the negotiated feature namespace");
730
+ }
731
+ return { featureContract, credential };
732
+ }
733
+ function exactRegistrationKind(session, value) {
734
+ assertBossCanonicalData(session, "$.session");
735
+ assertRecord(session);
736
+ const boss = optionalOwnDataValue(session, "boss");
737
+ if (boss === void 0) {
738
+ if (value === void 0) return "ordinary";
739
+ throw new ContractValidationError("$.registrationKind", "must be absent when Boss metadata is absent");
740
+ }
741
+ if (value !== "boss") {
742
+ throw new ContractValidationError("$.registrationKind", "must be boss when Boss metadata is present");
743
+ }
744
+ return "boss";
745
+ }
746
+ function parseExactRegistrationFrame(value) {
747
+ assertBossCanonicalData(value, "$.register");
748
+ assertRecord(value);
749
+ const session = ownDataValue(value, "session");
750
+ assertRecord(session);
751
+ const registrationKind = optionalOwnDataValue(value, "registrationKind");
752
+ const kind = exactRegistrationKind(session, registrationKind);
753
+ if (kind === "ordinary") {
754
+ assertExactKeys(value, ["type", "protocol", "version", "session"], ["sessionId", "stateId", "access", "scopeId"]);
755
+ assertExactKeys(session, ORDINARY_SESSION_REGISTRATION_KEYS, OPTIONAL_SESSION_REGISTRATION_KEYS);
756
+ } else {
757
+ assertExactKeys(value, ["type", "registrationKind", "protocol", "version", "session"], ["sessionId", "stateId", "scopeId"]);
758
+ assertExactKeys(session, [...ORDINARY_SESSION_REGISTRATION_KEYS, "boss"], OPTIONAL_SESSION_REGISTRATION_KEYS);
759
+ parseBossParticipantRegistrationMetadata(ownDataValue(session, "boss"));
760
+ }
761
+ if (ownDataValue(value, "type") !== "register") {
762
+ throw new ContractValidationError("$.register.type", "must be register");
763
+ }
764
+ return value;
765
+ }
766
+ function parseExactRegisteredFrame(value, expected) {
767
+ assertBossCanonicalData(value, "$.registered");
768
+ assertRecord(value);
769
+ if (expected === "boss") {
770
+ assertExactKeys(value, ["type", "registrationKind", "sessionId", "protocol", "version", "capabilities", "boss"]);
771
+ if (ownDataValue(value, "registrationKind") !== "boss") {
772
+ throw new ContractValidationError("$.registered.registrationKind", "must be boss");
773
+ }
774
+ const sessionId = ownDataValue(value, "sessionId");
775
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
776
+ throw new ContractValidationError("$.registered.sessionId", "must be a non-empty string");
777
+ }
778
+ const advertisement = parseBrokerCapabilityAdvertisement(ownDataValue(value, "capabilities"));
779
+ const expectedAdvertisement = bossCapabilityAdvertisement({
780
+ protectedProvider: true,
781
+ brokerIdentity: true,
782
+ credentialRegistry: true,
783
+ authorityTransitions: true,
784
+ participantHealth: true
785
+ });
786
+ const bossFeature = advertisement.features.find((feature) => feature.feature === BOSS_RUN_FEATURE);
787
+ if (bossFeature === void 0 || canonicalJson(bossFeature) !== canonicalJson(expectedAdvertisement.features[0]) || advertisement.baseProtocolVersion !== expectedAdvertisement.baseProtocolVersion || advertisement.protocolFeatureContractHash !== expectedAdvertisement.protocolFeatureContractHash || advertisement.controlEnvelopeVersion !== expectedAdvertisement.controlEnvelopeVersion || advertisement.capabilityDigest !== expectedAdvertisement.capabilityDigest) throw new ContractValidationError("$.registered.capabilities", "must exactly echo the requested boss-run-v1 contract");
788
+ parseBossParticipantBindingMetadata(ownDataValue(value, "boss"), sessionId);
789
+ } else if (expected === "ordinary-remote") {
790
+ assertExactKeys(value, ["type", "sessionId", "protocol", "version", "remoteAccess", "access"]);
791
+ } else {
792
+ assertExactKeys(value, ["type", "sessionId", "protocol", "version"]);
793
+ }
794
+ if (ownDataValue(value, "type") !== "registered") {
795
+ throw new ContractValidationError("$.registered.type", "must be registered");
796
+ }
797
+ return value;
798
+ }
799
+ function parseBossParticipantBindingMetadata(value, expectedSessionId) {
800
+ assertBossCanonicalData(value);
801
+ assertRecord(value);
802
+ assertExactKeys(
803
+ value,
804
+ ["featureContract", "binding", "brokerIdentityVerified"],
805
+ ["assignedParticipantIds", "requestingPrincipalId", "workerIdentity", "participantState"]
806
+ );
807
+ const featureContract = parseBossRunFeatureContract(ownDataValue(value, "featureContract"));
808
+ if (featureContract.baseProtocolVersion !== INTERCOM_BASE_PROTOCOL_VERSION || canonicalJson(featureContract) !== canonicalJson(BOSS_RUN_FEATURE_CONTRACT)) {
809
+ throw new ContractValidationError("$.featureContract", "must exactly bind boss-run-v1 over base protocol v3");
810
+ }
811
+ const binding = parseBossParticipantBinding(ownDataValue(value, "binding"));
812
+ if (ownDataValue(value, "brokerIdentityVerified") !== true) {
813
+ throw new ContractValidationError("$.brokerIdentityVerified", "must be true for a broker-owned Boss binding");
814
+ }
815
+ if (expectedSessionId !== void 0 && binding.sessionId !== expectedSessionId) {
816
+ throw new ContractValidationError("$.binding.sessionId", "must match the registered intercom session");
817
+ }
818
+ const rawAssignedParticipantIds = optionalOwnDataValue(value, "assignedParticipantIds");
819
+ let assignedParticipantIds;
820
+ if (rawAssignedParticipantIds !== void 0) {
821
+ if (binding.role !== "manager" || !Array.isArray(rawAssignedParticipantIds) || rawAssignedParticipantIds.some((entry) => typeof entry !== "string" || entry.length === 0) || new Set(rawAssignedParticipantIds).size !== rawAssignedParticipantIds.length) {
822
+ throw new ContractValidationError("$.assignedParticipantIds", "must be a unique participant list present only for a Manager");
823
+ }
824
+ assignedParticipantIds = rawAssignedParticipantIds;
825
+ }
826
+ if (binding.role === "manager" && assignedParticipantIds === void 0) {
827
+ throw new ContractValidationError("$.assignedParticipantIds", "is required for a Manager policy binding");
828
+ }
829
+ const rawRequestingPrincipalId = optionalOwnDataValue(value, "requestingPrincipalId");
830
+ if (binding.role === "council" !== (typeof rawRequestingPrincipalId === "string" && rawRequestingPrincipalId.length > 0)) {
831
+ throw new ContractValidationError("$.requestingPrincipalId", "is required exactly for a Council policy binding");
832
+ }
833
+ const requestingPrincipalId = typeof rawRequestingPrincipalId === "string" ? rawRequestingPrincipalId : void 0;
834
+ const rawWorkerIdentity = optionalOwnDataValue(value, "workerIdentity");
835
+ const rawParticipantState = optionalOwnDataValue(value, "participantState");
836
+ if (rawWorkerIdentity === void 0 !== (rawParticipantState === void 0)) {
837
+ throw new ContractValidationError("$.workerIdentity", "workerIdentity and participantState must be supplied together");
838
+ }
839
+ const workerIdentity = rawWorkerIdentity === void 0 ? void 0 : parseWorkerIdentityV2(rawWorkerIdentity);
840
+ const participantState = rawParticipantState === void 0 ? void 0 : parseParticipantState(rawParticipantState, "$.participantState");
841
+ if (workerIdentity !== void 0 && (!("bossRunId" in workerIdentity) || workerIdentity.bossRunId !== binding.bossRunId || workerIdentity.participantId !== binding.participantId || workerIdentity.bindingEpoch !== binding.bindingEpoch)) throw new ContractValidationError("$.workerIdentity", "must match the broker-owned participant binding");
842
+ return {
843
+ featureContract,
844
+ binding,
845
+ brokerIdentityVerified: true,
846
+ ...assignedParticipantIds === void 0 ? {} : { assignedParticipantIds: [...assignedParticipantIds] },
847
+ ...requestingPrincipalId === void 0 ? {} : { requestingPrincipalId },
848
+ ...workerIdentity === void 0 ? {} : { workerIdentity, participantState }
849
+ };
850
+ }
851
+ var BOSS_CONTROL_KIND_BY_TYPE = {
852
+ "boss.assignment.created": "assignment_request",
853
+ "boss.assignment.accepted": "assignment_response",
854
+ "boss.assignment.checkpoint": "assignment_response",
855
+ "boss.assignment.submitted": "assignment_response",
856
+ "boss.assignment.rejected": "assignment_response",
857
+ "boss.assignment.cancelled": "lifecycle",
858
+ "boss.staffing.requested": "staffing",
859
+ "boss.staffing.resolved": "staffing",
860
+ "boss.review.requested": "review_request",
861
+ "boss.review.submitted": "review_result",
862
+ "boss.council.requested": "review_request",
863
+ "boss.council.submitted": "review_result",
864
+ "boss.proof.submitted": "proof",
865
+ "boss.worker.health": "health",
866
+ "boss.worker.blocked": "health",
867
+ "boss.worker.failed": "health",
868
+ "boss.worker.notice": "lifecycle",
869
+ "boss.worker.notice_delivery_failed": "lifecycle",
870
+ "boss.decision.required": "decision"
871
+ };
872
+ function bossControlKind(envelopeValue) {
873
+ assertBossCanonicalData(envelopeValue);
874
+ const envelope = parseBossControlEnvelope(envelopeValue);
875
+ return { envelope, controlKind: BOSS_CONTROL_KIND_BY_TYPE[envelope.type] };
876
+ }
877
+
878
+ // codex/bridge-config.ts
879
+ var DEFAULT_BRIDGE_CONFIG_PATH = join2(getIntercomDirPath(), "codex-bridge.json");
880
+ var DEFAULT_BRIDGE_STATE_PATH = join2(getIntercomDirPath(), "codex-bridge-state.json");
881
+ function isRecord(value) {
882
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !nodeUtilTypes2.isProxy(value);
883
+ }
884
+ function optionalString(value, field) {
885
+ if (value === void 0 || value === null) return void 0;
886
+ if (typeof value !== "string") throw new Error(`${field} must be a string`);
887
+ const trimmed = value.trim();
888
+ return trimmed || void 0;
889
+ }
890
+ function requireString(value, field) {
891
+ const result = optionalString(value, field);
892
+ if (!result) throw new Error(`${field} must be a non-empty string`);
893
+ return result;
894
+ }
895
+ function parseHardenedBossClientKind(value, field) {
896
+ if (value === void 0 || value === null) return void 0;
897
+ if (value === "boss_participant" || value === "boss_reviewer") return value;
898
+ throw new Error(`${field} must be boss_participant or boss_reviewer`);
899
+ }
900
+ function sandboxType(value) {
901
+ if (!isRecord(value)) return void 0;
902
+ return typeof value.type === "string" ? value.type : void 0;
903
+ }
904
+ function assertHardenedBossAgentConfig(agent) {
905
+ if (nodeUtilTypes2.isProxy(agent)) throw new Error("Hardened Boss agent config must not be a proxy");
906
+ assertBossCanonicalData(agent, "$.agent");
907
+ if (agent.bossClient === void 0) return;
908
+ if (agent.sandboxPolicy !== void 0 && !isRecord(agent.sandboxPolicy)) {
909
+ throw new Error(`${agent.bossClient} sandboxPolicy must be a plain object`);
910
+ }
911
+ if (agent.approvalPolicy !== void 0 && typeof agent.approvalPolicy !== "string") {
912
+ throw new Error(`${agent.bossClient} approvalPolicy must be a string`);
913
+ }
914
+ const type = sandboxType(agent.sandboxPolicy);
915
+ if (type === "dangerFullAccess" || type === "danger-full-access") {
916
+ throw new Error(`${agent.bossClient} cannot use danger-full-access`);
917
+ }
918
+ if (agent.approvalPolicy === "never") {
919
+ throw new Error(`${agent.bossClient} cannot disable approval checks`);
920
+ }
921
+ if (agent.bossClient === "boss_reviewer" && type !== void 0 && type !== "readOnly" && type !== "read-only") {
922
+ throw new Error("boss_reviewer must use a read-only sandbox");
923
+ }
924
+ const canonicalCwd = resolve2(agent.cwd);
925
+ if (agent.bossClient === "boss_participant" && canonicalCwd === parsePath(canonicalCwd).root) {
926
+ throw new Error("boss_participant workspace root must not be a filesystem root");
927
+ }
928
+ if (isRecord(agent.sandboxPolicy)) {
929
+ assertBossCanonicalData(agent.sandboxPolicy, "$.agent.sandboxPolicy");
930
+ const allowedKeys = type === "workspaceWrite" || type === "workspace-write" ? /* @__PURE__ */ new Set(["type", "writableRoots", "networkAccess"]) : /* @__PURE__ */ new Set(["type", "networkAccess"]);
931
+ const keys = Reflect.ownKeys(agent.sandboxPolicy);
932
+ if (keys.some((key) => typeof key !== "string" || !allowedKeys.has(key))) {
933
+ throw new Error(`${agent.bossClient} sandboxPolicy contains unsupported capability fields`);
934
+ }
935
+ if (agent.sandboxPolicy.networkAccess !== false) {
936
+ throw new Error(`${agent.bossClient} networkAccess must be false`);
937
+ }
938
+ if (type !== "readOnly" && type !== "read-only" && type !== "workspaceWrite" && type !== "workspace-write") {
939
+ throw new Error(`${agent.bossClient} sandboxPolicy type is unsupported`);
940
+ }
941
+ }
942
+ if (isRecord(agent.sandboxPolicy) && (type === "workspaceWrite" || type === "workspace-write")) {
943
+ const roots = agent.sandboxPolicy.writableRoots;
944
+ assertBossCanonicalData(roots, "$.agent.sandboxPolicy.writableRoots");
945
+ if (!Array.isArray(roots) || nodeUtilTypes2.isProxy(roots) || roots.some((root) => typeof root !== "string")) {
946
+ throw new Error(`${agent.bossClient} writableRoots must be a dense string array`);
947
+ }
948
+ if (agent.bossClient === "boss_reviewer" || roots.length !== 1 || resolve2(roots[0]) !== canonicalCwd) {
949
+ throw new Error(`${agent.bossClient} writable roots must be restricted to the agent cwd`);
950
+ }
951
+ }
952
+ if (agent.bossClient === "boss_participant") {
953
+ throw new Error("boss_participant requires unavailable broker-owned assigned workspace authority");
954
+ }
955
+ }
956
+ function bridgeAgentApprovalPolicy(agent) {
957
+ return agent.approvalPolicy ?? (agent.bossClient === void 0 ? "never" : HARDENED_BOSS_CODEX_DEFAULTS[agent.bossClient].approvalPolicy);
958
+ }
959
+ function bridgeAgentDefaultSandbox(agent) {
960
+ return agent.bossClient === void 0 ? void 0 : HARDENED_BOSS_CODEX_DEFAULTS[agent.bossClient].sandbox;
961
+ }
962
+ function assertHardenedBossBridgeConfig(config) {
963
+ assertBossCanonicalData(config, "$.bridgeConfig");
964
+ if (nodeUtilTypes2.isProxy(config) || nodeUtilTypes2.isProxy(config.agents) || !Array.isArray(config.agents)) {
965
+ throw new Error("Bridge config and agents must be plain non-proxy data");
966
+ }
967
+ for (let index = 0; index < config.agents.length; index += 1) {
968
+ if (!Object.hasOwn(config.agents, index)) throw new Error("Bridge agents must not be sparse");
969
+ const agent = config.agents[index];
970
+ if (typeof agent !== "object" || agent === null || Array.isArray(agent) || nodeUtilTypes2.isProxy(agent)) {
971
+ throw new Error("Bridge agents must be plain non-proxy objects");
972
+ }
973
+ }
974
+ if (!config.agents.some((agent) => agent.bossClient !== void 0)) return;
975
+ if (config.appServer !== void 0) {
976
+ assertBossCanonicalData(config.appServer, "$.appServer");
977
+ if (nodeUtilTypes2.isProxy(config.appServer)) throw new Error("Hardened Boss app-server config must not be a proxy");
978
+ if (config.appServer.command !== void 0 || config.appServer.startDaemonCommand !== void 0) {
979
+ throw new Error("Hardened Boss bridge cannot use caller-provided app-server commands");
980
+ }
981
+ }
982
+ for (const args of [config.appServer?.args, config.appServer?.startDaemonArgs]) {
983
+ if (!args) continue;
984
+ assertBossCanonicalData(args, "$.appServer.argv");
985
+ if (nodeUtilTypes2.isProxy(args) || args.some((arg) => typeof arg !== "string")) throw new Error("Hardened Boss bridge arguments must be dense string arrays");
986
+ for (const arg of args) {
987
+ if (arg === "--") {
988
+ continue;
989
+ }
990
+ if (arg.length > 2 && arg.startsWith("-C")) {
991
+ throw new Error("Hardened Boss bridge cannot pass launch escape -C to app-server");
992
+ }
993
+ const optionName = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
994
+ if (["-c", "--config", "-p", "--profile", "--enable", "--disable"].includes(optionName) || optionName.startsWith("-c") && optionName !== "-C" || optionName.startsWith("-p")) {
995
+ throw new Error(`Hardened Boss bridge cannot pass raw ${optionName} or profile configuration to app-server`);
996
+ }
997
+ if (["--dangerously-bypass-approvals-and-sandbox", "--dangerously-bypass-hook-trust", "--yolo", "--add-dir", "--cd", "-C"].includes(optionName)) {
998
+ throw new Error(`Hardened Boss bridge cannot pass launch escape ${optionName} to app-server`);
999
+ }
1000
+ if (["--sandbox", "--ask-for-approval"].includes(optionName) || optionName === "-s" || optionName === "-a" || optionName.startsWith("-s") || optionName.startsWith("-a")) {
1001
+ throw new Error(`Hardened Boss bridge cannot pass policy override ${optionName} to app-server`);
1002
+ }
1003
+ if (optionName.startsWith("-") && /(?:yolo|danger|bypass)/i.test(optionName)) {
1004
+ throw new Error(`Hardened Boss bridge cannot pass launch escape ${optionName} to app-server`);
1005
+ }
1006
+ }
1007
+ }
1008
+ for (const agent of config.agents) assertHardenedBossAgentConfig(agent);
1009
+ }
1010
+ function normalizeAgent(raw, index) {
1011
+ if (!isRecord(raw)) throw new Error(`agents[${index}] must be an object`);
1012
+ const id = requireString(raw.id, `agents[${index}].id`);
1013
+ const name = optionalString(raw.name, `agents[${index}].name`) ?? id;
1014
+ const agent = {
1015
+ id,
1016
+ name,
1017
+ cwd: resolve2(optionalString(raw.cwd, `agents[${index}].cwd`) ?? processCwd()),
1018
+ model: optionalString(raw.model, `agents[${index}].model`),
1019
+ threadId: optionalString(raw.threadId, `agents[${index}].threadId`),
1020
+ instructions: optionalString(raw.instructions, `agents[${index}].instructions`),
1021
+ approvalPolicy: raw.approvalPolicy,
1022
+ sandboxPolicy: raw.sandboxPolicy,
1023
+ bossClient: parseHardenedBossClientKind(raw.bossClient, `agents[${index}].bossClient`)
1024
+ };
1025
+ assertHardenedBossAgentConfig(agent);
1026
+ return agent;
1027
+ }
1028
+ function defaultBridgeConfig(env = process.env) {
1029
+ const id = env.CODEX_INTERCOM_BRIDGE_ID?.trim() || "codex-worker";
1030
+ const bossClient = parseHardenedBossClientKind(env.CODEX_INTERCOM_BOSS_CLIENT?.trim(), "CODEX_INTERCOM_BOSS_CLIENT");
1031
+ const agent = {
1032
+ id,
1033
+ name: env.CODEX_INTERCOM_BRIDGE_NAME?.trim() || id,
1034
+ cwd: resolve2(env.CODEX_INTERCOM_BRIDGE_CWD?.trim() || processCwd()),
1035
+ model: env.CODEX_INTERCOM_BRIDGE_MODEL?.trim() || void 0,
1036
+ instructions: env.CODEX_INTERCOM_BRIDGE_INSTRUCTIONS?.trim() || void 0,
1037
+ ...bossClient === void 0 ? {} : { bossClient }
1038
+ };
1039
+ assertHardenedBossAgentConfig(agent);
1040
+ return {
1041
+ statePath: env.CODEX_INTERCOM_BRIDGE_STATE?.trim() || DEFAULT_BRIDGE_STATE_PATH,
1042
+ agents: [agent]
1043
+ };
1044
+ }
1045
+ function loadBridgeConfig(path = process.env.CODEX_INTERCOM_BRIDGE_CONFIG || DEFAULT_BRIDGE_CONFIG_PATH) {
1046
+ if (!existsSync(path)) return defaultBridgeConfig();
1047
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1048
+ if (!isRecord(parsed)) throw new Error("Bridge config must be a JSON object");
1049
+ if (!Array.isArray(parsed.agents)) throw new Error("Bridge config requires an agents array");
1050
+ const appServer = isRecord(parsed.appServer) ? {
1051
+ command: optionalString(parsed.appServer.command, "appServer.command"),
1052
+ args: Array.isArray(parsed.appServer.args) ? parsed.appServer.args.map((arg, index) => requireString(arg, `appServer.args[${index}]`)) : void 0,
1053
+ transport: parsed.appServer.transport === "unix-websocket" || parsed.appServer.transport === "stdio" ? parsed.appServer.transport : void 0,
1054
+ socketPath: optionalString(parsed.appServer.socketPath, "appServer.socketPath"),
1055
+ startDaemon: typeof parsed.appServer.startDaemon === "boolean" ? parsed.appServer.startDaemon : void 0,
1056
+ startDaemonCommand: optionalString(parsed.appServer.startDaemonCommand, "appServer.startDaemonCommand"),
1057
+ startDaemonArgs: Array.isArray(parsed.appServer.startDaemonArgs) ? parsed.appServer.startDaemonArgs.map((arg, index) => requireString(arg, `appServer.startDaemonArgs[${index}]`)) : void 0
1058
+ } : void 0;
1059
+ return {
1060
+ statePath: resolve2(optionalString(parsed.statePath, "statePath") ?? DEFAULT_BRIDGE_STATE_PATH),
1061
+ agents: parsed.agents.map(normalizeAgent),
1062
+ ...appServer ? { appServer } : {}
1063
+ };
1064
+ }
1065
+ function loadBridgeState(path) {
1066
+ if (!existsSync(path)) return { agents: {} };
1067
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1068
+ if (!isRecord(parsed) || !isRecord(parsed.agents)) return { agents: {} };
1069
+ const agents = {};
1070
+ for (const [id, value] of Object.entries(parsed.agents)) {
1071
+ if (!isRecord(value) || typeof value.threadId !== "string") continue;
1072
+ agents[id] = {
1073
+ threadId: value.threadId,
1074
+ updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : 0
1075
+ };
1076
+ }
1077
+ return { agents };
1078
+ }
1079
+ function saveBridgeState(path, state) {
1080
+ mkdirSync2(dirname(path), { recursive: true });
1081
+ writeFileSync(path, `${JSON.stringify(state, null, 2)}
1082
+ `, { mode: 384 });
1083
+ restrictIntercomRuntimeFile(path);
1084
+ }
1085
+
1086
+ // broker/client.ts
1087
+ import { EventEmitter as EventEmitter2 } from "events";
1088
+ import net2 from "net";
1089
+ import { randomUUID as randomUUID2 } from "crypto";
1090
+ import { POLICY_SEMANTICS_HASH, POLICY_SEMANTICS_VERSION } from "@ctliz/agent-intercom-core";
1091
+ import {
1092
+ BOSS_RUN_FEATURE as BOSS_RUN_FEATURE2,
1093
+ parseBrokerCapabilityAdvertisement as parseBrokerCapabilityAdvertisement2
1094
+ } from "@ctliz/agent-intercom-core/boss";
1095
+
1096
+ // broker/framing.ts
1097
+ var MAX_FRAME_BYTES = 1024 * 1024;
1098
+ function writeMessage(socket, msg) {
1099
+ const json = JSON.stringify(msg);
1100
+ const payload = Buffer.from(json, "utf-8");
1101
+ const header = Buffer.alloc(4);
1102
+ header.writeUInt32BE(payload.length, 0);
1103
+ socket.write(Buffer.concat([header, payload]));
1104
+ }
1105
+ function createMessageReader(onMessage, onError, maxFrameBytes = MAX_FRAME_BYTES) {
1106
+ let buffer = Buffer.alloc(0);
1107
+ function reportMessage(payload) {
1108
+ let msg;
1109
+ try {
1110
+ msg = JSON.parse(payload.toString("utf-8"));
1111
+ } catch (error) {
1112
+ const message = error instanceof Error ? error.message : String(error);
1113
+ onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error }));
1114
+ return false;
1115
+ }
1116
+ try {
1117
+ onMessage(msg);
1118
+ return true;
1119
+ } catch (error) {
1120
+ const message = error instanceof Error ? error.message : String(error);
1121
+ onError(new Error(`Failed to handle intercom message: ${message}`, { cause: error }));
1122
+ return false;
1123
+ }
1124
+ }
1125
+ return (data) => {
1126
+ let remaining = data;
1127
+ while (remaining.length > 0) {
1128
+ if (buffer.length < 4) {
1129
+ const headerBytes = Math.min(4 - buffer.length, remaining.length);
1130
+ buffer = Buffer.concat([buffer, remaining.subarray(0, headerBytes)]);
1131
+ remaining = remaining.subarray(headerBytes);
1132
+ if (buffer.length < 4) {
1133
+ return;
1134
+ }
1135
+ }
1136
+ const length = buffer.readUInt32BE(0);
1137
+ if (length > maxFrameBytes) {
1138
+ buffer = Buffer.alloc(0);
1139
+ onError(new Error(`Intercom frame length ${length} exceeds maximum ${maxFrameBytes} bytes`));
1140
+ return;
1141
+ }
1142
+ const missingPayloadBytes = length - Math.max(0, buffer.length - 4);
1143
+ const payloadBytes = Math.min(missingPayloadBytes, remaining.length);
1144
+ if (payloadBytes > 0) {
1145
+ buffer = Buffer.concat([buffer, remaining.subarray(0, payloadBytes)]);
1146
+ remaining = remaining.subarray(payloadBytes);
1147
+ }
1148
+ if (buffer.length < 4 + length) {
1149
+ return;
1150
+ }
1151
+ const payload = buffer.subarray(4, 4 + length);
1152
+ buffer = Buffer.alloc(0);
1153
+ if (!reportMessage(payload)) {
1154
+ return;
1155
+ }
1156
+ }
1157
+ };
1158
+ }
1159
+
1160
+ // protocol-v4/contract.ts
1161
+ import {
1162
+ INTERCOM_PROTOCOL_NAME as INTERCOM_PROTOCOL_NAME2,
1163
+ INTERCOM_PROTOCOL_V4_SEMANTICS_HASH,
1164
+ INTERCOM_PROTOCOL_V4_VECTOR_SCHEMA_VERSION,
1165
+ INTERCOM_PROTOCOL_V4_VECTORS,
1166
+ INTERCOM_PROTOCOL_VERSION as INTERCOM_PROTOCOL_VERSION2,
1167
+ INTERCOM_SCOPE_ENV,
1168
+ INTERCOM_SCOPE_ID_PATTERN,
1169
+ INTERCOM_SCOPE_ID_PATTERN_SOURCE,
1170
+ intercomScopeIdFromEnv,
1171
+ parseIntercomScopeId,
1172
+ sameIntercomScope
1173
+ } from "@ctliz/agent-intercom-core/protocol-v4";
1174
+
1175
+ // outbound-outbox.ts
1176
+ import { createHash as createHash2 } from "crypto";
1177
+ import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2 } from "fs";
1178
+ import { join as join3 } from "path";
1179
+
1180
+ // durable-json.ts
1181
+ import { randomUUID } from "crypto";
1182
+ import { closeSync, fsyncSync, openSync, renameSync, writeFileSync as writeFileSync2 } from "fs";
1183
+ import { dirname as dirname2 } from "path";
1184
+ function writeDurableJson(filePath, value) {
1185
+ const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
1186
+ writeFileSync2(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE });
1187
+ const fileDescriptor = openSync(temporaryPath, "r");
1188
+ try {
1189
+ fsyncSync(fileDescriptor);
1190
+ } finally {
1191
+ closeSync(fileDescriptor);
1192
+ }
1193
+ renameSync(temporaryPath, filePath);
1194
+ restrictIntercomRuntimeFile(filePath);
1195
+ if (process.platform !== "win32") {
1196
+ const directoryDescriptor = openSync(dirname2(filePath), "r");
1197
+ try {
1198
+ fsyncSync(directoryDescriptor);
1199
+ } finally {
1200
+ closeSync(directoryDescriptor);
1201
+ }
1202
+ }
1203
+ }
1204
+
1205
+ // outbound-outbox.ts
1206
+ var OUTBOX_STATE_VERSION = 1;
1207
+ var MAX_OUTBOX_MESSAGES = 256;
1208
+ function fingerprint(entry) {
1209
+ return JSON.stringify({
1210
+ to: entry.to,
1211
+ replyTo: entry.message.replyTo,
1212
+ expectsReply: entry.message.expectsReply,
1213
+ content: entry.message.content
1214
+ });
1215
+ }
1216
+ function isStoredOutboundMessage(value) {
1217
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1218
+ const entry = value;
1219
+ if (typeof entry.to !== "string" || typeof entry.queuedAt !== "number") return false;
1220
+ if (typeof entry.message !== "object" || entry.message === null || Array.isArray(entry.message)) return false;
1221
+ const message = entry.message;
1222
+ return typeof message.id === "string" && typeof message.timestamp === "number" && typeof message.content === "object" && message.content !== null && typeof message.content.text === "string";
1223
+ }
1224
+ function fileName(sessionId) {
1225
+ return `${createHash2("sha256").update(sessionId).digest("hex")}.json`;
1226
+ }
1227
+ var PersistentOutboundOutbox = class {
1228
+ directory;
1229
+ filePath;
1230
+ state;
1231
+ constructor(sessionId, intercomDir = getIntercomDirPath()) {
1232
+ ensureIntercomRuntimeDir(intercomDir);
1233
+ this.directory = join3(intercomDir, "outbox");
1234
+ mkdirSync3(this.directory, { recursive: true, mode: INTERCOM_DIR_MODE });
1235
+ if (process.platform !== "win32") chmodSync2(this.directory, INTERCOM_DIR_MODE);
1236
+ this.filePath = join3(this.directory, fileName(sessionId));
1237
+ this.state = this.load();
1238
+ }
1239
+ list() {
1240
+ return this.state.entries.map((entry) => ({ ...entry, message: { ...entry.message, content: { ...entry.message.content } } }));
1241
+ }
1242
+ enqueue(to, message) {
1243
+ const existing = this.state.entries.find((entry) => entry.message.id === message.id);
1244
+ if (existing) {
1245
+ if (fingerprint(existing) !== fingerprint({ to, message })) {
1246
+ throw new Error(`Message ID ${message.id} is already queued with a different payload`);
1247
+ }
1248
+ return "existing";
1249
+ }
1250
+ if (this.state.entries.length >= MAX_OUTBOX_MESSAGES) {
1251
+ throw new Error(`Durable outbox is full (${MAX_OUTBOX_MESSAGES} messages)`);
1252
+ }
1253
+ this.state.entries.push({ to, message, queuedAt: Date.now() });
1254
+ this.persist();
1255
+ return "added";
1256
+ }
1257
+ remove(messageId) {
1258
+ const remaining = this.state.entries.filter((entry) => entry.message.id !== messageId);
1259
+ if (remaining.length === this.state.entries.length) return;
1260
+ this.state.entries = remaining;
1261
+ this.persist();
1262
+ }
1263
+ clear() {
1264
+ if (this.state.entries.length === 0) return;
1265
+ this.state.entries = [];
1266
+ this.persist();
1267
+ }
1268
+ load() {
1269
+ if (!existsSync2(this.filePath)) return { version: OUTBOX_STATE_VERSION, entries: [] };
1270
+ try {
1271
+ const parsed = JSON.parse(readFileSync3(this.filePath, "utf-8"));
1272
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("expected object");
1273
+ const state = parsed;
1274
+ if (state.version !== OUTBOX_STATE_VERSION || !Array.isArray(state.entries) || !state.entries.every(isStoredOutboundMessage)) {
1275
+ throw new Error("invalid outbox state");
1276
+ }
1277
+ return { version: OUTBOX_STATE_VERSION, entries: state.entries };
1278
+ } catch {
1279
+ const corruptPath = `${this.filePath}.corrupt-${Date.now()}`;
1280
+ renameSync2(this.filePath, corruptPath);
1281
+ restrictIntercomRuntimeFile(corruptPath);
1282
+ return { version: OUTBOX_STATE_VERSION, entries: [] };
1283
+ }
1284
+ }
1285
+ persist() {
1286
+ writeDurableJson(this.filePath, this.state);
1287
+ }
1288
+ };
1289
+
1290
+ // boss-control-outbox.ts
1291
+ import { createHash as createHash3 } from "node:crypto";
1292
+ import { chmodSync as chmodSync3, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync3 } from "node:fs";
1293
+ import { join as join4 } from "node:path";
1294
+ import { canonicalHash } from "@ctliz/agent-intercom-core/canonical";
1295
+ import { parseBossControlEnvelope as parseBossControlEnvelope2 } from "@ctliz/agent-intercom-core/boss";
1296
+ var BOSS_CONTROL_OUTBOX_VERSION = 2;
1297
+ var MAX_BOSS_CONTROL_OUTBOX_ENTRIES = 256;
1298
+ function scope(envelope) {
1299
+ return canonicalHash("agent-intercom-codex/boss-control/outbox-scope/v1", {
1300
+ bossRunId: envelope.bossRunId,
1301
+ participantId: envelope.participantId,
1302
+ bindingEpoch: Number(envelope.bindingEpoch),
1303
+ idempotencyKey: envelope.idempotencyKey
1304
+ });
1305
+ }
1306
+ function fingerprint2(to, envelope) {
1307
+ const { messageId: _transportMessageId, ...stableEnvelope } = envelope;
1308
+ return canonicalHash("agent-intercom-codex/boss-control/outbox-request/v1", { to, envelope: stableEnvelope });
1309
+ }
1310
+ function exactKeys(value, required, optional = []) {
1311
+ const permitted = /* @__PURE__ */ new Set([...required, ...optional]);
1312
+ const keys = Reflect.ownKeys(value);
1313
+ return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => typeof key === "string" && permitted.has(key));
1314
+ }
1315
+ function parseEntry(value) {
1316
+ assertBossCanonicalData(value, "$.bossControlOutbox.entries[]");
1317
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Invalid Boss outbox entry");
1318
+ const entry = value;
1319
+ if (!exactKeys(entry, ["to", "envelope", "scope", "fingerprint", "queuedAt", "state"], ["deliveryId"])) {
1320
+ throw new Error("Invalid Boss outbox entry fields");
1321
+ }
1322
+ if (typeof entry.to !== "string" || entry.to.length === 0 || typeof entry.scope !== "string" || !/^[a-f0-9]{64}$/.test(entry.scope) || typeof entry.fingerprint !== "string" || !/^[a-f0-9]{64}$/.test(entry.fingerprint) || typeof entry.queuedAt !== "number" || !Number.isSafeInteger(entry.queuedAt) || entry.state !== "queued" && entry.state !== "accepted" || entry.state === "queued" && Object.hasOwn(entry, "deliveryId") || entry.state === "accepted" && (!Object.hasOwn(entry, "deliveryId") || typeof entry.deliveryId !== "string" || entry.deliveryId.length === 0)) throw new Error("Invalid Boss outbox entry binding");
1323
+ const envelope = parseBossControlEnvelope2(entry.envelope);
1324
+ if (entry.scope !== scope(envelope) || entry.fingerprint !== fingerprint2(entry.to, envelope)) {
1325
+ throw new Error("Boss outbox entry canonical binding mismatch");
1326
+ }
1327
+ return {
1328
+ to: entry.to,
1329
+ envelope,
1330
+ scope: entry.scope,
1331
+ fingerprint: entry.fingerprint,
1332
+ queuedAt: entry.queuedAt,
1333
+ state: entry.state,
1334
+ ...entry.deliveryId === void 0 ? {} : { deliveryId: entry.deliveryId }
1335
+ };
1336
+ }
1337
+ function fileName2(sessionId) {
1338
+ return `${createHash3("sha256").update(sessionId).digest("hex")}.json`;
1339
+ }
1340
+ var PersistentBossControlOutbox = class {
1341
+ path;
1342
+ state;
1343
+ constructor(sessionId, intercomDir = getIntercomDirPath()) {
1344
+ ensureIntercomRuntimeDir(intercomDir);
1345
+ const directory = join4(intercomDir, "boss-control-outbox");
1346
+ mkdirSync4(directory, { recursive: true, mode: INTERCOM_DIR_MODE });
1347
+ if (process.platform !== "win32") chmodSync3(directory, INTERCOM_DIR_MODE);
1348
+ this.path = join4(directory, fileName2(sessionId));
1349
+ this.state = this.load();
1350
+ }
1351
+ list() {
1352
+ return structuredClone(this.state.entries);
1353
+ }
1354
+ find(idempotencyKey) {
1355
+ const entry = this.state.entries.find((candidate) => candidate.envelope.idempotencyKey === idempotencyKey);
1356
+ return entry === void 0 ? void 0 : structuredClone(entry);
1357
+ }
1358
+ enqueue(to, envelopeValue) {
1359
+ if (typeof to !== "string" || to.length === 0) throw new Error("Boss target session ID is required");
1360
+ assertBossCanonicalData(envelopeValue, "$.envelope");
1361
+ const envelope = parseBossControlEnvelope2(envelopeValue);
1362
+ const candidateScope = scope(envelope);
1363
+ const candidateFingerprint = fingerprint2(to, envelope);
1364
+ const existing = this.state.entries.find((entry) => entry.scope === candidateScope);
1365
+ if (existing) {
1366
+ if (existing.fingerprint !== candidateFingerprint) {
1367
+ throw new Error(`Boss idempotency key ${envelope.idempotencyKey} is queued with a different canonical request`);
1368
+ }
1369
+ if (existing.envelope.messageId !== envelope.messageId) {
1370
+ existing.envelope = envelope;
1371
+ existing.queuedAt = Date.now();
1372
+ this.persist();
1373
+ }
1374
+ return "existing";
1375
+ }
1376
+ if (this.state.entries.some((entry) => entry.envelope.messageId === envelope.messageId)) {
1377
+ throw new Error(`Boss message ID ${envelope.messageId} is queued with a different idempotency scope`);
1378
+ }
1379
+ if (this.state.entries.length >= MAX_BOSS_CONTROL_OUTBOX_ENTRIES) throw new Error("Durable Boss control outbox is full");
1380
+ this.state.entries.push({
1381
+ to,
1382
+ envelope,
1383
+ scope: candidateScope,
1384
+ fingerprint: candidateFingerprint,
1385
+ queuedAt: Date.now(),
1386
+ state: "queued"
1387
+ });
1388
+ this.persist();
1389
+ return "added";
1390
+ }
1391
+ markAccepted(idempotencyKey, messageId, deliveryId) {
1392
+ const entry = this.state.entries.find((candidate) => candidate.envelope.idempotencyKey === idempotencyKey);
1393
+ if (!entry || entry.envelope.messageId !== messageId || !deliveryId) {
1394
+ throw new Error("Boss acknowledgement does not match the durable outbox binding");
1395
+ }
1396
+ if (entry.state === "accepted") {
1397
+ if (entry.deliveryId !== deliveryId) throw new Error("Boss acknowledgement changed the durable deliveryId");
1398
+ return "already-accepted";
1399
+ }
1400
+ entry.state = "accepted";
1401
+ entry.deliveryId = deliveryId;
1402
+ this.persist();
1403
+ return "accepted";
1404
+ }
1405
+ removeCorrelated(idempotencyKey, messageId, deliveryId) {
1406
+ const index = this.state.entries.findIndex((candidate) => candidate.envelope.idempotencyKey === idempotencyKey);
1407
+ if (index < 0) throw new Error("Boss terminal result has no durable outbox binding");
1408
+ const entry = this.state.entries[index];
1409
+ if (entry.envelope.messageId !== messageId) throw new Error("Boss terminal result messageId does not match the durable caller");
1410
+ if (deliveryId === void 0) {
1411
+ if (entry.state !== "queued") throw new Error("Boss post-acceptance failure omitted the durable deliveryId");
1412
+ } else if (entry.state !== "accepted" || entry.deliveryId !== deliveryId) {
1413
+ throw new Error("Boss terminal result arrived before the matching durable acknowledgement");
1414
+ }
1415
+ this.state.entries.splice(index, 1);
1416
+ this.persist();
1417
+ }
1418
+ load() {
1419
+ if (!existsSync3(this.path)) return { version: BOSS_CONTROL_OUTBOX_VERSION, entries: [] };
1420
+ try {
1421
+ const parsed = JSON.parse(readFileSync4(this.path, "utf8"));
1422
+ assertBossCanonicalData(parsed, "$.bossControlOutbox");
1423
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("expected object");
1424
+ const state = parsed;
1425
+ if (!exactKeys(state, ["version", "entries"]) || state.version !== BOSS_CONTROL_OUTBOX_VERSION || !Array.isArray(state.entries)) {
1426
+ throw new Error("invalid Boss outbox state");
1427
+ }
1428
+ return { version: BOSS_CONTROL_OUTBOX_VERSION, entries: state.entries.map(parseEntry) };
1429
+ } catch (error) {
1430
+ const corruptPath = `${this.path}.corrupt-${Date.now()}`;
1431
+ renameSync3(this.path, corruptPath);
1432
+ restrictIntercomRuntimeFile(corruptPath);
1433
+ throw new Error(`Boss control outbox was corrupt and quarantined at ${corruptPath}`, { cause: error });
1434
+ }
1435
+ }
1436
+ persist() {
1437
+ writeDurableJson(this.path, this.state);
1438
+ }
1439
+ };
1440
+
1441
+ // broker/access-credential.ts
1442
+ import { readFileSync as readFileSync5 } from "fs";
1443
+ var ACCESS_CREDENTIAL_ENV = "AGENT_INTERCOM_ACCESS_CREDENTIAL_PATH";
1444
+ var ACCESS_CREDENTIAL_VERSION = 1;
1445
+ function nonEmptyString(value) {
1446
+ return typeof value === "string" && value.length > 0 && !value.includes("\0");
1447
+ }
1448
+ function loadRemoteAccessCredential(env = process.env) {
1449
+ const path = env[ACCESS_CREDENTIAL_ENV]?.trim();
1450
+ if (!path) return void 0;
1451
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
1452
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1453
+ throw new Error(`Invalid Agent Intercom access credential at ${path}`);
1454
+ }
1455
+ const credential = parsed;
1456
+ if (nonEmptyString(credential.enrollmentToken)) {
1457
+ return { path, access: { enrollmentToken: credential.enrollmentToken }, enrollment: true };
1458
+ }
1459
+ if (credential.version === ACCESS_CREDENTIAL_VERSION && nonEmptyString(credential.sessionCredential) && nonEmptyString(credential.sessionId) && typeof credential.generation === "number" && Number.isSafeInteger(credential.generation) && credential.generation > 0) {
1460
+ return {
1461
+ path,
1462
+ access: {
1463
+ sessionCredential: credential.sessionCredential,
1464
+ sessionId: credential.sessionId,
1465
+ generation: credential.generation
1466
+ },
1467
+ enrollment: false
1468
+ };
1469
+ }
1470
+ throw new Error(`Invalid Agent Intercom access credential at ${path}`);
1471
+ }
1472
+ function writeRemoteSessionCredential(path, sessionId, metadata) {
1473
+ if (!metadata.sessionCredential) {
1474
+ throw new Error("Remote enrollment response omitted the session credential");
1475
+ }
1476
+ writeDurableJson(path, {
1477
+ version: ACCESS_CREDENTIAL_VERSION,
1478
+ sessionCredential: metadata.sessionCredential,
1479
+ sessionId,
1480
+ generation: metadata.generation
1481
+ });
1482
+ }
1483
+
1484
+ // broker/boss-control-ledger.ts
1485
+ import { canonicalJson as canonicalJson2 } from "@ctliz/agent-intercom-core/canonical";
1486
+ var BOSS_CONTROL_FAILURE_CODES = /* @__PURE__ */ new Set([
1487
+ "INVALID_CONTROL",
1488
+ "IDEMPOTENCY_CONFLICT",
1489
+ "SESSION_NOT_FOUND",
1490
+ "POLICY_DENIED",
1491
+ "RECIPIENT_DISCONNECTED",
1492
+ "DELIVERY_TIMEOUT"
1493
+ ]);
1494
+ function exactStringKeys(value, required, optional = []) {
1495
+ const keys = Reflect.ownKeys(value);
1496
+ const permitted = /* @__PURE__ */ new Set([...required, ...optional]);
1497
+ return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => typeof key === "string" && permitted.has(key));
1498
+ }
1499
+ function parseBossControlResult(value) {
1500
+ assertBossCanonicalData(value, "$.bossControlResult");
1501
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1502
+ throw new Error("Boss control result must be an exact plain object");
1503
+ }
1504
+ const result = value;
1505
+ const base = typeof result.requestId === "string" && result.requestId.length > 0 && result.messageId === result.requestId && typeof result.idempotencyKey === "string" && result.idempotencyKey.length > 0;
1506
+ if (!base || result.type !== "boss_control_result") throw new Error("Invalid Boss control result binding");
1507
+ if (result.status === "delivered" && result.delivered === true && typeof result.deliveryId === "string" && result.deliveryId.length > 0 && exactStringKeys(result, ["type", "requestId", "messageId", "idempotencyKey", "status", "delivered", "deliveryId"])) return result;
1508
+ if (result.status === "rejected" && result.delivered === false && typeof result.code === "string" && BOSS_CONTROL_FAILURE_CODES.has(result.code) && typeof result.reason === "string" && result.reason.length > 0 && (!Object.hasOwn(result, "deliveryId") || typeof result.deliveryId === "string" && result.deliveryId.length > 0) && exactStringKeys(
1509
+ result,
1510
+ ["type", "requestId", "messageId", "idempotencyKey", "status", "delivered", "code", "reason"],
1511
+ ["deliveryId"]
1512
+ )) return result;
1513
+ throw new Error("Invalid Boss control result discriminant");
1514
+ }
1515
+ function parseBossControlAck(value) {
1516
+ assertBossCanonicalData(value, "$.bossControlAck");
1517
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1518
+ throw new Error("Boss control acknowledgement must be an exact plain object");
1519
+ }
1520
+ const ack = value;
1521
+ if (!exactStringKeys(ack, ["type", "requestId", "messageId", "idempotencyKey", "status", "deliveryId"]) || ack.type !== "boss_control_ack" || typeof ack.requestId !== "string" || ack.requestId.length === 0 || ack.messageId !== ack.requestId || typeof ack.idempotencyKey !== "string" || ack.idempotencyKey.length === 0 || ack.status !== "accepted" || typeof ack.deliveryId !== "string" || ack.deliveryId.length === 0) throw new Error("Invalid Boss control acknowledgement discriminant");
1522
+ return ack;
1523
+ }
1524
+
1525
+ // broker/client.ts
1526
+ import { types as nodeUtilTypes3 } from "node:util";
1527
+ function toError(error) {
1528
+ return error instanceof Error ? error : new Error(String(error));
1529
+ }
1530
+ function connectToBrokerTarget(target) {
1531
+ return typeof target === "string" ? net2.connect(target) : net2.connect({ host: target.host, port: target.port });
1532
+ }
1533
+ function isAttachment(value) {
1534
+ if (typeof value !== "object" || value === null) {
1535
+ return false;
1536
+ }
1537
+ const attachment = value;
1538
+ if (attachment.type !== "file" && attachment.type !== "snippet" && attachment.type !== "context") {
1539
+ return false;
1540
+ }
1541
+ if (typeof attachment.name !== "string" || typeof attachment.content !== "string") {
1542
+ return false;
1543
+ }
1544
+ return attachment.language === void 0 || typeof attachment.language === "string";
1545
+ }
1546
+ function isMessage(value) {
1547
+ if (typeof value !== "object" || value === null) {
1548
+ return false;
1549
+ }
1550
+ const message = value;
1551
+ if (typeof message.id !== "string" || typeof message.timestamp !== "number") {
1552
+ return false;
1553
+ }
1554
+ if (message.replyTo !== void 0 && typeof message.replyTo !== "string") {
1555
+ return false;
1556
+ }
1557
+ if (message.expectsReply !== void 0 && typeof message.expectsReply !== "boolean") {
1558
+ return false;
1559
+ }
1560
+ if (typeof message.content !== "object" || message.content === null) {
1561
+ return false;
1562
+ }
1563
+ const content = message.content;
1564
+ if (typeof content.text !== "string") {
1565
+ return false;
1566
+ }
1567
+ return content.attachments === void 0 || Array.isArray(content.attachments) && content.attachments.every(isAttachment);
1568
+ }
1569
+ function isSessionInfo(value) {
1570
+ if (typeof value !== "object" || value === null) {
1571
+ return false;
1572
+ }
1573
+ const session = value;
1574
+ 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") {
1575
+ return false;
1576
+ }
1577
+ if (session.name !== void 0 && typeof session.name !== "string") {
1578
+ return false;
1579
+ }
1580
+ if (session.status !== void 0 && typeof session.status !== "string") {
1581
+ return false;
1582
+ }
1583
+ if (session.peerUid !== void 0 && typeof session.peerUid !== "number") {
1584
+ return false;
1585
+ }
1586
+ if (session.trustedLocal !== void 0 && typeof session.trustedLocal !== "boolean") return false;
1587
+ if (session.origin !== void 0 && session.origin !== "local" && session.origin !== "remote") return false;
1588
+ if (session.remoteHostId !== void 0 && typeof session.remoteHostId !== "string") return false;
1589
+ if (session.parentSessionId !== void 0 && typeof session.parentSessionId !== "string") return false;
1590
+ if (session.rootSessionId !== void 0 && typeof session.rootSessionId !== "string") return false;
1591
+ if (session.generation !== void 0 && (typeof session.generation !== "number" || !Number.isSafeInteger(session.generation))) return false;
1592
+ if (session.canDelegate !== void 0 && typeof session.canDelegate !== "boolean") return false;
1593
+ for (const field of ["depth", "maxDepth", "maxChildren"]) {
1594
+ if (session[field] !== void 0 && (typeof session[field] !== "number" || !Number.isSafeInteger(session[field]))) return false;
1595
+ }
1596
+ if (session.boss !== void 0) {
1597
+ try {
1598
+ parseBossParticipantBindingMetadata(session.boss, session.id);
1599
+ } catch {
1600
+ return false;
1601
+ }
1602
+ }
1603
+ return true;
1604
+ }
1605
+ function isRemoteAccessMetadata(value) {
1606
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1607
+ const access = value;
1608
+ 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");
1609
+ }
1610
+ var IntercomClient = class extends EventEmitter2 {
1611
+ socket = null;
1612
+ scopeId;
1613
+ _sessionId = null;
1614
+ pendingSends = /* @__PURE__ */ new Map();
1615
+ pendingLists = /* @__PURE__ */ new Map();
1616
+ pendingAskControls = /* @__PURE__ */ new Map();
1617
+ pendingBossControls = /* @__PURE__ */ new Map();
1618
+ outbox = null;
1619
+ bossControlOutbox = null;
1620
+ remoteAccessCredential;
1621
+ requestedBossRegistration;
1622
+ _bossBinding;
1623
+ disconnecting = false;
1624
+ disconnectError = null;
1625
+ constructor(options = {}) {
1626
+ super();
1627
+ this.scopeId = options.scopeId === void 0 ? intercomScopeIdFromEnv(options.env ?? process.env) : parseIntercomScopeId(options.scopeId, "scopeId");
1628
+ }
1629
+ failPending(error) {
1630
+ for (const pending of this.pendingSends.values()) {
1631
+ pending.reject(error);
1632
+ }
1633
+ this.pendingSends.clear();
1634
+ for (const pending of this.pendingLists.values()) {
1635
+ pending.reject(error);
1636
+ }
1637
+ this.pendingLists.clear();
1638
+ for (const pending of this.pendingAskControls.values()) {
1639
+ clearTimeout(pending.timeout);
1640
+ pending.resolve(false);
1641
+ }
1642
+ this.pendingAskControls.clear();
1643
+ for (const pending of this.pendingBossControls.values()) {
1644
+ clearTimeout(pending.timeout);
1645
+ pending.reject(error);
1646
+ }
1647
+ this.pendingBossControls.clear();
1648
+ }
1649
+ get sessionId() {
1650
+ return this._sessionId;
1651
+ }
1652
+ get outboxSize() {
1653
+ return this.outbox?.list().length ?? 0;
1654
+ }
1655
+ get bossBinding() {
1656
+ return this._bossBinding;
1657
+ }
1658
+ get bossControlOutboxSize() {
1659
+ return this.bossControlOutbox?.list().length ?? 0;
1660
+ }
1661
+ isConnected() {
1662
+ const socket = this.socket;
1663
+ return Boolean(socket && this._sessionId && !this.disconnecting && !socket.destroyed && !socket.writableEnded && socket.writable);
1664
+ }
1665
+ requireActiveSocket() {
1666
+ if (this.disconnecting) {
1667
+ throw new Error("Client disconnecting");
1668
+ }
1669
+ const socket = this.socket;
1670
+ if (!socket || !this._sessionId) {
1671
+ throw new Error("Not connected");
1672
+ }
1673
+ if (socket.destroyed || socket.writableEnded || !socket.writable) {
1674
+ throw new Error("Client disconnected");
1675
+ }
1676
+ return socket;
1677
+ }
1678
+ connect(session, sessionId) {
1679
+ if (this.socket) {
1680
+ return Promise.reject(new Error("Already connected"));
1681
+ }
1682
+ try {
1683
+ const canonicalSession = parseExactRegistrationFrame({
1684
+ type: "register",
1685
+ ...typeof session === "object" && session !== null && !nodeUtilTypes3.isProxy(session) && Object.getOwnPropertyDescriptor(session, "boss") !== void 0 ? { registrationKind: "boss" } : {},
1686
+ protocol: INTERCOM_PROTOCOL_NAME,
1687
+ version: INTERCOM_PROTOCOL_VERSION,
1688
+ session
1689
+ }).session;
1690
+ this.requestedBossRegistration = session.boss === void 0 ? void 0 : parseBossParticipantRegistrationMetadata(session.boss);
1691
+ if (canonicalSession !== session) throw new Error("Registration session identity changed during validation");
1692
+ } catch (error) {
1693
+ return Promise.reject(toError(error));
1694
+ }
1695
+ return new Promise((resolve4, reject) => {
1696
+ let socket;
1697
+ let target;
1698
+ try {
1699
+ target = getBrokerConnectTarget();
1700
+ this.remoteAccessCredential = loadRemoteAccessCredential();
1701
+ socket = connectToBrokerTarget(target);
1702
+ } catch (error) {
1703
+ reject(toError(error));
1704
+ return;
1705
+ }
1706
+ this.socket = socket;
1707
+ this.disconnectError = null;
1708
+ let settled = false;
1709
+ const timeout = setTimeout(() => {
1710
+ if (!this._sessionId) {
1711
+ cleanupConnectionAttempt();
1712
+ cleanupSocketListeners();
1713
+ if (this.socket === socket) {
1714
+ this.socket = null;
1715
+ }
1716
+ socket.destroy();
1717
+ reject(new Error("Connection timeout"));
1718
+ }
1719
+ }, 1e4);
1720
+ let connectionEstablished = false;
1721
+ const onRegistered = () => {
1722
+ settled = true;
1723
+ connectionEstablished = true;
1724
+ cleanupConnectionAttempt();
1725
+ resolve4();
1726
+ };
1727
+ const onError = (err) => {
1728
+ settled = true;
1729
+ cleanupConnectionAttempt();
1730
+ cleanupSocketListeners();
1731
+ if (this.socket === socket) {
1732
+ this.socket = null;
1733
+ }
1734
+ socket.destroy();
1735
+ reject(err);
1736
+ };
1737
+ const onClose = () => {
1738
+ const wasConnecting = !settled && !this._sessionId;
1739
+ const wasDisconnecting = this.disconnecting;
1740
+ const disconnectError = this.disconnectError ?? new Error("Client disconnected");
1741
+ this.disconnecting = false;
1742
+ cleanupConnectionAttempt();
1743
+ cleanupSocketListeners();
1744
+ this.failPending(disconnectError);
1745
+ if (this.socket === socket) {
1746
+ this.socket = null;
1747
+ }
1748
+ this._sessionId = null;
1749
+ this._bossBinding = void 0;
1750
+ this.requestedBossRegistration = void 0;
1751
+ this.disconnectError = null;
1752
+ if (connectionEstablished && !wasDisconnecting) {
1753
+ this.emit("disconnected", disconnectError);
1754
+ }
1755
+ if (wasConnecting) {
1756
+ reject(new Error("Connection closed before registration"));
1757
+ }
1758
+ };
1759
+ const onSocketError = (err) => {
1760
+ if (connectionEstablished) {
1761
+ this.disconnectError = err;
1762
+ this.emit("error", err);
1763
+ }
1764
+ };
1765
+ const onReaderError = (error) => {
1766
+ const protocolError = new Error(`Intercom protocol error: ${error.message}`, { cause: error });
1767
+ if (!connectionEstablished) {
1768
+ onError(protocolError);
1769
+ return;
1770
+ }
1771
+ this.disconnectError = protocolError;
1772
+ this.emit("error", protocolError);
1773
+ socket.destroy();
1774
+ };
1775
+ const reader = createMessageReader((msg) => {
1776
+ this.handleBrokerMessage(msg);
1777
+ }, onReaderError);
1778
+ const cleanupConnectionAttempt = () => {
1779
+ this.off("_registered", onRegistered);
1780
+ socket.off("error", onError);
1781
+ clearTimeout(timeout);
1782
+ };
1783
+ const cleanupSocketListeners = () => {
1784
+ socket.off("data", reader);
1785
+ socket.off("error", onSocketError);
1786
+ socket.off("close", onClose);
1787
+ };
1788
+ socket.on("data", reader);
1789
+ socket.on("error", onError);
1790
+ socket.on("close", onClose);
1791
+ socket.on("error", onSocketError);
1792
+ this.once("_registered", onRegistered);
1793
+ try {
1794
+ writeMessage(socket, {
1795
+ type: "register",
1796
+ ...session.boss === void 0 ? {} : { registrationKind: "boss" },
1797
+ protocol: INTERCOM_PROTOCOL_NAME,
1798
+ version: INTERCOM_PROTOCOL_VERSION,
1799
+ session,
1800
+ ...!this.remoteAccessCredential && sessionId ? { sessionId } : {},
1801
+ ...this.remoteAccessCredential ? { access: this.remoteAccessCredential.access } : {},
1802
+ ...this.scopeId ? { scopeId: this.scopeId } : {},
1803
+ ...typeof target === "string" ? {} : { stateId: target.stateId }
1804
+ });
1805
+ } catch (error) {
1806
+ cleanupConnectionAttempt();
1807
+ cleanupSocketListeners();
1808
+ if (this.socket === socket) {
1809
+ this.socket = null;
1810
+ }
1811
+ socket.destroy();
1812
+ reject(toError(error));
1813
+ }
1814
+ });
1815
+ }
1816
+ handleBrokerMessage(msg) {
1817
+ if (typeof msg !== "object" || msg === null || nodeUtilTypes3.isProxy(msg)) {
1818
+ throw new Error("Invalid broker message");
1819
+ }
1820
+ const typeDescriptor = Object.getOwnPropertyDescriptor(msg, "type");
1821
+ if (typeDescriptor === void 0 || !typeDescriptor.enumerable || !Object.hasOwn(typeDescriptor, "value") || typeof typeDescriptor.value !== "string") throw new Error("Invalid broker message");
1822
+ const brokerMessage = msg;
1823
+ if (this._sessionId === null && brokerMessage.type !== "registered" && brokerMessage.type !== "error") {
1824
+ throw new Error(`Received ${brokerMessage.type} before registered`);
1825
+ }
1826
+ switch (brokerMessage.type) {
1827
+ case "registered": {
1828
+ parseExactRegisteredFrame(
1829
+ brokerMessage,
1830
+ this.requestedBossRegistration === void 0 ? this.remoteAccessCredential === void 0 ? "ordinary-local" : "ordinary-remote" : "boss"
1831
+ );
1832
+ if (typeof brokerMessage.sessionId !== "string" || brokerMessage.protocol !== INTERCOM_PROTOCOL_NAME || brokerMessage.version !== INTERCOM_PROTOCOL_VERSION) {
1833
+ throw new Error("Invalid registered message");
1834
+ }
1835
+ if (this._sessionId !== null) {
1836
+ throw new Error("Received duplicate registered message");
1837
+ }
1838
+ if (this.remoteAccessCredential) {
1839
+ const contract = brokerMessage.remoteAccess;
1840
+ const contractFields = typeof contract === "object" && contract !== null ? contract : void 0;
1841
+ if (!contractFields || contractFields.feature !== "remote-access-v1" || contractFields.policySemanticsVersion !== POLICY_SEMANTICS_VERSION || contractFields.policySemanticsHash !== POLICY_SEMANTICS_HASH) {
1842
+ throw new Error("Remote Intercom policy contract is absent or incompatible");
1843
+ }
1844
+ if (!isRemoteAccessMetadata(brokerMessage.access)) {
1845
+ throw new Error("Remote Intercom registration omitted broker-owned provenance");
1846
+ }
1847
+ if (this.remoteAccessCredential.enrollment) {
1848
+ writeRemoteSessionCredential(this.remoteAccessCredential.path, brokerMessage.sessionId, brokerMessage.access);
1849
+ } else {
1850
+ const reconnect = this.remoteAccessCredential.access;
1851
+ if (!("sessionId" in reconnect) || reconnect.sessionId !== brokerMessage.sessionId || reconnect.generation !== brokerMessage.access.generation) {
1852
+ throw new Error("Remote Intercom reconnect identity or generation changed unexpectedly");
1853
+ }
1854
+ }
1855
+ }
1856
+ if (this.requestedBossRegistration !== void 0) {
1857
+ if (brokerMessage.remoteAccess !== void 0 || brokerMessage.access !== void 0) {
1858
+ throw new Error("Boss registration returned folded remote-access metadata");
1859
+ }
1860
+ const advertisement = parseBrokerCapabilityAdvertisement2(brokerMessage.capabilities);
1861
+ if (!advertisement.features.some((feature) => feature.feature === BOSS_RUN_FEATURE2)) {
1862
+ throw new Error("Broker did not echo the required boss-run-v1 feature contract");
1863
+ }
1864
+ const binding = parseBossParticipantBindingMetadata(brokerMessage.boss, brokerMessage.sessionId);
1865
+ const credential = this.requestedBossRegistration.credential;
1866
+ if (binding.featureContract.feature !== this.requestedBossRegistration.featureContract.feature || binding.binding.bossRunId !== credential.bossRunId || binding.binding.participantId !== credential.participantId || binding.binding.role !== credential.role || binding.binding.communicationProfile !== credential.communicationProfile || binding.binding.bindingEpoch !== credential.bindingEpoch) {
1867
+ throw new Error("Broker returned a Boss binding that does not match the authenticated registration request");
1868
+ }
1869
+ this._bossBinding = binding;
1870
+ } else if (brokerMessage.boss !== void 0) {
1871
+ throw new Error("Broker attached unsolicited Boss binding metadata to an ordinary registration");
1872
+ }
1873
+ this._sessionId = brokerMessage.sessionId;
1874
+ this.outbox = new PersistentOutboundOutbox(brokerMessage.sessionId);
1875
+ this.bossControlOutbox = this._bossBinding === void 0 ? null : new PersistentBossControlOutbox(brokerMessage.sessionId);
1876
+ this.replayOutbox();
1877
+ this.replayBossControlOutbox();
1878
+ this.emit("_registered", { type: "registered", sessionId: brokerMessage.sessionId });
1879
+ break;
1880
+ }
1881
+ case "sessions": {
1882
+ const { requestId, sessions } = brokerMessage;
1883
+ if (typeof requestId !== "string" || !Array.isArray(sessions) || !sessions.every(isSessionInfo)) {
1884
+ throw new Error("Invalid sessions message");
1885
+ }
1886
+ const pending = this.pendingLists.get(requestId);
1887
+ if (!pending) {
1888
+ return;
1889
+ }
1890
+ this.pendingLists.delete(requestId);
1891
+ pending.resolve(sessions);
1892
+ break;
1893
+ }
1894
+ case "message": {
1895
+ const { deliveryId, from, message } = brokerMessage;
1896
+ if (typeof deliveryId !== "string" || !isSessionInfo(from) || !isMessage(message)) {
1897
+ throw new Error("Invalid message event");
1898
+ }
1899
+ this.emit("message", from, message, deliveryId);
1900
+ break;
1901
+ }
1902
+ case "boss_control": {
1903
+ const { deliveryId, from } = brokerMessage;
1904
+ if (typeof deliveryId !== "string" || !isSessionInfo(from)) {
1905
+ throw new Error("Invalid boss_control event");
1906
+ }
1907
+ const envelope = bossControlKind(brokerMessage.envelope).envelope;
1908
+ const source = from.boss === void 0 ? void 0 : parseBossParticipantBindingMetadata(from.boss, from.id).binding;
1909
+ if (source === void 0 || source.state !== "active" || source.bossRunId !== envelope.bossRunId || source.participantId !== envelope.participantId || source.bindingEpoch !== envelope.bindingEpoch) throw new Error("Boss control event sender does not match its broker-owned binding");
1910
+ this.emit("boss_control", from, envelope, deliveryId);
1911
+ break;
1912
+ }
1913
+ case "boss_control_result": {
1914
+ const result = parseBossControlResult(brokerMessage);
1915
+ const { requestId, messageId, idempotencyKey, deliveryId } = result;
1916
+ const stored = this.bossControlOutbox?.find(idempotencyKey);
1917
+ if (!stored || stored.envelope.messageId !== requestId) throw new Error("Boss control result does not match the durable outbox binding");
1918
+ const pending = this.pendingBossControls.get(requestId);
1919
+ if (pending && (pending.messageId !== messageId || pending.idempotencyKey !== idempotencyKey)) {
1920
+ throw new Error("Boss control result correlation does not match the pending request");
1921
+ }
1922
+ this.bossControlOutbox.removeCorrelated(idempotencyKey, messageId, deliveryId);
1923
+ if (pending) {
1924
+ clearTimeout(pending.timeout);
1925
+ this.pendingBossControls.delete(requestId);
1926
+ pending.resolve(result);
1927
+ }
1928
+ break;
1929
+ }
1930
+ case "boss_control_ack": {
1931
+ const { requestId, messageId, idempotencyKey, deliveryId } = parseBossControlAck(brokerMessage);
1932
+ const pending = this.pendingBossControls.get(requestId);
1933
+ if (pending && (pending.messageId !== messageId || pending.idempotencyKey !== idempotencyKey)) {
1934
+ throw new Error("Boss control acknowledgement correlation does not match the pending request");
1935
+ }
1936
+ const transition = this.bossControlOutbox?.markAccepted(idempotencyKey, messageId, deliveryId);
1937
+ if (transition === void 0) throw new Error("Boss control acknowledgement has no durable outbox");
1938
+ if (pending?.deliveryId !== void 0 && pending.deliveryId !== deliveryId) {
1939
+ throw new Error("Boss control acknowledgement changed the pending deliveryId");
1940
+ }
1941
+ if (pending) pending.deliveryId = deliveryId;
1942
+ break;
1943
+ }
1944
+ case "delivery_accepted": {
1945
+ const { deliveryId, messageId } = brokerMessage;
1946
+ if (typeof deliveryId !== "string" || typeof messageId !== "string") {
1947
+ throw new Error("Invalid delivery_accepted message");
1948
+ }
1949
+ const pending = this.pendingSends.get(messageId);
1950
+ if (!pending) {
1951
+ return;
1952
+ }
1953
+ pending.accepted = true;
1954
+ pending.deliveryId = deliveryId;
1955
+ this.emit("delivery_accepted", messageId, deliveryId);
1956
+ break;
1957
+ }
1958
+ case "delivered": {
1959
+ const { deliveryId, messageId } = brokerMessage;
1960
+ if (typeof deliveryId !== "string" || typeof messageId !== "string") {
1961
+ throw new Error("Invalid delivered message");
1962
+ }
1963
+ this.outbox?.remove(messageId);
1964
+ const pending = this.pendingSends.get(messageId);
1965
+ if (!pending) {
1966
+ this.emit("outbox_delivered", messageId, deliveryId);
1967
+ return;
1968
+ }
1969
+ this.pendingSends.delete(messageId);
1970
+ pending.resolve({ id: messageId, accepted: true, delivered: true, deliveryId });
1971
+ break;
1972
+ }
1973
+ case "delivery_failed": {
1974
+ const { accepted, code, messageId, reason } = brokerMessage;
1975
+ if (typeof accepted !== "boolean" || typeof code !== "string" || typeof messageId !== "string" || typeof reason !== "string") {
1976
+ throw new Error("Invalid delivery_failed message");
1977
+ }
1978
+ this.outbox?.remove(messageId);
1979
+ const pending = this.pendingSends.get(messageId);
1980
+ if (!pending) {
1981
+ this.emit("outbox_failed", messageId, code, reason);
1982
+ return;
1983
+ }
1984
+ this.pendingSends.delete(messageId);
1985
+ pending.resolve({
1986
+ id: messageId,
1987
+ accepted,
1988
+ delivered: false,
1989
+ code,
1990
+ reason,
1991
+ ...pending.deliveryId ? { deliveryId: pending.deliveryId } : {}
1992
+ });
1993
+ break;
1994
+ }
1995
+ case "ask_deferred": {
1996
+ const { fromSessionId, messageId } = brokerMessage;
1997
+ if (typeof fromSessionId !== "string" || typeof messageId !== "string") {
1998
+ throw new Error("Invalid ask_deferred message");
1999
+ }
2000
+ this.emit("ask_deferred", messageId, fromSessionId);
2001
+ break;
2002
+ }
2003
+ case "ask_cancelled": {
2004
+ const { fromSessionId, messageId, reason } = brokerMessage;
2005
+ if (typeof fromSessionId !== "string" || typeof messageId !== "string" || typeof reason !== "string") {
2006
+ throw new Error("Invalid ask_cancelled message");
2007
+ }
2008
+ this.emit("ask_cancelled", messageId, fromSessionId, reason);
2009
+ break;
2010
+ }
2011
+ case "ask_control_result": {
2012
+ const { action, applied, messageId, requestId } = brokerMessage;
2013
+ if (action !== "defer" && action !== "cancel" || typeof applied !== "boolean" || typeof messageId !== "string" || typeof requestId !== "string") {
2014
+ throw new Error("Invalid ask_control_result message");
2015
+ }
2016
+ const pending = this.pendingAskControls.get(requestId);
2017
+ if (!pending) return;
2018
+ clearTimeout(pending.timeout);
2019
+ this.pendingAskControls.delete(requestId);
2020
+ pending.resolve(applied);
2021
+ break;
2022
+ }
2023
+ case "session_joined": {
2024
+ if (!isSessionInfo(brokerMessage.session)) {
2025
+ throw new Error("Invalid session_joined message");
2026
+ }
2027
+ this.emit("session_joined", brokerMessage.session);
2028
+ break;
2029
+ }
2030
+ case "session_left": {
2031
+ if (typeof brokerMessage.sessionId !== "string") {
2032
+ throw new Error("Invalid session_left message");
2033
+ }
2034
+ this.emit("session_left", brokerMessage.sessionId);
2035
+ break;
2036
+ }
2037
+ case "presence_update": {
2038
+ if (!isSessionInfo(brokerMessage.session)) {
2039
+ throw new Error("Invalid presence_update message");
2040
+ }
2041
+ this.emit("presence_update", brokerMessage.session);
2042
+ break;
2043
+ }
2044
+ case "error": {
2045
+ if (typeof brokerMessage.code !== "string" || typeof brokerMessage.error !== "string") {
2046
+ throw new Error("Invalid error message");
2047
+ }
2048
+ if (this._sessionId === null) {
2049
+ const error2 = new Error(brokerMessage.error);
2050
+ error2.code = brokerMessage.code;
2051
+ throw error2;
2052
+ }
2053
+ const error = new Error(brokerMessage.error);
2054
+ error.code = brokerMessage.code;
2055
+ this.emit("error", error);
2056
+ break;
2057
+ }
2058
+ default:
2059
+ throw new Error(`Unknown broker message type: ${brokerMessage.type}`);
2060
+ }
2061
+ }
2062
+ async disconnect(preserveAsks = false) {
2063
+ const socket = this.socket;
2064
+ if (!socket) {
2065
+ return;
2066
+ }
2067
+ this.disconnecting = true;
2068
+ this.disconnectError = null;
2069
+ this.failPending(new Error("Client disconnected"));
2070
+ if (!preserveAsks) this.outbox?.clear();
2071
+ await new Promise((resolve4) => {
2072
+ let settled = false;
2073
+ const finish = () => {
2074
+ if (settled) {
2075
+ return;
2076
+ }
2077
+ settled = true;
2078
+ clearTimeout(timeout);
2079
+ socket.off("close", onClose);
2080
+ socket.off("error", onError);
2081
+ resolve4();
2082
+ };
2083
+ const onClose = () => finish();
2084
+ const onError = () => {
2085
+ socket.destroy();
2086
+ };
2087
+ const timeout = setTimeout(() => {
2088
+ socket.destroy();
2089
+ }, 2e3);
2090
+ socket.once("close", onClose);
2091
+ socket.once("error", onError);
2092
+ try {
2093
+ writeMessage(socket, { type: "unregister", ...preserveAsks ? { preserveAsks: true } : {} });
2094
+ socket.end();
2095
+ } catch {
2096
+ socket.destroy();
2097
+ }
2098
+ });
2099
+ }
2100
+ listSessions() {
2101
+ let socket;
2102
+ try {
2103
+ socket = this.requireActiveSocket();
2104
+ } catch (error) {
2105
+ return Promise.reject(toError(error));
2106
+ }
2107
+ return new Promise((resolve4, reject) => {
2108
+ const requestId = randomUUID2();
2109
+ const wrappedResolve = (sessions) => {
2110
+ clearTimeout(timeout);
2111
+ resolve4(sessions);
2112
+ };
2113
+ const wrappedReject = (error) => {
2114
+ clearTimeout(timeout);
2115
+ reject(error);
2116
+ };
2117
+ const timeout = setTimeout(() => {
2118
+ if (this.pendingLists.has(requestId)) {
2119
+ this.pendingLists.delete(requestId);
2120
+ wrappedReject(new Error("List sessions timeout"));
2121
+ }
2122
+ }, 5e3);
2123
+ this.pendingLists.set(requestId, { resolve: wrappedResolve, reject: wrappedReject });
2124
+ try {
2125
+ writeMessage(socket, { type: "list", requestId });
2126
+ } catch (error) {
2127
+ clearTimeout(timeout);
2128
+ this.pendingLists.delete(requestId);
2129
+ reject(toError(error));
2130
+ }
2131
+ });
2132
+ }
2133
+ send(to, options) {
2134
+ let socket;
2135
+ try {
2136
+ socket = this.requireActiveSocket();
2137
+ } catch (error) {
2138
+ return Promise.reject(toError(error));
2139
+ }
2140
+ const messageId = options.messageId ?? randomUUID2();
2141
+ if (this.pendingSends.has(messageId)) {
2142
+ return Promise.resolve({
2143
+ id: messageId,
2144
+ accepted: false,
2145
+ delivered: false,
2146
+ code: "DUPLICATE_MESSAGE_ID",
2147
+ reason: `Message ID ${messageId} is already pending`
2148
+ });
2149
+ }
2150
+ const message = {
2151
+ id: messageId,
2152
+ timestamp: Date.now(),
2153
+ replyTo: options.replyTo,
2154
+ expectsReply: options.expectsReply,
2155
+ content: {
2156
+ text: options.text,
2157
+ attachments: options.attachments
2158
+ }
2159
+ };
2160
+ try {
2161
+ this.outbox?.enqueue(to, message);
2162
+ } catch (error) {
2163
+ return Promise.reject(toError(error));
2164
+ }
2165
+ return new Promise((resolve4, reject) => {
2166
+ const wrappedResolve = (result) => {
2167
+ clearTimeout(timeout);
2168
+ resolve4(result);
2169
+ };
2170
+ const wrappedReject = (error) => {
2171
+ clearTimeout(timeout);
2172
+ reject(error);
2173
+ };
2174
+ const timeout = setTimeout(() => {
2175
+ if (this.pendingSends.has(messageId)) {
2176
+ this.pendingSends.delete(messageId);
2177
+ wrappedReject(new Error("Send timeout"));
2178
+ }
2179
+ }, 1e4);
2180
+ this.pendingSends.set(messageId, {
2181
+ accepted: false,
2182
+ resolve: wrappedResolve,
2183
+ reject: wrappedReject
2184
+ });
2185
+ try {
2186
+ writeMessage(socket, { type: "send", to, message });
2187
+ } catch (error) {
2188
+ clearTimeout(timeout);
2189
+ this.pendingSends.delete(messageId);
2190
+ reject(toError(error));
2191
+ }
2192
+ });
2193
+ }
2194
+ sendBossControl(to, envelopeValue) {
2195
+ let socket;
2196
+ try {
2197
+ socket = this.requireActiveSocket();
2198
+ } catch (error) {
2199
+ return Promise.reject(toError(error));
2200
+ }
2201
+ let envelope;
2202
+ try {
2203
+ envelope = bossControlKind(envelopeValue).envelope;
2204
+ const binding = this._bossBinding?.binding;
2205
+ if (binding === void 0 || binding.state !== "active" || envelope.bossRunId !== binding.bossRunId || envelope.participantId !== binding.participantId || envelope.bindingEpoch !== binding.bindingEpoch) throw new Error("Boss control envelope does not match this client's active participant binding");
2206
+ } catch (error) {
2207
+ return Promise.reject(toError(error));
2208
+ }
2209
+ const requestId = envelope.messageId;
2210
+ if (this.pendingBossControls.has(requestId)) {
2211
+ return Promise.resolve({
2212
+ requestId,
2213
+ messageId: envelope.messageId,
2214
+ idempotencyKey: envelope.idempotencyKey,
2215
+ status: "rejected",
2216
+ delivered: false,
2217
+ code: "INVALID_CONTROL",
2218
+ reason: "Boss requestId is already pending"
2219
+ });
2220
+ }
2221
+ try {
2222
+ if (!this.bossControlOutbox) throw new Error("Durable Boss control outbox is unavailable");
2223
+ this.bossControlOutbox.enqueue(to, envelope);
2224
+ } catch (error) {
2225
+ return Promise.reject(toError(error));
2226
+ }
2227
+ return new Promise((resolve4, reject) => {
2228
+ const timeout = setTimeout(() => {
2229
+ if (!this.pendingBossControls.delete(requestId)) return;
2230
+ reject(new Error("Boss control delivery timeout"));
2231
+ }, 1e4);
2232
+ timeout.unref?.();
2233
+ this.pendingBossControls.set(requestId, {
2234
+ messageId: envelope.messageId,
2235
+ idempotencyKey: envelope.idempotencyKey,
2236
+ resolve: resolve4,
2237
+ reject,
2238
+ timeout
2239
+ });
2240
+ try {
2241
+ writeMessage(socket, { type: "boss_control", requestId, to, envelope });
2242
+ } catch (error) {
2243
+ clearTimeout(timeout);
2244
+ this.pendingBossControls.delete(requestId);
2245
+ reject(toError(error));
2246
+ }
2247
+ });
2248
+ }
2249
+ acknowledgeBossControl(deliveryId, messageId, idempotencyKey) {
2250
+ return this.writeControlMessage({ type: "boss_control_received", deliveryId, messageId, idempotencyKey });
2251
+ }
2252
+ acknowledgeMessage(deliveryId) {
2253
+ return this.writeControlMessage({ type: "message_received", deliveryId });
2254
+ }
2255
+ rejectMessage(deliveryId, reason) {
2256
+ return this.writeControlMessage({ type: "message_rejected", deliveryId, code: "CONFLICTING_MESSAGE_ID", reason });
2257
+ }
2258
+ deferAsk(messageId) {
2259
+ return this.sendAskControl("defer", messageId);
2260
+ }
2261
+ cancelAsk(messageId) {
2262
+ return this.sendAskControl("cancel", messageId);
2263
+ }
2264
+ sendAskControl(action, messageId) {
2265
+ const requestId = randomUUID2();
2266
+ return new Promise((resolve4) => {
2267
+ const timeout = setTimeout(() => {
2268
+ this.pendingAskControls.delete(requestId);
2269
+ resolve4(false);
2270
+ }, 2e3);
2271
+ timeout.unref?.();
2272
+ this.pendingAskControls.set(requestId, { resolve: resolve4, timeout });
2273
+ if (!this.writeControlMessage({ type: action === "defer" ? "defer_ask" : "cancel_ask", requestId, messageId })) {
2274
+ clearTimeout(timeout);
2275
+ this.pendingAskControls.delete(requestId);
2276
+ resolve4(false);
2277
+ }
2278
+ });
2279
+ }
2280
+ writeControlMessage(message) {
2281
+ if (this.disconnecting) {
2282
+ return false;
2283
+ }
2284
+ const socket = this.socket;
2285
+ if (!socket || !this._sessionId || socket.destroyed || socket.writableEnded || !socket.writable) {
2286
+ return false;
2287
+ }
2288
+ try {
2289
+ writeMessage(socket, message);
2290
+ return true;
2291
+ } catch {
2292
+ return false;
2293
+ }
2294
+ }
2295
+ replayOutbox() {
2296
+ const socket = this.socket;
2297
+ if (!socket || !this._sessionId || socket.destroyed || socket.writableEnded || !socket.writable) return;
2298
+ for (const entry of this.outbox?.list() ?? []) {
2299
+ if (this.pendingSends.has(entry.message.id)) continue;
2300
+ try {
2301
+ writeMessage(socket, { type: "send", to: entry.to, message: entry.message });
2302
+ } catch {
2303
+ return;
2304
+ }
2305
+ }
2306
+ }
2307
+ replayBossControlOutbox() {
2308
+ const socket = this.socket;
2309
+ if (!socket || socket.destroyed || !this._sessionId || !this.bossControlOutbox) return;
2310
+ for (const entry of this.bossControlOutbox.list()) {
2311
+ try {
2312
+ writeMessage(socket, {
2313
+ type: "boss_control",
2314
+ requestId: entry.envelope.messageId,
2315
+ to: entry.to,
2316
+ envelope: entry.envelope
2317
+ });
2318
+ } catch {
2319
+ return;
2320
+ }
2321
+ }
2322
+ }
2323
+ updatePresence(updates) {
2324
+ if (this.disconnecting) {
2325
+ return;
2326
+ }
2327
+ const socket = this.socket;
2328
+ if (!socket || !this._sessionId || socket.destroyed || socket.writableEnded || !socket.writable) {
2329
+ return;
2330
+ }
2331
+ writeMessage(socket, { type: "presence", ...updates });
2332
+ }
2333
+ };
2334
+
2335
+ // broker/spawn.ts
2336
+ import { spawn as spawn2 } from "child_process";
2337
+ import { existsSync as existsSync4, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
2338
+ import { join as join5, dirname as dirname3 } from "path";
2339
+ import { fileURLToPath } from "url";
2340
+ import { createRequire } from "module";
2341
+ import net3 from "net";
2342
+ import { randomUUID as randomUUID3 } from "crypto";
2343
+ import { POLICY_SEMANTICS_HASH as POLICY_SEMANTICS_HASH2, POLICY_SEMANTICS_VERSION as POLICY_SEMANTICS_VERSION2 } from "@ctliz/agent-intercom-core";
2344
+ var INTERCOM_DIR = getIntercomDirPath();
2345
+ var EXTENSION_DIR = join5(dirname3(fileURLToPath(import.meta.url)), "..");
2346
+ var BROKER_PID = join5(INTERCOM_DIR, "broker.pid");
2347
+ var BROKER_SPAWN_LOCK = join5(INTERCOM_DIR, "broker.spawn.lock");
2348
+ function sleep(ms) {
2349
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
2350
+ }
2351
+ function getBrokerEntryPath(moduleUrl = import.meta.url) {
2352
+ const moduleDir = dirname3(fileURLToPath(moduleUrl));
2353
+ const bundledBroker = join5(moduleDir, "broker.mjs");
2354
+ return existsSync4(bundledBroker) ? bundledBroker : join5(moduleDir, "broker.ts");
2355
+ }
2356
+ function getTsxCliPath(extensionDir = EXTENSION_DIR) {
2357
+ try {
2358
+ const requireFromExtension = createRequire(import.meta.url);
2359
+ const tsxMain = requireFromExtension.resolve("tsx");
2360
+ return join5(dirname3(tsxMain), "cli.mjs");
2361
+ } catch {
2362
+ return join5(extensionDir, "node_modules", "tsx", "dist", "cli.mjs");
2363
+ }
2364
+ }
2365
+ function quoteWindowsArg(value) {
2366
+ return `"${value.replace(/"/g, '""')}"`;
2367
+ }
2368
+ function getWindowsHiddenLauncherPath(intercomDir = INTERCOM_DIR) {
2369
+ return join5(intercomDir, "broker-launch.vbs");
2370
+ }
2371
+ function usesDefaultBrokerCommand(brokerCommand, brokerArgs) {
2372
+ return brokerCommand === "npx" && brokerArgs.length === 2 && brokerArgs[0] === "--no-install" && brokerArgs[1] === "tsx";
2373
+ }
2374
+ function getWindowsBrokerCommandLine(brokerPath, extensionDir = EXTENSION_DIR, nodePath = process.execPath, brokerCommand = "npx", brokerArgs = ["--no-install", "tsx"]) {
2375
+ if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
2376
+ return [quoteWindowsArg(nodePath), quoteWindowsArg(getTsxCliPath(extensionDir)), quoteWindowsArg(brokerPath)].join(" ");
2377
+ }
2378
+ return [quoteWindowsArg(brokerCommand), ...brokerArgs.map(quoteWindowsArg), quoteWindowsArg(brokerPath)].join(" ");
2379
+ }
2380
+ function getWindowsHiddenLauncherScript(commandLine) {
2381
+ return [
2382
+ 'Set WshShell = CreateObject("WScript.Shell")',
2383
+ `WshShell.Run "${commandLine.replace(/"/g, '""')}", 0, False`,
2384
+ "Set WshShell = Nothing",
2385
+ ""
2386
+ ].join("\r\n");
2387
+ }
2388
+ function isBrokerHealthOkMessage(message, requestId) {
2389
+ if (typeof message !== "object" || message === null || !("type" in message)) {
2390
+ return false;
2391
+ }
2392
+ const response = message;
2393
+ if (response.type !== "health_ok" || response.requestId !== requestId || response.protocol !== INTERCOM_PROTOCOL_NAME || response.version !== INTERCOM_PROTOCOL_VERSION || response.endpoint !== "local") return false;
2394
+ const remoteAccess = response.remoteAccess;
2395
+ if (typeof remoteAccess !== "object" || remoteAccess === null || Array.isArray(remoteAccess)) return false;
2396
+ const contract = remoteAccess;
2397
+ return contract.feature === "remote-access-v1" && contract.policySemanticsVersion === POLICY_SEMANTICS_VERSION2 && contract.policySemanticsHash === POLICY_SEMANTICS_HASH2;
2398
+ }
2399
+ function writeWindowsHiddenLauncher(commandLine, launcherPath = getWindowsHiddenLauncherPath()) {
2400
+ ensureIntercomRuntimeDir(dirname3(launcherPath));
2401
+ writeFileSync3(launcherPath, getWindowsHiddenLauncherScript(commandLine), {
2402
+ encoding: "utf-8",
2403
+ mode: INTERCOM_RUNTIME_FILE_MODE
2404
+ });
2405
+ restrictIntercomRuntimeFile(launcherPath);
2406
+ return launcherPath;
2407
+ }
2408
+ function getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs, extensionDir = EXTENSION_DIR, platform = process.platform, intercomDir = INTERCOM_DIR, nodePath = process.execPath) {
2409
+ if (platform === "win32") {
2410
+ const launcherPath = getWindowsHiddenLauncherPath(intercomDir);
2411
+ return {
2412
+ kind: "windows-launcher",
2413
+ command: "wscript.exe",
2414
+ args: [launcherPath],
2415
+ launcherPath,
2416
+ launcherCommandLine: getWindowsBrokerCommandLine(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs)
2417
+ };
2418
+ }
2419
+ if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
2420
+ return {
2421
+ kind: "direct",
2422
+ command: nodePath,
2423
+ args: [getTsxCliPath(extensionDir), brokerPath]
2424
+ };
2425
+ }
2426
+ return {
2427
+ kind: "direct",
2428
+ command: brokerCommand,
2429
+ args: [...brokerArgs, brokerPath]
2430
+ };
2431
+ }
2432
+ function getBrokerSpawnOptions(extensionDir = EXTENSION_DIR, env = process.env) {
2433
+ return {
2434
+ detached: true,
2435
+ stdio: "ignore",
2436
+ cwd: extensionDir,
2437
+ env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
2438
+ windowsHide: true
2439
+ };
2440
+ }
2441
+ function toError2(error) {
2442
+ return error instanceof Error ? error : new Error(String(error));
2443
+ }
2444
+ async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
2445
+ ensureIntercomRuntimeDir(INTERCOM_DIR);
2446
+ if (await isBrokerRunning()) {
2447
+ return;
2448
+ }
2449
+ const ownsLock = acquireSpawnLock();
2450
+ if (!ownsLock) {
2451
+ await waitForBroker();
2452
+ return;
2453
+ }
2454
+ try {
2455
+ if (await isBrokerRunning()) {
2456
+ return;
2457
+ }
2458
+ if (await checkBrokerHealth() === "incompatible") {
2459
+ throw new Error(`Incompatible live intercom broker; expected ${INTERCOM_PROTOCOL_NAME} v${INTERCOM_PROTOCOL_VERSION}. Stop it explicitly during a coordinated migration.`);
2460
+ }
2461
+ const brokerPath = getBrokerEntryPath();
2462
+ const launch = getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs);
2463
+ if (launch.kind === "windows-launcher") {
2464
+ writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
2465
+ }
2466
+ const child = spawn2(launch.command, launch.args, getBrokerSpawnOptions());
2467
+ child.unref();
2468
+ await new Promise((resolve4, reject) => {
2469
+ const cleanup = () => {
2470
+ child.off("error", onError);
2471
+ child.off("exit", onExit);
2472
+ };
2473
+ const onError = (error) => {
2474
+ cleanup();
2475
+ reject(new Error(`Failed to spawn intercom broker: ${error.message}`, { cause: error }));
2476
+ };
2477
+ const onExit = (code, signal) => {
2478
+ if (launch.kind === "windows-launcher" && code === 0 && signal === null) {
2479
+ return;
2480
+ }
2481
+ cleanup();
2482
+ if (signal) {
2483
+ reject(new Error(`Intercom broker exited before startup with signal ${signal}`));
2484
+ return;
2485
+ }
2486
+ reject(new Error(`Intercom broker exited before startup with code ${code ?? "unknown"}`));
2487
+ };
2488
+ child.once("error", onError);
2489
+ child.once("exit", onExit);
2490
+ waitForBroker().then(() => {
2491
+ cleanup();
2492
+ resolve4();
2493
+ }, (error) => {
2494
+ cleanup();
2495
+ reject(toError2(error));
2496
+ });
2497
+ });
2498
+ } finally {
2499
+ releaseSpawnLock();
2500
+ }
2501
+ }
2502
+ async function isBrokerRunning() {
2503
+ if (await checkSocketConnectable()) {
2504
+ return true;
2505
+ }
2506
+ if (!existsSync4(BROKER_PID)) return false;
2507
+ try {
2508
+ const pid = parseInt(readFileSync6(BROKER_PID, "utf-8").trim(), 10);
2509
+ if (!Number.isFinite(pid)) return false;
2510
+ process.kill(pid, 0);
2511
+ return checkSocketConnectable();
2512
+ } catch {
2513
+ return false;
2514
+ }
2515
+ }
2516
+ function connectToBrokerTarget2(target) {
2517
+ return typeof target === "string" ? net3.connect(target) : net3.connect({ host: target.host, port: target.port });
2518
+ }
2519
+ async function checkSocketConnectable() {
2520
+ return await checkBrokerHealth() === "compatible";
2521
+ }
2522
+ function checkBrokerHealth() {
2523
+ return new Promise((resolve4) => {
2524
+ let target;
2525
+ try {
2526
+ target = getBrokerConnectTarget();
2527
+ } catch {
2528
+ resolve4("unreachable");
2529
+ return;
2530
+ }
2531
+ const socket = connectToBrokerTarget2(target);
2532
+ const requestId = randomUUID3();
2533
+ const expectedStateId = typeof target === "string" ? void 0 : target.stateId;
2534
+ let settled = false;
2535
+ const finish = (health) => {
2536
+ if (settled) {
2537
+ return;
2538
+ }
2539
+ settled = true;
2540
+ clearTimeout(timeout);
2541
+ socket.off("connect", onConnect);
2542
+ socket.off("error", onError);
2543
+ socket.off("data", reader);
2544
+ socket.destroy();
2545
+ resolve4(health);
2546
+ };
2547
+ const onConnect = () => {
2548
+ try {
2549
+ writeMessage(socket, {
2550
+ type: "health",
2551
+ requestId,
2552
+ ...expectedStateId ? { stateId: expectedStateId } : {}
2553
+ });
2554
+ } catch {
2555
+ finish("unreachable");
2556
+ }
2557
+ };
2558
+ const onError = () => finish("unreachable");
2559
+ const reader = createMessageReader((message) => {
2560
+ if (isBrokerHealthOkMessage(message, requestId)) {
2561
+ finish("compatible");
2562
+ return;
2563
+ }
2564
+ if (typeof message === "object" && message !== null && "type" in message && message.type === "health_ok" && "requestId" in message && message.requestId === requestId) {
2565
+ finish("incompatible");
2566
+ return;
2567
+ }
2568
+ finish("unreachable");
2569
+ }, () => finish("unreachable"));
2570
+ socket.on("connect", onConnect);
2571
+ socket.on("error", onError);
2572
+ socket.on("data", reader);
2573
+ const timeout = setTimeout(() => finish("unreachable"), 1e3);
2574
+ });
2575
+ }
2576
+ function acquireSpawnLock() {
2577
+ const maxRetries = 5;
2578
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
2579
+ try {
2580
+ writeFileSync3(BROKER_SPAWN_LOCK, `${process.pid}
2581
+ ${Date.now()}
2582
+ `, {
2583
+ flag: "wx",
2584
+ mode: INTERCOM_RUNTIME_FILE_MODE
2585
+ });
2586
+ restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);
2587
+ return true;
2588
+ } catch (error) {
2589
+ if (!(error instanceof Error) || error.code !== "EEXIST") {
2590
+ throw error;
2591
+ }
2592
+ if (isSpawnLockStale()) {
2593
+ try {
2594
+ unlinkSync(BROKER_SPAWN_LOCK);
2595
+ } catch {
2596
+ }
2597
+ continue;
2598
+ }
2599
+ return false;
2600
+ }
2601
+ }
2602
+ return false;
2603
+ }
2604
+ function isSpawnLockStale() {
2605
+ if (!existsSync4(BROKER_SPAWN_LOCK)) {
2606
+ return false;
2607
+ }
2608
+ try {
2609
+ const [pidLine = "", createdAtLine = "0"] = readFileSync6(BROKER_SPAWN_LOCK, "utf-8").trim().split("\n");
2610
+ const pid = Number.parseInt(pidLine, 10);
2611
+ const createdAt = Number.parseInt(createdAtLine, 10);
2612
+ const ageMs = Date.now() - createdAt;
2613
+ if (Number.isFinite(pid)) {
2614
+ try {
2615
+ process.kill(pid, 0);
2616
+ } catch {
2617
+ return true;
2618
+ }
2619
+ }
2620
+ return !Number.isFinite(createdAt) || ageMs > 1e4;
2621
+ } catch {
2622
+ return true;
2623
+ }
2624
+ }
2625
+ function releaseSpawnLock() {
2626
+ try {
2627
+ unlinkSync(BROKER_SPAWN_LOCK);
2628
+ } catch {
2629
+ }
2630
+ }
2631
+ async function waitForBroker(timeoutMs = 5e3) {
2632
+ const start = Date.now();
2633
+ while (Date.now() - start < timeoutMs) {
2634
+ if (await checkSocketConnectable()) {
2635
+ return;
2636
+ }
2637
+ await sleep(100);
2638
+ }
2639
+ throw new Error("Broker failed to start within timeout");
2640
+ }
2641
+
2642
+ // config.ts
2643
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
2644
+ import { join as join6, resolve as resolve3 } from "path";
2645
+ import { homedir as homedir2 } from "os";
2646
+ var DEFAULT_ASK_TIMEOUT_MS = 45 * 1e3;
2647
+ var MAX_ASK_TIMEOUT_MS = 120 * 1e3;
2648
+ function validateAskTimeoutMs(value, name = "timeout_ms") {
2649
+ if (!Number.isSafeInteger(value) || typeof value !== "number" || value <= 0) {
2650
+ throw new Error(`${name} must be a positive integer number of milliseconds`);
2651
+ }
2652
+ if (value > MAX_ASK_TIMEOUT_MS) {
2653
+ throw new Error(`${name} must be ${MAX_ASK_TIMEOUT_MS} ms or less; use intercom_send plus intercom_pending for longer-running work`);
2654
+ }
2655
+ return value;
2656
+ }
2657
+ function getConfigPath() {
2658
+ const agentDir = process.env.PI_CODING_AGENT_DIR ? resolve3(process.env.PI_CODING_AGENT_DIR) : join6(homedir2(), ".pi", "agent");
2659
+ return join6(agentDir, "intercom", "config.json");
2660
+ }
2661
+ var defaults = {
2662
+ brokerCommand: "npx",
2663
+ brokerArgs: ["--no-install", "tsx"],
2664
+ confirmSend: false,
2665
+ enabled: true,
2666
+ replyHint: true,
2667
+ inboundForkHandlers: {
2668
+ enabled: true,
2669
+ when: "auto",
2670
+ notify: "summary",
2671
+ triggerParentOnSummary: "auto"
2672
+ }
2673
+ };
2674
+ function loadConfig() {
2675
+ const configPath = getConfigPath();
2676
+ if (!existsSync5(configPath)) {
2677
+ return { ...defaults };
2678
+ }
2679
+ try {
2680
+ const raw = readFileSync7(configPath, "utf-8");
2681
+ const parsed = JSON.parse(raw);
2682
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2683
+ throw new Error("Config must be a JSON object");
2684
+ }
2685
+ const parsedConfig = parsed;
2686
+ const config = { ...defaults };
2687
+ if (Object.hasOwn(parsedConfig, "brokerCommand")) {
2688
+ if (typeof parsedConfig.brokerCommand !== "string") {
2689
+ throw new Error(`"brokerCommand" must be a string`);
2690
+ }
2691
+ const brokerCommand = parsedConfig.brokerCommand.trim();
2692
+ if (!brokerCommand) {
2693
+ throw new Error(`"brokerCommand" must not be empty`);
2694
+ }
2695
+ config.brokerCommand = brokerCommand;
2696
+ }
2697
+ if (Object.hasOwn(parsedConfig, "brokerArgs")) {
2698
+ if (!Array.isArray(parsedConfig.brokerArgs)) {
2699
+ throw new Error(`"brokerArgs" must be an array`);
2700
+ }
2701
+ const brokerArgs = [];
2702
+ for (const arg of parsedConfig.brokerArgs) {
2703
+ if (typeof arg !== "string") {
2704
+ throw new Error(`"brokerArgs" items must be strings`);
2705
+ }
2706
+ brokerArgs.push(arg);
2707
+ }
2708
+ config.brokerArgs = brokerArgs;
2709
+ }
2710
+ if (Object.hasOwn(parsedConfig, "confirmSend")) {
2711
+ if (typeof parsedConfig.confirmSend !== "boolean") {
2712
+ throw new Error(`"confirmSend" must be a boolean`);
2713
+ }
2714
+ config.confirmSend = parsedConfig.confirmSend;
2715
+ }
2716
+ if (Object.hasOwn(parsedConfig, "enabled")) {
2717
+ if (typeof parsedConfig.enabled !== "boolean") {
2718
+ throw new Error(`"enabled" must be a boolean`);
2719
+ }
2720
+ config.enabled = parsedConfig.enabled;
2721
+ }
2722
+ if (Object.hasOwn(parsedConfig, "replyHint")) {
2723
+ if (typeof parsedConfig.replyHint !== "boolean") {
2724
+ throw new Error(`"replyHint" must be a boolean`);
2725
+ }
2726
+ config.replyHint = parsedConfig.replyHint;
2727
+ }
2728
+ if (Object.hasOwn(parsedConfig, "status")) {
2729
+ if (typeof parsedConfig.status !== "string") {
2730
+ throw new Error(`"status" must be a string`);
2731
+ }
2732
+ config.status = parsedConfig.status;
2733
+ }
2734
+ if (Object.hasOwn(parsedConfig, "inboundForkHandlers")) {
2735
+ if (typeof parsedConfig.inboundForkHandlers !== "object" || parsedConfig.inboundForkHandlers === null || Array.isArray(parsedConfig.inboundForkHandlers)) {
2736
+ throw new Error(`"inboundForkHandlers" must be an object`);
2737
+ }
2738
+ const forkConfig = parsedConfig.inboundForkHandlers;
2739
+ config.inboundForkHandlers = { ...defaults.inboundForkHandlers };
2740
+ if (Object.hasOwn(forkConfig, "enabled")) {
2741
+ if (typeof forkConfig.enabled !== "boolean") throw new Error(`"inboundForkHandlers.enabled" must be a boolean`);
2742
+ config.inboundForkHandlers.enabled = forkConfig.enabled;
2743
+ }
2744
+ if (Object.hasOwn(forkConfig, "when")) {
2745
+ if (forkConfig.when !== "auto" && forkConfig.when !== "busy" && forkConfig.when !== "always") throw new Error(`"inboundForkHandlers.when" must be "auto", "busy", or "always"`);
2746
+ config.inboundForkHandlers.when = forkConfig.when;
2747
+ }
2748
+ if (Object.hasOwn(forkConfig, "notify")) {
2749
+ 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"`);
2750
+ config.inboundForkHandlers.notify = forkConfig.notify;
2751
+ }
2752
+ if (Object.hasOwn(forkConfig, "piCommand")) {
2753
+ if (typeof forkConfig.piCommand !== "string") throw new Error(`"inboundForkHandlers.piCommand" must be a string`);
2754
+ const piCommand = forkConfig.piCommand.trim();
2755
+ if (piCommand) config.inboundForkHandlers.piCommand = piCommand;
2756
+ }
2757
+ if (Object.hasOwn(forkConfig, "triggerParentOnSummary")) {
2758
+ const triggerParentOnSummary = forkConfig.triggerParentOnSummary;
2759
+ if (typeof triggerParentOnSummary !== "boolean" && triggerParentOnSummary !== "auto") {
2760
+ throw new Error(`"inboundForkHandlers.triggerParentOnSummary" must be a boolean or "auto"`);
2761
+ }
2762
+ config.inboundForkHandlers.triggerParentOnSummary = triggerParentOnSummary;
2763
+ }
2764
+ }
2765
+ return config;
2766
+ } catch (error) {
2767
+ console.error(`Failed to load intercom config at ${configPath}:`, error);
2768
+ return { ...defaults };
2769
+ }
2770
+ }
2771
+
2772
+ // codex/contact.ts
2773
+ function duplicateSessionNames(sessions) {
2774
+ const counts = /* @__PURE__ */ new Map();
2775
+ for (const session of sessions) {
2776
+ const name = session.name?.trim().toLowerCase();
2777
+ if (name) counts.set(name, (counts.get(name) ?? 0) + 1);
2778
+ }
2779
+ return new Set([...counts].filter(([, count]) => count > 1).map(([name]) => name));
2780
+ }
2781
+ function chooseContactTarget(currentSession, sessions) {
2782
+ const duplicates = duplicateSessionNames(sessions);
2783
+ const name = currentSession.name?.trim() || void 0;
2784
+ const duplicateName = Boolean(name && duplicates.has(name.toLowerCase()));
2785
+ return {
2786
+ target: name && !duplicateName ? name : currentSession.id,
2787
+ id: currentSession.id,
2788
+ ...name ? { name } : {},
2789
+ duplicateName
2790
+ };
2791
+ }
2792
+ async function resolveContactTarget(id, name, listSessions) {
2793
+ try {
2794
+ const sessions = await listSessions();
2795
+ const currentSession = sessions.find((session) => session.id === id);
2796
+ if (currentSession) return chooseContactTarget(currentSession, sessions);
2797
+ } catch {
2798
+ }
2799
+ return { target: id, id, ...name ? { name } : {}, duplicateName: false, fallback: true };
2800
+ }
2801
+
2802
+ // codex/team.ts
2803
+ import { readFile } from "node:fs/promises";
2804
+ import { join as join7 } from "node:path";
2805
+ import {
2806
+ BOSS_PARTICIPANT_ROLES,
2807
+ parseParticipantState as parseParticipantState2,
2808
+ parseWorkerIdentityV2 as parseWorkerIdentityV22,
2809
+ workerIdentityFromEnvironment
2810
+ } from "@ctliz/agent-intercom-core/boss";
2811
+ var LEGACY_LIVE_STATES = /* @__PURE__ */ new Set(["provisioning", "running", "idle", "needs_attention", "stopping"]);
2812
+ var CANONICAL_LIVE_STATES = /* @__PURE__ */ new Set(["provisioning", "registering", "ready", "working", "waiting", "paused", "stalled", "blocked", "unreachable"]);
2813
+ var stringValue = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
2814
+ var connectedTo = (sessions, target) => {
2815
+ const normalized = target.toLowerCase();
2816
+ return sessions.some((session) => session.id === target || session.name?.toLowerCase() === normalized);
2817
+ };
2818
+ function bossIdentityFromEnvironment(env) {
2819
+ const bossKeys = ["AGENT_INTERCOM_BOSS_RUN_ID", "AGENT_INTERCOM_PARTICIPANT_ID", "AGENT_INTERCOM_BINDING_EPOCH"];
2820
+ if (!bossKeys.some((key) => env[key] !== void 0)) return void 0;
2821
+ const identity = workerIdentityFromEnvironment(env);
2822
+ if (!("bossRunId" in identity)) throw new Error("Incomplete Boss worker identity cannot discover a team");
2823
+ return identity;
2824
+ }
2825
+ function canonicalWorker(value) {
2826
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("worker must be an object");
2827
+ const worker = value;
2828
+ const identity = parseWorkerIdentityV22({
2829
+ version: "orc.worker-identity.v2",
2830
+ workerId: worker.id,
2831
+ workerIncarnationId: worker.workerIncarnationId,
2832
+ workerGeneration: worker.workerGeneration,
2833
+ ...worker.bossRunId === void 0 ? {} : { bossRunId: worker.bossRunId },
2834
+ ...worker.participantId === void 0 ? {} : { participantId: worker.participantId },
2835
+ ...worker.bindingEpoch === void 0 ? {} : { bindingEpoch: worker.bindingEpoch }
2836
+ });
2837
+ parseParticipantState2(worker.state, "$.worker.state");
2838
+ if (typeof worker.role !== "string" || !BOSS_PARTICIPANT_ROLES.includes(worker.role)) {
2839
+ throw new Error("worker role is not canonical");
2840
+ }
2841
+ if (worker.owned !== true || !stringValue(worker.managerSessionId) || !stringValue(worker.intercomTarget)) {
2842
+ throw new Error("canonical worker ownership routing is incomplete");
2843
+ }
2844
+ return { ...worker, canonicalIdentity: identity };
2845
+ }
2846
+ function exactBossRosterSession(sessions, worker) {
2847
+ const identity = worker.canonicalIdentity;
2848
+ const target = stringValue(worker.intercomTarget);
2849
+ const role = stringValue(worker.role);
2850
+ const state = stringValue(worker.state);
2851
+ if (!identity || !("bossRunId" in identity) || !target || !role || !state) return void 0;
2852
+ const matches = sessions.filter((candidate) => candidate.id === target);
2853
+ if (matches.length !== 1) return void 0;
2854
+ const [session] = matches;
2855
+ if (!session?.boss?.binding || session.boss.workerIdentity === void 0 || session.boss.participantState === void 0) return void 0;
2856
+ try {
2857
+ const sessionIdentity = parseWorkerIdentityV22(session.boss.workerIdentity);
2858
+ const sessionState = parseParticipantState2(session.boss.participantState, "$.session.boss.participantState");
2859
+ const binding = session.boss.binding;
2860
+ return "bossRunId" in sessionIdentity && session.id === target && binding.sessionId === session.id && binding.state === "active" && binding.bossRunId === identity.bossRunId && binding.participantId === identity.participantId && binding.bindingEpoch === identity.bindingEpoch && binding.role === role && sessionIdentity.workerId === identity.workerId && sessionIdentity.workerIncarnationId === identity.workerIncarnationId && sessionIdentity.workerGeneration === identity.workerGeneration && sessionIdentity.bossRunId === identity.bossRunId && sessionIdentity.participantId === identity.participantId && sessionIdentity.bindingEpoch === identity.bindingEpoch && sessionState === state ? session : void 0;
2861
+ } catch {
2862
+ return void 0;
2863
+ }
2864
+ }
2865
+ async function readWorkers(agentDir) {
2866
+ try {
2867
+ const parsed = JSON.parse(await readFile(join7(agentDir, "intercom", "orchestrator", "workers.json"), "utf8"));
2868
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("worker snapshot must be an object");
2869
+ const snapshot = parsed;
2870
+ if (snapshot.version !== 1 && snapshot.version !== 2 || !Array.isArray(snapshot.workers)) throw new Error("unsupported worker snapshot version");
2871
+ if (snapshot.version === 1) return { version: 1, workers: snapshot.workers };
2872
+ return { version: 2, workers: snapshot.workers.map(canonicalWorker) };
2873
+ } catch {
2874
+ return { version: 1, workers: [] };
2875
+ }
2876
+ }
2877
+ async function resolveIntercomTeam(input) {
2878
+ const env = input.env ?? process.env;
2879
+ const snapshot = await readWorkers(input.agentDir ?? getAgentDirPath());
2880
+ const workers = snapshot.workers;
2881
+ const workerId = stringValue(env.AGENT_INTERCOM_WORKER_ID);
2882
+ const bossIdentity = bossIdentityFromEnvironment(env);
2883
+ const runId = stringValue(env.AGENT_INTERCOM_RUN_ID);
2884
+ const currentMatches = workerId ? workers.filter((worker) => stringValue(worker.id) === workerId && (bossIdentity === void 0 ? !runId || stringValue(worker.runId) === runId : snapshot.version === 2 && worker.canonicalIdentity?.workerId === bossIdentity.workerId && worker.canonicalIdentity.workerIncarnationId === bossIdentity.workerIncarnationId && worker.canonicalIdentity.workerGeneration === bossIdentity.workerGeneration && "bossRunId" in worker.canonicalIdentity && "bossRunId" in bossIdentity && worker.canonicalIdentity.bossRunId === bossIdentity.bossRunId && worker.canonicalIdentity.participantId === bossIdentity.participantId && worker.canonicalIdentity.bindingEpoch === bossIdentity.bindingEpoch)) : [];
2885
+ const current = bossIdentity === void 0 ? currentMatches[0] : currentMatches.length === 1 ? currentMatches[0] : void 0;
2886
+ const currentTarget = stringValue(current?.intercomTarget);
2887
+ const exactCurrentProjection = current !== void 0 && currentTarget === input.selfId && workers.filter((worker) => stringValue(worker.id) === workerId).length === 1 && workers.filter((worker) => stringValue(worker.intercomTarget) === currentTarget).length === 1 && exactBossRosterSession(input.sessions, current) !== void 0;
2888
+ if (bossIdentity !== void 0 && !exactCurrentProjection) {
2889
+ return { self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false }, coworkers: [] };
2890
+ }
2891
+ const managerTarget = stringValue(current?.managerSessionId) ?? (bossIdentity === void 0 ? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID) : void 0);
2892
+ const teamId = managerTarget ?? input.selfId;
2893
+ const currentRole = stringValue(current?.role);
2894
+ const canDiscoverOwnedRoster = bossIdentity === void 0 || currentRole === "manager" || currentRole === "controller";
2895
+ const coworkers = (canDiscoverOwnedRoster ? workers : []).filter((worker) => worker.owned === true).filter((worker) => bossIdentity === void 0 || snapshot.version === 2 && worker.canonicalIdentity !== void 0 && "bossRunId" in worker.canonicalIdentity && "bossRunId" in bossIdentity && worker.canonicalIdentity.bossRunId === bossIdentity.bossRunId).filter((worker) => stringValue(worker.managerSessionId) === teamId).filter((worker) => stringValue(worker.intercomTarget) !== managerTarget).filter((worker) => (snapshot.version === 2 ? CANONICAL_LIVE_STATES : LEGACY_LIVE_STATES).has(stringValue(worker.state) ?? "")).filter((worker) => stringValue(worker.id) !== workerId).map((worker) => {
2896
+ const id = stringValue(worker.id);
2897
+ if (!id) return void 0;
2898
+ const target = stringValue(worker.intercomTarget) ?? id;
2899
+ const connected = bossIdentity === void 0 ? connectedTo(input.sessions, target) : exactBossRosterSession(input.sessions, worker) !== void 0;
2900
+ if (!connected) return void 0;
2901
+ return {
2902
+ id,
2903
+ target,
2904
+ ...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
2905
+ ...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
2906
+ ...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
2907
+ connected
2908
+ };
2909
+ }).filter((member) => Boolean(member));
2910
+ const managerWorker = managerTarget === void 0 ? void 0 : workers.find((worker) => stringValue(worker.intercomTarget) === managerTarget && (bossIdentity === void 0 || snapshot.version === 2 && stringValue(worker.role) === "manager" && worker.canonicalIdentity !== void 0 && "bossRunId" in worker.canonicalIdentity && "bossRunId" in bossIdentity && worker.canonicalIdentity.bossRunId === bossIdentity.bossRunId));
2911
+ const managerConnected = managerTarget === void 0 ? true : bossIdentity === void 0 ? connectedTo(input.sessions, managerTarget) : managerWorker !== void 0 && exactBossRosterSession(input.sessions, managerWorker) !== void 0;
2912
+ return {
2913
+ teamId,
2914
+ self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: bossIdentity === void 0 && !managerTarget },
2915
+ ...managerTarget ? { manager: { target: managerTarget, connected: managerConnected } } : bossIdentity === void 0 ? { manager: { target: input.selfId, connected: true } } : {},
2916
+ coworkers
2917
+ };
2918
+ }
2919
+ function formatIntercomTeam(team) {
2920
+ const lines = [
2921
+ `Manager: ${team.manager ? `${team.manager.target} [${team.manager.connected ? "connected" : "not connected"}]` : "unknown"}`,
2922
+ `You: ${team.self.workerId ?? team.self.id}${team.self.isManager ? " [manager]" : ""}`
2923
+ ];
2924
+ if (!team.coworkers.length) lines.push("Coworkers: none");
2925
+ else {
2926
+ lines.push("Coworkers:");
2927
+ for (const coworker of team.coworkers) {
2928
+ const metadata = [coworker.harness, coworker.role, coworker.state].filter(Boolean).join(", ");
2929
+ lines.push(`- ${coworker.id} target=${coworker.target}${metadata ? ` (${metadata})` : ""} [${coworker.connected ? "connected" : "not connected"}]`);
2930
+ }
2931
+ }
2932
+ return lines.join("\n");
2933
+ }
2934
+
2935
+ // codex/runtime.ts
2936
+ function formatAttachments(attachments) {
2937
+ if (!attachments?.length) return "";
2938
+ return attachments.map((attachment) => {
2939
+ if (attachment.language) {
2940
+ return `
2941
+
2942
+ ---
2943
+ Attachment: ${attachment.name}
2944
+ ~~~${attachment.language}
2945
+ ${attachment.content}
2946
+ ~~~`;
2947
+ }
2948
+ return `
2949
+
2950
+ ---
2951
+ Attachment: ${attachment.name}
2952
+ ${attachment.content}`;
2953
+ }).join("");
2954
+ }
2955
+ function resolveSessionTarget(sessions, nameOrId) {
2956
+ const byId = sessions.find((session) => session.id === nameOrId);
2957
+ if (byId) return byId.id;
2958
+ const lowerName = nameOrId.toLowerCase();
2959
+ const byName = sessions.filter((session) => session.name?.toLowerCase() === lowerName);
2960
+ if (byName.length > 1) {
2961
+ throw new Error(`Multiple sessions named "${nameOrId}" are connected. Use the session ID instead.`);
2962
+ }
2963
+ if (byName[0]) return byName[0].id;
2964
+ if (nameOrId.length >= 4) {
2965
+ const byPrefix = sessions.filter((session) => session.id.startsWith(nameOrId));
2966
+ if (byPrefix.length > 1) {
2967
+ throw new Error(`Multiple sessions match the ID prefix "${nameOrId}". Use the full session ID or a unique name.`);
2968
+ }
2969
+ if (byPrefix[0]) return byPrefix[0].id;
2970
+ }
2971
+ return null;
2972
+ }
2973
+ function formatSessionDisplay(session) {
2974
+ const name = session.name || session.id;
2975
+ return session.origin === "remote" ? `${name} [remote:${session.remoteHostId || "unknown-host"}]` : name;
2976
+ }
2977
+ function formatSessionList(sessions, currentSessionId, currentCwd) {
2978
+ if (!sessions.length) return "No intercom sessions connected.";
2979
+ return sessions.map((session) => {
2980
+ const tags = [
2981
+ session.id === currentSessionId ? "self" : void 0,
2982
+ session.cwd === currentCwd ? "same cwd" : void 0,
2983
+ session.status
2984
+ ].filter((tag) => Boolean(tag));
2985
+ const suffix = tags.length ? ` [${tags.join(", ")}]` : "";
2986
+ return `- ${formatSessionDisplay(session)} (${session.id.slice(0, 8)}) - ${session.cwd} (${session.model})${suffix}`;
2987
+ }).join("\n");
2988
+ }
2989
+
2990
+ // codex/bridge-daemon.ts
2991
+ var APPROVED_INTERCOM_TOOLS = /* @__PURE__ */ new Set([
2992
+ "intercom_whoami",
2993
+ "intercom_team",
2994
+ "intercom_status",
2995
+ "intercom_list",
2996
+ "intercom_set_summary",
2997
+ "intercom_send",
2998
+ "intercom_ask",
2999
+ "intercom_pending",
3000
+ "intercom_reply"
3001
+ ]);
3002
+ var MAX_TOOL_MESSAGES_PER_TURN = 8;
3003
+ var MAX_TOOL_MESSAGES_PER_MINUTE = 30;
3004
+ function formatMessage(from, message, agent) {
3005
+ const replyInstruction = message.expectsReply ? [
3006
+ "",
3007
+ "",
3008
+ "The sender is waiting for a blocking intercom reply.",
3009
+ "The coi sidecar will automatically send your final assistant message as the reply to this ask.",
3010
+ "Do not use intercom_reply or intercom_send to answer this ask; normal Codex MCP intercom tools run under a separate session identity and will not unblock the sender.",
3011
+ "If you need to acknowledge first, put the acknowledgement at the start of your final assistant message."
3012
+ ].join("\n") : "";
3013
+ const attachments = message.content.attachments?.map((attachment) => {
3014
+ const language = attachment.language ? ` (${attachment.language})` : "";
3015
+ return `
3016
+
3017
+ Attachment: ${attachment.name}${language}
3018
+ ${attachment.content}`;
3019
+ }).join("") ?? "";
3020
+ const custom = agent.instructions ? `
3021
+
3022
+ Agent instructions:
3023
+ ${agent.instructions}` : "";
3024
+ return [
3025
+ `Intercom message for ${agent.name}.`,
3026
+ `From: ${formatSessionDisplay(from)} (${from.id})`,
3027
+ `Message id: ${message.id}`,
3028
+ "",
3029
+ message.content.text,
3030
+ attachments,
3031
+ custom,
3032
+ replyInstruction
3033
+ ].join("\n");
3034
+ }
3035
+ function textInput(text) {
3036
+ return { type: "text", text, text_elements: [] };
3037
+ }
3038
+ function statusText(status) {
3039
+ if (!status || typeof status !== "object" || !("type" in status)) return "unknown";
3040
+ const type = status.type;
3041
+ return typeof type === "string" ? type : "unknown";
3042
+ }
3043
+ function getThreadId(result) {
3044
+ const thread = result && typeof result === "object" ? result.thread : void 0;
3045
+ if (!thread || typeof thread !== "object" || typeof thread.id !== "string") {
3046
+ throw new Error("Codex app-server response did not include thread.id");
3047
+ }
3048
+ return thread.id;
3049
+ }
3050
+ function threadSandboxMode(sandboxPolicy) {
3051
+ if (!sandboxPolicy || typeof sandboxPolicy !== "object" || Array.isArray(sandboxPolicy)) return "read-only";
3052
+ const type = sandboxPolicy.type;
3053
+ switch (type) {
3054
+ case "readOnly":
3055
+ case "read-only":
3056
+ return "read-only";
3057
+ case "workspaceWrite":
3058
+ case "workspace-write":
3059
+ return "workspace-write";
3060
+ case "dangerFullAccess":
3061
+ case "danger-full-access":
3062
+ return "danger-full-access";
3063
+ default:
3064
+ return "read-only";
3065
+ }
3066
+ }
3067
+ function protectedBossClientForBridge(config) {
3068
+ assertBossCanonicalData(config, "$.bridgeConfig");
3069
+ if (!Array.isArray(config.agents)) return void 0;
3070
+ for (const agent of config.agents) {
3071
+ if (typeof agent !== "object" || agent === null || Array.isArray(agent)) continue;
3072
+ if (agent.bossClient === "boss_participant" || agent.bossClient === "boss_reviewer") return agent.bossClient;
3073
+ }
3074
+ return void 0;
3075
+ }
3076
+ function bridgeAgentSandboxMode(agent) {
3077
+ return agent.sandboxPolicy === void 0 ? bridgeAgentDefaultSandbox(agent) ?? "read-only" : threadSandboxMode(agent.sandboxPolicy);
3078
+ }
3079
+ function bridgeAgentTurnSandboxPolicy(agent) {
3080
+ if (agent.sandboxPolicy !== void 0) return agent.sandboxPolicy;
3081
+ if (bridgeAgentSandboxMode(agent) === "workspace-write") {
3082
+ throw new Error("workspace-write requires unavailable broker-owned assigned workspace authority");
3083
+ }
3084
+ return { type: "readOnly", networkAccess: false };
3085
+ }
3086
+ function getTurnId(result) {
3087
+ const turn = result && typeof result === "object" ? result.turn : void 0;
3088
+ if (!turn || typeof turn !== "object" || typeof turn.id !== "string") {
3089
+ throw new Error("Codex app-server response did not include turn.id");
3090
+ }
3091
+ return turn.id;
3092
+ }
3093
+ function getNotificationThreadId(params) {
3094
+ if (!params || typeof params !== "object") return null;
3095
+ const value = params.threadId;
3096
+ return typeof value === "string" ? value : null;
3097
+ }
3098
+ function getNotificationTurnId(params) {
3099
+ if (!params || typeof params !== "object") return null;
3100
+ const direct = params.turnId;
3101
+ if (typeof direct === "string") return direct;
3102
+ const turn = params.turn;
3103
+ if (turn && typeof turn === "object" && typeof turn.id === "string") {
3104
+ return turn.id;
3105
+ }
3106
+ return null;
3107
+ }
3108
+ function getCompletedAgentText(params) {
3109
+ if (!params || typeof params !== "object") return null;
3110
+ const item = params.item;
3111
+ if (!item || typeof item !== "object") return null;
3112
+ const raw = item;
3113
+ return raw.type === "agentMessage" && typeof raw.text === "string" ? raw.text : null;
3114
+ }
3115
+ function intercomSendFromArgs(rawArgs) {
3116
+ let args;
3117
+ try {
3118
+ args = parseToolArguments(rawArgs);
3119
+ } catch {
3120
+ return null;
3121
+ }
3122
+ return typeof args.to === "string" && typeof args.message === "string" ? { to: args.to, message: args.message } : null;
3123
+ }
3124
+ function getCompletedIntercomSend(params) {
3125
+ if (!params || typeof params !== "object") return null;
3126
+ const item = params.item;
3127
+ if (!isRecord2(item)) return null;
3128
+ const rawName = item.name ?? item.toolName ?? item.tool_name;
3129
+ if (typeof rawName !== "string" || normalizeToolName(rawName) !== "intercom_send") return null;
3130
+ return intercomSendFromArgs(item.arguments ?? item.args ?? item.input);
3131
+ }
3132
+ function getApprovedIntercomSend(params) {
3133
+ if (getApprovedIntercomToolFromApproval(params) !== "intercom_send") return null;
3134
+ if (!isRecord2(params)) return null;
3135
+ const meta = isRecord2(params._meta) ? params._meta : {};
3136
+ return intercomSendFromArgs(meta.tool_params ?? meta.toolParams ?? meta.tool_params_json);
3137
+ }
3138
+ function isRecord2(value) {
3139
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3140
+ }
3141
+ function asString(value, name) {
3142
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${name} must be a non-empty string`);
3143
+ return value;
3144
+ }
3145
+ function asOptionalPositiveInteger(value, name) {
3146
+ if (value === void 0) return void 0;
3147
+ return validateAskTimeoutMs(value, name);
3148
+ }
3149
+ function normalizeToolName(name) {
3150
+ const mcpMatch = name.match(/(?:^|__|\.)intercom_(whoami|status|list|set_summary|send|ask|pending|reply)$/);
3151
+ if (mcpMatch) return `intercom_${mcpMatch[1]}`;
3152
+ return name;
3153
+ }
3154
+ function parseToolArguments(value) {
3155
+ if (value === void 0 || value === null) return {};
3156
+ if (typeof value === "string") {
3157
+ const parsed = value.trim() ? JSON.parse(value) : {};
3158
+ if (!isRecord2(parsed)) throw new Error("tool arguments must be an object");
3159
+ return parsed;
3160
+ }
3161
+ if (!isRecord2(value)) throw new Error("tool arguments must be an object");
3162
+ return value;
3163
+ }
3164
+ function extractToolCall(message) {
3165
+ const params = isRecord2(message.params) ? message.params : {};
3166
+ const nested = ["toolCall", "tool", "call", "item"].map((key) => params[key]).find(isRecord2) ?? {};
3167
+ const rawName = params.name ?? params.toolName ?? params.tool_name ?? nested.name ?? nested.toolName ?? nested.tool_name;
3168
+ if (typeof rawName !== "string") throw new Error("item/tool/call did not include a tool name");
3169
+ const rawArgs = params.arguments ?? params.args ?? params.input ?? nested.arguments ?? nested.args ?? nested.input;
3170
+ const threadId = typeof params.threadId === "string" ? params.threadId : null;
3171
+ const turnId = typeof params.turnId === "string" ? params.turnId : null;
3172
+ return { threadId, turnId, name: normalizeToolName(rawName), args: parseToolArguments(rawArgs) };
3173
+ }
3174
+ function textToolResult(text, structuredContent, isError = false) {
3175
+ return {
3176
+ content: [{ type: "text", text }],
3177
+ ...structuredContent ? { structuredContent } : {},
3178
+ ...isError ? { isError: true } : {}
3179
+ };
3180
+ }
3181
+ function appServerToolResponse(result) {
3182
+ return {
3183
+ success: !result.isError,
3184
+ contentItems: result.content,
3185
+ ...result.structuredContent ? { structuredContent: result.structuredContent } : {}
3186
+ };
3187
+ }
3188
+ var VirtualCodexAgent = class {
3189
+ constructor(agent, app, state, statePath, hooks = {}, options = {}, scopeId) {
3190
+ this.agent = agent;
3191
+ this.app = app;
3192
+ this.state = state;
3193
+ this.statePath = statePath;
3194
+ this.hooks = hooks;
3195
+ this.threadId = agent.threadId ?? state.agents[agent.id]?.threadId ?? null;
3196
+ this.scopeId = scopeId;
3197
+ this.client = options.client ?? new IntercomClient(scopeId ? { scopeId } : {});
3198
+ this.prepareConnection = options.prepareConnection ?? (async () => {
3199
+ const config = loadConfig();
3200
+ await spawnBrokerIfNeeded(config.brokerCommand, config.brokerArgs);
3201
+ });
3202
+ this.reconnectDelays = options.reconnectDelays?.length ? options.reconnectDelays : [250, 500, 1e3, 2e3, 5e3];
3203
+ }
3204
+ agent;
3205
+ app;
3206
+ state;
3207
+ statePath;
3208
+ hooks;
3209
+ client;
3210
+ threadId;
3211
+ activeTurnId = null;
3212
+ waiters = /* @__PURE__ */ new Map();
3213
+ finalMessages = /* @__PURE__ */ new Map();
3214
+ toolReplyWaiters = /* @__PURE__ */ new Map();
3215
+ messageQueue = Promise.resolve();
3216
+ idleWaiters = [];
3217
+ turnCompletionWaiters = /* @__PURE__ */ new Map();
3218
+ toolMessageCountsByTurn = /* @__PURE__ */ new Map();
3219
+ toolMessageTimestamps = [];
3220
+ externalTurns = /* @__PURE__ */ new Map();
3221
+ connectPromise = null;
3222
+ reconnectTimer = null;
3223
+ reconnectAttempt = 0;
3224
+ reconnectEnabled = true;
3225
+ intercomStartedAt = Date.now();
3226
+ prepareConnection;
3227
+ reconnectDelays;
3228
+ scopeId;
3229
+ async start() {
3230
+ this.reconnectEnabled = true;
3231
+ this.client.on("message", (from, message, deliveryId) => {
3232
+ const routed = this.routeMessage(from, message);
3233
+ this.client.acknowledgeMessage(deliveryId);
3234
+ void routed.catch((error) => {
3235
+ this.client.updatePresence({ status: `error: ${error instanceof Error ? error.message : String(error)}` });
3236
+ });
3237
+ });
3238
+ this.client.on("error", (error) => {
3239
+ process.stderr.write(`intercom ${this.agent.id}: ${error.message}
3240
+ `);
3241
+ });
3242
+ this.client.on("disconnected", (error) => {
3243
+ this.rejectAllToolReplies(new Error(`Disconnected while waiting for reply: ${error.message}`, { cause: error }));
3244
+ this.scheduleReconnect();
3245
+ });
3246
+ await this.connectIntercom();
3247
+ }
3248
+ async connectIntercom() {
3249
+ this.clearReconnectTimer();
3250
+ if (this.client.isConnected()) return;
3251
+ if (this.connectPromise) return this.connectPromise;
3252
+ this.connectPromise = (async () => {
3253
+ await this.prepareConnection();
3254
+ await this.client.connect({
3255
+ name: this.agent.name,
3256
+ cwd: this.agent.cwd,
3257
+ model: this.agent.model ?? "codex-app-server",
3258
+ pid: process.pid,
3259
+ startedAt: this.intercomStartedAt,
3260
+ lastActivity: Date.now(),
3261
+ status: this.threadId ? "idle" : "idle:no-thread"
3262
+ }, this.agent.id);
3263
+ this.reconnectAttempt = 0;
3264
+ })();
3265
+ try {
3266
+ await this.connectPromise;
3267
+ } finally {
3268
+ this.connectPromise = null;
3269
+ }
3270
+ }
3271
+ scheduleReconnect() {
3272
+ if (!this.reconnectEnabled || this.reconnectTimer) return;
3273
+ const delay2 = this.reconnectDelays[Math.min(this.reconnectAttempt, this.reconnectDelays.length - 1)];
3274
+ this.reconnectTimer = setTimeout(() => {
3275
+ this.reconnectTimer = null;
3276
+ void this.connectIntercom().then(() => {
3277
+ if (!this.client.isConnected()) {
3278
+ this.reconnectAttempt += 1;
3279
+ this.scheduleReconnect();
3280
+ }
3281
+ }).catch((error) => {
3282
+ this.reconnectAttempt += 1;
3283
+ process.stderr.write(`intercom ${this.agent.id}: reconnect failed: ${error instanceof Error ? error.message : String(error)}
3284
+ `);
3285
+ this.scheduleReconnect();
3286
+ });
3287
+ }, delay2);
3288
+ this.reconnectTimer.unref?.();
3289
+ }
3290
+ clearReconnectTimer() {
3291
+ if (!this.reconnectTimer) return;
3292
+ clearTimeout(this.reconnectTimer);
3293
+ this.reconnectTimer = null;
3294
+ }
3295
+ rejectAllToolReplies(error) {
3296
+ for (const [replyTo, waiter] of Array.from(this.toolReplyWaiters.entries())) {
3297
+ clearTimeout(waiter.timeout);
3298
+ waiter.cleanup?.();
3299
+ this.toolReplyWaiters.delete(replyTo);
3300
+ waiter.reject(error);
3301
+ }
3302
+ }
3303
+ async stop() {
3304
+ this.reconnectEnabled = false;
3305
+ this.clearReconnectTimer();
3306
+ if (this.connectPromise) {
3307
+ try {
3308
+ await this.connectPromise;
3309
+ } catch {
3310
+ }
3311
+ }
3312
+ await this.client.disconnect();
3313
+ }
3314
+ get id() {
3315
+ return this.agent.id;
3316
+ }
3317
+ async getContactTarget() {
3318
+ return resolveContactTarget(this.agent.id, this.agent.name, () => this.client.listSessions());
3319
+ }
3320
+ ownsThread(threadId) {
3321
+ return this.threadId === threadId;
3322
+ }
3323
+ onNotification(message) {
3324
+ const threadId = getNotificationThreadId(message.params);
3325
+ if (!threadId || threadId !== this.threadId) return;
3326
+ if (message.method === "error") {
3327
+ const params = isRecord2(message.params) ? message.params : {};
3328
+ const detail = isRecord2(params.error) && typeof params.error.message === "string" ? params.error.message : "Codex turn error";
3329
+ const willRetry = params.willRetry === true;
3330
+ this.client.updatePresence({ status: willRetry ? `reconnecting: ${detail}` : `error: ${detail}` });
3331
+ process.stderr.write(`codex ${this.agent.id}: ${detail}${willRetry ? "; app-server is retrying" : ""}
3332
+ `);
3333
+ return;
3334
+ }
3335
+ if (message.method === "turn/started") {
3336
+ this.activeTurnId = getNotificationTurnId(message.params);
3337
+ this.client.updatePresence({ status: "active" });
3338
+ return;
3339
+ }
3340
+ if (message.method === "thread/status/changed" && message.params && typeof message.params === "object") {
3341
+ const status = statusText(message.params.status);
3342
+ this.client.updatePresence({ status });
3343
+ return;
3344
+ }
3345
+ if (message.method === "item/completed") {
3346
+ const turnId = getNotificationTurnId(message.params);
3347
+ const text = getCompletedAgentText(message.params);
3348
+ if (turnId && text) this.finalMessages.set(turnId, text);
3349
+ const intercomSend = getCompletedIntercomSend(message.params);
3350
+ if (turnId && intercomSend) {
3351
+ void this.replyToWaitersFromIntercomSend(turnId, intercomSend).catch((error) => {
3352
+ process.stderr.write(`reply failed for ${this.agent.id} after intercom_send: ${error instanceof Error ? error.message : String(error)}
3353
+ `);
3354
+ });
3355
+ }
3356
+ return;
3357
+ }
3358
+ if (message.method === "turn/completed") {
3359
+ const turnId = getNotificationTurnId(message.params);
3360
+ if (!turnId) return;
3361
+ if (this.activeTurnId === turnId) this.activeTurnId = null;
3362
+ this.client.updatePresence({ status: "idle" });
3363
+ const idleWaiters = this.idleWaiters.splice(0);
3364
+ for (const resolve4 of idleWaiters) resolve4();
3365
+ void this.finishTurn(turnId);
3366
+ }
3367
+ }
3368
+ async ensureThread() {
3369
+ if (this.threadId) {
3370
+ try {
3371
+ const sandbox2 = bridgeAgentSandboxMode(this.agent);
3372
+ await this.app.request("thread/resume", {
3373
+ threadId: this.threadId,
3374
+ cwd: this.agent.cwd,
3375
+ model: this.agent.model ?? null,
3376
+ approvalPolicy: bridgeAgentApprovalPolicy(this.agent),
3377
+ sandbox: sandbox2
3378
+ });
3379
+ return this.threadId;
3380
+ } catch {
3381
+ this.threadId = null;
3382
+ }
3383
+ }
3384
+ const sandbox = bridgeAgentSandboxMode(this.agent);
3385
+ const result = await this.app.request("thread/start", {
3386
+ cwd: this.agent.cwd,
3387
+ model: this.agent.model ?? null,
3388
+ approvalPolicy: bridgeAgentApprovalPolicy(this.agent),
3389
+ sandbox,
3390
+ serviceName: "codex-intercom",
3391
+ developerInstructions: this.agent.instructions ?? null,
3392
+ threadSource: "cli"
3393
+ });
3394
+ this.threadId = getThreadId(result);
3395
+ this.state.agents[this.agent.id] = { threadId: this.threadId, updatedAt: Date.now() };
3396
+ saveBridgeState(this.statePath, this.state);
3397
+ await this.app.request("thread/name/set", { threadId: this.threadId, name: this.agent.name }).catch(() => void 0);
3398
+ this.client.updatePresence({ status: "idle" });
3399
+ return this.threadId;
3400
+ }
3401
+ routeMessage(from, message) {
3402
+ const toolWaiter = this.toolReplyWaiters.get(message.replyTo ?? "");
3403
+ if (toolWaiter) {
3404
+ if (from.id === toolWaiter.from) {
3405
+ this.toolReplyWaiters.delete(message.replyTo ?? "");
3406
+ clearTimeout(toolWaiter.timeout);
3407
+ toolWaiter.cleanup?.();
3408
+ toolWaiter.resolve(message);
3409
+ return Promise.resolve();
3410
+ }
3411
+ }
3412
+ const run = this.messageQueue.catch(() => void 0).then(() => this.handleMessage(from, message));
3413
+ this.messageQueue = run.catch((error) => {
3414
+ this.client.updatePresence({ status: `error: ${error instanceof Error ? error.message : String(error)}` });
3415
+ });
3416
+ return run;
3417
+ }
3418
+ async handleMessage(from, message) {
3419
+ const threadId = await this.ensureThread();
3420
+ await this.waitUntilIdle();
3421
+ const input = [textInput(formatMessage(from, message, this.agent))];
3422
+ const result = await this.startTurn(threadId, input);
3423
+ const turnId = getTurnId(result);
3424
+ this.externalTurns.set(turnId, { from, message });
3425
+ const completed = this.waitForTurnCompletion(turnId);
3426
+ if (message.expectsReply) {
3427
+ const waiters = this.waiters.get(turnId) ?? [];
3428
+ waiters.push({ from, message });
3429
+ this.waiters.set(turnId, waiters);
3430
+ }
3431
+ await completed;
3432
+ }
3433
+ startTurn(threadId, input) {
3434
+ this.client.updatePresence({ status: "active" });
3435
+ return this.app.request("turn/start", {
3436
+ threadId,
3437
+ input,
3438
+ cwd: this.agent.cwd,
3439
+ approvalPolicy: bridgeAgentApprovalPolicy(this.agent),
3440
+ sandboxPolicy: bridgeAgentTurnSandboxPolicy(this.agent),
3441
+ model: this.agent.model ?? null
3442
+ });
3443
+ }
3444
+ async replyToWaiters(turnId) {
3445
+ const waiters = this.waiters.get(turnId);
3446
+ if (!waiters?.length) return;
3447
+ this.waiters.delete(turnId);
3448
+ const reply = this.finalMessages.get(turnId)?.trim() || "Codex turn completed without a final message.";
3449
+ for (const waiter of waiters) {
3450
+ await this.client.send(waiter.from.id, { text: reply, replyTo: waiter.message.id }).catch((error) => {
3451
+ process.stderr.write(`reply failed for ${this.agent.id}: ${error instanceof Error ? error.message : String(error)}
3452
+ `);
3453
+ });
3454
+ }
3455
+ }
3456
+ async replyToWaitersFromIntercomSend(turnId, send) {
3457
+ const waiters = this.waiters.get(turnId);
3458
+ if (!waiters?.length) return;
3459
+ const lowerTo = send.to.toLowerCase();
3460
+ const remaining = [];
3461
+ for (const waiter of waiters) {
3462
+ const matchesSender = send.to === waiter.from.id || waiter.from.id.startsWith(send.to) || waiter.from.name?.toLowerCase() === lowerTo;
3463
+ if (!matchesSender) {
3464
+ remaining.push(waiter);
3465
+ continue;
3466
+ }
3467
+ await this.client.send(waiter.from.id, { text: send.message, replyTo: waiter.message.id }).catch((error) => {
3468
+ remaining.push(waiter);
3469
+ process.stderr.write(`reply failed for ${this.agent.id}: ${error instanceof Error ? error.message : String(error)}
3470
+ `);
3471
+ });
3472
+ }
3473
+ if (remaining.length) {
3474
+ this.waiters.set(turnId, remaining);
3475
+ } else {
3476
+ this.waiters.delete(turnId);
3477
+ }
3478
+ }
3479
+ waitUntilIdle() {
3480
+ if (!this.activeTurnId) return Promise.resolve();
3481
+ return new Promise((resolve4) => {
3482
+ this.idleWaiters.push(resolve4);
3483
+ });
3484
+ }
3485
+ waitForTurnCompletion(turnId) {
3486
+ return new Promise((resolve4) => {
3487
+ const waiters = this.turnCompletionWaiters.get(turnId) ?? [];
3488
+ waiters.push(resolve4);
3489
+ this.turnCompletionWaiters.set(turnId, waiters);
3490
+ });
3491
+ }
3492
+ async finishTurn(turnId) {
3493
+ try {
3494
+ await this.replyToWaiters(turnId);
3495
+ const external = this.externalTurns.get(turnId);
3496
+ if (external && this.threadId) {
3497
+ this.hooks.onExternalTurnComplete?.({
3498
+ agentId: this.agent.id,
3499
+ threadId: this.threadId,
3500
+ from: external.from,
3501
+ message: external.message,
3502
+ response: this.finalMessages.get(turnId)?.trim() || "Codex turn completed without a final message."
3503
+ });
3504
+ }
3505
+ } finally {
3506
+ this.externalTurns.delete(turnId);
3507
+ this.finalMessages.delete(turnId);
3508
+ this.waiters.delete(turnId);
3509
+ this.toolMessageCountsByTurn.delete(turnId);
3510
+ const waiters = this.turnCompletionWaiters.get(turnId) ?? [];
3511
+ this.turnCompletionWaiters.delete(turnId);
3512
+ for (const resolve4 of waiters) resolve4();
3513
+ }
3514
+ }
3515
+ async handleToolCall(name, args, turnId, signal) {
3516
+ try {
3517
+ const result = await this.callIntercomTool(name, args, turnId, signal);
3518
+ return appServerToolResponse(result);
3519
+ } catch (error) {
3520
+ return appServerToolResponse(textToolResult(error instanceof Error ? error.message : String(error), { ok: false }, true));
3521
+ }
3522
+ }
3523
+ async callIntercomTool(name, args, turnId, signal) {
3524
+ switch (name) {
3525
+ case "intercom_whoami":
3526
+ return textToolResult(
3527
+ `session_id: ${this.agent.id}
3528
+ name: ${this.agent.name}
3529
+ cwd: ${this.agent.cwd}`,
3530
+ { session_id: this.agent.id, name: this.agent.name, cwd: this.agent.cwd, model: this.agent.model ?? "codex-app-server" }
3531
+ );
3532
+ case "intercom_team": {
3533
+ const sessions = await this.client.listSessions();
3534
+ const team = await resolveIntercomTeam({ selfId: this.agent.id, sessions });
3535
+ return textToolResult(formatIntercomTeam(team), team);
3536
+ }
3537
+ case "intercom_status": {
3538
+ const sessions = await this.client.listSessions();
3539
+ return textToolResult(
3540
+ `Connected: Yes
3541
+ Session ID: ${this.agent.id}
3542
+ Active sessions: ${sessions.length}`,
3543
+ { connected: true, session_id: this.agent.id, active_sessions: sessions.length }
3544
+ );
3545
+ }
3546
+ case "intercom_list": {
3547
+ const includeSelf = typeof args.include_self === "boolean" ? args.include_self : false;
3548
+ const sessions = (await this.client.listSessions()).filter((session) => includeSelf || session.id !== this.agent.id);
3549
+ return textToolResult(formatSessionList(sessions, this.agent.id, this.agent.cwd), { sessions });
3550
+ }
3551
+ case "intercom_set_summary": {
3552
+ const summary = asString(args.summary, "summary");
3553
+ this.client.updatePresence({ status: summary.trim() || "idle" });
3554
+ return textToolResult("Summary updated.", { ok: true, summary });
3555
+ }
3556
+ case "intercom_send": {
3557
+ const limit = this.reserveToolMessage(turnId);
3558
+ if (limit) return limit;
3559
+ const to = asString(args.to, "to");
3560
+ const message = asString(args.message, "message");
3561
+ const sendTo = await this.resolveTarget(to);
3562
+ const result = await this.client.send(sendTo, { text: message });
3563
+ if (!result.delivered) {
3564
+ return textToolResult(`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);
3565
+ }
3566
+ return textToolResult(`Message sent to ${to}.`, { ok: true, message_id: result.id, to });
3567
+ }
3568
+ case "intercom_ask": {
3569
+ const limit = this.reserveToolMessage(turnId);
3570
+ if (limit) return limit;
3571
+ const to = asString(args.to, "to");
3572
+ const message = asString(args.message, "message");
3573
+ const timeoutMs = asOptionalPositiveInteger(args.timeout_ms, "timeout_ms") ?? DEFAULT_ASK_TIMEOUT_MS;
3574
+ const sendTo = await this.resolveTarget(to);
3575
+ const questionId = randomUUID4();
3576
+ const replyPromise = this.waitForToolReply(sendTo, questionId, timeoutMs, signal);
3577
+ void replyPromise.catch(() => void 0);
3578
+ const result = await this.client.send(sendTo, { messageId: questionId, text: message, expectsReply: true });
3579
+ if (!result.delivered) {
3580
+ this.rejectToolReply(questionId, new Error(result.reason ?? "Session may not exist or has disconnected."));
3581
+ return textToolResult(`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);
3582
+ }
3583
+ const reply = await replyPromise;
3584
+ return textToolResult(`Reply from ${to}:
3585
+ ${reply.content.text}${formatAttachments(reply.content.attachments)}`, { ok: true, message_id: result.id, reply });
3586
+ }
3587
+ case "intercom_pending":
3588
+ return textToolResult("No unread messages.", { unread_messages: [], pending_asks: [] });
3589
+ case "intercom_reply":
3590
+ return textToolResult("No matching pending ask. App-server sidecar asks are answered automatically by final assistant messages.", { ok: false }, true);
3591
+ default:
3592
+ return textToolResult(`Unknown tool: ${name}`, { ok: false }, true);
3593
+ }
3594
+ }
3595
+ reserveToolMessage(turnId) {
3596
+ const now = Date.now();
3597
+ this.toolMessageTimestamps = this.toolMessageTimestamps.filter((timestamp) => now - timestamp < 6e4);
3598
+ if (this.toolMessageTimestamps.length >= MAX_TOOL_MESSAGES_PER_MINUTE) {
3599
+ return textToolResult(`Intercom message limit reached: max ${MAX_TOOL_MESSAGES_PER_MINUTE} sidecar-originated sends per minute.`, { ok: false, limit: "per_minute" }, true);
3600
+ }
3601
+ const key = turnId ?? "unknown-turn";
3602
+ const count = this.toolMessageCountsByTurn.get(key) ?? 0;
3603
+ if (count >= MAX_TOOL_MESSAGES_PER_TURN) {
3604
+ return textToolResult(`Intercom message limit reached: max ${MAX_TOOL_MESSAGES_PER_TURN} sidecar-originated sends per turn.`, { ok: false, limit: "per_turn" }, true);
3605
+ }
3606
+ this.toolMessageCountsByTurn.set(key, count + 1);
3607
+ this.toolMessageTimestamps.push(now);
3608
+ return null;
3609
+ }
3610
+ async resolveTarget(to) {
3611
+ const sessions = await this.client.listSessions();
3612
+ return resolveSessionTarget(sessions, to) ?? to;
3613
+ }
3614
+ waitForToolReply(from, replyTo, timeoutMs = DEFAULT_ASK_TIMEOUT_MS, signal) {
3615
+ return new Promise((resolve4, reject) => {
3616
+ if (signal?.aborted) {
3617
+ reject(new Error("intercom_ask cancelled"));
3618
+ return;
3619
+ }
3620
+ let timeout;
3621
+ const cleanup = () => {
3622
+ clearTimeout(timeout);
3623
+ signal?.removeEventListener("abort", onAbort);
3624
+ };
3625
+ const onAbort = () => {
3626
+ this.toolReplyWaiters.delete(replyTo);
3627
+ cleanup();
3628
+ void this.client.cancelAsk(replyTo);
3629
+ reject(new Error("intercom_ask cancelled"));
3630
+ };
3631
+ timeout = setTimeout(() => {
3632
+ this.toolReplyWaiters.delete(replyTo);
3633
+ void this.client.deferAsk(replyTo);
3634
+ signal?.removeEventListener("abort", onAbort);
3635
+ reject(new Error(`No reply from "${from}" within ${Math.round(timeoutMs / 1e3)} seconds`));
3636
+ }, timeoutMs);
3637
+ signal?.addEventListener("abort", onAbort, { once: true });
3638
+ this.toolReplyWaiters.set(replyTo, { from, resolve: resolve4, reject, timeout, cleanup });
3639
+ });
3640
+ }
3641
+ rejectToolReply(replyTo, error) {
3642
+ const waiter = this.toolReplyWaiters.get(replyTo);
3643
+ if (!waiter) return;
3644
+ clearTimeout(waiter.timeout);
3645
+ waiter.cleanup?.();
3646
+ this.toolReplyWaiters.delete(replyTo);
3647
+ waiter.reject(error);
3648
+ void this.client.cancelAsk(replyTo);
3649
+ }
3650
+ };
3651
+ var CodexBridgeDaemon = class {
3652
+ constructor(config, hooks = {}, scopeId) {
3653
+ this.config = config;
3654
+ this.hooks = hooks;
3655
+ this.scopeId = scopeId;
3656
+ const protectedBossClient = protectedBossClientForBridge(config);
3657
+ assertHardenedBossProviderAuthority(protectedBossClient);
3658
+ assertHardenedBossBridgeConfig(config);
3659
+ for (const agent of config.agents) assertHardenedBossAgentConfig(agent);
3660
+ this.app = new CodexAppServerClient(config.appServer, protectedBossClient);
3661
+ this.app.setServerRequestHandler((message) => this.handleServerRequest(message));
3662
+ }
3663
+ config;
3664
+ hooks;
3665
+ scopeId;
3666
+ app;
3667
+ agents = [];
3668
+ inflightToolCalls = /* @__PURE__ */ new Map();
3669
+ async start() {
3670
+ assertHardenedBossProviderAuthority(protectedBossClientForBridge(this.config));
3671
+ assertHardenedBossBridgeConfig(this.config);
3672
+ for (const agent of this.config.agents) assertHardenedBossAgentConfig(agent);
3673
+ const intercomConfig = loadConfig();
3674
+ await spawnBrokerIfNeeded(intercomConfig.brokerCommand, intercomConfig.brokerArgs);
3675
+ await this.app.connect();
3676
+ const state = loadBridgeState(this.config.statePath);
3677
+ this.app.on("notification", (message) => {
3678
+ if (message.method === "notifications/cancelled" && message.params && typeof message.params === "object") {
3679
+ const requestId = message.params.requestId;
3680
+ if (typeof requestId === "string" || typeof requestId === "number") {
3681
+ this.inflightToolCalls.get(requestId)?.abort();
3682
+ }
3683
+ }
3684
+ for (const agent of this.agents) agent.onNotification(message);
3685
+ });
3686
+ this.agents = this.config.agents.map((agent) => new VirtualCodexAgent(agent, this.app, state, this.config.statePath, this.hooks, {}, this.scopeId));
3687
+ for (const agent of this.agents) await agent.start();
3688
+ process.stderr.write(`codex-intercom bridge running ${this.agents.length} virtual agent(s)
3689
+ `);
3690
+ }
3691
+ async stop() {
3692
+ for (const agent of this.agents) await agent.stop().catch(() => void 0);
3693
+ await this.app.disconnect();
3694
+ }
3695
+ async ensureThreadForAgent(agentId) {
3696
+ const agent = this.agents.find((candidate) => candidate.id === agentId);
3697
+ if (!agent) throw new Error(`No bridge agent registered with id: ${agentId}`);
3698
+ return agent.ensureThread();
3699
+ }
3700
+ async getContactTargetForAgent(agentId) {
3701
+ const agent = this.agents.find((candidate) => candidate.id === agentId);
3702
+ if (!agent) throw new Error(`No bridge agent registered with id: ${agentId}`);
3703
+ return agent.getContactTarget();
3704
+ }
3705
+ async handleServerRequest(message) {
3706
+ if (message.method === "mcpServer/elicitation/request" && isIntercomToolApprovalRequest(message.params)) {
3707
+ const threadId = getNotificationThreadId(message.params);
3708
+ const turnId = getNotificationTurnId(message.params);
3709
+ const intercomSend = getApprovedIntercomSend(message.params);
3710
+ if (threadId && turnId && intercomSend) {
3711
+ const agent2 = this.agents.find((candidate) => candidate.ownsThread(threadId));
3712
+ if (agent2) await agent2.replyToWaitersFromIntercomSend(turnId, intercomSend);
3713
+ }
3714
+ return { action: "accept", content: {}, _meta: null };
3715
+ }
3716
+ if (message.method !== "item/tool/call") {
3717
+ if (!message.method) throw new Error("Unsupported app-server request");
3718
+ return defaultServerRequestResponse(message.method);
3719
+ }
3720
+ const call = extractToolCall(message);
3721
+ const agent = call.threadId ? this.agents.find((candidate) => candidate.ownsThread(call.threadId)) : this.agents[0];
3722
+ if (!agent) return appServerToolResponse(textToolResult("No bridge agent owns this tool call.", { ok: false }, true));
3723
+ const requestId = message.id;
3724
+ const abortController = typeof requestId === "string" || typeof requestId === "number" ? new AbortController() : null;
3725
+ if (abortController && requestId !== void 0) this.inflightToolCalls.set(requestId, abortController);
3726
+ try {
3727
+ return await agent.handleToolCall(call.name, call.args, call.turnId, abortController?.signal);
3728
+ } finally {
3729
+ if (abortController && requestId !== void 0) this.inflightToolCalls.delete(requestId);
3730
+ }
3731
+ }
3732
+ };
3733
+ function isIntercomToolApprovalRequest(params) {
3734
+ if (!isRecord2(params)) return false;
3735
+ const meta = isRecord2(params._meta) ? params._meta : {};
3736
+ return params.serverName === "codex-intercom" && meta.codex_approval_kind === "mcp_tool_call" && Boolean(getApprovedIntercomToolFromApproval(params));
3737
+ }
3738
+ function getApprovedIntercomToolFromApproval(params) {
3739
+ if (!isRecord2(params)) return null;
3740
+ const meta = isRecord2(params._meta) ? params._meta : {};
3741
+ const candidates = [
3742
+ meta.tool,
3743
+ meta.toolName,
3744
+ meta.tool_name,
3745
+ meta.name,
3746
+ typeof params.message === "string" ? params.message.match(/tool "([^"]+)"/)?.[1] : void 0
3747
+ ];
3748
+ for (const candidate of candidates) {
3749
+ if (typeof candidate === "string" && APPROVED_INTERCOM_TOOLS.has(candidate)) return candidate;
3750
+ }
3751
+ return null;
3752
+ }
3753
+ async function main() {
3754
+ const configPath = process.argv.includes("--config") ? process.argv[process.argv.indexOf("--config") + 1] : void 0;
3755
+ const config = loadBridgeConfig(configPath);
3756
+ if (!config.agents.length) throw new Error("Bridge config must include at least one agent");
3757
+ const daemon = new CodexBridgeDaemon(config);
3758
+ const stop = () => {
3759
+ void daemon.stop().finally(() => process.exit(0));
3760
+ };
3761
+ process.on("SIGINT", stop);
3762
+ process.on("SIGTERM", stop);
3763
+ await daemon.start();
3764
+ await once(process, "SIGTERM");
3765
+ }
3766
+ if (process.argv[1] && (basename(process.argv[1]) === "bridge-daemon.ts" || basename(process.argv[1]) === "bridge-daemon.mjs")) {
3767
+ void main().catch((error) => {
3768
+ process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
3769
+ `);
3770
+ process.exit(1);
3771
+ });
3772
+ }
3773
+ export {
3774
+ CodexBridgeDaemon,
3775
+ VirtualCodexAgent,
3776
+ getApprovedIntercomSend,
3777
+ getApprovedIntercomToolFromApproval,
3778
+ getCompletedIntercomSend,
3779
+ isIntercomToolApprovalRequest,
3780
+ threadSandboxMode
3781
+ };