agent-embassy 1.6.0 → 1.7.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 (97) hide show
  1. package/CHANGELOG.md +31 -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.en.d.ts +1 -1
  26. package/dist/src/gateway/cli-copy.en.js +2 -2
  27. package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
  28. package/dist/src/gateway/cli-copy.zh-CN.js +2 -2
  29. package/dist/src/gateway/cli.d.ts +1 -1
  30. package/dist/src/gateway/cli.js +26 -5
  31. package/dist/src/gateway/cli.js.map +1 -1
  32. package/dist/src/gateway/codex-app-server.d.ts +5 -74
  33. package/dist/src/gateway/codex-app-server.js +9 -597
  34. package/dist/src/gateway/codex-app-server.js.map +1 -1
  35. package/dist/src/gateway/codex-local-transport.d.ts +8 -26
  36. package/dist/src/gateway/codex-local-transport.js +26 -76
  37. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  38. package/dist/src/gateway/compatibility.d.ts +6 -66
  39. package/dist/src/gateway/compatibility.js +14 -296
  40. package/dist/src/gateway/compatibility.js.map +1 -1
  41. package/dist/src/gateway/config.d.ts +9 -0
  42. package/dist/src/gateway/config.js +6 -2
  43. package/dist/src/gateway/config.js.map +1 -1
  44. package/dist/src/gateway/control.d.ts +16 -1
  45. package/dist/src/gateway/control.js +89 -57
  46. package/dist/src/gateway/control.js.map +1 -1
  47. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  48. package/dist/src/gateway/dashboard-copy.en.d.ts +14 -44
  49. package/dist/src/gateway/dashboard-copy.en.js +37 -67
  50. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  51. package/dist/src/gateway/dashboard-copy.js +14 -44
  52. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  53. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +14 -44
  54. package/dist/src/gateway/dashboard-copy.zh-CN.js +37 -67
  55. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  56. package/dist/src/gateway/dashboard-model.d.ts +38 -76
  57. package/dist/src/gateway/dashboard-model.js +158 -256
  58. package/dist/src/gateway/dashboard-model.js.map +1 -1
  59. package/dist/src/gateway/dashboard.d.ts +0 -3
  60. package/dist/src/gateway/dashboard.js +75 -76
  61. package/dist/src/gateway/dashboard.js.map +1 -1
  62. package/dist/src/gateway/deepseek-detect.d.ts +7 -6
  63. package/dist/src/gateway/deepseek-detect.js +31 -115
  64. package/dist/src/gateway/deepseek-detect.js.map +1 -1
  65. package/dist/src/gateway/live-dashboard-app/app.js +166 -621
  66. package/dist/src/gateway/live-dashboard-command.js +2 -4
  67. package/dist/src/gateway/live-dashboard-command.js.map +1 -1
  68. package/dist/src/gateway/live-dashboard-http.d.ts +1 -2
  69. package/dist/src/gateway/live-dashboard-http.js +14 -15
  70. package/dist/src/gateway/live-dashboard-http.js.map +1 -1
  71. package/dist/src/gateway/progress-watch-machine.d.ts +1 -4
  72. package/dist/src/gateway/progress-watch-machine.js.map +1 -1
  73. package/dist/src/gateway/provenance-envelope.d.ts +3 -2
  74. package/dist/src/gateway/provenance-envelope.js +44 -11
  75. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  76. package/dist/src/gateway/providers.d.ts +12 -84
  77. package/dist/src/gateway/providers.js +69 -806
  78. package/dist/src/gateway/providers.js.map +1 -1
  79. package/dist/src/gateway/server.d.ts +8 -9
  80. package/dist/src/gateway/server.js +51 -99
  81. package/dist/src/gateway/server.js.map +1 -1
  82. package/dist/src/gateway/service.d.ts +22 -49
  83. package/dist/src/gateway/service.js +196 -432
  84. package/dist/src/gateway/service.js.map +1 -1
  85. package/dist/src/gateway/store.d.ts +20 -29
  86. package/dist/src/gateway/store.js +281 -827
  87. package/dist/src/gateway/store.js.map +1 -1
  88. package/dist/src/gateway/types.d.ts +55 -55
  89. package/dist/src/gateway/types.js +49 -91
  90. package/dist/src/gateway/types.js.map +1 -1
  91. package/docs/CONFIGURATION.md +11 -10
  92. package/docs/CONFIGURATION.zh-CN.md +10 -9
  93. package/docs/DASHBOARD.md +4 -38
  94. package/docs/DASHBOARD.zh-CN.md +4 -2
  95. package/docs/GATEWAY-ARCHITECTURE.md +61 -119
  96. package/package.json +1 -1
  97. 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,25 +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
