@pasko70/pibo 2.5.1 → 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 (104) 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 +307 -31
  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/models.js +48 -1
  10. package/dist/agent-runtimes/codex-native/process.js +15 -1
  11. package/dist/agent-runtimes/codex-native/thread.js +75 -31
  12. package/dist/agent-runtimes/codex-native/turn.js +30 -0
  13. package/dist/agent-runtimes/history-proof.js +8 -0
  14. package/dist/agent-runtimes/omp/adapter.js +61 -14
  15. package/dist/agent-runtimes/omp/history.js +189 -34
  16. package/dist/agent-runtimes/omp/thread.js +82 -36
  17. package/dist/agent-runtimes/pi/adapter.js +30 -3
  18. package/dist/agent-runtimes/pi/history.js +66 -5
  19. package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
  20. package/dist/agent-runtimes/pi/routed-session.js +344 -37
  21. package/dist/apps/chat/agent-store.js +3 -0
  22. package/dist/apps/chat/chat-api-routes.js +1 -1
  23. package/dist/apps/chat/chat-files.js +130 -9
  24. package/dist/apps/chat/chat-request-normalizers.js +6 -0
  25. package/dist/apps/chat/chat-settings-routes.js +27 -2
  26. package/dist/apps/chat/chat-speech.js +100 -0
  27. package/dist/apps/chat/data/chat-data-mappers.js +25 -5
  28. package/dist/apps/chat/data/timeline-query-service.js +89 -1
  29. package/dist/apps/chat/trace-v2.js +210 -8
  30. package/dist/apps/chat/trace.js +3 -0
  31. package/dist/apps/chat/web-app.js +108 -3
  32. package/dist/apps/chat-ui/assets/{dist-GS8xUdfg.js → dist-BBDMeU5-.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-BpVSf6ZV.js → dist-BDIATCQt.js} +1 -1
  34. package/dist/apps/chat-ui/assets/{dist-CRv7fzi3.js → dist-JnW4v8UE.js} +1 -1
  35. package/dist/apps/chat-ui/assets/{dist-CbBXbGVi.js → dist-lRiLP9rr.js} +1 -1
  36. package/dist/apps/chat-ui/assets/{dist-HC0xQ92C.js → dist-mqCviI-c.js} +1 -1
  37. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
  38. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
  39. package/dist/apps/chat-ui/index.html +2 -2
  40. package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
  41. package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
  42. package/dist/apps/chat-vscode-web/index.html +2 -2
  43. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  44. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
  45. package/dist/cli.js +49 -5
  46. package/dist/config/config.js +59 -8
  47. package/dist/core/preview-server-settings.js +20 -0
  48. package/dist/core/profiles.js +1 -0
  49. package/dist/core/session-router.js +476 -44
  50. package/dist/core/user-settings.js +10 -0
  51. package/dist/data/payload-store.js +57 -1
  52. package/dist/data/schema.js +18 -1
  53. package/dist/debug/trace.js +5 -0
  54. package/dist/gateway/client.js +368 -65
  55. package/dist/gateway/protocol.js +2 -0
  56. package/dist/gateway/server.js +8 -0
  57. package/dist/gateway/web.js +2 -0
  58. package/dist/index.js +1 -1
  59. package/dist/loops/store.js +6 -4
  60. package/dist/mcp/config.js +89 -17
  61. package/dist/mcp/daemon-client.js +473 -153
  62. package/dist/mcp/daemon.js +401 -37
  63. package/dist/plugins/builtin.js +2 -2
  64. package/dist/plugins/codex-native.js +2 -0
  65. package/dist/plugins/registry.js +260 -1
  66. package/dist/previews/cli.js +345 -0
  67. package/dist/previews/config.js +61 -0
  68. package/dist/previews/manager.js +644 -0
  69. package/dist/previews/network.js +198 -0
  70. package/dist/previews/plugin.js +11 -0
  71. package/dist/previews/process-supervisor.js +38 -0
  72. package/dist/previews/proxy.js +387 -0
  73. package/dist/previews/store.js +750 -0
  74. package/dist/previews/types.js +1 -0
  75. package/dist/previews/web-app.js +446 -0
  76. package/dist/runs/registry.js +22 -0
  77. package/dist/runs/tools.js +46 -7
  78. package/dist/session-ui/terminalRows.js +187 -13
  79. package/dist/sessions/pibo-data-store.js +37 -0
  80. package/dist/sessions/runtime-binding-persistence.js +34 -0
  81. package/dist/sessions/runtime-binding.js +6 -0
  82. package/dist/sessions/sqlite-store.js +37 -0
  83. package/dist/shared/trace-engine.js +12 -64
  84. package/dist/shared/trace-event-projection.js +16 -0
  85. package/dist/shared/trace-history.js +684 -184
  86. package/dist/shared/trace-limits.js +7 -0
  87. package/dist/shared/trace-live-patch.js +61 -0
  88. package/dist/shared/trace-tool-identity.js +43 -0
  89. package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
  90. package/dist/speech/openai-codex.js +407 -0
  91. package/dist/speech/types.js +8 -0
  92. package/dist/subagents/context.js +5 -3
  93. package/dist/subagents/observations.js +94 -6
  94. package/dist/subagents/tool.js +103 -20
  95. package/dist/tools/codex-image-artifacts.js +24 -0
  96. package/dist/tools/codex-image-generation.js +4 -22
  97. package/dist/web/channel.js +88 -4
  98. package/dist/web/http.js +2 -1
  99. package/npm-shrinkwrap.json +2 -2
  100. package/package.json +1 -1
  101. package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
  102. package/dist/apps/chat-ui/assets/index-DT5jCQBP.js +0 -228
  103. package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
  104. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.1.vsix +0 -0
