agent-embassy 1.0.0

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 (145) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/CONTRIBUTING.md +122 -0
  3. package/LICENSE +21 -0
  4. package/README.md +185 -0
  5. package/README.zh-CN.md +185 -0
  6. package/SECURITY.md +224 -0
  7. package/assets/live-dashboard/app.css +1610 -0
  8. package/assets/mark-seal.svg +7 -0
  9. package/assets/mark.svg +7 -0
  10. package/assets/social-preview.png +0 -0
  11. package/assets/vendor/react/LICENSE +21 -0
  12. package/assets/vendor/react/react-dom.production.min.js +267 -0
  13. package/assets/vendor/react/react.production.min.js +31 -0
  14. package/dist/src/errors.d.ts +5 -0
  15. package/dist/src/errors.js +11 -0
  16. package/dist/src/errors.js.map +1 -0
  17. package/dist/src/gateway/claude-compatibility-scratch.d.ts +18 -0
  18. package/dist/src/gateway/claude-compatibility-scratch.js +158 -0
  19. package/dist/src/gateway/claude-compatibility-scratch.js.map +1 -0
  20. package/dist/src/gateway/claude-helper-client.d.ts +43 -0
  21. package/dist/src/gateway/claude-helper-client.js +212 -0
  22. package/dist/src/gateway/claude-helper-client.js.map +1 -0
  23. package/dist/src/gateway/claude-helper-protocol.d.ts +172 -0
  24. package/dist/src/gateway/claude-helper-protocol.js +373 -0
  25. package/dist/src/gateway/claude-helper-protocol.js.map +1 -0
  26. package/dist/src/gateway/claude-helper-supervisor.d.ts +74 -0
  27. package/dist/src/gateway/claude-helper-supervisor.js +497 -0
  28. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -0
  29. package/dist/src/gateway/claude-helper.d.ts +1 -0
  30. package/dist/src/gateway/claude-helper.js +269 -0
  31. package/dist/src/gateway/claude-helper.js.map +1 -0
  32. package/dist/src/gateway/claude-peer.d.ts +273 -0
  33. package/dist/src/gateway/claude-peer.js +2359 -0
  34. package/dist/src/gateway/claude-peer.js.map +1 -0
  35. package/dist/src/gateway/claude-runtime.d.ts +40 -0
  36. package/dist/src/gateway/claude-runtime.js +302 -0
  37. package/dist/src/gateway/claude-runtime.js.map +1 -0
  38. package/dist/src/gateway/cli-copy.d.ts +8 -0
  39. package/dist/src/gateway/cli-copy.en.d.ts +13 -0
  40. package/dist/src/gateway/cli-copy.en.js +43 -0
  41. package/dist/src/gateway/cli-copy.en.js.map +1 -0
  42. package/dist/src/gateway/cli-copy.js +19 -0
  43. package/dist/src/gateway/cli-copy.js.map +1 -0
  44. package/dist/src/gateway/cli-copy.zh-CN.d.ts +13 -0
  45. package/dist/src/gateway/cli-copy.zh-CN.js +43 -0
  46. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -0
  47. package/dist/src/gateway/cli.d.ts +44 -0
  48. package/dist/src/gateway/cli.js +969 -0
  49. package/dist/src/gateway/cli.js.map +1 -0
  50. package/dist/src/gateway/codex-app-server.d.ts +310 -0
  51. package/dist/src/gateway/codex-app-server.js +1746 -0
  52. package/dist/src/gateway/codex-app-server.js.map +1 -0
  53. package/dist/src/gateway/codex-local-transport.d.ts +81 -0
  54. package/dist/src/gateway/codex-local-transport.js +617 -0
  55. package/dist/src/gateway/codex-local-transport.js.map +1 -0
  56. package/dist/src/gateway/codex-registration-generation.d.ts +5 -0
  57. package/dist/src/gateway/codex-registration-generation.js +19 -0
  58. package/dist/src/gateway/codex-registration-generation.js.map +1 -0
  59. package/dist/src/gateway/codex-registration-succession.d.ts +209 -0
  60. package/dist/src/gateway/codex-registration-succession.js +588 -0
  61. package/dist/src/gateway/codex-registration-succession.js.map +1 -0
  62. package/dist/src/gateway/compatibility.d.ts +70 -0
  63. package/dist/src/gateway/compatibility.js +252 -0
  64. package/dist/src/gateway/compatibility.js.map +1 -0
  65. package/dist/src/gateway/config.d.ts +24 -0
  66. package/dist/src/gateway/config.js +130 -0
  67. package/dist/src/gateway/config.js.map +1 -0
  68. package/dist/src/gateway/control.d.ts +308 -0
  69. package/dist/src/gateway/control.js +1573 -0
  70. package/dist/src/gateway/control.js.map +1 -0
  71. package/dist/src/gateway/dashboard-copy.d.ts +7 -0
  72. package/dist/src/gateway/dashboard-copy.en.d.ts +506 -0
  73. package/dist/src/gateway/dashboard-copy.en.js +507 -0
  74. package/dist/src/gateway/dashboard-copy.en.js.map +1 -0
  75. package/dist/src/gateway/dashboard-copy.js +516 -0
  76. package/dist/src/gateway/dashboard-copy.js.map +1 -0
  77. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +506 -0
  78. package/dist/src/gateway/dashboard-copy.zh-CN.js +507 -0
  79. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -0
  80. package/dist/src/gateway/dashboard-model.d.ts +207 -0
  81. package/dist/src/gateway/dashboard-model.js +720 -0
  82. package/dist/src/gateway/dashboard-model.js.map +1 -0
  83. package/dist/src/gateway/dashboard.d.ts +26 -0
  84. package/dist/src/gateway/dashboard.js +941 -0
  85. package/dist/src/gateway/dashboard.js.map +1 -0
  86. package/dist/src/gateway/delivery-machine.d.ts +235 -0
  87. package/dist/src/gateway/delivery-machine.js +540 -0
  88. package/dist/src/gateway/delivery-machine.js.map +1 -0
  89. package/dist/src/gateway/instance-lease.d.ts +21 -0
  90. package/dist/src/gateway/instance-lease.js +549 -0
  91. package/dist/src/gateway/instance-lease.js.map +1 -0
  92. package/dist/src/gateway/live-dashboard-app/app.js +2914 -0
  93. package/dist/src/gateway/live-dashboard-assets.d.ts +10 -0
  94. package/dist/src/gateway/live-dashboard-assets.js +73 -0
  95. package/dist/src/gateway/live-dashboard-assets.js.map +1 -0
  96. package/dist/src/gateway/live-dashboard-bootstrap.d.ts +36 -0
  97. package/dist/src/gateway/live-dashboard-bootstrap.js +176 -0
  98. package/dist/src/gateway/live-dashboard-bootstrap.js.map +1 -0
  99. package/dist/src/gateway/live-dashboard-command.d.ts +54 -0
  100. package/dist/src/gateway/live-dashboard-command.js +324 -0
  101. package/dist/src/gateway/live-dashboard-command.js.map +1 -0
  102. package/dist/src/gateway/live-dashboard-http.d.ts +41 -0
  103. package/dist/src/gateway/live-dashboard-http.js +445 -0
  104. package/dist/src/gateway/live-dashboard-http.js.map +1 -0
  105. package/dist/src/gateway/live-dashboard-protocol.d.ts +38 -0
  106. package/dist/src/gateway/live-dashboard-protocol.js +156 -0
  107. package/dist/src/gateway/live-dashboard-protocol.js.map +1 -0
  108. package/dist/src/gateway/live-dashboard-server.d.ts +33 -0
  109. package/dist/src/gateway/live-dashboard-server.js +130 -0
  110. package/dist/src/gateway/live-dashboard-server.js.map +1 -0
  111. package/dist/src/gateway/live-dashboard-stream.d.ts +46 -0
  112. package/dist/src/gateway/live-dashboard-stream.js +233 -0
  113. package/dist/src/gateway/live-dashboard-stream.js.map +1 -0
  114. package/dist/src/gateway/live-dashboard.d.ts +31 -0
  115. package/dist/src/gateway/live-dashboard.js +190 -0
  116. package/dist/src/gateway/live-dashboard.js.map +1 -0
  117. package/dist/src/gateway/locale.d.ts +4 -0
  118. package/dist/src/gateway/locale.js +10 -0
  119. package/dist/src/gateway/locale.js.map +1 -0
  120. package/dist/src/gateway/progress-watch-machine.d.ts +97 -0
  121. package/dist/src/gateway/progress-watch-machine.js +172 -0
  122. package/dist/src/gateway/progress-watch-machine.js.map +1 -0
  123. package/dist/src/gateway/providers.d.ts +287 -0
  124. package/dist/src/gateway/providers.js +2451 -0
  125. package/dist/src/gateway/providers.js.map +1 -0
  126. package/dist/src/gateway/server.d.ts +57 -0
  127. package/dist/src/gateway/server.js +264 -0
  128. package/dist/src/gateway/server.js.map +1 -0
  129. package/dist/src/gateway/service.d.ts +449 -0
  130. package/dist/src/gateway/service.js +4831 -0
  131. package/dist/src/gateway/service.js.map +1 -0
  132. package/dist/src/gateway/store.d.ts +340 -0
  133. package/dist/src/gateway/store.js +4047 -0
  134. package/dist/src/gateway/store.js.map +1 -0
  135. package/dist/src/gateway/types.d.ts +514 -0
  136. package/dist/src/gateway/types.js +414 -0
  137. package/dist/src/gateway/types.js.map +1 -0
  138. package/dist/src/mutex.d.ts +4 -0
  139. package/dist/src/mutex.js +23 -0
  140. package/dist/src/mutex.js.map +1 -0
  141. package/docs/DESIGN.md +134 -0
  142. package/docs/GATEWAY-ARCHITECTURE.md +858 -0
  143. package/package.json +80 -0
  144. package/skills/embassy-peer/SKILL.md +198 -0
  145. package/skills/embassy-peer/agents/openai.yaml +4 -0
