agent-embassy 1.5.0 → 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 (52) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/CONTRIBUTING.md +11 -3
  3. package/README.md +4 -2
  4. package/README.zh-CN.md +4 -2
  5. package/SECURITY.md +30 -9
  6. package/dist/src/gateway/cli.d.ts +1 -1
  7. package/dist/src/gateway/cli.js +4 -1
  8. package/dist/src/gateway/cli.js.map +1 -1
  9. package/dist/src/gateway/codex-app-server.d.ts +42 -1
  10. package/dist/src/gateway/codex-app-server.js +536 -2
  11. package/dist/src/gateway/codex-app-server.js.map +1 -1
  12. package/dist/src/gateway/compatibility.d.ts +31 -3
  13. package/dist/src/gateway/compatibility.js +100 -29
  14. package/dist/src/gateway/compatibility.js.map +1 -1
  15. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  16. package/dist/src/gateway/dashboard-copy.en.d.ts +6 -0
  17. package/dist/src/gateway/dashboard-copy.en.js +6 -0
  18. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  19. package/dist/src/gateway/dashboard-copy.js +6 -0
  20. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  21. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +6 -0
  22. package/dist/src/gateway/dashboard-copy.zh-CN.js +6 -0
  23. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  24. package/dist/src/gateway/dashboard-model.d.ts +27 -7
  25. package/dist/src/gateway/dashboard-model.js +78 -25
  26. package/dist/src/gateway/dashboard-model.js.map +1 -1
  27. package/dist/src/gateway/dashboard.d.ts +3 -0
  28. package/dist/src/gateway/dashboard.js +15 -3
  29. package/dist/src/gateway/dashboard.js.map +1 -1
  30. package/dist/src/gateway/deepseek-detect.d.ts +13 -0
  31. package/dist/src/gateway/deepseek-detect.js +125 -0
  32. package/dist/src/gateway/deepseek-detect.js.map +1 -0
  33. package/dist/src/gateway/live-dashboard-app/app.js +12 -11
  34. package/dist/src/gateway/providers.d.ts +12 -2
  35. package/dist/src/gateway/providers.js +326 -6
  36. package/dist/src/gateway/providers.js.map +1 -1
  37. package/dist/src/gateway/server.d.ts +2 -0
  38. package/dist/src/gateway/server.js +6 -0
  39. package/dist/src/gateway/server.js.map +1 -1
  40. package/dist/src/gateway/service.d.ts +21 -2
  41. package/dist/src/gateway/service.js +116 -37
  42. package/dist/src/gateway/service.js.map +1 -1
  43. package/dist/src/gateway/types.d.ts +4 -3
  44. package/dist/src/gateway/types.js +17 -14
  45. package/dist/src/gateway/types.js.map +1 -1
  46. package/docs/CONFIGURATION.md +2 -2
  47. package/docs/CONFIGURATION.zh-CN.md +2 -2
  48. package/docs/DASHBOARD.md +10 -4
  49. package/docs/DASHBOARD.zh-CN.md +1 -1
  50. package/docs/GATEWAY-ARCHITECTURE.md +40 -14
  51. package/package.json +1 -1
  52. package/skills/embassy-peer/SKILL.md +1 -1
@@ -1,8 +1,9 @@
1
1
  import path from "node:path";
2
+ import { chmod, lstat, mkdir, mkdtemp, readdir, realpath, rmdir, } from "node:fs/promises";
2
3
  import { BridgeError } from "../errors.js";
3
4
  import { CLAUDE_PEER_COMPATIBILITY, ClaudePeerAdapter, } from "./claude-peer.js";
4
5
  import { certifiedCompatibilityVersions, compatibilityProbeNames, evaluateCompatibilityAttestation, isCompatibilityAttestation, isCompatibilityVersion, sharesCompatibilityMajor, UNKNOWN_COMPATIBILITY_VERSION, } from "./compatibility.js";
5
- import { CODEX_APP_SERVER_WRITABLE_VERSIONS, CodexAppServerConnector, CodexConnectorError, } from "./codex-app-server.js";
6
+ import { CODEX_APP_SERVER_WRITABLE_VERSIONS, CodexAppServerConnector, CodexConnectorError, codexWriteProbeFailure, } from "./codex-app-server.js";
6
7
  import { LocalCodexTransportError } from "./codex-local-transport.js";
7
8
  import { ClaudeNativeHelperSupervisor, } from "./claude-helper-supervisor.js";
8
9
  import { isDashboardLocale } from "./locale.js";
