@pasko70/pibo 2.5.2 → 3.0.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 (100) hide show
  1. package/context/pibo-native-tooling.md +1 -0
  2. package/dist/agent-runtime/contract.js +2 -1
  3. package/dist/agent-runtime/history.js +53 -1
  4. package/dist/agent-runtime/routed-session.js +100 -26
  5. package/dist/agent-runtimes/codex-native/adapter.js +304 -30
  6. package/dist/agent-runtimes/codex-native/client.js +22 -3
  7. package/dist/agent-runtimes/codex-native/first-use.js +294 -0
  8. package/dist/agent-runtimes/codex-native/history.js +26 -7
  9. package/dist/agent-runtimes/codex-native/process.js +14 -0
  10. package/dist/agent-runtimes/codex-native/thread.js +59 -25
  11. package/dist/agent-runtimes/codex-native/turn.js +27 -0
  12. package/dist/agent-runtimes/history-proof.js +8 -0
  13. package/dist/agent-runtimes/omp/adapter.js +61 -14
  14. package/dist/agent-runtimes/omp/history.js +189 -34
  15. package/dist/agent-runtimes/omp/thread.js +82 -36
  16. package/dist/agent-runtimes/pi/adapter.js +30 -3
  17. package/dist/agent-runtimes/pi/history.js +66 -5
  18. package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
  19. package/dist/agent-runtimes/pi/routed-session.js +344 -37
  20. package/dist/apps/chat/chat-api-routes.js +1 -1
  21. package/dist/apps/chat/chat-files.js +130 -9
  22. package/dist/apps/chat/chat-settings-routes.js +27 -2
  23. package/dist/apps/chat/chat-speech.js +100 -0
  24. package/dist/apps/chat/data/chat-data-mappers.js +25 -5
  25. package/dist/apps/chat/data/timeline-query-service.js +89 -1
  26. package/dist/apps/chat/trace-v2.js +210 -8
  27. package/dist/apps/chat/trace.js +3 -0
  28. package/dist/apps/chat/web-app.js +108 -3
  29. package/dist/apps/chat-ui/assets/{dist-DTWJf37A.js → dist-BBDMeU5-.js} +1 -1
  30. package/dist/apps/chat-ui/assets/{dist-CkYWKnvy.js → dist-BDIATCQt.js} +1 -1
  31. package/dist/apps/chat-ui/assets/{dist-DZ8Wpj-H.js → dist-JnW4v8UE.js} +1 -1
  32. package/dist/apps/chat-ui/assets/{dist-Dmblz15N.js → dist-lRiLP9rr.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-DjcwoyNY.js → dist-mqCviI-c.js} +1 -1
  34. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
  35. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
  36. package/dist/apps/chat-ui/index.html +2 -2
  37. package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
  38. package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
  39. package/dist/apps/chat-vscode-web/index.html +2 -2
  40. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  41. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
  42. package/dist/cli.js +49 -5
  43. package/dist/config/config.js +59 -8
  44. package/dist/core/preview-server-settings.js +20 -0
  45. package/dist/core/session-router.js +457 -37
  46. package/dist/core/user-settings.js +10 -0
  47. package/dist/data/payload-store.js +57 -1
  48. package/dist/data/schema.js +18 -1
  49. package/dist/debug/trace.js +5 -0
  50. package/dist/gateway/client.js +368 -65
  51. package/dist/gateway/protocol.js +2 -0
  52. package/dist/gateway/server.js +8 -0
  53. package/dist/gateway/web.js +2 -0
  54. package/dist/index.js +1 -1
  55. package/dist/loops/store.js +6 -4
  56. package/dist/mcp/config.js +89 -17
  57. package/dist/mcp/daemon-client.js +473 -153
  58. package/dist/mcp/daemon.js +401 -37
  59. package/dist/plugins/builtin.js +2 -2
  60. package/dist/plugins/codex-native.js +2 -0
  61. package/dist/plugins/registry.js +259 -1
  62. package/dist/previews/cli.js +345 -0
  63. package/dist/previews/config.js +61 -0
  64. package/dist/previews/manager.js +644 -0
  65. package/dist/previews/network.js +198 -0
  66. package/dist/previews/plugin.js +11 -0
  67. package/dist/previews/process-supervisor.js +38 -0
  68. package/dist/previews/proxy.js +387 -0
  69. package/dist/previews/store.js +750 -0
  70. package/dist/previews/types.js +1 -0
  71. package/dist/previews/web-app.js +446 -0
  72. package/dist/runs/registry.js +22 -0
  73. package/dist/runs/tools.js +46 -7
  74. package/dist/session-ui/terminalRows.js +187 -13
  75. package/dist/sessions/pibo-data-store.js +37 -0
  76. package/dist/sessions/runtime-binding-persistence.js +34 -0
  77. package/dist/sessions/runtime-binding.js +6 -0
  78. package/dist/sessions/sqlite-store.js +37 -0
  79. package/dist/shared/trace-engine.js +12 -64
  80. package/dist/shared/trace-event-projection.js +16 -0
  81. package/dist/shared/trace-history.js +684 -184
  82. package/dist/shared/trace-limits.js +7 -0
  83. package/dist/shared/trace-live-patch.js +61 -0
  84. package/dist/shared/trace-tool-identity.js +43 -0
  85. package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
  86. package/dist/speech/openai-codex.js +407 -0
  87. package/dist/speech/types.js +8 -0
  88. package/dist/subagents/context.js +5 -3
  89. package/dist/subagents/observations.js +94 -6
  90. package/dist/subagents/tool.js +103 -20
  91. package/dist/tools/codex-image-artifacts.js +24 -0
  92. package/dist/tools/codex-image-generation.js +4 -22
  93. package/dist/web/channel.js +88 -4
  94. package/dist/web/http.js +2 -1
  95. package/npm-shrinkwrap.json +2 -2
  96. package/package.json +1 -1
  97. package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
  98. package/dist/apps/chat-ui/assets/index-DsgTm1Aw.js +0 -228
  99. package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
  100. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.2.vsix +0 -0
