@linzumi/cli 1.0.204 → 1.0.205

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.204",
3
+ "version": "1.0.205",
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": {
@@ -13,14 +13,14 @@
13
13
  "scripts"
14
14
  ],
15
15
  "scripts": {
16
- "build": "node scripts/build.mjs",
16
+ "build": "rescript build ../linzumi-cli-rescript && node scripts/build.mjs",
17
17
  "bump-version": "node scripts/bump-cli-version.mjs",
18
18
  "codex:history-table": "node scripts/codex_history_table.mjs",
19
- "test": "rescript build ../linzumi-cli-rescript && rescript build ../../kandan/server_v2/web/replay_harness && vitest run --config vitest.config.mjs",
19
+ "test": "rescript build ../linzumi-cli-rescript && node --import tsx --test ../linzumi-cli-rescript/src/parity/McpToolCallBoundaryTest.res.mjs && rescript build ../../kandan/server_v2/web/replay_harness && vitest run --config vitest.config.mjs",
20
20
  "typecheck": "tsgo --noEmit -p tsconfig.source.json",
21
21
  "typecheck:all": "tsgo --noEmit -p tsconfig.json",
22
22
  "typecheck:watch": "tsgo --noEmit -w -p tsconfig.source.json",
23
- "prepack": "node scripts/build.mjs",
23
+ "prepack": "rescript build -q ../linzumi-cli-rescript && node scripts/build.mjs",
24
24
  "pack:dry-run": "npm pack --dry-run",
25
25
  "record:agent-fixtures": "tsx scripts/record-agent-replay-fixtures.ts",
26
26
  "normalize:agent-fixture": "tsx scripts/normalize-agent-replay-fixture.ts",
@@ -21,12 +21,72 @@
21
21
  // SECRETS: the oracle only ever sees the conformance suite's synthetic
22
22
  // payloads. The output is a local test artifact (.differential/ is
23
23
  // gitignored, never published) and stays unminified for debuggability.
24
- import { mkdir } from 'node:fs/promises';
24
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
25
25
  import { dirname, join } from 'node:path';
26
26
  import { fileURLToPath } from 'node:url';
27
27
  import { build } from 'esbuild';
28
28
 
29
29
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
30
+ const providerSlasPath = join(
31
+ packageRoot,
32
+ 'src',
33
+ 'fateLadderProviderSlas.json'
34
+ );
35
+ const generatedProviderSlasPath = join(
36
+ packageRoot,
37
+ '..',
38
+ 'linzumi-cli-rescript',
39
+ 'src',
40
+ 'generated',
41
+ 'CommanderFateLadderProviderSlas.res'
42
+ );
43
+
44
+ function positiveFiniteNumber(value, label) {
45
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
46
+ throw new Error(`invalid provider fate SLA ${label}: ${String(value)}`);
47
+ }
48
+ return value;
49
+ }
50
+
51
+ const providerSlas = JSON.parse(await readFile(providerSlasPath, 'utf8'));
52
+ const defaultMaxLadderMs = positiveFiniteNumber(
53
+ providerSlas?.default?.maxLadderMs,
54
+ 'default.maxLadderMs'
55
+ );
56
+ const waferMaxLadderMs = positiveFiniteNumber(
57
+ providerSlas?.wafer?.maxLadderMs,
58
+ 'wafer.maxLadderMs'
59
+ );
60
+ const generatedProviderSlas = `// GENERATED from packages/linzumi-cli/src/fateLadderProviderSlas.json by
61
+ // packages/linzumi-cli/scripts/build-core-fate-ladder-parity-oracle.mjs.
62
+ // Do not edit this file by hand.
63
+ // Spec: plans/2026-08-07-wafer-provider-aware-fate-budget.spec.md
64
+
65
+ let defaultMaxLadderMs = ${defaultMaxLadderMs}.
66
+ let waferMaxLadderMs = ${waferMaxLadderMs}.
67
+
68
+ let maxLadderMsForModelProvider = (modelProvider: option<string>): float =>
69
+ switch modelProvider {
70
+ | Some("wafer") => waferMaxLadderMs
71
+ | Some(_) | None => defaultMaxLadderMs
72
+ }
73
+ `;
74
+
75
+ if (process.argv.includes('--write-provider-slas')) {
76
+ await writeFile(generatedProviderSlasPath, generatedProviderSlas, 'utf8');
77
+ } else {
78
+ const trackedProviderSlas = await readFile(generatedProviderSlasPath, 'utf8');
79
+ if (trackedProviderSlas !== generatedProviderSlas) {
80
+ throw new Error(
81
+ 'generated CommanderFateLadderProviderSlas.res is stale; run build-core-fate-ladder-parity-oracle.mjs --write-provider-slas'
82
+ );
83
+ }
84
+ }
85
+
86
+ if (process.argv.includes('--check-provider-slas-only')) {
87
+ process.stdout.write(generatedProviderSlasPath + '\n');
88
+ process.exit(0);
89
+ }
30
90
 
