@linzumi/cli 1.0.112 → 1.0.114

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.
@@ -0,0 +1,577 @@
1
+ // Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
2
+ // (M3 cluster 11, the TUI).
3
+ // Relationship: Builds the TS-side PARITY ORACLE for the commander
4
+ // console/TUI port - the deterministic core of the console-reporter module
5
+ // (dashboard state fold, selection machinery, key/chunk parsers, every
6
+ // render surface incl. the TUI table/header-layout models) plus the
7
+ // blessed hardening transforms (terminfo conditional repair, the ACS
8
+ // charset fix, the #2955 screen-registry sweep) and the reporter's
9
+ // teardown/degrade/fail-soft latches. ONE bundle imports the REAL TS
10
+ // module exports behind the one-shot batch driver: a JSON array of cases
11
+ // on stdin, a JSON array of results on stdout. The ReScript package's
12
+ // ConsoleTuiParityConformance drives the SAME inputs through
13
+ // src/tui/CommanderConsole* / CommanderTuiGuards and asserts byte-equal
14
+ // rendered strings, deep-equal state projections, and identical event
15
+ // traces.
16
+ //
17
+ // Determinism: every op is pure given its inputs - clocks arrive as
18
+ // explicit nowMs values, and the reporter-latch ops reset the module
19
+ // latches per case via the exported *ForTest hooks. The teardown/degrade
20
+ // ops route observability through explicit sinks (so the TS default audit
21
+ // sink never fires), and scripted dashboards/streams throw
22
+ // deterministically shaped errors.
23
+ //
24
+ // SECRETS: the oracle only ever sees the conformance suite's synthetic
25
+ // payloads; nothing here logs case contents.
26
+ //
27
+ // The output is a local test artifact (.differential/ is gitignored, never
28
+ // published) and stays unminified for debuggability.
29
+ import { mkdir } from 'node:fs/promises';
30
+ import { dirname, join } from 'node:path';
31
+ import { fileURLToPath } from 'node:url';
32
+ import { build } from 'esbuild';
33
+
34
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
35
+
36
+ const driverSource = `
37
+ import {
38
+ arrowKeysInRawChunk,
39
+ createRunnerConsoleDashboard,
40
+ dashboardJobKeyAtTableRow,
41
+ dashboardNoticeLines,
42
+ droppedArrowKeysInRawChunk,
43
+ formatRunnerConsoleEvent,
44
+ keypressIsEscape,
45
+ rawInputChunkContainsMouseReport,
46
+ rawInputChunkSignalsEscape,
47
+ renderRunnerConsoleDashboard,
48
+ resolveSelectionAfterRefresh,
49
+ runnerConsoleDashboardTableModel,
50
+ runnerConsoleTuiEscapePressed,
51
+ runnerConsoleTuiHeaderLayout,
52
+ selectDashboardJobAtVisibleIndex,
53
+ selectDashboardJobByKey,
54
+ tableSelectionPageStep,
55
+ tableSelectionRowIndex,
56
+ updateRunnerConsoleDashboard,
57
+ updateRunnerConsoleDashboardMode,
58
+ createRunnerConsoleDashboardTuiFailSoft,
59
+ resetRunnerConsoleDashboardTuiConstructionForTest,
60
+ teardownRunnerConsoleDashboardTuiSafely,
61
+ degradeRunnerConsoleTuiToHeadless,
62
+ resetRunnerConsoleTuiDegradeForTest,
63
+ runnerConsoleTuiDegradedToHeadless,
64
+ setRunnerConsoleDashboardTuiForTest,
65
+ runnerConsoleDashboardTuiActiveForTest,
66
+ } from './src/runnerConsoleReporter';
67
+ import { balanceTerminfoConditionals } from './src/blessedTputSetulcShim';
68
+ import {
69
+ applyBlessedAcsCharsetFix,
70
+ blessedWouldUseAcsCharset,
71
+ forceUnicodeBoxDrawingOnTput,
72
+ } from './src/blessedAcsCharsetShim';
73
+ import {
74
+ constructBlessedScreenGuarded,
75
+ detachBlessedScreenFromRegistry,
76
+ sweepStrandedBlessedScreens,
77
+ } from './src/blessedScreenLifecycleGuard';
78
+ import { linzumiCliVersion } from './src/version';
79
+
80
+ // --- Projections ------------------------------------------------------------------------
81
+
82
+ const orNull = (value) => (value === undefined ? null : value);
83
+
84
+ function projectJob(job) {
85
+ return {
86
+ key: job.key,
87
+ createdAtMs: job.createdAtMs,
88
+ createdOrdinal: job.createdOrdinal,
89
+ agentProvider: orNull(job.agentProvider),
90
+ linzumiThreadId: orNull(job.linzumiThreadId),
91
+ linzumiThreadTitle: orNull(job.linzumiThreadTitle),
92
+ // The shared agent-session slot: the TS field carries the deprecated
93
+ // name; the projection uses the canonical key (D44) so both impls
94
+ // compare on neutral vocabulary.
95
+ harnessSessionId: orNull(job.codexThreadId),
96
+ tokenUsage: orNull(job.tokenUsage),
97
+ lastIncomingPreview: orNull(job.lastIncomingPreview),
98
+ lastIncomingAtMs: orNull(job.lastIncomingAtMs),
99
+ lastAssistantPreview: orNull(job.lastAssistantPreview),
100
+ lastAssistantAtMs: orNull(job.lastAssistantAtMs),
101
+ latestHarnessEventId: orNull(job.latestCodexEventId),
102
+ eventType: orNull(job.eventType),
103
+ creditExhaustion: orNull(job.creditExhaustion),
104
+ queueDepth: orNull(job.queueDepth),
105
+ turnId: orNull(job.turnId),
106
+ updatedAtMs: job.updatedAtMs,
107
+ };
108
+ }
109
+
110
+ function projectState(state) {
111
+ return {
112
+ jobs: [...state.jobs.entries()].map(([key, job]) => [key, projectJob(job)]),
113
+ discovery: [...state.discovery.entries()].map(([key, status]) => [
114
+ key,
115
+ {
116
+ key: status.key,
117
+ phase: status.phase,
118
+ status: status.status,
119
+ placesSearched: status.placesSearched,
120
+ message: orNull(status.message),
121
+ currentPlace: orNull(status.currentPlace),
122
+ currentSource: orNull(status.currentSource),
123
+ lastError: orNull(status.lastError),
124
+ metadata: orNull(status.metadata),
125
+ updatedAtMs: status.updatedAtMs,
126
+ },
127
+ ]),
128
+ notices: [...state.notices.entries()].map(([key, notice]) => [
129
+ key,
130
+ {
131
+ key: notice.key,
132
+ severity: notice.severity,
133
+ message: notice.message,
134
+ count: notice.count,
135
+ updatedAtMs: notice.updatedAtMs,
136
+ },
137
+ ]),
138
+ rawLines: state.rawLines.map((line) => ({
139
+ jobKey: orNull(line.jobKey),
140
+ line: line.line,
141
+ })),
142
+ nextJobOrdinal: state.nextJobOrdinal,
143
+ mode:
144
+ state.mode.type === 'raw_job'
145
+ ? { type: 'raw_job', jobKey: state.mode.jobKey }
146
+ : { type: state.mode.type },
147
+ selectedJobKey: orNull(state.selectedJobKey),
148
+ selectedJobIndexHint: orNull(state.selectedJobIndexHint),
149
+ tokenUsage: orNull(state.tokenUsage),
150
+ rateLimit: orNull(state.rateLimit),
151
+ creditExhaustion: orNull(state.creditExhaustion),
152
+ lastUpdateAtMs: orNull(state.lastUpdateAtMs),
153
+ browserUrl: orNull(state.browserUrl),
154
+ workspace: orNull(state.workspace),
155
+ runnerId: orNull(state.runnerId),
156
+ connectionStale: state.connectionStale,
157
+ rosterTruncated: state.rosterTruncated,
158
+ };
159
+ }
160
+
161
+ function projectTableModel(model) {
162
+ return {
163
+ rows: model.rows,
164
+ rowJobKeys: model.rowJobKeys.map((key) => orNull(key)),
165
+ selectedJobKey: orNull(model.selectedJobKey),
166
+ jobKeys: model.jobs.map((job) => job.key),
167
+ selectionRowIndex: tableSelectionRowIndex(model),
168
+ };
169
+ }
170
+
171
+ // --- scenario: the state fold + render surfaces ------------------------------------------
172
+
173
+ function handleScenario(testCase) {
174
+ const state = createRunnerConsoleDashboard();
175
+ const outputs = [];
176
+ for (const step of testCase.steps) {
177
+ switch (step.op) {
178
+ case 'event':
179
+ updateRunnerConsoleDashboard(state, step.event, step.payload, step.nowMs);
180
+ outputs.push(null);
181
+ break;
182
+ case 'mode_key':
183
+ updateRunnerConsoleDashboardMode(state, step.key);
184
+ outputs.push(null);
185
+ break;
186
+ case 'select_index':
187
+ selectDashboardJobAtVisibleIndex(state, step.index);
188
+ outputs.push(null);
189
+ break;
190
+ case 'select_key':
191
+ selectDashboardJobByKey(state, step.key ?? undefined);
192
+ outputs.push(null);
193
+ break;
194
+ case 'escape_pressed':
195
+ outputs.push(runnerConsoleTuiEscapePressed(state));
196
+ break;
197
+ case 'escape_chunk': {
198
+ const signals = rawInputChunkSignalsEscape(step.chunk);
199
+ const changed = signals ? runnerConsoleTuiEscapePressed(state) : false;
200
+ outputs.push({ signals, changed });
201
+ break;
202
+ }
203
+ case 'render':
204
+ outputs.push(orNull(renderRunnerConsoleDashboard(state, step.nowMs)));
205
+ break;
206
+ case 'table_model':
207
+ outputs.push(projectTableModel(runnerConsoleDashboardTableModel(state, step.nowMs)));
208
+ break;
209
+ case 'header_layout':
210
+ outputs.push(
211
+ runnerConsoleTuiHeaderLayout(
212
+ state,
213
+ step.jobCount,
214
+ step.nowMs,
215
+ step.terminalRows
216
+ )
217
+ );
218
+ break;
219
+ case 'notice_lines':
220
+ outputs.push(dashboardNoticeLines(state, step.mode));
221
+ break;
222
+ case 'state':
223
+ outputs.push(projectState(state));
224
+ break;
225
+ default:
226
+ outputs.push({ threw: true, message: 'unknown scenario step ' + String(step.op) });
227
+ break;
228
+ }
229
+ }
230
+ return outputs;
231
+ }
232
+
233
+ // --- Reporter-latch ops -------------------------------------------------------------------
234
+
235
+ function makeSinkCollector() {
236
+ const events = [];
237
+ return {
238
+ events,
239
+ sinks: {
240
+ audit: (event, fields) => events.push({ sink: 'audit', event, fields }),
241
+ log: (event, fields) => events.push({ sink: 'log', event, fields }),
242
+ },
243
+ };
244
+ }
245
+
246
+ function scriptedError(spec) {
247
+ const error = new Error(spec.message);
248
+ if (spec.code !== undefined) {
249
+ error.code = spec.code;
250
+ }
251
+ return error;
252
+ }
253
+
254
+ function handleTeardownScenario(testCase) {
255
+ resetRunnerConsoleTuiDegradeForTest();
256
+ setRunnerConsoleDashboardTuiForTest(undefined);
257
+ const collector = makeSinkCollector();
258
+ const outputs = [];
259
+ let stopCalls = 0;
260
+ let destroyCalls = 0;
261
+ for (const step of testCase.steps) {
262
+ switch (step.op) {
263
+ case 'install': {
264
+ destroyCalls = 0;
265
+ stopCalls = 0;
266
+ const dashboard = {
267
+ render: () => {},
268
+ destroy: () => {
269
+ destroyCalls += 1;
270
+ if (step.destroyError !== undefined) {
271
+ throw scriptedError(step.destroyError);
272
+ }
273
+ },
274
+ };
275
+ setRunnerConsoleDashboardTuiForTest(
276
+ dashboard,
277
+ { stdin: step.stdinTty ?? true, stdout: step.stdoutTty ?? true },
278
+ () => {
279
+ stopCalls += 1;
280
+ }
281
+ );
282
+ outputs.push(null);
283
+ break;
284
+ }
285
+ case 'teardown': {
286
+ const outcome = teardownRunnerConsoleDashboardTuiSafely(
287
+ { trigger: step.trigger },
288
+ collector.sinks
289
+ );
290
+ outputs.push({
291
+ hadTui: outcome.hadTui,
292
+ ok: outcome.ok,
293
+ swallowedErrorCode: orNull(outcome.swallowedErrorCode),
294
+ active: runnerConsoleDashboardTuiActiveForTest(),
295
+ stopCalls,
296
+ destroyCalls,
297
+ });
298
+ break;
299
+ }
300
+ case 'degrade': {
301
+ degradeRunnerConsoleTuiToHeadless(
302
+ { code: step.code ?? undefined, stage: step.stage },
303
+ collector.sinks
304
+ );
305
+ outputs.push({
306
+ degraded: runnerConsoleTuiDegradedToHeadless(),
307
+ active: runnerConsoleDashboardTuiActiveForTest(),
308
+ stopCalls,
309
+ destroyCalls,
310
+ });
311
+ break;
312
+ }
313
+ case 'reset_degrade':
314
+ resetRunnerConsoleTuiDegradeForTest();
315
+ outputs.push(null);
316
+ break;
317
+ default:
318
+ outputs.push({ threw: true, message: 'unknown teardown step ' + String(step.op) });
319
+ break;
320
+ }
321
+ }
322
+ return { outputs, events: collector.events };
323
+ }
324
+
325
+ function handleFailSoftScenario(testCase) {
326
+ resetRunnerConsoleDashboardTuiConstructionForTest();
327
+ const collector = makeSinkCollector();
328
+ const outputs = [];
329
+ let createCalls = 0;
330
+ for (const step of testCase.steps) {
331
+ switch (step.op) {
332
+ case 'create': {
333
+ const result = createRunnerConsoleDashboardTuiFailSoft(() => {
334
+ createCalls += 1;
335
+ if (step.createError !== undefined) {
336
+ throw scriptedError(step.createError);
337
+ }
338
+ return { render: () => {}, destroy: () => {} };
339
+ }, collector.sinks);
340
+ outputs.push({ defined: result !== undefined, createCalls });
341
+ break;
342
+ }
343
+ case 'reset_construction':
344
+ resetRunnerConsoleDashboardTuiConstructionForTest();
345
+ outputs.push(null);
346
+ break;
347
+ default:
348
+ outputs.push({ threw: true, message: 'unknown failsoft step ' + String(step.op) });
349
+ break;
350
+ }
351
+ }
352
+ return {
353
+ outputs,
354
+ // The guarded construction wraps the REAL blessed registry; only the
355
+ // event vocabulary is compared (stack contents are impl-specific), so
356
+ // project events to sink+event+swept_screens.
357
+ events: collector.events.map((entry) => ({
358
+ sink: entry.sink,
359
+ event: entry.event,
360
+ swept_screens: orNull(entry.fields.swept_screens),
361
+ })),
362
+ };
363
+ }
364
+
365
+ // --- Blessed-guard ops -----------------------------------------------------------------------
366
+
367
+ function makeFakeScreen(spec, sweepTrace) {
368
+ const screen = {};
369
+ if (spec.hasProgram) {
370
+ screen.program = {};
371
+ }
372
+ screen.destroy = () => {
373
+ sweepTrace.push('destroy:' + spec.name);
374
+ if (spec.destroyError !== undefined) {
375
+ throw scriptedError(spec.destroyError);
376
+ }
377
+ // A real complete screen's destroy() detaches itself from the
378
+ // registry; the scripted double mirrors that so the sweep's
379
+ // idempotent re-detach is exercised the same way on both impls.
380
+ if (spec.selfDetach !== false) {
381
+ const index = spec.registry.instances.indexOf(screen);
382
+ if (index !== -1) {
383
+ spec.registry.instances.splice(index, 1);
384
+ spec.registry.total -= 1;
385
+ spec.registry.global = spec.registry.instances[0];
386
+ if (spec.registry.total === 0) {
387
+ spec.registry.global = null;
388
+ }
389
+ }
390
+ }
391
+ };
392
+ return screen;
393
+ }
394
+
395
+ function projectRegistry(registry, names) {
396
+ return {
397
+ instanceNames: registry.instances.map((instance) => names.get(instance) ?? '?'),
398
+ total: registry.total,
399
+ global:
400
+ registry.global === null
401
+ ? null
402
+ : registry.global === undefined
403
+ ? '<undefined>'
404
+ : (names.get(registry.global) ?? '?'),
405
+ };
406
+ }
407
+
408
+ function handleRegistryGuard(testCase) {
409
+ const collector = makeSinkCollector();
410
+ const sweepTrace = [];
411
+ const registry = { instances: [], total: 0, global: null };
412
+ const names = new Map();
413
+ const preexisting = [];
414
+ for (const spec of testCase.preexisting ?? []) {
415
+ const screen = makeFakeScreen({ ...spec, registry }, sweepTrace);
416
+ names.set(screen, spec.name);
417
+ registry.instances.push(screen);
418
+ registry.total += 1;
419
+ preexisting.push(screen);
420
+ }
421
+ registry.global = registry.instances[0] ?? null;
422
+ let result;
423
+ try {
424
+ result = {
425
+ returned: constructBlessedScreenGuarded(
426
+ registry,
427
+ () => {
428
+ for (const spec of testCase.constructed ?? []) {
429
+ const screen = makeFakeScreen({ ...spec, registry }, sweepTrace);
430
+ names.set(screen, spec.name);
431
+ registry.instances.push(screen);
432
+ registry.total += 1;
433
+ }
434
+ if (testCase.constructError !== undefined) {
435
+ throw scriptedError(testCase.constructError);
436
+ }
437
+ return 'constructed';
438
+ },
439
+ { sinks: collector.sinks, context: { surface: 'parity_case' } }
440
+ ),
441
+ };
442
+ } catch (error) {
443
+ result = { threw: true, message: error instanceof Error ? error.message : String(error) };
444
+ }
445
+ return {
446
+ result,
447
+ registry: projectRegistry(registry, names),
448
+ sweepTrace,
449
+ events: collector.events,
450
+ };
451
+ }
452
+
453
+ function handleRegistryDetach(testCase) {
454
+ const registry = { instances: [], total: testCase.total ?? 0, global: null };
455
+ const names = new Map();
456
+ const screens = [];
457
+ for (const name of testCase.instances) {
458
+ const screen = { name };
459
+ names.set(screen, name);
460
+ registry.instances.push(screen);
461
+ screens.push(screen);
462
+ }
463
+ registry.global = registry.instances[0] ?? null;
464
+ const target = screens[testCase.detachIndex];
465
+ const detached = detachBlessedScreenFromRegistry(
466
+ registry,
467
+ target ?? { name: 'unregistered' }
468
+ );
469
+ return { detached, registry: projectRegistry(registry, names) };
470
+ }
471
+
472
+ // --- Dispatch ----------------------------------------------------------------------------------
473
+
474
+ function handle(testCase) {
475
+ switch (testCase.op) {
476
+ case 'version':
477
+ return linzumiCliVersion;
478
+ case 'format_line':
479
+ return orNull(formatRunnerConsoleEvent(testCase.event, testCase.payload));
480
+ case 'keys':
481
+ return {
482
+ escape: rawInputChunkSignalsEscape(testCase.chunk),
483
+ mouse: rawInputChunkContainsMouseReport(testCase.chunk),
484
+ arrows: arrowKeysInRawChunk(testCase.chunk),
485
+ dropped: droppedArrowKeysInRawChunk(testCase.chunk),
486
+ };
487
+ case 'keypress_escape':
488
+ return keypressIsEscape(testCase.key);
489
+ case 'selection_resolve': {
490
+ const resolved = resolveSelectionAfterRefresh(
491
+ testCase.previousKey ?? undefined,
492
+ testCase.previousIndexHint ?? undefined,
493
+ testCase.jobKeys
494
+ );
495
+ return { key: orNull(resolved.key), index: orNull(resolved.index) };
496
+ }
497
+ case 'page_step':
498
+ return tableSelectionPageStep(testCase.tableHeight);
499
+ case 'job_key_at_row': {
500
+ const state = createRunnerConsoleDashboard();
501
+ for (const step of testCase.events) {
502
+ updateRunnerConsoleDashboard(state, step.event, step.payload, step.nowMs);
503
+ }
504
+ const model = runnerConsoleDashboardTableModel(state, testCase.nowMs);
505
+ return testCase.rows.map((row) => orNull(dashboardJobKeyAtTableRow(model, row)));
506
+ }
507
+ case 'terminfo_balance':
508
+ return balanceTerminfoConditionals(testCase.cap);
509
+ case 'acs_would_use':
510
+ return blessedWouldUseAcsCharset(
511
+ testCase.tput ?? undefined,
512
+ testCase.ch,
513
+ testCase.alreadyInAcsRun ?? false
514
+ );
515
+ case 'acs_force': {
516
+ const tput = testCase.tput ?? undefined;
517
+ const mutated = forceUnicodeBoxDrawingOnTput(tput);
518
+ return { mutated, tput: orNull(tput) };
519
+ }
520
+ case 'acs_fix': {
521
+ const screen = testCase.screen ?? undefined;
522
+ if (screen !== undefined && testCase.shareProgramTput === true) {
523
+ screen.program = { tput: screen.tput };
524
+ }
525
+ applyBlessedAcsCharsetFix(screen);
526
+ return orNull(screen);
527
+ }
528
+ case 'scenario':
529
+ return handleScenario(testCase);
530
+ case 'teardown_scenario':
531
+ return handleTeardownScenario(testCase);
532
+ case 'failsoft_scenario':
533
+ return handleFailSoftScenario(testCase);
534
+ case 'registry_guard':
535
+ return handleRegistryGuard(testCase);
536
+ case 'registry_detach':
537
+ return handleRegistryDetach(testCase);
538
+ default:
539
+ return { threw: true, message: 'unknown op ' + String(testCase.op) };
540
+ }
541
+ }
542
+
543
+ let stdin = '';
544
+ process.stdin.setEncoding('utf8');
545
+ for await (const chunk of process.stdin) {
546
+ stdin += chunk;
547
+ }
548
+ const cases = JSON.parse(stdin);
549
+ const results = [];
550
+ for (const testCase of cases) {
551
+ results.push(handle(testCase));
552
+ }
553
+ process.stdout.write(JSON.stringify(results));
554
+ `;
555
+
556
+ await mkdir(join(packageRoot, '.differential'), { recursive: true });
557
+
558
+ await build({
559
+ stdin: {
560
+ contents: driverSource,
561
+ resolveDir: packageRoot,
562
+ sourcefile: 'tui-parity-driver.ts',
563
+ loader: 'ts',
564
+ },
565
+ bundle: true,
566
+ platform: 'node',
567
+ target: 'node20',
568
+ format: 'esm',
569
+ outfile: join(packageRoot, '.differential/tui-parity-oracle.mjs'),
570
+ minify: false,
571
+ sourcemap: false,
572
+ legalComments: 'none',
573
+ // The console-reporter module imports blessed (and the crash-handler /
574
+ // logger slice pulls the runtime graph); the runtime externals stay
575
+ // aligned with the other cluster oracles.
576
+ external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
577
+ });
package/scripts/build.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // Spec: plans/2026-05-17-linzumi-cli-node-toolchain-note.md
2
2
  // Relationship: Builds the published CLI package with Node-hosted tooling.
