@pasko70/pibo 2.5.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +304 -30
- 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/process.js +14 -0
- package/dist/agent-runtimes/codex-native/thread.js +59 -25
- package/dist/agent-runtimes/codex-native/turn.js +27 -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/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-files.js +130 -9
- 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-DTWJf37A.js → dist-BBDMeU5-.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CkYWKnvy.js → dist-BDIATCQt.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DZ8Wpj-H.js → dist-JnW4v8UE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dmblz15N.js → dist-lRiLP9rr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DjcwoyNY.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.1.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/session-router.js +464 -37
- 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 +259 -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-DsgTm1Aw.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.2.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,
|
|
@@ -419,6 +419,12 @@ 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 {
|
|
@@ -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)),
|
|
@@ -165,17 +165,14 @@ function secondsToIso(seconds) {
|
|
|
165
165
|
const date = new Date(Number(seconds) * 1_000);
|
|
166
166
|
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
167
167
|
}
|
|
168
|
-
function
|
|
169
|
-
if (item.type
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return text || undefined;
|
|
177
|
-
}
|
|
178
|
-
return undefined;
|
|
168
|
+
function userMessageText(item) {
|
|
169
|
+
if (item.type !== "userMessage" || !Array.isArray(item.content))
|
|
170
|
+
return undefined;
|
|
171
|
+
const text = item.content
|
|
172
|
+
.filter((part) => isRecord(part) && part.type === "text" && typeof part.text === "string")
|
|
173
|
+
.map((part) => redactCodexNativeSensitiveText(part.text))
|
|
174
|
+
.join("\n");
|
|
175
|
+
return text || undefined;
|
|
179
176
|
}
|
|
180
177
|
function threadLeafId(thread) {
|
|
181
178
|
return thread.turns.at(-1)?.id ?? null;
|
|
@@ -204,12 +201,21 @@ export function codexThreadInfo(runtimeInstanceId, thread) {
|
|
|
204
201
|
firstMessage: thread.preview ? redactCodexNativeSensitiveText(thread.preview) : undefined,
|
|
205
202
|
};
|
|
206
203
|
}
|
|
207
|
-
|
|
208
|
-
return thread.turns.
|
|
209
|
-
const
|
|
210
|
-
|
|
204
|
+
function codexThreadForkTargets(thread) {
|
|
205
|
+
return thread.turns.flatMap((turn, index) => {
|
|
206
|
+
const userMessage = turn.items.find((item) => item.type === "userMessage");
|
|
207
|
+
if (!userMessage)
|
|
208
|
+
return [];
|
|
209
|
+
return [{
|
|
210
|
+
entryId: userMessage.id,
|
|
211
|
+
text: userMessageText(userMessage) ?? `Codex turn ${turn.id}`,
|
|
212
|
+
...(index > 0 ? { previousTurnId: thread.turns[index - 1].id } : {}),
|
|
213
|
+
}];
|
|
211
214
|
});
|
|
212
215
|
}
|
|
216
|
+
export function codexThreadForkCandidates(thread) {
|
|
217
|
+
return codexThreadForkTargets(thread).map(({ entryId, text }) => ({ entryId, text }));
|
|
218
|
+
}
|
|
213
219
|
export class CodexNativeThreadController {
|
|
214
220
|
client;
|
|
215
221
|
currentThread;
|
|
@@ -241,6 +247,7 @@ export class CodexNativeThreadController {
|
|
|
241
247
|
...(selection.sandbox ? { sandbox: selection.sandbox } : {}),
|
|
242
248
|
...(selection.config ? { config: structuredClone(selection.config) } : {}),
|
|
243
249
|
...(selection.developerInstructions ? { developerInstructions: selection.developerInstructions } : {}),
|
|
250
|
+
...(selection.personality !== undefined ? { personality: selection.personality } : {}),
|
|
244
251
|
});
|
|
245
252
|
}
|
|
246
253
|
static async resume(client, threadId, workspace, selection = {}) {
|
|
@@ -265,6 +272,7 @@ export class CodexNativeThreadController {
|
|
|
265
272
|
...(selection.sandbox ? { sandbox: selection.sandbox } : {}),
|
|
266
273
|
...(selection.config ? { config: structuredClone(selection.config) } : {}),
|
|
267
274
|
...(selection.developerInstructions ? { developerInstructions: selection.developerInstructions } : {}),
|
|
275
|
+
...(selection.personality !== undefined ? { personality: selection.personality } : {}),
|
|
268
276
|
});
|
|
269
277
|
}
|
|
270
278
|
catch (error) {
|
|
@@ -345,15 +353,29 @@ export class CodexNativeThreadController {
|
|
|
345
353
|
.sort((left, right) => right.updatedAt - left.updatedAt || right.createdAt - left.createdAt)
|
|
346
354
|
.map((thread) => codexThreadInfo(runtimeInstanceId, thread));
|
|
347
355
|
}
|
|
348
|
-
async fork(runtimeInstanceId, workspace,
|
|
349
|
-
if (!
|
|
350
|
-
throw new Error("Codex thread fork requires a native
|
|
351
|
-
|
|
356
|
+
async fork(runtimeInstanceId, workspace, entryId, validateThread) {
|
|
357
|
+
if (!entryId.trim())
|
|
358
|
+
throw new Error("Codex thread fork requires a native user-message id.");
|
|
359
|
+
const target = codexThreadForkTargets(this.currentThread).find((candidate) => candidate.entryId === entryId);
|
|
360
|
+
if (target) {
|
|
361
|
+
if (!target.previousTurnId) {
|
|
362
|
+
return this.branchBeforeFirstTurn(runtimeInstanceId, workspace, target);
|
|
363
|
+
}
|
|
364
|
+
return await this.forkAt(runtimeInstanceId, workspace, target.previousTurnId, validateThread, target);
|
|
365
|
+
}
|
|
366
|
+
const legacyTurn = this.currentThread.turns.find((turn) => turn.id === entryId);
|
|
367
|
+
if (!legacyTurn)
|
|
368
|
+
throw new Error(`Codex fork target "${entryId}" was not found.`);
|
|
369
|
+
const legacyText = legacyTurn.items.map(userMessageText).find((text) => text?.trim());
|
|
370
|
+
return await this.forkAt(runtimeInstanceId, workspace, entryId, validateThread, {
|
|
371
|
+
entryId,
|
|
372
|
+
text: legacyText ?? `Codex turn ${entryId}`,
|
|
373
|
+
});
|
|
352
374
|
}
|
|
353
375
|
async clone(runtimeInstanceId, workspace, validateThread) {
|
|
354
376
|
return await this.forkAt(runtimeInstanceId, workspace, undefined, validateThread);
|
|
355
377
|
}
|
|
356
|
-
async forkAt(runtimeInstanceId, workspace, lastTurnId, validateThread) {
|
|
378
|
+
async forkAt(runtimeInstanceId, workspace, lastTurnId, validateThread, selectedTarget) {
|
|
357
379
|
const previousThread = this.currentThread;
|
|
358
380
|
const previous = codexThreadSnapshot(runtimeInstanceId, previousThread);
|
|
359
381
|
const params = {
|
|
@@ -376,9 +398,6 @@ export class CodexNativeThreadController {
|
|
|
376
398
|
throw new CodexNativeThreadProtocolError("Codex thread/fork returned the source thread id.");
|
|
377
399
|
}
|
|
378
400
|
await validateThread?.(forked.id);
|
|
379
|
-
const selectedText = lastTurnId
|
|
380
|
-
? codexThreadForkCandidates(previousThread).find((candidate) => candidate.entryId === lastTurnId)?.text
|
|
381
|
-
: undefined;
|
|
382
401
|
this.knownThreads.set(previousThread.id, structuredClone(previousThread));
|
|
383
402
|
this.knownThreads.set(forked.id, structuredClone(forked));
|
|
384
403
|
this.currentThread = forked;
|
|
@@ -387,12 +406,27 @@ export class CodexNativeThreadController {
|
|
|
387
406
|
previous,
|
|
388
407
|
current: codexThreadSnapshot(runtimeInstanceId, forked),
|
|
389
408
|
cancelled: false,
|
|
390
|
-
...(
|
|
391
|
-
...(
|
|
409
|
+
...(selectedTarget?.text ? { selectedText: selectedTarget.text } : {}),
|
|
410
|
+
...(selectedTarget ? { summaryEntryId: selectedTarget.entryId } : {}),
|
|
392
411
|
};
|
|
393
412
|
}
|
|
394
413
|
catch (error) {
|
|
395
414
|
return normalizeThreadError(error, previousThread.id);
|
|
396
415
|
}
|
|
397
416
|
}
|
|
417
|
+
branchBeforeFirstTurn(runtimeInstanceId, workspace, target) {
|
|
418
|
+
const previousThread = this.currentThread;
|
|
419
|
+
return {
|
|
420
|
+
previous: codexThreadSnapshot(runtimeInstanceId, previousThread),
|
|
421
|
+
current: {
|
|
422
|
+
adapterId: CODEX_NATIVE_ADAPTER_ID,
|
|
423
|
+
runtimeInstanceId,
|
|
424
|
+
cwd: workspace,
|
|
425
|
+
leafId: null,
|
|
426
|
+
},
|
|
427
|
+
cancelled: false,
|
|
428
|
+
selectedText: target.text,
|
|
429
|
+
summaryEntryId: target.entryId,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
398
432
|
}
|
|
@@ -319,6 +319,33 @@ export class CodexNativeTurnController {
|
|
|
319
319
|
throw error;
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
|
+
replayTerminalTurn(turn) {
|
|
323
|
+
if (this.disposed)
|
|
324
|
+
throw new Error("Codex native turn controller is disposed.");
|
|
325
|
+
if (this.pending)
|
|
326
|
+
throw new Error("Codex native session already has an active turn.");
|
|
327
|
+
if (turn.status === "inProgress") {
|
|
328
|
+
throw new CodexNativeTurnProtocolError("Codex cannot replay a non-terminal native turn.");
|
|
329
|
+
}
|
|
330
|
+
const replay = newPendingTurn();
|
|
331
|
+
this.assignTurnId(replay, turn.id);
|
|
332
|
+
replay.started = true;
|
|
333
|
+
for (const item of turn.items)
|
|
334
|
+
this.completeItem(replay, item);
|
|
335
|
+
this.finishIncompleteItems(replay, turn.status);
|
|
336
|
+
if (turn.status === "failed") {
|
|
337
|
+
const message = errorMessage(turn.error);
|
|
338
|
+
this.emit({
|
|
339
|
+
type: "turn_failed",
|
|
340
|
+
message,
|
|
341
|
+
details: providerErrorDetails(turn.error, message, false),
|
|
342
|
+
turnId: turn.id,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.emit({ type: "turn_completed", status: turn.status, turnId: turn.id });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
322
349
|
async compact() {
|
|
323
350
|
if (this.disposed)
|
|
324
351
|
throw new Error("Codex native turn controller is disposed.");
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { isCodexNativeBuiltInHistoryReconciliationProof } from "./codex-native/adapter.js";
|
|
2
|
+
import { isOmpBuiltInHistoryReconciliationProof } from "./omp/adapter.js";
|
|
3
|
+
import { isPiBuiltInHistoryReconciliationProof } from "./pi/adapter.js";
|
|
4
|
+
export function isBuiltInHistoryReconciliationProof(proof) {
|
|
5
|
+
return proof !== undefined && (isPiBuiltInHistoryReconciliationProof(proof)
|
|
6
|
+
|| isOmpBuiltInHistoryReconciliationProof(proof)
|
|
7
|
+
|| isCodexNativeBuiltInHistoryReconciliationProof(proof));
|
|
8
|
+
}
|