@openclaw/codex 2026.9.1-beta.1 → 2026.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/api.js +8 -9
  2. package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-Bp1ZOnk2.js} +105 -75
  3. package/dist/attempt-context-CSazkWns.js +634 -0
  4. package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
  5. package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-DxGJRYbY.js} +53 -57
  6. package/dist/{capabilities-BRMh0aIq.js → capabilities-B1mgvpP8.js} +2 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-DEsIufkt.js → command-formatters-DSbGMXA0.js} +5 -4
  9. package/dist/{command-handlers-D_-1_qRp.js → command-handlers-DOJvGv6-.js} +243 -148
  10. package/dist/{compact-BglNFZrQ.js → compact-Y_xBqrHN.js} +109 -65
  11. package/dist/{config-giW8X0QA.js → config-CyapYSji.js} +328 -374
  12. package/dist/config-utils-C57JiJrU.js +69 -0
  13. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  14. package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
  15. package/dist/doctor-contract-api.js +144 -14
  16. package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BwpvTxaX.js} +1119 -1135
  17. package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-CY9Jn4bA.js} +4 -3
  18. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-B9kp6iGC.js} +15 -6
  19. package/dist/event-projector-usage-ZqEVbAEY.js +212 -0
  20. package/dist/{harness-Csz0F-DK.js → harness-Czh2Sdoc.js} +65 -52
  21. package/dist/harness.js +1 -1
  22. package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
  23. package/dist/index.js +495 -457
  24. package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-CRh-51AK.js} +9 -21
  25. package/dist/{managed-binary-DFYK4DWq.js → managed-binary-DcpnJF9x.js} +117 -80
  26. package/dist/media-understanding-provider.js +1 -1
  27. package/dist/model-catalog-DT5vo6qo.js +101 -0
  28. package/dist/{models-TS3HBU-q.js → models-CcrgSMQR.js} +26 -29
  29. package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-B3bvQGcg.js} +63 -122
  30. package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-nfbUOtUB.js} +10 -7
  31. package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-aQVdyL5g.js} +30 -3
  32. package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-B7gHXJ3d.js} +44 -30
  33. package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BwCLTY_U.js} +6 -6
  34. package/dist/provider-capabilities-BpG9Ltc5.js +101 -0
  35. package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-Fx_l4eNa.js} +3 -2
  36. package/dist/{sandbox-guard-kuWTn9C5.js → request-C8H22rPi.js} +195 -2
  37. package/dist/{run-attempt-DYc0QCBF.js → run-attempt-BzthFQSM.js} +1616 -2634
  38. package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-Bh_gf6-t.js} +22 -15
  39. package/dist/{session-binding-Bcynfd-_.js → session-binding-4Ki04sW-.js} +282 -399
  40. package/dist/session-binding-record-CyaXZvTe.js +270 -0
  41. package/dist/{session-catalog-DaLKVtpH.js → session-catalog-B47uwekb.js} +812 -504
  42. package/dist/{session-cli-DMc4oS2w.js → session-cli-BjLhpmS1.js} +1 -1
  43. package/dist/session-history-DWmMqiqW.js +93 -0
  44. package/dist/session-history-read-LHldwvyh.js +36 -0
  45. package/dist/session-history-worker-runtime.js +48 -0
  46. package/dist/session-history.worker-zZVcx0ME.js +267 -0
  47. package/dist/session-history.worker.js +2 -0
  48. package/dist/session-initialization-BCFVnLL4.js +75 -0
  49. package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-BaueHmSj.js} +2 -2
  50. package/dist/session-retirement-DNdmYdFc.js +97 -0
  51. package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-DReTpGz6.js} +20 -4
  52. package/dist/settled-turn-context-BOM3Y1pl.js +45 -0
  53. package/dist/settled-turn-finalizer-BI-b1sry.js +115 -0
  54. package/dist/{shared-client-DKz23hHY.js → shared-client-CKhX2kFt.js} +3659 -847
  55. package/dist/{side-question-DjA_jsNe.js → side-question-B6DWAw1A.js} +168 -81
  56. package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DPK30jad.js} +2412 -2145
  57. package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-CbucAmmp.js} +27 -16
  58. package/dist/thread-policy-CTalhWFy.js +105 -0
  59. package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-BLgqkUc1.js} +61 -9
  60. package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-_njEB84z.js} +114 -110
  61. package/dist/transcript-mirror-attestation-Byl34PC-.js +76 -0
  62. package/dist/transport-stdio-CzTngV0j.js +728 -0
  63. package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
  64. package/dist/upstream-session-fork-1fwbNKzQ.js +886 -0
  65. package/dist/{usage-ROIaW3wo.js → usage-i9fv5-4m.js} +3 -3
  66. package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-DKQn67A7.js} +3 -3
  67. package/openclaw.plugin.json +1 -30
  68. package/package.json +6 -6
  69. package/dist/binding-connection-BMvPX73o.js +0 -45
  70. package/dist/command-rpc-CCeCkf_v.js +0 -90
  71. package/dist/event-projector-usage-Bk1I90Bg.js +0 -328
  72. package/dist/incognito-session-ET_ufDH-.js +0 -2
  73. package/dist/model-catalog-i9rNBthX.js +0 -48
  74. package/dist/protocol-validators-VeJxxJ0U.js +0 -2421
  75. package/dist/request-DXXGHko0.js +0 -157
  76. package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
  77. package/dist/session-retirement-CcNC3YSY.js +0 -36
  78. package/dist/settled-turn-finalizer-Dc3ApjU4.js +0 -328
  79. package/dist/thread-resume-BE9qNEW0.js +0 -30
  80. package/dist/transport-stdio-DhoGy33Z.js +0 -99
  81. package/dist/upstream-session-fork-DlaHxSLC.js +0 -326