3
- import { copyFile, mkdir, rm } from 'node:fs/promises';
3
+ import { copyFile, mkdir, rm, writeFile } from 'node:fs/promises';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { build } from 'esbuild';
@@ -84,6 +84,45 @@ await build({
84
84
  banner,
85
85
  });
86
86
 
87
+ // F5 cross-harness failover bridge (Workstream F task F5): the runner
88
+ // dynamic-imports ./crossHarnessFailover.mjs relative to its own bundle, so
89
+ // dist ships a self-contained sibling bundle with the compiled ReScript
90
+ // transpiler artifacts (packages/linzumi-cli-rescript, built by `rescript`)
91
+ // resolved at build time. When the artifacts are not built in this checkout
92
+ // the publish must not silently ship a broken rung: emit an HONEST stub
93
+ // whose import succeeds but whose executor refuses loudly - the ladder then
94
+ // terminates red-with-receipts exactly as F2 (the flag's rollback posture) -
95
+ // and warn in the build log.
96
+ try {
97
+ await build({
98
+ entryPoints: [join(packageRoot, 'src/crossHarnessFailover.mjs')],
99
+ bundle: true,
100
+ platform: 'node',
101
+ target: 'node20',
102
+ format: 'esm',
103
+ outfile: join(packageRoot, 'dist/crossHarnessFailover.mjs'),
104
+ minify: true,
105
+ sourcemap: false,
106
+ legalComments: 'none',
107
+ banner,
108
+ });
109
+ } catch (error) {
110
+ console.warn(
111
+ '[build] cross-harness failover bridge could not be bundled (are the ' +
112
+ 'linzumi-cli-rescript artifacts built?); shipping the honest refusal ' +
113
+ `stub instead: ${error instanceof Error ? error.message : String(error)}`
114
+ );
115
+ await writeFile(
116
+ join(packageRoot, 'dist/crossHarnessFailover.mjs'),
117
+ 'export async function transpileAndSeedClaudeSession() {\n' +
118
+ ' return {\n' +
119
+ ' ok: false,\n' +
120
+ " failure: 'the cross-harness failover transpiler bridge was not bundled into this build',\n" +
121
+ ' };\n' +
122
+ '}\n'
123
+ );
124
+ }
125
+
87
126
  await mkdir(join(packageRoot, 'dist/assets'), { recursive: true });
88
127
  await copyFile(
89
128
  join(packageRoot, 'src/assets/linzumi-logo.svg'),