@linzumi/cli 1.0.106 → 1.0.108
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/cloud-supervisor.mjs +11 -11
- package/dist/index.js +410 -410
- package/dist/mcp-server.mjs +35 -35
- package/package.json +1 -1
- package/scripts/build-harness-codex-parity-oracle.mjs +465 -0
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');
|