- throw new CodexConnectorError("INVALID_CONFIGURATION");
480
- }
481
- this.writesEnabled = options.writesEnabled;
482
- this.writeCompatibilityProbe = options.writeCompatibilityProbe === true;
377
+ validateCompatibility(options.compatibility, this.route);
483
378
  this.transport = options.transport;
484
379
  this.clientInfo = validateClientInfo(options.clientInfo ?? {
485
380
  name: "agent_embassy_gateway",
486
381
  title: "Embassy Gateway",
487
- version: "1.6.0",
382
+ version: "1.7.0",
488
383
  });
489
384
  this.maxFrameBytes = positiveInteger(options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES, "INVALID_CONFIGURATION");
490
385
  this.maxDeadlineMs = positiveInteger(options.maxDeadlineMs ?? DEFAULT_MAX_DEADLINE_MS, "INVALID_CONFIGURATION");
@@ -500,7 +395,6 @@ export class CodexAppServerConnector {
500
395
  this.unlisten.push(this.transport.onMessage((payload) => this.handlePayload(payload)), this.transport.onClose(() => this.handleDisconnect()), this.transport.onError(() => this.protocolFault("TRANSPORT_CLOSED")));
501
396
  }
502
397
  observation() {
503
- const writeBlockCode = this.currentWriteBlockCode();
504
398
  return {
505
399
  connection: this.connection,
506
400
  eventSequence: this.eventSequence,
@@ -508,18 +402,14 @@ export class CodexAppServerConnector {
508
402
  queueDepth: this.queue.length,
509
403
  requestInFlight: this.inFlightMethod !== null,
510
404
  routeStatus: this.routeStatus,
511
- writableReady: this.isWritableReady(),
512
- writeBlockCode,
513
405
  };
514
406
  }
515
407
  guard() {
516
- const writableReady = this.isWritableReady();
517
408
  return {
518
409
  ...this.route,
519
410
  activeTurnId: this.activeTurnId,
520
411
  revision: this.revision,
521
412
  status: this.routeStatus,
522
- writableReady,
523
413
  };
524
414
  }
525
415
  async observeLoadedThread(guard) {
@@ -587,10 +477,7 @@ export class CodexAppServerConnector {
587
477
  throw new CodexConnectorError("THREAD_NOT_OBSERVED");
588
478
  }
589
479
  if (this.routeStatus !== "unknown" &&
590
- this.routeStatus !== "not_loaded" &&
591
- !(this.currentWriteBlockCode() !== null &&
592
- this.routeStatus === "idle" &&
593
- this.activeTurnId === null)) {
480
+ this.routeStatus !== "not_loaded") {
594
481
  throw new CodexConnectorError("ROUTE_BUSY");
595
482
  }
596
483
  const statusEpoch = this.statusEpoch;
@@ -622,10 +509,6 @@ export class CodexAppServerConnector {
622
509
  }
623
510
  }
624
511
  this.emit("thread_resumed");
625
- const writeBlockCode = this.currentWriteBlockCode();
626
- if (writeBlockCode !== null) {
627
- this.emit("route_write_blocked", { errorCode: writeBlockCode });
628
- }
629
512
  }
