humanish 0.100.0 → 0.100.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/dist/actor-contract.d.ts +1 -1
- package/dist/actor-contract.js +2 -1
- package/dist/actor-contract.js.map +1 -1
- package/dist/observer-app.html +1 -1
- package/dist/restricted-codex-participant-policy.d.ts +0 -2
- package/dist/restricted-codex-participant-policy.js +2 -3
- package/dist/restricted-codex-participant-policy.js.map +1 -1
- package/dist/restricted-codex-participant.d.ts +2 -2
- package/dist/restricted-codex-participant.js +43 -36
- package/dist/restricted-codex-participant.js.map +1 -1
- package/dist/restricted-codex-session.d.ts +6 -2
- package/dist/restricted-codex-session.js +279 -214
- package/dist/restricted-codex-session.js.map +1 -1
- package/dist/restricted-codex-transport.d.ts +4 -2
- package/dist/restricted-codex-transport.js +14 -2
- package/dist/restricted-codex-transport.js.map +1 -1
- package/docs/architecture/restricted-codex-analysis.md +14 -5
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +3 -3
- package/docs/ramp/README.md +4 -1
- package/docs/release/0.100.1-participant-continuity.md +20 -0
- package/package.json +1 -1
|
@@ -111,20 +111,19 @@ async function checkVersion(file, env, cwd, spawnFn, deadline) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
+
const rawCompactionTypes = ["compaction", "compaction_summary", "context_compaction"];
|
|
115
|
+
const allowedRawItemTypes = ["message", "reasoning", ...rawCompactionTypes];
|
|
114
116
|
const unclosedChildren = new Set();
|
|
115
117
|
function retainUnclosedChild(closed) {
|
|
116
118
|
unclosedChildren.add(closed);
|
|
117
119
|
void closed.then(() => { unclosedChildren.delete(closed); });
|
|
118
120
|
}
|
|
119
|
-
/**
|
|
120
|
-
* new work, but ordinary participant turns may run concurrently. */
|
|
121
|
+
/** Analysts/readiness keep their one-shot lifetime; participants own a session. */
|
|
121
122
|
export async function runRestrictedCodexSession(request, options = {}, readinessOnly = false) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return restrictedCodexFailure("codex_busy");
|
|
127
|
-
return executeRestrictedCodexSession(request, options, readinessOnly);
|
|
123
|
+
const session = createRestrictedCodexSession(options);
|
|
124
|
+
const result = await session.run(request, readinessOnly);
|
|
125
|
+
return await session.close() ? result
|
|
126
|
+
: { ...restrictedCodexFailure("codex_cleanup_failed", result.dispatched, result.usage), failurePhase: "cleanup" };
|
|
128
127
|
}
|
|
129
128
|
async function writeRecoveryMarker(work, sourceEnv, reason) {
|
|
130
129
|
const cache = sourceEnv.XDG_CACHE_HOME ?? path.join(sourceEnv.HOME ?? homedir(), ".cache");
|
|
@@ -163,128 +162,146 @@ function hasScopedIdentity(method, params, threadId, turnId) {
|
|
|
163
162
|
&& (turnId === undefined || params.turnId === turnId);
|
|
164
163
|
return (params.threadId === undefined || params.threadId === threadId) && (params.turnId === undefined || params.turnId === turnId);
|
|
165
164
|
}
|
|
166
|
-
/**
|
|
167
|
-
|
|
168
|
-
const admissionError = restrictedCodexRequestError(request);
|
|
169
|
-
if (admissionError)
|
|
170
|
-
return restrictedCodexFailure(admissionError);
|
|
165
|
+
/** One private process and conversation per owner. Only completed turns may continue. */
|
|
166
|
+
export function createRestrictedCodexSession(options = {}) {
|
|
171
167
|
const platform = options.platform ?? process.platform, arch = options.arch ?? process.arch;
|
|
172
|
-
if (!(platform === "linux" && arch === "x64") && !(platform === "darwin" && arch === "arm64"))
|
|
173
|
-
return restrictedCodexFailure("codex_unsupported_platform");
|
|
174
|
-
const deadline = new RestrictedCodexDeadline(request.timeoutMs, request.signal);
|
|
175
168
|
const sourceEnv = options.env ?? process.env;
|
|
176
169
|
const spawnFn = options.spawnFn ?? ((file, args, settings) => spawn(file, args, settings));
|
|
177
170
|
let work, authLink, transport;
|
|
178
|
-
let threadId,
|
|
179
|
-
let
|
|
180
|
-
let
|
|
181
|
-
let
|
|
182
|
-
let
|
|
183
|
-
let
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (method === "rawResponseItem/completed") {
|
|
194
|
-
if (!["message", "reasoning"].includes(String(item.type))) {
|
|
195
|
-
deadline.stop("codex_tool_call");
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (item.type === "message" && Array.isArray(item.content)
|
|
199
|
-
&& item.content.some(content => codexRecord(content).type === "refusal"))
|
|
200
|
-
deadline.stop("refusal");
|
|
201
|
-
}
|
|
202
|
-
if (method === "thread/tokenUsage/updated")
|
|
203
|
-
usage = restrictedCodexUsage(params.tokenUsage);
|
|
204
|
-
if (method === "item/started" || method === "item/completed") {
|
|
205
|
-
if (!["userMessage", "agentMessage", "reasoning"].includes(String(item.type))) {
|
|
206
|
-
deadline.stop("codex_tool_call");
|
|
207
|
-
return;
|
|
171
|
+
let threadId, cwd = "", scratch = "";
|
|
172
|
+
let identity;
|
|
173
|
+
let previousUsage = { input: 0, output: 0, cachedInput: 0, cacheWriteInput: 0 };
|
|
174
|
+
let activeDeadline;
|
|
175
|
+
let interrupt;
|
|
176
|
+
let closed = false, cleanupTrusted = true;
|
|
177
|
+
let pending, closing, disposing;
|
|
178
|
+
const dispose = () => {
|
|
179
|
+
closed = true;
|
|
180
|
+
return disposing ??= (async () => {
|
|
181
|
+
let cleaned = cleanupTrusted;
|
|
182
|
+
if (transport) {
|
|
183
|
+
cleaned = await transport.close(interrupt).catch(() => false) && cleaned;
|
|
184
|
+
if (!cleaned)
|
|
185
|
+
retainUnclosedChild(transport.owned.closed);
|
|
208
186
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
187
|
+
let authReplaced = false;
|
|
188
|
+
if (authLink && cleaned) {
|
|
189
|
+
try {
|
|
190
|
+
authReplaced = !(await lstat(authLink)).isSymbolicLink();
|
|
191
|
+
if (!authReplaced)
|
|
192
|
+
await unlink(authLink);
|
|
213
193
|
}
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|| (outputItem && (outputItem.id !== item.id || outputItem.text !== item.text))) {
|
|
218
|
-
deadline.stop("invalid_response");
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
outputItem = { id: item.id, text: item.text };
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (codexRecord(error).code !== "ENOENT")
|
|
196
|
+
cleaned = false;
|
|
222
197
|
}
|
|
223
198
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
199
|
+
// Preserve unexpected login rotation for private recovery, never overwrite host auth.
|
|
200
|
+
if (authReplaced) {
|
|
201
|
+
cleaned = false;
|
|
202
|
+
if (work)
|
|
203
|
+
await preserveUnexpectedAuth(work, sourceEnv).catch(() => undefined);
|
|
204
|
+
}
|
|
205
|
+
if (work && cleaned)
|
|
206
|
+
await rm(work, { recursive: true, force: true }).catch(() => { cleaned = false; });
|
|
207
|
+
if (work && !cleaned && !authReplaced)
|
|
208
|
+
await writeRecoveryMarker(work, sourceEnv, "process_cleanup_unconfirmed").catch(() => undefined);
|
|
209
|
+
return cleaned;
|
|
210
|
+
})();
|
|
211
|
+
};
|
|
212
|
+
async function execute(request, readinessOnly) {
|
|
213
|
+
const deadline = new RestrictedCodexDeadline(request.timeoutMs, request.signal);
|
|
214
|
+
activeDeadline = deadline;
|
|
215
|
+
let turnId, earlyTurnId;
|
|
216
|
+
let dispatched = false, usage = null;
|
|
217
|
+
let completed = false, compacted = false;
|
|
218
|
+
let latestUsage = null;
|
|
219
|
+
let generatedDeltaBytes = 0;
|
|
220
|
+
let outputItem;
|
|
221
|
+
let result = restrictedCodexFailure("codex_process_failed");
|
|
222
|
+
let phase = "startup";
|
|
223
|
+
const early = [];
|
|
224
|
+
let resolveTurn;
|
|
225
|
+
const finished = new Promise(resolve => { resolveTurn = resolve; });
|
|
226
|
+
const handleTurnEvent = (method, params) => {
|
|
227
|
+
if (!hasScopedIdentity(method, params, threadId, turnId)) {
|
|
228
228
|
deadline.stop("codex_protocol_error");
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
231
|
+
const item = codexRecord(params.item);
|
|
232
|
+
// CLI 0.154 thread totals omit compaction requests. Retain known usage,
|
|
233
|
+
// but never label it complete when native compaction occurred in this turn.
|
|
234
|
+
if (method === "thread/compacted" || item.type === "contextCompaction" || rawCompactionTypes.includes(String(item.type)))
|
|
235
|
+
compacted = true;
|
|
236
|
+
if (method === "rawResponseItem/completed") {
|
|
237
|
+
if (!allowedRawItemTypes.includes(String(item.type))) {
|
|
238
|
+
deadline.stop("codex_tool_call");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (item.type === "message" && Array.isArray(item.content)
|
|
242
|
+
&& item.content.some(content => codexRecord(content).type === "refusal"))
|
|
243
|
+
deadline.stop("refusal");
|
|
235
244
|
}
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
245
|
+
if (method === "thread/tokenUsage/updated") {
|
|
246
|
+
const total = restrictedCodexUsage(params.tokenUsage);
|
|
247
|
+
// App-server reports cumulative thread usage. Receipts must charge only this turn.
|
|
248
|
+
if (total && previousUsage) {
|
|
249
|
+
const delta = { input: total.input - previousUsage.input, output: total.output - previousUsage.output,
|
|
250
|
+
cachedInput: (total.cachedInput ?? 0) - (previousUsage.cachedInput ?? 0),
|
|
251
|
+
cacheWriteInput: (total.cacheWriteInput ?? 0) - (previousUsage.cacheWriteInput ?? 0) };
|
|
252
|
+
usage = Object.values(delta).every(value => Number.isSafeInteger(value) && value >= 0)
|
|
253
|
+
&& delta.cachedInput + delta.cacheWriteInput <= delta.input ? delta : null;
|
|
254
|
+
}
|
|
255
|
+
else
|
|
256
|
+
usage = null;
|
|
257
|
+
latestUsage = total;
|
|
239
258
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
259
|
+
if (method === "item/started" || method === "item/completed") {
|
|
260
|
+
if (!["userMessage", "agentMessage", "reasoning", "contextCompaction"].includes(String(item.type))) {
|
|
261
|
+
deadline.stop("codex_tool_call");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (item.type === "agentMessage") {
|
|
265
|
+
if (item.delivery === "async" || (Array.isArray(item.questions) && item.questions.length > 0)) {
|
|
266
|
+
deadline.stop("codex_tool_call");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (method === "item/completed" && item.phase !== "commentary") {
|
|
270
|
+
if (typeof item.id !== "string" || typeof item.text !== "string" || Buffer.byteLength(item.text) > CODEX_MAX_OUTPUT_BYTES
|
|
271
|
+
|| (item.phase !== null && item.phase !== "final_answer") || (item.delivery !== null && item.delivery !== undefined)
|
|
272
|
+
|| (outputItem && (outputItem.id !== item.id || outputItem.text !== item.text))) {
|
|
273
|
+
deadline.stop("invalid_response");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
outputItem = { id: item.id, text: item.text };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
243
279
|
}
|
|
244
|
-
|
|
245
|
-
|
|
280
|
+
if (method === "turn/completed") {
|
|
281
|
+
const turn = codexRecord(params.turn);
|
|
282
|
+
if (turn.id !== turnId || completed) {
|
|
283
|
+
deadline.stop("codex_protocol_error");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
completed = true;
|
|
287
|
+
if (turn.status === "interrupted") {
|
|
288
|
+
deadline.stop("cancelled");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (turn.status !== "completed" || turn.error !== null || !outputItem) {
|
|
292
|
+
deadline.stop("invalid_response");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
resolveTurn({ status: "completed", output: JSON.parse(outputItem.text), usage,
|
|
297
|
+
usageComplete: usage !== null && !compacted, dispatched: true, errorCode: null });
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
deadline.stop("invalid_response");
|
|
301
|
+
}
|
|
246
302
|
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
deadline.check();
|
|
251
|
-
const file = await resolveExecutable(options, sourceEnv);
|
|
252
|
-
deadline.check();
|
|
253
|
-
work = await mkdtemp(path.join(options.tempRoot ?? tmpdir(), "humanish-codex-analysis-"));
|
|
254
|
-
await chmod(work, 0o700);
|
|
255
|
-
work = await realpath(work);
|
|
256
|
-
const home = path.join(work, "home"), cwd = path.join(work, "cwd"), scratch = path.join(work, "scratch");
|
|
257
|
-
for (const directory of [home, cwd, scratch])
|
|
258
|
-
await mkdir(directory, { mode: 0o700 });
|
|
259
|
-
const env = childEnvironment(sourceEnv, home, scratch);
|
|
260
|
-
await checkVersion(file, env, cwd, spawnFn, deadline);
|
|
261
|
-
const config = restrictedCodexConfig(request.model), configPath = path.join(home, "config.toml");
|
|
262
|
-
await writeFile(configPath, config.toml, { mode: 0o600, flag: "wx" });
|
|
263
|
-
const authHome = options.authHome ?? sourceEnv.CODEX_HOME ?? path.join(sourceEnv.HOME ?? homedir(), ".codex");
|
|
264
|
-
if (!path.isAbsolute(authHome))
|
|
265
|
-
throw new RestrictedCodexStop("codex_unsupported_auth");
|
|
266
|
-
let authFile;
|
|
267
|
-
try {
|
|
268
|
-
authFile = await realpath(path.join(authHome, "auth.json"));
|
|
269
|
-
if (!(await stat(authFile)).isFile())
|
|
270
|
-
throw new Error();
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
throw new RestrictedCodexStop("codex_login_required");
|
|
274
|
-
}
|
|
275
|
-
deadline.check();
|
|
276
|
-
authLink = path.join(home, "auth.json");
|
|
277
|
-
await symlink(authFile, authLink);
|
|
278
|
-
deadline.check();
|
|
279
|
-
const owned = ownCodexProcess(spawnFn(file, ["app-server", "--strict-config"], {
|
|
280
|
-
cwd, env, detached: false, stdio: ["pipe", "pipe", "pipe"]
|
|
281
|
-
}));
|
|
282
|
-
// Raw input notifications echo supplied image data. Bound that wire separately
|
|
283
|
-
// from the 2 MiB generated report, without dropping admitted evidence.
|
|
284
|
-
const frameLimit = Math.max(CODEX_MAX_OUTPUT_BYTES, Buffer.byteLength(JSON.stringify({ instructions: request.instructions,
|
|
285
|
-
evidence: request.evidence, images: request.images, schema: request.schema })) + 1024 * 1024);
|
|
286
|
-
transport = new RestrictedCodexTransport(owned, deadline, frameLimit);
|
|
287
|
-
transport.onNotification = (method, params) => {
|
|
303
|
+
};
|
|
304
|
+
const onNotification = (method, params) => {
|
|
288
305
|
if (!dispatched)
|
|
289
306
|
return;
|
|
290
307
|
if (!hasScopedIdentity(method, params, threadId, turnId ?? earlyTurnId)) {
|
|
@@ -304,7 +321,7 @@ async function executeRestrictedCodexSession(request, options = {}, readinessOnl
|
|
|
304
321
|
}
|
|
305
322
|
const item = codexRecord(params.item);
|
|
306
323
|
// Tool requests must fail even if the turn-start acknowledgment is lost.
|
|
307
|
-
if ((method === "rawResponseItem/completed" && !
|
|
324
|
+
if ((method === "rawResponseItem/completed" && !allowedRawItemTypes.includes(String(item.type)))
|
|
308
325
|
|| (["item/started", "item/completed"].includes(method) && item.type === "agentMessage"
|
|
309
326
|
&& (item.delivery === "async" || (Array.isArray(item.questions) && item.questions.length > 0)))) {
|
|
310
327
|
deadline.stop("codex_tool_call");
|
|
@@ -314,118 +331,166 @@ async function executeRestrictedCodexSession(request, options = {}, readinessOnl
|
|
|
314
331
|
const value = codexRecord(params.turn).id;
|
|
315
332
|
if (typeof value !== "string" || value.length === 0 || (earlyTurnId !== undefined && value !== earlyTurnId))
|
|
316
333
|
deadline.stop("codex_protocol_error");
|
|
317
|
-
else
|
|
334
|
+
else {
|
|
318
335
|
earlyTurnId = value;
|
|
336
|
+
interrupt = { threadId: threadId, turnId: value };
|
|
337
|
+
}
|
|
319
338
|
}
|
|
320
339
|
if (turnId === undefined)
|
|
321
340
|
early.push({ method, params });
|
|
322
341
|
else
|
|
323
342
|
handleTurnEvent(method, params);
|
|
324
343
|
};
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
phase = "turn/start";
|
|
359
|
-
const input = [{ type: "text", text: request.evidence, text_elements: [] }];
|
|
360
|
-
for (const [index, image] of request.images.entries()) {
|
|
344
|
+
try {
|
|
345
|
+
deadline.check();
|
|
346
|
+
const frameLimit = Math.max(CODEX_MAX_OUTPUT_BYTES, Buffer.byteLength(JSON.stringify({ instructions: request.instructions,
|
|
347
|
+
evidence: request.evidence, images: request.images, schema: request.schema })) + 1024 * 1024);
|
|
348
|
+
if (!transport) {
|
|
349
|
+
const file = await resolveExecutable(options, sourceEnv);
|
|
350
|
+
deadline.check();
|
|
351
|
+
work = await mkdtemp(path.join(options.tempRoot ?? tmpdir(), "humanish-codex-analysis-"));
|
|
352
|
+
await chmod(work, 0o700);
|
|
353
|
+
work = await realpath(work);
|
|
354
|
+
const home = path.join(work, "home");
|
|
355
|
+
cwd = path.join(work, "cwd");
|
|
356
|
+
scratch = path.join(work, "scratch");
|
|
357
|
+
for (const directory of [home, cwd, scratch])
|
|
358
|
+
await mkdir(directory, { mode: 0o700 });
|
|
359
|
+
const env = childEnvironment(sourceEnv, home, scratch);
|
|
360
|
+
await checkVersion(file, env, cwd, spawnFn, deadline);
|
|
361
|
+
const config = restrictedCodexConfig(request.model), configPath = path.join(home, "config.toml");
|
|
362
|
+
await writeFile(configPath, config.toml, { mode: 0o600, flag: "wx" });
|
|
363
|
+
const authHome = options.authHome ?? sourceEnv.CODEX_HOME ?? path.join(sourceEnv.HOME ?? homedir(), ".codex");
|
|
364
|
+
if (!path.isAbsolute(authHome))
|
|
365
|
+
throw new RestrictedCodexStop("codex_unsupported_auth");
|
|
366
|
+
let authFile;
|
|
367
|
+
try {
|
|
368
|
+
authFile = await realpath(path.join(authHome, "auth.json"));
|
|
369
|
+
if (!(await stat(authFile)).isFile())
|
|
370
|
+
throw new Error();
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
throw new RestrictedCodexStop("codex_login_required");
|
|
374
|
+
}
|
|
361
375
|
deadline.check();
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
376
|
+
authLink = path.join(home, "auth.json");
|
|
377
|
+
await symlink(authFile, authLink);
|
|
378
|
+
deadline.check();
|
|
379
|
+
const owned = ownCodexProcess(spawnFn(file, ["app-server", "--strict-config"], {
|
|
380
|
+
cwd, env, detached: false, stdio: ["pipe", "pipe", "pipe"]
|
|
381
|
+
}));
|
|
382
|
+
transport = new RestrictedCodexTransport(owned, deadline, frameLimit);
|
|
383
|
+
transport.onNotification = onNotification;
|
|
384
|
+
phase = "initialize";
|
|
385
|
+
const initialize = await transport.rpc("initialize", {
|
|
386
|
+
clientInfo: { name: "humanish_analysis", version: "1.0.0" }, capabilities: { experimentalApi: true }
|
|
387
|
+
});
|
|
388
|
+
if (typeof initialize.userAgent !== "string" || !initialize.userAgent.includes(`/0.154.0 `)
|
|
389
|
+
|| initialize.codexHome !== home || initialize.platformOs !== (platform === "darwin" ? "macos" : "linux") || initialize.platformFamily !== "unix")
|
|
390
|
+
throw new RestrictedCodexStop("codex_unsupported_version");
|
|
391
|
+
transport.notify("initialized", {});
|
|
392
|
+
phase = "config/read";
|
|
393
|
+
const effective = await transport.rpc("config/read", { includeLayers: true, cwd });
|
|
394
|
+
if (!admitsRestrictedCodexConfig(effective, configPath, request.model))
|
|
395
|
+
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
396
|
+
phase = "account/read";
|
|
397
|
+
const account = await transport.rpc("account/read", { refreshToken: false });
|
|
398
|
+
if (account.account === null)
|
|
399
|
+
throw new RestrictedCodexStop("codex_login_required");
|
|
400
|
+
if (codexRecord(account.account).type !== "chatgpt" || account.requiresOpenaiAuth !== true)
|
|
401
|
+
throw new RestrictedCodexStop("codex_unsupported_auth");
|
|
402
|
+
phase = "thread/start";
|
|
403
|
+
const thread = await transport.rpc("thread/start", { cwd, ephemeral: true, experimentalRawEvents: true,
|
|
404
|
+
approvalPolicy: "never", sandbox: "read-only", model: request.model, modelProvider: "openai", allowProviderModelFallback: false,
|
|
405
|
+
environments: [], runtimeWorkspaceRoots: [], dynamicTools: [], baseInstructions: request.instructions, config: config.overrides });
|
|
406
|
+
if (!admitsRestrictedCodexThread(thread, request.model, cwd))
|
|
407
|
+
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
408
|
+
threadId = String(codexRecord(thread.thread).id);
|
|
409
|
+
phase = "mcpServerStatus/list";
|
|
410
|
+
const mcp = await transport.rpc("mcpServerStatus/list", { limit: 100 });
|
|
411
|
+
if (!Array.isArray(mcp.data) || mcp.data.length !== 0 || mcp.nextCursor !== null)
|
|
412
|
+
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
413
|
+
identity = { model: request.model, instructions: request.instructions };
|
|
366
414
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const turn = await transport.rpc("turn/start", { threadId, cwd, approvalPolicy: "never", sandboxPolicy: { type: "readOnly" },
|
|
371
|
-
environments: [], runtimeWorkspaceRoots: [], effort: "low", model: request.model, outputSchema: request.schema, input });
|
|
372
|
-
const returnedTurnId = codexRecord(turn.turn).id;
|
|
373
|
-
if (typeof returnedTurnId !== "string" || returnedTurnId.length === 0 || returnedTurnId.length > 200
|
|
374
|
-
|| (earlyTurnId !== undefined && earlyTurnId !== returnedTurnId))
|
|
375
|
-
throw new RestrictedCodexStop("codex_protocol_error");
|
|
376
|
-
turnId = returnedTurnId;
|
|
377
|
-
for (const event of early)
|
|
378
|
-
handleTurnEvent(event.method, event.params);
|
|
379
|
-
early.length = 0;
|
|
380
|
-
phase = "response";
|
|
381
|
-
result = await deadline.wait(finished);
|
|
382
|
-
deadline.check();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
const code = error instanceof RestrictedCodexStop ? error.code : "codex_process_failed";
|
|
387
|
-
result = { ...restrictedCodexFailure(code === "codex_cleanup_failed" ? code : deadline.code ?? code, dispatched, usage), failurePhase: phase };
|
|
388
|
-
}
|
|
389
|
-
finally {
|
|
390
|
-
// Deadline remains authoritative until acceptance; teardown has its own small grace.
|
|
391
|
-
deadline.close();
|
|
392
|
-
let cleaned = result.errorCode !== "codex_cleanup_failed";
|
|
393
|
-
if (transport) {
|
|
394
|
-
const cleanupTurn = turnId ?? earlyTurnId;
|
|
395
|
-
cleaned = await transport.close(dispatched && !completed && threadId && cleanupTurn
|
|
396
|
-
? { threadId, turnId: cleanupTurn } : undefined).catch(() => false);
|
|
397
|
-
if (!cleaned)
|
|
398
|
-
retainUnclosedChild(transport.owned.closed);
|
|
399
|
-
}
|
|
400
|
-
let authReplaced = false;
|
|
401
|
-
if (authLink && cleaned) {
|
|
402
|
-
try {
|
|
403
|
-
const info = await lstat(authLink);
|
|
404
|
-
authReplaced = !info.isSymbolicLink();
|
|
405
|
-
if (!authReplaced)
|
|
406
|
-
await unlink(authLink);
|
|
415
|
+
else {
|
|
416
|
+
transport.beginRequest(deadline, frameLimit);
|
|
417
|
+
transport.onNotification = onNotification;
|
|
407
418
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
419
|
+
if (readinessOnly) {
|
|
420
|
+
result = { status: "completed", output: null, usage: null, usageComplete: false, dispatched: false, errorCode: null };
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
phase = "turn/start";
|
|
424
|
+
const input = [{ type: "text", text: request.evidence, text_elements: [] }];
|
|
425
|
+
for (const [index, image] of request.images.entries()) {
|
|
426
|
+
deadline.check();
|
|
427
|
+
const match = CODEX_IMAGE.exec(image.dataUrl);
|
|
428
|
+
const imagePath = path.join(scratch, `evidence-${index}.${match[1] === "jpeg" ? "jpg" : match[1]}`);
|
|
429
|
+
await writeFile(imagePath, Buffer.from(match[2], "base64"), { mode: 0o600 });
|
|
430
|
+
input.push({ type: "text", text: JSON.stringify({ captureEvidenceId: image.evidenceId }), text_elements: [] }, { type: "localImage", path: imagePath });
|
|
431
|
+
}
|
|
432
|
+
deadline.check();
|
|
433
|
+
// Even a lost acknowledgment may have dispatched the request. Never claim zero cost.
|
|
434
|
+
dispatched = true;
|
|
435
|
+
const turn = await transport.rpc("turn/start", { threadId, cwd, approvalPolicy: "never", sandboxPolicy: { type: "readOnly" },
|
|
436
|
+
environments: [], runtimeWorkspaceRoots: [], effort: "low", model: request.model, outputSchema: request.schema, input });
|
|
437
|
+
const returnedTurnId = codexRecord(turn.turn).id;
|
|
438
|
+
if (typeof returnedTurnId !== "string" || returnedTurnId.length === 0 || returnedTurnId.length > 200
|
|
439
|
+
|| (earlyTurnId !== undefined && earlyTurnId !== returnedTurnId))
|
|
440
|
+
throw new RestrictedCodexStop("codex_protocol_error");
|
|
441
|
+
turnId = returnedTurnId;
|
|
442
|
+
interrupt = { threadId: threadId, turnId };
|
|
443
|
+
for (const event of early)
|
|
444
|
+
handleTurnEvent(event.method, event.params);
|
|
445
|
+
early.length = 0;
|
|
446
|
+
phase = "response";
|
|
447
|
+
result = await deadline.wait(finished);
|
|
448
|
+
deadline.check();
|
|
449
|
+
previousUsage = latestUsage;
|
|
450
|
+
interrupt = undefined;
|
|
411
451
|
}
|
|
412
452
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (authReplaced) {
|
|
417
|
-
cleaned = false;
|
|
418
|
-
if (work)
|
|
419
|
-
await preserveUnexpectedAuth(work, sourceEnv).catch(() => undefined);
|
|
453
|
+
catch (error) {
|
|
454
|
+
const code = error instanceof RestrictedCodexStop ? error.code : "codex_process_failed";
|
|
455
|
+
result = { ...restrictedCodexFailure(code === "codex_cleanup_failed" ? code : deadline.code ?? code, dispatched, usage), failurePhase: phase };
|
|
420
456
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
457
|
+
finally {
|
|
458
|
+
deadline.close();
|
|
459
|
+
activeDeadline = undefined;
|
|
460
|
+
if (transport)
|
|
461
|
+
transport.onNotification = () => undefined;
|
|
462
|
+
if (result.errorCode !== null) {
|
|
463
|
+
if (result.errorCode === "codex_cleanup_failed")
|
|
464
|
+
cleanupTrusted = false;
|
|
465
|
+
if (!await dispose())
|
|
466
|
+
result = { ...restrictedCodexFailure("codex_cleanup_failed", dispatched, usage), failurePhase: "cleanup" };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return result.errorCode !== null && result.failurePhase === undefined ? { ...result, failurePhase: phase } : result;
|
|
427
470
|
}
|
|
428
|
-
return
|
|
471
|
+
return {
|
|
472
|
+
run(request, readinessOnly = false) {
|
|
473
|
+
const error = restrictedCodexRequestError(request);
|
|
474
|
+
if (error)
|
|
475
|
+
return Promise.resolve(restrictedCodexFailure(error));
|
|
476
|
+
if (closed || (identity && (identity.model !== request.model || identity.instructions !== request.instructions)))
|
|
477
|
+
return Promise.resolve(restrictedCodexFailure("invalid_request"));
|
|
478
|
+
if (pending || unclosedChildren.size)
|
|
479
|
+
return Promise.resolve(restrictedCodexFailure("codex_busy"));
|
|
480
|
+
if (!(platform === "linux" && arch === "x64") && !(platform === "darwin" && arch === "arm64"))
|
|
481
|
+
return Promise.resolve(restrictedCodexFailure("codex_unsupported_platform"));
|
|
482
|
+
const task = execute(request, readinessOnly);
|
|
483
|
+
pending = task;
|
|
484
|
+
void task.finally(() => { if (pending === task)
|
|
485
|
+
pending = undefined; }).catch(() => undefined);
|
|
486
|
+
return task;
|
|
487
|
+
},
|
|
488
|
+
close() {
|
|
489
|
+
closed = true;
|
|
490
|
+
activeDeadline?.stop("cancelled");
|
|
491
|
+
return closing ??= (async () => { await pending; return dispose(); })();
|
|
492
|
+
}
|
|
493
|
+
};
|
|
429
494
|
}
|
|
430
495
|
export async function checkRestrictedCodexSessionReadiness(input = {}, options = {}) {
|
|
431
496
|
return runRestrictedCodexSession({ model: RESTRICTED_CODEX_ANALYSIS_MODELS[0], instructions: "Analyze only supplied study evidence.",
|