agent-embassy 1.6.1 → 1.7.1

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 (105) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +9 -9
  3. package/README.zh-CN.md +9 -9
  4. package/SECURITY.md +34 -77
  5. package/dist/src/gateway/acp-client.d.ts +117 -0
  6. package/dist/src/gateway/acp-client.js +382 -0
  7. package/dist/src/gateway/acp-client.js.map +1 -0
  8. package/dist/src/gateway/acp-provider.d.ts +65 -0
  9. package/dist/src/gateway/acp-provider.js +163 -0
  10. package/dist/src/gateway/acp-provider.js.map +1 -0
  11. package/dist/src/gateway/claude-helper-protocol.d.ts +3 -1
  12. package/dist/src/gateway/claude-helper-protocol.js +15 -5
  13. package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
  14. package/dist/src/gateway/claude-helper-supervisor.d.ts +3 -1
  15. package/dist/src/gateway/claude-helper-supervisor.js +16 -2
  16. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
  17. package/dist/src/gateway/claude-helper.js +4 -3
  18. package/dist/src/gateway/claude-helper.js.map +1 -1
  19. package/dist/src/gateway/claude-peer.d.ts +1 -1
  20. package/dist/src/gateway/claude-peer.js +11 -13
  21. package/dist/src/gateway/claude-peer.js.map +1 -1
  22. package/dist/src/gateway/claude-runtime.d.ts +0 -20
  23. package/dist/src/gateway/claude-runtime.js +6 -127
  24. package/dist/src/gateway/claude-runtime.js.map +1 -1
  25. package/dist/src/gateway/cli-copy.d.ts +1 -1
  26. package/dist/src/gateway/cli-copy.en.d.ts +3 -1
  27. package/dist/src/gateway/cli-copy.en.js +5 -2
  28. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  29. package/dist/src/gateway/cli-copy.js +2 -0
  30. package/dist/src/gateway/cli-copy.js.map +1 -1
  31. package/dist/src/gateway/cli-copy.zh-CN.d.ts +3 -1
  32. package/dist/src/gateway/cli-copy.zh-CN.js +5 -2
  33. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  34. package/dist/src/gateway/cli.d.ts +2 -2
  35. package/dist/src/gateway/cli.js +73 -6
  36. package/dist/src/gateway/cli.js.map +1 -1
  37. package/dist/src/gateway/codex-app-server.d.ts +5 -74
  38. package/dist/src/gateway/codex-app-server.js +9 -599
  39. package/dist/src/gateway/codex-app-server.js.map +1 -1
  40. package/dist/src/gateway/codex-doctor.d.ts +33 -0
  41. package/dist/src/gateway/codex-doctor.js +148 -0
  42. package/dist/src/gateway/codex-doctor.js.map +1 -0
  43. package/dist/src/gateway/codex-local-transport.d.ts +13 -26
  44. package/dist/src/gateway/codex-local-transport.js +31 -76
  45. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  46. package/dist/src/gateway/compatibility.d.ts +6 -66
  47. package/dist/src/gateway/compatibility.js +14 -296
  48. package/dist/src/gateway/compatibility.js.map +1 -1
  49. package/dist/src/gateway/config.d.ts +9 -0
  50. package/dist/src/gateway/config.js +6 -2
  51. package/dist/src/gateway/config.js.map +1 -1
  52. package/dist/src/gateway/control.d.ts +16 -1
  53. package/dist/src/gateway/control.js +128 -59
  54. package/dist/src/gateway/control.js.map +1 -1
  55. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  56. package/dist/src/gateway/dashboard-copy.en.d.ts +19 -44
  57. package/dist/src/gateway/dashboard-copy.en.js +42 -67
  58. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  59. package/dist/src/gateway/dashboard-copy.js +19 -44
  60. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  61. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +19 -44
  62. package/dist/src/gateway/dashboard-copy.zh-CN.js +42 -67
  63. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  64. package/dist/src/gateway/dashboard-model.d.ts +41 -76
  65. package/dist/src/gateway/dashboard-model.js +167 -257
  66. package/dist/src/gateway/dashboard-model.js.map +1 -1
  67. package/dist/src/gateway/dashboard.d.ts +0 -3
  68. package/dist/src/gateway/dashboard.js +77 -76
  69. package/dist/src/gateway/dashboard.js.map +1 -1
  70. package/dist/src/gateway/deepseek-detect.d.ts +7 -6
  71. package/dist/src/gateway/deepseek-detect.js +31 -115
  72. package/dist/src/gateway/deepseek-detect.js.map +1 -1
  73. package/dist/src/gateway/live-dashboard-app/app.js +175 -621
  74. package/dist/src/gateway/live-dashboard-command.js +2 -4
  75. package/dist/src/gateway/live-dashboard-command.js.map +1 -1
  76. package/dist/src/gateway/live-dashboard-http.d.ts +1 -2
  77. package/dist/src/gateway/live-dashboard-http.js +14 -15
  78. package/dist/src/gateway/live-dashboard-http.js.map +1 -1
  79. package/dist/src/gateway/progress-watch-machine.d.ts +1 -4
  80. package/dist/src/gateway/progress-watch-machine.js.map +1 -1
  81. package/dist/src/gateway/provenance-envelope.d.ts +3 -2
  82. package/dist/src/gateway/provenance-envelope.js +44 -11
  83. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  84. package/dist/src/gateway/providers.d.ts +19 -85
  85. package/dist/src/gateway/providers.js +132 -835
  86. package/dist/src/gateway/providers.js.map +1 -1
  87. package/dist/src/gateway/server.d.ts +8 -9
  88. package/dist/src/gateway/server.js +63 -99
  89. package/dist/src/gateway/server.js.map +1 -1
  90. package/dist/src/gateway/service.d.ts +48 -51
  91. package/dist/src/gateway/service.js +748 -540
  92. package/dist/src/gateway/service.js.map +1 -1
  93. package/dist/src/gateway/store.d.ts +20 -29
  94. package/dist/src/gateway/store.js +306 -838
  95. package/dist/src/gateway/store.js.map +1 -1
  96. package/dist/src/gateway/types.d.ts +63 -54
  97. package/dist/src/gateway/types.js +51 -91
  98. package/dist/src/gateway/types.js.map +1 -1
  99. package/docs/CONFIGURATION.md +11 -10
  100. package/docs/CONFIGURATION.zh-CN.md +10 -9
  101. package/docs/DASHBOARD.md +4 -38
  102. package/docs/DASHBOARD.zh-CN.md +4 -2
  103. package/docs/GATEWAY-ARCHITECTURE.md +61 -119
  104. package/package.json +1 -1
  105. package/skills/embassy-peer/SKILL.md +6 -6