@@ -1,10 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { unsupportedAgentRuntimeCapability, } from "../../agent-runtime/capabilities.js";
3
3
  import { AgentRuntimeBindingMissingError, AgentRuntimeUnavailableError, } from "../../agent-runtime/errors.js";
4
+ import { isAgentRuntimeBindingPersistence } from "../../sessions/runtime-binding-persistence.js";
5
+ import { PENDING_NATIVE_SESSION_METADATA_KEY } from "../../sessions/runtime-binding.js";
4
6
  import { selectRequestedFastMode, selectRequestedModelProfile, selectRequestedThinkingLevel, } from "../../core/model-defaults.js";
5
7
  import { CODEX_NATIVE_RUNTIME_CONFIG_SCHEMA, defaultCodexNativeRuntimeConfig, parseCodexNativeRuntimeConfig, } from "./config.js";
6
8
  import { diagnoseCodexNativeRuntime, startCodexNativeAppServer, } from "./process.js";
7
9
  import { inspectCodexThreadHistory, pageCodexThreadHistory, unavailableCodexThreadHistoryInspection, } from "./history.js";
10
+ import { historyReconciliationDigest, } from "../../agent-runtime/history.js";
8
11
  import { CODEX_NATIVE_ADAPTER_ID, CodexNativeThreadController, CodexNativeThreadMissingError, } from "./thread.js";
9
12
  import { CODEX_APP_SERVER_PROTOCOL_NAME, CODEX_APP_SERVER_SUPPORTED_RANGE, CODEX_APP_SERVER_VERSION, } from "./protocol-version.js";
10
13
  import { CodexNativeTurnController } from "./turn.js";
@@ -13,8 +16,69 @@ import { CodexNativeResourceDelivery } from "./resource-delivery.js";
13
16
  import { CODEX_NATIVE_MODEL_OPTIONS_SCHEMA, CODEX_NATIVE_MODEL_PROVIDER_ID, CODEX_NATIVE_REASONING_VALUES, CodexNativeSessionSettingsController, parseCodexNativeProfileOptions, readCodexNativeModelCatalog, readCodexNativePersistedSettings, toAgentRuntimeModelCatalog, } from "./models.js";
14
17
  import { CodexNativeAuthController } from "./auth.js";
15
18
  import { injectPortableHistoryIntoCodex } from "./portable-history.js";
19
+ import { CODEX_FIRST_USE_METADATA_KEY, CODEX_FIRST_USE_METADATA_VERSION, assertCodexNativeFirstUseMessageId, assertCodexNativeFirstUseDeliveryReceiptTurn, assertCodexNativePendingFirstUseRequest, assertCodexNativePendingFirstUseTurn, beginCodexNativeFirstUseAttempt, codexNativeFirstUseDeliveryReceipt, codexNativePendingFirstUseOwnerLiveness, endCodexNativeFirstUseAttempt, hashCanonicalCodexNativeFirstUsePrompt, isExactCodexNativeFirstUseDeliveryReplay, readCodexNativeFirstUseDeliveryReceipt, readCodexNativePendingFirstUse, } from "./first-use.js";
16
20
  export { CODEX_NATIVE_ADAPTER_ID } from "./thread.js";
17
21
  export const CODEX_NATIVE_ADAPTER_VERSION = "1.0.0";
