@linzumi/cli 1.0.113 → 1.0.115

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "description": "Linzumi CLI \u2014 point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,107 @@
1
+ // Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
2
+ // (M3 cluster 11, the TUI - visual evidence).
3
+ // Relationship: Builds the TS-side VISUAL-EVIDENCE demo driver: a bundle
4
+ // that mounts the REAL TS commander console TUI (reportRunnerConsoleEvent
5
+ // on a live TTY) and replays the SAME dummy-event feed the ReScript demo
6
+ // entrypoint replays
7
+ // (packages/linzumi-cli-rescript/src/tui/commanderTuiDemoFeed.json), so
8
+ // the cluster's before/after terminal captures compare the two
9
+ // implementations on identical input.
10
+ //
11
+ // Run (inside a real terminal / tmux pane):
12
+ // node scripts/build-tui-demo-evidence.mjs
13
+ // LINZUMI_TUI_DEMO_FIXED_NOW=<ms> node .differential/tui-demo.mjs
14
+ //
15
+ // Env knobs (mirroring the ReScript demo):
16
+ // LINZUMI_TUI_DEMO_FIXED_NOW pin Date.now() (and therefore every
17
+ // time-ago cell + the render ticker clock)
18
+ // so captures are deterministic.
19
+ //
20
+ // The output is a local evidence artifact (.differential/ is gitignored,
21
+ // never published).
22
+ import { mkdir } from 'node:fs/promises';
23
+ import { dirname, join } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+ import { build } from 'esbuild';
26
+
27
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
28
+
29
+ const driverSource = `
30
+ import { readFileSync } from 'node:fs';
31
+ import { join } from 'node:path';
32
+ import { reportRunnerConsoleEvent } from './src/runnerConsoleReporter';
33
+
34
+ const fixedNowRaw = process.env.LINZUMI_TUI_DEMO_FIXED_NOW;
35
+ const fixedNow = fixedNowRaw === undefined ? undefined : Number(fixedNowRaw);
36
+ if (fixedNow !== undefined && Number.isFinite(fixedNow)) {
37
+ const RealDate = Date;
38
+ class FixedDate extends RealDate {
39
+ constructor(...args) {
40
+ if (args.length === 0) {
41
+ super(fixedNow);
42
+ } else {
43
+ super(...args);
44
+ }
45
+ }
46
+ static now() {
47
+ return fixedNow;
48
+ }
49
+ }
50
+ globalThis.Date = FixedDate;
51
+ }
52
+
53
+ const now = Date.now();
54
+
55
+ // Resolve *_offset timestamp fields against the demo clock (the ReScript
56
+ // demo performs the same resolution).
57
+ function resolveOffsets(value) {
58
+ if (Array.isArray(value)) {
59
+ return value.map(resolveOffsets);
60
+ }
61
+ if (value !== null && typeof value === 'object') {
62
+ const out = {};
63
+ for (const [key, entry] of Object.entries(value)) {
64
+ if (key.endsWith('_offset') && typeof entry === 'number') {
65
+ out[key.slice(0, -'_offset'.length)] = now + entry;
66
+ } else {
67
+ out[key] = resolveOffsets(entry);
68
+ }
69
+ }
70
+ return out;
71
+ }
72
+ return value;
73
+ }
74
+
75
+ const feedPath = join(
76
+ process.cwd(),
77
+ '../linzumi-cli-rescript/src/tui/commanderTuiDemoFeed.json'
78
+ );
79
+ const feed = JSON.parse(readFileSync(feedPath, 'utf8'));
80
+ for (const entry of feed.events) {
81
+ reportRunnerConsoleEvent(entry.event, resolveOffsets(entry.payload));
82
+ }
83
+
84
+ // Keep the process (and the TUI) alive for interactive navigation; the
85
+ // TUI's own C-c binding exits.
86
+ setInterval(() => {}, 1000);
87
+ `;
88
+
89
+ await mkdir(join(packageRoot, '.differential'), { recursive: true });
90
+
91
+ await build({
92
+ stdin: {
93
+ contents: driverSource,
94
+ resolveDir: packageRoot,
95
+ sourcefile: 'tui-demo-driver.ts',
96
+ loader: 'ts',
97
+ },
98
+ bundle: true,
99
+ platform: 'node',
100
+ target: 'node20',
101
+ format: 'esm',
102
+ outfile: join(packageRoot, '.differential/tui-demo.mjs'),
103
+ minify: false,
104
+ sourcemap: false,
105
+ legalComments: 'none',
106
+ external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
107
+ });
@@ -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
+ });