@@ -1,43 +1,20 @@
1
1
  import { Buffer } from "node:buffer";
2
- import { readdir, stat } from "node:fs/promises";
3
- import { isAbsolute } from "node:path";
4
2
  import WebSocket from "ws";
5
- import { sharesCompatibilityMajor } from "./compatibility.js";
6
3
  /**
7
- * Stable App Server methods reviewed for the gateway's first writable version.
4
+ * Closed App Server method surface used by the gateway.
8
5
  *
9
6
  * `turn/steer` is exposed only through the exact Claude-to-Codex `STEER:`
10
7
  * contract. It queues input at the attested next tool-call boundary and never
11
8
  * authorizes an interrupt or a generic/public JSON-RPC escape hatch.
12
9
  */
13
10
  export const CODEX_APP_SERVER_V1_METHODS = [
14
- "account/rateLimits/read",
15
- "model/list",
16
- "thread/archive",
17
11
  "thread/loaded/list",
18
12
  "thread/resume",
19
- "thread/start",
20
13
  "thread/unsubscribe",
21
14
  "turn/start",
22
15
  "turn/steer",
23
16
  "turn/interrupt",
24
17
  ];
25
- export const CODEX_PROBE_MODEL_PREFERENCE = ["gpt-5.6-luna"];
26
- export const CODEX_PROBE_EFFORT = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
27
- export const CODEX_WRITE_PROBE_INPUT = "Reply with exactly OK. Do not use tools.";
28
- /** Exact App Server builds whose writable v2 schema was reviewed. */
29
- export const CODEX_APP_SERVER_WRITABLE_VERSIONS = ["0.147.0"];
30
- export function codexWriteProbeFailure(safeErrorCode, evidence = {}) {
31
- return {
32
- ...(evidence.archivedThreadCount === 1 ? { archivedThreadCount: 1 } : {}),
33
- outcome: "fail",
34
- safeErrorCode,
35
- settingsEchoObserved: evidence.settingsEchoObserved ?? false,
36
- ...(evidence.tokenCount === undefined
37
- ? {}
38
- : { tokenCount: evidence.tokenCount }),
39
- };
40
- }
41
18
  export class CodexConnectorError extends Error {
42
19
  ambiguous;
43
20
  code;
@@ -263,9 +240,6 @@ export class WebSocketDuplexTransport {
263
240
  }
264
241
  }
265
242
  const V1_METHOD_SET = new Set(CODEX_APP_SERVER_V1_METHODS);