630
513
  catch (error) {
631
514
  const normalized = this.normalizeError(error, true);
@@ -700,7 +583,6 @@ export class CodexAppServerConnector {
700
583
  throw new CodexConnectorError("THREAD_NOT_OBSERVED");
701
584
  }
702
585
  if (input.steer === true && this.routeStatus === "active") {
703
- this.assertWritableReady();
704
586
  const message = this.validateMessage(input);
705
587
  if (this.activeTurnId === null || this.inFlightMethod !== null) {
706
588
  this.acceptedMessageIds.delete(message.messageId);
@@ -713,7 +595,6 @@ export class CodexAppServerConnector {
713
595
  this.routeStatus === "active" ||
714
596
  this.routeStatus === "waiting_approval" ||
715
597
  this.routeStatus === "interrupting") {
716
- this.assertWritableReady();
717
598
  const message = this.validateMessage(input);
718
599
  if (input.steer === true) {
719
600
  this.acceptedMessageIds.delete(message.messageId);
@@ -722,7 +603,6 @@ export class CodexAppServerConnector {
722
603
  this.enqueue(message);
723
604
  return { disposition: "queued", observation: this.observation() };
724
605
  }
725
- this.assertWritableReady();
726
606
  const message = this.validateMessage(input);
727
607
  if (this.routeStatus !== "idle") {
728
608
  this.acceptedMessageIds.delete(message.messageId);
@@ -794,318 +674,14 @@ export class CodexAppServerConnector {
794
674
  this.handleDisconnect();
795
675
  }
796
676
  }
797
- /**
798
- * Run the one fixed disposable-thread write probe. The method never exposes
799
- * JSON-RPC, native IDs, content, or route state, and every failure settles as
800
- * a safe result so compatibility discovery cannot take down the broker.
801
- */
802
- async runWriteCompatibilityProbe(input) {
803
- if (this.probeAttempted) {
804
- return codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
805
- }
806
- this.probeAttempted = true;
807
- let threadId = null;
808
- let archivedThreadCount;
809
- let result = codexWriteProbeFailure("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
810
- try {
811
- if (!this.writeCompatibilityProbe ||
812
- this.connection !== "ready" ||
813
- this.inFlightMethod !== null ||
814
- this.pending.size !== 0 ||
815
- !isAbsolute(input.cwd) ||
816
- !Array.isArray(input.forbiddenThreadIds) ||
817
- !input.forbiddenThreadIds.every((value) => typeof value === "string" && validOpaqueId(value))) {
818
- return result;
819
- }
820
- const beforeStat = await stat(input.cwd, { bigint: true });
821
- const beforeEntries = await readdir(input.cwd);
822
- if (!beforeStat.isDirectory() ||
823
- beforeStat.uid !== BigInt(process.getuid?.() ?? -1) ||
824
- (beforeStat.mode & 511n) !== 448n ||
825
- beforeEntries.length !== 0) {
826
- return result;
827
- }
828
- const limits = rateLimitConstrained(await this.request("account/rateLimits/read", null));
829
- if (limits === null)
830
- return result;
831
- if (limits) {
832
- return codexWriteProbeFailure("CODEX_WRITE_PROBE_RATE_LIMIT_CONSTRAINED");
833
- }
834
- const selection = this.selectProbeModel(await this.request("model/list", {
835
- includeHidden: true,
836
- limit: 100,
837
- }).catch(() => null));
838
- if (selection === null) {
839
- return codexWriteProbeFailure("CODEX_WRITE_PROBE_MODEL_PIN_UNAVAILABLE");
840
- }
841
- const [model, effort] = selection;
842
- const loadedBefore = parseLoadedThreadIds(await this.request("thread/loaded/list", {}));
843
- if (loadedBefore === null)
844
- return result;
845
- const runtime = {
846
- expectedCwd: input.cwd,
847
- expectedEffort: effort,
848
- expectedModel: model,
849
- failCode: null,
850
- phase: "awaiting_start",
851
- resolveTurn: null,
852
- settingsEchoObserved: false,
853
- settingsThreadId: null,
854
- threadId: null,
855
- tokenCount: null,
856
- turnId: null,
857
- };
858
- this.probeRuntime = runtime;
859
- const started = await this.request("thread/start", {
860
- allowProviderModelFallback: false,
861
- approvalPolicy: "never",
862
- cwd: input.cwd,
863
- dynamicTools: [],
864
- environments: [],
865
- ephemeral: false,
866
- model,
867
- runtimeWorkspaceRoots: [],
868
- sandbox: "read-only",
869
- selectedCapabilityRoots: [],
870
- });
871
- const candidateThreadId = this.probeCleanupThreadId(started, loadedBefore, input.forbiddenThreadIds);
872
- if (candidateThreadId === null ||
873
- !this.validateProbeThread(started, input.cwd, model, candidateThreadId)) {
874
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH", true);
875
- }
876
- threadId = candidateThreadId;
877
- runtime.threadId = candidateThreadId;
878
- if (runtime.settingsThreadId !== null &&
879
- runtime.settingsThreadId !== candidateThreadId) {
880
- runtime.failCode = "CODEX_WRITE_PROBE_THREAD_SETUP_FAILED";
881
- }
882
- if (runtime.failCode === null) {
883
- const turnResult = await this.request("turn/start", {
884
- approvalPolicy: "never",
885
- cwd: input.cwd,
886
- effort,
887
- environments: [],
888
- input: [{ text: CODEX_WRITE_PROBE_INPUT, type: "text" }],
889
- model,
890
- runtimeWorkspaceRoots: [],
891
- sandboxPolicy: { networkAccess: false, type: "readOnly" },
892
- threadId,
893
- });
894
- const turn = isRecord(turnResult) ? parseTurn(turnResult.turn) : null;
895
- if (turn === null ||
896
- !isRecord(turnResult) ||
897
- !isRecord(turnResult.turn) ||
898
- !Array.isArray(turnResult.turn.items) ||
899
- (turn.status !== "inProgress" && turn.status !== "completed")) {
900
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH", true);
901
- }
902
- if (runtime.turnId !== null && runtime.turnId !== turn.id) {
903
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH", true);
904
- }
905
- runtime.turnId = turn.id;
906
- await this.waitForProbeTurn(runtime);
907
- }
908
- if (runtime.failCode !== null) {
909
- result = codexWriteProbeFailure(runtime.failCode, {
910
- settingsEchoObserved: runtime.settingsEchoObserved,
911
- ...(runtime.tokenCount === null
912
- ? {}
913
- : { tokenCount: runtime.tokenCount }),
914
- });
915
- }
916
- else if (runtime.phase === "terminal" &&
917
- runtime.tokenCount !== null) {
918
- const afterStat = await stat(input.cwd, { bigint: true });
919
- const afterEntries = await readdir(input.cwd);
920
- if (afterEntries.length === 0 &&
921
- afterStat.dev === beforeStat.dev &&
922
- afterStat.ino === beforeStat.ino &&
923
- afterStat.mtimeNs === beforeStat.mtimeNs &&
924
- afterStat.uid === beforeStat.uid &&
925
- afterStat.mode === beforeStat.mode) {
926
- result = {
927
- archivedThreadCount: 1,
928
- outcome: "pass",
929
- settingsEchoObserved: runtime.settingsEchoObserved,
930
- tokenCount: runtime.tokenCount,
931
- };
932
- }
933
- else {
934
- result = codexWriteProbeFailure("CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED", {
935
- settingsEchoObserved: runtime.settingsEchoObserved,
936
- tokenCount: runtime.tokenCount,
937
- });
938
- }
939
- }
940
- }
941
- catch (error) {
942
- const normalized = this.normalizeError(error, true);
943
- const runtime = this.probeRuntime;
944
- result = codexWriteProbeFailure(normalized.code === "REQUEST_TIMEOUT"
945
- ? "CODEX_WRITE_PROBE_TIMEOUT"
946
- : "CODEX_WRITE_PROBE_THREAD_SETUP_FAILED", {
947
- settingsEchoObserved: runtime?.settingsEchoObserved ?? false,
948
- ...(runtime?.tokenCount === null || runtime?.tokenCount === undefined
949
- ? {}
950
- : { tokenCount: runtime.tokenCount }),
951
- });
952
- }
953
- finally {
954
- const runtime = this.probeRuntime;
955
- if (threadId !== null &&
956
- runtime !== null &&
957
- runtime.turnId !== null &&
958
- (result.outcome === "fail" || runtime.failCode !== null)) {
959
- try {
960
- await this.request("turn/interrupt", {
961
- threadId,
962
- turnId: runtime.turnId,
963
- });
964
- }
965
- catch {
966
- // Archival remains the one required cleanup proof below.
967
- }
968
- }
969
- let cleanupUnconfirmed = runtime !== null && threadId === null;
970
- if (threadId !== null) {
971
- try {
972
- const archived = await this.request("thread/archive", { threadId });
973
- if (!isRecord(archived) || Object.keys(archived).length !== 0) {
974
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH");
975
- }
976
- archivedThreadCount = 1;
977
- const unsubscribed = await this.request("thread/unsubscribe", {
978
- threadId,
979
- });
980
- if (!isRecord(unsubscribed) ||
981
- (unsubscribed.status !== "unsubscribed" &&
982
- unsubscribed.status !== "notSubscribed" &&
983
- unsubscribed.status !== "notLoaded")) {
984
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH");
985
- }
986
- const loadedAfter = parseLoadedThreadIds(await this.request("thread/loaded/list", {}));
987
- if (loadedAfter === null || loadedAfter.includes(threadId)) {
988
- throw new CodexConnectorError("RESULT_SCHEMA_MISMATCH");
989
- }
990
- }
991
- catch {
992
- cleanupUnconfirmed = true;
993
- }
994
- }
995
- const tokenCount = runtime?.tokenCount ?? result.tokenCount;
996
- const evidence = {
997
- ...(archivedThreadCount === undefined ? {} : { archivedThreadCount }),
998
- settingsEchoObserved: runtime?.settingsEchoObserved ?? result.settingsEchoObserved,
999
- ...(tokenCount === undefined ? {} : { tokenCount }),
1000
- };
1001
- if (cleanupUnconfirmed || (result.outcome === "pass" && archivedThreadCount !== 1)) {
1002
- result = codexWriteProbeFailure("CODEX_WRITE_PROBE_CLEANUP_UNCONFIRMED", evidence);
1003
- }
1004
- else if (runtime?.failCode !== null && runtime?.failCode !== undefined) {
1005
- result = codexWriteProbeFailure(runtime.failCode, evidence);
1006
- }
1007
- if (result.outcome === "fail") {
1008
- result = codexWriteProbeFailure(result.safeErrorCode, evidence);
1009
- }
1010
- this.probeRuntime = null;
1011
- }
1012
- return result;
1013
- }
1014
- selectProbeModel(value) {
1015
- if (!isRecord(value) || !Array.isArray(value.data) || value.data.length > 100) {
1016
- return null;
1017
- }
1018
- const data = value.data;
1019
- for (const model of CODEX_PROBE_MODEL_PREFERENCE) {
1020
- const candidate = data.find((entry) => isRecord(entry) &&
1021
- entry.model === model &&
1022
- entry.hidden === false);
1023
- if (!isRecord(candidate) || !Array.isArray(candidate.supportedReasoningEfforts)) {
1024
- continue;
1025
- }
1026
- const supported = candidate.supportedReasoningEfforts;
1027
- const effort = CODEX_PROBE_EFFORT.find((preference) => supported.some((option) => isRecord(option) && option.reasoningEffort === preference));
1028
- if (effort !== undefined)
1029
- return [model, effort];
1030
- }
1031
- return null;
1032
- }
1033
- probeCleanupThreadId(value, loadedBefore, forbiddenThreadIds) {
1034
- if (!isRecord(value) ||
1035
- !isRecord(value.thread) ||
1036
- typeof value.thread.id !== "string" ||
1037
- !validUuidV7(value.thread.id) ||
1038
- value.thread.id === this.route.threadId ||
1039
- loadedBefore.includes(value.thread.id) ||
1040
- forbiddenThreadIds.includes(value.thread.id)) {
1041
- return null;
1042
- }
1043
- return value.thread.id;
1044
- }
1045
- validateProbeThread(value, cwd, model, threadId) {
1046
- if (!isRecord(value) || !isRecord(value.thread))
1047
- return false;
1048
- const roots = value.runtimeWorkspaceRoots;
1049
- const networkAccess = isRecord(value.sandbox)
1050
- ? value.sandbox.networkAccess
1051
- : undefined;
1052
- return (value.approvalPolicy === "never" &&
1053
- value.cwd === cwd &&
1054
- value.model === model &&
1055
- isRecord(value.sandbox) &&
1056
- value.sandbox.type === "readOnly" &&
1057
- (networkAccess === undefined || networkAccess === false) &&
1058
- (roots === undefined || (Array.isArray(roots) && roots.length === 0)) &&
1059
- value.thread.id === threadId &&
1060
- value.thread.cwd === cwd &&
1061
- value.thread.ephemeral === false &&
1062
- isRecord(value.thread.status) &&
1063
- value.thread.status.type === "idle" &&
1064
- Array.isArray(value.thread.turns) &&
1065
- value.thread.turns.length === 0);
1066
- }
1067
- async waitForProbeTurn(runtime) {
1068
- if (runtime.failCode !== null ||
1069
- (runtime.phase === "terminal" && runtime.tokenCount !== null)) {
1070
- return;
1071
- }
1072
- let timer;
1073
- await new Promise((resolve) => {
1074
- runtime.resolveTurn = resolve;
1075
- timer = setTimeout(resolve, this.turnWatchdogMs);
1076
- });
1077
- runtime.resolveTurn = null;
1078
- if (timer !== undefined)
1079
- clearTimeout(timer);
1080
- if (runtime.failCode === null &&
1081
- !(runtime.phase === "terminal" && runtime.tokenCount !== null)) {
1082
- runtime.failCode = "CODEX_WRITE_PROBE_TIMEOUT";
1083
- }
1084
- }
1085
- settleProbeRuntime() {
1086
- const runtime = this.probeRuntime;
1087
- if (runtime === null || runtime.resolveTurn === null)
1088
- return;
1089
- if (runtime.failCode !== null ||
1090
- (runtime.phase === "terminal" && runtime.tokenCount !== null)) {
1091
- runtime.resolveTurn();
1092
- }
1093
- }
1094
- settleProbeConnectionLoss() {
1095
- if (this.probeRuntime === null)
1096
- return;
1097
- this.probeRuntime.failCode ??= "CODEX_WRITE_PROBE_THREAD_SETUP_FAILED";
1098
- this.settleProbeRuntime();
1099
- }
1100
677
  async initialize() {
1101
678
  try {
1102
679
  const result = await this.request("initialize", {
1103
680
  capabilities: {
1104
681
  // `thread/resume.excludeTurns` is field-gated behind this capability
1105
- // in the exactly attested 0.147.0 schema. The client method allowlist
1106
- // remains closed and exposes no experimental method.
682
+ // and is the only reason Embassy enables the experimental API.
1107
683
  experimentalApi: true,
1108
- optOutNotificationMethods: OUTPUT_NOTIFICATION_OPT_OUTS.filter((method) => method !== "item/started" || !this.writeCompatibilityProbe),
684
+ optOutNotificationMethods: OUTPUT_NOTIFICATION_OPT_OUTS,
1109
685
  },
1110
686
  clientInfo: this.clientInfo,
1111
687
  });
@@ -1171,7 +747,6 @@ export class CodexAppServerConnector {
1171
747
  }
1172
748
  async startMessage(message) {
1173
749
  this.assertNoRequest();
1174
- this.assertWritableReady();
1175
750
  if (this.messageExpired(message)) {
1176
751
  this.expireMessage(message);
1177
752
  return "expired";
@@ -1270,7 +845,6 @@ export class CodexAppServerConnector {
1270
845
  */
1271
846
  async steerMessage(message) {
1272
847
  this.assertNoRequest();
1273
- this.assertWritableReady();
1274
848
  if (this.messageExpired(message)) {
1275
849
  this.expireMessage(message);
1276
850
  return "expired";
@@ -1319,7 +893,6 @@ export class CodexAppServerConnector {
1319
893
  if (this.drainScheduled ||
1320
894
  this.connection !== "ready" ||
1321
895
  this.routeStatus !== "idle" ||
1322
- this.currentWriteBlockCode() !== null ||
1323
896
  this.inFlightMethod !== null ||
1324
897
  this.queue.length === 0) {
1325
898
  return;
@@ -1333,7 +906,6 @@ export class CodexAppServerConnector {
1333
906
  this.dropExpiredQueuedMessages();
1334
907
  if (this.connection !== "ready" ||
1335
908
  this.routeStatus !== "idle" ||
1336
- this.currentWriteBlockCode() !== null ||
1337
909
  this.inFlightMethod !== null ||
1338
910
  this.queue.length === 0) {
1339
911
  return;
@@ -1355,8 +927,7 @@ export class CodexAppServerConnector {
1355
927
  }
1356
928
  if (guard.revision !== this.revision ||
1357
929
  guard.status !== this.routeStatus ||
1358
- guard.activeTurnId !== this.activeTurnId ||
1359
- guard.writableReady !== this.isWritableReady()) {
930
+ guard.activeTurnId !== this.activeTurnId) {
1360
931
  throw new CodexConnectorError("ROUTE_CAS_MISMATCH");
1361
932
  }
1362
933
  }
@@ -1372,17 +943,6 @@ export class CodexAppServerConnector {
1372
943
  throw new CodexConnectorError("ROUTE_BUSY");
1373
944
  }
1374
945
  }
1375
- currentWriteBlockCode() {
1376
- return this.writesEnabled ? null : "WRITES_DISABLED";
1377
- }
1378
- isWritableReady() {
1379
- return this.connection === "ready" && this.currentWriteBlockCode() === null;
1380
- }
1381
- assertWritableReady() {
1382
- const code = this.currentWriteBlockCode();
1383
- if (code !== null)
1384
- throw new CodexConnectorError(code);
1385
- }
1386
946
  /**
1387
947
  * Refresh the exact thread on this same connection immediately before a
1388
948
  * `turn/start`. This confirms the registered task is still the observed idle
@@ -1390,7 +950,6 @@ export class CodexAppServerConnector {
1390
950
  * policy.
1391
951
  */
1392
952
  async refreshExactRouteBoundary() {
1393
- this.assertWritableReady();
1394
953
  if (this.routeStatus !== "starting" ||
1395
954
  this.activeMessageId === null ||
1396
955
  this.activeTurnId !== null ||
@@ -1419,7 +978,6 @@ export class CodexAppServerConnector {
1419
978
  this.setRouteStatus(refreshedStatus);
1420
979
  throw new CodexConnectorError("ROUTE_NOT_READY");
1421
980
  }
1422
- this.assertWritableReady();
1423
981
  if (this.statusEpoch !== statusEpoch ||
1424
982
  this.routeStatus !== "starting" ||
1425
983
  this.activeMessageId === null ||
@@ -1473,11 +1031,6 @@ export class CodexAppServerConnector {
1473
1031
  if (method !== "initialize" && !V1_METHOD_SET.has(method)) {
1474
1032
  return Promise.reject(new CodexConnectorError("METHOD_NOT_ALLOWED"));
1475
1033
  }
1476
- if (method !== "initialize" &&
1477
- PROBE_ONLY_METHODS.has(method) &&
1478
- !this.writeCompatibilityProbe) {
1479
- return Promise.reject(new CodexConnectorError("METHOD_NOT_ALLOWED"));
1480
- }
1481
1034
  if (method === "initialize") {
1482
1035
  if (this.initializeRequested || this.connection !== "connecting") {
1483
1036
  return Promise.reject(new CodexConnectorError("METHOD_NOT_ALLOWED"));
@@ -1594,8 +1147,6 @@ export class CodexAppServerConnector {
1594
1147
  pending.resolve(parsed.result);
1595
1148
  }
1596
1149
  handleNotification(method, params) {
1597
- if (this.handleProbeNotification(method, params))
1598
- return;
1599
1150
  if (method === "item/completed") {
1600
1151
  this.handleCompletedItem(params);
1601
1152
  return;
@@ -1825,14 +1376,6 @@ export class CodexAppServerConnector {
1825
1376
  // item/model/tool payloads are neither normalized nor retained.
1826
1377
  }
1827
1378
  handleServerRequest(method, params) {
1828
- if (this.probeRuntime !== null &&
1829
- isRecord(params) &&
1830
- params.threadId === this.probeRuntime.threadId) {
1831
- this.probeRuntime.failCode ??=
1832
- "CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED";
1833
- this.settleProbeRuntime();
1834
- return;
1835
- }
1836
1379
  if (!APPROVAL_REQUEST_METHODS.has(method)) {
1837
1380
  this.emit("server_request_ignored");
1838
1381
  return;
@@ -1857,135 +1400,6 @@ export class CodexAppServerConnector {
1857
1400
  // Intentionally no JSON-RPC response: the gateway is not an approval UI or
1858
1401
  // authority. The owning Desktop client must resolve the request.
1859
1402
  }
1860
- handleProbeNotification(method, params) {
1861
- const runtime = this.probeRuntime;
1862
- if (runtime === null)
1863
- return false;
1864
- const fail = (code) => {
1865
- runtime.failCode ??= code;
1866
- this.settleProbeRuntime();
1867
- };
1868
- if (method === "model/rerouted") {
1869
- fail("CODEX_WRITE_PROBE_MODEL_REROUTED");
1870
- return true;
1871
- }
1872
- if (!isRecord(params))
1873
- return false;
1874
- const observeTurnId = (value) => {
1875
- if (typeof value !== "string" || !validOpaqueId(value))
1876
- return false;
1877
- if (runtime.turnId !== null && runtime.turnId !== value)
1878
- return false;
1879
- runtime.turnId = value;
1880
- return true;
1881
- };
1882
- if (method === "thread/settings/updated") {
1883
- runtime.settingsEchoObserved = true;
1884
- const settingsThreadId = params.threadId;
1885
- const settings = params.threadSettings;
1886
- const sandbox = isRecord(settings) ? settings.sandboxPolicy : null;
1887
- if (typeof settingsThreadId !== "string" ||
1888
- !validUuidV7(settingsThreadId) ||
1889
- (runtime.threadId !== null && runtime.threadId !== settingsThreadId) ||
1890
- (runtime.settingsThreadId !== null &&
1891
- runtime.settingsThreadId !== settingsThreadId) ||
1892
- !isRecord(settings) ||
1893
- settings.cwd !== runtime.expectedCwd ||
1894
- settings.model !== runtime.expectedModel ||
1895
- settings.approvalPolicy !== "never" ||
1896
- (settings.effort !== undefined &&
1897
- settings.effort !== runtime.expectedEffort) ||
1898
- !isRecord(sandbox) ||
1899
- sandbox.type !== "readOnly" ||
1900
- (sandbox.networkAccess !== undefined &&
1901
- sandbox.networkAccess !== false)) {
1902
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1903
- }
1904
- else {
1905
- runtime.settingsThreadId = settingsThreadId;
1906
- }
1907
- return true;
1908
- }
1909
- if (runtime.threadId === null || params.threadId !== runtime.threadId) {
1910
- return false;
1911
- }
1912
- if (PROBE_TOOL_NOTIFICATION_METHODS.has(method)) {
1913
- fail("CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED");
1914
- return true;
1915
- }
1916
- if (method === "thread/tokenUsage/updated") {
1917
- const totalTokens = observeTurnId(params.turnId) &&
1918
- isRecord(params.tokenUsage) &&
1919
- isRecord(params.tokenUsage.last)
1920
- ? params.tokenUsage.last.totalTokens
1921
- : null;
1922
- if (!Number.isSafeInteger(totalTokens) ||
1923
- totalTokens < 0 ||
1924
- totalTokens > MAX_PROBE_TOKEN_COUNT) {
1925
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1926
- }
1927
- else {
1928
- runtime.tokenCount = totalTokens;
1929
- this.settleProbeRuntime();
1930
- }
1931
- return true;
1932
- }
1933
- if (method === "turn/started" || method === "turn/completed") {
1934
- const turn = parseTurn(params.turn);
1935
- if (turn === null ||
1936
- !isRecord(params.turn) ||
1937
- !Array.isArray(params.turn.items) ||
1938
- !observeTurnId(turn.id)) {
1939
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1940
- }
1941
- else if (method === "turn/started") {
1942
- if (turn.status !== "inProgress" ||
1943
- runtime.phase !== "awaiting_start") {
1944
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1945
- }
1946
- else {
1947
- runtime.phase = "started";
1948
- }
1949
- }
1950
- else if (turn.status === "completed" &&
1951
- runtime.phase === "item_completed") {
1952
- runtime.phase = "terminal";
1953
- this.settleProbeRuntime();
1954
- }
1955
- else {
1956
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1957
- }
1958
- return true;
1959
- }
1960
- if (method === "item/started" || method === "item/completed") {
1961
- const observedAt = method === "item/started" ? params.startedAtMs : params.completedAtMs;
1962
- const itemType = isRecord(params.item) ? params.item.type : null;
1963
- if (typeof itemType === "string" &&
1964
- !PROBE_PASSIVE_ITEM_TYPES.has(itemType)) {
1965
- fail("CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED");
1966
- return true;
1967
- }
1968
- if (!observeTurnId(params.turnId) ||
1969
- !isRecord(params.item) ||
1970
- typeof itemType !== "string" ||
1971
- !Number.isSafeInteger(observedAt) ||
1972
- observedAt < 0 ||
1973
- (runtime.phase !== "started" && runtime.phase !== "item_completed")) {
1974
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1975
- }
1976
- else if (method === "item/completed" &&
1977
- itemType === "agentMessage") {
1978
- if (runtime.phase !== "started") {
1979
- fail("CODEX_WRITE_PROBE_THREAD_SETUP_FAILED");
1980
- }
1981
- else {
1982
- runtime.phase = "item_completed";
1983
- }
1984
- }
1985
- return true;
1986
- }
1987
- return false;
1988
- }
1989
1403
  handleCompletedItem(params) {
1990
1404
  if (!isRecord(params) ||
1991
1405
  typeof params.threadId !== "string") {
@@ -2129,7 +1543,6 @@ export class CodexAppServerConnector {
2129
1543
  return;
2130
1544
  this.faulting = true;
2131
1545
  this.connection = "faulted";
2132
- this.settleProbeConnectionLoss();
2133
1546
  this.initialized = false;
2134
1547
  this.routeStatus = "stale";
2135
1548
  this.selectedThreadObserved = false;
@@ -2150,7 +1563,6 @@ export class CodexAppServerConnector {
2150
1563
  if (this.connection === "closed" || this.connection === "faulted")
2151
1564
  return;
2152
1565
  this.connection = "closed";
2153
- this.settleProbeConnectionLoss();
2154
1566
  this.initialized = false;
2155
1567
  this.routeStatus = "stale";
2156
1568
  this.selectedThreadObserved = false;