agent-embassy 1.4.0 → 1.5.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.
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +61 -6
- package/README.md +6 -5
- package/README.zh-CN.md +4 -4
- package/SECURITY.md +128 -24
- package/dist/src/gateway/claude-peer.d.ts +8 -5
- package/dist/src/gateway/claude-peer.js +81 -36
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/claude-runtime.d.ts +4 -0
- package/dist/src/gateway/claude-runtime.js +50 -39
- package/dist/src/gateway/claude-runtime.js.map +1 -1
- package/dist/src/gateway/cli-copy.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.js +1 -1
- package/dist/src/gateway/cli-copy.en.js.map +1 -1
- package/dist/src/gateway/cli-copy.js +1 -1
- package/dist/src/gateway/cli-copy.js.map +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +1 -1
- package/dist/src/gateway/cli.js +3 -7
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-app-server.d.ts +2 -1
- package/dist/src/gateway/codex-app-server.js +1 -1
- package/dist/src/gateway/codex-app-server.js.map +1 -1
- package/dist/src/gateway/codex-local-transport.d.ts +10 -6
- package/dist/src/gateway/codex-local-transport.js +101 -56
- package/dist/src/gateway/codex-local-transport.js.map +1 -1
- package/dist/src/gateway/compatibility.d.ts +11 -0
- package/dist/src/gateway/compatibility.js +104 -22
- package/dist/src/gateway/compatibility.js.map +1 -1
- package/dist/src/gateway/control.js +28 -30
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.d.ts +1 -1
- package/dist/src/gateway/dashboard-copy.en.d.ts +36 -17
- package/dist/src/gateway/dashboard-copy.en.js +50 -31
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +36 -17
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +36 -17
- package/dist/src/gateway/dashboard-copy.zh-CN.js +50 -31
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +176 -8
- package/dist/src/gateway/dashboard-model.js +420 -70
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/dashboard.js +48 -135
- package/dist/src/gateway/dashboard.js.map +1 -1
- package/dist/src/gateway/live-dashboard-app/app.js +148 -247
- package/dist/src/gateway/live-dashboard-assets.js +2 -1
- package/dist/src/gateway/live-dashboard-assets.js.map +1 -1
- package/dist/src/gateway/live-dashboard-stream.d.ts +2 -2
- package/dist/src/gateway/live-dashboard-stream.js +2 -2
- package/dist/src/gateway/live-dashboard-stream.js.map +1 -1
- package/dist/src/gateway/progress-watch-machine.d.ts +50 -64
- package/dist/src/gateway/progress-watch-machine.js +42 -133
- package/dist/src/gateway/progress-watch-machine.js.map +1 -1
- package/dist/src/gateway/provenance-envelope.d.ts +2 -0
- package/dist/src/gateway/provenance-envelope.js +13 -3
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +69 -15
- package/dist/src/gateway/providers.js +456 -109
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +8 -5
- package/dist/src/gateway/server.js +94 -13
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +25 -1
- package/dist/src/gateway/service.js +252 -69
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/store.d.ts +23 -28
- package/dist/src/gateway/store.js +479 -467
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +38 -11
- package/dist/src/gateway/types.js +141 -3
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +8 -8
- package/docs/CONFIGURATION.zh-CN.md +8 -8
- package/docs/DASHBOARD.md +29 -6
- package/docs/DASHBOARD.zh-CN.md +1 -1
- package/docs/GATEWAY-ARCHITECTURE.md +143 -57
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +2 -2
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { BridgeError } from "../errors.js";
|
|
4
3
|
import { CLAUDE_PEER_COMPATIBILITY, ClaudePeerAdapter, } from "./claude-peer.js";
|
|
5
|
-
import { certifiedCompatibilityVersions, compatibilityProbeNames, evaluateCompatibilityAttestation, sharesCompatibilityMajor, } from "./compatibility.js";
|
|
4
|
+
import { certifiedCompatibilityVersions, compatibilityProbeNames, evaluateCompatibilityAttestation, isCompatibilityAttestation, isCompatibilityVersion, sharesCompatibilityMajor, UNKNOWN_COMPATIBILITY_VERSION, } from "./compatibility.js";
|
|
6
5
|
import { CODEX_APP_SERVER_WRITABLE_VERSIONS, CodexAppServerConnector, CodexConnectorError, } from "./codex-app-server.js";
|
|
7
6
|
import { LocalCodexTransportError } from "./codex-local-transport.js";
|
|
8
7
|
import { ClaudeNativeHelperSupervisor, } from "./claude-helper-supervisor.js";
|
|
9
8
|
import { isDashboardLocale } from "./locale.js";
|
|
10
9
|
import { composeProvenanceEnvelope } from "./provenance-envelope.js";
|
|
11
10
|
const LOCAL_HOST = "this-mac";
|
|
11
|
+
const STABLE_CLAUDE_ENDPOINT_GENERATION = "claude_local_endpoint";
|
|
12
12
|
const NATIVE_CLAUDE_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
13
13
|
const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
|
|
14
14
|
const OPAQUE_ROUTE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
@@ -30,6 +30,27 @@ const CLAUDE_CLEAN_PREWRITE_RETRY_CODES = new Set([
|
|
|
30
30
|
"CLAUDE_PEER_TARGET_CHANGED",
|
|
31
31
|
"CLAUDE_PEER_WORKSPACE_UNATTESTED",
|
|
32
32
|
]);
|
|
33
|
+
const CODEX_TRUST_FAILURE_CODES = new Set([
|
|
34
|
+
"HOME_INVALID",
|
|
35
|
+
"MANAGED_CODEX_INVALID",
|
|
36
|
+
"LOCAL_APP_SERVER_ENDPOINT_UNSAFE",
|
|
37
|
+
"APP_SERVER_VERSION_MISMATCH",
|
|
38
|
+
"ENDPOINT_GENERATION_CHANGED",
|
|
39
|
+
"CLEANUP_FAILED",
|
|
40
|
+
"FACTORY_CLOSED",
|
|
41
|
+
"INVALID_CONFIGURATION",
|
|
42
|
+
"CODEX_ROUTE_CLEANUP_FAILED",
|
|
43
|
+
"CODEX_PROVIDER_CLEANUP_FAILED",
|
|
44
|
+
"CODEX_PROVIDER_CLEANUP_AMBIGUOUS",
|
|
45
|
+
"CODEX_PROVIDER_CLEANUP_TIMEOUT",
|
|
46
|
+
]);
|
|
47
|
+
function isCodexTrustFailure(error) {
|
|
48
|
+
return (typeof error === "object" &&
|
|
49
|
+
error !== null &&
|
|
50
|
+
"code" in error &&
|
|
51
|
+
typeof error.code === "string" &&
|
|
52
|
+
CODEX_TRUST_FAILURE_CODES.has(error.code));
|
|
53
|
+
}
|
|
33
54
|
function claudeCleanPrewriteResult(error) {
|
|
34
55
|
if (error instanceof BridgeError &&
|
|
35
56
|
error.code === "CLAUDE_PEER_MESSAGE_EXPIRED") {
|
|
@@ -105,33 +126,184 @@ function invokeCallback(operation) {
|
|
|
105
126
|
}
|
|
106
127
|
}
|
|
107
128
|
function validateAttestedClaudeRuntime(runtime) {
|
|
108
|
-
if (runtime.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
if (runtime.versionEvidenceFailure !== undefined) {
|
|
130
|
+
throw new BridgeError(runtime.versionEvidenceFailure, "The local Claude provider requires bounded, internally consistent runtime version evidence.");
|
|
131
|
+
}
|
|
132
|
+
const majorEvidence = runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
|
|
133
|
+
? runtime.launcherVersionEvidence
|
|
134
|
+
: runtime.claudeCodeVersion;
|
|
135
|
+
if (majorEvidence === undefined ||
|
|
136
|
+
!sharesCompatibilityMajor(majorEvidence, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion)) {
|
|
137
|
+
throw new BridgeError(majorEvidence === undefined
|
|
138
|
+
? "CLAUDE_VERSION_UNPARSEABLE"
|
|
139
|
+
: "CLAUDE_PEER_VERSION_UNSUPPORTED", "The local Claude provider requires a positively observed supported runtime major.");
|
|
140
|
+
}
|
|
141
|
+
validateClaudeRuntimePathEvidence(runtime);
|
|
119
142
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
.
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
.slice(0, 32)}`;
|
|
143
|
+
function validateClaudeRuntimePathEvidence(runtime) {
|
|
144
|
+
if ([
|
|
145
|
+
runtime.claudeExecutable,
|
|
146
|
+
runtime.sessionsDir,
|
|
147
|
+
runtime.socketDir,
|
|
148
|
+
].some((value) => !path.isAbsolute(value) ||
|
|
149
|
+
path.normalize(value) !== value ||
|
|
150
|
+
value.includes("\0"))) {
|
|
151
|
+
throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "The local Claude provider requires bounded absolute runtime evidence.");
|
|
152
|
+
}
|
|
131
153
|
}
|
|
132
154
|
function claudeRouteState(peer) {
|
|
133
155
|
return peer.status === "idle" ? "idle" : "busy";
|
|
134
156
|
}
|
|
157
|
+
function claudeRegistryObservation(discovery) {
|
|
158
|
+
const rejected = Object.entries(discovery.rejected)
|
|
159
|
+
.filter((entry) => Number.isSafeInteger(entry[1]) && entry[1] > 0)
|
|
160
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
161
|
+
.map(([safeErrorCode, count]) => Object.freeze({ safeErrorCode, count }));
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
entriesScanned: discovery.entriesScanned,
|
|
164
|
+
parseableRecords: discovery.parseableRecords,
|
|
165
|
+
rejected: Object.freeze(rejected),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function unavailableClaudeRegistryObservation() {
|
|
169
|
+
return Object.freeze({
|
|
170
|
+
entriesScanned: 0,
|
|
171
|
+
parseableRecords: 0,
|
|
172
|
+
rejected: Object.freeze([
|
|
173
|
+
Object.freeze({
|
|
174
|
+
safeErrorCode: "CLAUDE_REGISTRY_UNAVAILABLE",
|
|
175
|
+
count: 1,
|
|
176
|
+
}),
|
|
177
|
+
]),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function isClaudeRegistryAvailabilityError(error) {
|
|
181
|
+
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return (error.code === "ENOENT" ||
|
|
185
|
+
error.code === "EACCES" ||
|
|
186
|
+
error.code === "EPERM" ||
|
|
187
|
+
error.code === "UNSAFE_PEER_DIRECTORY");
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A bounded observation-only surface for an installed provider generation
|
|
191
|
+
* whose protocol cannot be used safely. It deliberately performs no provider
|
|
192
|
+
* discovery, listener construction, route selection, or message write.
|
|
193
|
+
*/
|
|
194
|
+
export class IncompatibleGatewayProvider {
|
|
195
|
+
identity;
|
|
196
|
+
protocol = "not-attested";
|
|
197
|
+
protocolVersion = "unknown";
|
|
198
|
+
version;
|
|
199
|
+
probes;
|
|
200
|
+
safeErrorCode;
|
|
201
|
+
initialized = false;
|
|
202
|
+
closed = false;
|
|
203
|
+
constructor(options) {
|
|
204
|
+
const attestation = evaluateCompatibilityAttestation({
|
|
205
|
+
surface: options.identity.provider,
|
|
206
|
+
version: options.version,
|
|
207
|
+
checkedAt: "2000-01-01T00:00:00.000Z",
|
|
208
|
+
certifiedVersions: certifiedCompatibilityVersions[options.identity.provider],
|
|
209
|
+
probes: options.probes,
|
|
210
|
+
});
|
|
211
|
+
if (attestation.tier !== "incompatible" ||
|
|
212
|
+
attestation.safeErrorCode !== options.safeErrorCode) {
|
|
213
|
+
throw new BridgeError("INCOMPATIBLE_PROVIDER_EVIDENCE_INVALID", "A disabled provider requires exact bounded incompatibility evidence.");
|
|
214
|
+
}
|
|
215
|
+
exactLocalHost(options.identity.hostId);
|
|
216
|
+
this.identity = Object.freeze({ ...options.identity });
|
|
217
|
+
this.version = options.version;
|
|
218
|
+
this.probes = Object.freeze(options.probes.map((probe) => Object.freeze({ ...probe })));
|
|
219
|
+
this.safeErrorCode = options.safeErrorCode;
|
|
220
|
+
}
|
|
221
|
+
compatibilitySurface() {
|
|
222
|
+
return { surface: this.identity.provider, version: this.version };
|
|
223
|
+
}
|
|
224
|
+
async runCompatibilityProbes() {
|
|
225
|
+
return this.probes;
|
|
226
|
+
}
|
|
227
|
+
acceptCompatibilityAttestation(attestation) {
|
|
228
|
+
if (!isCompatibilityAttestation(attestation) ||
|
|
229
|
+
attestation.surface !== this.identity.provider ||
|
|
230
|
+
attestation.version !== this.version ||
|
|
231
|
+
attestation.tier !== "incompatible" ||
|
|
232
|
+
attestation.safeErrorCode !== this.safeErrorCode ||
|
|
233
|
+
attestation.probes.length !== this.probes.length ||
|
|
234
|
+
attestation.probes.some((probe, index) => probe.name !== this.probes[index]?.name ||
|
|
235
|
+
probe.outcome !== this.probes[index]?.outcome ||
|
|
236
|
+
probe.safeErrorCode !== this.probes[index]?.safeErrorCode)) {
|
|
237
|
+
throw new BridgeError("INCOMPATIBLE_PROVIDER_ATTESTATION_MISMATCH", "The compatibility attestation does not match this disabled provider surface.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async initialize(_callbacks) {
|
|
241
|
+
if (this.closed || this.initialized) {
|
|
242
|
+
throw new BridgeError("INCOMPATIBLE_PROVIDER_UNAVAILABLE", "The disabled provider surface cannot be initialized again.");
|
|
243
|
+
}
|
|
244
|
+
this.initialized = true;
|
|
245
|
+
return {
|
|
246
|
+
health: "degraded",
|
|
247
|
+
compatibility: "incompatible",
|
|
248
|
+
safeErrorCode: this.safeErrorCode,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async discoverClaudePeers() {
|
|
252
|
+
throw this.unavailable();
|
|
253
|
+
}
|
|
254
|
+
async selectRoute(_input) {
|
|
255
|
+
throw this.unavailable();
|
|
256
|
+
}
|
|
257
|
+
async assertWorkspaceDisjoint(_routeHandle, _stateRoot) {
|
|
258
|
+
throw this.unavailable();
|
|
259
|
+
}
|
|
260
|
+
async resolveReplyAddress(_address) {
|
|
261
|
+
throw this.unavailable();
|
|
262
|
+
}
|
|
263
|
+
async advertiseNativeCodexPeer(_input) {
|
|
264
|
+
throw this.unavailable();
|
|
265
|
+
}
|
|
266
|
+
async dispatch(_input) {
|
|
267
|
+
throw this.unavailable();
|
|
268
|
+
}
|
|
269
|
+
async releaseRoute(_routeHandle) { }
|
|
270
|
+
async close() {
|
|
271
|
+
this.closed = true;
|
|
272
|
+
}
|
|
273
|
+
unavailable() {
|
|
274
|
+
return new BridgeError(this.safeErrorCode, "This provider surface is observation-only because its compatibility boundary did not pass.");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export function createIncompatibleClaudeGatewayProvider(options) {
|
|
278
|
+
validateClaudeRuntimePathEvidence(options.runtime);
|
|
279
|
+
const [launcher, version, ...unobserved] = compatibilityProbeNames.claude;
|
|
280
|
+
return new IncompatibleGatewayProvider({
|
|
281
|
+
identity: {
|
|
282
|
+
provider: "claude",
|
|
283
|
+
hostId: LOCAL_HOST,
|
|
284
|
+
endpointGeneration: STABLE_CLAUDE_ENDPOINT_GENERATION,
|
|
285
|
+
},
|
|
286
|
+
version: options.version,
|
|
287
|
+
probes: [
|
|
288
|
+
passedProbe(launcher),
|
|
289
|
+
failedProbe(version, options.safeErrorCode),
|
|
290
|
+
...unobserved.map((name) => failedProbe(name, options.safeErrorCode)),
|
|
291
|
+
],
|
|
292
|
+
safeErrorCode: options.safeErrorCode,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
export function createIncompatibleCodexGatewayProvider(options) {
|
|
296
|
+
const [installation, ...unobserved] = compatibilityProbeNames.codex;
|
|
297
|
+
return new IncompatibleGatewayProvider({
|
|
298
|
+
identity: options.identity,
|
|
299
|
+
version: options.version,
|
|
300
|
+
probes: [
|
|
301
|
+
passedProbe(installation),
|
|
302
|
+
...unobserved.map((name) => failedProbe(name, options.safeErrorCode)),
|
|
303
|
+
],
|
|
304
|
+
safeErrorCode: options.safeErrorCode,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
135
307
|
export class LocalClaudeGatewayProvider {
|
|
136
308
|
identity;
|
|
137
309
|
protocol = "claude-peer";
|
|
@@ -165,6 +337,7 @@ export class LocalClaudeGatewayProvider {
|
|
|
165
337
|
/** Provider-owned terminal rejections that never enter the service queue. */
|
|
166
338
|
rejectedNativeInbound = new Map();
|
|
167
339
|
discoveryInFlight;
|
|
340
|
+
registryObservation;
|
|
168
341
|
monitorTimer;
|
|
169
342
|
/** Invocation-ordered registry mutations; the tail itself never rejects. */
|
|
170
343
|
nativeCodexRegistrationTail = Promise.resolve();
|
|
@@ -175,10 +348,25 @@ export class LocalClaudeGatewayProvider {
|
|
|
175
348
|
retiredNativeCodexListener;
|
|
176
349
|
/** Fences discovery callbacks and monitor restarts across listener rotation. */
|
|
177
350
|
nativeCodexSuccessionFreeze;
|
|
351
|
+
/** A completed failed probe set fences every authority-bearing operation. */
|
|
352
|
+
compatibilityBlocked = false;
|
|
353
|
+
compatibilityFailureCode;
|
|
178
354
|
initialized = false;
|
|
179
355
|
closed = false;
|
|
180
356
|
constructor(options) {
|
|
181
357
|
validateAttestedClaudeRuntime(options.runtime);
|
|
358
|
+
const compatibilityVersion = options.compatibilityVersion ??
|
|
359
|
+
(options.runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
|
|
360
|
+
? options.runtime.launcherVersionEvidence
|
|
361
|
+
: options.runtime.claudeCodeVersion);
|
|
362
|
+
if (compatibilityVersion === undefined ||
|
|
363
|
+
!isCompatibilityVersion(compatibilityVersion) ||
|
|
364
|
+
(compatibilityVersion !== options.runtime.claudeCodeVersion &&
|
|
365
|
+
!(options.runtime.claudeCodeVersion ===
|
|
366
|
+
UNKNOWN_COMPATIBILITY_VERSION &&
|
|
367
|
+
options.runtime.launcherVersionEvidence === compatibilityVersion))) {
|
|
368
|
+
throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "Claude compatibility classification requires exact bounded launcher evidence.");
|
|
369
|
+
}
|
|
182
370
|
if (options.locale !== undefined && !isDashboardLocale(options.locale)) {
|
|
183
371
|
throw new BridgeError("DASHBOARD_LOCALE_UNSUPPORTED", "The Claude provider notice locale is unsupported.");
|
|
184
372
|
}
|
|
@@ -186,12 +374,20 @@ export class LocalClaudeGatewayProvider {
|
|
|
186
374
|
this.identity = {
|
|
187
375
|
provider: "claude",
|
|
188
376
|
hostId,
|
|
189
|
-
endpointGeneration:
|
|
377
|
+
endpointGeneration: STABLE_CLAUDE_ENDPOINT_GENERATION,
|
|
190
378
|
};
|
|
191
|
-
this.runtimeVersion =
|
|
379
|
+
this.runtimeVersion = compatibilityVersion;
|
|
192
380
|
this.maxPending = positiveBounded(options.maxPendingMessages, MAX_CLAUDE_PENDING, 4_096);
|
|
193
381
|
this.discoveryPollMs = positiveBounded(options.discoveryPollMs, DEFAULT_CLAUDE_DISCOVERY_POLL_MS, 30_000);
|
|
194
382
|
this.now = options.now ?? Date.now;
|
|
383
|
+
const protocolRuntime = {
|
|
384
|
+
claudeExecutable: options.runtime.claudeExecutable,
|
|
385
|
+
claudeCodeVersion: options.runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
|
|
386
|
+
? options.runtime.launcherVersionEvidence
|
|
387
|
+
: options.runtime.claudeCodeVersion,
|
|
388
|
+
sessionsDir: options.runtime.sessionsDir,
|
|
389
|
+
socketDir: options.runtime.socketDir,
|
|
390
|
+
};
|
|
195
391
|
this.peer = (options.peerFactory ??
|
|
196
392
|
((runtime, locale, deliveryNotices) => new ClaudePeerAdapter({
|
|
197
393
|
sessionsDir: runtime.sessionsDir,
|
|
@@ -199,18 +395,18 @@ export class LocalClaudeGatewayProvider {
|
|
|
199
395
|
attestedClaudeCodeVersion: runtime.claudeCodeVersion,
|
|
200
396
|
locale,
|
|
201
397
|
deliveryNotices,
|
|
202
|
-
})))(
|
|
398
|
+
})))(protocolRuntime, options.locale ?? "en", options.deliveryNotices ?? "merged");
|
|
203
399
|
this.nativeHelpers =
|
|
204
400
|
options.nativeHelpers === undefined
|
|
205
401
|
? undefined
|
|
206
402
|
: new ClaudeNativeHelperSupervisor({
|
|
207
403
|
identity: this.identity,
|
|
208
|
-
runtime:
|
|
404
|
+
runtime: protocolRuntime,
|
|
209
405
|
locale: options.locale ?? "en",
|
|
210
406
|
deliveryNotices: options.deliveryNotices ?? "merged",
|
|
211
407
|
maxPendingMessages: this.maxPending,
|
|
212
408
|
maxHelpers: positiveBounded(options.nativeHelpers.maxHelpers, 256, 128),
|
|
213
|
-
callbacks: () => this.callbacks,
|
|
409
|
+
callbacks: () => this.compatibilityBlocked ? undefined : this.callbacks,
|
|
214
410
|
...(options.nativeHelpers.factory === undefined
|
|
215
411
|
? {}
|
|
216
412
|
: { factory: options.nativeHelpers.factory }),
|
|
@@ -223,33 +419,49 @@ export class LocalClaudeGatewayProvider {
|
|
|
223
419
|
const [launcher, version, registry, socket, protocol] = compatibilityProbeNames.claude;
|
|
224
420
|
try {
|
|
225
421
|
const discovery = await this.peer.discover();
|
|
226
|
-
|
|
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);
|
|
422
|
+
this.registryObservation = claudeRegistryObservation(discovery);
|
|
233
423
|
return [
|
|
234
424
|
passedProbe(launcher),
|
|
235
425
|
passedProbe(version),
|
|
236
|
-
|
|
237
|
-
? failedProbe(registry, "CLAUDE_REGISTRY_SCHEMA_REJECTED")
|
|
238
|
-
: passedProbe(registry),
|
|
426
|
+
passedProbe(registry),
|
|
239
427
|
passedProbe(socket),
|
|
240
428
|
passedProbe(protocol),
|
|
241
429
|
];
|
|
242
430
|
}
|
|
243
|
-
catch {
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (!isClaudeRegistryAvailabilityError(error))
|
|
433
|
+
throw error;
|
|
434
|
+
this.registryObservation = unavailableClaudeRegistryObservation();
|
|
435
|
+
// A missing or unreadable registry is provider-local incompatibility:
|
|
436
|
+
// keep the broker and the other surface alive, but fence this provider
|
|
437
|
+
// before listener construction can turn the same missing path into a
|
|
438
|
+
// broker-wide startup failure.
|
|
244
439
|
return [
|
|
245
440
|
passedProbe(launcher),
|
|
246
441
|
passedProbe(version),
|
|
247
442
|
failedProbe(registry, "CLAUDE_REGISTRY_UNAVAILABLE"),
|
|
248
|
-
failedProbe(socket, "
|
|
443
|
+
failedProbe(socket, "CLAUDE_REGISTRY_UNAVAILABLE"),
|
|
249
444
|
passedProbe(protocol),
|
|
250
445
|
];
|
|
251
446
|
}
|
|
252
447
|
}
|
|
448
|
+
latestRegistryObservation() {
|
|
449
|
+
return this.registryObservation;
|
|
450
|
+
}
|
|
451
|
+
acceptCompatibilityAttestation(attestation) {
|
|
452
|
+
if (!isCompatibilityAttestation(attestation) ||
|
|
453
|
+
attestation.surface !== "claude" ||
|
|
454
|
+
attestation.version !== this.runtimeVersion) {
|
|
455
|
+
throw new BridgeError("CLAUDE_COMPATIBILITY_ATTESTATION_MISMATCH", "The Claude compatibility attestation does not match this runtime.");
|
|
456
|
+
}
|
|
457
|
+
this.compatibilityBlocked = attestation.tier === "incompatible";
|
|
458
|
+
this.compatibilityFailureCode = attestation.safeErrorCode;
|
|
459
|
+
if (this.compatibilityBlocked) {
|
|
460
|
+
if (this.monitorTimer !== undefined)
|
|
461
|
+
clearTimeout(this.monitorTimer);
|
|
462
|
+
this.monitorTimer = undefined;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
253
465
|
async initialize(callbacks) {
|
|
254
466
|
if (this.closed) {
|
|
255
467
|
throw new BridgeError("CLAUDE_PROVIDER_CLOSED", "The local Claude provider is closed.");
|
|
@@ -258,6 +470,14 @@ export class LocalClaudeGatewayProvider {
|
|
|
258
470
|
throw new BridgeError("CLAUDE_PROVIDER_ALREADY_INITIALIZED", "The local Claude provider can be initialized only once.");
|
|
259
471
|
}
|
|
260
472
|
this.callbacks = callbacks;
|
|
473
|
+
if (this.compatibilityBlocked) {
|
|
474
|
+
this.initialized = true;
|
|
475
|
+
return {
|
|
476
|
+
health: "degraded",
|
|
477
|
+
compatibility: "incompatible",
|
|
478
|
+
safeErrorCode: this.compatibilityFailureCode ?? "CLAUDE_COMPATIBILITY_CHECK_FAILED",
|
|
479
|
+
};
|
|
480
|
+
}
|
|
261
481
|
if (this.nativeHelpers !== undefined) {
|
|
262
482
|
this.initialized = true;
|
|
263
483
|
return { health: "healthy", compatibility: "compatible" };
|
|
@@ -297,8 +517,24 @@ export class LocalClaudeGatewayProvider {
|
|
|
297
517
|
this.initialized = true;
|
|
298
518
|
return { health: "healthy", compatibility: "compatible" };
|
|
299
519
|
}
|
|
300
|
-
catch {
|
|
520
|
+
catch (error) {
|
|
521
|
+
if (error instanceof BridgeError &&
|
|
522
|
+
error.code === "UNSAFE_PEER_DIRECTORY") {
|
|
523
|
+
this.compatibilityBlocked = true;
|
|
524
|
+
this.compatibilityFailureCode = "CLAUDE_REGISTRY_UNAVAILABLE";
|
|
525
|
+
this.registryObservation = unavailableClaudeRegistryObservation();
|
|
526
|
+
this.initialized = true;
|
|
527
|
+
return {
|
|
528
|
+
health: "degraded",
|
|
529
|
+
compatibility: "incompatible",
|
|
530
|
+
safeErrorCode: "CLAUDE_REGISTRY_UNAVAILABLE",
|
|
531
|
+
};
|
|
532
|
+
}
|
|
301
533
|
this.callbacks = undefined;
|
|
534
|
+
if (error instanceof BridgeError &&
|
|
535
|
+
error.code === "CLAUDE_PEER_CALLBACK_UNSAFE") {
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
302
538
|
throw new BridgeError("CLAUDE_CALLBACK_UNAVAILABLE", "The private Claude callback listener could not be established.");
|
|
303
539
|
}
|
|
304
540
|
}
|
|
@@ -769,6 +1005,12 @@ export class LocalClaudeGatewayProvider {
|
|
|
769
1005
|
active.inboundQuiesced = true;
|
|
770
1006
|
}
|
|
771
1007
|
async dispatch(input) {
|
|
1008
|
+
if (this.compatibilityBlocked) {
|
|
1009
|
+
return {
|
|
1010
|
+
state: "failed",
|
|
1011
|
+
safeErrorCode: this.compatibilityFailureCode ?? "CLAUDE_COMPATIBILITY_CHECK_FAILED",
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
772
1014
|
if (this.nativeHelpers !== undefined) {
|
|
773
1015
|
if (input.sourceAlias === undefined ||
|
|
774
1016
|
!this.initialized ||
|
|
@@ -1050,7 +1292,7 @@ export class LocalClaudeGatewayProvider {
|
|
|
1050
1292
|
}
|
|
1051
1293
|
}
|
|
1052
1294
|
assertReady() {
|
|
1053
|
-
if (!this.initialized || this.closed) {
|
|
1295
|
+
if (!this.initialized || this.closed || this.compatibilityBlocked) {
|
|
1054
1296
|
throw new BridgeError("CLAUDE_PROVIDER_UNAVAILABLE", "The local Claude provider is not available.", true);
|
|
1055
1297
|
}
|
|
1056
1298
|
}
|
|
@@ -1109,7 +1351,8 @@ export class LocalClaudeGatewayProvider {
|
|
|
1109
1351
|
await this.rejectNativeInboundReceipt(listenerGeneration, message.receiptHandle, diagnosticCode);
|
|
1110
1352
|
}
|
|
1111
1353
|
};
|
|
1112
|
-
if (this.
|
|
1354
|
+
if (this.compatibilityBlocked ||
|
|
1355
|
+
this.closed ||
|
|
1113
1356
|
listenerGeneration.listener.closed ||
|
|
1114
1357
|
!this.ownsNativeCodexListener(listenerGeneration)) {
|
|
1115
1358
|
try {
|
|
@@ -1433,13 +1676,21 @@ export class LocalClaudeGatewayProvider {
|
|
|
1433
1676
|
dispatchEpochAtStart.set(routeHandle, this.selectedDispatchEpoch.get(routeHandle) ?? 0);
|
|
1434
1677
|
}
|
|
1435
1678
|
const pendingAtStart = new Set(this.pendingTargetByGatewayId.values());
|
|
1436
|
-
|
|
1679
|
+
let discovery;
|
|
1680
|
+
try {
|
|
1681
|
+
discovery = await this.peer.discover();
|
|
1682
|
+
}
|
|
1683
|
+
catch (error) {
|
|
1684
|
+
this.registryObservation = unavailableClaudeRegistryObservation();
|
|
1685
|
+
throw error;
|
|
1686
|
+
}
|
|
1687
|
+
const registry = claudeRegistryObservation(discovery);
|
|
1688
|
+
this.registryObservation = registry;
|
|
1437
1689
|
this.discovered.clear();
|
|
1438
1690
|
const rows = [];
|
|
1439
1691
|
for (const peer of discovery.peers) {
|
|
1440
1692
|
if (peer.kind !== "interactive" ||
|
|
1441
|
-
!NATIVE_CLAUDE_NAME.test(peer.alias)
|
|
1442
|
-
peer.alias.startsWith("codex-")) {
|
|
1693
|
+
!NATIVE_CLAUDE_NAME.test(peer.alias)) {
|
|
1443
1694
|
continue;
|
|
1444
1695
|
}
|
|
1445
1696
|
const alias = `${peer.alias}@${this.identity.hostId}`;
|
|
@@ -1480,7 +1731,7 @@ export class LocalClaudeGatewayProvider {
|
|
|
1480
1731
|
this.emitClaudeRouteObservation(routeHandle, row.state, undefined, true);
|
|
1481
1732
|
}
|
|
1482
1733
|
}
|
|
1483
|
-
return { peers: rows, complete: !discovery.truncated };
|
|
1734
|
+
return { peers: rows, complete: !discovery.truncated, registry };
|
|
1484
1735
|
}
|
|
1485
1736
|
emitClaudeRouteObservation(routeHandle, state, safeErrorCode, authoritative = false) {
|
|
1486
1737
|
if (this.nativeCodexSuccessionFreeze !== undefined)
|
|
@@ -1540,33 +1791,52 @@ export class LocalClaudeGatewayProvider {
|
|
|
1540
1791
|
export function createLocalClaudeGatewayProvider(options) {
|
|
1541
1792
|
return new LocalClaudeGatewayProvider(options);
|
|
1542
1793
|
}
|
|
1543
|
-
function
|
|
1794
|
+
function codexVersionFailureCode(version) {
|
|
1795
|
+
if (version === UNKNOWN_COMPATIBILITY_VERSION) {
|
|
1796
|
+
return "CODEX_APP_SERVER_VERSION_UNPARSEABLE";
|
|
1797
|
+
}
|
|
1798
|
+
if (!isCompatibilityVersion(version)) {
|
|
1799
|
+
throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The local Codex factory version evidence is malformed.");
|
|
1800
|
+
}
|
|
1801
|
+
return CODEX_APP_SERVER_WRITABLE_VERSIONS.some((certifiedVersion) => sharesCompatibilityMajor(certifiedVersion, version))
|
|
1802
|
+
? undefined
|
|
1803
|
+
: "CODEX_APP_SERVER_VERSION_UNSUPPORTED";
|
|
1804
|
+
}
|
|
1805
|
+
function validateCodexFactory(factory, options = {}) {
|
|
1544
1806
|
const schema = factory.schemaCompatibility;
|
|
1545
1807
|
const write = factory.writeCompatibility;
|
|
1808
|
+
const versionFailure = codexVersionFailureCode(factory.appServerVersion);
|
|
1546
1809
|
const certified = CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(factory.appServerVersion);
|
|
1547
1810
|
const schemaCandidate = schema.observedSchemaCandidate === true &&
|
|
1548
1811
|
CODEX_APP_SERVER_WRITABLE_VERSIONS.some((version) => sharesCompatibilityMajor(version, factory.appServerVersion));
|
|
1812
|
+
const unsupportedVersionInvalid = versionFailure !== undefined &&
|
|
1813
|
+
(options.allowUnsupportedVersion !== true ||
|
|
1814
|
+
write !== null ||
|
|
1815
|
+
factory.writableReady);
|
|
1816
|
+
const supportedSchemaInvalid = versionFailure === undefined &&
|
|
1817
|
+
((!certified &&
|
|
1818
|
+
(!schemaCandidate || write !== null || factory.writableReady)) ||
|
|
1819
|
+
schema.protocol !== "app-server-v2-stable" ||
|
|
1820
|
+
schema.steering?.method !== "turn/steer" ||
|
|
1821
|
+
schema.steering.requestSchema !== "expected-turn-id-text-v1" ||
|
|
1822
|
+
schema.steering.deliveryBoundary !== "next-tool-call-boundary" ||
|
|
1823
|
+
(write !== null &&
|
|
1824
|
+
(write.appServerVersion !== schema.appServerVersion ||
|
|
1825
|
+
write.endpointGeneration !== schema.endpointGeneration ||
|
|
1826
|
+
write.protocol !== schema.protocol ||
|
|
1827
|
+
write.observedSchemaCandidate !== schema.observedSchemaCandidate ||
|
|
1828
|
+
write.steering?.method !== schema.steering.method ||
|
|
1829
|
+
write.steering.requestSchema !== schema.steering.requestSchema ||
|
|
1830
|
+
write.steering.deliveryBoundary !==
|
|
1831
|
+
schema.steering.deliveryBoundary)) ||
|
|
1832
|
+
factory.writableReady !== (write !== null));
|
|
1549
1833
|
if (factory.hostId !== LOCAL_HOST ||
|
|
1550
1834
|
factory.protocol !== "codex-app-server" ||
|
|
1551
1835
|
factory.protocolVersion !== factory.appServerVersion ||
|
|
1552
|
-
(!certified &&
|
|
1553
|
-
(!schemaCandidate || write !== null || factory.writableReady)) ||
|
|
1554
1836
|
schema.appServerVersion !== factory.appServerVersion ||
|
|
1555
1837
|
schema.endpointGeneration !== factory.endpointGeneration ||
|
|
1556
|
-
|
|
1557
|
-
|
|
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)) {
|
|
1838
|
+
unsupportedVersionInvalid ||
|
|
1839
|
+
supportedSchemaInvalid) {
|
|
1570
1840
|
throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The local Codex provider requires exact schema and write attestations.");
|
|
1571
1841
|
}
|
|
1572
1842
|
}
|
|
@@ -1628,6 +1898,7 @@ export class LocalCodexGatewayProvider {
|
|
|
1628
1898
|
endpointActivationRetryTimer;
|
|
1629
1899
|
callbackDrainFrozen = false;
|
|
1630
1900
|
compatibilityAttested;
|
|
1901
|
+
compatibilityFailureCode;
|
|
1631
1902
|
endpointUnavailable = false;
|
|
1632
1903
|
pendingEndpointAttestation;
|
|
1633
1904
|
pendingEndpointRefreshEvent;
|
|
@@ -1673,26 +1944,48 @@ export class LocalCodexGatewayProvider {
|
|
|
1673
1944
|
}
|
|
1674
1945
|
acceptCompatibilityAttestation(attestation) {
|
|
1675
1946
|
const pending = this.pendingEndpointAttestation;
|
|
1676
|
-
|
|
1947
|
+
const pendingMatches = pending === undefined ||
|
|
1948
|
+
(attestation.checkedAt === pending.checkedAt &&
|
|
1949
|
+
attestation.tier === pending.tier &&
|
|
1950
|
+
attestation.safeErrorCode === pending.safeErrorCode &&
|
|
1951
|
+
attestation.probes.length === pending.probes.length &&
|
|
1952
|
+
attestation.probes.every((probe, index) => probe.name === pending.probes[index]?.name &&
|
|
1953
|
+
probe.outcome === pending.probes[index]?.outcome &&
|
|
1954
|
+
probe.safeErrorCode === pending.probes[index]?.safeErrorCode));
|
|
1955
|
+
if (!isCompatibilityAttestation(attestation) ||
|
|
1956
|
+
attestation.surface !== "codex" ||
|
|
1677
1957
|
attestation.version !== this.factory.appServerVersion ||
|
|
1678
|
-
|
|
1679
|
-
|
|
1958
|
+
!pendingMatches) {
|
|
1959
|
+
throw new BridgeError("CODEX_COMPATIBILITY_ATTESTATION_MISMATCH", "The Codex compatibility attestation does not match this endpoint.");
|
|
1960
|
+
}
|
|
1961
|
+
if (attestation.tier === "incompatible") {
|
|
1962
|
+
this.pendingEndpointAttestation = undefined;
|
|
1963
|
+
this.pendingEndpointRefreshEvent = undefined;
|
|
1964
|
+
this.clearEndpointActivationRetry();
|
|
1965
|
+
this.compatibilityAttested = false;
|
|
1966
|
+
this.endpointUnavailable = true;
|
|
1967
|
+
this.compatibilityFailureCode =
|
|
1968
|
+
attestation.safeErrorCode ?? "CODEX_COMPATIBILITY_CHECK_FAILED";
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
const writableCertified = attestation.tier === "certified" &&
|
|
1972
|
+
CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(attestation.version) &&
|
|
1973
|
+
this.factory.writableReady &&
|
|
1974
|
+
this.factory.writeCompatibility !== null;
|
|
1975
|
+
const schemaObserved = attestation.tier === "schema_attested" &&
|
|
1976
|
+
!this.factory.writableReady &&
|
|
1977
|
+
this.factory.writeCompatibility === null;
|
|
1978
|
+
if ((!writableCertified && !schemaObserved) ||
|
|
1680
1979
|
(this.endpointUnavailable && pending === undefined) ||
|
|
1681
|
-
|
|
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)))) {
|
|
1980
|
+
!pendingMatches) {
|
|
1689
1981
|
throw new BridgeError("CODEX_COMPATIBILITY_ATTESTATION_MISMATCH", "The Codex compatibility attestation does not match this endpoint.");
|
|
1690
1982
|
}
|
|
1691
1983
|
this.pendingEndpointAttestation = undefined;
|
|
1692
1984
|
this.pendingEndpointRefreshEvent = undefined;
|
|
1693
1985
|
this.clearEndpointActivationRetry();
|
|
1694
|
-
this.compatibilityAttested =
|
|
1695
|
-
this.endpointUnavailable =
|
|
1986
|
+
this.compatibilityAttested = writableCertified;
|
|
1987
|
+
this.endpointUnavailable = !writableCertified;
|
|
1988
|
+
this.compatibilityFailureCode = undefined;
|
|
1696
1989
|
}
|
|
1697
1990
|
async runCompatibilityProbes() {
|
|
1698
1991
|
return await this.runCompatibilityProbesFor(this.factory);
|
|
@@ -1731,19 +2024,26 @@ export class LocalCodexGatewayProvider {
|
|
|
1731
2024
|
if (this.isEndpointGenerationChanged(error))
|
|
1732
2025
|
throw error;
|
|
1733
2026
|
if (stage === "transport") {
|
|
1734
|
-
const
|
|
2027
|
+
const availabilityFailure = error instanceof LocalCodexTransportError &&
|
|
1735
2028
|
[
|
|
2029
|
+
"MANAGED_CODEX_UNAVAILABLE",
|
|
1736
2030
|
"LOCAL_APP_SERVER_NOT_RUNNING",
|
|
1737
|
-
"
|
|
2031
|
+
"SPAWN_FAILED",
|
|
2032
|
+
"SPAWN_TIMEOUT",
|
|
2033
|
+
"PROXY_STDERR_LIMIT",
|
|
1738
2034
|
"TRANSPORT_CONNECT_FAILED",
|
|
1739
2035
|
].includes(error.code);
|
|
2036
|
+
if (!availabilityFailure)
|
|
2037
|
+
throw error;
|
|
2038
|
+
const installationUnavailable = error instanceof LocalCodexTransportError &&
|
|
2039
|
+
error.code === "MANAGED_CODEX_UNAVAILABLE";
|
|
1740
2040
|
return [
|
|
1741
|
-
|
|
1742
|
-
?
|
|
1743
|
-
:
|
|
1744
|
-
failedProbe(control,
|
|
1745
|
-
? "
|
|
1746
|
-
: "
|
|
2041
|
+
installationUnavailable
|
|
2042
|
+
? failedProbe(installation, "CODEX_INSTALLATION_INVALID")
|
|
2043
|
+
: passedProbe(installation),
|
|
2044
|
+
failedProbe(control, installationUnavailable
|
|
2045
|
+
? "CODEX_COMPAT_PROBE_BLOCKED"
|
|
2046
|
+
: "CODEX_CONTROL_SOCKET_UNAVAILABLE"),
|
|
1747
2047
|
failedProbe(initialize, "CODEX_COMPAT_PROBE_BLOCKED"),
|
|
1748
2048
|
failedProbe(threadList, "CODEX_COMPAT_PROBE_BLOCKED"),
|
|
1749
2049
|
];
|
|
@@ -1759,10 +2059,10 @@ export class LocalCodexGatewayProvider {
|
|
|
1759
2059
|
}
|
|
1760
2060
|
finally {
|
|
1761
2061
|
if (connector !== undefined) {
|
|
1762
|
-
await connector.close()
|
|
2062
|
+
await connector.close();
|
|
1763
2063
|
}
|
|
1764
2064
|
else if (transport !== undefined) {
|
|
1765
|
-
await transport.close()
|
|
2065
|
+
await transport.close();
|
|
1766
2066
|
}
|
|
1767
2067
|
}
|
|
1768
2068
|
}
|
|
@@ -1772,6 +2072,13 @@ export class LocalCodexGatewayProvider {
|
|
|
1772
2072
|
}
|
|
1773
2073
|
this.callbacks = callbacks;
|
|
1774
2074
|
this.initialized = true;
|
|
2075
|
+
if (this.compatibilityFailureCode !== undefined) {
|
|
2076
|
+
return {
|
|
2077
|
+
health: "degraded",
|
|
2078
|
+
compatibility: "incompatible",
|
|
2079
|
+
safeErrorCode: this.compatibilityFailureCode,
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
1775
2082
|
return this.factory.writeCompatibility === null
|
|
1776
2083
|
? {
|
|
1777
2084
|
health: "degraded",
|
|
@@ -1977,6 +2284,9 @@ export class LocalCodexGatewayProvider {
|
|
|
1977
2284
|
...(input.progressWatchActive === true
|
|
1978
2285
|
? { progressWatchActive: true }
|
|
1979
2286
|
: {}),
|
|
2287
|
+
...(input.steer === true && input.queuedAhead !== undefined
|
|
2288
|
+
? { queuedAhead: input.queuedAhead }
|
|
2289
|
+
: {}),
|
|
1980
2290
|
});
|
|
1981
2291
|
}
|
|
1982
2292
|
catch (error) {
|
|
@@ -2214,13 +2524,23 @@ export class LocalCodexGatewayProvider {
|
|
|
2214
2524
|
return route;
|
|
2215
2525
|
}
|
|
2216
2526
|
catch (error) {
|
|
2527
|
+
let cleanupFailure;
|
|
2217
2528
|
if (connector !== undefined) {
|
|
2218
|
-
await connector.close().catch(() =>
|
|
2529
|
+
await connector.close().catch((closeError) => {
|
|
2530
|
+
cleanupFailure = closeError;
|
|
2531
|
+
});
|
|
2219
2532
|
}
|
|
2220
2533
|
else if (transport !== undefined) {
|
|
2221
|
-
await transport.close().catch(() =>
|
|
2534
|
+
await transport.close().catch((closeError) => {
|
|
2535
|
+
cleanupFailure = closeError;
|
|
2536
|
+
});
|
|
2222
2537
|
}
|
|
2223
|
-
if (
|
|
2538
|
+
if (cleanupFailure !== undefined) {
|
|
2539
|
+
if (isCodexTrustFailure(cleanupFailure))
|
|
2540
|
+
throw cleanupFailure;
|
|
2541
|
+
throw new BridgeError("CODEX_ROUTE_CLEANUP_FAILED", "Codex route setup could not prove exact provider-process cleanup.");
|
|
2542
|
+
}
|
|
2543
|
+
if (isCodexTrustFailure(error))
|
|
2224
2544
|
throw error;
|
|
2225
2545
|
if (error instanceof BridgeError)
|
|
2226
2546
|
throw error;
|
|
@@ -2415,18 +2735,14 @@ export class LocalCodexGatewayProvider {
|
|
|
2415
2735
|
candidate = undefined;
|
|
2416
2736
|
return undefined;
|
|
2417
2737
|
}
|
|
2418
|
-
|
|
2419
|
-
candidate = undefined;
|
|
2420
|
-
this.retiredEndpointGeneration = undefined;
|
|
2421
|
-
this.compatibilityAttested = false;
|
|
2422
|
-
this.endpointUnavailable = true;
|
|
2423
|
-
this.pendingEndpointAttestation = attestation;
|
|
2738
|
+
const replacementFactory = candidate;
|
|
2424
2739
|
const refreshedRoutes = [];
|
|
2740
|
+
const stagedRoutes = [];
|
|
2425
2741
|
for (const routeHandle of [...this.trackedRoutes]) {
|
|
2426
2742
|
if (this.closing || this.closed)
|
|
2427
2743
|
break;
|
|
2428
2744
|
try {
|
|
2429
|
-
const route = await this.createRoute(routeHandle,
|
|
2745
|
+
const route = await this.createRoute(routeHandle, replacementFactory, false);
|
|
2430
2746
|
if (this.closing || this.closed) {
|
|
2431
2747
|
await this.closeRoute(route);
|
|
2432
2748
|
break;
|
|
@@ -2436,17 +2752,34 @@ export class LocalCodexGatewayProvider {
|
|
|
2436
2752
|
await this.closeRoute(route);
|
|
2437
2753
|
throw new BridgeError("CODEX_ROUTE_SETUP_REJECTED", "The exact Codex task was stale on the replacement endpoint.");
|
|
2438
2754
|
}
|
|
2439
|
-
|
|
2440
|
-
this.routesRequiringRecovery.delete(routeHandle);
|
|
2441
|
-
this.routeRecoveryAttempts.delete(routeHandle);
|
|
2442
|
-
refreshedRoutes.push({ routeHandle, state });
|
|
2755
|
+
stagedRoutes.push({ routeHandle, route, state });
|
|
2443
2756
|
}
|
|
2444
|
-
catch {
|
|
2757
|
+
catch (error) {
|
|
2445
2758
|
this.routesRequiringRecovery.add(routeHandle);
|
|
2759
|
+
if (isCodexTrustFailure(error))
|
|
2760
|
+
throw error;
|
|
2446
2761
|
}
|
|
2447
2762
|
}
|
|
2448
|
-
if (this.closing || this.closed)
|
|
2763
|
+
if (this.closing || this.closed) {
|
|
2764
|
+
await replacementFactory.close();
|
|
2765
|
+
candidate = undefined;
|
|
2449
2766
|
return undefined;
|
|
2767
|
+
}
|
|
2768
|
+
this.factory = replacementFactory;
|
|
2769
|
+
candidate = undefined;
|
|
2770
|
+
this.retiredEndpointGeneration = undefined;
|
|
2771
|
+
this.compatibilityAttested = false;
|
|
2772
|
+
this.endpointUnavailable = true;
|
|
2773
|
+
this.pendingEndpointAttestation = attestation;
|
|
2774
|
+
for (const staged of stagedRoutes) {
|
|
2775
|
+
this.routes.set(staged.routeHandle, staged.route);
|
|
2776
|
+
this.routesRequiringRecovery.delete(staged.routeHandle);
|
|
2777
|
+
this.routeRecoveryAttempts.delete(staged.routeHandle);
|
|
2778
|
+
refreshedRoutes.push({
|
|
2779
|
+
routeHandle: staged.routeHandle,
|
|
2780
|
+
state: staged.state,
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2450
2783
|
const result = {
|
|
2451
2784
|
event: {
|
|
2452
2785
|
outcome: "compatible",
|
|
@@ -2474,7 +2807,7 @@ export class LocalCodexGatewayProvider {
|
|
|
2474
2807
|
}
|
|
2475
2808
|
catch (error) {
|
|
2476
2809
|
if (candidate !== undefined && candidate !== previousFactory) {
|
|
2477
|
-
await candidate.close()
|
|
2810
|
+
await candidate.close();
|
|
2478
2811
|
}
|
|
2479
2812
|
throw error;
|
|
2480
2813
|
}
|
|
@@ -2490,7 +2823,10 @@ export class LocalCodexGatewayProvider {
|
|
|
2490
2823
|
let candidate;
|
|
2491
2824
|
try {
|
|
2492
2825
|
candidate = await refreshFactory();
|
|
2493
|
-
|
|
2826
|
+
const versionFailure = codexVersionFailureCode(candidate.appServerVersion);
|
|
2827
|
+
validateCodexFactory(candidate, {
|
|
2828
|
+
allowUnsupportedVersion: versionFailure !== undefined,
|
|
2829
|
+
});
|
|
2494
2830
|
if (candidate.hostId !== previous.hostId ||
|
|
2495
2831
|
candidate.protocol !== previousFactory.protocol) {
|
|
2496
2832
|
throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The replacement Codex factory moved outside its pinned boundary.");
|
|
@@ -2500,14 +2836,25 @@ export class LocalCodexGatewayProvider {
|
|
|
2500
2836
|
if (candidate !== previousFactory)
|
|
2501
2837
|
await candidate.close();
|
|
2502
2838
|
candidate = undefined;
|
|
2839
|
+
if (this.endpointUnavailable &&
|
|
2840
|
+
this.compatibilityFailureCode === undefined &&
|
|
2841
|
+
!this.factory.writableReady &&
|
|
2842
|
+
this.factory.writeCompatibility === null) {
|
|
2843
|
+
throw new BridgeError("CODEX_MONITOR_ONLY", "This Codex App Server remains monitor-only until a write-attested generation is available.");
|
|
2844
|
+
}
|
|
2503
2845
|
if (attempt + 1 < MAX_CODEX_ENDPOINT_REFRESH_CANDIDATES) {
|
|
2504
2846
|
continue;
|
|
2505
2847
|
}
|
|
2506
2848
|
throw new BridgeError("CODEX_ENDPOINT_GENERATION_CHURN", "The Codex endpoint did not stabilize within its bounded refresh window.", true);
|
|
2507
2849
|
}
|
|
2508
|
-
// Endpoint generations never inherit cached compatibility.
|
|
2509
|
-
//
|
|
2510
|
-
|
|
2850
|
+
// Endpoint generations never inherit cached compatibility. A version
|
|
2851
|
+
// outside the tested major is classified from OS-attested factory
|
|
2852
|
+
// evidence alone; no connector or transport may touch that protocol.
|
|
2853
|
+
const probes = versionFailure === undefined
|
|
2854
|
+
? await this.runCompatibilityProbesFor(candidate)
|
|
2855
|
+
: compatibilityProbeNames.codex.map((name, index) => index === 0
|
|
2856
|
+
? passedProbe(name)
|
|
2857
|
+
: failedProbe(name, versionFailure));
|
|
2511
2858
|
const attestation = evaluateCompatibilityAttestation({
|
|
2512
2859
|
surface: "codex",
|
|
2513
2860
|
version: candidate.appServerVersion,
|