@@ -0,0 +1,294 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { PENDING_NATIVE_SESSION_METADATA_KEY, } from "../../sessions/runtime-binding.js";
4
+ export const CODEX_FIRST_USE_METADATA_KEY = "codexNativeFirstUse";
5
+ export const CODEX_FIRST_USE_METADATA_VERSION = 3;
6
+ const LEGACY_BYTE_EXACT_METADATA_VERSIONS = new Set([1, 2]);
7
+ const MAX_NATIVE_ID_LENGTH = 512;
8
+ const MAX_MESSAGE_ID_LENGTH = 512;
9
+ const MAX_PROCESS_START_ID_LENGTH = 64;
10
+ const MAX_PID = 0x7fff_ffff;
11
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
12
+ const UUID_V4_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
13
+ const PROCESS_START_ID_PATTERN = /^([1-9][0-9]{0,9}):([1-9][0-9]{0,30})$/;
14
+ const TERMINAL_TURN_STATUSES = new Set(["completed", "interrupted", "failed"]);
15
+ const PROCESS_INSTANCE_ID = randomUUID();
16
+ const activeAttemptIds = new Set();
17
+ function boundedNonEmptyString(value, maximumLength) {
18
+ return typeof value === "string"
19
+ && value.length > 0
20
+ && value.length <= maximumLength
21
+ && !/[\u0000-\u001f\u007f]/.test(value);
22
+ }
23
+ function validPid(value) {
24
+ return Number.isSafeInteger(value) && Number(value) > 0 && Number(value) <= MAX_PID;
25
+ }
26
+ function errorCode(error) {
27
+ return error && typeof error === "object" && "code" in error && typeof error.code === "string"
28
+ ? error.code
29
+ : undefined;
30
+ }
31
+ function probeLinuxProcess(pid, readProcStat) {
32
+ let stat;
33
+ try {
34
+ stat = readProcStat(pid);
35
+ }
36
+ catch (error) {
37
+ return ["ENOENT", "ESRCH"].includes(errorCode(error) ?? "")
38
+ ? { liveness: "dead" }
39
+ : { liveness: "ambiguous" };
40
+ }
41
+ const close = stat.lastIndexOf(")");
42
+ if (close < 2 || close + 2 >= stat.length)
43
+ return { liveness: "ambiguous" };
44
+ const fields = stat.slice(close + 2).trim().split(/\s+/);
45
+ if (fields.length <= 19)
46
+ return { liveness: "ambiguous" };
47
+ if (fields[0] === "Z")
48
+ return { liveness: "dead" };
49
+ const startTicks = fields[19];
50
+ if (!startTicks || !/^[1-9][0-9]{0,30}$/.test(startTicks))
51
+ return { liveness: "ambiguous" };
52
+ return { liveness: "active", startId: `${pid}:${startTicks}` };
53
+ }
54
+ function probePortableProcess(pid, probePid) {
55
+ try {
56
+ probePid(pid);
57
+ return "active";
58
+ }
59
+ catch (error) {
60
+ return errorCode(error) === "ESRCH" ? "dead" : "ambiguous";
61
+ }
62
+ }
63
+ function defaultReadProcStat(pid) {
64
+ return readFileSync(`/proc/${pid}/stat`, "utf8");
65
+ }
66
+ function defaultProbePid(pid) {
67
+ process.kill(pid, 0);
68
+ }
69
+ function hashUtf8(value) {
70
+ return createHash("sha256").update(value, "utf8").digest("hex");
71
+ }
72
+ /**
73
+ * Canonical first-use prompts use LF line endings and Unicode NFC. Pibo hashes
74
+ * this representation before native execution and when proving native history.
75
+ */
76
+ export function canonicalizeCodexNativeFirstUsePrompt(prompt) {
77
+ return prompt.replace(/\r\n?/g, "\n").normalize("NFC");
78
+ }
79
+ export function hashCanonicalCodexNativeFirstUsePrompt(prompt) {
80
+ return hashUtf8(canonicalizeCodexNativeFirstUsePrompt(prompt));
81
+ }
82
+ function hashPersistedPromptEvidence(identity, prompt) {
83
+ return LEGACY_BYTE_EXACT_METADATA_VERSIONS.has(identity.version)
84
+ ? hashUtf8(prompt)
85
+ : hashCanonicalCodexNativeFirstUsePrompt(prompt);
86
+ }
87
+ export function codexNativeFirstUseDeliveryReceipt(pending) {
88
+ return {
89
+ version: pending.version,
90
+ state: "delivered",
91
+ messageId: pending.messageId,
92
+ promptHash: pending.promptHash,
93
+ };
94
+ }
95
+ export function readCodexNativePendingFirstUse(binding) {
96
+ const value = binding.metadata?.[CODEX_FIRST_USE_METADATA_KEY];
97
+ if (value === undefined)
98
+ return undefined;
99
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
100
+ throw new Error("The pending native Codex first-use metadata is invalid.");
101
+ }
102
+ const record = value;
103
+ const allowedKeys = new Set([
104
+ "version",
105
+ "state",
106
+ "threadId",
107
+ "messageId",
108
+ "promptHash",
109
+ "attemptId",
110
+ "ownerPid",
111
+ "ownerProcessStartId",
112
+ "ownerProcessInstanceId",
113
+ ]);
114
+ const ownerProcessStartId = record.ownerProcessStartId;
115
+ const startMatch = typeof ownerProcessStartId === "string"
116
+ ? PROCESS_START_ID_PATTERN.exec(ownerProcessStartId)
117
+ : undefined;
118
+ if (Object.keys(record).some((key) => !allowedKeys.has(key))
119
+ || (record.version !== 1 && record.version !== 2 && record.version !== CODEX_FIRST_USE_METADATA_VERSION)
120
+ || record.state !== "pending"
121
+ || !boundedNonEmptyString(record.threadId, MAX_NATIVE_ID_LENGTH)
122
+ || !boundedNonEmptyString(record.messageId, MAX_MESSAGE_ID_LENGTH)
123
+ || typeof record.promptHash !== "string"
124
+ || !SHA256_PATTERN.test(record.promptHash)
125
+ || typeof record.attemptId !== "string"
126
+ || !UUID_V4_PATTERN.test(record.attemptId)
127
+ || !validPid(record.ownerPid)
128
+ || typeof record.ownerProcessInstanceId !== "string"
129
+ || !UUID_V4_PATTERN.test(record.ownerProcessInstanceId)
130
+ || (ownerProcessStartId !== undefined
131
+ && (typeof ownerProcessStartId !== "string"
132
+ || ownerProcessStartId.length > MAX_PROCESS_START_ID_LENGTH
133
+ || !startMatch
134
+ || Number(startMatch[1]) !== record.ownerPid))) {
135
+ throw new Error("The pending native Codex first-use metadata is invalid.");
136
+ }
137
+ return record;
138
+ }
139
+ export function readCodexNativeFirstUseDeliveryReceipt(binding) {
140
+ const value = binding.metadata?.[CODEX_FIRST_USE_METADATA_KEY];
141
+ if (value === undefined)
142
+ return undefined;
143
+ if (binding.state !== "bound"
144
+ || binding.metadata?.[PENDING_NATIVE_SESSION_METADATA_KEY] !== undefined
145
+ || !value
146
+ || typeof value !== "object"
147
+ || Array.isArray(value)) {
148
+ throw new Error("The delivered native Codex first-use receipt is invalid.");
149
+ }
150
+ const record = value;
151
+ const allowedKeys = new Set(["version", "state", "messageId", "promptHash"]);
152
+ if (Object.keys(record).some((key) => !allowedKeys.has(key))
153
+ || (record.version !== 1 && record.version !== 2 && record.version !== CODEX_FIRST_USE_METADATA_VERSION)
154
+ || record.state !== "delivered"
155
+ || !boundedNonEmptyString(record.messageId, MAX_MESSAGE_ID_LENGTH)
156
+ || typeof record.promptHash !== "string"
157
+ || !SHA256_PATTERN.test(record.promptHash)) {
158
+ throw new Error("The delivered native Codex first-use receipt is invalid.");
159
+ }
160
+ return record;
161
+ }
162
+ export function beginCodexNativeFirstUseAttempt() {
163
+ const attemptId = randomUUID();
164
+ let ownerProcessStartId;
165
+ if (process.platform === "linux") {
166
+ const probe = probeLinuxProcess(process.pid, defaultReadProcStat);
167
+ if (probe.liveness !== "active" || !probe.startId) {
168
+ throw new Error("Native Codex first use cannot establish the current process identity safely.");
169
+ }
170
+ ownerProcessStartId = probe.startId;
171
+ }
172
+ activeAttemptIds.add(attemptId);
173
+ return {
174
+ attemptId,
175
+ ownerPid: process.pid,
176
+ ...(ownerProcessStartId ? { ownerProcessStartId } : {}),
177
+ ownerProcessInstanceId: PROCESS_INSTANCE_ID,
178
+ };
179
+ }
180
+ export function endCodexNativeFirstUseAttempt(attemptId) {
181
+ activeAttemptIds.delete(attemptId);
182
+ }
183
+ export function codexNativePendingFirstUseOwnerLiveness(pending, dependencies = {}) {
184
+ const platform = dependencies.platform ?? process.platform;
185
+ const currentPid = dependencies.currentPid ?? process.pid;
186
+ const currentProcessInstanceId = dependencies.currentProcessInstanceId ?? PROCESS_INSTANCE_ID;
187
+ let processLiveness;
188
+ let observedStartId;
189
+ if (platform === "linux") {
190
+ const probe = probeLinuxProcess(pending.ownerPid, dependencies.readProcStat ?? defaultReadProcStat);
191
+ processLiveness = probe.liveness;
192
+ observedStartId = probe.startId;
193
+ if (processLiveness !== "active")
194
+ return processLiveness;
195
+ if (!pending.ownerProcessStartId || !observedStartId)
196
+ return "ambiguous";
197
+ if (pending.ownerProcessStartId !== observedStartId)
198
+ return "dead";
199
+ }
200
+ else {
201
+ processLiveness = probePortableProcess(pending.ownerPid, dependencies.probePid ?? defaultProbePid);
202
+ if (processLiveness !== "active")
203
+ return processLiveness;
204
+ }
205
+ if (pending.ownerPid !== currentPid)
206
+ return "active";
207
+ if (pending.ownerProcessInstanceId !== currentProcessInstanceId)
208
+ return "ambiguous";
209
+ return activeAttemptIds.has(pending.attemptId) ? "active" : "dead";
210
+ }
211
+ function exactTextUserItem(item) {
212
+ if (item.type !== "userMessage" || !Array.isArray(item.content) || item.content.length !== 1)
213
+ return undefined;
214
+ const part = item.content[0];
215
+ if (!part || typeof part !== "object" || Array.isArray(part))
216
+ return undefined;
217
+ const record = part;
218
+ if (record.type !== "text" || typeof record.text !== "string")
219
+ return undefined;
220
+ if (item.clientId !== undefined && item.clientId !== null && typeof item.clientId !== "string")
221
+ return undefined;
222
+ return { ...(item.clientId !== undefined ? { clientId: item.clientId } : {}), text: record.text };
223
+ }
224
+ function assertCodexNativeFirstUseTurnEvidence(identity, turn, state) {
225
+ if (!TERMINAL_TURN_STATUSES.has(turn.status)) {
226
+ throw new Error(`The ${state} native Codex first turn is not terminal and cannot be reconciled safely.`);
227
+ }
228
+ const userItems = turn.items.filter((item) => item.type === "userMessage");
229
+ const evidence = userItems.map(exactTextUserItem);
230
+ if (evidence.length === 0 || evidence.some((item) => !item)) {
231
+ throw new Error(`The ${state} native Codex first turn has ambiguous user input evidence.`);
232
+ }
233
+ const exactEvidence = evidence;
234
+ const evidenceWithClientIds = exactEvidence.filter((item) => item.clientId !== undefined && item.clientId !== null);
235
+ let firstRequestEvidence;
236
+ if (evidenceWithClientIds.length > 0) {
237
+ if (evidenceWithClientIds.length !== exactEvidence.length) {
238
+ throw new Error(`The ${state} native Codex first turn has ambiguous user input evidence.`);
239
+ }
240
+ const matches = evidenceWithClientIds.filter((item) => item.clientId === identity.messageId);
241
+ if (matches.length === 0) {
242
+ throw new Error(`The ${state} native Codex first turn does not match the persisted message id.`);
243
+ }
244
+ if (matches.length > 1) {
245
+ throw new Error(`The ${state} native Codex first turn has ambiguous user input evidence.`);
246
+ }
247
+ firstRequestEvidence = matches[0];
248
+ }
249
+ else {
250
+ if (exactEvidence.length !== 1) {
251
+ throw new Error(`The ${state} native Codex first turn has ambiguous user input evidence.`);
252
+ }
253
+ firstRequestEvidence = exactEvidence[0];
254
+ }
255
+ if (hashPersistedPromptEvidence(identity, firstRequestEvidence.text) !== identity.promptHash) {
256
+ throw new Error(`The ${state} native Codex first turn does not match the persisted prompt hash.`);
257
+ }
258
+ if (firstRequestEvidence.clientId !== undefined
259
+ && firstRequestEvidence.clientId !== null
260
+ && !boundedNonEmptyString(firstRequestEvidence.clientId, MAX_MESSAGE_ID_LENGTH)) {
261
+ throw new Error(`The ${state} native Codex first turn has invalid message identity evidence.`);
262
+ }
263
+ }
264
+ export function assertCodexNativePendingFirstUseTurn(pending, thread) {
265
+ if (thread.id !== pending.threadId || thread.turns.length !== 1) {
266
+ throw new Error("The pending native Codex first turn is missing, multiple, or belongs to another thread.");
267
+ }
268
+ assertCodexNativeFirstUseTurnEvidence(pending, thread.turns[0], "pending");
269
+ }
270
+ export function assertCodexNativeFirstUseDeliveryReceiptTurn(receipt, thread) {
271
+ const firstTurn = thread.turns[0];
272
+ if (!firstTurn)
273
+ throw new Error("The delivered native Codex first turn is missing.");
274
+ assertCodexNativeFirstUseTurnEvidence(receipt, firstTurn, "delivered");
275
+ return firstTurn;
276
+ }
277
+ export function isExactCodexNativeFirstUseDeliveryReplay(receipt, messageId, prompt) {
278
+ if (messageId !== receipt.messageId)
279
+ return false;
280
+ if (hashPersistedPromptEvidence(receipt, prompt) !== receipt.promptHash) {
281
+ throw new Error("Native Codex first-use replay reuses the delivered message id with a different prompt.");
282
+ }
283
+ return true;
284
+ }
285
+ export function assertCodexNativePendingFirstUseRequest(pending, messageId, prompt) {
286
+ if (messageId !== pending.messageId || hashPersistedPromptEvidence(pending, prompt) !== pending.promptHash) {
287
+ throw new Error("Native Codex first-use retry does not match the persisted message id and prompt.");
288
+ }
289
+ }
290
+ export function assertCodexNativeFirstUseMessageId(messageId) {
291
+ if (!boundedNonEmptyString(messageId, MAX_MESSAGE_ID_LENGTH)) {
292
+ throw new Error("Native Codex first-use message id is invalid.");
293
+ }
294
+ }
@@ -1,5 +1,7 @@
1
+ import { historyReconciliationDigest } from "../../agent-runtime/history.js";
1
2
  import { redactCodexNativeSensitiveText } from "./redaction.js";
