agent-embassy 3.1.0 → 4.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 (112) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CONTRIBUTING.md +92 -202
  3. package/README.md +157 -155
  4. package/SECURITY.md +191 -348
  5. package/dist/src/gateway/broker-check.d.ts +26 -0
  6. package/dist/src/gateway/broker-check.js +89 -0
  7. package/dist/src/gateway/broker-check.js.map +1 -0
  8. package/dist/src/gateway/broker-control.d.ts +72 -0
  9. package/dist/src/gateway/broker-control.js +164 -0
  10. package/dist/src/gateway/broker-control.js.map +1 -0
  11. package/dist/src/gateway/broker.d.ts +117 -0
  12. package/dist/src/gateway/broker.js +186 -0
  13. package/dist/src/gateway/broker.js.map +1 -0
  14. package/dist/src/gateway/claude-peer.d.ts +1 -124
  15. package/dist/src/gateway/claude-peer.js +61 -1052
  16. package/dist/src/gateway/claude-peer.js.map +1 -1
  17. package/dist/src/gateway/codex-stateless-transport.d.ts +0 -12
  18. package/dist/src/gateway/codex-stateless-transport.js +8 -142
  19. package/dist/src/gateway/codex-stateless-transport.js.map +1 -1
  20. package/dist/src/gateway/config.d.ts +2 -6
  21. package/dist/src/gateway/config.js +17 -27
  22. package/dist/src/gateway/config.js.map +1 -1
  23. package/dist/src/gateway/coordinator.d.ts +57 -0
  24. package/dist/src/gateway/coordinator.js +217 -0
  25. package/dist/src/gateway/coordinator.js.map +1 -0
  26. package/dist/src/gateway/core-cli.d.ts +15 -0
  27. package/dist/src/gateway/core-cli.js +282 -0
  28. package/dist/src/gateway/core-cli.js.map +1 -0
  29. package/dist/src/gateway/core-service-command.d.ts +16 -0
  30. package/dist/src/gateway/core-service-command.js +71 -0
  31. package/dist/src/gateway/core-service-command.js.map +1 -0
  32. package/dist/src/gateway/endpoint-directory.d.ts +33 -0
  33. package/dist/src/gateway/endpoint-directory.js +223 -0
  34. package/dist/src/gateway/endpoint-directory.js.map +1 -0
  35. package/dist/src/gateway/federation.d.ts +89 -0
  36. package/dist/src/gateway/federation.js +632 -0
  37. package/dist/src/gateway/federation.js.map +1 -0
  38. package/dist/src/gateway/ledger-codec.d.ts +3 -0
  39. package/dist/src/gateway/ledger-codec.js +159 -0
  40. package/dist/src/gateway/ledger-codec.js.map +1 -0
  41. package/dist/src/gateway/ledger.d.ts +138 -0
  42. package/dist/src/gateway/ledger.js +319 -0
  43. package/dist/src/gateway/ledger.js.map +1 -0
  44. package/dist/src/gateway/local-control.d.ts +23 -0
  45. package/dist/src/gateway/local-control.js +489 -0
  46. package/dist/src/gateway/local-control.js.map +1 -0
  47. package/dist/src/gateway/native-destinations.d.ts +34 -0
  48. package/dist/src/gateway/native-destinations.js +272 -0
  49. package/dist/src/gateway/native-destinations.js.map +1 -0
  50. package/dist/src/gateway/owned-state.d.ts +57 -0
  51. package/dist/src/gateway/owned-state.js +398 -0
  52. package/dist/src/gateway/owned-state.js.map +1 -0
  53. package/dist/src/gateway/provenance-envelope.d.ts +3 -3
  54. package/dist/src/gateway/provenance-envelope.js +3 -23
  55. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  56. package/dist/src/gateway/runtime.d.ts +41 -0
  57. package/dist/src/gateway/runtime.js +175 -0
  58. package/dist/src/gateway/runtime.js.map +1 -0
  59. package/docs/CONFIGURATION.md +178 -266
  60. package/docs/DELIVERY.md +133 -82
  61. package/docs/GATEWAY-ARCHITECTURE.md +276 -949
  62. package/package.json +5 -5
  63. package/skills/embassy-peer/SKILL.md +38 -196
  64. package/skills/embassy-peer/agents/openai.yaml +2 -2
  65. package/dist/src/gateway/claude-helper-protocol.d.ts +0 -117
  66. package/dist/src/gateway/claude-helper-protocol.js +0 -122
  67. package/dist/src/gateway/claude-helper-protocol.js.map +0 -1
  68. package/dist/src/gateway/claude-helper-supervisor.d.ts +0 -87
  69. package/dist/src/gateway/claude-helper-supervisor.js +0 -374
  70. package/dist/src/gateway/claude-helper-supervisor.js.map +0 -1
  71. package/dist/src/gateway/claude-helper.d.ts +0 -1
  72. package/dist/src/gateway/claude-helper.js +0 -252
  73. package/dist/src/gateway/claude-helper.js.map +0 -1
  74. package/dist/src/gateway/cli.d.ts +0 -56
  75. package/dist/src/gateway/cli.js +0 -1532
  76. package/dist/src/gateway/cli.js.map +0 -1
  77. package/dist/src/gateway/codex-socket-holder.d.ts +0 -26
  78. package/dist/src/gateway/codex-socket-holder.js +0 -76
  79. package/dist/src/gateway/codex-socket-holder.js.map +0 -1
  80. package/dist/src/gateway/control.d.ts +0 -279
  81. package/dist/src/gateway/control.js +0 -933
  82. package/dist/src/gateway/control.js.map +0 -1
  83. package/dist/src/gateway/peer-client.d.ts +0 -58
  84. package/dist/src/gateway/peer-client.js +0 -217
  85. package/dist/src/gateway/peer-client.js.map +0 -1
  86. package/dist/src/gateway/peer-mailbox.d.ts +0 -78
  87. package/dist/src/gateway/peer-mailbox.js +0 -168
  88. package/dist/src/gateway/peer-mailbox.js.map +0 -1
  89. package/dist/src/gateway/peer-protocol.d.ts +0 -110
  90. package/dist/src/gateway/peer-protocol.js +0 -79
  91. package/dist/src/gateway/peer-protocol.js.map +0 -1
  92. package/dist/src/gateway/peer-stdio.d.ts +0 -23
  93. package/dist/src/gateway/peer-stdio.js +0 -100
  94. package/dist/src/gateway/peer-stdio.js.map +0 -1
  95. package/dist/src/gateway/providers.d.ts +0 -164
  96. package/dist/src/gateway/providers.js +0 -975
  97. package/dist/src/gateway/providers.js.map +0 -1
  98. package/dist/src/gateway/server.d.ts +0 -46
  99. package/dist/src/gateway/server.js +0 -216
  100. package/dist/src/gateway/server.js.map +0 -1
  101. package/dist/src/gateway/service.d.ts +0 -371
  102. package/dist/src/gateway/service.js +0 -2335
  103. package/dist/src/gateway/service.js.map +0 -1
  104. package/dist/src/gateway/status-view.d.ts +0 -119
  105. package/dist/src/gateway/status-view.js +0 -484
  106. package/dist/src/gateway/status-view.js.map +0 -1
  107. package/dist/src/gateway/store.d.ts +0 -188
  108. package/dist/src/gateway/store.js +0 -2360
  109. package/dist/src/gateway/store.js.map +0 -1
  110. package/dist/src/gateway/types.d.ts +0 -541
  111. package/dist/src/gateway/types.js +0 -332
  112. package/dist/src/gateway/types.js.map +0 -1