@@ -0,0 +1,728 @@
1
+ import { p as normalizeCodexAppServerArgs } from "./managed-binary-DcpnJF9x.js";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { z } from "zod";
4
+ import { readFile, readdir } from "node:fs/promises";
5
+ import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
6
+ import { execFile, spawn } from "node:child_process";
7
+ import { once } from "node:events";
8
+ import { setImmediate } from "node:timers/promises";
9
+ import { finished } from "node:stream/promises";
10
+ //#region extensions/codex/src/app-server/transport-process-snapshot.ts
11
+ const PROCESS_COLUMNS = "pid=,ppid=,pgid=,stat=,lstart=";
12
+ const MAX_PROCESS_CONTAINMENT_MS$1 = 2e3;
13
+ const PROCESS_INSPECTION_MAX_BYTES = 8388608;
14
+ var ProcessInspectionError = class extends Error {
15
+ constructor(reason) {
16
+ const detail = {
17
+ deadline: "Process inspection exceeded its deadline. Retry when the host is responsive.",
18
+ permission: "Check process inspection permissions (/proc on Linux, ps on macOS), then retry.",
19
+ unavailable: "Process identity is unavailable or invalid. Check /proc on Linux or ps on macOS, then retry."
20
+ }[reason];
21
+ super(`Cannot inspect Codex processes. ${detail}`);
22
+ this.reason = reason;
23
+ this.name = "ProcessInspectionError";
24
+ }
25
+ };
26
+ function inspectionFailure(error) {
27
+ if (error instanceof ProcessInspectionError) return error;
28
+ const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
29
+ return new ProcessInspectionError(code === "ABORT_ERR" ? "deadline" : code === "EACCES" || code === "EPERM" || code === "ERR_ACCESS_DENIED" ? "permission" : "unavailable");
30
+ }
31
+ async function readCodexAppServerProcessSnapshot(deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS$1, pids) {
32
+ const selected = pids === void 0 ? void 0 : [.../* @__PURE__ */ new Set([process.pid, ...pids])];
33
+ const rows = process.platform === "linux" ? await readLinuxProcesses(selected, deadline) : await readProcesses(selected ? [
34
+ "-o",
35
+ PROCESS_COLUMNS,
36
+ "-p",
37
+ selected.join(",")
38
+ ] : ["-axo", PROCESS_COLUMNS], deadline, selected !== void 0);
39
+ if (selected && !rows.some((row) => row.pid === process.pid)) throw new ProcessInspectionError("unavailable");
40
+ return rows;
41
+ }
42
+ async function readCodexAppServerProcess(pid, deadline) {
43
+ return (process.platform === "linux" ? await readLinuxProcesses([pid], deadline) : await readProcesses([
44
+ "-o",
45
+ PROCESS_COLUMNS,
46
+ "-p",
47
+ String(pid)
48
+ ], deadline)).find((row) => row.pid === pid);
49
+ }
50
+ async function readCodexAppServerProcessCommand(observed, deadline) {
51
+ let output;
52
+ if (process.platform === "linux") {
53
+ let pending = false;
54
+ do {
55
+ const remainingMs = deadline - Date.now();
56
+ if (remainingMs <= 0) throw new ProcessInspectionError("deadline");
57
+ let command;
58
+ try {
59
+ command = await readFile(`/proc/${observed.pid}/cmdline`, {
60
+ encoding: "utf8",
61
+ signal: AbortSignal.timeout(remainingMs)
62
+ });
63
+ } catch (error) {
64
+ throw inspectionFailure(error);
65
+ }
66
+ if (!command || pending) {
67
+ const current = await readCodexAppServerProcess(observed.pid, deadline);
68
+ if (!current || current.startedAt !== observed.startedAt || current.ppid !== observed.ppid || current.pgid !== observed.pgid || current.state.startsWith("Z")) throw new ProcessInspectionError("unavailable");
69
+ }
70
+ output = command.split("\0").join(" ").trim();
71
+ pending = command.length === 0;
72
+ if (pending) await setImmediate();
73
+ } while (pending);
74
+ } else output = (await readProcessOutput([
75
+ "-o",
76
+ "command=",
77
+ "-p",
78
+ String(observed.pid)
79
+ ], deadline)).split("\n")[0]?.trim() ?? "";
80
+ if (Date.now() >= deadline) throw new ProcessInspectionError("deadline");
81
+ if (!output) throw new ProcessInspectionError("unavailable");
82
+ return output;
83
+ }
84
+ async function readProcesses(args, deadline, selected = false) {
85
+ return parseProcesses(await readProcessOutput(args, deadline), selected);
86
+ }
87
+ async function readProcessOutput(args, deadline) {
88
+ const remainingMs = deadline - Date.now();
89
+ if (remainingMs <= 0) throw new ProcessInspectionError("deadline");
90
+ return await new Promise((resolve, reject) => {
91
+ let settled = false;
92
+ const settle = (output) => {
93
+ if (settled) return;
94
+ settled = true;
95
+ clearTimeout(timer);
96
+ if (output instanceof ProcessInspectionError) reject(output);
97
+ else resolve(output);
98
+ };
99
+ const inspector = execFile("ps", args, {
100
+ encoding: "utf8",
101
+ maxBuffer: PROCESS_INSPECTION_MAX_BYTES,
102
+ env: {
103
+ ...process.env,
104
+ LC_ALL: "C",
105
+ TZ: "UTC"
106
+ }
107
+ }, (error, stdout) => {
108
+ settle(Date.now() >= deadline ? new ProcessInspectionError("deadline") : error ? inspectionFailure(error) : stdout);
109
+ });
110
+ const timer = setTimeout(() => {
111
+ settle(new ProcessInspectionError("deadline"));
112
+ inspector.stdout?.destroy();
113
+ inspector.stderr?.destroy();
114
+ inspector.kill("SIGKILL");
115
+ inspector.unref();
116
+ }, Math.max(1, remainingMs));
117
+ timer.unref?.();
118
+ }).catch((error) => {
119
+ throw inspectionFailure(error);
120
+ });
121
+ }
122
+ function parseProcesses(output, selected) {
123
+ const rows = [];
124
+ for (const line of output.split("\n")) {
125
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
126
+ if (!match) {
127
+ if (selected && line.trim()) throw new ProcessInspectionError("unavailable");
128
+ continue;
129
+ }
130
+ const pid = Number(match[1] ?? "");
131
+ const ppid = Number(match[2] ?? "");
132
+ const pgid = Number(match[3] ?? "");
133
+ const startedAt = (match[5] ?? "").trim().replace(/\s+/g, " ");
134
+ if (![
135
+ pid,
136
+ ppid,
137
+ pgid
138
+ ].every(Number.isSafeInteger) || pid <= 0 || ppid < 0 || pgid <= 0 || !startedAt) {
139
+ if (selected) throw new ProcessInspectionError("unavailable");
140
+ continue;
141
+ }
142
+ rows.push({
143
+ pid,
144
+ ppid,
145
+ pgid,
146
+ state: match[4] ?? "",
147
+ startedAt
148
+ });
149
+ }
150
+ return rows;
151
+ }
152
+ async function readLinuxProcesses(selected, deadline) {
153
+ const remainingMs = deadline - Date.now();
154
+ if (remainingMs <= 0) throw new ProcessInspectionError("deadline");
155
+ const options = {
156
+ encoding: "utf8",
157
+ signal: AbortSignal.timeout(remainingMs)
158
+ };
159
+ try {
160
+ const bootId = (await readFile("/proc/sys/kernel/random/boot_id", options)).trim();
161
+ if (!/^[a-f0-9-]{36}$/.test(bootId)) throw new ProcessInspectionError("unavailable");
162
+ const pids = selected === void 0 ? await readdir("/proc") : selected.map(String);
163
+ const rows = [];
164
+ let bytes = 0;
165
+ for (const entry of pids) {
166
+ if (!/^\d+$/.test(entry)) continue;
167
+ if (Date.now() >= deadline) throw new ProcessInspectionError("deadline");
168
+ const stat = await readFile(`/proc/${entry}/stat`, options).catch((error) => {
169
+ if (error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ESRCH")) return;
170
+ throw error;
171
+ });
172
+ if (stat === void 0) continue;
173
+ bytes += stat.length;
174
+ if (bytes > PROCESS_INSPECTION_MAX_BYTES) throw new ProcessInspectionError("unavailable");
175
+ const commEnd = stat.lastIndexOf(")");
176
+ const fields = stat.slice(commEnd + 1).trim().split(/\s+/);
177
+ const ppid = Number(fields[1]);
178
+ const pgid = Number(fields[2]);
179
+ const startTicks = fields[19];
180
+ if (commEnd < 0 || ![ppid, pgid].every(Number.isSafeInteger) || selected !== void 0 && (pgid <= 0 || ppid < 0) || !/^\d+$/.test(startTicks ?? "")) throw new ProcessInspectionError("unavailable");
181
+ if (pgid > 0) rows.push({
182
+ pid: Number(entry),
183
+ ppid,
184
+ pgid,
185
+ state: fields[0],
186
+ startedAt: `${bootId}:${startTicks}`
187
+ });
188
+ }
189
+ if (Date.now() >= deadline) throw new ProcessInspectionError("deadline");
190
+ return rows;
191
+ } catch (error) {
192
+ throw inspectionFailure(error);
193
+ }
194
+ }
195
+ //#endregion
196
+ //#region extensions/codex/src/app-server/transport-process-containment.ts
197
+ const MAX_CONTAINED_PROCESSES = 512;
198
+ const MAX_PROCESS_CONTAINMENT_MS = 2e3;
199
+ const MAX_PROCESS_QUIESCE_PASSES = 16;
200
+ async function terminateCodexAppServerDescendants(child) {
201
+ const contained = await containDescendants(child);
202
+ return contained === "exited" ? contained : contained?.resume;
203
+ }
204
+ /** A durable spawn fact, never a command-line match, selects the orphan root. */
205
+ async function terminateCodexAppServerOrphan(expected) {
206
+ const deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS;
207
+ const result = await containDescendants({
208
+ pid: expected.pid,
209
+ kill: (signal) => signalProcess(expected.pid, signal ?? "SIGTERM")
210
+ }, expected, deadline);
211
+ const contained = result === "exited" ? void 0 : result;
212
+ let gone = false;
213
+ try {
214
+ if (contained) {
215
+ const current = await readCodexAppServerProcess(expected.pid, deadline).catch(() => void 0);
216
+ if (current && isSameLiveRoot(current, contained.root, true)) signalProcess(current.pgid === current.pid ? -current.pid : current.pid, "SIGKILL");
217
+ }
218
+ while (Date.now() < deadline) {
219
+ const snapshot = await readCodexAppServerProcessSnapshot(deadline).catch(() => void 0);
220
+ if (!snapshot?.some((row) => row.pid === process.pid)) return false;
221
+ const current = snapshot.find((row) => row.pid === expected.pid);
222
+ if (!current || !hasSameIdentity(current, expected) || current.state.startsWith("Z")) {
223
+ gone = true;
224
+ return true;
225
+ }
226
+ if (!contained) return false;
227
+ await new Promise((resolve) => {
228
+ setTimeout(resolve, 20);
229
+ });
230
+ }
231
+ return false;
232
+ } finally {
233
+ if (contained && !gone) await signalSameRoot(contained.root, "SIGCONT", Date.now() + MAX_PROCESS_CONTAINMENT_MS);
234
+ }
235
+ }
236
+ async function containDescendants(child, expected, deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS) {
237
+ const rootPid = child.pid;
238
+ if (hasExited(child)) return "exited";
239
+ if (process.platform === "win32" || !rootPid || !child.kill) return;
240
+ const snapshot = await readCodexAppServerProcessSnapshot(deadline).catch(() => void 0);
241
+ if (!snapshot || Date.now() >= deadline) return;
242
+ const root = snapshot.find((row) => row.pid === rootPid);
243
+ if (!expected && (!root || root.state.startsWith("Z"))) return "exited";
244
+ if (!root || !(expected ? isSameLiveProcess(root, expected) : root.ppid === process.pid) || root.state.startsWith("Z")) return;
245
+ const initialDescendants = collectDescendants(snapshot, [rootPid]);
246
+ if (initialDescendants.length > MAX_CONTAINED_PROCESSES) return;
247
+ const stoppedDescendants = /* @__PURE__ */ new Map();
248
+ if (!await signalSameRoot(root, "SIGSTOP", deadline)) return;
249
+ let resumeRootOnUnwind = true;
250
+ try {
251
+ const descendants = await quiesceDescendants(root, initialDescendants, stoppedDescendants, deadline);
252
+ if (!descendants) return;
253
+ for (const descendant of descendants.toReversed()) {
254
+ if (Date.now() >= deadline) return;
255
+ if (!descendant.state.startsWith("Z")) {
256
+ if (!await signalSameProcess(descendant, "SIGKILL", deadline) || Date.now() >= deadline) return;
257
+ }
258
+ }
259
+ resumeRootOnUnwind = false;
260
+ let resumed = false;
261
+ return {
262
+ root,
263
+ resume: () => {
264
+ if (resumed) return;
265
+ resumed = true;
266
+ resumeTransportRoot(child, root, false);
267
+ }
268
+ };
269
+ } finally {
270
+ if (resumeRootOnUnwind) {
271
+ if (expected) {
272
+ const releaseDeadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS;
273
+ for (const descendant of stoppedDescendants.values()) await signalSameProcess(descendant, "SIGCONT", releaseDeadline);
274
+ await signalSameRoot(root, "SIGCONT", releaseDeadline);
275
+ } else {
276
+ for (const descendant of stoppedDescendants.values()) signalProcess(descendant.pid, "SIGCONT");
277
+ resumeTransportRoot(child, root, true);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ async function quiesceDescendants(root, initialDescendants, stopped, deadline) {
283
+ const provenByPid = new Map(initialDescendants.map((descendant) => [descendant.pid, descendant]));
284
+ const stopFailures = /* @__PURE__ */ new Map();
285
+ for (let pass = 0; pass < MAX_PROCESS_QUIESCE_PASSES; pass += 1) {
286
+ if (Date.now() >= deadline) return;
287
+ const snapshot = await readCodexAppServerProcessSnapshot(deadline).catch(() => void 0);
288
+ if (!snapshot || Date.now() >= deadline) return;
289
+ const currentRoot = snapshot.find((row) => row.pid === root.pid);
290
+ if (!currentRoot || !isSameLiveRoot(currentRoot, root)) return;
291
+ if (!isSameLiveRoot(currentRoot, root, true)) {
292
+ if (!await signalSameRoot(root, "SIGSTOP", deadline) || Date.now() >= deadline) return;
293
+ continue;
294
+ }
295
+ const snapshotByPid = new Map(snapshot.map((process) => [process.pid, process]));
296
+ const liveProven = [];
297
+ for (const proven of provenByPid.values()) {
298
+ const current = snapshotByPid.get(proven.pid);
299
+ if (!current) {
300
+ provenByPid.delete(proven.pid);
301
+ stopped.delete(identityKey(proven));
302
+ continue;
303
+ }
304
+ if (!hasSameIdentity(proven, current)) return;
305
+ provenByPid.set(current.pid, current);
306
+ const key = identityKey(current);
307
+ if (stopped.has(key)) stopped.set(key, current);
308
+ liveProven.push(current);
309
+ }
310
+ const descendants = collectDescendants(snapshot, [root.pid, ...liveProven.map(({ pid }) => pid)]);
311
+ for (const descendant of descendants) {
312
+ const proven = provenByPid.get(descendant.pid);
313
+ if (proven && !hasSameIdentity(proven, descendant)) return;
314
+ provenByPid.set(descendant.pid, descendant);
315
+ }
316
+ if (provenByPid.size > MAX_CONTAINED_PROCESSES) return;
317
+ const quiescenceTargets = new Map(liveProven.map((process) => [process.pid, process]));
318
+ for (const descendant of descendants) quiescenceTargets.set(descendant.pid, descendant);
319
+ let allStopped = true;
320
+ for (const descendant of quiescenceTargets.values()) {
321
+ if (Date.now() >= deadline) return;
322
+ if (isStoppedState(descendant.state)) continue;
323
+ const stopQueued = await signalSameProcess(descendant, "SIGSTOP", deadline);
324
+ if (Date.now() >= deadline) return;
325
+ if (stopQueued) {
326
+ stopFailures.delete(identityKey(descendant));
327
+ stopped.set(identityKey(descendant), descendant);
328
+ } else {
329
+ const key = identityKey(descendant);
330
+ const failures = (stopFailures.get(key) ?? 0) + 1;
331
+ if (failures >= 2) return;
332
+ stopFailures.set(key, failures);
333
+ }
334
+ if (!isUninterruptibleState(descendant.state) || !stopQueued) allStopped = false;
335
+ }
336
+ if (allStopped) return [...provenByPid.values()];
337
+ }
338
+ }
339
+ function collectDescendants(snapshot, rootPids) {
340
+ const childrenByParent = /* @__PURE__ */ new Map();
341
+ for (const row of snapshot) {
342
+ const children = childrenByParent.get(row.ppid) ?? [];
343
+ children.push(row);
344
+ childrenByParent.set(row.ppid, children);
345
+ }
346
+ const descendants = [];
347
+ const pending = [...new Set(rootPids)];
348
+ const seen = new Set(pending);
349
+ for (const parentPid of pending) for (const child of childrenByParent.get(parentPid) ?? []) {
350
+ if (seen.has(child.pid)) continue;
351
+ seen.add(child.pid);
352
+ descendants.push(child);
353
+ pending.push(child.pid);
354
+ }
355
+ return descendants;
356
+ }
357
+ function isStoppedState(state) {
358
+ return state.startsWith("T") || state.startsWith("t") || state.startsWith("Z");
359
+ }
360
+ function isQuiescedState(state) {
361
+ return isStoppedState(state) || isUninterruptibleState(state);
362
+ }
363
+ function isUninterruptibleState(state) {
364
+ return state.startsWith("D") || state.startsWith("U");
365
+ }
366
+ function isSameLiveProcess(current, expected) {
367
+ return current.pgid === expected.pgid && !current.state.startsWith("Z") && hasSameIdentity(current, expected);
368
+ }
369
+ function isSameLiveRoot(current, expected, requireStopped = false) {
370
+ return current.ppid === expected.ppid && (!requireStopped || isQuiescedState(current.state)) && isSameLiveProcess(current, expected);
371
+ }
372
+ async function signalSameRoot(root, signal, deadline) {
373
+ const current = await readCodexAppServerProcess(root.pid, deadline).catch(() => void 0);
374
+ return Boolean(current && isSameLiveRoot(current, root) && signalProcess(current.pid, signal));
375
+ }
376
+ function resumeTransportRoot(child, root, allowSynchronousPidFallback) {
377
+ try {
378
+ if (child.kill) {
379
+ child.kill("SIGCONT");
380
+ return;
381
+ }
382
+ } catch {
383
+ if (!allowSynchronousPidFallback) return;
384
+ }
385
+ if (allowSynchronousPidFallback) signalProcess(root.pid, "SIGCONT");
386
+ }
387
+ async function signalSameProcess(expected, signal, deadline) {
388
+ const current = await readCodexAppServerProcess(expected.pid, deadline).catch(() => void 0);
389
+ return Boolean(current && isSameLiveProcess(current, expected) && signalProcess(current.pid, signal));
390
+ }
391
+ function hasSameIdentity(left, right) {
392
+ return identityKey(left) === identityKey(right);
393
+ }
394
+ function identityKey(row) {
395
+ return `${row.pid}\0${row.startedAt}`;
396
+ }
397
+ function hasExited(child) {
398
+ return child.exitCode != null || child.signalCode != null;
399
+ }
400
+ function signalProcess(pid, signal) {
401
+ try {
402
+ process.kill(pid, signal);
403
+ return true;
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+ //#endregion
409
+ //#region extensions/codex/src/app-server/transport-process-registration.ts
410
+ const PROCESS_REGISTRATION_INSPECTION_MS = 1e4;
411
+ const processIdentity = z.object({
412
+ pid: z.number().int().positive().safe(),
413
+ pgid: z.number().int().positive().safe(),
414
+ startedAt: z.string().min(1).max(64)
415
+ });
416
+ const childIdentity = processIdentity.extend({ commandFingerprint: z.string().regex(/^[a-f0-9]{64}$/).optional() });
417
+ const registrationSchema = z.object({
418
+ parent: processIdentity,
419
+ child: childIdentity
420
+ }).strict();
421
+ function fingerprintProcessCommand(command) {
422
+ return createHash("sha256").update(command).digest("hex");
423
+ }
424
+ async function openProcessRegistrationStore() {
425
+ const { createPluginStateSyncKeyedStore } = await import("openclaw/plugin-sdk/plugin-state-store-runtime");
426
+ return createPluginStateSyncKeyedStore("codex", {
427
+ namespace: "app-server-processes",
428
+ maxEntries: 512,
429
+ overflowPolicy: "reject-new"
430
+ });
431
+ }
432
+ async function reapRegisteredCodexAppServerOrphans() {
433
+ const store = await openProcessRegistrationStore();
434
+ const deadline = Date.now() + PROCESS_REGISTRATION_INSPECTION_MS;
435
+ for (const entry of store.entries()) {
436
+ if (Date.now() >= deadline) throw new Error("Codex orphan cleanup exceeded its startup budget. Retry to finish cleanup.");
437
+ const registration = registrationSchema.parse(entry.value);
438
+ const snapshot = await readCodexAppServerProcessSnapshot(deadline, [registration.parent.pid, registration.child.pid]);
439
+ const parent = snapshot.find((row) => row.pid === registration.parent.pid);
440
+ if (parent?.startedAt === registration.parent.startedAt && !parent.state.startsWith("Z")) continue;
441
+ const child = snapshot.find((row) => row.pid === registration.child.pid);
442
+ if (registration.child.commandFingerprint !== void 0 && child?.startedAt === registration.child.startedAt && !child.state.startsWith("Z")) {
443
+ let command;
444
+ try {
445
+ command = await readCodexAppServerProcessCommand(child, deadline);
446
+ } catch (error) {
447
+ if ((await readCodexAppServerProcessSnapshot(deadline, [registration.child.pid])).find((row) => row.pid === registration.child.pid)?.startedAt === registration.child.startedAt) throw error;
448
+ }
449
+ if (command !== void 0 && fingerprintProcessCommand(command) !== registration.child.commandFingerprint) {
450
+ store.delete(entry.key);
451
+ continue;
452
+ }
453
+ }
454
+ if (!await terminateCodexAppServerOrphan(registration.child)) throw new Error(`Cannot reap registered Codex process ${registration.child.pid}. Stop it before retrying.`);
455
+ store.delete(entry.key);
456
+ }
457
+ }
458
+ function createCodexAppServerProcessReaperService() {
459
+ return {
460
+ id: "codex-app-server-process-reaper",
461
+ start(ctx) {
462
+ if (process.platform === "win32") return;
463
+ (async () => {
464
+ try {
465
+ await reapRegisteredCodexAppServerOrphans();
466
+ } catch (error) {
467
+ ctx.logger.warn(`Codex app-server orphan cleanup failed: ${String(error)}`);
468
+ }
469
+ })();
470
+ }
471
+ };
472
+ }
473
+ /** Reap previous owners before spawn; commit this child's identity before initialization. */
474
+ async function prepareCodexAppServerProcessRegistration() {
475
+ if (process.platform === "win32") return async (child) => {
476
+ await once(child, "spawn");
477
+ };
478
+ await reapRegisteredCodexAppServerOrphans();
479
+ const store = await openProcessRegistrationStore();
480
+ return async (child) => {
481
+ await once(child, "spawn");
482
+ if (!child.pid) throw new ProcessInspectionError("unavailable");
483
+ const deadline = Date.now() + PROCESS_REGISTRATION_INSPECTION_MS;
484
+ const snapshot = await readCodexAppServerProcessSnapshot(deadline, [child.pid]);
485
+ const parent = snapshot.find((row) => row.pid === process.pid);
486
+ const spawned = snapshot.find((row) => row.pid === child.pid);
487
+ if (!parent || !spawned || spawned.ppid !== process.pid) throw new Error("Cannot register the Codex child process: its direct-parent identity is unavailable. Retry.");
488
+ const command = await readCodexAppServerProcessCommand(spawned, deadline);
489
+ if (child.exitCode !== null || child.signalCode !== null) throw new Error("Cannot register the Codex child process command: the child exited during inspection. Retry.");
490
+ const key = randomUUID();
491
+ store.register(key, {
492
+ parent: processIdentity.parse(parent),
493
+ child: childIdentity.parse({
494
+ ...spawned,
495
+ commandFingerprint: fingerprintProcessCommand(command)
496
+ })
497
+ });
498
+ child.once("exit", () => {
499
+ try {
500
+ store.delete(key);
501
+ } catch {}
502
+ });
503
+ };
504
+ }
505
+ //#endregion
506
+ //#region extensions/codex/src/app-server/transport.ts
507
+ /**
508
+ * Shared transport lifecycle helpers for stdio and WebSocket Codex app-server
509
+ * connections.
510
+ */
511
+ const CODEX_APP_SERVER_TRANSPORT_CLOSES = /* @__PURE__ */ new WeakMap();
512
+ /** True only after bounded settlement proves an exit that cleanup did not cause. */
513
+ function hasCodexAppServerNaturalExit(child) {
514
+ return CODEX_APP_SERVER_TRANSPORT_CLOSES.get(child)?.naturalExit === true;
515
+ }
516
+ /** Starts graceful transport shutdown and schedules a force kill fallback. */
517
+ function closeCodexAppServerTransport(child, options = {}) {
518
+ beginCodexAppServerTransportClose(child, options).closing;
519
+ }
520
+ function beginCodexAppServerTransportClose(child, options) {
521
+ const current = CODEX_APP_SERVER_TRANSPORT_CLOSES.get(child);
522
+ if (current) return current;
523
+ const closure = {
524
+ closing: (async () => {
525
+ if (hasCodexAppServerTransportExited(child)) return true;
526
+ if (process.platform === "win32" || !child.pid || !child.kill) {
527
+ finishCodexAppServerTransportClose(child, options);
528
+ return false;
529
+ }
530
+ let resumeRoot;
531
+ try {
532
+ const contained = await terminateCodexAppServerDescendants(child);
533
+ if (contained === "exited") return true;
534
+ resumeRoot = contained;
535
+ } catch {
536
+ resumeRoot = void 0;
537
+ }
538
+ try {
539
+ finishCodexAppServerTransportClose(child, options, resumeRoot);
540
+ } catch {
541
+ signalCodexAppServerTransport(child, "SIGKILL");
542
+ }
543
+ return false;
544
+ })(),
545
+ naturalExit: false
546
+ };
547
+ CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, closure);
548
+ return closure;
549
+ }
550
+ function finishCodexAppServerTransportClose(child, options, resumeRoot) {
551
+ const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
552
+ const forceKill = setTimeout(() => {
553
+ if (hasCodexAppServerTransportExited(child)) return;
554
+ signalCodexAppServerTransport(child, "SIGKILL");
555
+ }, Math.max(1, forceKillDelayMs));
556
+ forceKill.unref?.();
557
+ child.once("exit", () => {
558
+ clearTimeout(forceKill);
559
+ if (!options.drainStdio) {
560
+ child.stdout.destroy?.();
561
+ child.stderr.destroy?.();
562
+ }
563
+ });
564
+ try {
565
+ child.stdin.end?.();
566
+ child.stdin.destroy?.();
567
+ } finally {
568
+ resumeRoot?.();
569
+ }
570
+ child.unref?.();
571
+ child.stdout.unref?.();
572
+ child.stderr.unref?.();
573
+ child.stdin.unref?.();
574
+ }
575
+ /** Closes a transport and waits briefly for an exit event. */
576
+ async function closeCodexAppServerTransportAndWait(child, options = {}) {
577
+ const drained = options.drainStdio ? Promise.all([child.stdout, child.stderr].map((stream) => finished(stream, { cleanup: true }))).then(() => true, () => false) : void 0;
578
+ const closure = beginCodexAppServerTransportClose(child, options);
579
+ const naturalExit = await closure.closing;
580
+ const settled = await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3, drained);
581
+ closure.naturalExit = naturalExit && settled;
582
+ if (options.drainStdio) {
583
+ child.stdout.destroy?.();
584
+ child.stderr.destroy?.();
585
+ }
586
+ return settled;
587
+ }
588
+ function hasCodexAppServerTransportExited(child) {
589
+ return child.exitCode !== null && child.exitCode !== void 0 ? true : child.signalCode !== null && child.signalCode !== void 0;
590
+ }
591
+ async function waitForCodexAppServerTransportExit(child, timeoutMs, drained) {
592
+ return await new Promise((resolve) => {
593
+ let settled = false;
594
+ const finish = (exited) => {
595
+ if (settled) return;
596
+ settled = true;
597
+ clearTimeout(timeout);
598
+ child.off?.("exit", onExit);
599
+ resolve(exited);
600
+ };
601
+ const onExit = () => {
602
+ if (drained) drained.then(finish);
603
+ else finish(true);
604
+ };
605
+ const timeout = setTimeout(() => finish(false), Math.max(1, timeoutMs));
606
+ child.once("exit", onExit);
607
+ if (hasCodexAppServerTransportExited(child)) onExit();
608
+ });
609
+ }
610
+ function signalCodexAppServerTransport(child, signal) {
611
+ if (child.pid && process.platform !== "win32") try {
612
+ process.kill(-child.pid, signal);
613
+ return;
614
+ } catch {}
615
+ child.kill?.(signal);
616
+ }
617
+ //#endregion
618
+ //#region extensions/codex/src/app-server/transport-stdio.ts
619
+ /**
620
+ * Creates and configures stdio-backed Codex app-server transports, including
621
+ * Windows spawn normalization and environment filtering.
622
+ */
623
+ const UNSAFE_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
624
+ "__proto__",
625
+ "constructor",
626
+ "prototype"
627
+ ]);
628
+ const RUNTIME_INJECTION_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
629
+ "NODE_PATH",
630
+ "LD_AUDIT",
631
+ "LD_LIBRARY_PATH",
632
+ "LD_PRELOAD"
633
+ ]);
634
+ const QA_PARENT_PID_ENV = "OPENCLAW_QA_PARENT_PID";
635
+ const DEFAULT_SPAWN_RUNTIME = {
636
+ platform: process.platform,
637
+ env: process.env,
638
+ execPath: process.execPath
639
+ };
640
+ /** Resolves the concrete command/argv/shell settings used to spawn Codex app-server. */
641
+ function resolveCodexAppServerSpawnInvocation(options, runtime = DEFAULT_SPAWN_RUNTIME) {
642
+ if (options.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
643
+ const program = resolveWindowsSpawnProgram({
644
+ command: options.command,
645
+ platform: runtime.platform,
646
+ env: runtime.env,
647
+ execPath: runtime.execPath,
648
+ packageName: "@openai/codex"
649
+ });
650
+ const args = normalizeCodexAppServerArgs(options.args);
651
+ const resolved = materializeWindowsSpawnProgram(program, args);
652
+ return {
653
+ command: resolved.command,
654
+ args: resolved.argv,
655
+ shell: resolved.shell,
656
+ windowsHide: resolved.windowsHide
657
+ };
658
+ }
659
+ /** Merges app-server environment overrides while honoring clearEnv and unsafe key filtering. */
660
+ function resolveCodexAppServerSpawnEnv(options, baseEnv = process.env, platform = process.platform) {
661
+ const env = Object.create(null);
662
+ copySafeEnvironmentEntries(env, baseEnv);
663
+ copySafeEnvironmentEntries(env, options.env ?? {});
664
+ const keysToClear = normalizedEnvironmentKeys(options.clearEnv ?? []);
665
+ if (platform === "win32") {
666
+ const lowerCaseKeysToClear = new Set(keysToClear.map((key) => key.toLowerCase()));
667
+ for (const candidate of Object.keys(env)) if (lowerCaseKeysToClear.has(candidate.toLowerCase())) delete env[candidate];
668
+ } else for (const key of keysToClear) delete env[key];
669
+ for (const key of Object.keys(env)) if (isCodexRuntimeInjectionEnvironmentKey(key)) delete env[key];
670
+ return env;
671
+ }
672
+ function isCodexRuntimeInjectionEnvironmentKey(rawKey) {
673
+ const key = rawKey.toUpperCase();
674
+ return RUNTIME_INJECTION_ENVIRONMENT_KEYS.has(key) || key.startsWith("DYLD_");
675
+ }
676
+ /** Keeps QA-owned app-server processes inside the gateway process-group cleanup boundary. */
677
+ function resolveCodexAppServerDetachedMode(env, platform = process.platform) {
678
+ return platform !== "win32" && !env[QA_PARENT_PID_ENV]?.trim();
679
+ }
680
+ function normalizedEnvironmentKeys(rawKeys) {
681
+ const keys = [];
682
+ for (const rawKey of rawKeys) {
683
+ const key = rawKey.trim();
684
+ if (key.length > 0) keys.push(key);
685
+ }
686
+ return keys;
687
+ }
688
+ function copySafeEnvironmentEntries(target, source) {
689
+ for (const [key, value] of Object.entries(source)) {
690
+ if (UNSAFE_ENVIRONMENT_KEYS.has(key)) continue;
691
+ target[key] = value;
692
+ }
693
+ }
694
+ /** Spawns the Codex app-server process and returns the shared transport interface. */
695
+ async function createStdioTransport(options, baseEnv = process.env, assertCurrent, onSpawn) {
696
+ const env = resolveCodexAppServerSpawnEnv(options, baseEnv);
697
+ const invocation = resolveCodexAppServerSpawnInvocation(options, {
698
+ platform: process.platform,
699
+ env,
700
+ execPath: process.execPath
701
+ });
702
+ const register = await prepareCodexAppServerProcessRegistration();
703
+ assertCurrent?.();
704
+ const child = spawn(invocation.command, invocation.args, {
705
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
706
+ env,
707
+ detached: resolveCodexAppServerDetachedMode(env),
708
+ shell: invocation.shell,
709
+ stdio: [
710
+ "pipe",
711
+ "pipe",
712
+ "pipe"
713
+ ],
714
+ windowsHide: invocation.windowsHide
715
+ });
716
+ try {
717
+ onSpawn?.(child);
718
+ await register(child);
719
+ assertCurrent?.();
720
+ return child;
721
+ } catch (error) {
722
+ await closeCodexAppServerTransportAndWait(child, { drainStdio: true });
723
+ assertCurrent?.();
724
+ throw error;
725
+ }
726
+ }
727
+ //#endregion
728
+ export { hasCodexAppServerNaturalExit as a, closeCodexAppServerTransportAndWait as i, resolveCodexAppServerSpawnEnv as n, createCodexAppServerProcessReaperService as o, closeCodexAppServerTransport as r, createStdioTransport as t };