2
3
  import { CODEX_NATIVE_ADAPTER_ID } from "./thread.js";
4
+ import { TRACE_RECONCILIATION_ENTRY_CAP } from "../../shared/trace-limits.js";
3
5
  const CODEX_HISTORY_CURSOR_PREFIX = "codex-history:";
4
6
  const DEFAULT_HISTORY_LIMIT = 100;
5
7
  const MAX_HISTORY_LIMIT = 200;
@@ -67,7 +69,7 @@ function reasoningParts(item) {
67
69
  .map(redactCodexNativeSensitiveText);
68
70
  return values.length > 0 ? [{ type: "reasoning", text: values.join("\n") }] : [];
69
71
  }
70
- function itemEntry(thread, turn, item, sequence) {
72
+ function itemEntry(thread, turn, item, sequence, historyPosition, historyScopeId) {
71
73
  const createdAt = isoFromSeconds(turn.startedAt ?? turn.completedAt, thread.updatedAt);
72
74
  const base = {
73
75
  id: `codex:${thread.id}:${turn.id}:${item.id}`,
@@ -75,6 +77,8 @@ function itemEntry(thread, turn, item, sequence) {
75
77
  source: "native",
76
78
  createdAt,
77
79
  sequence,
80
+ historyPosition,
81
+ historyScopeId,
78
82
  turnId: turn.id,
79
83
  nativeTurnId: turn.id,
80
84
  nativeEntryId: item.id,
@@ -159,7 +163,7 @@ function itemEntry(thread, turn, item, sequence) {
159
163
  }
160
164
  return undefined;
161
165
  }
162
- export function codexThreadHistoryEntries(thread) {
166
+ export function codexThreadHistoryEntries(thread, historyScopeId = `codex:${thread.id}`) {
163
167
  const entries = [];
164
168
  let sequence = 0;
165
169
  if (thread.name) {
@@ -169,13 +173,15 @@ export function codexThreadHistoryEntries(thread) {
169
173
  source: "native",
170
174
  createdAt: isoFromSeconds(thread.createdAt, thread.createdAt),
171
175
  sequence: sequence++,
176
+ historyPosition: `codex:${thread.id}:session-info`,
177
+ historyScopeId,
172
178
  nativeEntryId: thread.id,
173
179
  name: redactCodexNativeSensitiveText(thread.name),
174
180
  });
175
181
  }
176
- for (const turn of thread.turns) {
177
- for (const item of turn.items) {
178
- const entry = itemEntry(thread, turn, item, sequence);
182
+ for (const [turnIndex, turn] of thread.turns.entries()) {
183
+ for (const [itemIndex, item] of turn.items.entries()) {
184
+ const entry = itemEntry(thread, turn, item, sequence, `codex:${thread.id}:turn:${turnIndex}:item:${itemIndex}:${item.type}`, historyScopeId);
179
185
  if (!entry)
180
186
  continue;
181
187
  entries.push(entry);
@@ -187,7 +193,9 @@ export function codexThreadHistoryEntries(thread) {
187
193
  type: "message",
188
194
  source: "native",
189
195
  createdAt: isoFromSeconds(turn.completedAt ?? turn.startedAt, thread.updatedAt),
190
- sequence: sequence++,
196
+ sequence,
197
+ historyPosition: `codex:${thread.id}:turn:${turnIndex}:error`,
198
+ historyScopeId,
191
199
  turnId: turn.id,
192
200
  nativeTurnId: turn.id,
193
201
  nativeEntryId: `${turn.id}:error`,
@@ -196,6 +204,7 @@ export function codexThreadHistoryEntries(thread) {
196
204
  status: "error",
197
205
  error: "Codex turn failed.",
198
206
  });
207
+ sequence += 1;
199
208
  }
200
209
  }
201
210
  return entries;
@@ -256,7 +265,8 @@ function selectedLimit(value) {
256
265
  return Math.min(value, MAX_HISTORY_LIMIT);
257
266
  }
258
267
  export function pageCodexThreadHistory(input) {
259
- const allEntries = codexThreadHistoryEntries(input.thread);
268
+ const historyScopeId = `codex:${input.runtimeInstanceId}:${input.thread.id}`;
269
+ const allEntries = codexThreadHistoryEntries(input.thread, historyScopeId);
260
270
  const beforeTimestampMs = input.beforeTimestamp === undefined ? undefined : Date.parse(input.beforeTimestamp);
261
271
  if (beforeTimestampMs !== undefined && Number.isNaN(beforeTimestampMs)) {
262
272
  throw new Error("Codex native history beforeTimestamp is invalid.");
@@ -270,11 +280,20 @@ export function pageCodexThreadHistory(input) {
270
280
  const start = Math.max(0, end - limit);
271
281
  const entries = filtered.slice(start, end);
272
282
  const hasMore = start > 0;
283
+ const proofComplete = allEntries.length <= TRACE_RECONCILIATION_ENTRY_CAP;
273
284
  return {
274
285
  runtimeInstanceId: input.runtimeInstanceId,
275
286
  adapterId: CODEX_NATIVE_ADAPTER_ID,
276
287
  source: "native",
277
288
  entries,
289
+ reconciliationProof: proofComplete
290
+ ? {
291
+ complete: true,
292
+ scopeId: historyScopeId,
293
+ fullScope: { entryCount: allEntries.length, digest: historyReconciliationDigest(allEntries) },
294
+ entries: allEntries,
295
+ }
296
+ : { complete: false, scopeId: historyScopeId, entries },
278
297
  orderOffset: start,
279
298
  ...(hasMore ? { nextCursor: encodeCursor({ v: 1, threadId: input.thread.id, beforeIndex: start }) } : {}),
280
299
  hasMore,
@@ -19,6 +19,13 @@ export const CODEX_NATIVE_MODEL_OPTIONS_SCHEMA = {
19
19
  enum: ["auto", "concise", "detailed", "none"],
20
20
  description: "Optional native Codex reasoning-summary mode.",
21
21
  },
22
+ permissionMode: {
23
+ type: "string",
24
+ title: "Permission mode",
25
+ enum: ["approval", "yolo", "plan"],
26
+ default: "approval",
27
+ description: "Codex permissions: Approval asks before privileged work, YOLO disables approvals with unrestricted host access, and Plan uses a read-only planning turn.",
28
+ },
22
29
  },
23
30
  };
24
31
  const MAX_MODEL_PAGES = 20;
@@ -34,6 +41,7 @@ const MAX_DESCRIPTION_LENGTH = 4_096;
34
41
  const MAX_CURSOR_LENGTH = 1_024;
35
42
  const PERSONALITIES = new Set(["none", "friendly", "pragmatic"]);
36
43
  const REASONING_SUMMARIES = new Set(["auto", "concise", "detailed", "none"]);
44
+ const PERMISSION_MODES = new Set(["approval", "yolo", "plan"]);
37
45
  const BINDING_MODEL_KEY = "codexNativeModelId";
38
46
  const BINDING_REASONING_KEY = "codexNativeReasoningEffort";
39
47
  const BINDING_SERVICE_TIER_KEY = "codexNativeServiceTier";
@@ -271,7 +279,7 @@ export function readCodexNativePersistedSettings(metadata) {
271
279
  };
272
280
  }
273
281
  export function parseCodexNativeProfileOptions(value) {
274
- const allowed = new Set(["serviceTier", "personality", "reasoningSummary"]);
282
+ const allowed = new Set(["serviceTier", "personality", "reasoningSummary", "permissionMode"]);
275
283
  const unknown = Object.keys(value).filter((key) => !allowed.has(key));
276
284
  if (unknown.length > 0)
277
285
  throw new Error(`Unsupported native Codex runtime option${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}.`);
@@ -297,8 +305,43 @@ export function parseCodexNativeProfileOptions(value) {
297
305
  }
298
306
  options.reasoningSummary = value.reasoningSummary;
299
307
  }
308
+ if (Object.hasOwn(value, "permissionMode")) {
309
+ if (typeof value.permissionMode !== "string" || !PERMISSION_MODES.has(value.permissionMode)) {
310
+ throw new Error("Native Codex runtime option permissionMode must be one of: approval, yolo, plan.");
311
+ }
312
+ options.permissionMode = value.permissionMode;
313
+ }
300
314
  return options;
301
315
  }
316
+ export function codexNativePermissionMode(options) {
317
+ return options.permissionMode ?? "approval";
318
+ }
319
+ export function codexNativeThreadPermissionSelection(mode) {
320
+ if (mode === "yolo")
321
+ return { approvalPolicy: "never", sandbox: "danger-full-access" };
322
+ if (mode === "plan")
323
+ return { approvalPolicy: "on-request", sandbox: "read-only" };
324
+ return { approvalPolicy: "on-request", sandbox: "workspace-write" };
325
+ }
326
+ export function codexNativeTurnPermissionOptions(mode, model, effort) {
327
+ if (mode === "yolo") {
328
+ return { approvalPolicy: "never", sandboxPolicy: { type: "dangerFullAccess" } };
329
+ }
330
+ if (mode === "plan") {
331
+ return {
332
+ approvalPolicy: "on-request",
333
+ sandboxPolicy: { type: "readOnly", networkAccess: false },
334
+ collaborationMode: {
335
+ mode: "plan",
336
+ settings: { model, reasoning_effort: effort, developer_instructions: null },
337
+ },
338
+ };
339
+ }
340
+ return {
341
+ approvalPolicy: "on-request",
342
+ sandboxPolicy: { type: "workspaceWrite", networkAccess: false },
343
+ };
344
+ }
302
345
  function findModel(catalog, value) {
303
346
  return catalog.models.find((model) => model.id === value || model.model === value);
304
347
  }
@@ -374,6 +417,7 @@ export class CodexNativeSessionSettingsController {
374
417
  serviceTier;
375
418
  personality;
376
419
  reasoningSummary;
420
+ permissionMode;
377
421
  activeThreadId;
378
422
  contextUsage;
379
423
  pendingContextUsage = new Map();
@@ -394,6 +438,7 @@ export class CodexNativeSessionSettingsController {
394
438
  this.serviceTier = validateServiceTier(this.currentModel, selectedTier);
395
439
  this.personality = initial.profileOptions.personality;
396
440
  this.reasoningSummary = initial.profileOptions.reasoningSummary;
441
+ this.permissionMode = codexNativePermissionMode(initial.profileOptions);
397
442
  this.bindClient(client);
398
443
  }
399
444
  bindClient(client) {
@@ -459,6 +504,7 @@ export class CodexNativeSessionSettingsController {
459
504
  model: this.currentModel.id,
460
505
  serviceTier: this.serviceTier,
461
506
  ...(this.personality !== undefined ? { personality: this.personality } : {}),
507
+ ...codexNativeThreadPermissionSelection(this.permissionMode),
462
508
  };
463
509
  }
464
510
  get turnOptions() {
@@ -468,6 +514,7 @@ export class CodexNativeSessionSettingsController {
468
514
  serviceTier: this.serviceTier,
469
515
  ...(this.reasoningSummary !== undefined ? { summary: this.reasoningSummary } : {}),
470
516
  ...(this.personality !== undefined ? { personality: this.personality } : {}),
517
+ ...codexNativeTurnPermissionOptions(this.permissionMode, this.currentModel.id, this.reasoningLevel),
471
518
  };
472
519
  }
473
520
  setModel(model) {
@@ -419,10 +419,16 @@ export async function startCodexNativeAppServer(input) {
419
419
  if (!input.clientVersion.trim()) {
420
420
  throw new CodexNativeProcessError("start_failed", "Codex App Server client version is required.");
421
421
  }
422
+ if (input.realtimeSidebandBaseUrl !== undefined && !input.realtimeSidebandBaseUrl.trim()) {
423
+ throw new CodexNativeProcessError("start_failed", "Codex realtime sideband base URL must not be empty.");
424
+ }
425
+ if (input.realtimeWebrtcCallBaseUrl !== undefined && !input.realtimeWebrtcCallBaseUrl.trim()) {
426
+ throw new CodexNativeProcessError("start_failed", "Codex realtime WebRTC call base URL must not be empty.");
427
+ }
422
428
  const paths = await prepareCodexNativeSessionPaths(input);
423
429
  let client;
424
430
  try {
425
- const capabilities = { experimentalApi: false };
431
+ const capabilities = { experimentalApi: input.experimentalApi === true };
426
432
  const invocation = codexExecutableInvocation(input.config.executable, [
427
433
  "app-server",
428
434
  "--stdio",
@@ -431,6 +437,13 @@ export async function startCodexNativeAppServer(input) {
431
437
  `tools.experimental_request_user_input.enabled=${input.config.experimentalUserInput}`,
432
438
  "-c",
433
439
  `features.default_mode_request_user_input=${input.config.experimentalUserInput}`,
440
+ ...(input.realtimeConversation ? ["-c", "features.realtime_conversation=true"] : []),
441
+ ...(input.realtimeSidebandBaseUrl
442
+ ? ["-c", `experimental_realtime_ws_base_url=${JSON.stringify(input.realtimeSidebandBaseUrl)}`]
443
+ : []),
444
+ ...(input.realtimeWebrtcCallBaseUrl
445
+ ? ["-c", `experimental_realtime_webrtc_call_base_url=${JSON.stringify(input.realtimeWebrtcCallBaseUrl)}`]
446
+ : []),
434
447
  ]);
435
448
  client = await CodexAppServerClient.start({
436
449
  command: invocation.command,
@@ -450,6 +463,7 @@ export async function startCodexNativeAppServer(input) {
450
463
  shutdownTimeoutMs: input.config.shutdownTimeoutMs,
451
464
  killTimeoutMs: input.config.killTimeoutMs,
452
465
  onDiagnostic: input.onDiagnostic,
466
+ signal: input.signal,
453
467
  });
454
468
  const [reportedHome, expectedHome] = await Promise.all([
455
469
  realpath(client.initializeResponse.codexHome).catch(() => resolve(client.initializeResponse.codexHome)),