@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.
- package/context/pibo-native-tooling.md +1 -0
- package/dist/agent-runtime/contract.js +2 -1
- package/dist/agent-runtime/history.js +53 -1
- package/dist/agent-runtime/routed-session.js +100 -26
- package/dist/agent-runtimes/codex-native/adapter.js +307 -31
- package/dist/agent-runtimes/codex-native/client.js +22 -3
- package/dist/agent-runtimes/codex-native/first-use.js +294 -0
- package/dist/agent-runtimes/codex-native/history.js +26 -7
- package/dist/agent-runtimes/codex-native/models.js +48 -1
- package/dist/agent-runtimes/codex-native/process.js +15 -1
- package/dist/agent-runtimes/codex-native/thread.js +75 -31
- package/dist/agent-runtimes/codex-native/turn.js +30 -0
- package/dist/agent-runtimes/history-proof.js +8 -0
- package/dist/agent-runtimes/omp/adapter.js +61 -14
- package/dist/agent-runtimes/omp/history.js +189 -34
- package/dist/agent-runtimes/omp/thread.js +82 -36
- package/dist/agent-runtimes/pi/adapter.js +30 -3
- package/dist/agent-runtimes/pi/history.js +66 -5
- package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
- package/dist/agent-runtimes/pi/routed-session.js +344 -37
- package/dist/apps/chat/agent-store.js +3 -0
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-files.js +130 -9
- package/dist/apps/chat/chat-request-normalizers.js +6 -0
- package/dist/apps/chat/chat-settings-routes.js +27 -2
- package/dist/apps/chat/chat-speech.js +100 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -5
- package/dist/apps/chat/data/timeline-query-service.js +89 -1
- package/dist/apps/chat/trace-v2.js +210 -8
- package/dist/apps/chat/trace.js +3 -0
- package/dist/apps/chat/web-app.js +108 -3
- package/dist/apps/chat-ui/assets/{dist-GS8xUdfg.js → dist-BBDMeU5-.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BpVSf6ZV.js → dist-BDIATCQt.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CRv7fzi3.js → dist-JnW4v8UE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CbBXbGVi.js → dist-lRiLP9rr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-HC0xQ92C.js → dist-mqCviI-c.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
- package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
- package/dist/apps/chat-vscode-web/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
- package/dist/cli.js +49 -5
- package/dist/config/config.js +59 -8
- package/dist/core/preview-server-settings.js +20 -0
- package/dist/core/profiles.js +1 -0
- package/dist/core/session-router.js +476 -44
- package/dist/core/user-settings.js +10 -0
- package/dist/data/payload-store.js +57 -1
- package/dist/data/schema.js +18 -1
- package/dist/debug/trace.js +5 -0
- package/dist/gateway/client.js +368 -65
- package/dist/gateway/protocol.js +2 -0
- package/dist/gateway/server.js +8 -0
- package/dist/gateway/web.js +2 -0
- package/dist/index.js +1 -1
- package/dist/loops/store.js +6 -4
- package/dist/mcp/config.js +89 -17
- package/dist/mcp/daemon-client.js +473 -153
- package/dist/mcp/daemon.js +401 -37
- package/dist/plugins/builtin.js +2 -2
- package/dist/plugins/codex-native.js +2 -0
- package/dist/plugins/registry.js +260 -1
- package/dist/previews/cli.js +345 -0
- package/dist/previews/config.js +61 -0
- package/dist/previews/manager.js +644 -0
- package/dist/previews/network.js +198 -0
- package/dist/previews/plugin.js +11 -0
- package/dist/previews/process-supervisor.js +38 -0
- package/dist/previews/proxy.js +387 -0
- package/dist/previews/store.js +750 -0
- package/dist/previews/types.js +1 -0
- package/dist/previews/web-app.js +446 -0
- package/dist/runs/registry.js +22 -0
- package/dist/runs/tools.js +46 -7
- package/dist/session-ui/terminalRows.js +187 -13
- package/dist/sessions/pibo-data-store.js +37 -0
- package/dist/sessions/runtime-binding-persistence.js +34 -0
- package/dist/sessions/runtime-binding.js +6 -0
- package/dist/sessions/sqlite-store.js +37 -0
- package/dist/shared/trace-engine.js +12 -64
- package/dist/shared/trace-event-projection.js +16 -0
- package/dist/shared/trace-history.js +684 -184
- package/dist/shared/trace-limits.js +7 -0
- package/dist/shared/trace-live-patch.js +61 -0
- package/dist/shared/trace-tool-identity.js +43 -0
- package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
- package/dist/speech/openai-codex.js +407 -0
- package/dist/speech/types.js +8 -0
- package/dist/subagents/context.js +5 -3
- package/dist/subagents/observations.js +94 -6
- package/dist/subagents/tool.js +103 -20
- package/dist/tools/codex-image-artifacts.js +24 -0
- package/dist/tools/codex-image-generation.js +4 -22
- package/dist/web/channel.js +88 -4
- package/dist/web/http.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
- package/dist/apps/chat-ui/assets/index-DT5jCQBP.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
- 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
|
|
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
|
|
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:
|
|
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)),
|