@@ -0,0 +1,2451 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import path from "node:path";
3
+ import { BridgeError } from "../errors.js";
4
+ import { CLAUDE_PEER_COMPATIBILITY, ClaudePeerAdapter, } from "./claude-peer.js";
5
+ import { startClaudeCompatibilityScratch, } from "./claude-compatibility-scratch.js";
6
+ import { compatibilityProbeNames, sharesCompatibilityMajor, } from "./compatibility.js";
7
+ import { CODEX_APP_SERVER_WRITABLE_VERSIONS, CodexAppServerConnector, CodexConnectorError, } from "./codex-app-server.js";
8
+ import { LocalCodexTransportError } from "./codex-local-transport.js";
9
+ import { ClaudeNativeHelperSupervisor, } from "./claude-helper-supervisor.js";
10
+ import { isDashboardLocale } from "./locale.js";
11
+ const LOCAL_HOST = "this-mac";
12
+ const NATIVE_CLAUDE_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
13
+ const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
14
+ const OPAQUE_ROUTE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
15
+ const MAX_CLAUDE_PENDING = 1_024;
16
+ const DEFAULT_CLAUDE_DISCOVERY_POLL_MS = 1_000;
17
+ const CLAUDE_REJECTION_RECEIPT_RETRY_DELAYS_MS = [25, 100, 250];
18
+ const MAX_CODEX_ROUTES = 128;
19
+ const MAX_CODEX_CALLBACKS = 512;
20
+ const MAX_TRANSIENT_REPLY_BYTES = 64 * 1024;
21
+ const DEFAULT_CODEX_CLEANUP_TIMEOUT_MS = 5_000;
22
+ const DEFAULT_CODEX_CLEANUP_POLL_MS = 25;
23
+ const DEFAULT_CODEX_RECOVERY_INITIAL_MS = 250;
24
+ const DEFAULT_CODEX_RECOVERY_MAX_MS = 5_000;
25
+ const COMPATIBILITY_PROBE_THREAD_ID = "00000000-0000-7000-8000-000000000000";
26
+ const COMPATIBILITY_TURN_TIMEOUT_MS = 60_000;
27
+ const CLAUDE_CERTIFICATION_TIMEOUT_MS = 10_000;
28
+ const CLAUDE_CERTIFICATION_DISCOVERY_POLL_MS = 100;
29
+ function passedProbe(name) {
30
+ return { name, outcome: "pass" };
31
+ }
32
+ function failedProbe(name, safeErrorCode) {
33
+ return { name, outcome: "fail", safeErrorCode };
34
+ }
35
+ function exactLocalHost(hostId) {
36
+ if ((hostId ?? LOCAL_HOST) !== LOCAL_HOST) {
37
+ throw new BridgeError("GATEWAY_REMOTE_PROVIDER_DISABLED", "This provider is restricted to the exact local host boundary.");
38
+ }
39
+ return LOCAL_HOST;
40
+ }
41
+ function positiveBounded(value, fallback, maximum) {
42
+ const resolved = value ?? fallback;
43
+ if (!Number.isSafeInteger(resolved) ||
44
+ resolved <= 0 ||
45
+ resolved > maximum) {
46
+ throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "A provider capacity is outside its reviewed bound.");
47
+ }
48
+ return resolved;
49
+ }
50
+ function strictDeadline(value, now) {
51
+ const parsed = Date.parse(value);
52
+ if (!Number.isFinite(parsed) ||
53
+ new Date(parsed).toISOString() !== value ||
54
+ parsed <= now) {
55
+ return undefined;
56
+ }
57
+ return parsed;
58
+ }
59
+ function sameEndpoint(binding, identity) {
60
+ return (binding.provider === identity.provider &&
61
+ binding.hostId === identity.hostId &&
62
+ binding.endpointGeneration === identity.endpointGeneration);
63
+ }
64
+ function callbackEndpoint(identity, routeHandle) {
65
+ return { ...identity, routeHandle };
66
+ }
67
+ function invokeCallback(operation) {
68
+ try {
69
+ operation();
70
+ }
71
+ catch {
72
+ // Provider observation consumers cannot change a transport outcome.
73
+ }
74
+ }
75
+ function validateAttestedClaudeRuntime(runtime) {
76
+ if (!sharesCompatibilityMajor(runtime.claudeCodeVersion, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion) ||
77
+ [
78
+ runtime.claudeExecutable,
79
+ runtime.sessionsDir,
80
+ runtime.socketDir,
81
+ ].some((value) => !path.isAbsolute(value) ||
82
+ path.normalize(value) !== value ||
83
+ value.includes("\0"))) {
84
+ throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "The local Claude provider requires exact pinned runtime evidence.");
85
+ }
86
+ }
87
+ function claudeEndpointGeneration(runtime) {
88
+ return `claude_${createHash("sha256")
89
+ .update(runtime.claudeCodeVersion)
90
+ .update("\0")
91
+ .update(runtime.claudeExecutable)
92
+ .update("\0")
93
+ .update(runtime.sessionsDir)
94
+ .update("\0")
95
+ .update(runtime.socketDir)
96
+ .digest("hex")
97
+ .slice(0, 32)}`;
98
+ }
99
+ function claudeRouteState(peer) {
100
+ return peer.status === "idle" ? "idle" : "busy";
101
+ }
102
+ export class LocalClaudeGatewayProvider {
103
+ identity;
104
+ protocol = "claude-peer";
105
+ protocolVersion = `${CLAUDE_PEER_COMPATIBILITY.peerProtocol}`;
106
+ peer;
107
+ runtimeVersion;
108
+ maxPending;
109
+ discoveryPollMs;
110
+ now;
111
+ selectedStateRoots = new Map();
112
+ nativeHelpers;
113
+ compatibilityScratchFactory;
114
+ discovered = new Map();
115
+ selected = new Map();
116
+ /** Exact live same-UID sessions observed through native inbound frames. */
117
+ nativeInbound = new Map();
118
+ /** Exact listener owner for every receipt capability handed to the service. */
119
+ nativeInboundReceiptOwners = new Map();
120
+ selectedObservations = new Map();
121
+ /** Invalidates any discovery that overlaps a dispatch on this route. */
122
+ selectedDispatchEpoch = new Map();
123
+ /**
124
+ * A dispatch invalidates the pre-dispatch registry observation. The first
125
+ * subsequent successful discovery must therefore be published even when
126
+ * the native peer completed between polls and is idle again.
127
+ */
128
+ selectedObservationDirty = new Set();
129
+ pendingByProviderId = new Map();
130
+ providerIdByGatewayId = new Map();
131
+ /** Includes the pre-write reservation through exact terminal settlement. */
132
+ pendingTargetByGatewayId = new Map();
133
+ /** Provider-owned terminal rejections that never enter the service queue. */
134
+ rejectedNativeInbound = new Map();
135
+ discoveryInFlight;
136
+ monitorTimer;
137
+ /** Invocation-ordered registry mutations; the tail itself never rejects. */
138
+ nativeCodexRegistrationTail = Promise.resolve();
139
+ callbacks;
140
+ activeNativeCodexListener;
141
+ preparedNativeCodexListener;
142
+ nativeCodexPreparationInFlight;
143
+ retiredNativeCodexListener;
144
+ /** Fences discovery callbacks and monitor restarts across listener rotation. */
145
+ nativeCodexSuccessionFreeze;
146
+ compatibilityCertificationInFlight = false;
147
+ initialized = false;
148
+ closed = false;
149
+ constructor(options) {
150
+ validateAttestedClaudeRuntime(options.runtime);
151
+ if (options.locale !== undefined && !isDashboardLocale(options.locale)) {
152
+ throw new BridgeError("DASHBOARD_LOCALE_UNSUPPORTED", "The Claude provider notice locale is unsupported.");
153
+ }
154
+ const hostId = exactLocalHost(options.hostId);
155
+ this.identity = {
156
+ provider: "claude",
157
+ hostId,
158
+ endpointGeneration: claudeEndpointGeneration(options.runtime),
159
+ };
160
+ this.runtimeVersion = options.runtime.claudeCodeVersion;
161
+ this.maxPending = positiveBounded(options.maxPendingMessages, MAX_CLAUDE_PENDING, 4_096);
162
+ this.discoveryPollMs = positiveBounded(options.discoveryPollMs, DEFAULT_CLAUDE_DISCOVERY_POLL_MS, 30_000);
163
+ this.now = options.now ?? Date.now;
164
+ this.peer = (options.peerFactory ??
165
+ ((runtime, locale, deliveryNotices) => new ClaudePeerAdapter({
166
+ sessionsDir: runtime.sessionsDir,
167
+ socketDir: runtime.socketDir,
168
+ attestedClaudeCodeVersion: runtime.claudeCodeVersion,
169
+ locale,
170
+ deliveryNotices,
171
+ })))(options.runtime, options.locale ?? "en", options.deliveryNotices ?? "merged");
172
+ this.compatibilityScratchFactory =
173
+ options.compatibilityScratchFactory ??
174
+ (() => startClaudeCompatibilityScratch(options.runtime));
175
+ this.nativeHelpers =
176
+ options.nativeHelpers === undefined
177
+ ? undefined
178
+ : new ClaudeNativeHelperSupervisor({
179
+ identity: this.identity,
180
+ runtime: options.runtime,
181
+ locale: options.locale ?? "en",
182
+ deliveryNotices: options.deliveryNotices ?? "merged",
183
+ maxPendingMessages: this.maxPending,
184
+ maxHelpers: positiveBounded(options.nativeHelpers.maxHelpers, 256, 128),
185
+ callbacks: () => this.callbacks,
186
+ ...(options.nativeHelpers.factory === undefined
187
+ ? {}
188
+ : { factory: options.nativeHelpers.factory }),
189
+ });
190
+ }
191
+ compatibilitySurface() {
192
+ return { surface: "claude", version: this.runtimeVersion };
193
+ }
194
+ async runCompatibilityProbes() {
195
+ const [launcher, version, registry, socket, protocol] = compatibilityProbeNames.claude;
196
+ try {
197
+ const discovery = await this.peer.discover();
198
+ // Runtime discovery is deliberately per-record fail-safe: malformed,
199
+ // dead, unsafe, or non-routable records are omitted rather than allowed
200
+ // to poison the whole registry. Startup compatibility must use the same
201
+ // boundary. Fail only when the bounded scan itself is incomplete, or a
202
+ // nonempty registry contains no record with a parseable closed schema.
203
+ const registryUnusable = discovery.truncated ||
204
+ (discovery.entriesScanned > 0 && discovery.parseableRecords === 0);
205
+ return [
206
+ passedProbe(launcher),
207
+ passedProbe(version),
208
+ registryUnusable
209
+ ? failedProbe(registry, "CLAUDE_REGISTRY_SCHEMA_REJECTED")
210
+ : passedProbe(registry),
211
+ passedProbe(socket),
212
+ passedProbe(protocol),
213
+ ];
214
+ }
215
+ catch {
216
+ return [
217
+ passedProbe(launcher),
218
+ passedProbe(version),
219
+ failedProbe(registry, "CLAUDE_REGISTRY_UNAVAILABLE"),
220
+ failedProbe(socket, "CLAUDE_SOCKET_VALIDATION_UNAVAILABLE"),
221
+ passedProbe(protocol),
222
+ ];
223
+ }
224
+ }
225
+ async runCompatibilityCertification(input) {
226
+ void input.routeHandle;
227
+ void input.withTurn;
228
+ const depth = "wire";
229
+ let scratch;
230
+ let listener;
231
+ if (this.compatibilityCertificationInFlight) {
232
+ return {
233
+ depth,
234
+ outcome: "fail",
235
+ certifiedAt: new Date(this.now()).toISOString(),
236
+ safeErrorCode: "CLAUDE_CERTIFICATION_BUSY",
237
+ };
238
+ }
239
+ this.compatibilityCertificationInFlight = true;
240
+ try {
241
+ this.assertReady();
242
+ scratch = await this.compatibilityScratchFactory();
243
+ const deadlineAt = this.now() + CLAUDE_CERTIFICATION_TIMEOUT_MS;
244
+ let targetId;
245
+ while (this.now() < deadlineAt) {
246
+ scratch.assertRunning();
247
+ const discovery = await this.peer.discover();
248
+ if (discovery.truncated) {
249
+ throw new BridgeError("CLAUDE_CERTIFICATION_DISCOVERY_INCOMPLETE", "The scratch session could not be proven from a complete registry scan.", true);
250
+ }
251
+ const matches = discovery.peers.filter((peer) => peer.alias === scratch?.name && peer.targetId === scratch?.sessionId);
252
+ if (matches.length > 1) {
253
+ throw new BridgeError("CLAUDE_CERTIFICATION_TARGET_COLLISION", "The scratch session identity was not unique.");
254
+ }
255
+ if (matches.length === 1) {
256
+ targetId = matches[0]?.targetId;
257
+ break;
258
+ }
259
+ await new Promise((resolve) => {
260
+ const timer = setTimeout(resolve, CLAUDE_CERTIFICATION_DISCOVERY_POLL_MS);
261
+ timer.unref();
262
+ });
263
+ }
264
+ if (targetId === undefined) {
265
+ throw new BridgeError("CLAUDE_CERTIFICATION_TARGET_UNAVAILABLE", "The bounded scratch session did not publish a discoverable peer record.", true);
266
+ }
267
+ await this.peer.assertTargetWorkspaceDisjoint(targetId, input.controllerStateRoot);
268
+ listener = await this.peer.listen({
269
+ onMessage: () => undefined,
270
+ onReceipt: () => undefined,
271
+ });
272
+ const sent = await this.peer.send(targetId, "[embassy compat-certify] ignore", {
273
+ listener,
274
+ receiptDeadlineAt: deadlineAt,
275
+ });
276
+ if (sent.transportStatus !== "transport_written" ||
277
+ sent.receiptStatus !== "pending") {
278
+ throw new BridgeError("CLAUDE_CERTIFICATION_WRITE_UNCONFIRMED", "The scratch diagnostic frame was not accepted for receipt tracking.", true);
279
+ }
280
+ // A direct `crossSessionInbound: accept` write emits no native
281
+ // peer_message_status frame. That status family describes the optional
282
+ // approval lifecycle, not universal delivery acknowledgement. For this
283
+ // wire-depth certification, the strongest portable evidence is the
284
+ // confirmed write to the exact, uniquely discovered scratch session.
285
+ return {
286
+ depth,
287
+ outcome: "pass",
288
+ certifiedAt: new Date(this.now()).toISOString(),
289
+ };
290
+ }
291
+ catch (error) {
292
+ const observedCode = error instanceof BridgeError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code)
293
+ ? error.code
294
+ : "CLAUDE_CERTIFICATION_FAILED";
295
+ return {
296
+ depth,
297
+ outcome: "fail",
298
+ certifiedAt: new Date(this.now()).toISOString(),
299
+ safeErrorCode: observedCode,
300
+ };
301
+ }
302
+ finally {
303
+ await listener?.close().catch(() => undefined);
304
+ await scratch?.close().catch(() => undefined);
305
+ await this.peer.discover().catch(() => undefined);
306
+ this.compatibilityCertificationInFlight = false;
307
+ }
308
+ }
309
+ async initialize(callbacks) {
310
+ if (this.closed) {
311
+ throw new BridgeError("CLAUDE_PROVIDER_CLOSED", "The local Claude provider is closed.");
312
+ }
313
+ if (this.initialized) {
314
+ throw new BridgeError("CLAUDE_PROVIDER_ALREADY_INITIALIZED", "The local Claude provider can be initialized only once.");
315
+ }
316
+ this.callbacks = callbacks;
317
+ if (this.nativeHelpers !== undefined) {
318
+ this.initialized = true;
319
+ return { health: "healthy", compatibility: "compatible" };
320
+ }
321
+ try {
322
+ let listenerGeneration;
323
+ const listener = await this.peer.listen({
324
+ onMessage: async (message) => {
325
+ if (listenerGeneration !== undefined) {
326
+ await this.onListenerMessage(listenerGeneration, message);
327
+ }
328
+ },
329
+ onReceipt: (event) => {
330
+ if (listenerGeneration !== undefined) {
331
+ this.onReceipt(listenerGeneration, event);
332
+ }
333
+ },
334
+ onProtocolNotice: (notice) => {
335
+ this.callbacks?.onProtocolNotice?.({ code: notice.code });
336
+ },
337
+ });
338
+ listenerGeneration = {
339
+ generation: listener.generation,
340
+ listener,
341
+ lifecycle: "active",
342
+ registrationProvisional: false,
343
+ provisionalIngressForwarded: false,
344
+ inboundQuiesced: false,
345
+ publicationAttempted: false,
346
+ publicationUncertain: false,
347
+ };
348
+ if (this.closed) {
349
+ await listener.close();
350
+ throw new BridgeError("CLAUDE_PROVIDER_CLOSED", "The local Claude provider closed during initialization.");
351
+ }
352
+ this.activeNativeCodexListener = listenerGeneration;
353
+ this.initialized = true;
354
+ return { health: "healthy", compatibility: "compatible" };
355
+ }
356
+ catch {
357
+ this.callbacks = undefined;
358
+ throw new BridgeError("CLAUDE_CALLBACK_UNAVAILABLE", "The private Claude callback listener could not be established.");
359
+ }
360
+ }
361
+ async discoverClaudePeers() {
362
+ this.assertReady();
363
+ return await this.refreshClaudeDiscovery();
364
+ }
365
+ async assertWorkspaceDisjoint(routeHandle, stateRoot) {
366
+ this.assertReady();
367
+ await this.peer.assertTargetWorkspaceDisjoint(routeHandle, stateRoot);
368
+ this.selectedStateRoots.set(routeHandle, stateRoot);
369
+ }
370
+ async selectRoute(input) {
371
+ this.assertReady();
372
+ const candidate = this.discovered.get(input.routeHandle);
373
+ if (candidate === undefined || candidate.alias !== input.alias) {
374
+ throw new BridgeError("CLAUDE_ROUTE_MISMATCH", "The selected Claude alias no longer matches that exact peer generation.");
375
+ }
376
+ this.selected.set(input.routeHandle, input.alias);
377
+ this.selectedDispatchEpoch.set(input.routeHandle, this.selectedDispatchEpoch.get(input.routeHandle) ?? 0);
378
+ this.selectedObservationDirty.delete(input.routeHandle);
379
+ this.selectedObservations.set(input.routeHandle, `${candidate.state}:`);
380
+ this.scheduleClaudeMonitor();
381
+ return { routeHandle: input.routeHandle, state: candidate.state };
382
+ }
383
+ async resolveReplyAddress(address) {
384
+ this.assertReady();
385
+ const resolved = await this.peer.resolveReplyAddress(address);
386
+ const selectedAlias = this.selected.get(resolved.targetId);
387
+ if (selectedAlias === undefined ||
388
+ resolved.kind !== "interactive" ||
389
+ selectedAlias !== `${resolved.alias}@${this.identity.hostId}`) {
390
+ throw new BridgeError("CLAUDE_REPLY_ROUTE_MISMATCH", "The reply capability is not the exact selected Claude generation.");
391
+ }
392
+ return { routeHandle: resolved.targetId };
393
+ }
394
+ async advertiseNativeCodexPeer(input) {
395
+ if (this.nativeHelpers !== undefined) {
396
+ this.assertReady();
397
+ await this.nativeHelpers.advertise(input);
398
+ return;
399
+ }
400
+ const registration = this.nativeCodexRegistration(input);
401
+ await this.serializeNativeCodexRegistration(async () => {
402
+ this.assertReady();
403
+ const active = this.requireActiveNativeCodexListener();
404
+ if (active.registration !== undefined &&
405
+ active.registration.alias !== registration.alias) {
406
+ throw new BridgeError("CODEX_PEER_ALREADY_ADVERTISED", "This listener generation already advertises another Codex peer.");
407
+ }
408
+ const installedProvisional = active.registration === undefined;
409
+ if (installedProvisional) {
410
+ active.registration = registration;
411
+ active.registrationProvisional = true;
412
+ active.provisionalIngressForwarded = false;
413
+ }
414
+ try {
415
+ await active.listener.advertise(registration.name, registration.cwd);
416
+ // Reassert exact ownership after publication. A preceding serialized
417
+ // attempt may have removed its own clean provisional registration.
418
+ active.registration = registration;
419
+ active.registrationProvisional = false;
420
+ active.provisionalIngressForwarded = false;
421
+ }
422
+ catch (error) {
423
+ if (error instanceof BridgeError &&
424
+ error.recoverable &&
425
+ installedProvisional &&
426
+ active.registration === registration &&
427
+ !active.provisionalIngressForwarded) {
428
+ delete active.registration;
429
+ active.registrationProvisional = false;
430
+ }
431
+ throw error;
432
+ }
433
+ });
434
+ }
435
+ currentNativeCodexPeerGeneration(alias) {
436
+ this.assertReady();
437
+ if (this.nativeHelpers !== undefined) {
438
+ return this.nativeHelpers.currentGeneration(alias);
439
+ }
440
+ const active = this.requireActiveNativeCodexListener();
441
+ if (active.registration?.alias !== alias) {
442
+ throw new BridgeError("CODEX_PEER_GENERATION_MISMATCH", "The requested Codex alias is not the active listener generation.");
443
+ }
444
+ return active.generation;
445
+ }
446
+ /** Child-host initialization seam before its immutable advertisement exists. */
447
+ currentUnadvertisedNativeCodexPeerGeneration() {
448
+ this.assertReady();
449
+ if (this.nativeHelpers !== undefined) {
450
+ throw new BridgeError("CLAUDE_NATIVE_HELPER_NESTING_FORBIDDEN", "A supervised provider cannot itself act as a native helper host.");
451
+ }
452
+ const active = this.requireActiveNativeCodexListener();
453
+ if (active.registration !== undefined) {
454
+ throw new BridgeError("CODEX_PEER_ALREADY_ADVERTISED", "The native listener already owns an advertised Codex identity.");
455
+ }
456
+ return active.generation;
457
+ }
458
+ async prepareNativeCodexPeerGeneration(input) {
459
+ this.assertReady();
460
+ if (this.nativeHelpers !== undefined) {
461
+ if (input.currentGeneration === undefined) {
462
+ throw new BridgeError("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH", "A supervised succession must name its exact current generation.");
463
+ }
464
+ await this.nativeHelpers.prepareGeneration({
465
+ alias: input.alias,
466
+ cwd: input.cwd,
467
+ generation: input.generation,
468
+ currentGeneration: input.currentGeneration,
469
+ });
470
+ return;
471
+ }
472
+ const active = this.requireActiveNativeCodexListener();
473
+ if (input.currentGeneration !== undefined &&
474
+ input.currentGeneration !== active.generation) {
475
+ throw new BridgeError("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH", "The prepared successor does not name the exact current generation.");
476
+ }
477
+ if (active.registration === undefined) {
478
+ throw new BridgeError("CODEX_PEER_NOT_ADVERTISED", "A successor requires one active advertised Codex generation.");
479
+ }
480
+ if (input.generation === active.generation ||
481
+ this.nativeCodexPreparationInFlight !== undefined ||
482
+ this.preparedNativeCodexListener !== undefined ||
483
+ this.retiredNativeCodexListener !== undefined) {
484
+ throw new BridgeError("CODEX_PEER_SUCCESSION_CAPACITY", "Only one distinct prepared or retired Codex generation is allowed.");
485
+ }
486
+ const registration = this.nativeCodexRegistration(input);
487
+ this.nativeCodexPreparationInFlight = input.generation;
488
+ let prepared;
489
+ try {
490
+ const listener = await this.peer.listenPrepared(input.generation, {
491
+ onMessage: async (message) => {
492
+ if (prepared !== undefined) {
493
+ await this.onListenerMessage(prepared, message);
494
+ }
495
+ },
496
+ onReceipt: (event) => {
497
+ if (prepared !== undefined)
498
+ this.onReceipt(prepared, event);
499
+ },
500
+ });
501
+ prepared = {
502
+ generation: listener.generation,
503
+ listener,
504
+ lifecycle: "prepared",
505
+ registration,
506
+ registrationProvisional: false,
507
+ provisionalIngressForwarded: false,
508
+ inboundQuiesced: true,
509
+ publicationAttempted: false,
510
+ publicationUncertain: false,
511
+ };
512
+ if (listener.generation !== input.generation || this.closed) {
513
+ await listener.close().catch(() => undefined);
514
+ throw new BridgeError("CLAUDE_CALLBACK_UNAVAILABLE", "The prepared Claude callback generation could not be established.");
515
+ }
516
+ this.preparedNativeCodexListener = prepared;
517
+ }
518
+ finally {
519
+ if (this.nativeCodexPreparationInFlight === input.generation) {
520
+ this.nativeCodexPreparationInFlight = undefined;
521
+ }
522
+ }
523
+ }
524
+ async quiesceNativeCodexPeerGeneration(generation) {
525
+ this.assertReady();
526
+ if (this.nativeHelpers !== undefined) {
527
+ await this.nativeHelpers.quiesceGeneration(generation);
528
+ return;
529
+ }
530
+ const active = this.requireNativeCodexListenerGeneration(this.activeNativeCodexListener, generation, "active");
531
+ if (this.nativeCodexSuccessionFreeze !== undefined &&
532
+ this.nativeCodexSuccessionFreeze !== generation) {
533
+ throw new BridgeError("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH", "Another native Codex generation already owns the succession freeze.");
534
+ }
535
+ this.nativeCodexSuccessionFreeze = generation;
536
+ if (this.monitorTimer !== undefined) {
537
+ clearTimeout(this.monitorTimer);
538
+ this.monitorTimer = undefined;
539
+ }
540
+ try {
541
+ await active.listener.quiesceInbound();
542
+ active.inboundQuiesced = true;
543
+ }
544
+ catch (error) {
545
+ if (this.nativeCodexSuccessionFreeze === generation) {
546
+ this.nativeCodexSuccessionFreeze = undefined;
547
+ }
548
+ this.scheduleClaudeMonitor();
549
+ throw error;
550
+ }
551
+ }
552
+ async observeNativeCodexSuccessionBarrier(generation) {
553
+ this.assertReady();
554
+ if (this.nativeHelpers !== undefined) {
555
+ return await this.nativeHelpers.observeBarrier(generation);
556
+ }
557
+ const active = this.activeNativeCodexListener;
558
+ const activeGenerationMatched = active?.generation === generation;
559
+ const pendingOutboundReceipts = this.providerIdByGatewayId.size;
560
+ let pendingInboundReceipts = 0;
561
+ for (const owner of this.nativeInboundReceiptOwners.values()) {
562
+ if (owner.generation === generation)
563
+ pendingInboundReceipts += 1;
564
+ }
565
+ let rejectedInboundSettlements = 0;
566
+ for (const rejection of this.rejectedNativeInbound.values()) {
567
+ if (rejection.listenerGeneration === generation) {
568
+ rejectedInboundSettlements += 1;
569
+ }
570
+ }
571
+ const ingressQuiesced = activeGenerationMatched && active.inboundQuiesced;
572
+ const monitorFrozen = activeGenerationMatched &&
573
+ this.nativeCodexSuccessionFreeze === generation;
574
+ const discoveryInFlight = this.discoveryInFlight !== undefined;
575
+ return {
576
+ generation,
577
+ activeGenerationMatched,
578
+ ingressQuiesced,
579
+ monitorFrozen,
580
+ discoveryInFlight,
581
+ pendingOutboundReceipts,
582
+ pendingInboundReceipts,
583
+ rejectedInboundSettlements,
584
+ clean: activeGenerationMatched &&
585
+ ingressQuiesced &&
586
+ monitorFrozen &&
587
+ !discoveryInFlight &&
588
+ pendingOutboundReceipts === 0 &&
589
+ pendingInboundReceipts === 0 &&
590
+ rejectedInboundSettlements === 0,
591
+ };
592
+ }
593
+ async publishPreparedNativeCodexPeer(input) {
594
+ this.assertReady();
595
+ if (this.nativeHelpers !== undefined) {
596
+ return await this.nativeHelpers.publishPrepared(input);
597
+ }
598
+ const current = this.requireNativeCodexListenerGeneration(this.activeNativeCodexListener, input.currentGeneration, "active");
599
+ const prepared = this.requireNativeCodexListenerGeneration(this.preparedNativeCodexListener, input.preparedGeneration, "prepared");
600
+ if (!current.inboundQuiesced || prepared.registration === undefined) {
601
+ throw new BridgeError("CODEX_PEER_SUCCESSION_NOT_QUIET", "The old listener must be quiesced before publication.");
602
+ }
603
+ if (!(await this.observeNativeCodexSuccessionBarrier(current.generation)).clean) {
604
+ throw new BridgeError("CODEX_PEER_SUCCESSION_NOT_QUIET", "Native Codex succession requires a clean provider-wide barrier.", true);
605
+ }
606
+ if (prepared.publicationOutcome === "published" ||
607
+ prepared.publicationOutcome === "not_published") {
608
+ return prepared.publicationOutcome;
609
+ }
610
+ if (prepared.publicationOperation !== undefined) {
611
+ return await prepared.publicationOperation;
612
+ }
613
+ prepared.publicationAttempted = true;
614
+ const operation = prepared.listener.publishReplacing(current.listener, prepared.registration.name, prepared.registration.cwd);
615
+ prepared.publicationOperation = operation;
616
+ try {
617
+ const outcome = await operation;
618
+ if (outcome === "published") {
619
+ prepared.publicationOutcome = "published";
620
+ return "published";
621
+ }
622
+ if (outcome === "unknown") {
623
+ prepared.publicationUncertain = true;
624
+ prepared.publicationOutcome = "unknown";
625
+ return "unknown";
626
+ }
627
+ if (prepared.publicationUncertain) {
628
+ prepared.publicationOutcome = "unknown";
629
+ return "unknown";
630
+ }
631
+ prepared.publicationOutcome = "not_published";
632
+ return "not_published";
633
+ }
634
+ catch (error) {
635
+ prepared.publicationUncertain = true;
636
+ prepared.publicationOutcome = "unknown";
637
+ throw error;
638
+ }
639
+ finally {
640
+ if (prepared.publicationOperation === operation) {
641
+ delete prepared.publicationOperation;
642
+ }
643
+ }
644
+ }
645
+ async activatePreparedNativeCodexPeerGeneration(generation) {
646
+ this.assertReady();
647
+ if (this.nativeHelpers !== undefined) {
648
+ await this.nativeHelpers.activatePrepared(generation);
649
+ return;
650
+ }
651
+ const prepared = this.requireNativeCodexListenerGeneration(this.preparedNativeCodexListener, generation, "prepared");
652
+ if (prepared.publicationOutcome !== "published" ||
653
+ this.retiredNativeCodexListener !== undefined) {
654
+ throw new BridgeError("CODEX_PEER_SUCCESSION_NOT_PUBLISHED", "Only a confirmed published generation can become active.");
655
+ }
656
+ const current = this.requireActiveNativeCodexListener();
657
+ prepared.listener.grantSuccessionActivation();
658
+ prepared.listener.resumeInbound();
659
+ prepared.inboundQuiesced = false;
660
+ current.lifecycle = "retired";
661
+ prepared.lifecycle = "active";
662
+ this.retiredNativeCodexListener = current;
663
+ this.activeNativeCodexListener = prepared;
664
+ this.preparedNativeCodexListener = undefined;
665
+ if (this.nativeCodexSuccessionFreeze === current.generation) {
666
+ this.nativeCodexSuccessionFreeze = undefined;
667
+ }
668
+ await this.purgeNativeCodexPeerGenerationReplyCapabilities(current.generation);
669
+ this.scheduleClaudeMonitor();
670
+ }
671
+ async cleanupPreparedNativeCodexPeerGeneration(generation) {
672
+ this.assertReady();
673
+ if (this.nativeHelpers !== undefined) {
674
+ await this.nativeHelpers.cleanupPrepared(generation);
675
+ return;
676
+ }
677
+ const prepared = this.requireNativeCodexListenerGeneration(this.preparedNativeCodexListener, generation, "prepared");
678
+ if (prepared.publicationAttempted &&
679
+ (prepared.publicationUncertain ||
680
+ prepared.publicationOutcome !== "not_published")) {
681
+ throw new BridgeError("CODEX_PEER_SUCCESSION_ROLLBACK_FORBIDDEN", "A possibly published Codex generation cannot be rolled back.");
682
+ }
683
+ await prepared.listener.close();
684
+ if (this.preparedNativeCodexListener === prepared) {
685
+ this.preparedNativeCodexListener = undefined;
686
+ }
687
+ }
688
+ async resumeNativeCodexPeerGeneration(generation) {
689
+ this.assertReady();
690
+ if (this.nativeHelpers !== undefined) {
691
+ await this.nativeHelpers.resumeGeneration(generation);
692
+ return;
693
+ }
694
+ const active = this.requireNativeCodexListenerGeneration(this.activeNativeCodexListener, generation, "active");
695
+ if (this.preparedNativeCodexListener !== undefined) {
696
+ throw new BridgeError("CODEX_PEER_SUCCESSION_CLEANUP_REQUIRED", "The prepared listener must be cleaned before resuming the old one.");
697
+ }
698
+ active.listener.resumeInbound();
699
+ active.inboundQuiesced = false;
700
+ if (this.nativeCodexSuccessionFreeze === generation) {
701
+ this.nativeCodexSuccessionFreeze = undefined;
702
+ }
703
+ this.scheduleClaudeMonitor();
704
+ }
705
+ async rollbackPreparedNativeCodexPeerGeneration(input) {
706
+ if (this.nativeHelpers !== undefined) {
707
+ await this.nativeHelpers.rollbackPrepared(input);
708
+ return;
709
+ }
710
+ await this.cleanupPreparedNativeCodexPeerGeneration(input.preparedGeneration);
711
+ await this.resumeNativeCodexPeerGeneration(input.resumeGeneration);
712
+ }
713
+ async retireNativeCodexPeerGeneration(input) {
714
+ this.assertReady();
715
+ if (this.nativeHelpers !== undefined) {
716
+ await this.nativeHelpers.retireGeneration(input);
717
+ return;
718
+ }
719
+ const active = this.requireNativeCodexListenerGeneration(this.activeNativeCodexListener, input.protectedActiveGeneration, "active");
720
+ const retired = this.requireNativeCodexListenerGeneration(this.retiredNativeCodexListener, input.retiredGeneration, "retired");
721
+ if (active === retired) {
722
+ throw new BridgeError("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH", "The protected and retired listener generations must be distinct.");
723
+ }
724
+ const ownsPendingReceipt = [...this.nativeInboundReceiptOwners.values()].some((owner) => owner === retired);
725
+ const ownsRejectedSettlement = [
726
+ ...this.rejectedNativeInbound.values(),
727
+ ].some((rejection) => rejection.listener === retired.listener);
728
+ const ownsOutboundReceipt = [...this.pendingByProviderId.values()].some((pending) => pending.listener === retired.listener);
729
+ if (ownsPendingReceipt || ownsRejectedSettlement || ownsOutboundReceipt) {
730
+ throw new BridgeError("CODEX_PEER_SUCCESSION_NOT_QUIET", "A retired listener cannot close while it owns receipt capabilities.", true);
731
+ }
732
+ await retired.listener.close();
733
+ if (this.retiredNativeCodexListener === retired) {
734
+ this.retiredNativeCodexListener = undefined;
735
+ }
736
+ }
737
+ async purgeNativeCodexPeerGenerationReplyCapabilities(generation) {
738
+ if (this.nativeHelpers !== undefined) {
739
+ return await this.nativeHelpers.purgeGenerationReplies(generation);
740
+ }
741
+ let purged = 0;
742
+ for (const [routeHandle, route] of this.nativeInbound) {
743
+ if (route.listenerGeneration !== generation)
744
+ continue;
745
+ this.nativeInbound.delete(routeHandle);
746
+ purged += 1;
747
+ }
748
+ return purged;
749
+ }
750
+ async unadvertiseNativeCodexPeer(alias) {
751
+ if (this.nativeHelpers !== undefined) {
752
+ await this.nativeHelpers.unadvertise(alias);
753
+ return;
754
+ }
755
+ await this.serializeNativeCodexRegistration(async () => {
756
+ const active = this.activeNativeCodexListener;
757
+ if (active?.registration?.alias !== alias)
758
+ return;
759
+ await active.listener.unadvertise(active.registration.name);
760
+ await this.purgeNativeCodexPeerGenerationReplyCapabilities(active.generation);
761
+ delete active.registration;
762
+ active.registrationProvisional = false;
763
+ active.provisionalIngressForwarded = false;
764
+ });
765
+ }
766
+ async updateNativeCodexPeerStatus(alias, status) {
767
+ if (this.nativeHelpers !== undefined) {
768
+ await this.nativeHelpers.updateStatus(alias, status);
769
+ return;
770
+ }
771
+ const active = this.activeNativeCodexListener;
772
+ if (active?.registration?.alias !== alias)
773
+ return;
774
+ await active.listener.updateAdvertisedStatus(status);
775
+ }
776
+ async updateNativeInboundStatus(receiptHandle, status, diagnosticCode) {
777
+ if (this.nativeHelpers !== undefined) {
778
+ await this.nativeHelpers.updateInboundStatus(receiptHandle, status, diagnosticCode);
779
+ return;
780
+ }
781
+ const owner = this.requireNativeInboundReceiptOwner(receiptHandle);
782
+ try {
783
+ await owner.listener.acknowledge(receiptHandle, status, diagnosticCode === undefined ? undefined : { code: diagnosticCode });
784
+ if (status !== "held") {
785
+ this.nativeInboundReceiptOwners.delete(receiptHandle);
786
+ }
787
+ }
788
+ catch (error) {
789
+ if (status !== "held" &&
790
+ (!(error instanceof BridgeError) || !error.recoverable)) {
791
+ this.nativeInboundReceiptOwners.delete(receiptHandle);
792
+ }
793
+ throw error;
794
+ }
795
+ }
796
+ async notifyNativeInboundProgress(receiptHandle, progress) {
797
+ if (this.nativeHelpers !== undefined) {
798
+ await this.nativeHelpers.notifyInboundProgress(receiptHandle, progress);
799
+ return;
800
+ }
801
+ const owner = this.requireNativeInboundReceiptOwner(receiptHandle);
802
+ await owner.listener.notifyInboundProgress(receiptHandle, progress);
803
+ }
804
+ async releaseNativeInboundReceipt(receiptHandle) {
805
+ if (this.nativeHelpers !== undefined) {
806
+ return await this.nativeHelpers.releaseInboundReceipt(receiptHandle);
807
+ }
808
+ const owner = this.nativeInboundReceiptOwners.get(receiptHandle);
809
+ if (owner === undefined)
810
+ return false;
811
+ this.nativeInboundReceiptOwners.delete(receiptHandle);
812
+ return owner.listener.releaseInboundReceipt(receiptHandle);
813
+ }
814
+ async quiesceNativeInbound() {
815
+ if (this.closed)
816
+ return;
817
+ if (this.nativeHelpers !== undefined) {
818
+ await this.nativeHelpers.quiesceAll();
819
+ return;
820
+ }
821
+ const active = this.activeNativeCodexListener;
822
+ if (active === undefined)
823
+ return;
824
+ await active.listener.quiesceInbound();
825
+ active.inboundQuiesced = true;
826
+ }
827
+ async dispatch(input) {
828
+ if (this.nativeHelpers !== undefined) {
829
+ if (input.sourceAlias === undefined ||
830
+ !this.initialized ||
831
+ this.closed ||
832
+ !sameEndpoint(input.binding, this.identity)) {
833
+ return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
834
+ }
835
+ const selectedAlias = this.selected.get(input.binding.routeHandle);
836
+ const stateRoot = this.selectedStateRoots.get(input.binding.routeHandle);
837
+ if (input.authorization === "selected_route" &&
838
+ (selectedAlias === undefined || stateRoot === undefined)) {
839
+ return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
840
+ }
841
+ if (input.authorization === "selected_route") {
842
+ this.selectedDispatchEpoch.set(input.binding.routeHandle, (this.selectedDispatchEpoch.get(input.binding.routeHandle) ?? 0) + 1);
843
+ this.selectedObservationDirty.add(input.binding.routeHandle);
844
+ this.emitClaudeRouteObservation(input.binding.routeHandle, "busy");
845
+ }
846
+ try {
847
+ const sourceAlias = input.sourceAlias;
848
+ const result = await this.nativeHelpers.dispatch({
849
+ ...input,
850
+ sourceAlias,
851
+ ...(selectedAlias === undefined ? {} : { selectedAlias }),
852
+ ...(stateRoot === undefined ? {} : { stateRoot }),
853
+ });
854
+ if (input.authorization === "selected_route" &&
855
+ result.state === "pending") {
856
+ this.selectedObservationDirty.delete(input.binding.routeHandle);
857
+ this.emitClaudeRouteObservation(input.binding.routeHandle, "idle", undefined, true);
858
+ }
859
+ return result;
860
+ }
861
+ catch (error) {
862
+ if (error instanceof BridgeError && error.recoverable) {
863
+ return {
864
+ state: "failed",
865
+ safeErrorCode: "CLAUDE_NATIVE_HELPER_UNAVAILABLE",
866
+ };
867
+ }
868
+ return {
869
+ state: "ambiguous",
870
+ safeErrorCode: "CLAUDE_NATIVE_HELPER_OUTCOME_UNKNOWN",
871
+ };
872
+ }
873
+ }
874
+ const activeListener = this.activeNativeCodexListener;
875
+ if (!this.initialized ||
876
+ this.closed ||
877
+ activeListener === undefined ||
878
+ activeListener.inboundQuiesced ||
879
+ this.nativeCodexSuccessionFreeze !== undefined ||
880
+ !sameEndpoint(input.binding, this.identity)) {
881
+ return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
882
+ }
883
+ if (input.authorization === "native_reply") {
884
+ try {
885
+ await this.refreshClaudeDiscovery();
886
+ }
887
+ catch {
888
+ return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
889
+ }
890
+ const observedRoute = this.nativeInbound.get(input.binding.routeHandle);
891
+ const current = this.discovered.get(input.binding.routeHandle);
892
+ if (observedRoute === undefined ||
893
+ observedRoute.listenerGeneration !== activeListener.generation ||
894
+ current?.alias !== observedRoute.alias) {
895
+ this.nativeInbound.delete(input.binding.routeHandle);
896
+ return { state: "failed", safeErrorCode: "CLAUDE_NATIVE_REPLY_STALE" };
897
+ }
898
+ }
899
+ else if (this.selected.get(input.binding.routeHandle) === undefined) {
900
+ return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
901
+ }
902
+ if (this.activeNativeCodexListener !== activeListener ||
903
+ activeListener.inboundQuiesced ||
904
+ this.nativeCodexSuccessionFreeze !== undefined) {
905
+ return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
906
+ }
907
+ const deadline = strictDeadline(input.deadlineAt, this.now());
908
+ if (deadline === undefined) {
909
+ return { state: "failed", safeErrorCode: "MESSAGE_EXPIRED" };
910
+ }
911
+ if (this.providerIdByGatewayId.size >= this.maxPending ||
912
+ this.providerIdByGatewayId.has(input.messageId)) {
913
+ return { state: "failed", safeErrorCode: "CLAUDE_RECEIPT_CAPACITY" };
914
+ }
915
+ this.providerIdByGatewayId.set(input.messageId, "");
916
+ this.pendingTargetByGatewayId.set(input.messageId, input.binding.routeHandle);
917
+ if (input.authorization === "selected_route") {
918
+ this.selectedDispatchEpoch.set(input.binding.routeHandle, (this.selectedDispatchEpoch.get(input.binding.routeHandle) ?? 0) + 1);
919
+ // Once a selected-route dispatch begins, keep the route conservatively
920
+ // busy until a later registry refresh observes authoritative state.
921
+ this.selectedObservationDirty.add(input.binding.routeHandle);
922
+ this.emitClaudeRouteObservation(input.binding.routeHandle, "busy");
923
+ }
924
+ let providerId;
925
+ let trackingFailed = false;
926
+ let terminalDuringSend = false;
927
+ const onTransportStatus = (event) => {
928
+ if (providerId === undefined) {
929
+ providerId = event.messageId;
930
+ trackingFailed = !this.trackClaudeMessage(event.messageId, input.messageId, input.binding.routeHandle, deadline, activeListener);
931
+ }
932
+ if (trackingFailed)
933
+ return;
934
+ if (providerId !== event.messageId) {
935
+ terminalDuringSend = true;
936
+ this.finishClaudeMessage(providerId, "ambiguous", "CLAUDE_RECEIPT_ID_MISMATCH");
937
+ return;
938
+ }
939
+ if (event.status === "transport_written") {
940
+ this.recordClaudeWriteEvidence(event.messageId, "transport_written");
941
+ this.emitDelivery({
942
+ messageId: input.messageId,
943
+ state: "transport_written",
944
+ });
945
+ }
946
+ else if (event.status === "write_started" ||
947
+ event.status === "ambiguous") {
948
+ const newlyUncertain = this.recordClaudeWriteEvidence(event.messageId, "transport_uncertain");
949
+ if (newlyUncertain) {
950
+ this.emitDelivery({
951
+ messageId: input.messageId,
952
+ state: "transport_uncertain",
953
+ safeErrorCode: "CLAUDE_TRANSPORT_OUTCOME_UNCERTAIN",
954
+ });
955
+ }
956
+ }
957
+ else if (event.status === "not_written") {
958
+ terminalDuringSend = true;
959
+ this.finishClaudeMessage(event.messageId, "failed", "CLAUDE_TRANSPORT_NOT_WRITTEN");
960
+ }
961
+ // A post-write ambiguity remains tracked. The exact receipt or the
962
+ // listener's bounded timeout is authoritative and may settle it later.
963
+ };
964
+ try {
965
+ const result = await this.peer.send(input.binding.routeHandle, input.text, {
966
+ listener: activeListener.listener,
967
+ receiptDeadlineAt: deadline,
968
+ onTransportStatus,
969
+ });
970
+ if (providerId === undefined ||
971
+ providerId !== result.messageId ||
972
+ result.receiptStatus !== "pending" ||
973
+ trackingFailed) {
974
+ if (trackingFailed) {
975
+ this.abandonClaudeTracking(input.messageId, providerId);
976
+ this.emitDelivery({
977
+ messageId: input.messageId,
978
+ state: "ambiguous",
979
+ safeErrorCode: "CLAUDE_RECEIPT_TRACKING_FAILED",
980
+ });
981
+ return { state: "pending" };
982
+ }
983
+ if (providerId !== undefined) {
984
+ this.finishClaudeMessage(providerId, "ambiguous", "CLAUDE_RECEIPT_TRACKING_FAILED");
985
+ }
986
+ else {
987
+ this.providerIdByGatewayId.delete(input.messageId);
988
+ this.pendingTargetByGatewayId.delete(input.messageId);
989
+ }
990
+ return { state: "ambiguous", safeErrorCode: "CLAUDE_RECEIPT_TRACKING_FAILED" };
991
+ }
992
+ if (input.authorization === "selected_route") {
993
+ this.selectedObservationDirty.delete(input.binding.routeHandle);
994
+ this.emitClaudeRouteObservation(input.binding.routeHandle, "idle", undefined, true);
995
+ }
996
+ return { state: "pending" };
997
+ }
998
+ catch (error) {
999
+ if (trackingFailed) {
1000
+ this.abandonClaudeTracking(input.messageId, providerId);
1001
+ this.emitDelivery({
1002
+ messageId: input.messageId,
1003
+ state: "ambiguous",
1004
+ safeErrorCode: "CLAUDE_RECEIPT_TRACKING_FAILED",
1005
+ });
1006
+ return { state: "pending" };
1007
+ }
1008
+ if (terminalDuringSend)
1009
+ return { state: "pending" };
1010
+ if (providerId !== undefined) {
1011
+ if (error instanceof BridgeError &&
1012
+ error.code === "CLAUDE_PEER_WRITE_AMBIGUOUS") {
1013
+ return { state: "pending" };
1014
+ }
1015
+ this.finishClaudeMessage(providerId, "ambiguous", "CLAUDE_DISPATCH_OUTCOME_AMBIGUOUS");
1016
+ return { state: "pending" };
1017
+ }
1018
+ this.providerIdByGatewayId.delete(input.messageId);
1019
+ this.pendingTargetByGatewayId.delete(input.messageId);
1020
+ if (error instanceof BridgeError &&
1021
+ error.code === "CLAUDE_PEER_MESSAGE_EXPIRED") {
1022
+ return { state: "failed", safeErrorCode: "MESSAGE_EXPIRED" };
1023
+ }
1024
+ return { state: "failed", safeErrorCode: "CLAUDE_DISPATCH_REJECTED" };
1025
+ }
1026
+ }
1027
+ async releaseRoute(routeHandle) {
1028
+ this.selected.delete(routeHandle);
1029
+ this.selectedDispatchEpoch.delete(routeHandle);
1030
+ this.selectedObservationDirty.delete(routeHandle);
1031
+ this.selectedObservations.delete(routeHandle);
1032
+ this.discovered.delete(routeHandle);
1033
+ this.selectedStateRoots.delete(routeHandle);
1034
+ if (this.nativeHelpers !== undefined) {
1035
+ await this.nativeHelpers.releaseRoute(routeHandle);
1036
+ }
1037
+ if (this.selected.size === 0 && this.monitorTimer !== undefined) {
1038
+ clearTimeout(this.monitorTimer);
1039
+ this.monitorTimer = undefined;
1040
+ }
1041
+ }
1042
+ async close() {
1043
+ if (this.closed)
1044
+ return;
1045
+ this.closed = true;
1046
+ for (const rejection of this.rejectedNativeInbound.values()) {
1047
+ this.releaseRejectedNativeInbound(rejection);
1048
+ }
1049
+ this.rejectedNativeInbound.clear();
1050
+ try {
1051
+ await this.nativeHelpers?.close();
1052
+ await this.peer.close();
1053
+ }
1054
+ finally {
1055
+ for (const pending of this.pendingByProviderId.values()) {
1056
+ clearTimeout(pending.timer);
1057
+ }
1058
+ this.pendingByProviderId.clear();
1059
+ this.providerIdByGatewayId.clear();
1060
+ this.pendingTargetByGatewayId.clear();
1061
+ this.selected.clear();
1062
+ this.selectedStateRoots.clear();
1063
+ this.nativeInbound.clear();
1064
+ this.nativeInboundReceiptOwners.clear();
1065
+ this.selectedDispatchEpoch.clear();
1066
+ this.selectedObservationDirty.clear();
1067
+ this.selectedObservations.clear();
1068
+ this.discovered.clear();
1069
+ if (this.monitorTimer !== undefined)
1070
+ clearTimeout(this.monitorTimer);
1071
+ this.monitorTimer = undefined;
1072
+ this.activeNativeCodexListener = undefined;
1073
+ this.preparedNativeCodexListener = undefined;
1074
+ this.retiredNativeCodexListener = undefined;
1075
+ this.nativeCodexPreparationInFlight = undefined;
1076
+ this.nativeCodexSuccessionFreeze = undefined;
1077
+ this.callbacks = undefined;
1078
+ }
1079
+ }
1080
+ assertReady() {
1081
+ if (!this.initialized || this.closed) {
1082
+ throw new BridgeError("CLAUDE_PROVIDER_UNAVAILABLE", "The local Claude provider is not available.", true);
1083
+ }
1084
+ }
1085
+ serializeNativeCodexRegistration(operation) {
1086
+ const queued = this.nativeCodexRegistrationTail.then(operation);
1087
+ this.nativeCodexRegistrationTail = queued.then(() => undefined, () => undefined);
1088
+ return queued;
1089
+ }
1090
+ nativeCodexRegistration(input) {
1091
+ const suffix = `@${this.identity.hostId}`;
1092
+ if (!input.alias.endsWith(suffix)) {
1093
+ throw new BridgeError("INVALID_CODEX_PEER_ALIAS", "The native Codex peer alias must target this host.");
1094
+ }
1095
+ const name = input.alias.slice(0, -suffix.length);
1096
+ if (!name.startsWith("codex-") || !NATIVE_CLAUDE_NAME.test(name)) {
1097
+ throw new BridgeError("INVALID_CODEX_PEER_ALIAS", "The native Codex peer alias must start with codex-.");
1098
+ }
1099
+ if (!path.isAbsolute(input.cwd) || input.cwd.includes("\0")) {
1100
+ throw new BridgeError("INVALID_CODEX_PEER_CWD", "A native Codex peer requires an absolute working directory.");
1101
+ }
1102
+ return { alias: input.alias, cwd: input.cwd, name };
1103
+ }
1104
+ requireActiveNativeCodexListener() {
1105
+ this.assertReady();
1106
+ const active = this.activeNativeCodexListener;
1107
+ if (active === undefined || active.lifecycle !== "active") {
1108
+ throw new BridgeError("CLAUDE_CALLBACK_UNAVAILABLE", "The private Claude callback listener is unavailable.", true);
1109
+ }
1110
+ return active;
1111
+ }
1112
+ requireNativeCodexListenerGeneration(listenerGeneration, generation, lifecycle) {
1113
+ if (listenerGeneration === undefined ||
1114
+ listenerGeneration.generation !== generation ||
1115
+ listenerGeneration.listener.generation !== generation ||
1116
+ listenerGeneration.lifecycle !== lifecycle) {
1117
+ throw new BridgeError("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH", "The requested native Codex listener generation is not the exact lifecycle owner.");
1118
+ }
1119
+ return listenerGeneration;
1120
+ }
1121
+ requireNativeInboundReceiptOwner(receiptHandle) {
1122
+ this.assertReady();
1123
+ const owner = this.nativeInboundReceiptOwners.get(receiptHandle);
1124
+ if (owner === undefined || !this.ownsNativeCodexListener(owner)) {
1125
+ throw new BridgeError("CLAUDE_PEER_RECEIPT_UNKNOWN", "The native Claude peer receipt is unknown or already settled.");
1126
+ }
1127
+ return owner;
1128
+ }
1129
+ ownsNativeCodexListener(listenerGeneration) {
1130
+ return (this.activeNativeCodexListener === listenerGeneration ||
1131
+ this.preparedNativeCodexListener === listenerGeneration ||
1132
+ this.retiredNativeCodexListener === listenerGeneration);
1133
+ }
1134
+ async onListenerMessage(listenerGeneration, message) {
1135
+ const reject = async (diagnosticCode) => {
1136
+ if (message.receiptHandle !== undefined) {
1137
+ await this.rejectNativeInboundReceipt(listenerGeneration, message.receiptHandle, diagnosticCode);
1138
+ }
1139
+ };
1140
+ if (this.closed ||
1141
+ listenerGeneration.listener.closed ||
1142
+ !this.ownsNativeCodexListener(listenerGeneration)) {
1143
+ try {
1144
+ if (message.receiptHandle !== undefined) {
1145
+ listenerGeneration.listener.releaseInboundReceipt(message.receiptHandle);
1146
+ }
1147
+ }
1148
+ catch {
1149
+ // A detached generation has no safe callback or settlement authority.
1150
+ }
1151
+ return;
1152
+ }
1153
+ if (this.activeNativeCodexListener !== listenerGeneration ||
1154
+ listenerGeneration.lifecycle !== "active" ||
1155
+ listenerGeneration.inboundQuiesced ||
1156
+ this.nativeCodexSuccessionFreeze !== undefined) {
1157
+ await reject("CLAUDE_NATIVE_GENERATION_STALE");
1158
+ return;
1159
+ }
1160
+ const registrationAtIngress = listenerGeneration.registration;
1161
+ if (message.sourceTargetId === undefined ||
1162
+ message.sourceAlias === undefined ||
1163
+ !message.replySupported) {
1164
+ await reject("CLAUDE_SOURCE_ROUTE_INVALID");
1165
+ return;
1166
+ }
1167
+ const sourceTargetId = message.sourceTargetId;
1168
+ let sourceAlias;
1169
+ try {
1170
+ await this.refreshClaudeDiscovery();
1171
+ const current = this.discovered.get(sourceTargetId);
1172
+ const expectedAlias = `${message.sourceAlias}@${this.identity.hostId}`;
1173
+ if (current?.alias === expectedAlias)
1174
+ sourceAlias = expectedAlias;
1175
+ }
1176
+ catch {
1177
+ sourceAlias = undefined;
1178
+ }
1179
+ // Discovery is asynchronous. Recheck exact generation ownership before
1180
+ // giving the body or its receipt capability to a service callback.
1181
+ if (this.activeNativeCodexListener !== listenerGeneration ||
1182
+ listenerGeneration.lifecycle !== "active" ||
1183
+ listenerGeneration.inboundQuiesced ||
1184
+ this.nativeCodexSuccessionFreeze !== undefined ||
1185
+ listenerGeneration.registration !== registrationAtIngress) {
1186
+ await reject("CLAUDE_NATIVE_GENERATION_STALE");
1187
+ return;
1188
+ }
1189
+ if (sourceAlias === undefined) {
1190
+ await reject("CLAUDE_SOURCE_ROUTE_STALE");
1191
+ return;
1192
+ }
1193
+ const callbacks = this.callbacks;
1194
+ const registration = registrationAtIngress;
1195
+ if (registration !== undefined) {
1196
+ if (callbacks?.onClaudeMessage === undefined ||
1197
+ (!this.nativeInbound.has(sourceTargetId) &&
1198
+ this.nativeInbound.size >= this.maxPending) ||
1199
+ (message.receiptHandle !== undefined &&
1200
+ !this.nativeInboundReceiptOwners.has(message.receiptHandle) &&
1201
+ this.nativeInboundReceiptOwners.size >= this.maxPending)) {
1202
+ await reject("CLAUDE_NATIVE_INGRESS_CAPACITY");
1203
+ return;
1204
+ }
1205
+ if (message.receiptHandle !== undefined &&
1206
+ this.nativeInboundReceiptOwners.has(message.receiptHandle)) {
1207
+ await reject("CLAUDE_NATIVE_RECEIPT_COLLISION");
1208
+ return;
1209
+ }
1210
+ this.nativeInbound.set(sourceTargetId, {
1211
+ alias: sourceAlias,
1212
+ listenerGeneration: listenerGeneration.generation,
1213
+ });
1214
+ if (message.receiptHandle !== undefined) {
1215
+ this.nativeInboundReceiptOwners.set(message.receiptHandle, listenerGeneration);
1216
+ }
1217
+ if (listenerGeneration.registrationProvisional) {
1218
+ listenerGeneration.provisionalIngressForwarded = true;
1219
+ }
1220
+ invokeCallback(() => callbacks.onClaudeMessage?.({
1221
+ endpoint: callbackEndpoint(this.identity, sourceTargetId),
1222
+ sourceAlias,
1223
+ targetAlias: registration.alias,
1224
+ text: message.content,
1225
+ ...(message.receiptHandle === undefined
1226
+ ? {}
1227
+ : { receiptHandle: message.receiptHandle }),
1228
+ }));
1229
+ return;
1230
+ }
1231
+ if (callbacks !== undefined && this.selected.get(sourceTargetId) === sourceAlias) {
1232
+ invokeCallback(() => callbacks.onClaudeReply({
1233
+ endpoint: callbackEndpoint(this.identity, sourceTargetId),
1234
+ text: message.content,
1235
+ }));
1236
+ return;
1237
+ }
1238
+ await reject("CLAUDE_ROUTE_UNAVAILABLE");
1239
+ }
1240
+ rejectNativeInboundReceipt(listenerGeneration, receiptHandle, diagnosticCode) {
1241
+ const rejectionKey = `${listenerGeneration.generation}\0${receiptHandle}`;
1242
+ const existing = this.rejectedNativeInbound.get(rejectionKey);
1243
+ if (existing !== undefined)
1244
+ return existing.operation;
1245
+ const listener = listenerGeneration.listener;
1246
+ if (this.closed ||
1247
+ listener.closed ||
1248
+ !this.ownsNativeCodexListener(listenerGeneration)) {
1249
+ try {
1250
+ listener.releaseInboundReceipt(receiptHandle);
1251
+ }
1252
+ catch {
1253
+ // A missing/closing listener leaves no safe terminal write path.
1254
+ }
1255
+ return Promise.resolve();
1256
+ }
1257
+ const rejection = {
1258
+ receiptHandle,
1259
+ listener,
1260
+ listenerGeneration: listenerGeneration.generation,
1261
+ released: false,
1262
+ operation: Promise.resolve(),
1263
+ };
1264
+ this.rejectedNativeInbound.set(rejectionKey, rejection);
1265
+ rejection.operation = this.settleRejectedNativeInbound(rejection, diagnosticCode).finally(() => {
1266
+ if (this.rejectedNativeInbound.get(rejectionKey) === rejection) {
1267
+ this.rejectedNativeInbound.delete(rejectionKey);
1268
+ }
1269
+ });
1270
+ return rejection.operation;
1271
+ }
1272
+ async settleRejectedNativeInbound(rejection, diagnosticCode) {
1273
+ for (let attempt = 0; attempt <= CLAUDE_REJECTION_RECEIPT_RETRY_DELAYS_MS.length; attempt += 1) {
1274
+ if (this.closed ||
1275
+ rejection.released ||
1276
+ rejection.listener.closed ||
1277
+ !this.ownsNativeCodexListenerGeneration(rejection.listenerGeneration, rejection.listener)) {
1278
+ this.releaseRejectedNativeInbound(rejection);
1279
+ return;
1280
+ }
1281
+ try {
1282
+ await rejection.listener.acknowledge(rejection.receiptHandle, "expired", { code: diagnosticCode });
1283
+ return;
1284
+ }
1285
+ catch (error) {
1286
+ if (rejection.released)
1287
+ return;
1288
+ const cleanPrewrite = error instanceof BridgeError && error.recoverable;
1289
+ if (!cleanPrewrite ||
1290
+ attempt === CLAUDE_REJECTION_RECEIPT_RETRY_DELAYS_MS.length) {
1291
+ // Ambiguous/nonrecoverable writes are never replayed. Explicit
1292
+ // release is idempotent even if the listener already consumed the
1293
+ // terminal capability after a possibly-written attempt.
1294
+ this.releaseRejectedNativeInbound(rejection);
1295
+ return;
1296
+ }
1297
+ }
1298
+ await this.waitForRejectedNativeInboundRetry(rejection, CLAUDE_REJECTION_RECEIPT_RETRY_DELAYS_MS[attempt]);
1299
+ }
1300
+ }
1301
+ ownsNativeCodexListenerGeneration(generation, listener) {
1302
+ return [
1303
+ this.activeNativeCodexListener,
1304
+ this.preparedNativeCodexListener,
1305
+ this.retiredNativeCodexListener,
1306
+ ].some((candidate) => candidate?.generation === generation && candidate.listener === listener);
1307
+ }
1308
+ async waitForRejectedNativeInboundRetry(rejection, delayMs) {
1309
+ await new Promise((resolve) => {
1310
+ let settled = false;
1311
+ const finish = () => {
1312
+ if (settled)
1313
+ return;
1314
+ settled = true;
1315
+ clearTimeout(timer);
1316
+ if (rejection.cancelRetry === finish) {
1317
+ delete rejection.cancelRetry;
1318
+ }
1319
+ resolve();
1320
+ };
1321
+ const timer = setTimeout(finish, delayMs);
1322
+ rejection.cancelRetry = finish;
1323
+ if (this.closed || rejection.released)
1324
+ finish();
1325
+ });
1326
+ }
1327
+ releaseRejectedNativeInbound(rejection) {
1328
+ if (rejection.released)
1329
+ return;
1330
+ rejection.released = true;
1331
+ rejection.cancelRetry?.();
1332
+ try {
1333
+ rejection.listener.releaseInboundReceipt(rejection.receiptHandle);
1334
+ }
1335
+ catch {
1336
+ // The listener owns a bounded process-lifetime capability. Closing must
1337
+ // remain best-effort even if its explicit release path is unavailable.
1338
+ }
1339
+ }
1340
+ trackClaudeMessage(providerId, gatewayMessageId, targetId, deadline, listenerGeneration) {
1341
+ if (this.pendingByProviderId.has(providerId) ||
1342
+ this.providerIdByGatewayId.get(gatewayMessageId) !== "" ||
1343
+ this.pendingByProviderId.size >= this.maxPending) {
1344
+ return false;
1345
+ }
1346
+ const timer = setTimeout(() => {
1347
+ const pending = this.pendingByProviderId.get(providerId);
1348
+ if (pending === undefined)
1349
+ return;
1350
+ pending.listener.untrack(providerId);
1351
+ if (pending.writeEvidence === "transport_written") {
1352
+ this.finishClaudeMessage(providerId, "unconfirmed", "CLAUDE_RECEIPT_UNCONFIRMED");
1353
+ }
1354
+ else if (pending.writeEvidence === "transport_uncertain") {
1355
+ this.finishClaudeMessage(providerId, "ambiguous", "CLAUDE_DISPATCH_OUTCOME_AMBIGUOUS");
1356
+ }
1357
+ else {
1358
+ this.finishClaudeMessage(providerId, "expired", "MESSAGE_EXPIRED");
1359
+ }
1360
+ }, Math.max(1, deadline - this.now()));
1361
+ timer.unref();
1362
+ this.pendingByProviderId.set(providerId, {
1363
+ gatewayMessageId,
1364
+ listener: listenerGeneration.listener,
1365
+ listenerGeneration: listenerGeneration.generation,
1366
+ targetId,
1367
+ writeEvidence: "none",
1368
+ timer,
1369
+ });
1370
+ this.providerIdByGatewayId.set(gatewayMessageId, providerId);
1371
+ return true;
1372
+ }
1373
+ abandonClaudeTracking(gatewayMessageId, providerId) {
1374
+ if (providerId !== undefined) {
1375
+ const pending = this.pendingByProviderId.get(providerId);
1376
+ if (pending?.gatewayMessageId === gatewayMessageId) {
1377
+ clearTimeout(pending.timer);
1378
+ this.pendingByProviderId.delete(providerId);
1379
+ pending.listener.untrack(providerId);
1380
+ }
1381
+ }
1382
+ this.providerIdByGatewayId.delete(gatewayMessageId);
1383
+ this.pendingTargetByGatewayId.delete(gatewayMessageId);
1384
+ }
1385
+ onReceipt(listenerGeneration, event) {
1386
+ const pending = this.pendingByProviderId.get(event.messageId);
1387
+ if (pending === undefined ||
1388
+ pending.listenerGeneration !== listenerGeneration.generation ||
1389
+ pending.listener !== listenerGeneration.listener) {
1390
+ return;
1391
+ }
1392
+ if (event.status === "held") {
1393
+ pending.writeEvidence = "transport_written";
1394
+ this.emitDelivery({ messageId: pending.gatewayMessageId, state: "held" });
1395
+ return;
1396
+ }
1397
+ // `released` is Claude's native approval-gate terminal: the frame reached
1398
+ // the recipient queue. It does not claim a model read or task completion.
1399
+ const state = event.status === "released"
1400
+ ? "released"
1401
+ : event.status === "denied"
1402
+ ? "denied"
1403
+ : event.status === "expired"
1404
+ ? "expired"
1405
+ : event.status === "unconfirmed"
1406
+ ? "unconfirmed"
1407
+ : "ambiguous";
1408
+ this.finishClaudeMessage(event.messageId, state, event.status === "unconfirmed"
1409
+ ? "CLAUDE_RECEIPT_UNCONFIRMED"
1410
+ : undefined);
1411
+ }
1412
+ recordClaudeWriteEvidence(providerId, evidence) {
1413
+ const pending = this.pendingByProviderId.get(providerId);
1414
+ if (pending === undefined)
1415
+ return false;
1416
+ const previous = pending.writeEvidence;
1417
+ if (pending.writeEvidence !== "transport_written" ||
1418
+ evidence === "transport_written") {
1419
+ pending.writeEvidence = evidence;
1420
+ }
1421
+ return pending.writeEvidence !== previous;
1422
+ }
1423
+ finishClaudeMessage(providerId, state, safeErrorCode) {
1424
+ const pending = this.pendingByProviderId.get(providerId);
1425
+ if (pending === undefined)
1426
+ return;
1427
+ clearTimeout(pending.timer);
1428
+ this.pendingByProviderId.delete(providerId);
1429
+ this.providerIdByGatewayId.delete(pending.gatewayMessageId);
1430
+ this.pendingTargetByGatewayId.delete(pending.gatewayMessageId);
1431
+ this.emitDelivery({
1432
+ messageId: pending.gatewayMessageId,
1433
+ state,
1434
+ ...(safeErrorCode === undefined ? {} : { safeErrorCode }),
1435
+ });
1436
+ }
1437
+ emitDelivery(event) {
1438
+ const callbacks = this.callbacks;
1439
+ if (callbacks === undefined || this.closed)
1440
+ return;
1441
+ invokeCallback(() => callbacks.onDelivery(event));
1442
+ }
1443
+ async refreshClaudeDiscovery() {
1444
+ if (this.discoveryInFlight !== undefined) {
1445
+ return await this.discoveryInFlight;
1446
+ }
1447
+ const operation = this.refreshClaudeDiscoveryOnce();
1448
+ this.discoveryInFlight = operation;
1449
+ try {
1450
+ return await operation;
1451
+ }
1452
+ finally {
1453
+ if (this.discoveryInFlight === operation) {
1454
+ this.discoveryInFlight = undefined;
1455
+ }
1456
+ }
1457
+ }
1458
+ async refreshClaudeDiscoveryOnce() {
1459
+ const dispatchEpochAtStart = new Map();
1460
+ for (const routeHandle of this.selected.keys()) {
1461
+ dispatchEpochAtStart.set(routeHandle, this.selectedDispatchEpoch.get(routeHandle) ?? 0);
1462
+ }
1463
+ const pendingAtStart = new Set(this.pendingTargetByGatewayId.values());
1464
+ const discovery = await this.peer.discover();
1465
+ this.discovered.clear();
1466
+ const rows = [];
1467
+ for (const peer of discovery.peers) {
1468
+ if (peer.kind !== "interactive" ||
1469
+ !NATIVE_CLAUDE_NAME.test(peer.alias) ||
1470
+ peer.alias.startsWith("codex-")) {
1471
+ continue;
1472
+ }
1473
+ const alias = `${peer.alias}@${this.identity.hostId}`;
1474
+ if (!PUBLIC_ALIAS.test(alias))
1475
+ continue;
1476
+ const row = {
1477
+ alias,
1478
+ routeHandle: peer.targetId,
1479
+ kind: "interactive",
1480
+ state: claudeRouteState(peer),
1481
+ compatibility: "compatible",
1482
+ };
1483
+ this.discovered.set(peer.targetId, row);
1484
+ rows.push(row);
1485
+ }
1486
+ for (const [routeHandle, route] of this.nativeInbound) {
1487
+ const current = this.discovered.get(routeHandle);
1488
+ if (current !== undefined) {
1489
+ // The stable session UUID owns native reply authority. A rename only
1490
+ // updates its current public coordinate.
1491
+ this.nativeInbound.set(routeHandle, {
1492
+ alias: current.alias,
1493
+ listenerGeneration: route.listenerGeneration,
1494
+ });
1495
+ }
1496
+ else if (!discovery.truncated) {
1497
+ this.nativeInbound.delete(routeHandle);
1498
+ }
1499
+ }
1500
+ for (const [routeHandle, selectedAlias] of this.selected) {
1501
+ const row = this.discovered.get(routeHandle);
1502
+ if (row === undefined) {
1503
+ this.emitClaudeRouteObservation(routeHandle, "busy", "CLAUDE_PEER_NOT_OBSERVED", true);
1504
+ }
1505
+ else {
1506
+ if (row.alias !== selectedAlias) {
1507
+ // A Claude rename changes only the live name index. The native
1508
+ // session UUID remains the selected logical route.
1509
+ this.selected.set(routeHandle, row.alias);
1510
+ }
1511
+ if (row.state === "idle" &&
1512
+ (pendingAtStart.has(routeHandle) ||
1513
+ this.hasPendingClaudeReceipt(routeHandle) ||
1514
+ dispatchEpochAtStart.get(routeHandle) !==
1515
+ (this.selectedDispatchEpoch.get(routeHandle) ?? 0))) {
1516
+ // Never publish an idle sample from a discovery that overlapped a
1517
+ // dispatch or its receipt lifetime. The dirty epoch remains set for
1518
+ // a wholly post-terminal discovery.
1519
+ continue;
1520
+ }
1521
+ this.emitClaudeRouteObservation(routeHandle, row.state, undefined, true);
1522
+ }
1523
+ }
1524
+ return { peers: rows, complete: !discovery.truncated };
1525
+ }
1526
+ emitClaudeRouteObservation(routeHandle, state, safeErrorCode, authoritative = false) {
1527
+ if (this.nativeCodexSuccessionFreeze !== undefined)
1528
+ return;
1529
+ if (authoritative &&
1530
+ state === "idle" &&
1531
+ this.hasPendingClaudeReceipt(routeHandle)) {
1532
+ // A registry peer can still report idle after our write but before it
1533
+ // consumes the inbox frame. Preserve the dirty dispatch epoch so only a
1534
+ // fresh discovery after exact terminal receipt can unlock this route.
1535
+ return;
1536
+ }
1537
+ const signature = `${state}:${safeErrorCode ?? ""}`;
1538
+ const mustPublish = authoritative && this.selectedObservationDirty.delete(routeHandle);
1539
+ if (!mustPublish && this.selectedObservations.get(routeHandle) === signature) {
1540
+ return;
1541
+ }
1542
+ this.selectedObservations.set(routeHandle, signature);
1543
+ const callbacks = this.callbacks;
1544
+ if (callbacks === undefined || this.closed)
1545
+ return;
1546
+ invokeCallback(() => callbacks.onRouteState({
1547
+ endpoint: callbackEndpoint(this.identity, routeHandle),
1548
+ state,
1549
+ ...(safeErrorCode === undefined ? {} : { safeErrorCode }),
1550
+ }));
1551
+ }
1552
+ hasPendingClaudeReceipt(routeHandle) {
1553
+ for (const targetId of this.pendingTargetByGatewayId.values()) {
1554
+ if (targetId === routeHandle)
1555
+ return true;
1556
+ }
1557
+ return false;
1558
+ }
1559
+ scheduleClaudeMonitor() {
1560
+ if (this.closed ||
1561
+ this.nativeCodexSuccessionFreeze !== undefined ||
1562
+ this.selected.size === 0 ||
1563
+ this.monitorTimer !== undefined) {
1564
+ return;
1565
+ }
1566
+ this.monitorTimer = setTimeout(() => {
1567
+ this.monitorTimer = undefined;
1568
+ void this.refreshClaudeDiscovery()
1569
+ .catch(() => {
1570
+ if (this.nativeCodexSuccessionFreeze !== undefined)
1571
+ return;
1572
+ for (const routeHandle of this.selected.keys()) {
1573
+ this.emitClaudeRouteObservation(routeHandle, "busy", "CLAUDE_DISCOVERY_UNAVAILABLE");
1574
+ }
1575
+ })
1576
+ .finally(() => this.scheduleClaudeMonitor());
1577
+ }, this.discoveryPollMs);
1578
+ this.monitorTimer.unref();
1579
+ }
1580
+ }
1581
+ export function createLocalClaudeGatewayProvider(options) {
1582
+ return new LocalClaudeGatewayProvider(options);
1583
+ }
1584
+ function validateCodexFactory(factory) {
1585
+ const schema = factory.schemaCompatibility;
1586
+ const write = factory.writeCompatibility;
1587
+ const certified = CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(factory.appServerVersion);
1588
+ const observedCandidate = factory.compatibilityPolicy === "observed" &&
1589
+ schema.observedSchemaCandidate === true &&
1590
+ CODEX_APP_SERVER_WRITABLE_VERSIONS.some((version) => sharesCompatibilityMajor(version, factory.appServerVersion));
1591
+ if (factory.hostId !== LOCAL_HOST ||
1592
+ factory.protocol !== "codex-app-server" ||
1593
+ factory.protocolVersion !== factory.appServerVersion ||
1594
+ (!certified && !observedCandidate) ||
1595
+ schema.appServerVersion !== factory.appServerVersion ||
1596
+ schema.endpointGeneration !== factory.endpointGeneration ||
1597
+ schema.protocol !== "app-server-v2-stable" ||
1598
+ schema.steering?.method !== "turn/steer" ||
1599
+ schema.steering.requestSchema !== "expected-turn-id-text-v1" ||
1600
+ schema.steering.deliveryBoundary !== "next-tool-call-boundary" ||
1601
+ (write !== null &&
1602
+ (write.appServerVersion !== schema.appServerVersion ||
1603
+ write.endpointGeneration !== schema.endpointGeneration ||
1604
+ write.protocol !== schema.protocol ||
1605
+ write.observedSchemaCandidate !== schema.observedSchemaCandidate ||
1606
+ write.steering?.method !== schema.steering.method ||
1607
+ write.steering.requestSchema !== schema.steering.requestSchema ||
1608
+ write.steering.deliveryBoundary !==
1609
+ schema.steering.deliveryBoundary)) ||
1610
+ factory.writableReady !== (write !== null)) {
1611
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The local Codex provider requires exact schema and write attestations.");
1612
+ }
1613
+ }
1614
+ function codexRouteState(observation) {
1615
+ if (observation.connection !== "ready")
1616
+ return "stale";
1617
+ if (observation.routeStatus === "idle")
1618
+ return "idle";
1619
+ if (observation.routeStatus === "waiting_approval") {
1620
+ return "awaiting_approval";
1621
+ }
1622
+ if (observation.routeStatus === "starting" ||
1623
+ observation.routeStatus === "active" ||
1624
+ observation.routeStatus === "interrupting") {
1625
+ return "busy";
1626
+ }
1627
+ // Unknown, not-loaded, uncertain, system-error, and explicit stale states
1628
+ // are never cosmetic busy. They cannot authorize another write.
1629
+ return "stale";
1630
+ }
1631
+ function codexRouteSafeCode(observation) {
1632
+ if (codexRouteState(observation) === "stale") {
1633
+ return "CODEX_ROUTE_STALE";
1634
+ }
1635
+ if (!observation.writableReady) {
1636
+ return "CODEX_WRITES_DISABLED";
1637
+ }
1638
+ return undefined;
1639
+ }
1640
+ export class LocalCodexGatewayProvider {
1641
+ identity;
1642
+ protocol;
1643
+ protocolVersion;
1644
+ factory;
1645
+ maxCallbacks;
1646
+ maxRoutes;
1647
+ maxReplyBytes;
1648
+ cleanupPollMs;
1649
+ cleanupTimeoutMs;
1650
+ recoveryInitialMs;
1651
+ recoveryMaxMs;
1652
+ now;
1653
+ routes = new Map();
1654
+ routeCreations = new Map();
1655
+ routeReleases = new Map();
1656
+ routeRecoveryAttempts = new Map();
1657
+ routeRecoveryTimers = new Map();
1658
+ routeRecoveries = new Map();
1659
+ routesRequiringRecovery = new Set();
1660
+ expectsReply = new Map();
1661
+ compatibilityCertifyingRoutes = new Set();
1662
+ compatibilityTurns = new Map();
1663
+ callbackQueue = [];
1664
+ callbackScheduled = false;
1665
+ callbacks;
1666
+ compatibilityAttested;
1667
+ initialized = false;
1668
+ closing = false;
1669
+ closed = false;
1670
+ constructor(options) {
1671
+ validateCodexFactory(options.factory);
1672
+ this.factory = options.factory;
1673
+ this.identity = {
1674
+ provider: "codex",
1675
+ hostId: exactLocalHost(options.factory.hostId),
1676
+ endpointGeneration: options.factory.endpointGeneration,
1677
+ };
1678
+ this.protocol = options.factory.protocol;
1679
+ this.protocolVersion = options.factory.protocolVersion;
1680
+ this.compatibilityAttested = CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(options.factory.appServerVersion);
1681
+ this.maxCallbacks = positiveBounded(options.maxCallbackEvents, MAX_CODEX_CALLBACKS, 4_096);
1682
+ this.maxRoutes = positiveBounded(options.maxRoutes, MAX_CODEX_ROUTES, 256);
1683
+ this.maxReplyBytes = positiveBounded(options.maxReplyBytes, MAX_TRANSIENT_REPLY_BYTES, 1024 * 1024);
1684
+ this.cleanupPollMs = positiveBounded(options.cleanupPollMs, DEFAULT_CODEX_CLEANUP_POLL_MS, 1_000);
1685
+ this.cleanupTimeoutMs = positiveBounded(options.cleanupTimeoutMs, DEFAULT_CODEX_CLEANUP_TIMEOUT_MS, 60_000);
1686
+ if (this.cleanupPollMs >= this.cleanupTimeoutMs) {
1687
+ throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "The cleanup polling interval must be shorter than its deadline.");
1688
+ }
1689
+ this.recoveryInitialMs = positiveBounded(options.recoveryInitialMs, DEFAULT_CODEX_RECOVERY_INITIAL_MS, 60_000);
1690
+ this.recoveryMaxMs = positiveBounded(options.recoveryMaxMs, DEFAULT_CODEX_RECOVERY_MAX_MS, 5 * 60_000);
1691
+ if (this.recoveryInitialMs > this.recoveryMaxMs) {
1692
+ throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "The Codex recovery initial delay cannot exceed its capped delay.");
1693
+ }
1694
+ this.now = options.now ?? (() => new Date());
1695
+ }
1696
+ compatibilitySurface() {
1697
+ return { surface: "codex", version: this.factory.appServerVersion };
1698
+ }
1699
+ acceptCompatibilityAttestation(attestation) {
1700
+ if (attestation.surface !== "codex" ||
1701
+ attestation.version !== this.factory.appServerVersion ||
1702
+ attestation.tier === "incompatible") {
1703
+ throw new BridgeError("CODEX_COMPATIBILITY_ATTESTATION_MISMATCH", "The Codex compatibility attestation does not match this endpoint.");
1704
+ }
1705
+ this.compatibilityAttested = true;
1706
+ }
1707
+ async runCompatibilityProbes() {
1708
+ const [installation, control, initialize, threadList] = compatibilityProbeNames.codex;
1709
+ let transport;
1710
+ let connector;
1711
+ let stage = "transport";
1712
+ try {
1713
+ transport = await this.factory.connectTransport();
1714
+ stage = "initialize";
1715
+ connector = await CodexAppServerConnector.connect({
1716
+ compatibility: this.factory.schemaCompatibility,
1717
+ writesEnabled: false,
1718
+ route: {
1719
+ endpointGeneration: this.identity.endpointGeneration,
1720
+ threadId: COMPATIBILITY_PROBE_THREAD_ID,
1721
+ },
1722
+ transport,
1723
+ maxReplyBytes: this.maxReplyBytes,
1724
+ now: this.now,
1725
+ onEvent: () => undefined,
1726
+ onTurnResult: () => undefined,
1727
+ });
1728
+ stage = "thread_list";
1729
+ await connector.observeLoadedThread(connector.guard());
1730
+ this.compatibilityAttested = true;
1731
+ return [
1732
+ passedProbe(installation),
1733
+ passedProbe(control),
1734
+ passedProbe(initialize),
1735
+ passedProbe(threadList),
1736
+ ];
1737
+ }
1738
+ catch (error) {
1739
+ if (stage === "transport") {
1740
+ const controlFailure = error instanceof LocalCodexTransportError &&
1741
+ [
1742
+ "LOCAL_APP_SERVER_NOT_RUNNING",
1743
+ "LOCAL_APP_SERVER_ENDPOINT_UNSAFE",
1744
+ "TRANSPORT_CONNECT_FAILED",
1745
+ ].includes(error.code);
1746
+ return [
1747
+ controlFailure
1748
+ ? passedProbe(installation)
1749
+ : failedProbe(installation, "CODEX_INSTALLATION_INVALID"),
1750
+ failedProbe(control, controlFailure
1751
+ ? "CODEX_CONTROL_SOCKET_UNAVAILABLE"
1752
+ : "CODEX_COMPAT_PROBE_BLOCKED"),
1753
+ failedProbe(initialize, "CODEX_COMPAT_PROBE_BLOCKED"),
1754
+ failedProbe(threadList, "CODEX_COMPAT_PROBE_BLOCKED"),
1755
+ ];
1756
+ }
1757
+ return [
1758
+ passedProbe(installation),
1759
+ passedProbe(control),
1760
+ stage === "initialize"
1761
+ ? failedProbe(initialize, "CODEX_INITIALIZE_SCHEMA_REJECTED")
1762
+ : passedProbe(initialize),
1763
+ failedProbe(threadList, "CODEX_THREAD_LIST_SCHEMA_REJECTED"),
1764
+ ];
1765
+ }
1766
+ finally {
1767
+ if (connector !== undefined) {
1768
+ await connector.close().catch(() => undefined);
1769
+ }
1770
+ else if (transport !== undefined) {
1771
+ await transport.close().catch(() => undefined);
1772
+ }
1773
+ }
1774
+ }
1775
+ async runCompatibilityCertification(input) {
1776
+ void input.controllerStateRoot;
1777
+ const certifiedAt = this.now().toISOString();
1778
+ const depth = input.withTurn ? "turn" : "thread_ops";
1779
+ const routeHandle = input.routeHandle;
1780
+ if (routeHandle === undefined || this.compatibilityCertifyingRoutes.has(routeHandle)) {
1781
+ return {
1782
+ depth,
1783
+ outcome: "fail",
1784
+ certifiedAt,
1785
+ safeErrorCode: "CODEX_CERTIFICATION_ROUTE_REQUIRED",
1786
+ };
1787
+ }
1788
+ this.assertReady();
1789
+ const route = this.routes.get(routeHandle);
1790
+ if (route === undefined || !this.observeRouteSuccessionBarrier(routeHandle).clean) {
1791
+ return {
1792
+ depth,
1793
+ outcome: "fail",
1794
+ certifiedAt,
1795
+ safeErrorCode: "CODEX_CERTIFICATION_ROUTE_BUSY",
1796
+ };
1797
+ }
1798
+ this.compatibilityCertifyingRoutes.add(routeHandle);
1799
+ try {
1800
+ const loaded = await route.connector.observeLoadedThread(route.connector.guard());
1801
+ if (!loaded.selectedThreadLoaded) {
1802
+ return {
1803
+ depth,
1804
+ outcome: "fail",
1805
+ certifiedAt: this.now().toISOString(),
1806
+ safeErrorCode: "CODEX_CERTIFICATION_THREAD_UNAVAILABLE",
1807
+ };
1808
+ }
1809
+ await route.connector.certifyThreadOperations(route.connector.guard());
1810
+ if (!input.withTurn) {
1811
+ return {
1812
+ depth,
1813
+ outcome: "pass",
1814
+ certifiedAt: this.now().toISOString(),
1815
+ };
1816
+ }
1817
+ const messageId = `compat_${randomBytes(12).toString("hex")}`;
1818
+ const result = new Promise((resolve) => {
1819
+ const timer = setTimeout(() => {
1820
+ this.compatibilityTurns.delete(messageId);
1821
+ resolve({
1822
+ messageId,
1823
+ outcome: "expired",
1824
+ replyCode: "REPLY_UNAVAILABLE",
1825
+ text: null,
1826
+ });
1827
+ }, COMPATIBILITY_TURN_TIMEOUT_MS);
1828
+ timer.unref();
1829
+ this.compatibilityTurns.set(messageId, { resolve, timer });
1830
+ });
1831
+ try {
1832
+ const disposition = await route.connector.submitMessage(route.connector.guard(), {
1833
+ deadlineAt: new Date(this.now().getTime() + COMPATIBILITY_TURN_TIMEOUT_MS).toISOString(),
1834
+ messageId,
1835
+ text: "reply OK",
1836
+ });
1837
+ if (disposition.disposition !== "started") {
1838
+ throw new BridgeError("CODEX_CERTIFICATION_TURN_NOT_STARTED", "The bounded certification turn did not start on the exact idle route.");
1839
+ }
1840
+ const settled = await result;
1841
+ if (settled.outcome !== "completed" ||
1842
+ settled.text?.trim() !== "OK") {
1843
+ return {
1844
+ depth,
1845
+ outcome: "fail",
1846
+ certifiedAt: this.now().toISOString(),
1847
+ safeErrorCode: "CODEX_CERTIFICATION_TURN_FAILED",
1848
+ };
1849
+ }
1850
+ return {
1851
+ depth,
1852
+ outcome: "pass",
1853
+ certifiedAt: this.now().toISOString(),
1854
+ };
1855
+ }
1856
+ finally {
1857
+ const pending = this.compatibilityTurns.get(messageId);
1858
+ if (pending !== undefined) {
1859
+ clearTimeout(pending.timer);
1860
+ this.compatibilityTurns.delete(messageId);
1861
+ }
1862
+ }
1863
+ }
1864
+ catch {
1865
+ return {
1866
+ depth,
1867
+ outcome: "fail",
1868
+ certifiedAt: this.now().toISOString(),
1869
+ safeErrorCode: "CODEX_CERTIFICATION_FAILED",
1870
+ };
1871
+ }
1872
+ finally {
1873
+ this.compatibilityCertifyingRoutes.delete(routeHandle);
1874
+ }
1875
+ }
1876
+ async initialize(callbacks) {
1877
+ if (this.closed || this.initialized) {
1878
+ throw new BridgeError("CODEX_PROVIDER_INITIALIZATION_REJECTED", "The local Codex provider cannot be initialized in this state.");
1879
+ }
1880
+ this.callbacks = callbacks;
1881
+ this.initialized = true;
1882
+ return this.factory.writeCompatibility === null
1883
+ ? {
1884
+ health: "degraded",
1885
+ compatibility: "compatible",
1886
+ safeErrorCode: "CODEX_MONITOR_ONLY",
1887
+ }
1888
+ : { health: "healthy", compatibility: "compatible" };
1889
+ }
1890
+ async selectRoute(input) {
1891
+ this.assertReady();
1892
+ if (!PUBLIC_ALIAS.test(input.alias) ||
1893
+ !input.alias.endsWith(`@${this.identity.hostId}`)) {
1894
+ throw new BridgeError("CODEX_ALIAS_MISMATCH", "The Codex alias is outside the exact local provider boundary.");
1895
+ }
1896
+ if (!OPAQUE_ROUTE.test(input.routeHandle)) {
1897
+ throw new BridgeError("CODEX_ROUTE_INVALID", "The Codex task identifier is outside the exact provider boundary.");
1898
+ }
1899
+ this.cancelRouteRecovery(input.routeHandle);
1900
+ const route = await this.ensureRoute(input.routeHandle);
1901
+ const observation = route.connector.observation();
1902
+ const state = codexRouteState(observation);
1903
+ if (state === "stale") {
1904
+ await this.releaseRoute(input.routeHandle);
1905
+ throw new BridgeError("CODEX_ROUTE_SETUP_REJECTED", "The exact Codex task connection closed during route selection.");
1906
+ }
1907
+ this.queueRouteObservation(route, observation);
1908
+ return {
1909
+ routeHandle: input.routeHandle,
1910
+ state,
1911
+ };
1912
+ }
1913
+ observeRouteSuccessionBarrier(routeHandle) {
1914
+ this.assertReady();
1915
+ const route = this.routes.get(routeHandle);
1916
+ const observation = route?.connector.observation();
1917
+ const routeCreationInFlight = this.routeCreations.has(routeHandle);
1918
+ const routeReleaseInFlight = this.routeReleases.has(routeHandle);
1919
+ const pendingReplyCorrelations = this.expectsReply.size;
1920
+ const pendingCallbacks = Math.max(this.callbackQueue.length, this.callbackScheduled ? 1 : 0);
1921
+ const routePresent = route !== undefined;
1922
+ const connection = observation?.connection ?? "absent";
1923
+ const routeStatus = observation?.routeStatus ?? "absent";
1924
+ const queueDepth = observation?.queueDepth ?? 0;
1925
+ const hasActiveTurn = observation?.hasActiveTurn ?? false;
1926
+ const requestInFlight = observation?.requestInFlight ?? false;
1927
+ return {
1928
+ routePresent,
1929
+ connection,
1930
+ routeStatus,
1931
+ queueDepth,
1932
+ hasActiveTurn,
1933
+ requestInFlight,
1934
+ routeCreationInFlight,
1935
+ routeReleaseInFlight,
1936
+ pendingReplyCorrelations,
1937
+ pendingCallbacks,
1938
+ clean: routePresent &&
1939
+ connection === "ready" &&
1940
+ routeStatus === "idle" &&
1941
+ queueDepth === 0 &&
1942
+ !hasActiveTurn &&
1943
+ !requestInFlight &&
1944
+ !routeCreationInFlight &&
1945
+ !routeReleaseInFlight &&
1946
+ pendingReplyCorrelations === 0 &&
1947
+ pendingCallbacks === 0,
1948
+ };
1949
+ }
1950
+ async dispatch(input) {
1951
+ if (!this.initialized ||
1952
+ this.closing ||
1953
+ this.closed ||
1954
+ !this.compatibilityAttested ||
1955
+ input.authorization !== "selected_route" ||
1956
+ !sameEndpoint(input.binding, this.identity)) {
1957
+ return { state: "failed", safeErrorCode: "CODEX_ROUTE_UNAVAILABLE" };
1958
+ }
1959
+ const route = this.routes.get(input.binding.routeHandle);
1960
+ if (route === undefined ||
1961
+ this.routeReleases.has(input.binding.routeHandle)) {
1962
+ return { state: "failed", safeErrorCode: "CODEX_ROUTE_UNAVAILABLE" };
1963
+ }
1964
+ if (this.compatibilityCertifyingRoutes.has(input.binding.routeHandle)) {
1965
+ return { state: "deferred", safeErrorCode: "CODEX_ROUTE_HELD" };
1966
+ }
1967
+ if (!this.factory.writableReady ||
1968
+ this.factory.writeCompatibility === null) {
1969
+ return { state: "failed", safeErrorCode: "CODEX_WRITES_DISABLED" };
1970
+ }
1971
+ const guard = route.connector.guard();
1972
+ if (!guard.writableReady) {
1973
+ return { state: "failed", safeErrorCode: "CODEX_WRITES_DISABLED" };
1974
+ }
1975
+ if (strictDeadline(input.deadlineAt, this.now().getTime()) === undefined) {
1976
+ return { state: "failed", safeErrorCode: "MESSAGE_EXPIRED" };
1977
+ }
1978
+ if (this.expectsReply.has(input.messageId)) {
1979
+ return { state: "failed", safeErrorCode: "CODEX_MESSAGE_DUPLICATE" };
1980
+ }
1981
+ this.expectsReply.set(input.messageId, input.expectsReply);
1982
+ try {
1983
+ const disposition = await route.connector.submitMessage(guard, {
1984
+ deadlineAt: input.deadlineAt,
1985
+ messageId: input.messageId,
1986
+ ...(input.steer === true ? { steer: true } : {}),
1987
+ text: input.text,
1988
+ });
1989
+ if (disposition.disposition === "deferred") {
1990
+ this.expectsReply.delete(input.messageId);
1991
+ return { state: "deferred", safeErrorCode: "CODEX_ROUTE_HELD" };
1992
+ }
1993
+ if (disposition.disposition === "steered") {
1994
+ this.expectsReply.delete(input.messageId);
1995
+ return { state: "delivered" };
1996
+ }
1997
+ return { state: "accepted" };
1998
+ }
1999
+ catch (error) {
2000
+ if (!this.expectsReply.has(input.messageId)) {
2001
+ // The connector synchronously handed off an exact terminal result.
2002
+ return { state: "pending" };
2003
+ }
2004
+ this.expectsReply.delete(input.messageId);
2005
+ if (error instanceof CodexConnectorError && error.ambiguous) {
2006
+ return {
2007
+ state: "ambiguous",
2008
+ safeErrorCode: "CODEX_DISPATCH_OUTCOME_AMBIGUOUS",
2009
+ };
2010
+ }
2011
+ if (error instanceof CodexConnectorError &&
2012
+ [
2013
+ "ROUTE_NOT_READY",
2014
+ "ROUTE_BUSY",
2015
+ ].includes(error.code)) {
2016
+ return { state: "deferred", safeErrorCode: "CODEX_ROUTE_HELD" };
2017
+ }
2018
+ return { state: "failed", safeErrorCode: "CODEX_DISPATCH_REJECTED" };
2019
+ }
2020
+ }
2021
+ async releaseRoute(routeHandle) {
2022
+ this.cancelRouteRecovery(routeHandle);
2023
+ const recovery = this.routeRecoveries.get(routeHandle);
2024
+ if (recovery !== undefined)
2025
+ await recovery;
2026
+ await this.releaseRouteInternal(routeHandle);
2027
+ }
2028
+ async releaseRouteInternal(routeHandle) {
2029
+ const pending = this.routeReleases.get(routeHandle);
2030
+ if (pending !== undefined)
2031
+ return await pending;
2032
+ const route = this.routes.get(routeHandle);
2033
+ if (route === undefined)
2034
+ return;
2035
+ const release = this.closeRoute(route).then(() => {
2036
+ if (this.routes.get(routeHandle) === route) {
2037
+ this.routes.delete(routeHandle);
2038
+ }
2039
+ });
2040
+ this.routeReleases.set(routeHandle, release);
2041
+ try {
2042
+ await release;
2043
+ }
2044
+ finally {
2045
+ this.routeReleases.delete(routeHandle);
2046
+ }
2047
+ }
2048
+ async close() {
2049
+ if (this.closed || this.closing)
2050
+ return;
2051
+ this.closing = true;
2052
+ for (const timer of this.routeRecoveryTimers.values())
2053
+ clearTimeout(timer);
2054
+ this.routeRecoveryTimers.clear();
2055
+ this.routesRequiringRecovery.clear();
2056
+ const entries = [...this.routes.entries()];
2057
+ const routeResults = await Promise.allSettled(entries.map(async ([routeHandle]) => this.releaseRoute(routeHandle)));
2058
+ await Promise.allSettled([
2059
+ ...this.routeRecoveries.values(),
2060
+ ...this.routeCreations.values(),
2061
+ ]);
2062
+ this.drainCallbackQueue();
2063
+ if (routeResults.some((result) => result.status === "rejected")) {
2064
+ this.closing = false;
2065
+ throw new BridgeError("CODEX_PROVIDER_CLEANUP_FAILED", "An exact owned Codex turn did not confirm termination.");
2066
+ }
2067
+ try {
2068
+ await this.factory.close();
2069
+ }
2070
+ catch {
2071
+ this.closing = false;
2072
+ throw new BridgeError("CODEX_PROVIDER_CLEANUP_FAILED", "An exact owned Codex provider process did not confirm cleanup.");
2073
+ }
2074
+ this.closed = true;
2075
+ this.closing = false;
2076
+ this.callbackQueue.length = 0;
2077
+ for (const [messageId, pending] of this.compatibilityTurns) {
2078
+ clearTimeout(pending.timer);
2079
+ pending.resolve({
2080
+ messageId,
2081
+ outcome: "abandoned",
2082
+ replyCode: "REPLY_UNAVAILABLE",
2083
+ text: null,
2084
+ });
2085
+ }
2086
+ this.compatibilityTurns.clear();
2087
+ this.compatibilityCertifyingRoutes.clear();
2088
+ this.expectsReply.clear();
2089
+ this.callbacks = undefined;
2090
+ }
2091
+ assertReady() {
2092
+ if (!this.initialized ||
2093
+ this.closing ||
2094
+ this.closed ||
2095
+ !this.compatibilityAttested) {
2096
+ throw new BridgeError("CODEX_PROVIDER_UNAVAILABLE", "The local Codex provider is unavailable.", true);
2097
+ }
2098
+ }
2099
+ async ensureRoute(threadId) {
2100
+ this.assertReady();
2101
+ const pendingRelease = this.routeReleases.get(threadId);
2102
+ if (pendingRelease !== undefined) {
2103
+ await pendingRelease;
2104
+ return await this.ensureRoute(threadId);
2105
+ }
2106
+ const existing = this.routes.get(threadId);
2107
+ if (existing !== undefined) {
2108
+ if (existing.connector.observation().connection === "ready") {
2109
+ return existing;
2110
+ }
2111
+ // A cached connector cannot recover after its transport closes or the
2112
+ // protocol faults. Fully release that exact connector before constructing
2113
+ // a fresh one. The identity-checked release lets automatic recovery and
2114
+ // an explicit selector join the same cleanup safely.
2115
+ await this.releaseRouteInternal(threadId);
2116
+ return await this.ensureRoute(threadId);
2117
+ }
2118
+ const pending = this.routeCreations.get(threadId);
2119
+ if (pending !== undefined)
2120
+ return await pending;
2121
+ if (this.routes.size + this.routeCreations.size >= this.maxRoutes) {
2122
+ throw new BridgeError("CODEX_ROUTE_CAPACITY", "The bounded local Codex route table is full.", true);
2123
+ }
2124
+ const creation = this.createRoute(threadId);
2125
+ this.routeCreations.set(threadId, creation);
2126
+ try {
2127
+ const route = await creation;
2128
+ if (this.closing || this.closed) {
2129
+ await this.closeRoute(route);
2130
+ throw new BridgeError("CODEX_PROVIDER_UNAVAILABLE", "The local Codex provider closed during route creation.");
2131
+ }
2132
+ this.routes.set(threadId, route);
2133
+ return route;
2134
+ }
2135
+ finally {
2136
+ this.routeCreations.delete(threadId);
2137
+ }
2138
+ }
2139
+ async createRoute(threadId) {
2140
+ let transport;
2141
+ let connector;
2142
+ const generation = Symbol(threadId);
2143
+ try {
2144
+ transport = await this.factory.connectTransport();
2145
+ const writesEnabled = this.factory.writableReady &&
2146
+ this.factory.writeCompatibility !== null;
2147
+ connector = await CodexAppServerConnector.connect({
2148
+ compatibility: this.factory.writeCompatibility ??
2149
+ this.factory.schemaCompatibility,
2150
+ writesEnabled,
2151
+ route: {
2152
+ endpointGeneration: this.identity.endpointGeneration,
2153
+ threadId,
2154
+ },
2155
+ transport,
2156
+ maxReplyBytes: this.maxReplyBytes,
2157
+ now: this.now,
2158
+ onEvent: (event) => this.onConnectorEvent(threadId, generation, event),
2159
+ onTurnResult: (result) => this.onTurnResult(result),
2160
+ });
2161
+ const loaded = await connector.observeLoadedThread(connector.guard());
2162
+ if (!loaded.selectedThreadLoaded) {
2163
+ throw new BridgeError("CODEX_THREAD_NOT_OBSERVED", "The exact opted-in Codex task is not loaded on this endpoint.");
2164
+ }
2165
+ await connector.resumeThread(connector.guard());
2166
+ const route = {
2167
+ connector,
2168
+ generation,
2169
+ threadId,
2170
+ transport,
2171
+ };
2172
+ this.queueRouteObservation(route, connector.observation());
2173
+ return route;
2174
+ }
2175
+ catch (error) {
2176
+ if (connector !== undefined) {
2177
+ await connector.close().catch(() => undefined);
2178
+ }
2179
+ else if (transport !== undefined) {
2180
+ await transport.close().catch(() => undefined);
2181
+ }
2182
+ if (error instanceof BridgeError)
2183
+ throw error;
2184
+ if (error instanceof CodexConnectorError) {
2185
+ throw new BridgeError(error.ambiguous
2186
+ ? "CODEX_ROUTE_SETUP_AMBIGUOUS"
2187
+ : "CODEX_ROUTE_SETUP_REJECTED", "The exact Codex task could not be safely observed and resumed.");
2188
+ }
2189
+ throw new BridgeError("CODEX_ROUTE_SETUP_REJECTED", "The exact Codex task could not be safely observed and resumed.");
2190
+ }
2191
+ }
2192
+ onConnectorEvent(threadId, generation, event) {
2193
+ const route = this.routes.get(threadId);
2194
+ if (route === undefined || route.generation !== generation)
2195
+ return;
2196
+ this.queueRouteObservation(route, event);
2197
+ if (event.kind === "protocol_fault" ||
2198
+ (event.kind === "connection_closed" &&
2199
+ event.details?.errorCode !== undefined) ||
2200
+ (event.kind === "route_status_changed" &&
2201
+ codexRouteState(event) === "stale")) {
2202
+ this.routesRequiringRecovery.add(threadId);
2203
+ this.scheduleRouteRecovery(threadId);
2204
+ }
2205
+ }
2206
+ scheduleRouteRecovery(threadId) {
2207
+ if (this.closed ||
2208
+ this.closing ||
2209
+ !this.routesRequiringRecovery.has(threadId) ||
2210
+ this.routeRecoveryTimers.has(threadId) ||
2211
+ this.routeRecoveries.has(threadId)) {
2212
+ return;
2213
+ }
2214
+ const attempt = this.routeRecoveryAttempts.get(threadId) ?? 0;
2215
+ const delay = Math.min(this.recoveryMaxMs, this.recoveryInitialMs * 2 ** Math.min(attempt, 16));
2216
+ const timer = setTimeout(() => {
2217
+ if (this.routeRecoveryTimers.get(threadId) !== timer)
2218
+ return;
2219
+ this.routeRecoveryTimers.delete(threadId);
2220
+ const recovery = this.recoverRoute(threadId);
2221
+ this.routeRecoveries.set(threadId, recovery);
2222
+ void recovery.finally(() => {
2223
+ if (this.routeRecoveries.get(threadId) === recovery) {
2224
+ this.routeRecoveries.delete(threadId);
2225
+ }
2226
+ this.scheduleRouteRecovery(threadId);
2227
+ });
2228
+ }, delay);
2229
+ timer.unref();
2230
+ this.routeRecoveryTimers.set(threadId, timer);
2231
+ }
2232
+ async recoverRoute(threadId) {
2233
+ if (this.closed ||
2234
+ this.closing ||
2235
+ !this.routesRequiringRecovery.has(threadId)) {
2236
+ return;
2237
+ }
2238
+ try {
2239
+ const current = this.routes.get(threadId);
2240
+ if (current !== undefined &&
2241
+ codexRouteState(current.connector.observation()) === "stale") {
2242
+ await this.releaseRouteInternal(threadId);
2243
+ }
2244
+ if (!this.routesRequiringRecovery.has(threadId))
2245
+ return;
2246
+ const replacement = await this.ensureRoute(threadId);
2247
+ if (!this.routesRequiringRecovery.has(threadId)) {
2248
+ // An explicit selector can join this exact creation after cancelling
2249
+ // automatic recovery. In that case the replacement is now operator-
2250
+ // owned and must remain live. Explicit release waits for this recovery
2251
+ // promise before closing any late-created replacement.
2252
+ return;
2253
+ }
2254
+ this.routesRequiringRecovery.delete(threadId);
2255
+ this.routeRecoveryAttempts.delete(threadId);
2256
+ this.queueRouteObservation(replacement, replacement.connector.observation());
2257
+ }
2258
+ catch {
2259
+ this.routeRecoveryAttempts.set(threadId, (this.routeRecoveryAttempts.get(threadId) ?? 0) + 1);
2260
+ }
2261
+ }
2262
+ cancelRouteRecovery(threadId) {
2263
+ this.routesRequiringRecovery.delete(threadId);
2264
+ this.routeRecoveryAttempts.delete(threadId);
2265
+ const timer = this.routeRecoveryTimers.get(threadId);
2266
+ if (timer !== undefined)
2267
+ clearTimeout(timer);
2268
+ this.routeRecoveryTimers.delete(threadId);
2269
+ }
2270
+ onTurnResult(result) {
2271
+ const certification = this.compatibilityTurns.get(result.messageId);
2272
+ if (certification !== undefined) {
2273
+ clearTimeout(certification.timer);
2274
+ this.compatibilityTurns.delete(result.messageId);
2275
+ certification.resolve(result);
2276
+ return;
2277
+ }
2278
+ const wantsReply = this.expectsReply.get(result.messageId) === true;
2279
+ this.expectsReply.delete(result.messageId);
2280
+ let replyText;
2281
+ if (wantsReply && result.text !== null) {
2282
+ const bytes = Buffer.from(result.text, "utf8");
2283
+ if (bytes.length <= this.maxReplyBytes) {
2284
+ replyText = Buffer.from(bytes).toString("utf8");
2285
+ }
2286
+ }
2287
+ const state = result.outcome === "completed"
2288
+ ? "completed"
2289
+ : result.outcome === "interrupted"
2290
+ ? "cancelled"
2291
+ : result.outcome === "expired"
2292
+ ? "expired"
2293
+ : result.outcome === "failed"
2294
+ ? "failed"
2295
+ : "ambiguous";
2296
+ const safeErrorCode = result.outcome === "completed"
2297
+ ? result.replyCode === "REPLY_TOO_LARGE"
2298
+ ? "CODEX_REPLY_TOO_LARGE"
2299
+ : undefined
2300
+ : result.outcome === "expired"
2301
+ ? "MESSAGE_EXPIRED"
2302
+ : result.outcome === "interrupted"
2303
+ ? "CODEX_TURN_INTERRUPTED"
2304
+ : result.outcome === "failed"
2305
+ ? "CODEX_TURN_FAILED"
2306
+ : "CODEX_DELIVERY_AMBIGUOUS";
2307
+ this.enqueueCodexCallback({
2308
+ type: "delivery",
2309
+ value: {
2310
+ messageId: result.messageId,
2311
+ state,
2312
+ ...(safeErrorCode === undefined ? {} : { safeErrorCode }),
2313
+ ...(replyText === undefined ? {} : { replyText }),
2314
+ },
2315
+ });
2316
+ }
2317
+ queueRouteObservation(route, observation) {
2318
+ this.enqueueCodexCallback({
2319
+ type: "route",
2320
+ generation: route.generation,
2321
+ routeHandle: route.threadId,
2322
+ state: codexRouteState(observation),
2323
+ ...(codexRouteSafeCode(observation) === undefined
2324
+ ? {}
2325
+ : { safeErrorCode: codexRouteSafeCode(observation) }),
2326
+ });
2327
+ }
2328
+ enqueueCodexCallback(event) {
2329
+ if (this.closed)
2330
+ return;
2331
+ if (this.callbackQueue.length >= this.maxCallbacks) {
2332
+ const routeIndex = this.callbackQueue.findIndex((candidate) => candidate.type === "route");
2333
+ if (routeIndex >= 0) {
2334
+ this.callbackQueue.splice(routeIndex, 1);
2335
+ }
2336
+ else {
2337
+ // The callback consumer is synchronous and itself queues safely. Drain
2338
+ // one exact terminal handoff before accepting another; never drop one.
2339
+ this.drainOneCallback();
2340
+ }
2341
+ }
2342
+ this.callbackQueue.push(event);
2343
+ if (this.callbackScheduled)
2344
+ return;
2345
+ this.callbackScheduled = true;
2346
+ queueMicrotask(() => {
2347
+ this.callbackScheduled = false;
2348
+ this.drainCallbackQueue();
2349
+ });
2350
+ }
2351
+ drainOneCallback() {
2352
+ const event = this.callbackQueue.shift();
2353
+ const callbacks = this.callbacks;
2354
+ if (event === undefined || callbacks === undefined)
2355
+ return;
2356
+ if (event.type === "delivery") {
2357
+ invokeCallback(() => callbacks.onDelivery(event.value));
2358
+ }
2359
+ else {
2360
+ if (this.routes.get(event.routeHandle)?.generation !== event.generation) {
2361
+ return;
2362
+ }
2363
+ invokeCallback(() => callbacks.onRouteState({
2364
+ endpoint: callbackEndpoint(this.identity, event.routeHandle),
2365
+ state: event.state,
2366
+ ...(event.safeErrorCode === undefined
2367
+ ? {}
2368
+ : { safeErrorCode: event.safeErrorCode }),
2369
+ }));
2370
+ }
2371
+ }
2372
+ drainCallbackQueue() {
2373
+ while (this.callbackQueue.length > 0)
2374
+ this.drainOneCallback();
2375
+ }
2376
+ async closeRoute(route) {
2377
+ let observation = route.connector.observation();
2378
+ if (observation.connection === "ready") {
2379
+ try {
2380
+ route.connector.cancelQueuedMessages(route.connector.guard());
2381
+ }
2382
+ catch {
2383
+ throw new BridgeError("CODEX_PROVIDER_CLEANUP_AMBIGUOUS", "The exact Codex queue could not be safely cancelled.");
2384
+ }
2385
+ let guard = route.connector.guard();
2386
+ let externalTurnObserved = false;
2387
+ if (guard.activeTurnId !== null &&
2388
+ (guard.status === "active" || guard.status === "waiting_approval")) {
2389
+ try {
2390
+ await route.connector.interruptOwnedTurn(guard);
2391
+ }
2392
+ catch (error) {
2393
+ if (error instanceof CodexConnectorError &&
2394
+ error.code === "TURN_NOT_OWNED") {
2395
+ // An approval request or turn notification can expose another
2396
+ // Desktop client's turn ID. The connector verifies ownership
2397
+ // before issuing RPC, so release must detach without waiting for
2398
+ // or attempting to control that external work.
2399
+ externalTurnObserved = true;
2400
+ }
2401
+ else {
2402
+ throw new BridgeError("CODEX_PROVIDER_CLEANUP_AMBIGUOUS", "The exact bridge-owned Codex turn did not confirm interruption.");
2403
+ }
2404
+ }
2405
+ }
2406
+ if (!externalTurnObserved) {
2407
+ guard = await this.waitForOwnedTurnToSettle(route.connector);
2408
+ observation = route.connector.observation();
2409
+ if (guard.activeTurnId !== null ||
2410
+ observation.hasActiveTurn ||
2411
+ observation.queueDepth !== 0) {
2412
+ throw new BridgeError("CODEX_PROVIDER_CLEANUP_AMBIGUOUS", "The exact bridge-owned Codex work did not reach a terminal state.");
2413
+ }
2414
+ if (observation.routeStatus !== "active" &&
2415
+ observation.routeStatus !== "waiting_approval" &&
2416
+ observation.routeStatus !== "starting" &&
2417
+ observation.routeStatus !== "interrupting") {
2418
+ await route.connector
2419
+ .unsubscribeThread(route.connector.guard())
2420
+ .catch(() => undefined);
2421
+ }
2422
+ }
2423
+ }
2424
+ await route.connector.close();
2425
+ if (!route.transport.cleanupConfirmed) {
2426
+ await route.transport.close();
2427
+ }
2428
+ if (!route.transport.cleanupConfirmed) {
2429
+ throw new BridgeError("CODEX_PROVIDER_CLEANUP_FAILED", "The exact owned Codex proxy did not confirm termination.");
2430
+ }
2431
+ }
2432
+ async waitForOwnedTurnToSettle(connector) {
2433
+ const deadline = Date.now() + this.cleanupTimeoutMs;
2434
+ while (true) {
2435
+ const guard = connector.guard();
2436
+ if (guard.activeTurnId === null)
2437
+ return guard;
2438
+ if (connector.observation().connection !== "ready" ||
2439
+ Date.now() >= deadline) {
2440
+ throw new BridgeError("CODEX_PROVIDER_CLEANUP_TIMEOUT", "The exact bridge-owned Codex turn did not confirm termination before the cleanup deadline.");
2441
+ }
2442
+ await new Promise((resolve) => {
2443
+ setTimeout(resolve, this.cleanupPollMs);
2444
+ });
2445
+ }
2446
+ }
2447
+ }
2448
+ export function createLocalCodexGatewayProvider(options) {
2449
+ return new LocalCodexGatewayProvider(options);
2450
+ }
2451
+ //# sourceMappingURL=providers.js.map