@@ -1,975 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import path from "node:path";
3
- import { BridgeError } from "../errors.js";
4
- import { isAttestedGatewayNodeInventory } from "./federation-nodes.js";
5
- import { CLAUDE_PEER_COMPATIBILITY, ClaudePeerAdapter, } from "./claude-peer.js";
6
- import { ClaudeNativeHelperSupervisor, } from "./claude-helper-supervisor.js";
7
- import { composeProvenanceEnvelope } from "./provenance-envelope.js";
8
- const NATIVE_CLAUDE_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
9
- const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
10
- const OPAQUE_ROUTE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
11
- const MAX_CLAUDE_PENDING = 1_024;
12
- const DEFAULT_CLAUDE_DISCOVERY_POLL_MS = 1_000;
13
- const DEFAULT_CODEX_OBSERVATION_POLL_MS = 15_000;
14
- const DEFAULT_CODEX_OBSERVATION_TIMEOUT_MS = 15_000;
15
- const DEFAULT_CODEX_OBSERVATION_BACKOFF_MAX_MS = 60_000;
16
- const MAX_CODEX_OBSERVED_ROUTES = 128;
17
- export const CLAUDE_CLEAN_PREWRITE_RETRY_CODES = new Set([
18
- "CLAUDE_PEER_TARGET_UNKNOWN",
19
- "CLAUDE_PEER_WORKSPACE_UNATTESTED",
20
- ]);
21
- const CODEX_CLEAN_RETRY_CODES = new Set([
22
- "THREAD_NOT_OBSERVED",
23
- "ROUTE_BUSY",
24
- "APPROVAL_REQUIRED",
25
- "MANAGED_CODEX_UNAVAILABLE",
26
- "LOCAL_APP_SERVER_NOT_RUNNING",
27
- "ENDPOINT_GENERATION_CHANGED",
28
- "SPAWN_FAILED",
29
- "SPAWN_TIMEOUT",
30
- "TRANSPORT_CONNECT_FAILED",
31
- "REQUEST_TIMEOUT",
32
- ]);
33
- function claudeCleanPrewriteResult(error) {
34
- if (error instanceof BridgeError &&
35
- error.code === "CLAUDE_PEER_MESSAGE_EXPIRED") {
36
- return { state: "expired", safeErrorCode: "MESSAGE_EXPIRED" };
37
- }
38
- if (error instanceof BridgeError &&
39
- CLAUDE_CLEAN_PREWRITE_RETRY_CODES.has(error.code)) {
40
- return { state: "deferred", safeErrorCode: "ROUTE_BUSY" };
41
- }
42
- const systemCode = typeof error === "object" &&
43
- error !== null &&
44
- "code" in error &&
45
- typeof error.code === "string"
46
- ? error.code
47
- : undefined;
48
- return {
49
- state: "failed",
50
- safeErrorCode: error instanceof BridgeError
51
- ? error.code
52
- : systemCode === "ENOENT"
53
- ? "CLAUDE_DISPATCH_PREWRITE_PATH_MISSING"
54
- : systemCode === "EACCES" || systemCode === "EPERM"
55
- ? "CLAUDE_DISPATCH_PREWRITE_ACCESS_DENIED"
56
- : systemCode === "ETIMEDOUT"
57
- ? "CLAUDE_DISPATCH_PREWRITE_TIMEOUT"
58
- : "CLAUDE_DISPATCH_PREWRITE_FAILED",
59
- };
60
- }
61
- function claudePostAuthorizationResult(error) {
62
- if (error instanceof BridgeError && error.recoverable) {
63
- return {
64
- state: "failed",
65
- safeErrorCode: error.code === "CLAUDE_PEER_MESSAGE_EXPIRED"
66
- ? "MESSAGE_EXPIRED"
67
- : error.code,
68
- };
69
- }
70
- return {
71
- state: "ambiguous",
72
- safeErrorCode: error instanceof BridgeError &&
73
- /^[A-Z][A-Z0-9_]{0,95}$/.test(error.code)
74
- ? error.code
75
- : "CLAUDE_DISPATCH_OUTCOME_AMBIGUOUS",
76
- };
77
- }
78
- async function cancelPreparedClaude(prepared) {
79
- try {
80
- await prepared.cancel();
81
- }
82
- catch {
83
- // Cancellation is a no-write cleanup path. Its failure cannot turn a
84
- // denied or uncertain authorization into permission to perform.
85
- }
86
- }
87
- function exactLocalHost(hostId, inventory) {
88
- if (!isAttestedGatewayNodeInventory(inventory, hostId)) {
89
- throw new BridgeError("GATEWAY_REMOTE_PROVIDER_DISABLED", "The local host coordinate requires an attested node inventory.");
90
- }
91
- return hostId;
92
- }
93
- function positiveBounded(value, fallback, maximum) {
94
- const resolved = value ?? fallback;
95
- if (!Number.isSafeInteger(resolved) ||
96
- resolved <= 0 ||
97
- resolved > maximum) {
98
- throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "A provider capacity is outside its reviewed bound.");
99
- }
100
- return resolved;
101
- }
102
- function sameEndpoint(binding, identity) {
103
- return (binding.provider === identity.provider &&
104
- binding.hostId === identity.hostId);
105
- }
106
- function invokeCallback(operation) {
107
- try {
108
- operation();
109
- }
110
- catch {
111
- // Provider observation consumers cannot change a transport outcome.
112
- }
113
- }
114
- function validateClaudeRuntimePathEvidence(runtime) {
115
- if ([runtime.sessionsDir, runtime.socketDir].some((value) => !path.isAbsolute(value) ||
116
- path.normalize(value) !== value ||
117
- value.includes("\0"))) {
118
- throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "The local Claude provider requires bounded absolute runtime evidence.");
119
- }
120
- }
121
- function claudeRouteState(peer) {
122
- return peer.status === "idle" ? "idle" : "busy";
123
- }
124
- function claudeRegistryObservation(discovery) {
125
- const rejected = Object.entries(discovery.rejected)
126
- .filter((entry) => Number.isSafeInteger(entry[1]) && entry[1] > 0)
127
- .sort(([left], [right]) => left.localeCompare(right))
128
- .map(([safeErrorCode, count]) => Object.freeze({ safeErrorCode, count }));
129
- return Object.freeze({
130
- entriesScanned: discovery.entriesScanned,
131
- parseableRecords: discovery.parseableRecords,
132
- rejected: Object.freeze(rejected),
133
- });
134
- }
135
- function unavailableClaudeRegistryObservation() {
136
- return Object.freeze({
137
- entriesScanned: 0,
138
- parseableRecords: 0,
139
- rejected: Object.freeze([
140
- Object.freeze({
141
- safeErrorCode: "CLAUDE_REGISTRY_UNAVAILABLE",
142
- count: 1,
143
- }),
144
- ]),
145
- });
146
- }
147
- export class LocalClaudeGatewayProvider {
148
- identity;
149
- protocol = "claude-peer";
150
- protocolVersion = `${CLAUDE_PEER_COMPATIBILITY.peerProtocol}`;
151
- peer;
152
- stateRoot;
153
- maxPending;
154
- discoveryPollMs;
155
- now;
156
- nativeHelpers;
157
- discovered = new Map();
158
- /** Exact UUID selection plus its display-only observation bookkeeping. */
159
- selected = new Map();
160
- discoveryInFlight;
161
- registryObservation;
162
- monitorTimer;
163
- callbacks;
164
- initialized = false;
165
- closed = false;
166
- constructor(options) {
167
- validateClaudeRuntimePathEvidence(options.runtime);
168
- if (!path.isAbsolute(options.stateRoot) || options.stateRoot.includes("\0")) {
169
- throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "The Claude provider state root must be an absolute path.");
170
- }
171
- const hostId = exactLocalHost(options.hostId, options.nodeInventory);
172
- this.identity = {
173
- provider: "claude",
174
- hostId,
175
- };
176
- this.stateRoot = options.stateRoot;
177
- this.maxPending = positiveBounded(options.maxPendingMessages, MAX_CLAUDE_PENDING, 4_096);
178
- this.discoveryPollMs = positiveBounded(options.discoveryPollMs, DEFAULT_CLAUDE_DISCOVERY_POLL_MS, 30_000);
179
- this.now = options.now ?? Date.now;
180
- const protocolRuntime = options.runtime;
181
- this.peer = (options.peerFactory ??
182
- ((runtime, deliveryNotices) => new ClaudePeerAdapter({
183
- sessionsDir: runtime.sessionsDir,
184
- socketDir: runtime.socketDir,
185
- deliveryNotices,
186
- })))(protocolRuntime, options.deliveryNotices ?? "merged");
187
- this.nativeHelpers =
188
- options.nativeHelpers === undefined
189
- ? undefined
190
- : new ClaudeNativeHelperSupervisor({
191
- identity: this.identity,
192
- runtime: protocolRuntime,
193
- deliveryNotices: options.deliveryNotices ?? "merged",
194
- maxPendingMessages: this.maxPending,
195
- maxHelpers: positiveBounded(options.nativeHelpers.maxHelpers, 128, 128),
196
- callbacks: () => this.callbacks,
197
- ...(options.nativeHelpers.factory === undefined
198
- ? {}
199
- : { factory: options.nativeHelpers.factory }),
200
- });
201
- }
202
- latestRegistryObservation() {
203
- return this.registryObservation;
204
- }
205
- async initialize(callbacks) {
206
- if (this.closed) {
207
- throw new BridgeError("CLAUDE_PROVIDER_CLOSED", "The local Claude provider is closed.");
208
- }
209
- if (this.initialized) {
210
- throw new BridgeError("CLAUDE_PROVIDER_ALREADY_INITIALIZED", "The local Claude provider can be initialized only once.");
211
- }
212
- this.callbacks = callbacks;
213
- this.initialized = true;
214
- return { health: "healthy" };
215
- }
216
- async discoverClaudePeers() {
217
- this.assertReady();
218
- return await this.refreshClaudeDiscovery();
219
- }
220
- async assertWorkspaceDisjoint(routeHandle, stateRoot) {
221
- this.assertReady();
222
- await this.peer.assertTargetWorkspaceDisjoint(routeHandle, stateRoot);
223
- const route = this.selected.get(routeHandle);
224
- const alias = route?.alias ?? this.discovered.get(routeHandle)?.alias;
225
- if (alias !== undefined) {
226
- this.selected.set(routeHandle, {
227
- alias,
228
- dispatchEpoch: route?.dispatchEpoch ?? 0,
229
- observationDirty: route?.observationDirty ?? false,
230
- ...(route?.registrationId === undefined ? {} : { registrationId: route.registrationId }),
231
- ...(route?.observation === undefined ? {} : { observation: route.observation }),
232
- stateRoot,
233
- });
234
- }
235
- }
236
- async selectRoute(input) {
237
- this.assertReady();
238
- const candidate = this.discovered.get(input.routeHandle);
239
- if (candidate === undefined || candidate.alias !== input.alias) {
240
- // The TARGET moved between discovery and this pin — it renamed or left.
241
- // A distinct code from the caller-side CLAUDE_ROUTE_MISMATCH, so the CLI
242
- // cannot answer "fix your --from" to a problem at the other end.
243
- throw new BridgeError("CLAUDE_TARGET_CHANGED", "The addressed Claude session no longer matches that exact peer generation.");
244
- }
245
- const route = this.selected.get(input.routeHandle);
246
- this.selected.set(input.routeHandle, {
247
- alias: input.alias,
248
- dispatchEpoch: route?.dispatchEpoch ?? 0,
249
- observation: `${candidate.state}:`,
250
- observationDirty: false,
251
- ...(route?.registrationId === undefined ? {} : { registrationId: route.registrationId }),
252
- ...(route?.stateRoot === undefined ? {} : { stateRoot: route.stateRoot }),
253
- });
254
- this.scheduleClaudeMonitor();
255
- return { routeHandle: input.routeHandle, state: candidate.state };
256
- }
257
- observeLogicalRoute(input) {
258
- if (this.closed ||
259
- !PUBLIC_ALIAS.test(input.alias) ||
260
- !input.alias.endsWith(`@${this.identity.hostId}`) ||
261
- !OPAQUE_ROUTE.test(input.routeHandle) ||
262
- !OPAQUE_ROUTE.test(input.registrationId)) {
263
- return;
264
- }
265
- const route = this.selected.get(input.routeHandle);
266
- this.selected.set(input.routeHandle, {
267
- alias: input.alias,
268
- registrationId: input.registrationId,
269
- stateRoot: this.stateRoot,
270
- dispatchEpoch: route?.dispatchEpoch ?? 0,
271
- observationDirty: route?.observationDirty ?? false,
272
- ...(route?.observation === undefined ? {} : { observation: route.observation }),
273
- });
274
- }
275
- forgetLogicalRoute(registrationId) {
276
- for (const [routeHandle, route] of this.selected) {
277
- if (route.registrationId === registrationId) {
278
- this.selected.delete(routeHandle);
279
- }
280
- }
281
- }
282
- /**
283
- * Converts an inherited messaging-socket capability into the owning
284
- * session's UUID. The session need not hold a route yet: its route installs
285
- * on its first send, and the service compares the claimed alias against
286
- * discovery for that UUID.
287
- */
288
- async resolveReplyAddress(address) {
289
- this.assertReady();
290
- const resolved = await this.peer.resolveReplyAddress(address);
291
- if (resolved.kind !== "interactive" && resolved.kind !== "bg") {
292
- throw new BridgeError("CLAUDE_REPLY_ROUTE_MISMATCH", "The reply capability does not belong to a Claude session.");
293
- }
294
- return { routeHandle: resolved.targetId };
295
- }
296
- async advertiseNativeSourcePeer(input) {
297
- await this.helpers().advertise(input);
298
- }
299
- async unadvertiseNativeSourcePeer(alias) {
300
- await this.helpers().unadvertise(alias);
301
- }
302
- async updateNativeSourcePeerStatus(alias, status) {
303
- await this.helpers().updateStatus(alias, status);
304
- }
305
- async updateNativeInboundStatus(receiptHandle, status, diagnosticCode) {
306
- await this.helpers().updateInboundStatus(receiptHandle, status, diagnosticCode);
307
- }
308
- async notifyNativeInboundProgress(receiptHandle, progress) {
309
- await this.helpers().notifyInboundProgress(receiptHandle, progress);
310
- }
311
- async releaseNativeInboundReceipt(receiptHandle) {
312
- return await this.helpers().releaseInboundReceipt(receiptHandle);
313
- }
314
- async dispatch(input) {
315
- if (!this.initialized ||
316
- this.closed ||
317
- !sameEndpoint(input.binding, this.identity)) {
318
- return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
319
- }
320
- if (input.authorization === "selected_route") {
321
- const logical = this.selected.get(input.binding.routeHandle);
322
- if (logical === undefined ||
323
- logical.alias !== input.targetAlias ||
324
- logical.registrationId !== input.binding.registrationId) {
325
- return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
326
- }
327
- logical.dispatchEpoch += 1;
328
- logical.observationDirty = true;
329
- this.emitClaudeRouteObservation(input.binding.routeHandle, "busy");
330
- }
331
- let prepared;
332
- try {
333
- const selected = this.selected.get(input.binding.routeHandle);
334
- prepared = await this.helpers().prepareDispatch({
335
- sourceAlias: input.sourceAlias,
336
- sourceProvider: input.sourceProvider,
337
- targetAlias: input.targetAlias,
338
- conversationId: input.conversationId,
339
- binding: input.binding,
340
- authorization: input.authorization,
341
- messageId: input.messageId,
342
- text: input.text,
343
- expectsReply: input.expectsReply,
344
- deadlineAt: input.deadlineAt,
345
- ...(selected?.alias === undefined ? {} : { selectedAlias: selected.alias }),
346
- ...(input.authorization !== "selected_route" || selected?.stateRoot === undefined
347
- ? {}
348
- : { stateRoot: selected.stateRoot }),
349
- });
350
- }
351
- catch (error) {
352
- if (error instanceof BridgeError &&
353
- CLAUDE_CLEAN_PREWRITE_RETRY_CODES.has(error.code)) {
354
- this.emitClaudeRouteObservation(input.binding.routeHandle, "busy", error.code);
355
- }
356
- return claudeCleanPrewriteResult(error);
357
- }
358
- const evidence = {
359
- attemptId: input.attemptId,
360
- kind: "claude_mailbox",
361
- bodyBytes: Buffer.byteLength(input.text, "utf8"),
362
- bodySha256: createHash("sha256").update(input.text).digest("hex"),
363
- frameBytes: prepared.frameBytes,
364
- sha256: prepared.sha256,
365
- };
366
- let authorized;
367
- try {
368
- authorized = await input.authorizeWrite(evidence);
369
- }
370
- catch {
371
- await cancelPreparedClaude(prepared);
372
- return {
373
- state: "ambiguous",
374
- safeErrorCode: "WRITE_AUTHORIZATION_UNCERTAIN",
375
- };
376
- }
377
- if (!authorized) {
378
- await cancelPreparedClaude(prepared);
379
- return {
380
- state: "failed",
381
- safeErrorCode: "WRITE_AUTHORIZATION_DENIED",
382
- };
383
- }
384
- try {
385
- // Authorization is the write-authorization linearization point. Invoke the exact
386
- // prepared mailbox write in the same continuation with no intervening
387
- // await. Claude has no separate acceptance phase: confirmed mailbox
388
- // completion is terminal delivered.
389
- const operation = prepared.perform();
390
- const result = await operation;
391
- if (result.state === "deferred") {
392
- return {
393
- state: "ambiguous",
394
- safeErrorCode: "CLAUDE_PREPARED_RESULT_INVALID",
395
- };
396
- }
397
- return result.state === "expired"
398
- ? { state: "failed", safeErrorCode: "MESSAGE_EXPIRED" }
399
- : result;
400
- }
401
- catch (error) {
402
- return claudePostAuthorizationResult(error);
403
- }
404
- }
405
- async releaseRoute(routeHandle) {
406
- this.selected.delete(routeHandle);
407
- this.discovered.delete(routeHandle);
408
- if (this.selected.size === 0 && this.monitorTimer !== undefined) {
409
- clearTimeout(this.monitorTimer);
410
- this.monitorTimer = undefined;
411
- }
412
- }
413
- async close() {
414
- if (this.closed)
415
- return;
416
- this.closed = true;
417
- try {
418
- await this.nativeHelpers?.close();
419
- await this.peer.close();
420
- }
421
- finally {
422
- this.selected.clear();
423
- this.discovered.clear();
424
- if (this.monitorTimer !== undefined)
425
- clearTimeout(this.monitorTimer);
426
- this.monitorTimer = undefined;
427
- this.callbacks = undefined;
428
- }
429
- }
430
- assertReady() {
431
- if (!this.initialized || this.closed) {
432
- throw new BridgeError("CLAUDE_PROVIDER_UNAVAILABLE", "The local Claude provider is not available.", true);
433
- }
434
- }
435
- helpers() {
436
- this.assertReady();
437
- if (this.nativeHelpers === undefined) {
438
- throw new BridgeError("CLAUDE_NATIVE_HELPER_UNAVAILABLE", "The supervised native Claude helper is unavailable.", true);
439
- }
440
- return this.nativeHelpers;
441
- }
442
- async refreshClaudeDiscovery() {
443
- if (this.discoveryInFlight !== undefined) {
444
- return await this.discoveryInFlight;
445
- }
446
- const operation = this.refreshClaudeDiscoveryOnce();
447
- this.discoveryInFlight = operation;
448
- try {
449
- return await operation;
450
- }
451
- finally {
452
- if (this.discoveryInFlight === operation) {
453
- this.discoveryInFlight = undefined;
454
- }
455
- }
456
- }
457
- async refreshClaudeDiscoveryOnce() {
458
- const dispatchEpochAtStart = new Map();
459
- for (const [routeHandle, route] of this.selected) {
460
- dispatchEpochAtStart.set(routeHandle, route.dispatchEpoch);
461
- }
462
- let discovery;
463
- try {
464
- discovery = await this.peer.discover();
465
- }
466
- catch (error) {
467
- this.registryObservation = unavailableClaudeRegistryObservation();
468
- throw error;
469
- }
470
- const registry = claudeRegistryObservation(discovery);
471
- this.registryObservation = registry;
472
- this.discovered.clear();
473
- const rows = [];
474
- for (const peer of discovery.peers) {
475
- if ((peer.kind !== "interactive" && peer.kind !== "bg") ||
476
- !NATIVE_CLAUDE_NAME.test(peer.alias)) {
477
- continue;
478
- }
479
- const alias = `${peer.alias}@${this.identity.hostId}`;
480
- if (!PUBLIC_ALIAS.test(alias))
481
- continue;
482
- const row = {
483
- alias,
484
- routeHandle: peer.targetId,
485
- kind: peer.kind,
486
- state: claudeRouteState(peer),
487
- };
488
- this.discovered.set(peer.targetId, row);
489
- rows.push(row);
490
- }
491
- for (const [routeHandle, selected] of this.selected) {
492
- const row = this.discovered.get(routeHandle);
493
- if (row === undefined) {
494
- this.emitClaudeRouteObservation(routeHandle, "busy", "CLAUDE_PEER_NOT_OBSERVED", true);
495
- }
496
- else {
497
- if (!discovery.truncated && row.alias !== selected.alias) {
498
- // A Claude rename changes only the live name index. The native
499
- // session UUID remains the selected logical route. An incomplete
500
- // scan is display evidence only and cannot rename dispatch authority.
501
- selected.alias = row.alias;
502
- }
503
- if (row.state === "idle" &&
504
- (dispatchEpochAtStart.get(routeHandle) !==
505
- selected.dispatchEpoch)) {
506
- // Never publish an idle sample from a discovery that overlapped the
507
- // exact mailbox write. A wholly post-terminal discovery may clear
508
- // the dirty epoch.
509
- continue;
510
- }
511
- this.emitClaudeRouteObservation(routeHandle, row.state, undefined, true);
512
- }
513
- }
514
- return { peers: rows, complete: !discovery.truncated, registry };
515
- }
516
- emitClaudeRouteObservation(routeHandle, state, safeErrorCode, authoritative = false) {
517
- const signature = `${state}:${safeErrorCode ?? ""}`;
518
- const selected = this.selected.get(routeHandle);
519
- const mustPublish = authoritative && selected?.observationDirty === true;
520
- if (selected !== undefined && mustPublish)
521
- selected.observationDirty = false;
522
- if (!mustPublish && selected?.observation === signature) {
523
- return;
524
- }
525
- if (selected !== undefined)
526
- selected.observation = signature;
527
- const callbacks = this.callbacks;
528
- const registrationId = selected?.registrationId;
529
- if (callbacks === undefined ||
530
- this.closed ||
531
- registrationId === undefined) {
532
- return;
533
- }
534
- invokeCallback(() => callbacks.onRouteState({
535
- route: {
536
- ...this.identity,
537
- routeHandle,
538
- registrationId,
539
- },
540
- observedAt: new Date(this.now()).toISOString(),
541
- state,
542
- ...(safeErrorCode === undefined ? {} : { safeErrorCode }),
543
- }));
544
- }
545
- scheduleClaudeMonitor() {
546
- if (this.closed ||
547
- this.selected.size === 0 ||
548
- this.monitorTimer !== undefined) {
549
- return;
550
- }
551
- this.monitorTimer = setTimeout(() => {
552
- this.monitorTimer = undefined;
553
- void this.refreshClaudeDiscovery()
554
- .catch(() => {
555
- for (const routeHandle of this.selected.keys()) {
556
- this.emitClaudeRouteObservation(routeHandle, "busy", "CLAUDE_DISCOVERY_UNAVAILABLE");
557
- }
558
- })
559
- .finally(() => this.scheduleClaudeMonitor());
560
- }, this.discoveryPollMs);
561
- this.monitorTimer.unref();
562
- }
563
- }
564
- export function createLocalClaudeGatewayProvider(options) {
565
- return new LocalClaudeGatewayProvider(options);
566
- }
567
- function boundedProviderOption(value, fallback, maximum) {
568
- const candidate = value ?? fallback;
569
- if (!Number.isSafeInteger(candidate) ||
570
- candidate <= 0 ||
571
- candidate > maximum) {
572
- throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "The local Codex provider has an invalid bounded option.");
573
- }
574
- return candidate;
575
- }
576
- function cleanCodexResult(result) {
577
- if (result.safeErrorCode === "MESSAGE_EXPIRED") {
578
- return { state: "expired", safeErrorCode: "MESSAGE_EXPIRED" };
579
- }
580
- if (CODEX_CLEAN_RETRY_CODES.has(result.safeErrorCode)) {
581
- return { state: "deferred", safeErrorCode: result.safeErrorCode };
582
- }
583
- return { state: "failed", safeErrorCode: result.safeErrorCode };
584
- }
585
- function terminalCodexResult(result, expectsReply) {
586
- if (result.outcome === "failed") {
587
- return { state: "failed", safeErrorCode: "CODEX_TURN_FAILED" };
588
- }
589
- if (result.outcome === "interrupted") {
590
- return { state: "cancelled", safeErrorCode: "CODEX_TURN_INTERRUPTED" };
591
- }
592
- return {
593
- state: "delivered",
594
- ...(result.replyCode === "REPLY_TOO_LARGE"
595
- ? { safeErrorCode: "CODEX_REPLY_TOO_LARGE" }
596
- : {}),
597
- ...(expectsReply && result.replyText !== null
598
- ? { replyText: result.replyText }
599
- : {}),
600
- };
601
- }
602
- function mapCodexStartResult(result, expectsReply) {
603
- if (result.phase === "clean")
604
- return cleanCodexResult(result);
605
- if (result.phase === "armed") {
606
- return { state: "ambiguous", safeErrorCode: result.safeErrorCode };
607
- }
608
- if (result.phase === "accepted") {
609
- return { state: "unconfirmed", safeErrorCode: result.safeErrorCode };
610
- }
611
- return terminalCodexResult(result, expectsReply);
612
- }
613
- function mapCodexSteerResult(result) {
614
- if (result.phase === "clean")
615
- return cleanCodexResult(result);
616
- if (result.phase === "armed") {
617
- return { state: "ambiguous", safeErrorCode: result.safeErrorCode };
618
- }
619
- return { state: "delivered" };
620
- }
621
- /**
622
- * Thin logical Codex adapter. Registration and construction perform no App
623
- * Server I/O. Each semantic operation owns a fresh attested transport, while
624
- * the independent observer publishes only best-effort in-memory freshness.
625
- */
626
- export class LocalCodexGatewayProvider {
627
- hostId;
628
- operation;
629
- observationPollMs;
630
- observationTimeoutMs;
631
- observationBackoffMaxMs;
632
- maxObservedRoutes;
633
- now;
634
- timers;
635
- observedRoutes = new Map();
636
- activeTurns = new Map();
637
- inFlightOperations = new Set();
638
- operationControllers = new Set();
639
- callbacks;
640
- observationTimer;
641
- observationInFlight;
642
- observationController;
643
- initialized = false;
644
- closing = false;
645
- closed = false;
646
- constructor(options) {
647
- if (!isAttestedGatewayNodeInventory(options.nodeInventory, options.hostId) ||
648
- options.operation === undefined ||
649
- typeof options.operation.execute !== "function") {
650
- throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "The stateless Codex provider requires the exact attested local host.");
651
- }
652
- this.hostId = options.hostId;
653
- this.operation = options.operation;
654
- this.observationPollMs = boundedProviderOption(options.observationPollMs, DEFAULT_CODEX_OBSERVATION_POLL_MS, 60_000);
655
- this.observationTimeoutMs = boundedProviderOption(options.observationTimeoutMs, DEFAULT_CODEX_OBSERVATION_TIMEOUT_MS, 60_000);
656
- this.observationBackoffMaxMs = boundedProviderOption(options.observationBackoffMaxMs, DEFAULT_CODEX_OBSERVATION_BACKOFF_MAX_MS, 5 * 60_000);
657
- if (this.observationPollMs > this.observationBackoffMaxMs) {
658
- throw new BridgeError("INVALID_GATEWAY_PROVIDER_CONFIGURATION", "Codex observer backoff must cover its polling interval.");
659
- }
660
- this.maxObservedRoutes = boundedProviderOption(options.maxObservedRoutes, MAX_CODEX_OBSERVED_ROUTES, 256);
661
- this.now = options.now ?? (() => new Date());
662
- this.timers = options.timers ?? {
663
- setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
664
- clearTimeout: (timer) => clearTimeout(timer),
665
- };
666
- }
667
- get identity() {
668
- return { provider: "codex", hostId: this.hostId };
669
- }
670
- get protocol() {
671
- return "codex-app-server";
672
- }
673
- get protocolVersion() {
674
- return "stateless-v1";
675
- }
676
- async initialize(callbacks) {
677
- if (this.initialized || this.closing || this.closed) {
678
- throw new BridgeError("CODEX_PROVIDER_INITIALIZATION_REJECTED", "The stateless Codex provider cannot initialize in this state.");
679
- }
680
- this.callbacks = callbacks;
681
- this.initialized = true;
682
- this.scheduleObservation();
683
- return { health: "healthy" };
684
- }
685
- observeLogicalRoute(input) {
686
- if (!this.initialized ||
687
- this.closing ||
688
- this.closed ||
689
- !PUBLIC_ALIAS.test(input.alias) ||
690
- !input.alias.endsWith("@" + this.hostId) ||
691
- !OPAQUE_ROUTE.test(input.routeHandle) ||
692
- !OPAQUE_ROUTE.test(input.registrationId)) {
693
- return;
694
- }
695
- const existing = this.observedRoutes.get(input.registrationId);
696
- if (existing === undefined &&
697
- this.observedRoutes.size >= this.maxObservedRoutes) {
698
- return;
699
- }
700
- this.observedRoutes.set(input.registrationId, {
701
- ...input,
702
- attempt: 0,
703
- nextAt: this.now().getTime(),
704
- });
705
- this.scheduleObservation();
706
- }
707
- forgetLogicalRoute(registrationId) {
708
- this.observedRoutes.delete(registrationId);
709
- this.activeTurns.delete(registrationId);
710
- this.scheduleObservation();
711
- }
712
- async dispatch(input) {
713
- if (!this.initialized ||
714
- this.closing ||
715
- this.closed ||
716
- input.authorization !== "selected_route" ||
717
- input.binding.provider !== "codex" ||
718
- input.binding.hostId !== this.hostId ||
719
- !PUBLIC_ALIAS.test(input.targetAlias) ||
720
- !input.targetAlias.endsWith("@" + this.hostId)) {
721
- return { state: "failed", safeErrorCode: "CODEX_ROUTE_UNAVAILABLE" };
722
- }
723
- let content;
724
- try {
725
- content = composeProvenanceEnvelope({
726
- sourceProvider: input.sourceProvider,
727
- recipientProvider: "codex",
728
- sourceAlias: input.sourceAlias,
729
- targetAlias: input.targetAlias,
730
- conversationId: input.conversationId,
731
- body: input.text,
732
- ...(input.steer === true && input.queuedAhead !== undefined
733
- ? { queuedAhead: input.queuedAhead }
734
- : {}),
735
- });
736
- }
737
- catch (error) {
738
- return {
739
- state: "failed",
740
- safeErrorCode: error instanceof BridgeError &&
741
- error.code === "PROVENANCE_ENVELOPE_TOO_LARGE"
742
- ? error.code
743
- : "PROVENANCE_ENVELOPE_INVALID",
744
- };
745
- }
746
- return input.steer === true
747
- ? this.dispatchSteer(input, content)
748
- : this.dispatchStart(input, content);
749
- }
750
- async dispatchSteer(input, content) {
751
- const active = this.activeTurns.get(input.binding.registrationId);
752
- if (active === undefined ||
753
- active.alias !== input.targetAlias ||
754
- active.routeHandle !== input.binding.routeHandle) {
755
- return { state: "deferred", safeErrorCode: "ROUTE_BUSY" };
756
- }
757
- const steering = active.accepted.steer({
758
- attemptId: input.attemptId,
759
- authorizeWrite: async (evidence) => await input.authorizeWrite({
760
- attemptId: input.attemptId,
761
- kind: "codex_turn_steer",
762
- bodyBytes: Buffer.byteLength(input.text, "utf8"),
763
- bodySha256: createHash("sha256").update(input.text).digest("hex"),
764
- frameBytes: evidence.frameBytes,
765
- sha256: evidence.sha256,
766
- }),
767
- deadlineAt: input.deadlineAt,
768
- text: content,
769
- });
770
- this.inFlightOperations.add(steering);
771
- try {
772
- return mapCodexSteerResult(await steering);
773
- }
774
- finally {
775
- this.inFlightOperations.delete(steering);
776
- }
777
- }
778
- async dispatchStart(input, content) {
779
- if (this.activeTurns.has(input.binding.registrationId)) {
780
- return { state: "deferred", safeErrorCode: "ROUTE_BUSY" };
781
- }
782
- let accepted;
783
- const controller = new AbortController();
784
- this.operationControllers.add(controller);
785
- const operation = this.operation.execute({
786
- attemptId: input.attemptId,
787
- authorizeWrite: async (evidence) => await input.authorizeWrite({
788
- attemptId: input.attemptId,
789
- kind: "codex_turn_start",
790
- bodyBytes: Buffer.byteLength(input.text, "utf8"),
791
- bodySha256: createHash("sha256").update(input.text).digest("hex"),
792
- frameBytes: evidence.frameBytes,
793
- sha256: evidence.sha256,
794
- }),
795
- deadlineAt: input.deadlineAt,
796
- kind: "start",
797
- signal: controller.signal,
798
- route: {
799
- alias: input.targetAlias,
800
- hostId: input.binding.hostId,
801
- registrationId: input.binding.registrationId,
802
- threadId: input.binding.routeHandle,
803
- },
804
- text: content,
805
- onAccepted: async (current) => {
806
- if (this.closing ||
807
- this.closed ||
808
- current.attemptId !== input.attemptId ||
809
- this.activeTurns.has(input.binding.registrationId)) {
810
- throw new BridgeError("ACCEPTANCE_UNCONFIRMED", "The exact Codex attempt cannot acquire its active-turn slot.");
811
- }
812
- await input.onAccepted({ attemptId: input.attemptId });
813
- accepted = current;
814
- this.activeTurns.set(input.binding.registrationId, {
815
- attemptId: input.attemptId,
816
- accepted: current,
817
- alias: input.targetAlias,
818
- routeHandle: input.binding.routeHandle,
819
- });
820
- },
821
- });
822
- this.inFlightOperations.add(operation);
823
- try {
824
- const result = await operation;
825
- this.publishOperationObservation(input, result);
826
- return mapCodexStartResult(result, input.expectsReply);
827
- }
828
- finally {
829
- this.inFlightOperations.delete(operation);
830
- this.operationControllers.delete(controller);
831
- if (accepted !== undefined &&
832
- this.activeTurns.get(input.binding.registrationId)?.attemptId ===
833
- input.attemptId) {
834
- this.activeTurns.delete(input.binding.registrationId);
835
- }
836
- }
837
- }
838
- async close() {
839
- if (this.closed || this.closing)
840
- return;
841
- this.closing = true;
842
- if (this.observationTimer !== undefined) {
843
- this.timers.clearTimeout(this.observationTimer);
844
- this.observationTimer = undefined;
845
- }
846
- this.observationController?.abort();
847
- for (const controller of this.operationControllers)
848
- controller.abort();
849
- await this.observationInFlight?.catch(() => undefined);
850
- for (const operation of this.inFlightOperations) {
851
- void operation.catch(() => undefined);
852
- }
853
- this.operationControllers.clear();
854
- this.activeTurns.clear();
855
- this.observedRoutes.clear();
856
- this.callbacks = undefined;
857
- this.initialized = false;
858
- this.closed = true;
859
- this.closing = false;
860
- }
861
- publishOperationObservation(input, result) {
862
- const state = !result.cleanupConfirmed
863
- ? { state: "unobserved", safeErrorCode: "CLEANUP_FAILED" }
864
- : result.phase === "terminal"
865
- ? { state: "idle" }
866
- : result.safeErrorCode === "ROUTE_BUSY"
867
- ? { state: "busy" }
868
- : result.safeErrorCode === "APPROVAL_REQUIRED"
869
- ? { state: "awaiting_approval" }
870
- : result.safeErrorCode === "THREAD_NOT_OBSERVED"
871
- ? {
872
- state: "unobserved",
873
- safeErrorCode: "THREAD_NOT_OBSERVED",
874
- }
875
- : {
876
- state: "unobserved",
877
- safeErrorCode: "CODEX_OBSERVER_UNAVAILABLE",
878
- };
879
- this.emitObservation({
880
- alias: input.targetAlias,
881
- registrationId: input.binding.registrationId,
882
- routeHandle: input.binding.routeHandle,
883
- }, state);
884
- }
885
- scheduleObservation() {
886
- if (!this.initialized ||
887
- this.closing ||
888
- this.closed ||
889
- this.observationInFlight !== undefined) {
890
- return;
891
- }
892
- if (this.observationTimer !== undefined) {
893
- this.timers.clearTimeout(this.observationTimer);
894
- this.observationTimer = undefined;
895
- }
896
- const next = [...this.observedRoutes.values()].reduce((selected, candidate) => selected === undefined || candidate.nextAt < selected.nextAt
897
- ? candidate
898
- : selected, undefined);
899
- if (next === undefined)
900
- return;
901
- const delay = Math.max(0, next.nextAt - this.now().getTime());
902
- const timer = this.timers.setTimeout(() => {
903
- if (this.observationTimer !== timer)
904
- return;
905
- this.observationTimer = undefined;
906
- const operation = this.runObservation(next);
907
- this.observationInFlight = operation;
908
- void operation.finally(() => {
909
- if (this.observationInFlight === operation) {
910
- this.observationInFlight = undefined;
911
- }
912
- this.scheduleObservation();
913
- });
914
- }, delay);
915
- timer.unref();
916
- this.observationTimer = timer;
917
- }
918
- async runObservation(route) {
919
- const controller = new AbortController();
920
- this.observationController = controller;
921
- let observation;
922
- const timeout = this.timers.setTimeout(() => controller.abort(), this.observationTimeoutMs);
923
- timeout.unref();
924
- try {
925
- observation = await this.operation.observe({
926
- alias: route.alias,
927
- hostId: this.hostId,
928
- registrationId: route.registrationId,
929
- threadId: route.routeHandle,
930
- }, controller.signal);
931
- }
932
- finally {
933
- this.timers.clearTimeout(timeout);
934
- if (this.observationController === controller) {
935
- this.observationController = undefined;
936
- }
937
- }
938
- const current = this.observedRoutes.get(route.registrationId);
939
- if (current !== route || this.closing || this.closed)
940
- return;
941
- const failed = observation.state === "unobserved";
942
- current.attempt = failed ? Math.min(current.attempt + 1, 16) : 0;
943
- const delay = failed
944
- ? Math.min(this.observationBackoffMaxMs, this.observationPollMs * 2 ** Math.max(0, current.attempt - 1))
945
- : this.observationPollMs;
946
- current.nextAt = this.now().getTime() + delay;
947
- this.emitObservation(current, observation);
948
- }
949
- emitObservation(route, observation) {
950
- const current = this.observedRoutes.get(route.registrationId);
951
- if (this.callbacks === undefined ||
952
- current === undefined ||
953
- current.alias !== route.alias ||
954
- current.routeHandle !== route.routeHandle) {
955
- return;
956
- }
957
- invokeCallback(() => this.callbacks.onRouteState({
958
- route: {
959
- provider: "codex",
960
- hostId: this.hostId,
961
- routeHandle: route.routeHandle,
962
- registrationId: route.registrationId,
963
- },
964
- state: observation.state,
965
- observedAt: this.now().toISOString(),
966
- ...(observation.safeErrorCode === undefined
967
- ? {}
968
- : { safeErrorCode: observation.safeErrorCode }),
969
- }));
970
- }
971
- }
972
- export function createLocalCodexGatewayProvider(options) {
973
- return new LocalCodexGatewayProvider(options);
974
- }
975
- //# sourceMappingURL=providers.js.map