31
91
  // PARITY SCOPE (owner ruling 2026-07-18, failover becomes a user-decision
32
92
  // capability): the compared surface is the SHARED F2 recovery engine only.
@@ -229,7 +289,7 @@ function runCase(input) {
229
289
  rateLimitHoldReasonPrefix,
230
290
  };
231
291
  case 'budgets':
232
- return fateLadderBudgets(input.env ?? {});
292
+ return fateLadderBudgets(input.env ?? {}, input.modelProvider);
233
293
  case 'holdReason':
234
294
  return rateLimitHoldReason(input.resetAtMs);
235
295
  case 'resetFromNotification':
@@ -278,31 +278,130 @@ run().then(
278
278
  );
279
279
  `;
280
280
 
281
+ const questionCardDriverSource = `
282
+ /*
283
+ * Spec: spec/commander-completion-pipeline-rules.md
284
+ * (\`:commander-completion-pipeline/rule-07-claude-events-publish\`).
285
+ * Relationship: Batch driver over the active TS Claude question-card model;
286
+ * it contains no copied approval behavior.
287
+ */
288
+ import {
289
+ claudeCodeQuestionAnsweredInput,
290
+ claudeCodeToolApprovalRequest,
291
+ claudeCodeUserQuestionStreamKey,
292
+ claudeCodeUserQuestionStructuredMessage,
293
+ parseClaudeCodeUserQuestionRequest,
294
+ } from './src/claudeCodeUserQuestions';
295
+ import type { ClaudeCodeUserQuestionAnswer } from './src/claudeCodeUserQuestions';
296
+ import type { ClaudeCodePermissionRequest } from './src/claudeCodeSession';
297
+ import type { JsonObject } from './src/protocol';
298
+
299
+ type OracleCase = {
300
+ readonly op?: 'approval' | 'question';
301
+ readonly request: ClaudeCodePermissionRequest;
302
+ readonly answers?: readonly ClaudeCodeUserQuestionAnswer[];
303
+ readonly sessionId: string | undefined;
304
+ readonly sourceSeq: number;
305
+ readonly answerState: JsonObject;
306
+ readonly streamState: 'streaming' | 'completed' | 'failed' | 'interrupted';
307
+ };
308
+
309
+ function handle(testCase: OracleCase): JsonObject {
310
+ if (testCase.op === 'question') {
311
+ const parsed = parseClaudeCodeUserQuestionRequest(testCase.request);
312
+ if (parsed.type === 'question' && testCase.answers !== undefined) {
313
+ return {
314
+ parsed,
315
+ answeredInput: claudeCodeQuestionAnsweredInput(
316
+ testCase.request.input,
317
+ parsed.request,
318
+ testCase.answers
319
+ ),
320
+ };
321
+ }
322
+ return { parsed };
323
+ }
324
+ const approval = claudeCodeToolApprovalRequest(testCase.request);
325
+ return {
326
+ approval,
327
+ structured: claudeCodeUserQuestionStructuredMessage({
328
+ request: approval.request,
329
+ sessionId: testCase.sessionId,
330
+ sourceSeq: testCase.sourceSeq,
331
+ answerState: testCase.answerState,
332
+ streamState: testCase.streamState,
333
+ }),
334
+ streamKey: claudeCodeUserQuestionStreamKey(testCase.request.requestId),
335
+ decisions: Object.fromEntries(
336
+ approval.choices.map((choice) => [choice.decision, choice.permissionDecision])
337
+ ),
338
+ };
339
+ }
340
+
341
+ const chunks: Buffer[] = [];
342
+ for await (const chunk of process.stdin) {
343
+ chunks.push(Buffer.from(chunk));
344
+ }
345
+ const cases = JSON.parse(Buffer.concat(chunks).toString('utf8')) as OracleCase[];
346
+ process.stdout.write(JSON.stringify(cases.map(handle)));
347
+
348
+ `;
349
+
281
350
  const outDir = join(packageRoot, '.differential');
282
351
  const outPath = join(outDir, 'harness-claude-parity-oracle.mjs');
352
+ const buildQuestionCard = process.argv[2] === 'question-card';
283
353
 
284
354
  await mkdir(outDir, { recursive: true });
285
355
 
286
- await build({
287
- stdin: {
288
- contents: driverSource,
289
- resolveDir: packageRoot,
290
- sourcefile: 'harness-claude-parity-driver.ts',
291
- loader: 'ts',
292
- },
293
- bundle: true,
294
- platform: 'node',
295
- target: 'node20',
296
- format: 'esm',
297
- outfile: outPath,
298
- minify: false,
299
- sourcemap: false,
300
- legalComments: 'none',
301
- logLevel: 'silent',
302
- external: ['ws', 'undici', 'blessed'],
303
- alias: {
304
- '@anthropic-ai/claude-agent-sdk': stubModulePath,
305
- },
306
- });
356
+ if (!buildQuestionCard) {
357
+ await build({
358
+ stdin: {
359
+ contents: driverSource,
360
+ resolveDir: packageRoot,
361
+ sourcefile: 'harness-claude-parity-driver.ts',
362
+ loader: 'ts',
363
+ },
364
+ bundle: true,
365
+ platform: 'node',
366
+ target: 'node20',
367
+ format: 'esm',
368
+ outfile: outPath,
369
+ minify: false,
370
+ sourcemap: false,
371
+ legalComments: 'none',
372
+ logLevel: 'silent',
373
+ external: ['ws', 'undici', 'blessed'],
374
+ alias: {
375
+ '@anthropic-ai/claude-agent-sdk': stubModulePath,
376
+ },
377
+ });
378
+ }
307
379
 
308
- process.stdout.write(outPath + '\n');
380
+ const questionCardOutPath = join(
381
+ outDir,
382
+ 'claude-question-card-parity-oracle.mjs'
383
+ );
384
+
385
+ if (buildQuestionCard) {
386
+ await build({
387
+ stdin: {
388
+ contents: questionCardDriverSource,
389
+ resolveDir: packageRoot,
390
+ sourcefile: 'claude-question-card-parity-driver.ts',
391
+ loader: 'ts',
392
+ },
393
+ bundle: true,
394
+ platform: 'node',
395
+ target: 'node22',
396
+ format: 'esm',
397
+ outfile: questionCardOutPath,
398
+ minify: false,
399
+ sourcemap: 'inline',
400
+ legalComments: 'none',
401
+ logLevel: 'silent',
402
+ });
403
+ }
404
+
405
+ process.stdout.write(
406
+ (buildQuestionCard ? questionCardOutPath : outPath) + '\n'
407
+ );
@@ -503,6 +503,10 @@ function buildersFromSpec(spec, recordedReplies) {
503
503
  ...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
504
504
  };
505
505
  },
506
+ // Spec: spec/commander-recovery-rules.md. These generic parity entries
507
+ // exercise must_ack/best_effort transport acknowledgement, never the
508
+ // approval-failure consensus protocol.
509
+ replyPolicy: { kind: 'transport_ack' },
506
510
  ...(config.onReply === 'record'
507
511
  ? {
508
512
  onReply: (data, reply) => {
package/scripts/build.mjs CHANGED
@@ -1,8 +1,9 @@
1
- // Spec: plans/2026-05-17-linzumi-cli-node-toolchain-note.md
1
+ // Spec: plans/2026-05-17-linzumi-cli-node-toolchain-note.md and
2
+ // plans/2026-08-06-hosted-runner-proper-lockfile-boot-incident.md
2
3
  // Relationship: Builds the published CLI package with Node-hosted tooling.
3
4
  import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
5
  import { dirname, join } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
7
  import { build } from 'esbuild';
7
8
 
8
9
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -326,3 +327,10 @@ try {
326
327
  '}\n'
327
328
  );
328
329
  }
330
+
331
+ // LOAD-SMOKE TOOTH (prod RCA 2026-08-06): parsing alone did not catch a
332
+ // createRequire dependency that was absent from the Nix image. The published
333
+ // package has a separate build recipe, so import its fully installed commander
334
+ // bundle here as well; any eager MODULE_NOT_FOUND or other load-time failure
335
+ // fails the build.
336
+ await import(pathToFileURL(join(packageRoot, 'dist/impl-ts/index.js')).href);