agent-embassy 1.4.1 → 1.6.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 (86) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/CONTRIBUTING.md +69 -6
  3. package/README.md +8 -5
  4. package/README.zh-CN.md +6 -4
  5. package/SECURITY.md +152 -27
  6. package/dist/src/gateway/claude-peer.d.ts +7 -4
  7. package/dist/src/gateway/claude-peer.js +80 -36
  8. package/dist/src/gateway/claude-peer.js.map +1 -1
  9. package/dist/src/gateway/claude-runtime.d.ts +4 -0
  10. package/dist/src/gateway/claude-runtime.js +50 -39
  11. package/dist/src/gateway/claude-runtime.js.map +1 -1
  12. package/dist/src/gateway/cli-copy.d.ts +1 -1
  13. package/dist/src/gateway/cli-copy.en.d.ts +1 -1
  14. package/dist/src/gateway/cli-copy.en.js +1 -1
  15. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  16. package/dist/src/gateway/cli-copy.js +1 -1
  17. package/dist/src/gateway/cli-copy.js.map +1 -1
  18. package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
  19. package/dist/src/gateway/cli-copy.zh-CN.js +1 -1
  20. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  21. package/dist/src/gateway/cli.d.ts +1 -1
  22. package/dist/src/gateway/cli.js +6 -7
  23. package/dist/src/gateway/cli.js.map +1 -1
  24. package/dist/src/gateway/codex-app-server.d.ts +44 -2
  25. package/dist/src/gateway/codex-app-server.js +536 -2
  26. package/dist/src/gateway/codex-app-server.js.map +1 -1
  27. package/dist/src/gateway/codex-local-transport.d.ts +10 -6
  28. package/dist/src/gateway/codex-local-transport.js +101 -56
  29. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  30. package/dist/src/gateway/compatibility.d.ts +42 -3
  31. package/dist/src/gateway/compatibility.js +190 -37
  32. package/dist/src/gateway/compatibility.js.map +1 -1
  33. package/dist/src/gateway/control.js +28 -30
  34. package/dist/src/gateway/control.js.map +1 -1
  35. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  36. package/dist/src/gateway/dashboard-copy.en.d.ts +42 -17
  37. package/dist/src/gateway/dashboard-copy.en.js +56 -31
  38. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  39. package/dist/src/gateway/dashboard-copy.js +42 -17
  40. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  41. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +42 -17
  42. package/dist/src/gateway/dashboard-copy.zh-CN.js +56 -31
  43. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  44. package/dist/src/gateway/dashboard-model.d.ts +199 -11
  45. package/dist/src/gateway/dashboard-model.js +478 -75
  46. package/dist/src/gateway/dashboard-model.js.map +1 -1
  47. package/dist/src/gateway/dashboard.d.ts +3 -0
  48. package/dist/src/gateway/dashboard.js +62 -137
  49. package/dist/src/gateway/dashboard.js.map +1 -1
  50. package/dist/src/gateway/deepseek-detect.d.ts +13 -0
  51. package/dist/src/gateway/deepseek-detect.js +125 -0
  52. package/dist/src/gateway/deepseek-detect.js.map +1 -0
  53. package/dist/src/gateway/live-dashboard-app/app.js +158 -256
  54. package/dist/src/gateway/live-dashboard-assets.js +2 -1
  55. package/dist/src/gateway/live-dashboard-assets.js.map +1 -1
  56. package/dist/src/gateway/live-dashboard-stream.d.ts +2 -2
  57. package/dist/src/gateway/live-dashboard-stream.js +2 -2
  58. package/dist/src/gateway/live-dashboard-stream.js.map +1 -1
  59. package/dist/src/gateway/progress-watch-machine.d.ts +50 -64
  60. package/dist/src/gateway/progress-watch-machine.js +42 -133
  61. package/dist/src/gateway/progress-watch-machine.js.map +1 -1
  62. package/dist/src/gateway/provenance-envelope.d.ts +2 -0
  63. package/dist/src/gateway/provenance-envelope.js +13 -3
  64. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  65. package/dist/src/gateway/providers.d.ts +80 -16
  66. package/dist/src/gateway/providers.js +782 -115
  67. package/dist/src/gateway/providers.js.map +1 -1
  68. package/dist/src/gateway/server.d.ts +10 -5
  69. package/dist/src/gateway/server.js +100 -13
  70. package/dist/src/gateway/server.js.map +1 -1
  71. package/dist/src/gateway/service.d.ts +46 -3
  72. package/dist/src/gateway/service.js +363 -101
  73. package/dist/src/gateway/service.js.map +1 -1
  74. package/dist/src/gateway/store.d.ts +23 -28
  75. package/dist/src/gateway/store.js +479 -467
  76. package/dist/src/gateway/store.js.map +1 -1
  77. package/dist/src/gateway/types.d.ts +40 -12
  78. package/dist/src/gateway/types.js +145 -4
  79. package/dist/src/gateway/types.js.map +1 -1
  80. package/docs/CONFIGURATION.md +8 -8
  81. package/docs/CONFIGURATION.zh-CN.md +8 -8
  82. package/docs/DASHBOARD.md +35 -6
  83. package/docs/DASHBOARD.zh-CN.md +1 -1
  84. package/docs/GATEWAY-ARCHITECTURE.md +173 -61
  85. package/package.json +1 -1
  86. package/skills/embassy-peer/SKILL.md +2 -2
@@ -1,14 +1,15 @@
1
- import { createHash } from "node:crypto";
2
1
  import path from "node:path";
2
+ import { chmod, lstat, mkdir, mkdtemp, readdir, realpath, rmdir, } from "node:fs/promises";
3
3
  import { BridgeError } from "../errors.js";
4
4
  import { CLAUDE_PEER_COMPATIBILITY, ClaudePeerAdapter, } from "./claude-peer.js";
5
- import { certifiedCompatibilityVersions, compatibilityProbeNames, evaluateCompatibilityAttestation, sharesCompatibilityMajor, } from "./compatibility.js";
6
- import { CODEX_APP_SERVER_WRITABLE_VERSIONS, CodexAppServerConnector, CodexConnectorError, } from "./codex-app-server.js";
5
+ import { certifiedCompatibilityVersions, compatibilityProbeNames, evaluateCompatibilityAttestation, isCompatibilityAttestation, isCompatibilityVersion, sharesCompatibilityMajor, UNKNOWN_COMPATIBILITY_VERSION, } from "./compatibility.js";
6
+ import { CODEX_APP_SERVER_WRITABLE_VERSIONS, CodexAppServerConnector, CodexConnectorError, codexWriteProbeFailure, } from "./codex-app-server.js";
7
7
  import { LocalCodexTransportError } from "./codex-local-transport.js";
8
8
  import { ClaudeNativeHelperSupervisor, } from "./claude-helper-supervisor.js";
9
9
  import { isDashboardLocale } from "./locale.js";
10
10
  import { composeProvenanceEnvelope } from "./provenance-envelope.js";
11
11
  const LOCAL_HOST = "this-mac";
12
+ const STABLE_CLAUDE_ENDPOINT_GENERATION = "claude_local_endpoint";
12
13
  const NATIVE_CLAUDE_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
13
14
  const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
14
15
  const OPAQUE_ROUTE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
@@ -24,12 +25,39 @@ const DEFAULT_CODEX_RECOVERY_INITIAL_MS = 250;
24
25
  const DEFAULT_CODEX_RECOVERY_MAX_MS = 5_000;
25
26
  const MAX_CODEX_ENDPOINT_REFRESH_CANDIDATES = 3;
26
27
  const COMPATIBILITY_PROBE_THREAD_ID = "00000000-0000-7000-8000-000000000000";
28
+ const CODEX_WRITE_PROBE_DIRECTORY_PREFIX = ".codex-write-probe-";
29
+ const CODEX_WRITE_PROBE_ROOT = "write-probes";
30
+ const MAX_CODEX_WRITE_PROBE_ATTEMPTS = 16;
31
+ const CODEX_WRITE_PROBE_TRANSIENT_ERRNOS = new Set([
32
+ "EAGAIN", "EIO", "EMFILE", "ENFILE",
33
+ ]);
27
34
  const CLAUDE_CLEAN_PREWRITE_RETRY_CODES = new Set([
28
35
  "CLAUDE_PEER_TARGET_UNKNOWN",
29
36
  "CLAUDE_PEER_TARGET_STALE",
30
37
  "CLAUDE_PEER_TARGET_CHANGED",
31
38
  "CLAUDE_PEER_WORKSPACE_UNATTESTED",
32
39
  ]);
