humanish 0.100.0 → 0.101.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/dist/actor-contract.d.ts +10 -10
- package/dist/actor-contract.js +15 -4
- package/dist/actor-contract.js.map +1 -1
- package/dist/computer-use.d.ts +13 -2
- package/dist/computer-use.js +108 -24
- package/dist/computer-use.js.map +1 -1
- package/dist/cua-actor-lab.js +34 -15
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/local-agent-cli.d.ts +10 -0
- package/dist/local-agent-cli.js +23 -7
- package/dist/local-agent-cli.js.map +1 -1
- package/dist/observer-app.html +1 -1
- package/dist/restricted-codex-participant-policy.d.ts +11 -7
- package/dist/restricted-codex-participant-policy.js +22 -51
- package/dist/restricted-codex-participant-policy.js.map +1 -1
- package/dist/restricted-codex-participant.d.ts +6 -2
- package/dist/restricted-codex-participant.js +166 -107
- package/dist/restricted-codex-participant.js.map +1 -1
- package/dist/restricted-codex-policy.d.ts +12 -5
- package/dist/restricted-codex-policy.js +62 -37
- package/dist/restricted-codex-policy.js.map +1 -1
- package/dist/restricted-codex-session.d.ts +28 -3
- package/dist/restricted-codex-session.js +440 -218
- package/dist/restricted-codex-session.js.map +1 -1
- package/dist/restricted-codex-transport.d.ts +15 -4
- package/dist/restricted-codex-transport.js +71 -8
- package/dist/restricted-codex-transport.js.map +1 -1
- package/docs/architecture/restricted-codex-analysis.md +17 -5
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +3 -3
- package/docs/ramp/README.md +7 -1
- package/docs/release/0.100.1-participant-continuity.md +20 -0
- package/docs/release/0.101.0-codex-ui-tools.md +28 -0
- package/package.json +1 -1
- package/dist/local-agent-appserver.d.ts +0 -44
- package/dist/local-agent-appserver.js +0 -269
- package/dist/local-agent-appserver.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { access, chmod, lstat, mkdir, mkdtemp, open, readdir, realpath, rm, stat
|
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { CODEX_IMAGE, CODEX_MAX_OUTPUT_BYTES, RESTRICTED_CODEX_ANALYSIS_IDENTITY, RESTRICTED_CODEX_ANALYSIS_MODELS, admitsRestrictedCodexConfig, admitsRestrictedCodexThread, codexRecord, restrictedCodexConfig, restrictedCodexFailure, restrictedCodexRequestError, restrictedCodexUsage } from "./restricted-codex-policy.js";
|
|
7
|
+
import { CODEX_IMAGE, CODEX_MAX_OUTPUT_BYTES, CODEX_MAX_REQUEST_BYTES, RESTRICTED_CODEX_ANALYSIS_IDENTITY, RESTRICTED_CODEX_ANALYSIS_MODELS, admitsRestrictedCodexConfig, admitsRestrictedCodexThread, codexRecord, restrictedCodexConfig, restrictedCodexFailure, restrictedCodexRequestError, restrictedCodexUsage } from "./restricted-codex-policy.js";
|
|
8
8
|
import { RestrictedCodexDeadline, RestrictedCodexStop, RestrictedCodexTransport, closeOwnedCodexProcess, ownCodexProcess } from "./restricted-codex-transport.js";
|
|
9
9
|
function childEnvironment(source, home, scratch) {
|
|
10
10
|
const result = { HOME: home, CODEX_HOME: home, TMPDIR: scratch };
|
|
@@ -13,6 +13,26 @@ function childEnvironment(source, home, scratch) {
|
|
|
13
13
|
result[key] = source[key];
|
|
14
14
|
return result;
|
|
15
15
|
}
|
|
16
|
+
function configArguments(overrides) {
|
|
17
|
+
return Object.entries(overrides).flatMap(([key, value]) => ["-c", `${key}=${JSON.stringify(value)}`]);
|
|
18
|
+
}
|
|
19
|
+
function validParticipant(options) {
|
|
20
|
+
if (!options)
|
|
21
|
+
return true;
|
|
22
|
+
const { tool } = options;
|
|
23
|
+
if ((options.authMode !== undefined && options.authMode !== "operator")
|
|
24
|
+
|| typeof tool?.name !== "string" || !/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(tool.name)
|
|
25
|
+
|| typeof tool.description !== "string" || tool.description.length === 0 || tool.description.length > 4096
|
|
26
|
+
|| !tool.inputSchema || typeof tool.inputSchema !== "object" || Array.isArray(tool.inputSchema)
|
|
27
|
+
|| typeof tool.call !== "function")
|
|
28
|
+
return false;
|
|
29
|
+
try {
|
|
30
|
+
return Buffer.byteLength(JSON.stringify(tool.inputSchema)) <= 256 * 1024;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
16
36
|
async function isNativeExecutable(file, platform) {
|
|
17
37
|
try {
|
|
18
38
|
await access(file, constants.X_OK);
|
|
@@ -32,10 +52,22 @@ async function isNativeExecutable(file, platform) {
|
|
|
32
52
|
return false;
|
|
33
53
|
}
|
|
34
54
|
}
|
|
55
|
+
/** Official npm launcher target map for the Unix hosts supported by this transport. */
|
|
56
|
+
export function restrictedCodexNpmTarget(platform, arch) {
|
|
57
|
+
if (platform === "linux" && arch === "x64")
|
|
58
|
+
return { triple: "x86_64-unknown-linux-musl", packageName: "codex-linux-x64" };
|
|
59
|
+
if (platform === "linux" && arch === "arm64")
|
|
60
|
+
return { triple: "aarch64-unknown-linux-musl", packageName: "codex-linux-arm64" };
|
|
61
|
+
if (platform === "darwin" && arch === "x64")
|
|
62
|
+
return { triple: "x86_64-apple-darwin", packageName: "codex-darwin-x64" };
|
|
63
|
+
if (platform === "darwin" && arch === "arm64")
|
|
64
|
+
return { triple: "aarch64-apple-darwin", packageName: "codex-darwin-arm64" };
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
35
67
|
/** Resolve PATH without running a shell. The npm launcher is resolved to its
|
|
36
68
|
* native optional package so cleanup owns the real app-server child. */
|
|
37
69
|
async function resolveExecutable(options, env) {
|
|
38
|
-
const platform = options.platform ?? process.platform;
|
|
70
|
+
const platform = options.platform ?? process.platform, arch = options.arch ?? process.arch;
|
|
39
71
|
let selected = options.executable;
|
|
40
72
|
if (selected === undefined) {
|
|
41
73
|
for (const directory of (env.PATH ?? "").split(path.delimiter).filter(entry => path.isAbsolute(entry))) {
|
|
@@ -61,8 +93,10 @@ async function resolveExecutable(options, env) {
|
|
|
61
93
|
return resolved;
|
|
62
94
|
if (path.basename(resolved) === "codex.js" && path.basename(path.dirname(resolved)) === "bin") {
|
|
63
95
|
const packageRoot = path.dirname(path.dirname(resolved));
|
|
64
|
-
const
|
|
65
|
-
|
|
96
|
+
const target = restrictedCodexNpmTarget(platform, arch);
|
|
97
|
+
if (!target)
|
|
98
|
+
throw new RestrictedCodexStop("codex_unavailable");
|
|
99
|
+
const { triple, packageName: nativePackage } = target;
|
|
66
100
|
const candidates = [];
|
|
67
101
|
try {
|
|
68
102
|
// Match the npm launcher's resolution: optional packages may be hoisted or
|
|
@@ -111,20 +145,21 @@ async function checkVersion(file, env, cwd, spawnFn, deadline) {
|
|
|
111
145
|
}
|
|
112
146
|
}
|
|
113
147
|
}
|
|
148
|
+
const rawCompactionTypes = ["compaction", "compaction_summary", "context_compaction"];
|
|
149
|
+
const analystRawItemTypes = ["message", "reasoning", ...rawCompactionTypes];
|
|
150
|
+
const participantRawItemTypes = [...analystRawItemTypes, "custom_tool_call", "custom_tool_call_output",
|
|
151
|
+
"function_call", "function_call_output"];
|
|
114
152
|
const unclosedChildren = new Set();
|
|
115
153
|
function retainUnclosedChild(closed) {
|
|
116
154
|
unclosedChildren.add(closed);
|
|
117
155
|
void closed.then(() => { unclosedChildren.delete(closed); });
|
|
118
156
|
}
|
|
119
|
-
/**
|
|
120
|
-
* new work, but ordinary participant turns may run concurrently. */
|
|
157
|
+
/** Analysts/readiness keep their one-shot lifetime; participants own a session. */
|
|
121
158
|
export async function runRestrictedCodexSession(request, options = {}, readinessOnly = false) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return restrictedCodexFailure("codex_busy");
|
|
127
|
-
return executeRestrictedCodexSession(request, options, readinessOnly);
|
|
159
|
+
const session = createRestrictedCodexSession(options);
|
|
160
|
+
const result = await session.run(request, readinessOnly);
|
|
161
|
+
return await session.close() ? result
|
|
162
|
+
: { ...restrictedCodexFailure("codex_cleanup_failed", result.dispatched, result.usage), failurePhase: "cleanup" };
|
|
128
163
|
}
|
|
129
164
|
async function writeRecoveryMarker(work, sourceEnv, reason) {
|
|
130
165
|
const cache = sourceEnv.XDG_CACHE_HOME ?? path.join(sourceEnv.HOME ?? homedir(), ".cache");
|
|
@@ -163,128 +198,187 @@ function hasScopedIdentity(method, params, threadId, turnId) {
|
|
|
163
198
|
&& (turnId === undefined || params.turnId === turnId);
|
|
164
199
|
return (params.threadId === undefined || params.threadId === threadId) && (params.turnId === undefined || params.turnId === turnId);
|
|
165
200
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
201
|
+
function usageDelta(total, baseline) {
|
|
202
|
+
const delta = { input: total.input - baseline.input, output: total.output - baseline.output,
|
|
203
|
+
cachedInput: (total.cachedInput ?? 0) - (baseline.cachedInput ?? 0),
|
|
204
|
+
cacheWriteInput: (total.cacheWriteInput ?? 0) - (baseline.cacheWriteInput ?? 0) };
|
|
205
|
+
return Object.values(delta).every(value => Number.isSafeInteger(value) && value >= 0)
|
|
206
|
+
&& delta.cachedInput + delta.cacheWriteInput <= delta.input ? delta : null;
|
|
207
|
+
}
|
|
208
|
+
export function createRestrictedCodexSession(options = {}) {
|
|
171
209
|
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
210
|
const sourceEnv = options.env ?? process.env;
|
|
211
|
+
const participant = options.participant, operatorAuth = participant?.authMode === "operator";
|
|
212
|
+
const reasoningEffort = participant?.reasoningEffort ?? "low";
|
|
176
213
|
const spawnFn = options.spawnFn ?? ((file, args, settings) => spawn(file, args, settings));
|
|
177
214
|
let work, authLink, transport;
|
|
178
|
-
let threadId,
|
|
179
|
-
let
|
|
180
|
-
let
|
|
181
|
-
let
|
|
182
|
-
let
|
|
183
|
-
let
|
|
184
|
-
|
|
185
|
-
let
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
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;
|
|
215
|
+
let threadId, cwd = "", scratch = "";
|
|
216
|
+
let identity;
|
|
217
|
+
let previousUsage = { input: 0, output: 0, cachedInput: 0, cacheWriteInput: 0 };
|
|
218
|
+
let pendingUsage;
|
|
219
|
+
let pendingInferenceUsage;
|
|
220
|
+
let resolvedModel;
|
|
221
|
+
let authentication;
|
|
222
|
+
let activeDeadline;
|
|
223
|
+
let interrupt;
|
|
224
|
+
let closed = false, cleanupTrusted = true;
|
|
225
|
+
let pending, closing, disposing;
|
|
226
|
+
const toolCallIds = new Set();
|
|
227
|
+
const dispose = () => {
|
|
228
|
+
closed = true;
|
|
229
|
+
return disposing ??= (async () => {
|
|
230
|
+
let cleaned = cleanupTrusted;
|
|
231
|
+
if (transport) {
|
|
232
|
+
cleaned = await transport.close(interrupt).catch(() => false) && cleaned;
|
|
233
|
+
if (!cleaned)
|
|
234
|
+
retainUnclosedChild(transport.owned.closed);
|
|
208
235
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
236
|
+
let authReplaced = false;
|
|
237
|
+
if (authLink && cleaned) {
|
|
238
|
+
try {
|
|
239
|
+
authReplaced = !(await lstat(authLink)).isSymbolicLink();
|
|
240
|
+
if (!authReplaced)
|
|
241
|
+
await unlink(authLink);
|
|
213
242
|
}
|
|
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 };
|
|
243
|
+
catch (error) {
|
|
244
|
+
if (codexRecord(error).code !== "ENOENT")
|
|
245
|
+
cleaned = false;
|
|
222
246
|
}
|
|
223
247
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
248
|
+
// Preserve unexpected login rotation for private recovery, never overwrite host auth.
|
|
249
|
+
if (authReplaced) {
|
|
250
|
+
cleaned = false;
|
|
251
|
+
if (work)
|
|
252
|
+
await preserveUnexpectedAuth(work, sourceEnv).catch(() => undefined);
|
|
253
|
+
}
|
|
254
|
+
if (work && cleaned)
|
|
255
|
+
await rm(work, { recursive: true, force: true }).catch(() => { cleaned = false; });
|
|
256
|
+
if (work && !cleaned && !authReplaced)
|
|
257
|
+
await writeRecoveryMarker(work, sourceEnv, "process_cleanup_unconfirmed").catch(() => undefined);
|
|
258
|
+
return cleaned;
|
|
259
|
+
})();
|
|
260
|
+
};
|
|
261
|
+
async function execute(request, readinessOnly) {
|
|
262
|
+
const deadline = new RestrictedCodexDeadline(request.timeoutMs, request.signal);
|
|
263
|
+
activeDeadline = deadline;
|
|
264
|
+
let turnId, earlyTurnId;
|
|
265
|
+
let dispatched = false, usage = null;
|
|
266
|
+
let completed = false, compacted = false;
|
|
267
|
+
let toolRequestPending = false;
|
|
268
|
+
let latestUsage = null;
|
|
269
|
+
let inferenceUsage = participant ? [] : null;
|
|
270
|
+
const requestUsageBaseline = previousUsage;
|
|
271
|
+
let generatedDeltaBytes = 0;
|
|
272
|
+
let selectedModel = identity?.model;
|
|
273
|
+
const allowedRawItemTypes = participant ? participantRawItemTypes : analystRawItemTypes;
|
|
274
|
+
let outputItem;
|
|
275
|
+
let result = restrictedCodexFailure("codex_process_failed");
|
|
276
|
+
let phase = "startup";
|
|
277
|
+
const early = [];
|
|
278
|
+
let resolveTurn;
|
|
279
|
+
const finished = new Promise(resolve => { resolveTurn = resolve; });
|
|
280
|
+
let resolveTurnReady;
|
|
281
|
+
const turnReady = new Promise(resolve => { resolveTurnReady = resolve; });
|
|
282
|
+
const handleTurnEvent = (method, params) => {
|
|
283
|
+
if (!hasScopedIdentity(method, params, threadId, turnId)) {
|
|
228
284
|
deadline.stop("codex_protocol_error");
|
|
229
285
|
return;
|
|
230
286
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
287
|
+
const item = codexRecord(params.item);
|
|
288
|
+
// CLI 0.154 thread totals omit compaction requests. Retain known usage,
|
|
289
|
+
// but never label it complete when native compaction occurred in this turn.
|
|
290
|
+
if (method === "thread/compacted" || item.type === "contextCompaction" || rawCompactionTypes.includes(String(item.type)))
|
|
291
|
+
compacted = true;
|
|
292
|
+
if (method === "rawResponseItem/completed") {
|
|
293
|
+
if (!allowedRawItemTypes.includes(String(item.type))) {
|
|
294
|
+
deadline.stop("codex_tool_call");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (item.type === "custom_tool_call" && item.name !== "exec") {
|
|
298
|
+
deadline.stop("codex_tool_call");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (item.type === "function_call" && item.name !== "wait") {
|
|
302
|
+
deadline.stop("codex_tool_call");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (item.type === "message" && Array.isArray(item.content)
|
|
306
|
+
&& item.content.some(content => codexRecord(content).type === "refusal"))
|
|
307
|
+
deadline.stop("refusal");
|
|
235
308
|
}
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
309
|
+
if (method === "thread/tokenUsage/updated") {
|
|
310
|
+
const total = restrictedCodexUsage(params.tokenUsage);
|
|
311
|
+
// App-server reports cumulative thread usage. Receipts must charge only this turn.
|
|
312
|
+
usage = total && requestUsageBaseline ? usageDelta(total, requestUsageBaseline) : null;
|
|
313
|
+
if (inferenceUsage !== null) {
|
|
314
|
+
const inferenceDelta = total && (latestUsage ?? requestUsageBaseline)
|
|
315
|
+
? usageDelta(total, (latestUsage ?? requestUsageBaseline)) : null;
|
|
316
|
+
if (!inferenceDelta)
|
|
317
|
+
inferenceUsage = null;
|
|
318
|
+
else if (Object.values(inferenceDelta).some(value => value > 0))
|
|
319
|
+
inferenceUsage.push(inferenceDelta);
|
|
320
|
+
}
|
|
321
|
+
pendingUsage = usage ?? undefined;
|
|
322
|
+
pendingInferenceUsage = inferenceUsage?.length ? inferenceUsage.map(item => ({ ...item })) : undefined;
|
|
323
|
+
latestUsage = total;
|
|
239
324
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
325
|
+
if (method === "item/started" || method === "item/completed") {
|
|
326
|
+
const allowedItems = participant
|
|
327
|
+
? ["userMessage", "agentMessage", "reasoning", "contextCompaction", "dynamicToolCall"]
|
|
328
|
+
: ["userMessage", "agentMessage", "reasoning", "contextCompaction"];
|
|
329
|
+
if (!allowedItems.includes(String(item.type))) {
|
|
330
|
+
deadline.stop("codex_tool_call");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (item.type === "dynamicToolCall" && (item.tool !== participant?.tool.name || item.namespace !== null
|
|
334
|
+
|| (method === "item/started" && item.status !== "inProgress")
|
|
335
|
+
|| (method === "item/completed" && (item.status !== "completed" || item.success !== true)))) {
|
|
336
|
+
deadline.stop("codex_tool_call");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (item.type === "agentMessage") {
|
|
340
|
+
if (item.delivery === "async" || (Array.isArray(item.questions) && item.questions.length > 0)) {
|
|
341
|
+
deadline.stop("codex_tool_call");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (method === "item/completed" && item.phase !== "commentary") {
|
|
345
|
+
if (typeof item.id !== "string" || typeof item.text !== "string" || Buffer.byteLength(item.text) > CODEX_MAX_OUTPUT_BYTES
|
|
346
|
+
|| (item.phase !== null && item.phase !== "final_answer") || (item.delivery !== null && item.delivery !== undefined)
|
|
347
|
+
|| (outputItem && (outputItem.id !== item.id || outputItem.text !== item.text))) {
|
|
348
|
+
deadline.stop("invalid_response");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
outputItem = { id: item.id, text: item.text };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
243
354
|
}
|
|
244
|
-
|
|
245
|
-
|
|
355
|
+
if (method === "turn/completed") {
|
|
356
|
+
const turn = codexRecord(params.turn);
|
|
357
|
+
if (turn.id !== turnId || completed || toolRequestPending) {
|
|
358
|
+
deadline.stop("codex_protocol_error");
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
completed = true;
|
|
362
|
+
if (turn.status === "interrupted") {
|
|
363
|
+
deadline.stop("cancelled");
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (turn.status !== "completed" || turn.error !== null || !outputItem) {
|
|
367
|
+
deadline.stop("invalid_response");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
pendingUsage = undefined;
|
|
372
|
+
pendingInferenceUsage = undefined;
|
|
373
|
+
resolveTurn({ status: "completed", output: JSON.parse(outputItem.text), usage,
|
|
374
|
+
usageComplete: usage !== null && !compacted, dispatched: true, errorCode: null });
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
deadline.stop("invalid_response");
|
|
378
|
+
}
|
|
246
379
|
}
|
|
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) => {
|
|
380
|
+
};
|
|
381
|
+
const onNotification = (method, params) => {
|
|
288
382
|
if (!dispatched)
|
|
289
383
|
return;
|
|
290
384
|
if (!hasScopedIdentity(method, params, threadId, turnId ?? earlyTurnId)) {
|
|
@@ -304,7 +398,9 @@ async function executeRestrictedCodexSession(request, options = {}, readinessOnl
|
|
|
304
398
|
}
|
|
305
399
|
const item = codexRecord(params.item);
|
|
306
400
|
// Tool requests must fail even if the turn-start acknowledgment is lost.
|
|
307
|
-
if ((method === "rawResponseItem/completed" && !
|
|
401
|
+
if ((method === "rawResponseItem/completed" && !allowedRawItemTypes.includes(String(item.type)))
|
|
402
|
+
|| (method === "rawResponseItem/completed" && item.type === "custom_tool_call" && item.name !== "exec")
|
|
403
|
+
|| (method === "rawResponseItem/completed" && item.type === "function_call" && item.name !== "wait")
|
|
308
404
|
|| (["item/started", "item/completed"].includes(method) && item.type === "agentMessage"
|
|
309
405
|
&& (item.delivery === "async" || (Array.isArray(item.questions) && item.questions.length > 0)))) {
|
|
310
406
|
deadline.stop("codex_tool_call");
|
|
@@ -314,118 +410,244 @@ async function executeRestrictedCodexSession(request, options = {}, readinessOnl
|
|
|
314
410
|
const value = codexRecord(params.turn).id;
|
|
315
411
|
if (typeof value !== "string" || value.length === 0 || (earlyTurnId !== undefined && value !== earlyTurnId))
|
|
316
412
|
deadline.stop("codex_protocol_error");
|
|
317
|
-
else
|
|
413
|
+
else {
|
|
318
414
|
earlyTurnId = value;
|
|
415
|
+
interrupt = { threadId: threadId, turnId: value };
|
|
416
|
+
}
|
|
319
417
|
}
|
|
320
418
|
if (turnId === undefined)
|
|
321
419
|
early.push({ method, params });
|
|
322
420
|
else
|
|
323
421
|
handleTurnEvent(method, params);
|
|
324
422
|
};
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
approvalPolicy: "never", sandbox: "read-only", model: request.model, modelProvider: "openai", allowProviderModelFallback: false,
|
|
346
|
-
environments: [], runtimeWorkspaceRoots: [], dynamicTools: [], baseInstructions: request.instructions, config: config.overrides });
|
|
347
|
-
if (!admitsRestrictedCodexThread(thread, request.model, cwd))
|
|
348
|
-
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
349
|
-
threadId = String(codexRecord(thread.thread).id);
|
|
350
|
-
phase = "mcpServerStatus/list";
|
|
351
|
-
const mcp = await transport.rpc("mcpServerStatus/list", { limit: 100 });
|
|
352
|
-
if (!Array.isArray(mcp.data) || mcp.data.length !== 0 || mcp.nextCursor !== null)
|
|
353
|
-
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
354
|
-
if (readinessOnly) {
|
|
355
|
-
result = { status: "completed", output: null, usage: null, usageComplete: false, dispatched: false, errorCode: null };
|
|
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()) {
|
|
361
|
-
deadline.check();
|
|
362
|
-
const match = CODEX_IMAGE.exec(image.dataUrl);
|
|
363
|
-
const imagePath = path.join(scratch, `evidence-${index}.${match[1] === "jpeg" ? "jpg" : match[1]}`);
|
|
364
|
-
await writeFile(imagePath, Buffer.from(match[2], "base64"), { mode: 0o600, flag: "wx" });
|
|
365
|
-
input.push({ type: "text", text: JSON.stringify({ captureEvidenceId: image.evidenceId }), text_elements: [] }, { type: "localImage", path: imagePath });
|
|
423
|
+
const onRequest = async (method, params) => {
|
|
424
|
+
const callId = params.callId;
|
|
425
|
+
if (!participant || method !== "item/tool/call" || params.threadId !== threadId || typeof params.turnId !== "string"
|
|
426
|
+
|| params.turnId.length === 0 || params.turnId.length > 200 || params.namespace !== null
|
|
427
|
+
|| params.tool !== participant.tool.name || typeof callId !== "string"
|
|
428
|
+
|| callId.length === 0 || callId.length > 200 || toolCallIds.has(callId) || toolRequestPending)
|
|
429
|
+
throw new RestrictedCodexStop("codex_tool_call");
|
|
430
|
+
// Mark the request outstanding before waiting for a same-chunk turn/start
|
|
431
|
+
// acknowledgment, so an early completion can never be accepted.
|
|
432
|
+
toolRequestPending = true;
|
|
433
|
+
const activeTurnId = turnId ?? earlyTurnId ?? await deadline.wait(turnReady);
|
|
434
|
+
if (params.turnId !== activeTurnId)
|
|
435
|
+
throw new RestrictedCodexStop("codex_tool_call");
|
|
436
|
+
toolCallIds.add(callId);
|
|
437
|
+
deadline.pause();
|
|
438
|
+
const text = await deadline.wait(participant.tool.call(params.arguments));
|
|
439
|
+
if (typeof text !== "string" || Buffer.byteLength(text) > CODEX_MAX_REQUEST_BYTES)
|
|
440
|
+
throw new RestrictedCodexStop("invalid_response");
|
|
441
|
+
try {
|
|
442
|
+
JSON.parse(text);
|
|
366
443
|
}
|
|
444
|
+
catch {
|
|
445
|
+
throw new RestrictedCodexStop("invalid_response");
|
|
446
|
+
}
|
|
447
|
+
return { success: true, contentItems: [{ type: "inputText", text }] };
|
|
448
|
+
};
|
|
449
|
+
try {
|
|
450
|
+
pendingUsage = undefined;
|
|
367
451
|
deadline.check();
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
452
|
+
const frameLimit = Math.max(participant ? CODEX_MAX_REQUEST_BYTES : CODEX_MAX_OUTPUT_BYTES, Buffer.byteLength(JSON.stringify({ instructions: request.instructions,
|
|
453
|
+
evidence: request.evidence, images: request.images, schema: request.schema })) + 1024 * 1024);
|
|
454
|
+
if (!transport) {
|
|
455
|
+
const file = await resolveExecutable(options, sourceEnv);
|
|
456
|
+
deadline.check();
|
|
457
|
+
work = await mkdtemp(path.join(options.tempRoot ?? tmpdir(), "humanish-codex-analysis-"));
|
|
458
|
+
await chmod(work, 0o700);
|
|
459
|
+
work = await realpath(work);
|
|
460
|
+
const home = path.join(work, "home");
|
|
461
|
+
cwd = path.join(work, "cwd");
|
|
462
|
+
scratch = path.join(work, "scratch");
|
|
463
|
+
for (const directory of [home, cwd, scratch])
|
|
464
|
+
await mkdir(directory, { mode: 0o700 });
|
|
465
|
+
const env = childEnvironment(sourceEnv, home, scratch);
|
|
466
|
+
await checkVersion(file, env, cwd, spawnFn, deadline);
|
|
467
|
+
const configMode = { participantCodeMode: participant !== undefined, reasoningEffort, operatorAuth };
|
|
468
|
+
const config = restrictedCodexConfig(request.model, configMode), configPath = path.join(home, "config.toml");
|
|
469
|
+
if (!operatorAuth) {
|
|
470
|
+
await writeFile(configPath, config.toml, { mode: 0o600, flag: "wx" });
|
|
471
|
+
const authHome = options.authHome ?? sourceEnv.CODEX_HOME ?? path.join(sourceEnv.HOME ?? homedir(), ".codex");
|
|
472
|
+
if (!path.isAbsolute(authHome))
|
|
473
|
+
throw new RestrictedCodexStop("codex_unsupported_auth");
|
|
474
|
+
let authFile;
|
|
475
|
+
try {
|
|
476
|
+
authFile = await realpath(path.join(authHome, "auth.json"));
|
|
477
|
+
if (!(await stat(authFile)).isFile())
|
|
478
|
+
throw new Error();
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
throw new RestrictedCodexStop("codex_login_required");
|
|
482
|
+
}
|
|
483
|
+
deadline.check();
|
|
484
|
+
authLink = path.join(home, "auth.json");
|
|
485
|
+
await symlink(authFile, authLink);
|
|
486
|
+
}
|
|
487
|
+
deadline.check();
|
|
488
|
+
const appServerArgs = ["app-server", "--strict-config", ...(operatorAuth ? configArguments(config.overrides) : [])];
|
|
489
|
+
const owned = ownCodexProcess(spawnFn(file, appServerArgs, {
|
|
490
|
+
cwd, env: operatorAuth ? { ...sourceEnv } : env, detached: false, stdio: ["pipe", "pipe", "pipe"]
|
|
491
|
+
}));
|
|
492
|
+
transport = new RestrictedCodexTransport(owned, deadline, frameLimit);
|
|
493
|
+
transport.onNotification = onNotification;
|
|
494
|
+
transport.onRequestComplete = () => { toolRequestPending = false; };
|
|
495
|
+
phase = "initialize";
|
|
496
|
+
const initialize = await transport.rpc("initialize", {
|
|
497
|
+
clientInfo: { name: "humanish_analysis", version: "1.0.0" }, capabilities: { experimentalApi: true }
|
|
498
|
+
});
|
|
499
|
+
if (typeof initialize.userAgent !== "string" || !initialize.userAgent.includes(`/0.154.0 `)
|
|
500
|
+
|| (!operatorAuth && initialize.codexHome !== home)
|
|
501
|
+
|| initialize.platformOs !== (platform === "darwin" ? "macos" : "linux") || initialize.platformFamily !== "unix")
|
|
502
|
+
throw new RestrictedCodexStop("codex_unsupported_version");
|
|
503
|
+
transport.notify("initialized", {});
|
|
504
|
+
phase = "config/read";
|
|
505
|
+
const effective = await transport.rpc("config/read", { includeLayers: true, cwd });
|
|
506
|
+
if (!admitsRestrictedCodexConfig(effective, configPath, request.model, configMode))
|
|
507
|
+
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
508
|
+
const configuredModel = codexRecord(effective.config).model;
|
|
509
|
+
selectedModel = typeof configuredModel === "string" ? configuredModel : undefined;
|
|
510
|
+
phase = "account/read";
|
|
511
|
+
const account = await transport.rpc("account/read", { refreshToken: false });
|
|
512
|
+
if (account.account === null)
|
|
513
|
+
throw new RestrictedCodexStop("codex_login_required");
|
|
514
|
+
const accountType = codexRecord(account.account).type;
|
|
515
|
+
if ((!operatorAuth && accountType !== "chatgpt") || (operatorAuth && !["chatgpt", "apiKey"].includes(String(accountType)))
|
|
516
|
+
|| account.requiresOpenaiAuth !== true)
|
|
517
|
+
throw new RestrictedCodexStop("codex_unsupported_auth");
|
|
518
|
+
authentication = accountType === "apiKey" ? "api-key" : "chatgpt-account";
|
|
519
|
+
const mcpServers = codexRecord(codexRecord(effective.config).mcp_servers);
|
|
520
|
+
const mcpNames = Object.keys(mcpServers);
|
|
521
|
+
// Request overrides are split literally on dots by Codex. Limit names to
|
|
522
|
+
// TOML bare-key characters so each override targets the inherited entry.
|
|
523
|
+
if (mcpNames.length > 100 || mcpNames.some(name => !/^[A-Za-z0-9_-]{1,200}$/.test(name)))
|
|
524
|
+
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
525
|
+
const threadConfig = { ...config.overrides };
|
|
526
|
+
for (const name of mcpNames)
|
|
527
|
+
threadConfig[`mcp_servers.${name}.enabled`] = false;
|
|
528
|
+
phase = "thread/start";
|
|
529
|
+
const thread = await transport.rpc("thread/start", { cwd, ephemeral: true, experimentalRawEvents: true,
|
|
530
|
+
approvalPolicy: "never", sandbox: "read-only", model: selectedModel, modelProvider: "openai", allowProviderModelFallback: false,
|
|
531
|
+
environments: [], runtimeWorkspaceRoots: [], dynamicTools: participant ? [{ type: "function", name: participant.tool.name,
|
|
532
|
+
description: participant.tool.description, inputSchema: participant.tool.inputSchema }] : [],
|
|
533
|
+
baseInstructions: request.instructions, config: threadConfig });
|
|
534
|
+
const returnedModel = codexRecord(thread.thread).model;
|
|
535
|
+
if (typeof returnedModel !== "string" || returnedModel.length === 0 || returnedModel.length > 200
|
|
536
|
+
|| (selectedModel !== undefined && returnedModel !== selectedModel)
|
|
537
|
+
|| !admitsRestrictedCodexThread(thread, returnedModel, cwd, reasoningEffort))
|
|
538
|
+
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
539
|
+
selectedModel = returnedModel;
|
|
540
|
+
resolvedModel = returnedModel;
|
|
541
|
+
threadId = String(codexRecord(thread.thread).id);
|
|
542
|
+
if (!operatorAuth) {
|
|
543
|
+
phase = "mcpServerStatus/list";
|
|
544
|
+
const mcp = await transport.rpc("mcpServerStatus/list", { limit: 100 });
|
|
545
|
+
if (!Array.isArray(mcp.data) || mcp.data.length !== 0 || mcp.nextCursor !== null)
|
|
546
|
+
throw new RestrictedCodexStop("codex_unsafe_configuration");
|
|
547
|
+
}
|
|
548
|
+
identity = { requestedModel: request.model, model: selectedModel, instructions: request.instructions };
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
transport.beginRequest(deadline, frameLimit);
|
|
552
|
+
transport.onNotification = onNotification;
|
|
553
|
+
transport.onRequestComplete = () => { toolRequestPending = false; };
|
|
407
554
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
555
|
+
if (readinessOnly) {
|
|
556
|
+
result = { status: "completed", output: null, usage: null, usageComplete: false, dispatched: false, errorCode: null };
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
phase = "turn/start";
|
|
560
|
+
if (participant)
|
|
561
|
+
transport.onRequest = onRequest;
|
|
562
|
+
const input = [{ type: "text", text: request.evidence, text_elements: [] }];
|
|
563
|
+
for (const [index, image] of request.images.entries()) {
|
|
564
|
+
deadline.check();
|
|
565
|
+
const match = CODEX_IMAGE.exec(image.dataUrl);
|
|
566
|
+
const imagePath = path.join(scratch, `evidence-${index}.${match[1] === "jpeg" ? "jpg" : match[1]}`);
|
|
567
|
+
await writeFile(imagePath, Buffer.from(match[2], "base64"), { mode: 0o600 });
|
|
568
|
+
input.push({ type: "text", text: JSON.stringify({ captureEvidenceId: image.evidenceId }), text_elements: [] }, { type: "localImage", path: imagePath });
|
|
569
|
+
}
|
|
570
|
+
deadline.check();
|
|
571
|
+
// Even a lost acknowledgment may have dispatched the request. Never claim zero cost.
|
|
572
|
+
dispatched = true;
|
|
573
|
+
const turn = await transport.rpc("turn/start", { threadId, cwd, approvalPolicy: "never", sandboxPolicy: { type: "readOnly" },
|
|
574
|
+
environments: [], runtimeWorkspaceRoots: [], effort: reasoningEffort, model: selectedModel, outputSchema: request.schema, input });
|
|
575
|
+
const returnedTurnId = codexRecord(turn.turn).id;
|
|
576
|
+
if (typeof returnedTurnId !== "string" || returnedTurnId.length === 0 || returnedTurnId.length > 200
|
|
577
|
+
|| (earlyTurnId !== undefined && earlyTurnId !== returnedTurnId))
|
|
578
|
+
throw new RestrictedCodexStop("codex_protocol_error");
|
|
579
|
+
turnId = returnedTurnId;
|
|
580
|
+
resolveTurnReady(turnId);
|
|
581
|
+
interrupt = { threadId: threadId, turnId };
|
|
582
|
+
for (const event of early)
|
|
583
|
+
handleTurnEvent(event.method, event.params);
|
|
584
|
+
early.length = 0;
|
|
585
|
+
phase = "response";
|
|
586
|
+
result = await deadline.wait(finished);
|
|
587
|
+
deadline.check();
|
|
588
|
+
previousUsage = latestUsage;
|
|
589
|
+
interrupt = undefined;
|
|
411
590
|
}
|
|
412
591
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
592
|
+
catch (error) {
|
|
593
|
+
const code = error instanceof RestrictedCodexStop ? error.code : "codex_process_failed";
|
|
594
|
+
result = { ...restrictedCodexFailure(code === "codex_cleanup_failed" ? code : deadline.code ?? code, dispatched, usage), failurePhase: phase };
|
|
595
|
+
}
|
|
596
|
+
finally {
|
|
597
|
+
pendingUsage = undefined;
|
|
598
|
+
pendingInferenceUsage = undefined;
|
|
599
|
+
deadline.close();
|
|
600
|
+
activeDeadline = undefined;
|
|
601
|
+
if (transport)
|
|
602
|
+
transport.onNotification = () => undefined;
|
|
603
|
+
if (transport)
|
|
604
|
+
transport.onRequest = undefined;
|
|
605
|
+
if (transport)
|
|
606
|
+
transport.onRequestComplete = undefined;
|
|
607
|
+
if (result.errorCode !== null) {
|
|
608
|
+
if (result.errorCode === "codex_cleanup_failed")
|
|
609
|
+
cleanupTrusted = false;
|
|
610
|
+
if (!await dispose())
|
|
611
|
+
result = { ...restrictedCodexFailure("codex_cleanup_failed", dispatched, usage), failurePhase: "cleanup" };
|
|
612
|
+
}
|
|
420
613
|
}
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
result = { ...restrictedCodexFailure("codex_cleanup_failed", dispatched, usage), failurePhase: "cleanup" };
|
|
614
|
+
if (result.errorCode !== null && result.failurePhase === undefined)
|
|
615
|
+
result = { ...result, failurePhase: phase };
|
|
616
|
+
if (participant && usage !== null && inferenceUsage !== null && inferenceUsage.length > 0)
|
|
617
|
+
result = { ...result, inferenceUsage: inferenceUsage.map(item => ({ ...item })) };
|
|
618
|
+
return result;
|
|
427
619
|
}
|
|
428
|
-
return
|
|
620
|
+
return {
|
|
621
|
+
get pendingUsage() { return pendingUsage; },
|
|
622
|
+
get pendingInferenceUsage() { return pendingInferenceUsage?.map(item => ({ ...item })); },
|
|
623
|
+
get resolvedModel() { return resolvedModel; },
|
|
624
|
+
get authentication() { return authentication; },
|
|
625
|
+
run(request, readinessOnly = false) {
|
|
626
|
+
const error = restrictedCodexRequestError(request, operatorAuth);
|
|
627
|
+
if (error)
|
|
628
|
+
return Promise.resolve(restrictedCodexFailure(error));
|
|
629
|
+
if (!validParticipant(participant))
|
|
630
|
+
return Promise.resolve(restrictedCodexFailure("invalid_request"));
|
|
631
|
+
if (closed || (identity && (identity.requestedModel !== request.model || identity.instructions !== request.instructions)))
|
|
632
|
+
return Promise.resolve(restrictedCodexFailure("invalid_request"));
|
|
633
|
+
if (pending || unclosedChildren.size)
|
|
634
|
+
return Promise.resolve(restrictedCodexFailure("codex_busy"));
|
|
635
|
+
const supportedPlatform = operatorAuth ? restrictedCodexNpmTarget(platform, arch) !== undefined
|
|
636
|
+
: (platform === "linux" && arch === "x64") || (platform === "darwin" && arch === "arm64");
|
|
637
|
+
if (!supportedPlatform)
|
|
638
|
+
return Promise.resolve(restrictedCodexFailure("codex_unsupported_platform"));
|
|
639
|
+
const task = execute(request, readinessOnly);
|
|
640
|
+
pending = task;
|
|
641
|
+
void task.finally(() => { if (pending === task)
|
|
642
|
+
pending = undefined; }).catch(() => undefined);
|
|
643
|
+
return task;
|
|
644
|
+
},
|
|
645
|
+
close() {
|
|
646
|
+
closed = true;
|
|
647
|
+
activeDeadline?.stop("cancelled");
|
|
648
|
+
return closing ??= (async () => { await pending; return dispose(); })();
|
|
649
|
+
}
|
|
650
|
+
};
|
|
429
651
|
}
|
|
430
652
|
export async function checkRestrictedCodexSessionReadiness(input = {}, options = {}) {
|
|
431
653
|
return runRestrictedCodexSession({ model: RESTRICTED_CODEX_ANALYSIS_MODELS[0], instructions: "Analyze only supplied study evidence.",
|