266
- const PROBE_ONLY_METHODS = new Set([
267
- "account/rateLimits/read", "model/list", "thread/archive", "thread/start",
268
- ]);
269
243
  const OUTPUT_NOTIFICATION_OPT_OUTS = [
270
244
  "item/started",
271
245
  "item/agentMessage/delta",
@@ -275,14 +249,6 @@ const OUTPUT_NOTIFICATION_OPT_OUTS = [
275
249
  "turn/diff/updated",
276
250
  "turn/plan/updated",
277
251
  ];
278
- const PROBE_PASSIVE_ITEM_TYPES = new Set(["agentMessage", "reasoning", "userMessage"]);
279
- const PROBE_TOOL_NOTIFICATION_METHODS = new Set([
280
- "item/autoApprovalReview/completed", "item/autoApprovalReview/started",
281
- "item/commandExecution/terminalInteraction",
282
- "item/fileChange/outputDelta", "item/fileChange/patchUpdated",
283
- "item/mcpToolCall/progress",
284
- ]);
285
- const MAX_PROBE_TOKEN_COUNT = 1_000_000_000;
286
252
  const APPROVAL_REQUEST_METHODS = new Set([
287
253
  "item/commandExecution/requestApproval",
288
254
  "item/fileChange/requestApproval",
@@ -301,56 +267,6 @@ function validOpaqueId(value, maximumLength = 256) {
301
267
  value.length <= maximumLength &&
302
268
  /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u.test(value));
303
269
  }
304
- function validUuidV7(value) {
305
- return /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value);
306
- }
307
- function parseLoadedThreadIds(value) {
308
- if (!isRecord(value) ||
309
- !Array.isArray(value.data) ||
310
- (value.nextCursor !== undefined && value.nextCursor !== null)) {
311
- return null;
312
- }
313
- if (value.data.length > 100_000 ||
314
- !value.data.every((threadId) => typeof threadId === "string" && validOpaqueId(threadId)) ||
315
- new Set(value.data).size !== value.data.length) {
316
- return null;
317
- }
318
- return value.data;
319
- }
320
- function rateLimitConstrained(value) {
321
- if (!isRecord(value) || !isRecord(value.rateLimits))
322
- return null;
323
- const byLimit = value.rateLimitsByLimitId;
324
- const snapshot = isRecord(byLimit) && isRecord(byLimit.codex)
325
- ? byLimit.codex
326
- : value.rateLimits;
327
- const percent = (container, key) => {
328
- if (container === null || container === undefined)
329
- return Number.NaN;
330
- if (!isRecord(container) || !Number.isSafeInteger(container[key]))
331
- return null;
332
- const result = container[key];
333
- return result >= 0 && result <= 100 ? result : null;
334
- };
335
- const remaining = percent(snapshot.individualLimit, "remainingPercent");
336
- const primary = percent(snapshot.primary, "usedPercent");
337
- const secondary = percent(snapshot.secondary, "usedPercent");
338
- if ([remaining, primary, secondary].includes(null))
339
- return null;
340
- if ((snapshot.rateLimitReachedType !== null &&
341
- snapshot.rateLimitReachedType !== undefined) ||
342
- snapshot.spendControlReached === true) {
343
- return true;
344
- }
345
- if (snapshot.spendControlReached !== null &&
346
- snapshot.spendControlReached !== undefined &&
347
- snapshot.spendControlReached !== false) {
348
- return null;
349
- }
350
- return ((!Number.isNaN(remaining) && remaining <= 5) ||
351
- (!Number.isNaN(primary) && primary >= 95) ||
352
- (!Number.isNaN(secondary) && secondary >= 95));
353
- }
354
270
  function validateRoute(route) {
355
271
  if (!validOpaqueId(route.threadId) ||
356
272
  !validOpaqueId(route.endpointGeneration, 128)) {
@@ -369,17 +285,10 @@ function validateClientInfo(clientInfo) {
369
285
  return { ...clientInfo };
370
286
  }
371
287
  function validateCompatibility(compatibility, route) {
372
- if (compatibility.endpointGeneration !== route.endpointGeneration ||
373
- compatibility.protocol !== "app-server-v2-stable" ||
374
- compatibility.steering?.method !== "turn/steer" ||
375
- compatibility.steering.requestSchema !== "expected-turn-id-text-v1" ||
376
- compatibility.steering.deliveryBoundary !== "next-tool-call-boundary" ||
377
- !(CODEX_APP_SERVER_WRITABLE_VERSIONS.some((version) => version === compatibility.appServerVersion) ||
378
- (compatibility.observedSchemaCandidate === true &&
379
- CODEX_APP_SERVER_WRITABLE_VERSIONS.some((version) => sharesCompatibilityMajor(version, compatibility.appServerVersion))))) {
288
+ if (compatibility.endpointGeneration !== route.endpointGeneration) {
380
289
  throw new CodexConnectorError("INVALID_CONFIGURATION");
381
290
  }
382
- return { ...compatibility, steering: { ...compatibility.steering } };
291
+ return { ...compatibility };
383
292
  }
384
293
  function parseRouteStatus(value) {
385
294
  if (!isRecord(value) || typeof value.type !== "string")
@@ -440,8 +349,6 @@ export class CodexAppServerConnector {
440
349
  nextRequestId = 1;
441
350
  ownsActiveTurn = false;
442
351
  pending = new Map();
443
- probeAttempted = false;
444
- probeRuntime = null;
445
352
  queue = [];
446
353
  revision = 0;
447
354
  routeStatus = "unknown";
@@ -452,7 +359,6 @@ export class CodexAppServerConnector {
452
359
  turnWatchdogTimer;
453
360
  unlisten = [];
454
361
  clientInfo;
455
- compatibility;
456
362
  maxDeadlineMs;
457
363
  maxFrameBytes;
458
364
  maxInputBytes;
@@ -466,27 +372,14 @@ export class CodexAppServerConnector {
466
372
  turnWatchdogMs;
467
373
  route;
468
374
  transport;
469
- writeCompatibilityProbe;
470
- writesEnabled;
471
375
  constructor(options) {
472
376
  this.route = validateRoute(options.route);
473
- this.compatibility = validateCompatibility(options.compatibility, this.route);
474
- if (typeof options.writesEnabled !== "boolean") {
475
- throw new CodexConnectorError("INVALID_CONFIGURATION");
476
- }
477
- if (options.writesEnabled &&
478
- !(CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(options.compatibility.appServerVersion) ||
479
- (options.compatibility.observedSchemaCandidate === true &&
480
- !options.compatibility.appServerVersion.includes("-")))) {
481
- throw new CodexConnectorError("INVALID_CONFIGURATION");
482
- }
483
- this.writesEnabled = options.writesEnabled;
484
- this.writeCompatibilityProbe = options.writeCompatibilityProbe === true;
377
+ validateCompatibility(options.compatibility, this.route);
485
378
  this.transport = options.transport;
486
379
  this.clientInfo = validateClientInfo(options.clientInfo ?? {
487
380
  name: "agent_embassy_gateway",
488
381
  title: "Embassy Gateway",
489
- version: "1.6.1",
382
+ version: "1.7.1",
490
383
  });
491
384
  this.maxFrameBytes = positiveInteger(options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES, "INVALID_CONFIGURATION");
492
385
  this.maxDeadlineMs = positiveInteger(options.maxDeadlineMs ?? DEFAULT_MAX_DEADLINE_MS, "INVALID_CONFIGURATION");
@@ -502,7 +395,6 @@ export class CodexAppServerConnector {
502
395
  this.unlisten.push(this.transport.onMessage((payload) => this.handlePayload(payload)), this.transport.onClose(() => this.handleDisconnect()), this.transport.onError(() => this.protocolFault("TRANSPORT_CLOSED")));
503
396
  }
504
397
  observation() {
505
- const writeBlockCode = this.currentWriteBlockCode();
506
398
  return {
507
399
  connection: this.connection,
508
400
  eventSequence: this.eventSequence,
@@ -510,18 +402,14 @@ export class CodexAppServerConnector {
510
402
  queueDepth: this.queue.length,
511
403
  requestInFlight: this.inFlightMethod !== null,
512
404
  routeStatus: this.routeStatus,
513
- writableReady: this.isWritableReady(),
514
- writeBlockCode,
515
405
  };
516
406
  }
517
407
  guard() {
518
- const writableReady = this.isWritableReady();
519
408
  return {
520
409
  ...this.route,
521
410
  activeTurnId: this.activeTurnId,
522
411
  revision: this.revision,
523
412
  status: this.routeStatus,
524
- writableReady,
525
413
  };
526
414
  }
527
415
  async observeLoadedThread(guard) {
@@ -589,10 +477,7 @@ export class CodexAppServerConnector {
589
477
  throw new CodexConnectorError("THREAD_NOT_OBSERVED");
590
478
  }
591
479
  if (this.routeStatus !== "unknown" &&
592
- this.routeStatus !== "not_loaded" &&
593
- !(this.currentWriteBlockCode() !== null &&
594
- this.routeStatus === "idle" &&
595
- this.activeTurnId === null)) {
480
+ this.routeStatus !== "not_loaded") {
596
481
  throw new CodexConnectorError("ROUTE_BUSY");
597
482
  }
598
483
  const statusEpoch = this.statusEpoch;
@@ -624,10 +509,6 @@ export class CodexAppServerConnector {
624
509
  }
625
510
  }
626
511
  this.emit("thread_resumed");
627
- const writeBlockCode = this.currentWriteBlockCode();
628
- if (writeBlockCode !== null) {
629
- this.emit("route_write_blocked", { errorCode: writeBlockCode });
630
- }
631
512
  }
632
513
  catch (error) {
633
514
  const normalized = this.normalizeError(error, true);
@@ -702,7 +583,6 @@ export class CodexAppServerConnector {
702
583
  throw new CodexConnectorError("THREAD_NOT_OBSERVED");
703
584
  }
704
585
  if (input.steer === true && this.routeStatus === "active") {
705
- this.assertWritableReady();
706
586
  const message = this.validateMessage(input);
707
587
  if (this.activeTurnId === null || this.inFlightMethod !== null) {
708
588
  this.acceptedMessageIds.delete(message.messageId);
@@ -715,7 +595,6 @@ export class CodexAppServerConnector {
715
595
  this.routeStatus === "active" ||
716
596
  this.routeStatus === "waiting_approval" ||
717
597
  this.routeStatus === "interrupting") {
718
- this.assertWritableReady();
719
598
  const message = this.validateMessage(input);
720
599
  if (input.steer === true) {
721
600
  this.acceptedMessageIds.delete(message.messageId);
@@ -724,7 +603,6 @@ export class CodexAppServerConnector {
724
603
  this.enqueue(message);
725
604
  return { disposition: "queued", observation: this.observation() };
726
605
  }
727
- this.assertWritableReady();
728
606
  const message = this.validateMessage(input);
729
607
  if (this.routeStatus !== "idle") {
730
608
  this.acceptedMessageIds.delete(message.messageId);
@@ -796,318 +674,14 @@ export class CodexAppServerConnector {
796
674
  this.handleDisconnect();
797
675
  }
798
676
  }
799
- /**
800
- * Run the one fixed disposable-thread write probe. The method never exposes
801
- * JSON-RPC, native IDs, content, or route state, and every failure settles as
802
- * a safe result so compatibility discovery cannot take down the broker.
803
- */
804
- async runWriteCompatibilityProbe(input) {
805
- if (this.probeAttempted) {
806
- return codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
807
- }
808
- this.probeAttempted = true;
809
- let threadId = null;
810
- let archivedThreadCount;
811
- let result = codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
812
- try {
813
- if (!this.writeCompatibilityProbe ||
814
- this.connection !== "ready" ||
815
- this.inFlightMethod !== null ||
816
- this.pending.size !== 0 ||
817
- !isAbsolute(input.cwd) ||
818
- !Array.isArray(input.forbiddenThreadIds) ||
819
- !input.forbiddenThreadIds.every((value) => typeof value === "string" && validOpaqueId(value))) {
820
- return result;
821
- }
822
- const beforeStat = await stat(input.cwd, { bigint: true });
823
- const beforeEntries = await readdir(input.cwd);
824
- if (!beforeStat.isDirectory() ||
825
- beforeStat.uid !== BigInt(process.getuid?.() ?? -1) ||
826
- (beforeStat.mode & 511n) !== 448n ||
827
- beforeEntries.length !== 0) {
828
- return result;
829
- }
830
- const limits = rateLimitConstrained(await this.request("account/rateLimits/read", null));
831
- if (limits === null)
832
- return result;
833
- if (limits) {
834
- return codexWriteProbeFailure("CODEX_WRITE_PROBE_RATE_LIMIT_CONSTRAINED");
835
- }
836
- const selection = this.selectProbeModel(await this.request("model/list", {
837
- includeHidden: true,
838
- limit: 100,
839
- }).catch(() => null));
840
- if (selection === null) {
841
- return codexWriteProbeFailure("CODEX_WRITE_PROBE_MODEL_PIN_UNAVAILABLE");
842
- }
843
- const [model, effort] = selection;
844
- const loadedBefore = parseLoadedThreadIds(await this.request("thread/loaded/list", {}));
845
- if (loadedBefore === null)
846
- return result;
847
- const runtime = {
848
- expectedCwd: input.cwd,
849
- expectedEffort: effort,
850
- expectedModel: model,
851
- failCode: null,
852
- phase: "awaiting_start",
853
- resolveTurn: null,
854
- settingsEchoObserved: false,
855
- settingsThreadId: null,
856
- threadId: null,
857
- tokenCount: null,
858
- turnId: null,
859
- };
860
- this.probeRuntime = runtime;
861
- const started = await this.request("thread/start", {
862
- allowProviderModelFallback: false,
863
- approvalPolicy: "never",
864
- cwd: input.cwd,
865
- dynamicTools: [],
866
- environments: [],
867
- ephemeral: false,
868
- model,
869
- runtimeWorkspaceRoots: [],
870
- sandbox: "read-only",
871
- selectedCapabilityRoots: [],
872
- });
873
- const candidateThreadId = this.probeCleanupThreadId(started, loadedBefore, input.forbiddenThreadIds);
874
- if (candidateThreadId === null ||
875
- !this.validateProbeThread(started, input.cwd, model, candidateThreadId)) {
876
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH", true);
877
- }
878
- threadId = candidateThreadId;
879
- runtime.threadId = candidateThreadId;
880
- if (runtime.settingsThreadId !== null &&
881
- runtime.settingsThreadId !== candidateThreadId) {
882
- runtime.failCode = "CODEX_WRITE_PROBE_THREAD_SETUP_FAILED";
883
- }
884
- if (runtime.failCode === null) {
885
- const turnResult = await this.request("turn/start", {
886
- approvalPolicy: "never",
887
- cwd: input.cwd,
888
- effort,
889
- environments: [],
890
- input: [{ text: CODEX_WRITE_PROBE_INPUT, type: "text" }],
891
- model,
892
- runtimeWorkspaceRoots: [],
893
- sandboxPolicy: { networkAccess: false, type: "readOnly" },
894
- threadId,
895
- });
896
- const turn = isRecord(turnResult) ? parseTurn(turnResult.turn) : null;
897
- if (turn === null ||
898
- !isRecord(turnResult) ||
899
- !isRecord(turnResult.turn) ||
900
- !Array.isArray(turnResult.turn.items) ||
901
- (turn.status !== "inProgress" && turn.status !== "completed")) {
902
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH", true);
903
- }
904
- if (runtime.turnId !== null && runtime.turnId !== turn.id) {
905
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH", true);
906
- }
907
- runtime.turnId = turn.id;
908
- await this.waitForProbeTurn(runtime);
909
- }
910
- if (runtime.failCode !== null) {
911
- result = codexWriteProbeFailure(runtime.failCode, {
912
- settingsEchoObserved: runtime.settingsEchoObserved,
913
- ...(runtime.tokenCount === null
914
- ? {}
915
- : { tokenCount: runtime.tokenCount }),
916
- });
917
- }
918
- else if (runtime.phase === "terminal" &&
919
- runtime.tokenCount !== null) {
920
- const afterStat = await stat(input.cwd, { bigint: true });
921
- const afterEntries = await readdir(input.cwd);
922
- if (afterEntries.length === 0 &&
923
- afterStat.dev === beforeStat.dev &&
924
- afterStat.ino === beforeStat.ino &&
925
- afterStat.mtimeNs === beforeStat.mtimeNs &&
926
- afterStat.uid === beforeStat.uid &&
927
- afterStat.mode === beforeStat.mode) {
928
- result = {
929
- archivedThreadCount: 1,
930
- outcome: "pass",
931
- settingsEchoObserved: runtime.settingsEchoObserved,
932
- tokenCount: runtime.tokenCount,
933
- };
934
- }
935
- else {
936
- result = codexWriteProbeFailure("CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED", {
937
- settingsEchoObserved: runtime.settingsEchoObserved,
938
- tokenCount: runtime.tokenCount,
939
- });
940
- }
941
- }
942
- }
943
- catch (error) {
944
- const normalized = this.normalizeError(error, true);
945
- const runtime = this.probeRuntime;
946
- result = codexWriteProbeFailure(normalized.code === "REQUEST_TIMEOUT"
947
- ? "CODEX_WRITE_PROBE_TIMEOUT"
948
- : "CODEX_WRITE_PROBE_THREAD_SETUP_FAILED", {
949
- settingsEchoObserved: runtime?.settingsEchoObserved ?? false,
950
- ...(runtime?.tokenCount === null || runtime?.tokenCount === undefined
951
- ? {}
952
- : { tokenCount: runtime.tokenCount }),
953
- });
954
- }
955
- finally {
956
- const runtime = this.probeRuntime;
957
- if (threadId !== null &&
958
- runtime !== null &&
959
- runtime.turnId !== null &&
960
- (result.outcome === "fail" || runtime.failCode !== null)) {
961
- try {
962
- await this.request("turn/interrupt", {
963
- threadId,
964
- turnId: runtime.turnId,
965
- });
966
- }
967
- catch {
968
- // Archival remains the one required cleanup proof below.
969
- }
970
- }
971
- let cleanupUnconfirmed = runtime !== null && threadId === null;
972
- if (threadId !== null) {
973
- try {
974
- const archived = await this.request("thread/archive", { threadId });
975
- if (!isRecord(archived) || Object.keys(archived).length !== 0) {
976
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH");
977
- }
978
- archivedThreadCount = 1;
979
- const unsubscribed = await this.request("thread/unsubscribe", {
980
- threadId,
981
- });
982
- if (!isRecord(unsubscribed) ||
983
- (unsubscribed.status !== "unsubscribed" &&
984
- unsubscribed.status !== "notSubscribed" &&
985
- unsubscribed.status !== "notLoaded")) {
986
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH");
987
- }
988
- const loadedAfter = parseLoadedThreadIds(await this.request("thread/loaded/list", {}));
989
- if (loadedAfter === null || loadedAfter.includes(threadId)) {
990
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH");
991
- }
992
- }
993
- catch {
994
- cleanupUnconfirmed = true;
995
- }
996
- }
997
- const tokenCount = runtime?.tokenCount ?? result.tokenCount;
998
- const evidence = {
999
- ...(archivedThreadCount === undefined ? {} : { archivedThreadCount }),
1000
- settingsEchoObserved: runtime?.settingsEchoObserved ?? result.settingsEchoObserved,
1001
- ...(tokenCount === undefined ? {} : { tokenCount }),
1002
- };
1003
- if (cleanupUnconfirmed || (result.outcome === "pass" && archivedThreadCount !== 1)) {
1004
- result = codexWriteProbeFailure("CODEX_WRITE_PROBE_CLEANUP_UNCONFIRMED", evidence);
1005
- }
1006
- else if (runtime?.failCode !== null && runtime?.failCode !== undefined) {
1007
- result = codexWriteProbeFailure(runtime.failCode, evidence);
1008
- }
1009
- if (result.outcome === "fail") {
1010
- result = codexWriteProbeFailure(result.safeErrorCode, evidence);
1011
- }
1012
- this.probeRuntime = null;
1013
- }
1014
- return result;
1015
- }
1016
- selectProbeModel(value) {
1017
- if (!isRecord(value) || !Array.isArray(value.data) || value.data.length > 100) {
1018
- return null;
1019
- }
1020
- const data = value.data;
1021
- for (const model of CODEX_PROBE_MODEL_PREFERENCE) {
1022
- const candidate = data.find((entry) => isRecord(entry) &&
1023
- entry.model === model &&
1024
- entry.hidden === false);
1025
- if (!isRecord(candidate) || !Array.isArray(candidate.supportedReasoningEfforts)) {
1026
- continue;
1027
- }
1028
- const supported = candidate.supportedReasoningEfforts;
1029
- const effort = CODEX_PROBE_EFFORT.find((preference) => supported.some((option) => isRecord(option) && option.reasoningEffort === preference));
1030
- if (effort !== undefined)
1031
- return [model, effort];
1032
- }
1033
- return null;
1034
- }
1035
- probeCleanupThreadId(value, loadedBefore, forbiddenThreadIds) {
1036
- if (!isRecord(value) ||
1037
- !isRecord(value.thread) ||
1038
- typeof value.thread.id !== "string" ||
1039
- !validUuidV7(value.thread.id) ||
1040
- value.thread.id === this.route.threadId ||
1041
- loadedBefore.includes(value.thread.id) ||
1042
- forbiddenThreadIds.includes(value.thread.id)) {
1043
- return null;
1044
- }
1045
- return value.thread.id;
1046
- }
1047
- validateProbeThread(value, cwd, model, threadId) {
1048
- if (!isRecord(value) || !isRecord(value.thread))
1049
- return false;
1050
- const roots = value.runtimeWorkspaceRoots;
1051
- const networkAccess = isRecord(value.sandbox)
1052
- ? value.sandbox.networkAccess
1053
- : undefined;
1054
- return (value.approvalPolicy === "never" &&
1055
- value.cwd === cwd &&
1056
- value.model === model &&
1057
- isRecord(value.sandbox) &&
1058
- value.sandbox.type === "readOnly" &&
1059
- (networkAccess === undefined || networkAccess === false) &&
1060
- (roots === undefined || (Array.isArray(roots) && roots.length === 0)) &&
1061
- value.thread.id === threadId &&
1062
- value.thread.cwd === cwd &&
1063
- value.thread.ephemeral === false &&
1064
- isRecord(value.thread.status) &&
1065
- value.thread.status.type === "idle" &&
1066
- Array.isArray(value.thread.turns) &&
1067
- value.thread.turns.length === 0);
1068
- }
1069
- async waitForProbeTurn(runtime) {
1070
- if (runtime.failCode !== null ||
1071
- (runtime.phase === "terminal" && runtime.tokenCount !== null)) {
1072
- return;
1073
- }
1074
- let timer;
1075
- await new Promise((resolve) => {
1076
- runtime.resolveTurn = resolve;
1077
- timer = setTimeout(resolve, this.turnWatchdogMs);
1078
- });
1079
- runtime.resolveTurn = null;
1080
- if (timer !== undefined)
1081
- clearTimeout(timer);
1082
- if (runtime.failCode === null &&
1083
- !(runtime.phase === "terminal" && runtime.tokenCount !== null)) {
1084
- runtime.failCode = "CODEX_WRITE_PROBE_TIMEOUT";
1085
- }
1086
- }
1087
- settleProbeRuntime() {
1088
- const runtime = this.probeRuntime;
1089
- if (runtime === null || runtime.resolveTurn === null)
1090
- return;
1091
- if (runtime.failCode !== null ||
1092
- (runtime.phase === "terminal" && runtime.tokenCount !== null)) {
1093
- runtime.resolveTurn();
1094
- }
1095
- }
1096
- settleProbeConnectionLoss() {
1097
- if (this.probeRuntime === null)
1098
- return;
1099
- this.probeRuntime.failCode ??= "CODEX_WRITE_PROBE_THREAD_SETUP_FAILED";
1100
- this.settleProbeRuntime();
1101
- }
1102
677
  async initialize() {
1103
678
  try {
1104
679
  const result = await this.request("initialize", {
1105
680
  capabilities: {
1106
681
  // `thread/resume.excludeTurns` is field-gated behind this capability
1107
- // in the exactly attested 0.147.0 schema. The client method allowlist
1108
- // remains closed and exposes no experimental method.
682
+ // and is the only reason Embassy enables the experimental API.
1109
683
  experimentalApi: true,
1110
- optOutNotificationMethods: OUTPUT_NOTIFICATION_OPT_OUTS.filter((method) => method !== "item/started" || !this.writeCompatibilityProbe),
684
+ optOutNotificationMethods: OUTPUT_NOTIFICATION_OPT_OUTS,
1111
685
  },
1112
686
  clientInfo: this.clientInfo,
1113
687
  });
@@ -1173,7 +747,6 @@ export class CodexAppServerConnector {
1173
747
  }
1174
748
  async startMessage(message) {
1175
749
  this.assertNoRequest();
1176
- this.assertWritableReady();
1177
750
  if (this.messageExpired(message)) {
1178
751
  this.expireMessage(message);
1179
752
  return "expired";
@@ -1272,7 +845,6 @@ export class CodexAppServerConnector {
1272
845
  */
1273
846
  async steerMessage(message) {
1274
847
  this.assertNoRequest();
1275
- this.assertWritableReady();
1276
848
  if (this.messageExpired(message)) {
1277
849
  this.expireMessage(message);
1278
850
  return "expired";
@@ -1321,7 +893,6 @@ export class CodexAppServerConnector {
1321
893
  if (this.drainScheduled ||
1322
894
  this.connection !== "ready" ||
1323
895
  this.routeStatus !== "idle" ||
1324
- this.currentWriteBlockCode() !== null ||
1325
896
  this.inFlightMethod !== null ||
1326
897
  this.queue.length === 0) {
1327
898
  return;
@@ -1335,7 +906,6 @@ export class CodexAppServerConnector {
1335
906
  this.dropExpiredQueuedMessages();
1336
907
  if (this.connection !== "ready" ||
1337
908
  this.routeStatus !== "idle" ||
1338
- this.currentWriteBlockCode() !== null ||
1339
909
  this.inFlightMethod !== null ||
1340
910
  this.queue.length === 0) {
1341
911
  return;
@@ -1357,8 +927,7 @@ export class CodexAppServerConnector {
1357
927
  }
1358
928
  if (guard.revision !== this.revision ||
1359
929
  guard.status !== this.routeStatus ||
1360
- guard.activeTurnId !== this.activeTurnId ||
1361
- guard.writableReady !== this.isWritableReady()) {
930
+ guard.activeTurnId !== this.activeTurnId) {
1362
931
  throw new CodexConnectorError("ROUTE_CAS_MISMATCH");
1363
932
  }
1364
933
  }
@@ -1374,17 +943,6 @@ export class CodexAppServerConnector {
1374
943
  throw new CodexConnectorError("ROUTE_BUSY");
1375
944
  }
1376
945
  }
1377
- currentWriteBlockCode() {
1378
- return this.writesEnabled ? null : "WRITES_DISABLED";
1379
- }
1380
- isWritableReady() {
1381
- return this.connection === "ready" && this.currentWriteBlockCode() === null;
1382
- }
1383
- assertWritableReady() {
1384
- const code = this.currentWriteBlockCode();
1385
- if (code !== null)
1386
- throw new CodexConnectorError(code);
1387
- }
1388
946
  /**
1389
947
  * Refresh the exact thread on this same connection immediately before a
1390
948
  * `turn/start`. This confirms the registered task is still the observed idle
@@ -1392,7 +950,6 @@ export class CodexAppServerConnector {
1392
950
  * policy.
1393
951
  */
1394
952
  async refreshExactRouteBoundary() {
1395
- this.assertWritableReady();
1396
953
  if (this.routeStatus !== "starting" ||
1397
954
  this.activeMessageId === null ||
1398
955
  this.activeTurnId !== null ||
@@ -1421,7 +978,6 @@ export class CodexAppServerConnector {
1421
978
  this.setRouteStatus(refreshedStatus);
1422
979
  throw new CodexConnectorError("ROUTE_NOT_READY");
1423
980
  }
1424
- this.assertWritableReady();
1425
981
  if (this.statusEpoch !== statusEpoch ||
1426
982
  this.routeStatus !== "starting" ||
1427
983
  this.activeMessageId === null ||
@@ -1475,11 +1031,6 @@ export class CodexAppServerConnector {
1475
1031
  if (method !== "initialize" && !V1_METHOD_SET.has(method)) {
1476
1032
  return Promise.reject(new CodexConnectorError("METHOD_NOT_ALLOWED"));
1477
1033
  }
1478
- if (method !== "initialize" &&
1479
- PROBE_ONLY_METHODS.has(method) &&
1480
- !this.writeCompatibilityProbe) {
1481
- return Promise.reject(new CodexConnectorError("METHOD_NOT_ALLOWED"));
1482
- }
1483
1034
  if (method === "initialize") {
1484
1035
  if (this.initializeRequested || this.connection !== "connecting") {
1485
1036
  return Promise.reject(new CodexConnectorError("METHOD_NOT_ALLOWED"));
@@ -1596,8 +1147,6 @@ export class CodexAppServerConnector {
1596
1147
  pending.resolve(parsed.result);
1597
1148
  }
1598
1149
  handleNotification(method, params) {
1599
- if (this.handleProbeNotification(method, params))
1600
- return;
1601
1150
  if (method === "item/completed") {
1602
1151
  this.handleCompletedItem(params);
1603
1152
  return;
@@ -1827,14 +1376,6 @@ export class CodexAppServerConnector {
1827
1376
  // item/model/tool payloads are neither normalized nor retained.
1828
1377
  }
1829
1378
  handleServerRequest(method, params) {
1830
- if (this.probeRuntime !== null &&
1831
- isRecord(params) &&
1832
- params.threadId === this.probeRuntime.threadId) {
1833
- this.probeRuntime.failCode ??=
1834
- "CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED";
1835
- this.settleProbeRuntime();
1836
- return;
1837
- }
1838
1379
  if (!APPROVAL_REQUEST_METHODS.has(method)) {
1839
1380
  this.emit("server_request_ignored");
1840
1381
  return;
@@ -1859,135 +1400,6 @@ export class CodexAppServerConnector {
1859
1400
  // Intentionally no JSON-RPC response: the gateway is not an approval UI or
1860
1401
  // authority. The owning Desktop client must resolve the request.
1861
1402
  }
1862
- handleProbeNotification(method, params) {
1863
- const runtime = this.probeRuntime;
1864
- if (runtime === null)
1865
- return false;
1866
- const fail = (code) => {
1867
- runtime.failCode ??= code;
1868
- this.settleProbeRuntime();
1869
- };
1870
- if (method === "model/rerouted") {
1871
- fail("CODEX_WRITE_PROBE_MODEL_REROUTED");
1872
- return true;
1873
- }
1874
- if (!isRecord(params))
1875
- return false;
1876
- const observeTurnId = (value) => {
1877
- if (typeof value !== "string" || !validOpaqueId(value))
1878
- return false;
1879
- if (runtime.turnId !== null && runtime.turnId !== value)
1880
- return false;
1881
- runtime.turnId = value;
1882
- return true;
1883
- };
1884
- if (method === "thread/settings/updated") {
1885
- runtime.settingsEchoObserved = true;
1886
- const settingsThreadId = params.threadId;
1887
- const settings = params.threadSettings;
1888
- const sandbox = isRecord(settings) ? settings.sandboxPolicy : null;
1889
- if (typeof settingsThreadId !== "string" ||
1890
- !validUuidV7(settingsThreadId) ||
1891
- (runtime.threadId !== null && runtime.threadId !== settingsThreadId) ||
1892
- (runtime.settingsThreadId !== null &&
1893
- runtime.settingsThreadId !== settingsThreadId) ||
1894
- !isRecord(settings) ||
1895
- settings.cwd !== runtime.expectedCwd ||
1896
- settings.model !== runtime.expectedModel ||
1897
- settings.approvalPolicy !== "never" ||
1898
- (settings.effort !== undefined &&
1899
- settings.effort !== runtime.expectedEffort) ||
1900
- !isRecord(sandbox) ||
1901
- sandbox.type !== "readOnly" ||
1902
- (sandbox.networkAccess !== undefined &&
1903
- sandbox.networkAccess !== false)) {
1904
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1905
- }
1906
- else {
1907
- runtime.settingsThreadId = settingsThreadId;
1908
- }
1909
- return true;
1910
- }
1911
- if (runtime.threadId === null || params.threadId !== runtime.threadId) {
1912
- return false;
1913
- }
1914
- if (PROBE_TOOL_NOTIFICATION_METHODS.has(method)) {
1915
- fail("CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED");
1916
- return true;
1917
- }
1918
- if (method === "thread/tokenUsage/updated") {
1919
- const totalTokens = observeTurnId(params.turnId) &&
1920
- isRecord(params.tokenUsage) &&
1921
- isRecord(params.tokenUsage.last)
1922
- ? params.tokenUsage.last.totalTokens
1923
- : null;
1924
- if (!Number.isSafeInteger(totalTokens) ||
1925
- totalTokens < 0 ||
1926
- totalTokens > MAX_PROBE_TOKEN_COUNT) {
1927
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1928
- }
1929
- else {
1930
- runtime.tokenCount = totalTokens;
1931
- this.settleProbeRuntime();
1932
- }
1933
- return true;
1934
- }
1935
- if (method === "turn/started" || method === "turn/completed") {
1936
- const turn = parseTurn(params.turn);
1937
- if (turn === null ||
1938
- !isRecord(params.turn) ||
1939
- !Array.isArray(params.turn.items) ||
1940
- !observeTurnId(turn.id)) {
1941
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1942
- }
1943
- else if (method === "turn/started") {
1944
- if (turn.status !== "inProgress" ||
1945
- runtime.phase !== "awaiting_start") {
1946
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1947
- }
1948
- else {
1949
- runtime.phase = "started";
1950
- }
1951
- }
1952
- else if (turn.status === "completed" &&
1953
- runtime.phase === "item_completed") {
1954
- runtime.phase = "terminal";
1955
- this.settleProbeRuntime();
1956
- }
1957
- else {
1958
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1959
- }
1960
- return true;
1961
- }
1962
- if (method === "item/started" || method === "item/completed") {
1963
- const observedAt = method === "item/started" ? params.startedAtMs : params.completedAtMs;
1964
- const itemType = isRecord(params.item) ? params.item.type : null;
1965
- if (typeof itemType === "string" &&
1966
- !PROBE_PASSIVE_ITEM_TYPES.has(itemType)) {
1967
- fail("CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED");
1968
- return true;
1969
- }
1970
- if (!observeTurnId(params.turnId) ||
1971
- !isRecord(params.item) ||
1972
- typeof itemType !== "string" ||
1973
- !Number.isSafeInteger(observedAt) ||
1974
- observedAt < 0 ||
1975
- (runtime.phase !== "started" && runtime.phase !== "item_completed")) {
1976
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1977
- }
1978
- else if (method === "item/completed" &&
1979
- itemType === "agentMessage") {
1980
- if (runtime.phase !== "started") {
1981
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1982
- }
1983
- else {
1984
- runtime.phase = "item_completed";
1985
- }
1986
- }
1987
- return true;
1988
- }
1989
- return false;
1990
- }
1991
1403
  handleCompletedItem(params) {
1992
1404
  if (!isRecord(params) ||
1993
1405
  typeof params.threadId !== "string") {
@@ -2131,7 +1543,6 @@ export class CodexAppServerConnector {
2131
1543
  return;
2132
1544
  this.faulting = true;
2133
1545
  this.connection = "faulted";
2134
- this.settleProbeConnectionLoss();
2135
1546
  this.initialized = false;
2136
1547
  this.routeStatus = "stale";
2137
1548
  this.selectedThreadObserved = false;
@@ -2152,7 +1563,6 @@ export class CodexAppServerConnector {
2152
1563
  if (this.connection === "closed" || this.connection === "faulted")
2153
1564
  return;
2154
1565
  this.connection = "closed";
2155
- this.settleProbeConnectionLoss();
2156
1566
  this.initialized = false;
2157
1567
  this.routeStatus = "stale";
2158
1568
  this.selectedThreadObserved = false;