@linzumi/cli 1.0.113 → 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.
package/README.md CHANGED
@@ -58,7 +58,7 @@ Install the CLI or run it with `npx`:
58
58
  ```bash
59
59
  npm install -g @linzumi/cli@latest
60
60
  npx -y @linzumi/cli@latest signup
61
- npx -y @linzumi/cli@1.0.113 --version
61
+ npx -y @linzumi/cli@1.0.114 --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -1473,7 +1473,7 @@ To kick the agent off:
1473
1473
  `,O=Buffer.byteLength(T,"utf8");if(O>s)return l("runner_diagnostics.spool_entry_dropped",{reason:"entry_too_large",entry_bytes:O,max_entry_bytes:s}),!1;VSe(n,{recursive:!0}),m(u)+O>i&&f(),_(O);let E=b()?`
1474
1474
  `:"";return BSe(u,`${E}${T}`,"utf8"),!0}catch(R){return l("runner_diagnostics.spool_failed",{error:R instanceof Error?R.message:String(R)}),!1}},v=k=>{let R=[],T=HSe(k,"utf8");for(let O of T.split(`
1475
1475
  `))if(O.trim()!=="")try{let E=JSON.parse(O);E!==null&&typeof E=="object"&&R.push({parsed:E,raw:O})}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,T=0;try{f(),_(0);let O=a()-o;for(let E of p()){let j;try{j=v(E)}catch(H){l("runner_diagnostics.spool_batch_unreadable",{file:E,error:H instanceof Error?H.message:String(H)}),y(E,"unreadable");continue}let z=j.filter(H=>{let ce=lke(H.parsed);return ce===void 0||ce>=O});if(z.length===0){y(E,"empty_or_expired");continue}let D=vP(E,".jsonl"),U=z.map(H=>H.raw).join(`
1476
- `),J=z.map(H=>H.parsed.spooled_at).filter(H=>typeof H=="string"),Z={bundle_name:"runner-diagnostics-spool",exported_at:new Date(a()).toISOString(),idempotency_key:D,diagnostics:{kind:"runner_diagnostic_log",spool_batch:!0,idempotency_key:D,entry_count:z.length,...J.length===0?{}:{first_spooled_at:J[0],last_spooled_at:J[J.length-1]}},telemetry_ndjson:U};try{await k(Z)}catch(H){l("runner_diagnostics.spool_upload_failed",{file:E,remaining_batches:p().length,error:H instanceof Error?H.message:String(H)});break}R+=1,T+=z.length;try{V9(E)}catch(H){l("runner_diagnostics.spool_delete_failed",{file:E,error:H instanceof Error?H.message:String(H)})}}}catch(O){l("runner_diagnostics.spool_drain_failed",{error:O instanceof Error?O.message:String(O)})}finally{c=!1}return{uploadedBatches:R,uploadedEntries:T,remainingBatches:p().length,skipped:!1}},pendingBatchFiles:p}}var eke,nke,tke,rke,oke,W9,ske,ake,G9=F(()=>{"use strict";cx();eke=32*1024*1024,nke=10080*60*1e3,tke=4*1024*1024,rke=1024*1024,oke="spool.jsonl",W9=/^batch-(\d{1,16})-[0-9a-f-]+\.jsonl$/;ske=/\b([A-Z][A-Z0-9_]{1,})=("[^"\n]*"|'[^'\n]*'|[^\s"'`;,)\]}]+)/g,ake=/(?:\/(?:[A-Za-z0-9._@~+-]|%[0-9A-Fa-f]{2})+){2,}/g});var Nt,kP,ra=F(()=>{"use strict";Nt="1.0.113",kP=`linzumi ${Nt}`});import{closeSync as Q9,existsSync as TP,mkdirSync as pke,openSync as X9,readFileSync as Y9,renameSync as fke,unlinkSync as kd,writeFileSync as mke,writeSync as e3}from"node:fs";import{dirname as n3,join as hke}from"node:path";function PP(e){return e instanceof xs||e instanceof Error&&e.name===r3&&"lockPath"in e&&"heldBy"in e}function IP(e,n=Hn(),t){let r=t===void 0?encodeURIComponent(e):gd(t);return hke(n3(n),"runners",`${r}.lock`)}function w_(e){let n=IP(e.machineId,e.configPath,e.linzumiUrl),t=e.isPidAlive??c3,r=e.now??(()=>new Date),o=e.killPid??Ike,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:zn(e.linzumiUrl)},...e.launchSource===void 0?{}:{launchSource:e.launchSource},startedAt:i,heartbeatAt:i,cliVersion:e.cliVersion??Nt,...e.gracefulUpdateDrain===!0?{gracefulUpdateDrain:!0}:{}};_ke(e.machineId,e.configPath,e.linzumiUrl,t,r,o,e.onTakeover),bke(n,s,t,r,o,e.onTakeover,e.beforeReadExistingLock,e.beforeReplaceStaleLock);let a=()=>o3(n,s,r),d=setInterval(a,t3);return d.unref(),{path:n,record:s,heartbeat:a,release:()=>{clearInterval(d),d3(n,s)}}}function o3(e,n,t){try{let r=S_(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`;mke(i,`${JSON.stringify(o,null,2)}
1476
+ `),J=z.map(H=>H.parsed.spooled_at).filter(H=>typeof H=="string"),Z={bundle_name:"runner-diagnostics-spool",exported_at:new Date(a()).toISOString(),idempotency_key:D,diagnostics:{kind:"runner_diagnostic_log",spool_batch:!0,idempotency_key:D,entry_count:z.length,...J.length===0?{}:{first_spooled_at:J[0],last_spooled_at:J[J.length-1]}},telemetry_ndjson:U};try{await k(Z)}catch(H){l("runner_diagnostics.spool_upload_failed",{file:E,remaining_batches:p().length,error:H instanceof Error?H.message:String(H)});break}R+=1,T+=z.length;try{V9(E)}catch(H){l("runner_diagnostics.spool_delete_failed",{file:E,error:H instanceof Error?H.message:String(H)})}}}catch(O){l("runner_diagnostics.spool_drain_failed",{error:O instanceof Error?O.message:String(O)})}finally{c=!1}return{uploadedBatches:R,uploadedEntries:T,remainingBatches:p().length,skipped:!1}},pendingBatchFiles:p}}var eke,nke,tke,rke,oke,W9,ske,ake,G9=F(()=>{"use strict";cx();eke=32*1024*1024,nke=10080*60*1e3,tke=4*1024*1024,rke=1024*1024,oke="spool.jsonl",W9=/^batch-(\d{1,16})-[0-9a-f-]+\.jsonl$/;ske=/\b([A-Z][A-Z0-9_]{1,})=("[^"\n]*"|'[^'\n]*'|[^\s"'`;,)\]}]+)/g,ake=/(?:\/(?:[A-Za-z0-9._@~+-]|%[0-9A-Fa-f]{2})+){2,}/g});var Nt,kP,ra=F(()=>{"use strict";Nt="1.0.114",kP=`linzumi ${Nt}`});import{closeSync as Q9,existsSync as TP,mkdirSync as pke,openSync as X9,readFileSync as Y9,renameSync as fke,unlinkSync as kd,writeFileSync as mke,writeSync as e3}from"node:fs";import{dirname as n3,join as hke}from"node:path";function PP(e){return e instanceof xs||e instanceof Error&&e.name===r3&&"lockPath"in e&&"heldBy"in e}function IP(e,n=Hn(),t){let r=t===void 0?encodeURIComponent(e):gd(t);return hke(n3(n),"runners",`${r}.lock`)}function w_(e){let n=IP(e.machineId,e.configPath,e.linzumiUrl),t=e.isPidAlive??c3,r=e.now??(()=>new Date),o=e.killPid??Ike,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:zn(e.linzumiUrl)},...e.launchSource===void 0?{}:{launchSource:e.launchSource},startedAt:i,heartbeatAt:i,cliVersion:e.cliVersion??Nt,...e.gracefulUpdateDrain===!0?{gracefulUpdateDrain:!0}:{}};_ke(e.machineId,e.configPath,e.linzumiUrl,t,r,o,e.onTakeover),bke(n,s,t,r,o,e.onTakeover,e.beforeReadExistingLock,e.beforeReplaceStaleLock);let a=()=>o3(n,s,r),d=setInterval(a,t3);return d.unref(),{path:n,record:s,heartbeat:a,release:()=>{clearInterval(d),d3(n,s)}}}function o3(e,n,t){try{let r=S_(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`;mke(i,`${JSON.stringify(o,null,2)}
1477
1477
  `,"utf8"),fke(i,e)}catch{}}function Cd(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>gke?{state:"wedged",reason:e.heartbeatAt===void 0?"missing_heartbeat":"stale_heartbeat"}:{state:"live"}}function i3(e,n,t,r){return t!==v_||e.launchSource!==v_?!1:r.state!=="live"?!0:yke(n,e.cliVersion)>0}function yke(e,n){let t=Z9(e),r=Z9(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:xke(t.prerelease,r.prerelease)}function Z9(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=[CP(i[0]),CP(i[1]),CP(i[2])],a=o===""?[]:o.split(".").filter(d=>d!=="");return{core:s,prerelease:a}}function CP(e){let n=Number.parseInt(e??"",10);return Number.isInteger(n)&&n>=0?n:0}function xke(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 s3(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 _ke(e,n,t,r,o,i,s){if(t===void 0||xr(t)!==xr(Kt))return;let a=IP(e,n),d=S_(a);if(d!==void 0){if(Cd(d,r,o).state==="live")throw new xs(a,d);a3(a,r,()=>{let u=TP(a)?RP(a):void 0;if(u===void 0)return;let c=Cd(u,r,o);if(c.state==="live")throw new xs(a,u);c.state==="wedged"&&s3(a,u,c.reason,i,s),kd(a)})}}function bke(e,n,t,r,o,i,s,a){if(b_(e,n))return;s?.();let d=S_(e);if(d===void 0){if(b_(e,n))return;throw new Error(`another Linzumi runner lock appeared while starting: ${e}`)}if(Cd(d,t,r).state==="live")throw new xs(e,d);a?.(),a3(e,t,()=>{let u=TP(e)?RP(e):void 0;if(u!==void 0){let c=Cd(u,t,r);if(c.state==="live")throw new xs(e,u);c.state==="wedged"&&s3(e,u,c.reason,o,i),kd(e)}if(!b_(e,n))throw new Error(`another Linzumi runner lock appeared while starting: ${e}`)})}function b_(e,n){pke(n3(e),{recursive:!0});try{let t=X9(e,"wx");try{e3(t,`${JSON.stringify(n,null,2)}
1478
1478
  `)}finally{Q9(t)}return!0}catch(t){if(Zc(t,"EEXIST"))return!1;throw t}}function a3(e,n,t){let r=`${e}.replace`;for(;;)try{let o=X9(r,"wx");try{e3(o,`${process.pid}
1479
1479
  `),t()}finally{Q9(o),kd(r)}return}catch(o){if(Zc(o,"EEXIST")){let i=wke(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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "1.0.113",
3
+ "version": "1.0.114",
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
+ });