40
+ const CODEX_TRUST_FAILURE_CODES = new Set([
41
+ "HOME_INVALID",
42
+ "MANAGED_CODEX_INVALID",
43
+ "LOCAL_APP_SERVER_ENDPOINT_UNSAFE",
44
+ "APP_SERVER_VERSION_MISMATCH",
45
+ "ENDPOINT_GENERATION_CHANGED",
46
+ "CLEANUP_FAILED",
47
+ "FACTORY_CLOSED",
48
+ "INVALID_CONFIGURATION",
49
+ "CODEX_ROUTE_CLEANUP_FAILED",
50
+ "CODEX_PROVIDER_CLEANUP_FAILED",
51
+ "CODEX_PROVIDER_CLEANUP_AMBIGUOUS",
52
+ "CODEX_PROVIDER_CLEANUP_TIMEOUT",
53
+ ]);
54
+ function isCodexTrustFailure(error) {
55
+ return (typeof error === "object" &&
56
+ error !== null &&
57
+ "code" in error &&
58
+ typeof error.code === "string" &&
59
+ CODEX_TRUST_FAILURE_CODES.has(error.code));
60
+ }
33
61
  function claudeCleanPrewriteResult(error) {
34
62
  if (error instanceof BridgeError &&
35
63
  error.code === "CLAUDE_PEER_MESSAGE_EXPIRED") {
@@ -64,6 +92,99 @@ function passedProbe(name) {
64
92
  function failedProbe(name, safeErrorCode) {
65
93
  return { name, outcome: "fail", safeErrorCode };
66
94
  }
95
+ function systemErrorCode(error) {
96
+ return typeof error === "object" &&
97
+ error !== null &&
98
+ "code" in error &&
99
+ typeof error.code === "string"
100
+ ? error.code
101
+ : undefined;
102
+ }
103
+ function transientCodexWriteProbeError(error) {
104
+ return CODEX_WRITE_PROBE_TRANSIENT_ERRNOS.has(systemErrorCode(error) ?? "");
105
+ }
106
+ function safeCodexWriteProbeFilesystemFailure(error) {
107
+ return (transientCodexWriteProbeError(error) ||
108
+ systemErrorCode(error) === "ENOTEMPTY" ||
109
+ (error instanceof BridgeError &&
110
+ error.code === "CODEX_WRITE_PROBE_THREAD_SETUP_FAILED"));
111
+ }
112
+ async function attestCodexWriteProbeDirectory(directory) {
113
+ try {
114
+ const resolved = path.resolve(directory);
115
+ const [metadata, canonical, entries] = await Promise.all([
116
+ lstat(resolved, { bigint: true }),
117
+ realpath(resolved),
118
+ readdir(resolved),
119
+ ]);
120
+ const uid = process.getuid?.();
121
+ if (uid === undefined ||
122
+ !metadata.isDirectory() ||
123
+ canonical !== resolved ||
124
+ metadata.uid !== BigInt(uid) ||
125
+ (metadata.mode & 511n) !== 448n) {
126
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The disposable Codex probe directory crossed its ownership, mode, or canonical-path boundary.");
127
+ }
128
+ if (entries.length !== 0) {
129
+ throw new BridgeError("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED", "The disposable Codex probe directory was not empty.");
130
+ }
131
+ return {
132
+ dev: metadata.dev,
133
+ ino: metadata.ino,
134
+ mode: metadata.mode & 511n,
135
+ mtimeNs: metadata.mtimeNs,
136
+ uid: metadata.uid,
137
+ };
138
+ }
139
+ catch (error) {
140
+ if (error instanceof BridgeError)
141
+ throw error;
142
+ if (transientCodexWriteProbeError(error)) {
143
+ throw new BridgeError("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED", "A transient filesystem error prevented disposable probe attestation.");
144
+ }
145
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The disposable Codex probe directory could not be attested safely.");
146
+ }
147
+ }
148
+ async function attestCodexWriteProbeRoot(root) {
149
+ try {
150
+ const resolved = path.resolve(root);
151
+ const [metadata, canonical] = await Promise.all([
152
+ lstat(resolved, { bigint: true }),
153
+ realpath(resolved),
154
+ ]);
155
+ const uid = process.getuid?.();
156
+ if (uid === undefined ||
157
+ !metadata.isDirectory() ||
158
+ canonical !== resolved ||
159
+ metadata.uid !== BigInt(uid) ||
160
+ (metadata.mode & 511n) !== 448n) {
161
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "A controller-owned Codex probe root changed outside its exact owned-directory boundary.");
162
+ }
163
+ return {
164
+ dev: metadata.dev,
165
+ ino: metadata.ino,
166
+ mode: metadata.mode & 511n,
167
+ uid: metadata.uid,
168
+ };
169
+ }
170
+ catch (error) {
171
+ if (error instanceof BridgeError)
172
+ throw error;
173
+ if (transientCodexWriteProbeError(error)) {
174
+ return null;
175
+ }
176
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "A controller-owned Codex probe root could not be attested.");
177
+ }
178
+ }
179
+ function sameCodexWriteProbeDirectory(before, after) {
180
+ return sameCodexWriteProbeRoot(before, after) && before.mtimeNs === after.mtimeNs;
181
+ }
182
+ function sameCodexWriteProbeRoot(before, after) {
183
+ return (before.dev === after.dev &&
184
+ before.ino === after.ino &&
185
+ before.mode === after.mode &&
186
+ before.uid === after.uid);
187
+ }
67
188
  function exactLocalHost(hostId) {
68
189
  if ((hostId ?? LOCAL_HOST) !== LOCAL_HOST) {
69
190
  throw new BridgeError("GATEWAY_REMOTE_PROVIDER_DISABLED", "This provider is restricted to the exact local host boundary.");
@@ -105,33 +226,184 @@ function invokeCallback(operation) {
105
226
  }
106
227
  }
107
228
  function validateAttestedClaudeRuntime(runtime) {
108
- if (runtime.claudeCodeVersion !==
109
- CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion ||
110
- [
111
- runtime.claudeExecutable,
112
- runtime.sessionsDir,
113
- runtime.socketDir,
114
- ].some((value) => !path.isAbsolute(value) ||
115
- path.normalize(value) !== value ||
116
- value.includes("\0"))) {
117
- throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "The local Claude provider requires exact pinned runtime evidence.");
118
- }
229
+ if (runtime.versionEvidenceFailure !== undefined) {
230
+ throw new BridgeError(runtime.versionEvidenceFailure, "The local Claude provider requires bounded, internally consistent runtime version evidence.");
231
+ }
232
+ const majorEvidence = runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
233
+ ? runtime.launcherVersionEvidence
234
+ : runtime.claudeCodeVersion;
235
+ if (majorEvidence === undefined ||
236
+ !sharesCompatibilityMajor(majorEvidence, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion)) {
237
+ throw new BridgeError(majorEvidence === undefined
238
+ ? "CLAUDE_VERSION_UNPARSEABLE"
239
+ : "CLAUDE_PEER_VERSION_UNSUPPORTED", "The local Claude provider requires a positively observed supported runtime major.");
240
+ }
241
+ validateClaudeRuntimePathEvidence(runtime);
119
242
  }
120
- function claudeEndpointGeneration(runtime) {
121
- return `claude_${createHash("sha256")
122
- .update(runtime.claudeCodeVersion)
123
- .update("\0")
124
- .update(runtime.claudeExecutable)
125
- .update("\0")
126
- .update(runtime.sessionsDir)
127
- .update("\0")
128
- .update(runtime.socketDir)
129
- .digest("hex")
130
- .slice(0, 32)}`;
243
+ function validateClaudeRuntimePathEvidence(runtime) {
244
+ if ([
245
+ runtime.claudeExecutable,
246
+ runtime.sessionsDir,
247
+ runtime.socketDir,
248
+ ].some((value) => !path.isAbsolute(value) ||
249
+ path.normalize(value) !== value ||
250
+ value.includes("\0"))) {
251
+ throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "The local Claude provider requires bounded absolute runtime evidence.");
252
+ }
131
253
  }
132
254
  function claudeRouteState(peer) {
133
255
  return peer.status === "idle" ? "idle" : "busy";
134
256
  }
257
+ function claudeRegistryObservation(discovery) {
258
+ const rejected = Object.entries(discovery.rejected)
259
+ .filter((entry) => Number.isSafeInteger(entry[1]) && entry[1] > 0)
260
+ .sort(([left], [right]) => left.localeCompare(right))
261
+ .map(([safeErrorCode, count]) => Object.freeze({ safeErrorCode, count }));
262
+ return Object.freeze({
263
+ entriesScanned: discovery.entriesScanned,
264
+ parseableRecords: discovery.parseableRecords,
265
+ rejected: Object.freeze(rejected),
266
+ });
267
+ }
268
+ function unavailableClaudeRegistryObservation() {
269
+ return Object.freeze({
270
+ entriesScanned: 0,
271
+ parseableRecords: 0,
272
+ rejected: Object.freeze([
273
+ Object.freeze({
274
+ safeErrorCode: "CLAUDE_REGISTRY_UNAVAILABLE",
275
+ count: 1,
276
+ }),
277
+ ]),
278
+ });
279
+ }
280
+ function isClaudeRegistryAvailabilityError(error) {
281
+ if (typeof error !== "object" || error === null || !("code" in error)) {
282
+ return false;
283
+ }
284
+ return (error.code === "ENOENT" ||
285
+ error.code === "EACCES" ||
286
+ error.code === "EPERM" ||
287
+ error.code === "UNSAFE_PEER_DIRECTORY");
288
+ }
289
+ /**
290
+ * A bounded observation-only surface for an installed provider generation
291
+ * whose protocol cannot be used safely. It deliberately performs no provider
292
+ * discovery, listener construction, route selection, or message write.
293
+ */
294
+ export class IncompatibleGatewayProvider {
295
+ identity;
296
+ protocol = "not-attested";
297
+ protocolVersion = "unknown";
298
+ version;
299
+ probes;
300
+ safeErrorCode;
301
+ initialized = false;
302
+ closed = false;
303
+ constructor(options) {
304
+ const attestation = evaluateCompatibilityAttestation({
305
+ surface: options.identity.provider,
306
+ version: options.version,
307
+ checkedAt: "2000-01-01T00:00:00.000Z",
308
+ certifiedVersions: certifiedCompatibilityVersions[options.identity.provider],
309
+ probes: options.probes,
310
+ });
311
+ if (attestation.tier !== "incompatible" ||
312
+ attestation.safeErrorCode !== options.safeErrorCode) {
313
+ throw new BridgeError("INCOMPATIBLE_PROVIDER_EVIDENCE_INVALID", "A disabled provider requires exact bounded incompatibility evidence.");
314
+ }
315
+ exactLocalHost(options.identity.hostId);
316
+ this.identity = Object.freeze({ ...options.identity });
317
+ this.version = options.version;
318
+ this.probes = Object.freeze(options.probes.map((probe) => Object.freeze({ ...probe })));
319
+ this.safeErrorCode = options.safeErrorCode;
320
+ }
321
+ compatibilitySurface() {
322
+ return { surface: this.identity.provider, version: this.version };
323
+ }
324
+ async runCompatibilityProbes() {
325
+ return this.probes;
326
+ }
327
+ acceptCompatibilityAttestation(attestation) {
328
+ if (!isCompatibilityAttestation(attestation) ||
329
+ attestation.surface !== this.identity.provider ||
330
+ attestation.version !== this.version ||
331
+ attestation.tier !== "incompatible" ||
332
+ attestation.safeErrorCode !== this.safeErrorCode ||
333
+ attestation.probes.length !== this.probes.length ||
334
+ attestation.probes.some((probe, index) => probe.name !== this.probes[index]?.name ||
335
+ probe.outcome !== this.probes[index]?.outcome ||
336
+ probe.safeErrorCode !== this.probes[index]?.safeErrorCode)) {
337
+ throw new BridgeError("INCOMPATIBLE_PROVIDER_ATTESTATION_MISMATCH", "The compatibility attestation does not match this disabled provider surface.");
338
+ }
339
+ }
340
+ async initialize(_callbacks) {
341
+ if (this.closed || this.initialized) {
342
+ throw new BridgeError("INCOMPATIBLE_PROVIDER_UNAVAILABLE", "The disabled provider surface cannot be initialized again.");
343
+ }
344
+ this.initialized = true;
345
+ return {
346
+ health: "degraded",
347
+ compatibility: "incompatible",
348
+ safeErrorCode: this.safeErrorCode,
349
+ };
350
+ }
351
+ async discoverClaudePeers() {
352
+ throw this.unavailable();
353
+ }
354
+ async selectRoute(_input) {
355
+ throw this.unavailable();
356
+ }
357
+ async assertWorkspaceDisjoint(_routeHandle, _stateRoot) {
358
+ throw this.unavailable();
359
+ }
360
+ async resolveReplyAddress(_address) {
361
+ throw this.unavailable();
362
+ }
363
+ async advertiseNativeCodexPeer(_input) {
364
+ throw this.unavailable();
365
+ }
366
+ async dispatch(_input) {
367
+ throw this.unavailable();
368
+ }
369
+ async releaseRoute(_routeHandle) { }
370
+ async close() {
371
+ this.closed = true;
372
+ }
373
+ unavailable() {
374
+ return new BridgeError(this.safeErrorCode, "This provider surface is observation-only because its compatibility boundary did not pass.");
375
+ }
376
+ }
377
+ export function createIncompatibleClaudeGatewayProvider(options) {
378
+ validateClaudeRuntimePathEvidence(options.runtime);
379
+ const [launcher, version, ...unobserved] = compatibilityProbeNames.claude;
380
+ return new IncompatibleGatewayProvider({
381
+ identity: {
382
+ provider: "claude",
383
+ hostId: LOCAL_HOST,
384
+ endpointGeneration: STABLE_CLAUDE_ENDPOINT_GENERATION,
385
+ },
386
+ version: options.version,
387
+ probes: [
388
+ passedProbe(launcher),
389
+ failedProbe(version, options.safeErrorCode),
390
+ ...unobserved.map((name) => failedProbe(name, options.safeErrorCode)),
391
+ ],
392
+ safeErrorCode: options.safeErrorCode,
393
+ });
394
+ }
395
+ export function createIncompatibleCodexGatewayProvider(options) {
396
+ const [installation, ...unobserved] = compatibilityProbeNames.codex;
397
+ return new IncompatibleGatewayProvider({
398
+ identity: options.identity,
399
+ version: options.version,
400
+ probes: [
401
+ passedProbe(installation),
402
+ ...unobserved.map((name) => failedProbe(name, options.safeErrorCode)),
403
+ ],
404
+ safeErrorCode: options.safeErrorCode,
405
+ });
406
+ }
135
407
  export class LocalClaudeGatewayProvider {
136
408
  identity;
137
409
  protocol = "claude-peer";
@@ -165,6 +437,7 @@ export class LocalClaudeGatewayProvider {
165
437
  /** Provider-owned terminal rejections that never enter the service queue. */
166
438
  rejectedNativeInbound = new Map();
167
439
  discoveryInFlight;
440
+ registryObservation;
168
441
  monitorTimer;
169
442
  /** Invocation-ordered registry mutations; the tail itself never rejects. */
170
443
  nativeCodexRegistrationTail = Promise.resolve();
@@ -175,10 +448,25 @@ export class LocalClaudeGatewayProvider {
175
448
  retiredNativeCodexListener;
176
449
  /** Fences discovery callbacks and monitor restarts across listener rotation. */
177
450
  nativeCodexSuccessionFreeze;
451
+ /** A completed failed probe set fences every authority-bearing operation. */
452
+ compatibilityBlocked = false;
453
+ compatibilityFailureCode;
178
454
  initialized = false;
179
455
  closed = false;
180
456
  constructor(options) {
181
457
  validateAttestedClaudeRuntime(options.runtime);
458
+ const compatibilityVersion = options.compatibilityVersion ??
459
+ (options.runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
460
+ ? options.runtime.launcherVersionEvidence
461
+ : options.runtime.claudeCodeVersion);
462
+ if (compatibilityVersion === undefined ||
463
+ !isCompatibilityVersion(compatibilityVersion) ||
464
+ (compatibilityVersion !== options.runtime.claudeCodeVersion &&
465
+ !(options.runtime.claudeCodeVersion ===
466
+ UNKNOWN_COMPATIBILITY_VERSION &&
467
+ options.runtime.launcherVersionEvidence === compatibilityVersion))) {
468
+ throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "Claude compatibility classification requires exact bounded launcher evidence.");
469
+ }
182
470
  if (options.locale !== undefined && !isDashboardLocale(options.locale)) {
183
471
  throw new BridgeError("DASHBOARD_LOCALE_UNSUPPORTED", "The Claude provider notice locale is unsupported.");
184
472
  }
@@ -186,12 +474,20 @@ export class LocalClaudeGatewayProvider {
186
474
  this.identity = {
187
475
  provider: "claude",
188
476
  hostId,
189
- endpointGeneration: claudeEndpointGeneration(options.runtime),
477
+ endpointGeneration: STABLE_CLAUDE_ENDPOINT_GENERATION,
190
478
  };
191
- this.runtimeVersion = options.runtime.claudeCodeVersion;
479
+ this.runtimeVersion = compatibilityVersion;
192
480
  this.maxPending = positiveBounded(options.maxPendingMessages, MAX_CLAUDE_PENDING, 4_096);
193
481
  this.discoveryPollMs = positiveBounded(options.discoveryPollMs, DEFAULT_CLAUDE_DISCOVERY_POLL_MS, 30_000);
194
482
  this.now = options.now ?? Date.now;
483
+ const protocolRuntime = {
484
+ claudeExecutable: options.runtime.claudeExecutable,
485
+ claudeCodeVersion: options.runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
486
+ ? options.runtime.launcherVersionEvidence
487
+ : options.runtime.claudeCodeVersion,
488
+ sessionsDir: options.runtime.sessionsDir,
489
+ socketDir: options.runtime.socketDir,
490
+ };
195
491
  this.peer = (options.peerFactory ??
196
492
  ((runtime, locale, deliveryNotices) => new ClaudePeerAdapter({
197
493
  sessionsDir: runtime.sessionsDir,
@@ -199,18 +495,18 @@ export class LocalClaudeGatewayProvider {
199
495
  attestedClaudeCodeVersion: runtime.claudeCodeVersion,
200
496
  locale,
201
497
  deliveryNotices,
202
- })))(options.runtime, options.locale ?? "en", options.deliveryNotices ?? "merged");
498
+ })))(protocolRuntime, options.locale ?? "en", options.deliveryNotices ?? "merged");
203
499
  this.nativeHelpers =
204
500
  options.nativeHelpers === undefined
205
501
  ? undefined
206
502
  : new ClaudeNativeHelperSupervisor({
207
503
  identity: this.identity,
208
- runtime: options.runtime,
504
+ runtime: protocolRuntime,
209
505
  locale: options.locale ?? "en",
210
506
  deliveryNotices: options.deliveryNotices ?? "merged",
211
507
  maxPendingMessages: this.maxPending,
212
508
  maxHelpers: positiveBounded(options.nativeHelpers.maxHelpers, 256, 128),
213
- callbacks: () => this.callbacks,
509
+ callbacks: () => this.compatibilityBlocked ? undefined : this.callbacks,
214
510
  ...(options.nativeHelpers.factory === undefined
215
511
  ? {}
216
512
  : { factory: options.nativeHelpers.factory }),
@@ -223,33 +519,49 @@ export class LocalClaudeGatewayProvider {
223
519
  const [launcher, version, registry, socket, protocol] = compatibilityProbeNames.claude;
224
520
  try {
225
521
  const discovery = await this.peer.discover();
226
- // Runtime discovery is deliberately per-record fail-safe: malformed,
227
- // dead, unsafe, or non-routable records are omitted rather than allowed
228
- // to poison the whole registry. Startup compatibility must use the same
229
- // boundary. Fail only when the bounded scan itself is incomplete, or a
230
- // nonempty registry contains no record with a parseable closed schema.
231
- const registryUnusable = discovery.truncated ||
232
- (discovery.entriesScanned > 0 && discovery.parseableRecords === 0);
522
+ this.registryObservation = claudeRegistryObservation(discovery);
233
523
  return [
234
524
  passedProbe(launcher),
235
525
  passedProbe(version),
236
- registryUnusable
237
- ? failedProbe(registry, "CLAUDE_REGISTRY_SCHEMA_REJECTED")
238
- : passedProbe(registry),
526
+ passedProbe(registry),
239
527
  passedProbe(socket),
240
528
  passedProbe(protocol),
241
529
  ];
242
530
  }
243
- catch {
531
+ catch (error) {
532
+ if (!isClaudeRegistryAvailabilityError(error))
533
+ throw error;
534
+ this.registryObservation = unavailableClaudeRegistryObservation();
535
+ // A missing or unreadable registry is provider-local incompatibility:
536
+ // keep the broker and the other surface alive, but fence this provider
537
+ // before listener construction can turn the same missing path into a
538
+ // broker-wide startup failure.
244
539
  return [
245
540
  passedProbe(launcher),
246
541
  passedProbe(version),
247
542
  failedProbe(registry, "CLAUDE_REGISTRY_UNAVAILABLE"),
248
- failedProbe(socket, "CLAUDE_SOCKET_VALIDATION_UNAVAILABLE"),
543
+ failedProbe(socket, "CLAUDE_REGISTRY_UNAVAILABLE"),
249
544
  passedProbe(protocol),
250
545
  ];
251
546
  }
252
547
  }
548
+ latestRegistryObservation() {
549
+ return this.registryObservation;
550
+ }
551
+ acceptCompatibilityAttestation(attestation) {
552
+ if (!isCompatibilityAttestation(attestation) ||
553
+ attestation.surface !== "claude" ||
554
+ attestation.version !== this.runtimeVersion) {
555
+ throw new BridgeError("CLAUDE_COMPATIBILITY_ATTESTATION_MISMATCH", "The Claude compatibility attestation does not match this runtime.");
556
+ }
557
+ this.compatibilityBlocked = attestation.tier === "incompatible";
558
+ this.compatibilityFailureCode = attestation.safeErrorCode;
559
+ if (this.compatibilityBlocked) {
560
+ if (this.monitorTimer !== undefined)
561
+ clearTimeout(this.monitorTimer);
562
+ this.monitorTimer = undefined;
563
+ }
564
+ }
253
565
  async initialize(callbacks) {
254
566
  if (this.closed) {
255
567
  throw new BridgeError("CLAUDE_PROVIDER_CLOSED", "The local Claude provider is closed.");
@@ -258,6 +570,14 @@ export class LocalClaudeGatewayProvider {
258
570
  throw new BridgeError("CLAUDE_PROVIDER_ALREADY_INITIALIZED", "The local Claude provider can be initialized only once.");
259
571
  }
260
572
  this.callbacks = callbacks;
573
+ if (this.compatibilityBlocked) {
574
+ this.initialized = true;
575
+ return {
576
+ health: "degraded",
577
+ compatibility: "incompatible",
578
+ safeErrorCode: this.compatibilityFailureCode ?? "CLAUDE_COMPATIBILITY_CHECK_FAILED",
579
+ };
580
+ }
261
581
  if (this.nativeHelpers !== undefined) {
262
582
  this.initialized = true;
263
583
  return { health: "healthy", compatibility: "compatible" };
@@ -297,8 +617,24 @@ export class LocalClaudeGatewayProvider {
297
617
  this.initialized = true;
298
618
  return { health: "healthy", compatibility: "compatible" };
299
619
  }
300
- catch {
620
+ catch (error) {
621
+ if (error instanceof BridgeError &&
622
+ error.code === "UNSAFE_PEER_DIRECTORY") {
623
+ this.compatibilityBlocked = true;
624
+ this.compatibilityFailureCode = "CLAUDE_REGISTRY_UNAVAILABLE";
625
+ this.registryObservation = unavailableClaudeRegistryObservation();
626
+ this.initialized = true;
627
+ return {
628
+ health: "degraded",
629
+ compatibility: "incompatible",
630
+ safeErrorCode: "CLAUDE_REGISTRY_UNAVAILABLE",
631
+ };
632
+ }
301
633
  this.callbacks = undefined;
634
+ if (error instanceof BridgeError &&
635
+ error.code === "CLAUDE_PEER_CALLBACK_UNSAFE") {
636
+ throw error;
637
+ }
302
638
  throw new BridgeError("CLAUDE_CALLBACK_UNAVAILABLE", "The private Claude callback listener could not be established.");
303
639
  }
304
640
  }
@@ -769,6 +1105,12 @@ export class LocalClaudeGatewayProvider {
769
1105
  active.inboundQuiesced = true;
770
1106
  }
771
1107
  async dispatch(input) {
1108
+ if (this.compatibilityBlocked) {
1109
+ return {
1110
+ state: "failed",
1111
+ safeErrorCode: this.compatibilityFailureCode ?? "CLAUDE_COMPATIBILITY_CHECK_FAILED",
1112
+ };
1113
+ }
772
1114
  if (this.nativeHelpers !== undefined) {
773
1115
  if (input.sourceAlias === undefined ||
774
1116
  !this.initialized ||
@@ -1050,7 +1392,7 @@ export class LocalClaudeGatewayProvider {
1050
1392
  }
1051
1393
  }
1052
1394
  assertReady() {
1053
- if (!this.initialized || this.closed) {
1395
+ if (!this.initialized || this.closed || this.compatibilityBlocked) {
1054
1396
  throw new BridgeError("CLAUDE_PROVIDER_UNAVAILABLE", "The local Claude provider is not available.", true);
1055
1397
  }
1056
1398
  }
@@ -1109,7 +1451,8 @@ export class LocalClaudeGatewayProvider {
1109
1451
  await this.rejectNativeInboundReceipt(listenerGeneration, message.receiptHandle, diagnosticCode);
1110
1452
  }
1111
1453
  };
1112
- if (this.closed ||
1454
+ if (this.compatibilityBlocked ||
1455
+ this.closed ||
1113
1456
  listenerGeneration.listener.closed ||
1114
1457
  !this.ownsNativeCodexListener(listenerGeneration)) {
1115
1458
  try {
@@ -1433,13 +1776,21 @@ export class LocalClaudeGatewayProvider {
1433
1776
  dispatchEpochAtStart.set(routeHandle, this.selectedDispatchEpoch.get(routeHandle) ?? 0);
1434
1777
  }
1435
1778
  const pendingAtStart = new Set(this.pendingTargetByGatewayId.values());
1436
- const discovery = await this.peer.discover();
1779
+ let discovery;
1780
+ try {
1781
+ discovery = await this.peer.discover();
1782
+ }
1783
+ catch (error) {
1784
+ this.registryObservation = unavailableClaudeRegistryObservation();
1785
+ throw error;
1786
+ }
1787
+ const registry = claudeRegistryObservation(discovery);
1788
+ this.registryObservation = registry;
1437
1789
  this.discovered.clear();
1438
1790
  const rows = [];
1439
1791
  for (const peer of discovery.peers) {
1440
1792
  if (peer.kind !== "interactive" ||
1441
- !NATIVE_CLAUDE_NAME.test(peer.alias) ||
1442
- peer.alias.startsWith("codex-")) {
1793
+ !NATIVE_CLAUDE_NAME.test(peer.alias)) {
1443
1794
  continue;
1444
1795
  }
1445
1796
  const alias = `${peer.alias}@${this.identity.hostId}`;
@@ -1480,7 +1831,7 @@ export class LocalClaudeGatewayProvider {
1480
1831
  this.emitClaudeRouteObservation(routeHandle, row.state, undefined, true);
1481
1832
  }
1482
1833
  }
1483
- return { peers: rows, complete: !discovery.truncated };
1834
+ return { peers: rows, complete: !discovery.truncated, registry };
1484
1835
  }
1485
1836
  emitClaudeRouteObservation(routeHandle, state, safeErrorCode, authoritative = false) {
1486
1837
  if (this.nativeCodexSuccessionFreeze !== undefined)
@@ -1540,33 +1891,52 @@ export class LocalClaudeGatewayProvider {
1540
1891
  export function createLocalClaudeGatewayProvider(options) {
1541
1892
  return new LocalClaudeGatewayProvider(options);
1542
1893
  }
1543
- function validateCodexFactory(factory) {
1894
+ function codexVersionFailureCode(version) {
1895
+ if (version === UNKNOWN_COMPATIBILITY_VERSION) {
1896
+ return "CODEX_APP_SERVER_VERSION_UNPARSEABLE";
1897
+ }
1898
+ if (!isCompatibilityVersion(version)) {
1899
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The local Codex factory version evidence is malformed.");
1900
+ }
1901
+ return CODEX_APP_SERVER_WRITABLE_VERSIONS.some((certifiedVersion) => sharesCompatibilityMajor(certifiedVersion, version))
1902
+ ? undefined
1903
+ : "CODEX_APP_SERVER_VERSION_UNSUPPORTED";
1904
+ }
1905
+ function validateCodexFactory(factory, options = {}) {
1544
1906
  const schema = factory.schemaCompatibility;
1545
1907
  const write = factory.writeCompatibility;
1908
+ const versionFailure = codexVersionFailureCode(factory.appServerVersion);
1546
1909
  const certified = CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(factory.appServerVersion);
1547
1910
  const schemaCandidate = schema.observedSchemaCandidate === true &&
1548
1911
  CODEX_APP_SERVER_WRITABLE_VERSIONS.some((version) => sharesCompatibilityMajor(version, factory.appServerVersion));
1912
+ const unsupportedVersionInvalid = versionFailure !== undefined &&
1913
+ (options.allowUnsupportedVersion !== true ||
1914
+ write !== null ||
1915
+ factory.writableReady);
1916
+ const supportedSchemaInvalid = versionFailure === undefined &&
1917
+ ((!certified &&
1918
+ (!schemaCandidate || write !== null || factory.writableReady)) ||
1919
+ schema.protocol !== "app-server-v2-stable" ||
1920
+ schema.steering?.method !== "turn/steer" ||
1921
+ schema.steering.requestSchema !== "expected-turn-id-text-v1" ||
1922
+ schema.steering.deliveryBoundary !== "next-tool-call-boundary" ||
1923
+ (write !== null &&
1924
+ (write.appServerVersion !== schema.appServerVersion ||
1925
+ write.endpointGeneration !== schema.endpointGeneration ||
1926
+ write.protocol !== schema.protocol ||
1927
+ write.observedSchemaCandidate !== schema.observedSchemaCandidate ||
1928
+ write.steering?.method !== schema.steering.method ||
1929
+ write.steering.requestSchema !== schema.steering.requestSchema ||
1930
+ write.steering.deliveryBoundary !==
1931
+ schema.steering.deliveryBoundary)) ||
1932
+ factory.writableReady !== (write !== null));
1549
1933
  if (factory.hostId !== LOCAL_HOST ||
1550
1934
  factory.protocol !== "codex-app-server" ||
1551
1935
  factory.protocolVersion !== factory.appServerVersion ||
1552
- (!certified &&
1553
- (!schemaCandidate || write !== null || factory.writableReady)) ||
1554
1936
  schema.appServerVersion !== factory.appServerVersion ||
1555
1937
  schema.endpointGeneration !== factory.endpointGeneration ||
1556
- schema.protocol !== "app-server-v2-stable" ||
1557
- schema.steering?.method !== "turn/steer" ||
1558
- schema.steering.requestSchema !== "expected-turn-id-text-v1" ||
1559
- schema.steering.deliveryBoundary !== "next-tool-call-boundary" ||
1560
- (write !== null &&
1561
- (write.appServerVersion !== schema.appServerVersion ||
1562
- write.endpointGeneration !== schema.endpointGeneration ||
1563
- write.protocol !== schema.protocol ||
1564
- write.observedSchemaCandidate !== schema.observedSchemaCandidate ||
1565
- write.steering?.method !== schema.steering.method ||
1566
- write.steering.requestSchema !== schema.steering.requestSchema ||
1567
- write.steering.deliveryBoundary !==
1568
- schema.steering.deliveryBoundary)) ||
1569
- factory.writableReady !== (write !== null)) {
1938
+ unsupportedVersionInvalid ||
1939
+ supportedSchemaInvalid) {
1570
1940
  throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The local Codex provider requires exact schema and write attestations.");
1571
1941
  }
1572
1942
  }
@@ -1627,10 +1997,15 @@ export class LocalCodexGatewayProvider {
1627
1997
  endpointActivationRetry;
1628
1998
  endpointActivationRetryTimer;
1629
1999
  callbackDrainFrozen = false;
2000
+ writeProbeObservationsByGeneration = new Map();
2001
+ writeProbeResults = new Map();
2002
+ writeProbeOverflow;
1630
2003
  compatibilityAttested;
2004
+ compatibilityFailureCode;
1631
2005
  endpointUnavailable = false;
1632
2006
  pendingEndpointAttestation;
1633
2007
  pendingEndpointRefreshEvent;
2008
+ pendingEndpointRefreshSelectorClaimed = false;
1634
2009
  initialized = false;
1635
2010
  closing = false;
1636
2011
  closed = false;
@@ -1673,31 +2048,82 @@ export class LocalCodexGatewayProvider {
1673
2048
  }
1674
2049
  acceptCompatibilityAttestation(attestation) {
1675
2050
  const pending = this.pendingEndpointAttestation;
1676
- if (attestation.surface !== "codex" ||
2051
+ const pendingMatches = pending === undefined ||
2052
+ (attestation.checkedAt === pending.checkedAt &&
2053
+ attestation.tier === pending.tier &&
2054
+ attestation.safeErrorCode === pending.safeErrorCode &&
2055
+ attestation.probes.length === pending.probes.length &&
2056
+ attestation.probes.every((probe, index) => probe.name === pending.probes[index]?.name &&
2057
+ probe.outcome === pending.probes[index]?.outcome &&
2058
+ probe.safeErrorCode === pending.probes[index]?.safeErrorCode));
2059
+ if (!isCompatibilityAttestation(attestation) ||
2060
+ attestation.surface !== "codex" ||
1677
2061
  attestation.version !== this.factory.appServerVersion ||
1678
- attestation.tier !== "certified" ||
1679
- !CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(attestation.version) ||
2062
+ !pendingMatches) {
2063
+ throw new BridgeError("CODEX_COMPATIBILITY_ATTESTATION_MISMATCH", "The Codex compatibility attestation does not match this endpoint.");
2064
+ }
2065
+ if (attestation.tier === "incompatible") {
2066
+ this.pendingEndpointAttestation = undefined;
2067
+ this.pendingEndpointRefreshEvent = undefined;
2068
+ this.pendingEndpointRefreshSelectorClaimed = false;
2069
+ this.clearEndpointActivationRetry();
2070
+ this.compatibilityAttested = false;
2071
+ this.endpointUnavailable = true;
2072
+ this.compatibilityFailureCode =
2073
+ attestation.safeErrorCode ?? "CODEX_COMPATIBILITY_CHECK_FAILED";
2074
+ return;
2075
+ }
2076
+ const writableCertified = attestation.tier === "certified" &&
2077
+ CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(attestation.version) &&
2078
+ this.factory.writableReady &&
2079
+ this.factory.writeCompatibility !== null;
2080
+ const schemaObserved = attestation.tier === "schema_attested" &&
2081
+ !this.factory.writableReady &&
2082
+ this.factory.writeCompatibility === null;
2083
+ if ((!writableCertified && !schemaObserved) ||
1680
2084
  (this.endpointUnavailable && pending === undefined) ||
1681
- (pending !== undefined &&
1682
- (attestation.checkedAt !== pending.checkedAt ||
1683
- attestation.tier !== pending.tier ||
1684
- attestation.safeErrorCode !== pending.safeErrorCode ||
1685
- attestation.probes.length !== pending.probes.length ||
1686
- attestation.probes.some((probe, index) => probe.name !== pending.probes[index]?.name ||
1687
- probe.outcome !== pending.probes[index]?.outcome ||
1688
- probe.safeErrorCode !== pending.probes[index]?.safeErrorCode)))) {
2085
+ !pendingMatches) {
1689
2086
  throw new BridgeError("CODEX_COMPATIBILITY_ATTESTATION_MISMATCH", "The Codex compatibility attestation does not match this endpoint.");
1690
2087
  }
1691
2088
  this.pendingEndpointAttestation = undefined;
1692
2089
  this.pendingEndpointRefreshEvent = undefined;
2090
+ this.pendingEndpointRefreshSelectorClaimed = false;
1693
2091
  this.clearEndpointActivationRetry();
1694
- this.compatibilityAttested = true;
1695
- this.endpointUnavailable = false;
2092
+ this.compatibilityAttested = writableCertified;
2093
+ this.endpointUnavailable = !writableCertified;
2094
+ this.compatibilityFailureCode = undefined;
1696
2095
  }
1697
- async runCompatibilityProbes() {
1698
- return await this.runCompatibilityProbesFor(this.factory);
2096
+ releaseEndpointRefreshSelectorClaim(endpointGeneration) {
2097
+ const event = this.pendingEndpointRefreshEvent;
2098
+ if (event?.current.endpointGeneration === endpointGeneration &&
2099
+ this.pendingEndpointAttestation !== undefined) {
2100
+ this.pendingEndpointRefreshSelectorClaimed = false;
2101
+ }
2102
+ }
2103
+ rearmEndpointRefreshActivation(endpointGeneration) {
2104
+ const event = this.pendingEndpointRefreshEvent;
2105
+ if (event?.current.endpointGeneration === endpointGeneration &&
2106
+ this.pendingEndpointAttestation !== undefined &&
2107
+ !this.closing &&
2108
+ !this.closed) {
2109
+ this.publishEndpointRefresh(event);
2110
+ }
1699
2111
  }
1700
- async runCompatibilityProbesFor(factory) {
2112
+ latestWriteCompatibilityProbeObservation(endpointGeneration) {
2113
+ return (this.writeProbeObservationsByGeneration.get(endpointGeneration) ??
2114
+ (this.writeProbeOverflow?.endpointGeneration === endpointGeneration
2115
+ ? this.writeProbeOverflow.observation
2116
+ : undefined));
2117
+ }
2118
+ async runCompatibilityProbes(context) {
2119
+ return await this.runCompatibilityProbesFor(this.factory, context === undefined
2120
+ ? undefined
2121
+ : {
2122
+ stateRoot: path.resolve(context.stateRoot),
2123
+ forbiddenCodexThreadIds: [...context.forbiddenCodexThreadIds],
2124
+ });
2125
+ }
2126
+ async runCompatibilityProbesFor(factory, context) {
1701
2127
  const [installation, control, initialize, threadList] = compatibilityProbeNames.codex;
1702
2128
  let transport;
1703
2129
  let connector;
@@ -1707,6 +2133,9 @@ export class LocalCodexGatewayProvider {
1707
2133
  stage = "initialize";
1708
2134
  connector = await CodexAppServerConnector.connect({
1709
2135
  compatibility: factory.schemaCompatibility,
2136
+ ...(context === undefined
2137
+ ? {}
2138
+ : { writeCompatibilityProbe: true }),
1710
2139
  writesEnabled: false,
1711
2140
  route: {
1712
2141
  endpointGeneration: factory.endpointGeneration,
@@ -1720,30 +2149,46 @@ export class LocalCodexGatewayProvider {
1720
2149
  });
1721
2150
  stage = "thread_list";
1722
2151
  await connector.observeLoadedThread(connector.guard());
1723
- return [
2152
+ const requiredProbes = [
1724
2153
  passedProbe(installation),
1725
2154
  passedProbe(control),
1726
2155
  passedProbe(initialize),
1727
2156
  passedProbe(threadList),
1728
2157
  ];
2158
+ if (context === undefined)
2159
+ return requiredProbes;
2160
+ const result = await this.runCodexWriteProbe(factory, connector, context);
2161
+ return result.outcome === "pass"
2162
+ ? [...requiredProbes, passedProbe("write_attestation")]
2163
+ : requiredProbes;
1729
2164
  }
1730
2165
  catch (error) {
1731
- if (this.isEndpointGenerationChanged(error))
2166
+ if (this.isEndpointGenerationChanged(error) ||
2167
+ (error instanceof BridgeError &&
2168
+ error.code === "CODEX_FACTORY_ATTESTATION_INVALID")) {
1732
2169
  throw error;
2170
+ }
1733
2171
  if (stage === "transport") {
1734
- const controlFailure = error instanceof LocalCodexTransportError &&
2172
+ const availabilityFailure = error instanceof LocalCodexTransportError &&
1735
2173
  [
2174
+ "MANAGED_CODEX_UNAVAILABLE",
1736
2175
  "LOCAL_APP_SERVER_NOT_RUNNING",
1737
- "LOCAL_APP_SERVER_ENDPOINT_UNSAFE",
2176
+ "SPAWN_FAILED",
2177
+ "SPAWN_TIMEOUT",
2178
+ "PROXY_STDERR_LIMIT",
1738
2179
  "TRANSPORT_CONNECT_FAILED",
1739
2180
  ].includes(error.code);
2181
+ if (!availabilityFailure)
2182
+ throw error;
2183
+ const installationUnavailable = error instanceof LocalCodexTransportError &&
2184
+ error.code === "MANAGED_CODEX_UNAVAILABLE";
1740
2185
  return [
1741
- controlFailure
1742
- ? passedProbe(installation)
1743
- : failedProbe(installation, "CODEX_INSTALLATION_INVALID"),
1744
- failedProbe(control, controlFailure
1745
- ? "CODEX_CONTROL_SOCKET_UNAVAILABLE"
1746
- : "CODEX_COMPAT_PROBE_BLOCKED"),
2186
+ installationUnavailable
2187
+ ? failedProbe(installation, "CODEX_INSTALLATION_INVALID")
2188
+ : passedProbe(installation),
2189
+ failedProbe(control, installationUnavailable
2190
+ ? "CODEX_COMPAT_PROBE_BLOCKED"
2191
+ : "CODEX_CONTROL_SOCKET_UNAVAILABLE"),
1747
2192
  failedProbe(initialize, "CODEX_COMPAT_PROBE_BLOCKED"),
1748
2193
  failedProbe(threadList, "CODEX_COMPAT_PROBE_BLOCKED"),
1749
2194
  ];
@@ -1759,19 +2204,168 @@ export class LocalCodexGatewayProvider {
1759
2204
  }
1760
2205
  finally {
1761
2206
  if (connector !== undefined) {
1762
- await connector.close().catch(() => undefined);
2207
+ await connector.close();
1763
2208
  }
1764
2209
  else if (transport !== undefined) {
1765
- await transport.close().catch(() => undefined);
2210
+ await transport.close();
1766
2211
  }
1767
2212
  }
1768
2213
  }
2214
+ async runCodexWriteProbe(factory, connector, context) {
2215
+ const key = `${factory.appServerVersion}\0${factory.endpointGeneration}`;
2216
+ const cached = this.writeProbeResults.get(key);
2217
+ if (cached !== undefined)
2218
+ return await cached;
2219
+ if (this.writeProbeOverflow?.key === key) {
2220
+ return this.writeProbeOverflow.observation;
2221
+ }
2222
+ if (this.writeProbeResults.size >= MAX_CODEX_WRITE_PROBE_ATTEMPTS) {
2223
+ const observation = Object.freeze(codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED"));
2224
+ this.writeProbeOverflow = Object.freeze({
2225
+ endpointGeneration: factory.endpointGeneration,
2226
+ key,
2227
+ observation,
2228
+ });
2229
+ if (this.callbacks !== undefined) {
2230
+ invokeCallback(() => this.callbacks?.onProtocolNotice?.({
2231
+ code: observation.safeErrorCode,
2232
+ }));
2233
+ }
2234
+ return observation;
2235
+ }
2236
+ const pending = this.executeCodexWriteProbe(connector, context).then((result) => {
2237
+ const observation = Object.freeze({ ...result });
2238
+ this.writeProbeObservationsByGeneration.set(factory.endpointGeneration, observation);
2239
+ if (observation.outcome === "fail" && this.callbacks !== undefined) {
2240
+ invokeCallback(() => this.callbacks?.onProtocolNotice?.({
2241
+ code: observation.safeErrorCode,
2242
+ }));
2243
+ }
2244
+ return observation;
2245
+ });
2246
+ this.writeProbeResults.set(key, pending);
2247
+ return await pending;
2248
+ }
2249
+ async executeCodexWriteProbe(connector, context) {
2250
+ const stateRoot = path.resolve(context.stateRoot);
2251
+ const rootBefore = await attestCodexWriteProbeRoot(stateRoot);
2252
+ if (rootBefore === null) {
2253
+ return codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
2254
+ }
2255
+ const probeRoot = path.join(stateRoot, CODEX_WRITE_PROBE_ROOT);
2256
+ let probeRootBefore;
2257
+ try {
2258
+ let created = false;
2259
+ try {
2260
+ await mkdir(probeRoot, { mode: 0o700 });
2261
+ created = true;
2262
+ }
2263
+ catch (error) {
2264
+ if (systemErrorCode(error) !== "EEXIST")
2265
+ throw error;
2266
+ }
2267
+ if (created)
2268
+ await chmod(probeRoot, 0o700);
2269
+ const evidence = await attestCodexWriteProbeRoot(probeRoot);
2270
+ if (evidence === null) {
2271
+ return codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
2272
+ }
2273
+ probeRootBefore = evidence;
2274
+ }
2275
+ catch (error) {
2276
+ if (error instanceof BridgeError &&
2277
+ error.code === "CODEX_FACTORY_ATTESTATION_INVALID") {
2278
+ throw error;
2279
+ }
2280
+ if (transientCodexWriteProbeError(error)) {
2281
+ return codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
2282
+ }
2283
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The controller-owned Codex probe root could not be established safely.");
2284
+ }
2285
+ let cwd;
2286
+ let before;
2287
+ let result = codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
2288
+ try {
2289
+ cwd = await mkdtemp(path.join(probeRoot, CODEX_WRITE_PROBE_DIRECTORY_PREFIX));
2290
+ if (path.dirname(cwd) !== probeRoot) {
2291
+ throw new Error("probe directory escaped its dedicated root");
2292
+ }
2293
+ await chmod(cwd, 0o700);
2294
+ before = await attestCodexWriteProbeDirectory(cwd);
2295
+ result = await connector.runWriteCompatibilityProbe({
2296
+ cwd,
2297
+ forbiddenThreadIds: [
2298
+ ...new Set([
2299
+ COMPATIBILITY_PROBE_THREAD_ID,
2300
+ ...context.forbiddenCodexThreadIds,
2301
+ ...this.trackedRoutes,
2302
+ ]),
2303
+ ],
2304
+ });
2305
+ }
2306
+ catch (error) {
2307
+ if (!safeCodexWriteProbeFilesystemFailure(error)) {
2308
+ if (error instanceof BridgeError)
2309
+ throw error;
2310
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The disposable Codex probe directory could not be established safely.");
2311
+ }
2312
+ result = codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
2313
+ }
2314
+ let unchanged = false;
2315
+ let removed = false;
2316
+ if (cwd !== undefined && before !== undefined) {
2317
+ try {
2318
+ const after = await attestCodexWriteProbeDirectory(cwd);
2319
+ unchanged = sameCodexWriteProbeDirectory(before, after);
2320
+ await rmdir(cwd);
2321
+ try {
2322
+ await lstat(cwd);
2323
+ }
2324
+ catch (error) {
2325
+ removed = systemErrorCode(error) === "ENOENT";
2326
+ }
2327
+ }
2328
+ catch (error) {
2329
+ if (!safeCodexWriteProbeFilesystemFailure(error)) {
2330
+ if (error instanceof BridgeError)
2331
+ throw error;
2332
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The disposable Codex probe directory could not be re-attested safely.");
2333
+ }
2334
+ // Never remove a path after its exact ownership evidence changed.
2335
+ }
2336
+ }
2337
+ if (cwd !== undefined && !removed) {
2338
+ result = codexWriteProbeFailure("CODEX_WRITE_PROBE_CLEANUP_UNCONFIRMED", result);
2339
+ }
2340
+ else if (cwd !== undefined && !unchanged) {
2341
+ result = codexWriteProbeFailure("CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED", result);
2342
+ }
2343
+ const [probeRootAfter, rootAfter] = await Promise.all([
2344
+ attestCodexWriteProbeRoot(probeRoot),
2345
+ attestCodexWriteProbeRoot(stateRoot),
2346
+ ]);
2347
+ if (probeRootAfter === null || rootAfter === null) {
2348
+ return codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED", result);
2349
+ }
2350
+ if (!sameCodexWriteProbeRoot(probeRootBefore, probeRootAfter) ||
2351
+ !sameCodexWriteProbeRoot(rootBefore, rootAfter)) {
2352
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "A controller-owned Codex probe root changed while the disposable probe ran.");
2353
+ }
2354
+ return result;
2355
+ }
1769
2356
  async initialize(callbacks) {
1770
2357
  if (this.closed || this.initialized) {
1771
2358
  throw new BridgeError("CODEX_PROVIDER_INITIALIZATION_REJECTED", "The local Codex provider cannot be initialized in this state.");
1772
2359
  }
1773
2360
  this.callbacks = callbacks;
1774
2361
  this.initialized = true;
2362
+ if (this.compatibilityFailureCode !== undefined) {
2363
+ return {
2364
+ health: "degraded",
2365
+ compatibility: "incompatible",
2366
+ safeErrorCode: this.compatibilityFailureCode,
2367
+ };
2368
+ }
1775
2369
  return this.factory.writeCompatibility === null
1776
2370
  ? {
1777
2371
  health: "degraded",
@@ -1817,9 +2411,13 @@ export class LocalCodexGatewayProvider {
1817
2411
  throw selectionError;
1818
2412
  }
1819
2413
  };
2414
+ const retainedEndpointRefresh = this.retainedEndpointRefreshResult();
1820
2415
  if (this.endpointRefresh !== undefined) {
1821
2416
  route = await stageRefreshedRoute(await this.refreshEndpoint("selector"));
1822
2417
  }
2418
+ else if (retainedEndpointRefresh !== undefined) {
2419
+ route = await stageRefreshedRoute(retainedEndpointRefresh);
2420
+ }
1823
2421
  else if (this.endpointUnavailable &&
1824
2422
  this.pendingEndpointAttestation === undefined) {
1825
2423
  route = await stageRefreshedRoute(await this.refreshEndpoint("selector"));
@@ -1977,6 +2575,9 @@ export class LocalCodexGatewayProvider {
1977
2575
  ...(input.progressWatchActive === true
1978
2576
  ? { progressWatchActive: true }
1979
2577
  : {}),
2578
+ ...(input.steer === true && input.queuedAhead !== undefined
2579
+ ? { queuedAhead: input.queuedAhead }
2580
+ : {}),
1980
2581
  });
1981
2582
  }
1982
2583
  catch (error) {
@@ -2075,6 +2676,7 @@ export class LocalCodexGatewayProvider {
2075
2676
  this.routesRequiringRecovery.clear();
2076
2677
  this.clearEndpointActivationRetry();
2077
2678
  this.pendingEndpointRefreshEvent = undefined;
2679
+ this.pendingEndpointRefreshSelectorClaimed = false;
2078
2680
  this.pendingEndpointAttestation = undefined;
2079
2681
  const refreshResult = this.endpointRefresh === undefined
2080
2682
  ? []
@@ -2103,6 +2705,7 @@ export class LocalCodexGatewayProvider {
2103
2705
  this.callbackQueue.length = 0;
2104
2706
  this.pendingEndpointAttestation = undefined;
2105
2707
  this.pendingEndpointRefreshEvent = undefined;
2708
+ this.pendingEndpointRefreshSelectorClaimed = false;
2106
2709
  this.trackedRoutes.clear();
2107
2710
  this.routeAliases.clear();
2108
2711
  this.expectsReply.clear();
@@ -2214,13 +2817,23 @@ export class LocalCodexGatewayProvider {
2214
2817
  return route;
2215
2818
  }
2216
2819
  catch (error) {
2820
+ let cleanupFailure;
2217
2821
  if (connector !== undefined) {
2218
- await connector.close().catch(() => undefined);
2822
+ await connector.close().catch((closeError) => {
2823
+ cleanupFailure = closeError;
2824
+ });
2219
2825
  }
2220
2826
  else if (transport !== undefined) {
2221
- await transport.close().catch(() => undefined);
2827
+ await transport.close().catch((closeError) => {
2828
+ cleanupFailure = closeError;
2829
+ });
2830
+ }
2831
+ if (cleanupFailure !== undefined) {
2832
+ if (isCodexTrustFailure(cleanupFailure))
2833
+ throw cleanupFailure;
2834
+ throw new BridgeError("CODEX_ROUTE_CLEANUP_FAILED", "Codex route setup could not prove exact provider-process cleanup.");
2222
2835
  }
2223
- if (this.isEndpointGenerationChanged(error))
2836
+ if (isCodexTrustFailure(error))
2224
2837
  throw error;
2225
2838
  if (error instanceof BridgeError)
2226
2839
  throw error;
@@ -2343,6 +2956,21 @@ export class LocalCodexGatewayProvider {
2343
2956
  event.current.endpointGeneration === this.factory.endpointGeneration &&
2344
2957
  event.routes.some((route) => route.routeHandle === routeHandle));
2345
2958
  }
2959
+ retainedEndpointRefreshResult() {
2960
+ const event = this.pendingEndpointRefreshEvent;
2961
+ if (event === undefined ||
2962
+ this.pendingEndpointRefreshSelectorClaimed ||
2963
+ this.pendingEndpointAttestation === undefined ||
2964
+ event.current.endpointGeneration !== this.factory.endpointGeneration) {
2965
+ return undefined;
2966
+ }
2967
+ return {
2968
+ event,
2969
+ delivery: "selector",
2970
+ selectorClaimed: false,
2971
+ published: true,
2972
+ };
2973
+ }
2346
2974
  async refreshEndpoint(delivery) {
2347
2975
  if (this.refreshFactory === undefined || this.closing || this.closed) {
2348
2976
  return undefined;
@@ -2387,6 +3015,7 @@ export class LocalCodexGatewayProvider {
2387
3015
  this.compatibilityAttested = false;
2388
3016
  this.pendingEndpointAttestation = undefined;
2389
3017
  this.pendingEndpointRefreshEvent = undefined;
3018
+ this.pendingEndpointRefreshSelectorClaimed = false;
2390
3019
  for (const routeHandle of this.trackedRoutes) {
2391
3020
  this.routesRequiringRecovery.add(routeHandle);
2392
3021
  }
@@ -2401,7 +3030,9 @@ export class LocalCodexGatewayProvider {
2401
3030
  },
2402
3031
  delivery: "callback",
2403
3032
  selectorClaimed: true,
3033
+ published: false,
2404
3034
  };
3035
+ result.published = true;
2405
3036
  this.emitEndpointRefresh(result.event);
2406
3037
  for (const routeHandle of this.routesRequiringRecovery) {
2407
3038
  if (this.trackedRoutes.has(routeHandle)) {
@@ -2415,18 +3046,14 @@ export class LocalCodexGatewayProvider {
2415
3046
  candidate = undefined;
2416
3047
  return undefined;
2417
3048
  }
2418
- this.factory = candidate;
2419
- candidate = undefined;
2420
- this.retiredEndpointGeneration = undefined;
2421
- this.compatibilityAttested = false;
2422
- this.endpointUnavailable = true;
2423
- this.pendingEndpointAttestation = attestation;
3049
+ const replacementFactory = candidate;
2424
3050
  const refreshedRoutes = [];
3051
+ const stagedRoutes = [];
2425
3052
  for (const routeHandle of [...this.trackedRoutes]) {
2426
3053
  if (this.closing || this.closed)
2427
3054
  break;
2428
3055
  try {
2429
- const route = await this.createRoute(routeHandle, this.factory, false);
3056
+ const route = await this.createRoute(routeHandle, replacementFactory, false);
2430
3057
  if (this.closing || this.closed) {
2431
3058
  await this.closeRoute(route);
2432
3059
  break;
@@ -2436,17 +3063,34 @@ export class LocalCodexGatewayProvider {
2436
3063
  await this.closeRoute(route);
2437
3064
  throw new BridgeError("CODEX_ROUTE_SETUP_REJECTED", "The exact Codex task was stale on the replacement endpoint.");
2438
3065
  }
2439
- this.routes.set(routeHandle, route);
2440
- this.routesRequiringRecovery.delete(routeHandle);
2441
- this.routeRecoveryAttempts.delete(routeHandle);
2442
- refreshedRoutes.push({ routeHandle, state });
3066
+ stagedRoutes.push({ routeHandle, route, state });
2443
3067
  }
2444
- catch {
3068
+ catch (error) {
2445
3069
  this.routesRequiringRecovery.add(routeHandle);
3070
+ if (isCodexTrustFailure(error))
3071
+ throw error;
2446
3072
  }
2447
3073
  }
2448
- if (this.closing || this.closed)
3074
+ if (this.closing || this.closed) {
3075
+ await replacementFactory.close();
3076
+ candidate = undefined;
2449
3077
  return undefined;
3078
+ }
3079
+ this.factory = replacementFactory;
3080
+ candidate = undefined;
3081
+ this.retiredEndpointGeneration = undefined;
3082
+ this.compatibilityAttested = false;
3083
+ this.endpointUnavailable = true;
3084
+ this.pendingEndpointAttestation = attestation;
3085
+ for (const staged of stagedRoutes) {
3086
+ this.routes.set(staged.routeHandle, staged.route);
3087
+ this.routesRequiringRecovery.delete(staged.routeHandle);
3088
+ this.routeRecoveryAttempts.delete(staged.routeHandle);
3089
+ refreshedRoutes.push({
3090
+ routeHandle: staged.routeHandle,
3091
+ state: staged.state,
3092
+ });
3093
+ }
2450
3094
  const result = {
2451
3095
  event: {
2452
3096
  outcome: "compatible",
@@ -2457,12 +3101,15 @@ export class LocalCodexGatewayProvider {
2457
3101
  },
2458
3102
  delivery: this.endpointRefreshDelivery ?? "callback",
2459
3103
  selectorClaimed: false,
3104
+ published: false,
2460
3105
  };
2461
3106
  if (result.event.outcome === "compatible") {
2462
3107
  this.pendingEndpointRefreshEvent = result.event;
3108
+ this.pendingEndpointRefreshSelectorClaimed = false;
2463
3109
  }
2464
3110
  if (result.delivery === "callback") {
2465
3111
  result.selectorClaimed = true;
3112
+ result.published = true;
2466
3113
  this.publishEndpointRefresh(result.event);
2467
3114
  }
2468
3115
  for (const routeHandle of this.routesRequiringRecovery) {
@@ -2474,7 +3121,7 @@ export class LocalCodexGatewayProvider {
2474
3121
  }
2475
3122
  catch (error) {
2476
3123
  if (candidate !== undefined && candidate !== previousFactory) {
2477
- await candidate.close().catch(() => undefined);
3124
+ await candidate.close();
2478
3125
  }
2479
3126
  throw error;
2480
3127
  }
@@ -2490,7 +3137,10 @@ export class LocalCodexGatewayProvider {
2490
3137
  let candidate;
2491
3138
  try {
2492
3139
  candidate = await refreshFactory();
2493
- validateCodexFactory(candidate);
3140
+ const versionFailure = codexVersionFailureCode(candidate.appServerVersion);
3141
+ validateCodexFactory(candidate, {
3142
+ allowUnsupportedVersion: versionFailure !== undefined,
3143
+ });
2494
3144
  if (candidate.hostId !== previous.hostId ||
2495
3145
  candidate.protocol !== previousFactory.protocol) {
2496
3146
  throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The replacement Codex factory moved outside its pinned boundary.");
@@ -2500,14 +3150,25 @@ export class LocalCodexGatewayProvider {
2500
3150
  if (candidate !== previousFactory)
2501
3151
  await candidate.close();
2502
3152
  candidate = undefined;
3153
+ if (this.endpointUnavailable &&
3154
+ this.compatibilityFailureCode === undefined &&
3155
+ !this.factory.writableReady &&
3156
+ this.factory.writeCompatibility === null) {
3157
+ throw new BridgeError("CODEX_MONITOR_ONLY", "This Codex App Server remains monitor-only until a write-attested generation is available.");
3158
+ }
2503
3159
  if (attempt + 1 < MAX_CODEX_ENDPOINT_REFRESH_CANDIDATES) {
2504
3160
  continue;
2505
3161
  }
2506
3162
  throw new BridgeError("CODEX_ENDPOINT_GENERATION_CHURN", "The Codex endpoint did not stabilize within its bounded refresh window.", true);
2507
3163
  }
2508
- // Endpoint generations never inherit cached compatibility. Every
2509
- // candidate independently proves all four read-only wire probes.
2510
- const probes = await this.runCompatibilityProbesFor(candidate);
3164
+ // Endpoint generations never inherit cached compatibility. A version
3165
+ // outside the tested major is classified from OS-attested factory
3166
+ // evidence alone; no connector or transport may touch that protocol.
3167
+ const probes = versionFailure === undefined
3168
+ ? await this.runCompatibilityProbesFor(candidate)
3169
+ : compatibilityProbeNames.codex.map((name, index) => index === 0
3170
+ ? passedProbe(name)
3171
+ : failedProbe(name, versionFailure));
2511
3172
  const attestation = evaluateCompatibilityAttestation({
2512
3173
  surface: "codex",
2513
3174
  version: candidate.appServerVersion,
@@ -2567,11 +3228,16 @@ export class LocalCodexGatewayProvider {
2567
3228
  return false;
2568
3229
  }
2569
3230
  result.selectorClaimed = true;
3231
+ if (this.pendingEndpointRefreshEvent === result.event) {
3232
+ this.pendingEndpointRefreshSelectorClaimed = true;
3233
+ }
2570
3234
  return true;
2571
3235
  }
2572
3236
  emitSelectorRefreshFallback(result, staleRouteHandle) {
2573
3237
  if (result.delivery !== "selector" || !result.selectorClaimed)
2574
3238
  return;
3239
+ if (result.published)
3240
+ return;
2575
3241
  let event = result.event;
2576
3242
  if (staleRouteHandle !== undefined &&
2577
3243
  event.outcome === "compatible" &&
@@ -2583,6 +3249,7 @@ export class LocalCodexGatewayProvider {
2583
3249
  result.event = event;
2584
3250
  this.pendingEndpointRefreshEvent = event;
2585
3251
  }
3252
+ result.published = true;
2586
3253
  this.publishEndpointRefresh(event);
2587
3254
  }
2588
3255
  publishEndpointRefresh(event) {