22
+ function readPendingFirstUse(binding) {
23
+ try {
24
+ return readCodexNativePendingFirstUse(binding);
25
+ }
26
+ catch (error) {
27
+ throw new AgentRuntimeUnavailableError(binding.runtimeInstanceId, error instanceof Error ? error.message : "The pending native Codex first-use metadata is invalid.");
28
+ }
29
+ }
30
+ function readFirstUseDeliveryReceipt(binding) {
31
+ try {
32
+ return readCodexNativeFirstUseDeliveryReceipt(binding);
33
+ }
34
+ catch (error) {
35
+ throw new AgentRuntimeUnavailableError(binding.runtimeInstanceId, error instanceof Error ? error.message : "The delivered native Codex first-use receipt is invalid.");
36
+ }
37
+ }
38
+ function deliveryReceiptForBinding(binding) {
39
+ if (binding.metadata?.[CODEX_FIRST_USE_METADATA_KEY] === undefined)
40
+ return undefined;
41
+ if (binding.state === "unbound") {
42
+ const pending = readPendingFirstUse(binding);
43
+ return pending ? codexNativeFirstUseDeliveryReceipt(pending) : undefined;
44
+ }
45
+ return readFirstUseDeliveryReceipt(binding);
46
+ }
47
+ function pendingFirstUseBinding(binding, threadId, messageId, promptHash, owner) {
48
+ return {
49
+ ...structuredClone(binding),
50
+ nativeSessionId: threadId,
51
+ state: "unbound",
52
+ protocol: CODEX_APP_SERVER_PROTOCOL_NAME,
53
+ protocolVersion: CODEX_APP_SERVER_VERSION,
54
+ adapterVersion: CODEX_NATIVE_ADAPTER_VERSION,
55
+ locator: { kind: "adapter-resolved" },
56
+ metadata: {
57
+ ...(binding.metadata ?? {}),
58
+ [PENDING_NATIVE_SESSION_METADATA_KEY]: true,
59
+ [CODEX_FIRST_USE_METADATA_KEY]: {
60
+ version: CODEX_FIRST_USE_METADATA_VERSION,
61
+ state: "pending",
62
+ threadId,
63
+ messageId,
64
+ promptHash,
65
+ ...owner,
66
+ },
67
+ },
68
+ };
69
+ }
70
+ function clearPendingFirstUse(binding) {
71
+ const metadata = { ...(binding.metadata ?? {}) };
72
+ delete metadata[CODEX_FIRST_USE_METADATA_KEY];
73
+ delete metadata[PENDING_NATIVE_SESSION_METADATA_KEY];
74
+ return {
75
+ ...structuredClone(binding),
76
+ nativeSessionId: undefined,
77
+ state: "unbound",
78
+ locator: undefined,
79
+ metadata,
80
+ };
81
+ }
18
82
  function codexNativeCapabilities(structuredUserInput) {
19
83
  return {
20
84
  lifecycle: {
@@ -111,8 +175,10 @@ export const CODEX_NATIVE_THREAD_CAPABILITIES = codexNativeCapabilities(false);
111
175
  function timestamp(seconds) {
112
176
  return new Date(seconds * 1_000).toISOString();
113
177
  }
114
- function safeThreadMetadata(thread, previous = {}, settings = {}) {
178
+ function safeThreadMetadata(thread, previous = {}, settings = {}, deliveryReceipt) {
115
179
  const { diagnosticCode: _diagnosticCode, diagnosticMessage: _diagnosticMessage, ...metadata } = previous;
180
+ delete metadata[CODEX_FIRST_USE_METADATA_KEY];
181
+ delete metadata[PENDING_NATIVE_SESSION_METADATA_KEY];
116
182
  return {
117
183
  ...metadata,
118
184
  ...settings,
@@ -122,9 +188,11 @@ function safeThreadMetadata(thread, previous = {}, settings = {}) {
122
188
  threadUpdatedAt: timestamp(thread.updatedAt),
123
189
  threadStatus: thread.status.type,
124
190
  modelProvider: thread.modelProvider,
191
+ ...(deliveryReceipt ? { [CODEX_FIRST_USE_METADATA_KEY]: deliveryReceipt } : {}),
125
192
  };
126
193
  }
127
194
  function bindingForThread(input) {
195
+ const deliveryReceipt = input.previous ? deliveryReceiptForBinding(input.previous) : undefined;
128
196
  return {
129
197
  ...(input.previous ? structuredClone(input.previous) : {}),
130
198
  piboSessionId: input.piboSessionId,
@@ -136,7 +204,7 @@ function bindingForThread(input) {
136
204
  protocolVersion: CODEX_APP_SERVER_VERSION,
137
205
  adapterVersion: CODEX_NATIVE_ADAPTER_VERSION,
138
206
  locator: { kind: "adapter-resolved" },
139
- metadata: safeThreadMetadata(input.thread, input.previous?.metadata, input.settings),
207
+ metadata: safeThreadMetadata(input.thread, input.previous?.metadata, input.settings, deliveryReceipt),
140
208
  };
141
209
  }
142
210
  function validateOpenBinding(input, runtimeInstanceId) {
@@ -163,8 +231,31 @@ function validateOpenBinding(input, runtimeInstanceId) {
163
231
  if (binding.state === "bound" && !binding.nativeSessionId) {
164
232
  throw new AgentRuntimeUnavailableError(runtimeInstanceId, "The persisted Codex binding has no native thread id.");
165
233
  }
234
+ if (binding.state === "bound")
235
+ readFirstUseDeliveryReceipt(binding);
166
236
  if (binding.state === "unbound" && binding.nativeSessionId) {
167
- throw new AgentRuntimeUnavailableError(runtimeInstanceId, "An unbound Codex binding cannot contain a native thread id.");
237
+ const pending = readPendingFirstUse(binding);
238
+ const hasCurrentPendingMarker = binding.metadata?.[PENDING_NATIVE_SESSION_METADATA_KEY] === true;
239
+ const isExactParentLegacyPending = pending?.version === 1
240
+ && binding.metadata?.[PENDING_NATIVE_SESSION_METADATA_KEY] === undefined;
241
+ if (!pending
242
+ || pending.threadId !== binding.nativeSessionId
243
+ || (!hasCurrentPendingMarker && !isExactParentLegacyPending)
244
+ || input.piboSession.kind !== "branch") {
245
+ throw new AgentRuntimeUnavailableError(runtimeInstanceId, "An unbound Codex binding contains an invalid pending native thread identity.");
246
+ }
247
+ if (isExactParentLegacyPending) {
248
+ return {
249
+ ...binding,
250
+ metadata: {
251
+ ...(binding.metadata ?? {}),
252
+ [PENDING_NATIVE_SESSION_METADATA_KEY]: true,
253
+ },
254
+ };
255
+ }
256
+ }
257
+ else if (binding.state === "unbound" && binding.metadata?.[CODEX_FIRST_USE_METADATA_KEY] !== undefined) {
258
+ throw new AgentRuntimeUnavailableError(runtimeInstanceId, "An unbound Codex binding contains pending metadata without a native thread identity.");
168
259
  }
169
260
  return binding;
170
261
  }
@@ -177,6 +268,8 @@ export class CodexNativeThreadSession {
177
268
  structuredUserInput;
178
269
  reloadProcess;
179
270
  productContext;
271
+ bindingPersistence;
272
+ firstUseTestFailpoints;
180
273
  adapterId = CODEX_NATIVE_ADAPTER_ID;
181
274
  cwd;
182
275
  capabilities;
@@ -198,12 +291,14 @@ export class CodexNativeThreadSession {
198
291
  selectedToolNames = new Set();
199
292
  observedToolNames = new Set();
200
293
  toolInventoryWarning;
201
- constructor(runtimeInstanceId, process, threads, settings, resourceDelivery, binding, structuredUserInput, reloadProcess, productContext) {
294
+ constructor(runtimeInstanceId, process, threads, settings, resourceDelivery, binding, structuredUserInput, reloadProcess, productContext, bindingPersistence, firstUseTestFailpoints) {
202
295
  this.runtimeInstanceId = runtimeInstanceId;
203
296
  this.settings = settings;
204
297
  this.structuredUserInput = structuredUserInput;
205
298
  this.reloadProcess = reloadProcess;
206
299
  this.productContext = productContext;
300
+ this.bindingPersistence = bindingPersistence;
301
+ this.firstUseTestFailpoints = firstUseTestFailpoints;
207
302
  this.process = process;
208
303
  this.threads = threads;
209
304
  this.resourceDelivery = resourceDelivery;
@@ -219,14 +314,16 @@ export class CodexNativeThreadSession {
219
314
  getForkCandidates: () => this.threads.getForkCandidates(),
220
315
  forkSession: async (entryId) => await this.runIdleOperation(async () => {
221
316
  const result = await this.threads.fork(this.runtimeInstanceId, this.cwd, entryId, async (threadId) => await this.resourceDelivery.verifyThread(this.process.client, threadId));
222
- this.settings.attachThread(this.threads.thread.id, this.threads.configuration);
223
- this.updateBindingFromCurrentThread();
317
+ if (result.current.nativeSessionId) {
318
+ this.settings.attachThread(this.threads.thread.id, this.threads.configuration);
319
+ this.promoteBindingFromCurrentThread();
320
+ }
224
321
  return result;
225
322
  }),
226
323
  cloneSession: async () => await this.runIdleOperation(async () => {
227
324
  const result = await this.threads.clone(this.runtimeInstanceId, this.cwd, async (threadId) => await this.resourceDelivery.verifyThread(this.process.client, threadId));
228
325
  this.settings.attachThread(this.threads.thread.id, this.threads.configuration);
229
- this.updateBindingFromCurrentThread();
326
+ this.promoteBindingFromCurrentThread();
230
327
  return result;
231
328
  }),
232
329
  getReasoning: () => this.settings.reasoning,
@@ -256,7 +353,7 @@ export class CodexNativeThreadSession {
256
353
  });
257
354
  }
258
355
  await this.turns.compact();
259
- this.updateBindingFromCurrentThread();
356
+ this.refreshBoundBindingFromCurrentThread();
260
357
  return {
261
358
  native: true,
262
359
  method: "thread/compact/start",
@@ -281,7 +378,7 @@ export class CodexNativeThreadSession {
281
378
  return this.requests.pendingUserInputs;
282
379
  }
283
380
  getBinding() {
284
- this.updateBindingFromCurrentThread();
381
+ this.refreshBoundBindingFromCurrentThread();
285
382
  return structuredClone(this.binding);
286
383
  }
287
384
  subscribe(listener) {
@@ -292,12 +389,25 @@ export class CodexNativeThreadSession {
292
389
  async prompt(input) {
293
390
  this.assertActive();
294
391
  await this.ensureFreshResourcesForTurn();
392
+ this.assertIdle();
295
393
  this.operationInFlight = true;
394
+ let ownedFirstUseAttempt;
296
395
  try {
297
- await this.turns.start(input.text, this.productContext?.getActiveMessage?.()?.id ?? randomUUID(), this.settings.turnOptions);
298
- this.updateBindingFromCurrentThread();
396
+ const firstUse = await this.acquirePendingFirstUse(this.productContext?.getActiveMessage?.()?.id ?? randomUUID(), input.text);
397
+ ownedFirstUseAttempt = firstUse.attemptId;
398
+ if (!firstUse.startTurn)
399
+ return;
400
+ await this.firstUseTestFailpoints?.afterPendingBindingPersisted?.();
401
+ await this.turns.start(input.text, firstUse.messageId, this.settings.turnOptions);
402
+ await this.firstUseTestFailpoints?.afterNativeTerminalDurable?.();
403
+ const pending = this.binding.state === "unbound" ? readPendingFirstUse(this.binding) : undefined;
404
+ if (pending)
405
+ assertCodexNativePendingFirstUseTurn(pending, this.threads.thread);
406
+ this.promoteBindingFromCurrentThread();
299
407
  }
300
408
  finally {
409
+ if (ownedFirstUseAttempt)
410
+ endCodexNativeFirstUseAttempt(ownedFirstUseAttempt);
301
411
  this.operationInFlight = false;
302
412
  if (this.recycleProcessAfterInterruptedTurn && !this.disposed) {
303
413
  this.recycleProcessAfterInterruptedTurn = false;
@@ -531,7 +641,7 @@ export class CodexNativeThreadSession {
531
641
  this.requests = this.createRequestController(next.process);
532
642
  previousRequests.dispose();
533
643
  previousTurns.dispose();
534
- this.updateBindingFromCurrentThread();
644
+ this.refreshBoundBindingFromCurrentThread();
535
645
  this.resourceProcessUnavailable = false;
536
646
  this.resourceWarning = undefined;
537
647
  next = undefined;
@@ -574,7 +684,65 @@ export class CodexNativeThreadSession {
574
684
  }
575
685
  }
576
686
  }
577
- updateBindingFromCurrentThread() {
687
+ refreshBoundBindingFromCurrentThread() {
688
+ if (this.binding.state !== "bound")
689
+ return;
690
+ this.promoteBindingFromCurrentThread();
691
+ }
692
+ async acquirePendingFirstUse(messageId, prompt) {
693
+ if (this.binding.state !== "unbound") {
694
+ const receipt = readFirstUseDeliveryReceipt(this.binding);
695
+ if (!receipt || !isExactCodexNativeFirstUseDeliveryReplay(receipt, messageId, prompt)) {
696
+ return { messageId, startTurn: true };
697
+ }
698
+ const firstTurn = assertCodexNativeFirstUseDeliveryReceiptTurn(receipt, this.threads.thread);
699
+ this.turns.replayTerminalTurn(firstTurn);
700
+ return { messageId: receipt.messageId, startTurn: false };
701
+ }
702
+ assertCodexNativeFirstUseMessageId(messageId);
703
+ const promptHash = hashCanonicalCodexNativeFirstUsePrompt(prompt);
704
+ const currentPending = readPendingFirstUse(this.binding);
705
+ if (currentPending && this.threads.thread.turns.length > 0) {
706
+ assertCodexNativePendingFirstUseTurn(currentPending, this.threads.thread);
707
+ assertCodexNativePendingFirstUseRequest(currentPending, messageId, prompt);
708
+ this.promoteBindingFromCurrentThread();
709
+ return { messageId: currentPending.messageId, startTurn: false };
710
+ }
711
+ if (currentPending) {
712
+ const ownerLiveness = codexNativePendingFirstUseOwnerLiveness(currentPending);
713
+ if (ownerLiveness === "active") {
714
+ throw new Error("Native Codex first use is already owned by another live router.");
715
+ }
716
+ if (ownerLiveness === "ambiguous") {
717
+ throw new Error("Native Codex first-use ownership is ambiguous and cannot be replaced safely.");
718
+ }
719
+ assertCodexNativePendingFirstUseRequest(currentPending, messageId, prompt);
720
+ }
721
+ if (!isAgentRuntimeBindingPersistence(this.bindingPersistence)) {
722
+ throw new Error("Native Codex first use requires audited durable cross-process atomic runtime-binding CAS before turn/start.");
723
+ }
724
+ const owner = beginCodexNativeFirstUseAttempt();
725
+ const proposed = pendingFirstUseBinding(this.binding, this.threads.thread.id, messageId, promptHash, owner);
726
+ try {
727
+ const expectedRevision = this.binding.revision ?? 1;
728
+ const persisted = await this.bindingPersistence.compareAndSet(proposed, expectedRevision);
729
+ const pending = readPendingFirstUse(persisted);
730
+ if (persisted.revision !== expectedRevision + 1
731
+ || persisted.state !== "unbound"
732
+ || persisted.nativeSessionId !== proposed.nativeSessionId
733
+ || !pending
734
+ || JSON.stringify(pending) !== JSON.stringify(readPendingFirstUse(proposed))) {
735
+ throw new Error("Runtime-binding persistence returned an inconsistent Codex first-use claim.");
736
+ }
737
+ this.binding = structuredClone(persisted);
738
+ return { attemptId: owner.attemptId, messageId, startTurn: true };
739
+ }
740
+ catch (error) {
741
+ endCodexNativeFirstUseAttempt(owner.attemptId);
742
+ throw error;
743
+ }
744
+ }
745
+ promoteBindingFromCurrentThread() {
578
746
  this.binding = bindingForThread({
579
747
  piboSessionId: this.binding.piboSessionId,
580
748
  runtimeInstanceId: this.runtimeInstanceId,
@@ -589,12 +757,38 @@ export class CodexNativeThreadSession {
589
757
  }
590
758
  assertIdle() {
591
759
  this.assertActive();
592
- if (this.turns.streaming || this.resourceRefresh || this.resourceProcessUnavailable) {
760
+ if (this.operationInFlight || this.turns.streaming || this.resourceRefresh || this.resourceProcessUnavailable) {
593
761
  throw new Error("Codex runtime controls can only change while the session is idle.");
594
762
  }
595
763
  }
596
764
  }
597
- export class CodexNativeAgentRuntimeAdapter {
765
+ const codexNativeCompleteHistoryProofs = new WeakMap();
766
+ const codexNativeHistoryReaders = new WeakMap();
767
+ const startCodexNativeHistoryProcess = startCodexNativeAppServer;
768
+ const readCodexNativeHistoryThread = CodexNativeThreadController.read;
769
+ function bindCodexNativeCompleteHistoryProof(page) {
770
+ const claim = page.reconciliationProof;
771
+ if (!claim?.complete)
772
+ return page;
773
+ const entries = Object.freeze([...claim.entries]);
774
+ const fullScope = Object.freeze({ entryCount: entries.length, digest: historyReconciliationDigest(entries) });
775
+ const proof = Object.freeze({
776
+ complete: true,
777
+ ...(claim.scopeId ? { scopeId: claim.scopeId } : {}),
778
+ fullScope,
779
+ entries,
780
+ });
781
+ codexNativeCompleteHistoryProofs.set(proof, { ...(claim.scopeId ? { scopeId: claim.scopeId } : {}), fullScope });
782
+ return { ...page, reconciliationProof: proof };
783
+ }
784
+ export function isCodexNativeBuiltInHistoryReconciliationProof(proof) {
785
+ const bound = codexNativeCompleteHistoryProofs.get(proof);
786
+ return proof.complete
787
+ && bound !== undefined
788
+ && proof.scopeId === bound.scopeId
789
+ && proof.fullScope === bound.fullScope;
790
+ }
791
+ class CodexNativeAgentRuntimeAdapter {
598
792
  instanceId;
599
793
  descriptor;
600
794
  config;
@@ -611,6 +805,24 @@ export class CodexNativeAgentRuntimeAdapter {
611
805
  };
612
806
  this.displayName = displayName ?? this.descriptor.displayName;
613
807
  this.enabled = enabled;
808
+ const historyConfig = structuredClone(this.config);
809
+ const historyInstanceId = this.instanceId;
810
+ codexNativeHistoryReaders.set(this, async (piboSessionId, workspace, threadId, includeTurns) => {
811
+ const process = await startCodexNativeHistoryProcess({
812
+ config: historyConfig,
813
+ runtimeInstanceId: historyInstanceId,
814
+ piboSessionId,
815
+ sessionGeneration: `history-${randomUUID()}`,
816
+ workspace,
817
+ clientVersion: CODEX_NATIVE_ADAPTER_VERSION,
818
+ });
819
+ try {
820
+ return await readCodexNativeHistoryThread(process.client, threadId, includeTurns);
821
+ }
822
+ finally {
823
+ await process.close();
824
+ }
825
+ });
614
826
  this.authController = new CodexNativeAuthController({
615
827
  config: this.config,
616
828
  startProcess: async (sessionGeneration) => await startCodexNativeAppServer({
@@ -724,6 +936,14 @@ export class CodexNativeAgentRuntimeAdapter {
724
936
  }
725
937
  async openSession(input) {
726
938
  const binding = validateOpenBinding(input, this.instanceId);
939
+ const activeFirstMessage = input.productContext.getActiveMessage?.();
940
+ const messageStartsLazyFirstUse = binding.state === "unbound"
941
+ && input.piboSession.kind === "branch"
942
+ && input.historyHandoff?.mode !== "import"
943
+ && Boolean(activeFirstMessage?.id);
944
+ if (messageStartsLazyFirstUse && !isAgentRuntimeBindingPersistence(input.services?.runtimeBindingPersistence)) {
945
+ throw new AgentRuntimeUnavailableError(this.instanceId, "Native Codex first use requires audited durable cross-process atomic runtime-binding CAS before thread/start.");
946
+ }
727
947
  const sessionGeneration = input.services?.resources?.sessionGeneration
728
948
  ?? input.services?.portableTools?.sessionGeneration
729
949
  ?? randomUUID();
@@ -801,16 +1021,50 @@ export class CodexNativeAgentRuntimeAdapter {
801
1021
  ? { developerInstructions: resourceDelivery.developerInstructions }
802
1022
  : {}),
803
1023
  };
1024
+ const pendingFirstUse = binding.state === "unbound" ? readPendingFirstUse(binding) : undefined;
1025
+ const deliveryReceipt = binding.state === "bound" ? readFirstUseDeliveryReceipt(binding) : undefined;
1026
+ let sessionBinding = binding;
1027
+ let pendingTerminalVerified = false;
804
1028
  let threads;
805
1029
  try {
806
- threads = binding.state === "bound"
1030
+ threads = binding.state === "bound" || pendingFirstUse
807
1031
  ? await CodexNativeThreadController.resume(process.client, binding.nativeSessionId, input.workspace, threadSelection)
808
1032
  : await CodexNativeThreadController.start(process.client, input.workspace, threadSelection);
809
1033
  }
810
1034
  catch (error) {
811
- if (error instanceof CodexNativeThreadMissingError || !resourceDelivery.hasMcpServers)
812
- throw error;
813
- throw new Error("Codex could not initialize every selected MCP server.");
1035
+ if (pendingFirstUse && error instanceof CodexNativeThreadMissingError) {
1036
+ const ownerLiveness = codexNativePendingFirstUseOwnerLiveness(pendingFirstUse);
1037
+ if (ownerLiveness === "active") {
1038
+ throw new AgentRuntimeUnavailableError(this.instanceId, "Native Codex first use is owned by another live router and is not yet restart-reconcilable.");
1039
+ }
1040
+ if (ownerLiveness === "ambiguous") {
1041
+ throw new AgentRuntimeUnavailableError(this.instanceId, "Native Codex first-use ownership is ambiguous; the pending identity was retained.");
1042
+ }
1043
+ threads = await CodexNativeThreadController.start(process.client, input.workspace, threadSelection);
1044
+ sessionBinding = clearPendingFirstUse(binding);
1045
+ }
1046
+ else {
1047
+ if (error instanceof CodexNativeThreadMissingError || !resourceDelivery.hasMcpServers)
1048
+ throw error;
1049
+ throw new Error("Codex could not initialize every selected MCP server.");
1050
+ }
1051
+ }
1052
+ if (pendingFirstUse && threads.thread.turns.length > 0) {
1053
+ try {
1054
+ assertCodexNativePendingFirstUseTurn(pendingFirstUse, threads.thread);
1055
+ pendingTerminalVerified = true;
1056
+ }
1057
+ catch (error) {
1058
+ throw new AgentRuntimeUnavailableError(this.instanceId, error instanceof Error ? error.message : "The pending native Codex first turn cannot be verified.");
1059
+ }
1060
+ }
1061
+ if (deliveryReceipt) {
1062
+ try {
1063
+ assertCodexNativeFirstUseDeliveryReceiptTurn(deliveryReceipt, threads.thread);
1064
+ }
1065
+ catch (error) {
1066
+ throw new AgentRuntimeUnavailableError(this.instanceId, error instanceof Error ? error.message : "The delivered native Codex first turn cannot be verified.");
1067
+ }
814
1068
  }
815
1069
  if (input.historyHandoff?.mode === "import") {
816
1070
  if (binding.state === "bound") {
@@ -820,13 +1074,27 @@ export class CodexNativeAgentRuntimeAdapter {
820
1074
  }
821
1075
  settings.attachThread(threads.thread.id, threads.configuration);
822
1076
  await resourceDelivery.verifyThread(process.client, threads.thread.id);
823
- return new CodexNativeThreadSession(this.instanceId, process, threads, settings, resourceDelivery, bindingForThread({
824
- piboSessionId: input.piboSession.id,
825
- runtimeInstanceId: this.instanceId,
826
- previous: binding,
827
- thread: threads.thread,
828
- settings: settings.bindingMetadata,
829
- }), this.config.experimentalUserInput, async () => await startProcessBundle(`${sessionGeneration}-credential-${randomUUID()}`), input.productContext);
1077
+ const lazyFirstMessageBranch = sessionBinding.state === "unbound"
1078
+ && input.piboSession.kind === "branch"
1079
+ && input.historyHandoff?.mode !== "import";
1080
+ const openedBinding = lazyFirstMessageBranch
1081
+ ? pendingTerminalVerified
1082
+ ? bindingForThread({
1083
+ piboSessionId: input.piboSession.id,
1084
+ runtimeInstanceId: this.instanceId,
1085
+ previous: sessionBinding,
1086
+ thread: threads.thread,
1087
+ settings: settings.bindingMetadata,
1088
+ })
1089
+ : sessionBinding
1090
+ : bindingForThread({
1091
+ piboSessionId: input.piboSession.id,
1092
+ runtimeInstanceId: this.instanceId,
1093
+ previous: sessionBinding,
1094
+ thread: threads.thread,
1095
+ settings: settings.bindingMetadata,
1096
+ });
1097
+ return new CodexNativeThreadSession(this.instanceId, process, threads, settings, resourceDelivery, openedBinding, this.config.experimentalUserInput, async () => await startProcessBundle(`${sessionGeneration}-credential-${randomUUID()}`), input.productContext, input.services?.runtimeBindingPersistence, compatibility?.testOnlyFirstUseFailpoints);
830
1098
  }
831
1099
  catch (error) {
832
1100
  settings?.dispose();
@@ -844,7 +1112,10 @@ export class CodexNativeAgentRuntimeAdapter {
844
1112
  return unavailableCodexThreadHistoryInspection(this.instanceId, input.binding, "codex_native_history_thread_id_missing", "The Codex runtime binding has no native thread id for history lookup.");
845
1113
  }
846
1114
  try {
847
- const thread = await this.withProcess(input.binding.piboSessionId, input.workspace, async (process) => await CodexNativeThreadController.read(process.client, threadId, false));
1115
+ const readHistory = codexNativeHistoryReaders.get(this);
1116
+ if (!readHistory)
1117
+ throw new Error("Codex built-in history reader is unavailable.");
1118
+ const thread = await readHistory(input.binding.piboSessionId, input.workspace, threadId, false);
848
1119
  return inspectCodexThreadHistory(this.instanceId, input.binding, thread);
849
1120
  }
850
1121
  catch (error) {
@@ -867,15 +1138,18 @@ export class CodexNativeAgentRuntimeAdapter {
867
1138
  };
868
1139
  }
869
1140
  try {
870
- const thread = await this.withProcess(input.binding.piboSessionId, input.workspace, async (process) => await CodexNativeThreadController.read(process.client, threadId, true));
871
- return pageCodexThreadHistory({
1141
+ const readHistory = codexNativeHistoryReaders.get(this);
1142
+ if (!readHistory)
1143
+ throw new Error("Codex built-in history reader is unavailable.");
1144
+ const thread = await readHistory(input.binding.piboSessionId, input.workspace, threadId, true);
1145
+ return bindCodexNativeCompleteHistoryProof(pageCodexThreadHistory({
872
1146
  runtimeInstanceId: this.instanceId,
873
1147
  binding: input.binding,
874
1148
  thread,
875
1149
  cursor: input.cursor,
876
1150
  beforeTimestamp: input.beforeTimestamp,
877
1151
  limit: input.limit,
878
- });
1152
+ }));
879
1153
  }
880
1154
  catch (error) {
881
1155
  if (error instanceof CodexNativeThreadMissingError) {
@@ -142,19 +142,28 @@ function serializeMessage(message, maxMessageBytes) {
142
142
  function timeoutError(label) {
143
143
  return new CodexAppServerClientError("timeout", `${label} timed out`);
144
144
  }
145
- async function waitWithTimeout(promise, timeoutMs, label) {
145
+ async function waitWithTimeout(promise, timeoutMs, label, signal) {
146
+ if (signal?.aborted)
147
+ throw new CodexAppServerClientError("aborted", `${label} was aborted`);
146
148
  let timer;
149
+ let onAbort;
147
150
  try {
148
151
  return await Promise.race([
149
152
  promise,
150
153
  new Promise((_resolve, reject) => {
151
154
  timer = setTimeout(() => reject(timeoutError(label)), timeoutMs);
152
155
  }),
156
+ ...(signal ? [new Promise((_resolve, reject) => {
157
+ onAbort = () => reject(new CodexAppServerClientError("aborted", `${label} was aborted`));
158
+ signal.addEventListener("abort", onAbort, { once: true });
159
+ })] : []),
153
160
  ]);
154
161
  }
155
162
  finally {
156
163
  if (timer)
157
164
  clearTimeout(timer);
165
+ if (onAbort)
166
+ signal?.removeEventListener("abort", onAbort);
158
167
  }
159
168
  }
160
169
  async function sleep(delayMs, signal) {
@@ -231,6 +240,8 @@ export class CodexAppServerClient {
231
240
  }
232
241
  static async start(options) {
233
242
  validateClientOptions(options);
243
+ if (options.signal?.aborted)
244
+ throw new CodexAppServerClientError("aborted", "Codex App Server startup was aborted");
234
245
  let previousMask;
235
246
  if (options.fileCreationMask !== undefined && globalThis.process.platform !== "win32") {
236
247
  previousMask = globalThis.process.umask(options.fileCreationMask);
@@ -255,9 +266,13 @@ export class CodexAppServerClient {
255
266
  };
256
267
  const client = new CodexAppServerClient(process, options, placeholder);
257
268
  client.attachProcessListeners();
269
+ const onAbort = () => {
270
+ void client.stopProcess(false).catch(() => { });
271
+ };
272
+ options.signal?.addEventListener("abort", onAbort, { once: true });
258
273
  const deadline = Date.now() + client.startupTimeoutMs;
259
274
  try {
260
- await waitWithTimeout(client.spawned.promise, client.startupTimeoutMs, "Codex App Server spawn");
275
+ await waitWithTimeout(client.spawned.promise, client.startupTimeoutMs, "Codex App Server spawn", options.signal);
261
276
  client.state = "initializing";
262
277
  const initializeResult = await client.requestWithRetry("initialize", {
263
278
  clientInfo: { ...options.clientInfo },
@@ -265,11 +280,12 @@ export class CodexAppServerClient {
265
280
  }, {
266
281
  timeoutMs: Math.max(1, deadline - Date.now()),
267
282
  retryOverloaded: true,
283
+ signal: options.signal,
268
284
  }, true);
269
285
  const initializeResponse = validateInitializeResponse(initializeResult);
270
286
  Object.assign(client.initializeResponse, initializeResponse);
271
287
  Object.freeze(client.initializeResponse);
272
- await waitWithTimeout(client.writeMessage({ method: "initialized" }, true), Math.max(1, deadline - Date.now()), "Codex App Server initialized notification");
288
+ await waitWithTimeout(client.writeMessage({ method: "initialized" }, true), Math.max(1, deadline - Date.now()), "Codex App Server initialized notification", options.signal);
273
289
  client.state = "ready";
274
290
  return client;
275
291
  }
@@ -279,6 +295,9 @@ export class CodexAppServerClient {
279
295
  await client.stopProcess(false).catch(() => { });
280
296
  throw normalized;
281
297
  }
298
+ finally {
299
+ options.signal?.removeEventListener("abort", onAbort);
300
+ }
282
301
  }
283
302
  get snapshot() {
284
303
  return {