@@ -24,6 +25,12 @@ 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",
@@ -85,6 +92,99 @@ function passedProbe(name) {
85
92
  function failedProbe(name, safeErrorCode) {
86
93
  return { name, outcome: "fail", safeErrorCode };
87
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
+ }
88
188
  function exactLocalHost(hostId) {
89
189
  if ((hostId ?? LOCAL_HOST) !== LOCAL_HOST) {
90
190
  throw new BridgeError("GATEWAY_REMOTE_PROVIDER_DISABLED", "This provider is restricted to the exact local host boundary.");
@@ -1897,11 +1997,15 @@ export class LocalCodexGatewayProvider {
1897
1997
  endpointActivationRetry;
1898
1998
  endpointActivationRetryTimer;
1899
1999
  callbackDrainFrozen = false;
2000
+ writeProbeObservationsByGeneration = new Map();
2001
+ writeProbeResults = new Map();
2002
+ writeProbeOverflow;
1900
2003
  compatibilityAttested;
1901
2004
  compatibilityFailureCode;
1902
2005
  endpointUnavailable = false;
1903
2006
  pendingEndpointAttestation;
1904
2007
  pendingEndpointRefreshEvent;
2008
+ pendingEndpointRefreshSelectorClaimed = false;
1905
2009
  initialized = false;
1906
2010
  closing = false;
1907
2011
  closed = false;
@@ -1961,6 +2065,7 @@ export class LocalCodexGatewayProvider {
1961
2065
  if (attestation.tier === "incompatible") {
1962
2066
  this.pendingEndpointAttestation = undefined;
1963
2067
  this.pendingEndpointRefreshEvent = undefined;
2068
+ this.pendingEndpointRefreshSelectorClaimed = false;
1964
2069
  this.clearEndpointActivationRetry();
1965
2070
  this.compatibilityAttested = false;
1966
2071
  this.endpointUnavailable = true;
@@ -1982,15 +2087,43 @@ export class LocalCodexGatewayProvider {
1982
2087
  }
1983
2088
  this.pendingEndpointAttestation = undefined;
1984
2089
  this.pendingEndpointRefreshEvent = undefined;
2090
+ this.pendingEndpointRefreshSelectorClaimed = false;
1985
2091
  this.clearEndpointActivationRetry();
1986
2092
  this.compatibilityAttested = writableCertified;
1987
2093
  this.endpointUnavailable = !writableCertified;
1988
2094
  this.compatibilityFailureCode = undefined;
1989
2095
  }
1990
- async runCompatibilityProbes() {
1991
- 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
+ }
2111
+ }
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
+ });
1992
2125
  }
1993
- async runCompatibilityProbesFor(factory) {
2126
+ async runCompatibilityProbesFor(factory, context) {
1994
2127
  const [installation, control, initialize, threadList] = compatibilityProbeNames.codex;
1995
2128
  let transport;
1996
2129
  let connector;
@@ -2000,6 +2133,9 @@ export class LocalCodexGatewayProvider {
2000
2133
  stage = "initialize";
2001
2134
  connector = await CodexAppServerConnector.connect({
2002
2135
  compatibility: factory.schemaCompatibility,
2136
+ ...(context === undefined
2137
+ ? {}
2138
+ : { writeCompatibilityProbe: true }),
2003
2139
  writesEnabled: false,
2004
2140
  route: {
2005
2141
  endpointGeneration: factory.endpointGeneration,
@@ -2013,16 +2149,25 @@ export class LocalCodexGatewayProvider {
2013
2149
  });
2014
2150
  stage = "thread_list";
2015
2151
  await connector.observeLoadedThread(connector.guard());
2016
- return [
2152
+ const requiredProbes = [
2017
2153
  passedProbe(installation),
2018
2154
  passedProbe(control),
2019
2155
  passedProbe(initialize),
2020
2156
  passedProbe(threadList),
2021
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;
2022
2164
  }
2023
2165
  catch (error) {
2024
- if (this.isEndpointGenerationChanged(error))
2166
+ if (this.isEndpointGenerationChanged(error) ||
2167
+ (error instanceof BridgeError &&
2168
+ error.code === "CODEX_FACTORY_ATTESTATION_INVALID")) {
2025
2169
  throw error;
2170
+ }
2026
2171
  if (stage === "transport") {
2027
2172
  const availabilityFailure = error instanceof LocalCodexTransportError &&
2028
2173
  [
@@ -2066,6 +2211,148 @@ export class LocalCodexGatewayProvider {
2066
2211
  }
2067
2212
  }
2068
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
+ }
2069
2356
  async initialize(callbacks) {
2070
2357
  if (this.closed || this.initialized) {
2071
2358
  throw new BridgeError("CODEX_PROVIDER_INITIALIZATION_REJECTED", "The local Codex provider cannot be initialized in this state.");
@@ -2124,9 +2411,13 @@ export class LocalCodexGatewayProvider {
2124
2411
  throw selectionError;
2125
2412
  }
2126
2413
  };
2414
+ const retainedEndpointRefresh = this.retainedEndpointRefreshResult();
2127
2415
  if (this.endpointRefresh !== undefined) {
2128
2416
  route = await stageRefreshedRoute(await this.refreshEndpoint("selector"));
2129
2417
  }
2418
+ else if (retainedEndpointRefresh !== undefined) {
2419
+ route = await stageRefreshedRoute(retainedEndpointRefresh);
2420
+ }
2130
2421
  else if (this.endpointUnavailable &&
2131
2422
  this.pendingEndpointAttestation === undefined) {
2132
2423
  route = await stageRefreshedRoute(await this.refreshEndpoint("selector"));
@@ -2385,6 +2676,7 @@ export class LocalCodexGatewayProvider {
2385
2676
  this.routesRequiringRecovery.clear();
2386
2677
  this.clearEndpointActivationRetry();
2387
2678
  this.pendingEndpointRefreshEvent = undefined;
2679
+ this.pendingEndpointRefreshSelectorClaimed = false;
2388
2680
  this.pendingEndpointAttestation = undefined;
2389
2681
  const refreshResult = this.endpointRefresh === undefined
2390
2682
  ? []
@@ -2413,6 +2705,7 @@ export class LocalCodexGatewayProvider {
2413
2705
  this.callbackQueue.length = 0;
2414
2706
  this.pendingEndpointAttestation = undefined;
2415
2707
  this.pendingEndpointRefreshEvent = undefined;
2708
+ this.pendingEndpointRefreshSelectorClaimed = false;
2416
2709
  this.trackedRoutes.clear();
2417
2710
  this.routeAliases.clear();
2418
2711
  this.expectsReply.clear();
@@ -2663,6 +2956,21 @@ export class LocalCodexGatewayProvider {
2663
2956
  event.current.endpointGeneration === this.factory.endpointGeneration &&
2664
2957
  event.routes.some((route) => route.routeHandle === routeHandle));
2665
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
+ }
2666
2974
  async refreshEndpoint(delivery) {
2667
2975
  if (this.refreshFactory === undefined || this.closing || this.closed) {
2668
2976
  return undefined;
@@ -2707,6 +3015,7 @@ export class LocalCodexGatewayProvider {
2707
3015
  this.compatibilityAttested = false;
2708
3016
  this.pendingEndpointAttestation = undefined;
2709
3017
  this.pendingEndpointRefreshEvent = undefined;
3018
+ this.pendingEndpointRefreshSelectorClaimed = false;
2710
3019
  for (const routeHandle of this.trackedRoutes) {
2711
3020
  this.routesRequiringRecovery.add(routeHandle);
2712
3021
  }
@@ -2721,7 +3030,9 @@ export class LocalCodexGatewayProvider {
2721
3030
  },
2722
3031
  delivery: "callback",
2723
3032
  selectorClaimed: true,
3033
+ published: false,
2724
3034
  };
3035
+ result.published = true;
2725
3036
  this.emitEndpointRefresh(result.event);
2726
3037
  for (const routeHandle of this.routesRequiringRecovery) {
2727
3038
  if (this.trackedRoutes.has(routeHandle)) {
@@ -2790,12 +3101,15 @@ export class LocalCodexGatewayProvider {
2790
3101
  },
2791
3102
  delivery: this.endpointRefreshDelivery ?? "callback",
2792
3103
  selectorClaimed: false,
3104
+ published: false,
2793
3105
  };
2794
3106
  if (result.event.outcome === "compatible") {
2795
3107
  this.pendingEndpointRefreshEvent = result.event;
3108
+ this.pendingEndpointRefreshSelectorClaimed = false;
2796
3109
  }
2797
3110
  if (result.delivery === "callback") {
2798
3111
  result.selectorClaimed = true;
3112
+ result.published = true;
2799
3113
  this.publishEndpointRefresh(result.event);
2800
3114
  }
2801
3115
  for (const routeHandle of this.routesRequiringRecovery) {
@@ -2914,11 +3228,16 @@ export class LocalCodexGatewayProvider {
2914
3228
  return false;
2915
3229
  }
2916
3230
  result.selectorClaimed = true;
3231
+ if (this.pendingEndpointRefreshEvent === result.event) {
3232
+ this.pendingEndpointRefreshSelectorClaimed = true;
3233
+ }
2917
3234
  return true;
2918
3235
  }
2919
3236
  emitSelectorRefreshFallback(result, staleRouteHandle) {
2920
3237
  if (result.delivery !== "selector" || !result.selectorClaimed)
2921
3238
  return;
3239
+ if (result.published)
3240
+ return;
2922
3241
  let event = result.event;
2923
3242
  if (staleRouteHandle !== undefined &&
2924
3243
  event.outcome === "compatible" &&
@@ -2930,6 +3249,7 @@ export class LocalCodexGatewayProvider {
2930
3249
  result.event = event;
2931
3250
  this.pendingEndpointRefreshEvent = event;
2932
3251
  }
3252
+ result.published = true;
2933
3253
  this.publishEndpointRefresh(event);
2934
3254
  }
2935
3255
  publishEndpointRefresh(event) {