@linzumi/cli 1.0.106 → 1.0.107
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/README.md +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/scripts/build-harness-codex-parity-oracle.mjs +465 -0
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1473,7 +1473,7 @@ To kick the agent off:
|
|
|
1473
1473
|
`,L=Buffer.byteLength(P,"utf8");if(L>s)return l("runner_diagnostics.spool_entry_dropped",{reason:"entry_too_large",entry_bytes:L,max_entry_bytes:s}),!1;_Se(n,{recursive:!0}),m(u)+L>i&&f(),_(L);let D=w()?`
|
|
1474
1474
|
`:"";return gSe(u,`${D}${P}`,"utf8"),!0}catch(R){return l("runner_diagnostics.spool_failed",{error:R instanceof Error?R.message:String(R)}),!1}},v=k=>{let R=[],P=wSe(k,"utf8");for(let L of P.split(`
|
|
1475
1475
|
`))if(L.trim()!=="")try{let D=JSON.parse(L);D!==null&&typeof D=="object"&&R.push({parsed:D,raw:L})}catch{}return R};return{append:S,drain:async k=>{if(c)return{uploadedBatches:0,uploadedEntries:0,remainingBatches:p().length,skipped:!0};c=!0;let R=0,P=0;try{f(),_(0);let L=a()-o;for(let D of p()){let j;try{j=v(D)}catch(Z){l("runner_diagnostics.spool_batch_unreadable",{file:D,error:Z instanceof Error?Z.message:String(Z)}),y(D,"unreadable");continue}let q=j.filter(Z=>{let me=FSe(Z.parsed);return me===void 0||me>=L});if(q.length===0){y(D,"empty_or_expired");continue}let E=uP(D,".jsonl"),B=q.map(Z=>Z.raw).join(`
|
|
1476
|
-
`),z=q.map(Z=>Z.parsed.spooled_at).filter(Z=>typeof Z=="string"),W={bundle_name:"runner-diagnostics-spool",exported_at:new Date(a()).toISOString(),idempotency_key:E,diagnostics:{kind:"runner_diagnostic_log",spool_batch:!0,idempotency_key:E,entry_count:q.length,...z.length===0?{}:{first_spooled_at:z[0],last_spooled_at:z[z.length-1]}},telemetry_ndjson:B};try{await k(W)}catch(Z){l("runner_diagnostics.spool_upload_failed",{file:D,remaining_batches:p().length,error:Z instanceof Error?Z.message:String(Z)});break}R+=1,P+=q.length;try{k9(D)}catch(Z){l("runner_diagnostics.spool_delete_failed",{file:D,error:Z instanceof Error?Z.message:String(Z)})}}}catch(L){l("runner_diagnostics.spool_drain_failed",{error:L instanceof Error?L.message:String(L)})}finally{c=!1}return{uploadedBatches:R,uploadedEntries:P,remainingBatches:p().length,skipped:!1}},pendingBatchFiles:p}}var PSe,ISe,RSe,ESe,DSe,C9,ASe,$Se,P9=F(()=>{"use strict";ix();PSe=32*1024*1024,ISe=10080*60*1e3,RSe=4*1024*1024,ESe=1024*1024,DSe="spool.jsonl",C9=/^batch-(\d{1,16})-[0-9a-f-]+\.jsonl$/;ASe=/\b([A-Z][A-Z0-9_]{1,})=("[^"\n]*"|'[^'\n]*'|[^\s"'`;,)\]}]+)/g,$Se=/(?:\/(?:[A-Za-z0-9._@~+-]|%[0-9A-Fa-f]{2})+){2,}/g});var qt,lP,ea=F(()=>{"use strict";qt="1.0.
|
|
1476
|
+
`),z=q.map(Z=>Z.parsed.spooled_at).filter(Z=>typeof Z=="string"),W={bundle_name:"runner-diagnostics-spool",exported_at:new Date(a()).toISOString(),idempotency_key:E,diagnostics:{kind:"runner_diagnostic_log",spool_batch:!0,idempotency_key:E,entry_count:q.length,...z.length===0?{}:{first_spooled_at:z[0],last_spooled_at:z[z.length-1]}},telemetry_ndjson:B};try{await k(W)}catch(Z){l("runner_diagnostics.spool_upload_failed",{file:D,remaining_batches:p().length,error:Z instanceof Error?Z.message:String(Z)});break}R+=1,P+=q.length;try{k9(D)}catch(Z){l("runner_diagnostics.spool_delete_failed",{file:D,error:Z instanceof Error?Z.message:String(Z)})}}}catch(L){l("runner_diagnostics.spool_drain_failed",{error:L instanceof Error?L.message:String(L)})}finally{c=!1}return{uploadedBatches:R,uploadedEntries:P,remainingBatches:p().length,skipped:!1}},pendingBatchFiles:p}}var PSe,ISe,RSe,ESe,DSe,C9,ASe,$Se,P9=F(()=>{"use strict";ix();PSe=32*1024*1024,ISe=10080*60*1e3,RSe=4*1024*1024,ESe=1024*1024,DSe="spool.jsonl",C9=/^batch-(\d{1,16})-[0-9a-f-]+\.jsonl$/;ASe=/\b([A-Z][A-Z0-9_]{1,})=("[^"\n]*"|'[^'\n]*'|[^\s"'`;,)\]}]+)/g,$Se=/(?:\/(?:[A-Za-z0-9._@~+-]|%[0-9A-Fa-f]{2})+){2,}/g});var qt,lP,ea=F(()=>{"use strict";qt="1.0.107",lP=`linzumi ${qt}`});import{closeSync as R9,existsSync as fP,mkdirSync as qSe,openSync as E9,readFileSync as D9,renameSync as zSe,unlinkSync as yd,writeFileSync as NSe,writeSync as M9}from"node:fs";import{dirname as A9,join as USe}from"node:path";function mP(e){return e instanceof gs||e instanceof Error&&e.name===O9&&"lockPath"in e&&"heldBy"in e}function hP(e,n=Wn(),t){let r=t===void 0?encodeURIComponent(e):ud(t);return USe(A9(n),"runners",`${r}.lock`)}function g0(e){let n=hP(e.machineId,e.configPath,e.linzumiUrl),t=e.isPidAlive??U9,r=e.now??(()=>new Date),o=e.killPid??nke,i=r().toISOString(),s={version:1,machineId:e.machineId,runnerId:e.runnerId,pid:e.pid??process.pid,cwd:e.cwd,workspace:e.workspace,...e.linzumiUrl===void 0?{}:{linzumiUrl:qn(e.linzumiUrl)},...e.launchSource===void 0?{}:{launchSource:e.launchSource},startedAt:i,heartbeatAt:i,cliVersion:e.cliVersion??qt,...e.gracefulUpdateDrain===!0?{gracefulUpdateDrain:!0}:{}};VSe(e.machineId,e.configPath,e.linzumiUrl,t,r,o,e.onTakeover),WSe(n,s,t,r,o,e.onTakeover,e.beforeReadExistingLock,e.beforeReplaceStaleLock);let a=()=>L9(n,s,r),d=setInterval(a,$9);return d.unref(),{path:n,record:s,heartbeat:a,release:()=>{clearInterval(d),z9(n,s)}}}function L9(e,n,t){try{let r=x0(e);if(r===void 0||r.machineId!==n.machineId||r.runnerId!==n.runnerId||r.pid!==n.pid||r.startedAt!==n.startedAt)return;let o={...r,heartbeatAt:t().toISOString()},i=`${e}.tmp`;NSe(i,`${JSON.stringify(o,null,2)}
|
|
1477
1477
|
`,"utf8"),zSe(i,e)}catch{}}function xd(e,n,t){if(!n(e.pid))return{state:"dead"};let r=e.heartbeatAt??e.startedAt,o=Date.parse(r);return Number.isNaN(o)||t().getTime()-o>BSe?{state:"wedged",reason:e.heartbeatAt===void 0?"missing_heartbeat":"stale_heartbeat"}:{state:"live"}}function j9(e,n,t,r){return t!==y0||e.launchSource!==y0?!1:r.state!=="live"?!0:JSe(n,e.cliVersion)>0}function JSe(e,n){let t=I9(e),r=I9(n);for(let o=0;o<3;o++){let i=t.core[o],s=r.core[o];if(i===void 0||s===void 0)throw new Error(`compareCliVersions: unexpected undefined at core index ${o}`);if(i!==s)return i>s?1:-1}return t.prerelease.length===0&&r.prerelease.length===0?0:t.prerelease.length===0?1:r.prerelease.length===0?-1:KSe(t.prerelease,r.prerelease)}function I9(e){let n=e.trim().replace(/^v/i,"").split("+",1)[0]??"",t=n.indexOf("-"),r=t===-1?n:n.slice(0,t),o=t===-1?"":n.slice(t+1),i=r.split("."),s=[pP(i[0]),pP(i[1]),pP(i[2])],a=o===""?[]:o.split(".").filter(d=>d!=="");return{core:s,prerelease:a}}function pP(e){let n=Number.parseInt(e??"",10);return Number.isInteger(n)&&n>=0?n:0}function KSe(e,n){let t=Math.max(e.length,n.length);for(let r=0;r<t;r++){let o=e[r],i=n[r];if(o===void 0)return-1;if(i===void 0)return 1;let s=Number.parseInt(o,10),a=Number.parseInt(i,10),d=s.toString()===o,u=a.toString()===i;if(d&&u){if(s!==a)return s>a?1:-1}else{if(d!==u)return d?-1:1;if(o!==i)return o>i?1:-1}}return 0}function F9(e,n,t,r,o){try{r(n.pid)}catch(i){let s=i!==null&&typeof i=="object"&&"code"in i?i.code:void 0;if(s!=="ESRCH")throw s==="EPERM"||s==="EACCES"?new Error(`Cannot take over the runner lock at ${e}: its holder process (pid ${n.pid}) is owned by another user and cannot be stopped. Stop that process, or remove the lock file, then retry.`):i}o?.({lockPath:e,holderPid:n.pid,holderRunnerId:n.runnerId,reason:t})}function VSe(e,n,t,r,o,i,s){if(t===void 0||yr(t)!==yr(Jt))return;let a=hP(e,n),d=x0(a);if(d!==void 0){if(xd(d,r,o).state==="live")throw new gs(a,d);q9(a,r,()=>{let u=fP(a)?gP(a):void 0;if(u===void 0)return;let c=xd(u,r,o);if(c.state==="live")throw new gs(a,u);c.state==="wedged"&&F9(a,u,c.reason,i,s),yd(a)})}}function WSe(e,n,t,r,o,i,s,a){if(h0(e,n))return;s?.();let d=x0(e);if(d===void 0){if(h0(e,n))return;throw new Error(`another Linzumi runner lock appeared while starting: ${e}`)}if(xd(d,t,r).state==="live")throw new gs(e,d);a?.(),q9(e,t,()=>{let u=fP(e)?gP(e):void 0;if(u!==void 0){let c=xd(u,t,r);if(c.state==="live")throw new gs(e,u);c.state==="wedged"&&F9(e,u,c.reason,o,i),yd(e)}if(!h0(e,n))throw new Error(`another Linzumi runner lock appeared while starting: ${e}`)})}function h0(e,n){qSe(A9(e),{recursive:!0});try{let t=E9(e,"wx");try{M9(t,`${JSON.stringify(n,null,2)}
|
|
1478
1478
|
`)}finally{R9(t)}return!0}catch(t){if(Kc(t,"EEXIST"))return!1;throw t}}function q9(e,n,t){let r=`${e}.replace`;for(;;)try{let o=E9(r,"wx");try{M9(o,`${process.pid}
|
|
1479
1479
|
`),t()}finally{R9(o),yd(r)}return}catch(o){if(Kc(o,"EEXIST")){let i=HSe(r);if(i===void 0)continue;if(n(i))throw new Error(["another Linzumi runner is already replacing a stale runner lock",`lock: ${e}`,"Wait for that startup to finish, then retry."].join(`
|
package/package.json
CHANGED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 7, harness-codex-adapter / workstream H.2).
|
|
3
|
+
// Relationship: Builds the TS-side PARITY ORACLE for the codex-adapter
|
|
4
|
+
// cluster of the ReScript commander port, the sibling of
|
|
5
|
+
// build-session-registry-parity-oracle.mjs (cluster 6). The oracle bundles
|
|
6
|
+
// the REAL TS codex-adapter surface - the app-server module's exported
|
|
7
|
+
// builders (argv/config/env/provider args, stdio, the forwardable-key
|
|
8
|
+
// resolver), the isolated-codex-home module (config.toml, the auth.json
|
|
9
|
+
// carry-through, the effective-auth probes), the parent-death watchdog
|
|
10
|
+
// module (program bytes, config codec, the reap trigger), the shared
|
|
11
|
+
// engine-child reaper, the dev-server reap-exemption read slice, the
|
|
12
|
+
// attach-wait module (tracker semantics, bounded-wait error text, the
|
|
13
|
+
// rollout probe), the runtime-options mapping (approval-policy gates,
|
|
14
|
+
// thread/turn override rendering), the turn-fate status classifier, and
|
|
15
|
+
// runner.ts's exported thread-start runtime param renderer +
|
|
16
|
+
// resumeCodexThreadForReconnect - behind two drivers:
|
|
17
|
+
//
|
|
18
|
+
// * the cluster 1/2 one-shot BATCH driver (a JSON array of cases on
|
|
19
|
+
// stdin, a JSON array of results on stdout) for the pure transforms;
|
|
20
|
+
// * a --flow line driver (the cluster 4 transport-oracle convention:
|
|
21
|
+
// one JSON op per stdin line, {id,...} replies + {obs:...} events on
|
|
22
|
+
// stdout) that runs the REAL connectCodexAppServer client - and the
|
|
23
|
+
// REAL resumeCodexThreadForReconnect recovery flow - against a live
|
|
24
|
+
// scripted codex app-server double URL, for the dual-impl wire
|
|
25
|
+
// differential in @linzumi/commander-harness.
|
|
26
|
+
//
|
|
27
|
+
// Determinism: clocks are injected per case (the tracker takes now()), the
|
|
28
|
+
// scratch dirs ride the case inputs, and nothing here reads wall time
|
|
29
|
+
// except the flow driver's transport waits (sequenced on awaited replies).
|
|
30
|
+
//
|
|
31
|
+
// SECRETS: the oracle only ever sees the conformance suite's synthetic
|
|
32
|
+
// payloads; auth flow cases use distinctive synthetic credentials minted by
|
|
33
|
+
// the suite. The output is a local test artifact (.differential/ is
|
|
34
|
+
// gitignored, never published) and stays unminified for debuggability.
|
|
35
|
+
import { copyFile, mkdir } from 'node:fs/promises';
|
|
36
|
+
import { dirname, join } from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
import { build } from 'esbuild';
|
|
39
|
+
|
|
40
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
41
|
+
|
|
42
|
+
const driverSource = `
|
|
43
|
+
import {
|
|
44
|
+
codexAppServerArgs,
|
|
45
|
+
codexAppServerConfigArgs,
|
|
46
|
+
codexAppServerStdio,
|
|
47
|
+
codexModelProviderConfigArgs,
|
|
48
|
+
connectCodexAppServer,
|
|
49
|
+
resolveForwardableOpenAiApiKey,
|
|
50
|
+
} from './src/codexAppServer';
|
|
51
|
+
import {
|
|
52
|
+
codexCredentialStoreFileArgs,
|
|
53
|
+
ensureIsolatedCodexHome,
|
|
54
|
+
inheritUserCodexAuth,
|
|
55
|
+
isolatedCodexHomePath,
|
|
56
|
+
resolveOpenAiAuthState,
|
|
57
|
+
userCodexHomePath,
|
|
58
|
+
} from './src/codexHome';
|
|
59
|
+
import {
|
|
60
|
+
encodeParentDeathWatchdogConfig,
|
|
61
|
+
parseParentDeathWatchdogConfig,
|
|
62
|
+
parentDeathWatchdogProgram,
|
|
63
|
+
parentDeathWatchdogShouldReap,
|
|
64
|
+
parentDeathWatchdogSpawn,
|
|
65
|
+
} from './src/engineParentDeathWatchdog';
|
|
66
|
+
import {
|
|
67
|
+
killProcessGroupWithProtectedListeners,
|
|
68
|
+
parsePidPgidRows,
|
|
69
|
+
protectedListenerFreshnessMs,
|
|
70
|
+
readProtectedPidsFromDir,
|
|
71
|
+
} from './src/devServerReapExemption';
|
|
72
|
+
import {
|
|
73
|
+
CODEX_ATTACH_WAIT_HARD_CAP_MS,
|
|
74
|
+
CODEX_ATTACH_WAIT_HEARTBEAT_MS,
|
|
75
|
+
CodexAttachWaitHardCapError,
|
|
76
|
+
CodexAttachWaitNoProgressError,
|
|
77
|
+
CodexSessionAttachFailedError,
|
|
78
|
+
awaitCodexSessionAttach,
|
|
79
|
+
codexAttachWaitHardCapMs,
|
|
80
|
+
codexRolloutFileBytesForThread,
|
|
81
|
+
createCodexAttachProgressTracker,
|
|
82
|
+
} from './src/codexAttachWait';
|
|
83
|
+
import {
|
|
84
|
+
codexApprovalPolicyForRequest,
|
|
85
|
+
codexThreadRuntimeOverrides,
|
|
86
|
+
codexTurnRuntimeOverrides,
|
|
87
|
+
codexWireApprovalPolicy,
|
|
88
|
+
} from './src/codexRuntimeOptions';
|
|
89
|
+
import { upstreamHttpStatusFate } from './src/terminalFailureRetry';
|
|
90
|
+
import {
|
|
91
|
+
codexThreadStartRuntimeParams,
|
|
92
|
+
resumeCodexThreadForReconnect,
|
|
93
|
+
} from './src/runner';
|
|
94
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
95
|
+
import { createInterface } from 'node:readline';
|
|
96
|
+
|
|
97
|
+
function tryCall(run) {
|
|
98
|
+
try {
|
|
99
|
+
return { ok: true, value: run() };
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
message: error instanceof Error ? error.message : String(error),
|
|
104
|
+
name: error instanceof Error ? error.name : 'Error',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function fileSnapshot(path) {
|
|
110
|
+
try {
|
|
111
|
+
return {
|
|
112
|
+
exists: true,
|
|
113
|
+
contents: readFileSync(path, 'utf8'),
|
|
114
|
+
mode: statSync(path).mode & 0o777,
|
|
115
|
+
};
|
|
116
|
+
} catch {
|
|
117
|
+
return { exists: false };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function runCase(input) {
|
|
122
|
+
switch (input.kind) {
|
|
123
|
+
case 'appServerArgs':
|
|
124
|
+
return codexAppServerArgs(input.listenUrl, input.options ?? {});
|
|
125
|
+
case 'configArgs':
|
|
126
|
+
return codexAppServerConfigArgs(input.options ?? {});
|
|
127
|
+
case 'modelProviderArgs':
|
|
128
|
+
return codexModelProviderConfigArgs(input.provider);
|
|
129
|
+
case 'credentialStoreArgs':
|
|
130
|
+
return [...codexCredentialStoreFileArgs()];
|
|
131
|
+
case 'stdio':
|
|
132
|
+
return codexAppServerStdio();
|
|
133
|
+
case 'watchdogProgram':
|
|
134
|
+
return parentDeathWatchdogProgram();
|
|
135
|
+
case 'watchdogEncode':
|
|
136
|
+
return encodeParentDeathWatchdogConfig(input.config);
|
|
137
|
+
case 'watchdogParse':
|
|
138
|
+
return tryCall(() => parseParentDeathWatchdogConfig(input.raw));
|
|
139
|
+
case 'watchdogShouldReap':
|
|
140
|
+
return parentDeathWatchdogShouldReap(input.originalParentPid, input.currentParentPid);
|
|
141
|
+
case 'watchdogSpawn':
|
|
142
|
+
return parentDeathWatchdogSpawn(input.nodeExecPath, input.config);
|
|
143
|
+
case 'parsePidPgidRows':
|
|
144
|
+
return parsePidPgidRows(input.output);
|
|
145
|
+
case 'freshnessMs':
|
|
146
|
+
return protectedListenerFreshnessMs;
|
|
147
|
+
case 'readProtectedPids':
|
|
148
|
+
return [...readProtectedPidsFromDir(input.dir, input.nowMs)].sort((a, b) => a - b);
|
|
149
|
+
case 'groupKill': {
|
|
150
|
+
const kills = [];
|
|
151
|
+
const outcome = killProcessGroupWithProtectedListeners(
|
|
152
|
+
input.groupLeaderPid,
|
|
153
|
+
input.signal,
|
|
154
|
+
(pid, signal) => {
|
|
155
|
+
kills.push({ pid, signal });
|
|
156
|
+
const failure = (input.killFailures ?? {})[String(pid)];
|
|
157
|
+
if (failure !== undefined) {
|
|
158
|
+
const error = new Error(failure);
|
|
159
|
+
error.code = failure;
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
() => (input.groupPids === null ? undefined : input.groupPids),
|
|
164
|
+
new Set(input.protectedPids ?? [])
|
|
165
|
+
);
|
|
166
|
+
return {
|
|
167
|
+
mode: outcome.mode,
|
|
168
|
+
sparedPids: [...outcome.sparedPids],
|
|
169
|
+
failureCodes: outcome.failures.map((failure) =>
|
|
170
|
+
failure.error instanceof Error && 'code' in failure.error
|
|
171
|
+
? { pid: failure.pid, code: failure.error.code }
|
|
172
|
+
: { pid: failure.pid, code: null }
|
|
173
|
+
),
|
|
174
|
+
kills: kills.sort((a, b) => a.pid - b.pid),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
case 'attachConstants':
|
|
178
|
+
return {
|
|
179
|
+
hardCapMs: CODEX_ATTACH_WAIT_HARD_CAP_MS,
|
|
180
|
+
heartbeatMs: CODEX_ATTACH_WAIT_HEARTBEAT_MS,
|
|
181
|
+
hardCapOfEnv: codexAttachWaitHardCapMs(),
|
|
182
|
+
};
|
|
183
|
+
case 'attachMessages':
|
|
184
|
+
return {
|
|
185
|
+
attachFailed: new CodexSessionAttachFailedError(
|
|
186
|
+
input.method,
|
|
187
|
+
input.codexThreadId,
|
|
188
|
+
input.failure
|
|
189
|
+
).message,
|
|
190
|
+
noProgress: new CodexAttachWaitNoProgressError({
|
|
191
|
+
method: input.method,
|
|
192
|
+
codexThreadId: input.codexThreadId,
|
|
193
|
+
timeoutMs: input.timeoutMs,
|
|
194
|
+
waitedMs: input.waitedMs,
|
|
195
|
+
blocker: input.blocker,
|
|
196
|
+
rollout: input.rollout,
|
|
197
|
+
}).message,
|
|
198
|
+
hardCap: new CodexAttachWaitHardCapError({
|
|
199
|
+
method: input.method,
|
|
200
|
+
codexThreadId: input.codexThreadId,
|
|
201
|
+
hardCapMs: input.hardCapMs,
|
|
202
|
+
blocker: input.blocker,
|
|
203
|
+
rollout: input.rollout,
|
|
204
|
+
}).message,
|
|
205
|
+
};
|
|
206
|
+
case 'attachTracker': {
|
|
207
|
+
// Scripted note/fail/clear/snapshot sequence with an injected clock.
|
|
208
|
+
let nowMs = 0;
|
|
209
|
+
const tracker = createCodexAttachProgressTracker(() => nowMs);
|
|
210
|
+
const snapshots = [];
|
|
211
|
+
for (const op of input.ops) {
|
|
212
|
+
nowMs = op.nowMs ?? nowMs;
|
|
213
|
+
if (op.op === 'note') tracker.note(op.id, op.stage, op.detail ?? undefined);
|
|
214
|
+
if (op.op === 'fail') tracker.fail(op.id, op.failure);
|
|
215
|
+
if (op.op === 'clear') tracker.clear(op.id);
|
|
216
|
+
if (op.op === 'snapshot') {
|
|
217
|
+
const snapshot = tracker.snapshot(op.id);
|
|
218
|
+
snapshots.push(
|
|
219
|
+
snapshot === undefined
|
|
220
|
+
? null
|
|
221
|
+
: {
|
|
222
|
+
stage: snapshot.stage,
|
|
223
|
+
detail: snapshot.detail ?? null,
|
|
224
|
+
atMs: snapshot.atMs,
|
|
225
|
+
failure: snapshot.failure ?? null,
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return snapshots;
|
|
231
|
+
}
|
|
232
|
+
case 'attachWaitOutcome': {
|
|
233
|
+
// The bounded wait driven with a scripted clock: the underlying wait
|
|
234
|
+
// never settles, so the poller's verdict (fail-fast, no-progress,
|
|
235
|
+
// hard cap) is the observable. Poll cadence 1ms against a virtual
|
|
236
|
+
// clock stepped by the poll itself.
|
|
237
|
+
let nowMs = 0;
|
|
238
|
+
const tracker = createCodexAttachProgressTracker(() => nowMs);
|
|
239
|
+
for (const op of input.ops ?? []) {
|
|
240
|
+
nowMs = op.nowMs ?? nowMs;
|
|
241
|
+
if (op.op === 'note') tracker.note(input.codexThreadId, op.stage, op.detail ?? undefined);
|
|
242
|
+
if (op.op === 'fail') tracker.fail(input.codexThreadId, op.failure);
|
|
243
|
+
}
|
|
244
|
+
nowMs = input.startMs ?? 0;
|
|
245
|
+
const logs = [];
|
|
246
|
+
return awaitCodexSessionAttach({
|
|
247
|
+
method: input.method,
|
|
248
|
+
codexThreadId: input.codexThreadId,
|
|
249
|
+
wait: new Promise(() => {}),
|
|
250
|
+
tracker,
|
|
251
|
+
log: (event, payload) => logs.push({ event, payload }),
|
|
252
|
+
rolloutBytes: () => input.rolloutBytes ?? undefined,
|
|
253
|
+
noProgressTimeoutMs: input.noProgressTimeoutMs,
|
|
254
|
+
hardCapMs: input.hardCapMs,
|
|
255
|
+
heartbeatMs: input.heartbeatMs,
|
|
256
|
+
pollMs: 1,
|
|
257
|
+
now: () => {
|
|
258
|
+
nowMs += input.msPerPoll ?? 25;
|
|
259
|
+
return nowMs;
|
|
260
|
+
},
|
|
261
|
+
}).then(
|
|
262
|
+
() => ({ settled: 'resolved', logs }),
|
|
263
|
+
(error) => ({
|
|
264
|
+
settled: 'rejected',
|
|
265
|
+
name: error instanceof Error ? error.name : 'Error',
|
|
266
|
+
message: error instanceof Error ? error.message : String(error),
|
|
267
|
+
heartbeats: logs.length,
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
case 'rolloutBytes':
|
|
272
|
+
return codexRolloutFileBytesForThread(input.codexHome, input.codexThreadId) ?? null;
|
|
273
|
+
case 'approvalPolicyForRequest':
|
|
274
|
+
return tryCall(() =>
|
|
275
|
+
codexApprovalPolicyForRequest(input.approvalPolicy ?? undefined, input.sandbox ?? undefined, {
|
|
276
|
+
sandboxedNeverDefault: input.sandboxedNeverDefault,
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
case 'wireApprovalPolicy':
|
|
280
|
+
return tryCall(() =>
|
|
281
|
+
codexWireApprovalPolicy(input.approvalPolicy ?? undefined, input.sandbox ?? undefined, {
|
|
282
|
+
sandboxedNeverDefault: input.sandboxedNeverDefault,
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
case 'threadRuntimeOverrides':
|
|
286
|
+
return tryCall(() => codexThreadRuntimeOverrides(input.options));
|
|
287
|
+
case 'turnRuntimeOverrides':
|
|
288
|
+
return tryCall(() => codexTurnRuntimeOverrides(input.options));
|
|
289
|
+
case 'threadStartRuntimeParams':
|
|
290
|
+
return tryCall(() => codexThreadStartRuntimeParams(input.runtimeSettings));
|
|
291
|
+
case 'statusFate':
|
|
292
|
+
return upstreamHttpStatusFate(input.status);
|
|
293
|
+
case 'isolatedHomePath':
|
|
294
|
+
return isolatedCodexHomePath(input.env ?? {});
|
|
295
|
+
case 'userHomePath':
|
|
296
|
+
return userCodexHomePath(input.env ?? {});
|
|
297
|
+
case 'ensureIsolatedHome': {
|
|
298
|
+
const home = ensureIsolatedCodexHome(input.env ?? {});
|
|
299
|
+
return { home, config: fileSnapshot(home + '/config.toml') };
|
|
300
|
+
}
|
|
301
|
+
case 'inheritAuth': {
|
|
302
|
+
const installed = inheritUserCodexAuth(input.isolatedHome, input.env ?? {});
|
|
303
|
+
return { installed, dest: fileSnapshot(input.isolatedHome + '/auth.json') };
|
|
304
|
+
}
|
|
305
|
+
case 'authState':
|
|
306
|
+
return resolveOpenAiAuthState(input.env ?? {});
|
|
307
|
+
case 'forwardableKey':
|
|
308
|
+
return resolveForwardableOpenAiApiKey(input.env ?? {}) ?? null;
|
|
309
|
+
default:
|
|
310
|
+
return { unknownCase: input.kind };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function runBatch() {
|
|
315
|
+
const chunks = [];
|
|
316
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
317
|
+
const cases = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
318
|
+
const results = [];
|
|
319
|
+
for (const item of cases) {
|
|
320
|
+
const outcome = runCase(item);
|
|
321
|
+
if (outcome instanceof Promise) {
|
|
322
|
+
// The attach-wait poller is unref'd by design; hold the event loop
|
|
323
|
+
// open while the case settles or node would exit mid-await.
|
|
324
|
+
const keepAlive = setInterval(() => {}, 1000);
|
|
325
|
+
try {
|
|
326
|
+
results.push(await outcome);
|
|
327
|
+
} finally {
|
|
328
|
+
clearInterval(keepAlive);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
results.push(outcome);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
process.stdout.write(JSON.stringify(results));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// --- The flow driver (the dual-impl wire differential's TS leg) -------------
|
|
338
|
+
|
|
339
|
+
async function runFlow() {
|
|
340
|
+
let client;
|
|
341
|
+
const obs = [];
|
|
342
|
+
const emit = (entry) => process.stdout.write(JSON.stringify({ obs: entry }) + '\\n');
|
|
343
|
+
const reply = (id, payload) =>
|
|
344
|
+
process.stdout.write(JSON.stringify({ id, ...payload }) + '\\n');
|
|
345
|
+
|
|
346
|
+
const rl = createInterface({ input: process.stdin });
|
|
347
|
+
for await (const line of rl) {
|
|
348
|
+
if (line.trim() === '') continue;
|
|
349
|
+
const op = JSON.parse(line);
|
|
350
|
+
try {
|
|
351
|
+
switch (op.op) {
|
|
352
|
+
case 'connect': {
|
|
353
|
+
client = await connectCodexAppServer(op.url);
|
|
354
|
+
client.onNotification((message) =>
|
|
355
|
+
emit({ kind: 'notification', method: message.method, params: message.params ?? null })
|
|
356
|
+
);
|
|
357
|
+
client.onClose?.(() => emit({ kind: 'closed' }));
|
|
358
|
+
reply(op.id, { ok: true });
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case 'armApproval': {
|
|
362
|
+
client.onRequest?.((request) => {
|
|
363
|
+
emit({ kind: 'server_request', method: request.method, params: request.params ?? null });
|
|
364
|
+
return { decision: op.decision };
|
|
365
|
+
});
|
|
366
|
+
reply(op.id, { ok: true });
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case 'request': {
|
|
370
|
+
const response = await client.request(op.method, op.params ?? undefined);
|
|
371
|
+
emit({ kind: 'reply', method: op.method, response });
|
|
372
|
+
reply(op.id, { ok: true });
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case 'resumeReconnect': {
|
|
376
|
+
const outcome = await resumeCodexThreadForReconnect(
|
|
377
|
+
client,
|
|
378
|
+
op.codexThreadId,
|
|
379
|
+
op.resumeOverrides ?? {},
|
|
380
|
+
{
|
|
381
|
+
forceRehydrate: op.forceRehydrate === true,
|
|
382
|
+
buildRehydrationText: async () => op.rehydrationText ?? 'scripted rehydration text',
|
|
383
|
+
log: (event, payload) => emit({ kind: 'log', event, payload }),
|
|
384
|
+
}
|
|
385
|
+
).then(
|
|
386
|
+
(value) => ({ ok: true, value }),
|
|
387
|
+
(error) => ({
|
|
388
|
+
ok: false,
|
|
389
|
+
message: error instanceof Error ? error.message : String(error),
|
|
390
|
+
})
|
|
391
|
+
);
|
|
392
|
+
emit({ kind: 'resume_outcome', outcome });
|
|
393
|
+
reply(op.id, { ok: true });
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case 'close': {
|
|
397
|
+
client?.close();
|
|
398
|
+
reply(op.id, { ok: true });
|
|
399
|
+
rl.close();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
default:
|
|
403
|
+
reply(op.id, { ok: false, error: 'unknown op ' + op.op });
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
reply(op.id, {
|
|
407
|
+
ok: false,
|
|
408
|
+
error: error instanceof Error ? error.message : String(error),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (process.argv.includes('--flow')) {
|
|
415
|
+
runFlow().then(
|
|
416
|
+
() => process.exit(0),
|
|
417
|
+
(error) => {
|
|
418
|
+
process.stderr.write(String(error && error.stack ? error.stack : error) + '\\n');
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
} else {
|
|
423
|
+
runBatch().then(
|
|
424
|
+
() => process.exit(0),
|
|
425
|
+
(error) => {
|
|
426
|
+
process.stderr.write(String(error && error.stack ? error.stack : error) + '\\n');
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
`;
|
|
432
|
+
|
|
433
|
+
const outDir = join(packageRoot, '.differential');
|
|
434
|
+
const outPath = join(outDir, 'harness-codex-parity-oracle.mjs');
|
|
435
|
+
|
|
436
|
+
await mkdir(outDir, { recursive: true });
|
|
437
|
+
// runner.ts's graph resolves assets/ relative to the bundle at import time
|
|
438
|
+
// (the hello-project scaffold's inlined logo), so the asset must sit next
|
|
439
|
+
// to the emitted oracle - the build-auth-parity-oracle precedent.
|
|
440
|
+
await mkdir(join(outDir, 'assets'), { recursive: true });
|
|
441
|
+
await copyFile(
|
|
442
|
+
join(packageRoot, 'src/assets/linzumi-logo.svg'),
|
|
443
|
+
join(outDir, 'assets/linzumi-logo.svg')
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
await build({
|
|
447
|
+
stdin: {
|
|
448
|
+
contents: driverSource,
|
|
449
|
+
resolveDir: packageRoot,
|
|
450
|
+
sourcefile: 'harness-codex-parity-driver.ts',
|
|
451
|
+
loader: 'ts',
|
|
452
|
+
},
|
|
453
|
+
bundle: true,
|
|
454
|
+
platform: 'node',
|
|
455
|
+
target: 'node20',
|
|
456
|
+
format: 'esm',
|
|
457
|
+
outfile: outPath,
|
|
458
|
+
minify: false,
|
|
459
|
+
sourcemap: false,
|
|
460
|
+
legalComments: 'none',
|
|
461
|
+
logLevel: 'silent',
|
|
462
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
process.stdout.write(outPath + '\n');
|