@linzumi/cli 1.0.256 → 1.0.257

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.256",
3
+ "version": "1.0.257",
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": {
@@ -78,6 +78,14 @@ import {
78
78
  staleStartInstanceAgeMs,
79
79
  staleStartInstanceWindowMs,
80
80
  } from './src/runner';
81
+ import {
82
+ registerRequiredAdtCandidate, acknowledgeRequiredAdtCandidate,
83
+ consumeRequiredAdtAdmission, closeRequiredAdtAdmissionForDisconnect,
84
+ reconcileRequiredAdtCandidates, requiredTransportFinalAdmission,
85
+ findRequiredAdtCandidate, releaseRequiredAdtCandidate,
86
+ recoverRetainedTerminalDebt,
87
+ } from './src/channelSession';
88
+ import {adoptAdtStreamBinding, installAdtPublicationBinding} from './src/pipeline/contracts';
81
89
 
82
90
  // --- Placeholder + snapshot plumbing --------------------------------------------
83
91
 
@@ -524,8 +532,110 @@ async function handleRespawn(testCase) {
524
532
 
525
533
  // --- Dispatch --------------------------------------------------------------------------------
526
534
 
535
+ // Spec: spec/commander-recovery-rules.md rule01 and rule05; retained terminal
536
+ // debt is not an unknown provider-start ACK and cannot reset unrelated jobs.
537
+ async function terminalDebtStorm(testCase) {
538
+ const binding = {session_id: 3600, lease_epoch: 1, next_frame_index: 39,
539
+ checkpoint_config: {version: 3, thresholds: {max_bytes: 1, max_deltas: 1, max_interval_ms: 2000}}};
540
+ const adopted = adoptAdtStreamBinding(binding, 44, 0);
541
+ if (adopted.kind !== 'accepted') throw new Error('terminal debt fixture binding refused');
542
+ const session = adopted.session;
543
+ const logs = [];
544
+ let resets = 0;
545
+ let restarts = 0;
546
+ let reconciliations = 0;
547
+ const transport = {
548
+ push: async (_topic, event) => {
549
+ if (event === 'adt_binding_ack') return {status: 'ok', response: {ok: true}};
550
+ if (event === 'adt_binding_reconcile') {
551
+ reconciliations += 1;
552
+ if(testCase.control === 'repair-unavailable' && reconciliations > 1) return {status:'ok',response:{ok:true,adt:{verdict:'still_unavailable'}}};
553
+ return {status: 'ok', response: {
554
+ ok: true, adt: {verdict: 'already_applied', binding: testCase.control === 'malformed-binding' ? {session_id: 3600} : binding},
555
+ }};
556
+ }
557
+ throw new Error('unexpected terminal-debt fixture event: ' + event);
558
+ },
559
+ hardReset: () => {resets += 1; return true;},
560
+ };
561
+ const candidate = registerRequiredAdtCandidate({transport,
562
+ identity: {thread_id: 'synthetic-terminal-debt', source_seq: 44, session_id: 3600,
563
+ lease_epoch: 1, checkpoint_config_version: 3},
564
+ installFreshBinding: raw => {
565
+ const fresh = adoptAdtStreamBinding(raw, 44, 0);
566
+ if (fresh.kind !== 'accepted') return false;
567
+ if (testCase.localState === 'idle') return false;
568
+ return installAdtPublicationBinding(session, fresh.session.binding, true).kind === 'accepted';
569
+ },
570
+ publicationSession: () => testCase.localState === 'idle' ? undefined : session,
571
+ failedTerminalEntries: () => {
572
+ const entries = [{entrySeq: 61, kind: 'adt.terminal_attempt',
573
+ data: {seq: 44, status: 'processed', turn_id: 'synthetic-completed-turn',
574
+ terminal_frame: {session_id: testCase.control === 'foreign-debt' ? 3601 : 3600, lease_epoch: 1, frame_index: 40,
575
+ frame_kind: 'terminal', assistant_phase: 'final_answer',
576
+ source_stream_key: 'synthetic-rejected-key', body: 'Synthetic completed answer.'}}}];
577
+ if (testCase.control === 'missing-debt') return [];
578
+ if (testCase.control === 'ambiguous-debt') return [...entries, {...entries[0], entrySeq: 62}];
579
+ return entries;
580
+ },
581
+ onUnrecoverable: () => {restarts += 1;},
582
+ log: event => logs.push(event),
583
+ });
584
+ try {
585
+ await acknowledgeRequiredAdtCandidate({topic: 'local_runner:fixture', payload: adopted.ackPayload, ticket: candidate});
586
+ if (testCase.control !== 'unconsumed-provider' && consumeRequiredAdtAdmission(candidate).kind !== 'admitted') throw new Error('fixture provider was not admitted');
587
+ session.emitter.fenced = true;
588
+ session.done = true;
589
+ for (let reconnect = 0; reconnect < 3; reconnect += 1) {
590
+ closeRequiredAdtAdmissionForDisconnect(transport);
591
+ await reconcileRequiredAdtCandidates({transport, topic: 'local_runner:fixture'});
592
+ }
593
+ return {resets, restarts, retained: findRequiredAdtCandidate(transport,
594
+ identity => identity.thread_id === 'synthetic-terminal-debt' && identity.source_seq === 44) === candidate,
595
+ providerEffect: candidate.providerEffect, verdict: candidate.verdict,
596
+ nextProviderAdmission: consumeRequiredAdtAdmission(candidate).kind,
597
+ admission: requiredTransportFinalAdmission(transport).kind, logs};
598
+ } finally {releaseRequiredAdtCandidate(candidate);}
599
+ }
600
+
527
601
  async function handle(testCase) {
528
602
  switch (testCase.op) {
603
+ case 'terminalDebtCold': {
604
+ const events = [];
605
+ let repairs = 0;
606
+ const identity = {thread_id:'synthetic-terminal-debt',source_seq:44,session_id:3600,lease_epoch:1,checkpoint_config_version:7};
607
+ const binding = {session_id:3600,lease_epoch:2,next_frame_index:39,checkpoint_config:{version:8,thresholds:{max_bytes:512,max_deltas:1,max_interval_ms:2000}}};
608
+ const transport = {push:async (_topic,event,payload) => {
609
+ events.push(event);
610
+ if(event === 'terminal_debt_identity') {
611
+ if(testCase.control === 'disconnect') closeRequiredAdtAdmissionForDisconnect(transport);
612
+ return {status:'ok',response:{ok:true,identity:{...identity,...(testCase.control === 'foreign' ? {thread_id:'foreign-thread'} : {})}}};
613
+ }
614
+ if(event === 'adt_binding_reconcile') {
615
+ if(payload.checkpoint_config_version !== 7 || payload.lease_epoch !== 1) throw new Error('cold lookup changed original generation');
616
+ return {status:'ok',response:{ok:true,adt:{verdict:'already_applied',binding}}};
617
+ }
618
+ throw new Error('cold recovery attempted unexpected event: '+event);
619
+ }};
620
+ const pipeline = {
621
+ failedTerminalEntries:()=>[{entrySeq:61,kind:'adt.terminal_attempt',data:{seq:44,status:'processed',turn_id:'synthetic-completed-turn',terminal_frame:{session_id:3600,lease_epoch:1,frame_index:40,frame_kind:'terminal',assistant_phase:'final_answer',source_stream_key:'wrong-key',body:'Synthetic completed answer.'}}}],
622
+ repairTerminalDebt:async request => {
623
+ if(request.sessionId !== 3600 || request.originalLeaseEpoch !== 1 || request.sourceSeq !== 44 || request.binding !== binding || request.authoritativeTerminal) throw new Error('cold repair identity mismatch');
624
+ if(!request.authorize({seq:44})) throw new Error('cold repair unexpectedly unauthorized');
625
+ repairs += 1;
626
+ return {kind:'ready',id:'fixture',serializedData:'fixture'};
627
+ },
628
+ };
629
+ try {
630
+ await recoverRetainedTerminalDebt({transport,topic:'local_runner:fixture',threadId:identity.thread_id,pipeline,log:()=>{}});
631
+ return {events,repairs};
632
+ } finally {
633
+ const candidate = findRequiredAdtCandidate(transport, value => value.thread_id === identity.thread_id);
634
+ if(candidate !== undefined) releaseRequiredAdtCandidate(candidate);
635
+ }
636
+ }
637
+ case 'terminalDebtStorm':
638
+ return await terminalDebtStorm(testCase);
529
639
  case 'controlDispatch':
530
640
  return await handleControlDispatch(testCase);
531
641
  case 'staleness':
@@ -45,7 +45,7 @@ import {
45
45
  preReduceSnapshotItems,
46
46
  rawSnapshotItemsForTurn,
47
47
  } from './src/pipeline/integration';
48
- import { outboxEntryKinds } from './src/pipeline/contracts';
48
+ import { outboxEntryKinds, adoptAdtStreamBinding } from './src/pipeline/contracts';
49
49
  import { phoenixPushErrors } from './src/phoenix';
50
50
  import {
51
51
  rememberTurnTraceparent,
@@ -306,8 +306,74 @@ async function runSessionCase(caseInput) {
306
306
  return { trace };
307
307
  }
308
308
 
309
+ // Spec: spec/adt-final-answer-identity-rules.md, rules 1–3.
310
+ // Actual producer/outbox boundary; assertions remain in the ReScript owner.
311
+ async function runFinalCheckpointIdentity(input) {
312
+ const adoption = adoptAdtStreamBinding({session_id: 3600, lease_epoch: 1, next_frame_index: 37,
313
+ checkpoint_config: {version: 3, thresholds: {max_bytes: 1, max_deltas: 1, max_interval_ms: 2000}}}, 44, Date.now());
314
+ if (adoption.kind !== 'accepted') throw new Error('fixture_adoption_refused');
315
+ const pushes = [];
316
+ const logs = [];
317
+ let finishProvider;
318
+ const providerTerminal = new Promise(resolve => {finishProvider = resolve;});
319
+ const pipeline = createSessionPipeline({
320
+ instanceId: 'synthetic-yc-identity', threadKey: 'synthetic-final-identity', persistDir: undefined,
321
+ messageStateReplyShape: {kind: 'inner_response'},
322
+ wire: () => ({workspaceSlug: 'fixture', channelSlug: 'office', kandanThreadId: 'fixture-thread',
323
+ codexThreadId: 'fixture-provider-thread', rootSeq: 1}),
324
+ runnerPayload: (eventType, codexThreadId, sourceSeq, extra) => ({metadata: {local_codex_runner: {
325
+ ...extra, event_type: eventType, codex_thread_id: codexThreadId, source_message_seq: sourceSeq,
326
+ }}}),
327
+ sourceSeqForTurn: () => 44,
328
+ adtAuthorityForSourceSeq: seq => seq === 44 ? adoption.session : undefined,
329
+ resolveSnapshot: async () => ({items: mergeSnapshotItems(input.readItems, input.sessionItems)}),
330
+ onTurnTerminal: () => finishProvider(),
331
+ log: (event, fields) => logs.push({event, fields}), statsIntervalMs: 0,
332
+ push: async (event, payload) => {
333
+ pushes.push({event, payload});
334
+ if (event === 'terminal_attempt') return {terminal_attempt: {
335
+ frame: {accepted: true}, message_state: {disposition: 'applied', thread_id: 'fixture-thread', source_seq: 44},
336
+ }};
337
+ return {seq: pushes.length, accepted: true};
338
+ },
339
+ });
340
+ try {
341
+ pipeline.submitNotification('item/agentMessage/delta', {threadId: 'fixture-provider-thread',
342
+ turnId: 'fixture-turn', itemId: 'msg-live-final', delta: input.body});
343
+ pipeline.submitNotification('item/completed', {threadId: 'fixture-provider-thread', turnId: 'fixture-turn',
344
+ item: {id: 'msg-live-final', type: 'agentMessage', text: input.body, phase: 'final_answer'}});
345
+ await pipeline.flushOutbox(1000);
346
+ pipeline.submitNotification('turn/completed', {threadId: 'fixture-provider-thread', turn: {id: 'fixture-turn'}});
347
+ await providerTerminal;
348
+ await pipeline.flushOutbox(1000);
349
+ const metrics = pipeline.metrics();
350
+ return {
351
+ standalone: pushes.filter(push => push.event === 'adt_frame').map(push => push.payload),
352
+ terminals: pushes.filter(push => push.event === 'terminal_attempt').map(push => push.payload.terminal_frame),
353
+ depth: metrics.depth, failures: metrics.totalPermanentFailures,
354
+ faults: logs.filter(log => /rejected|refused|failed/.test(log.event)),
355
+ };
356
+ } finally { await pipeline.close(1000); }
357
+ }
358
+
309
359
  async function runCase(input) {
310
360
  switch (input.kind) {
361
+ // Spec: spec/commander-recovery-rules.md authenticated terminal evidence.
362
+ case 'terminalRepairReceipt': {
363
+ const remembered = [];
364
+ const builder = createSessionPipelineBuilders({
365
+ instanceId: 'terminal-repair-fixture',
366
+ wire: () => ({workspaceSlug: 'synthetic', channelSlug: 'synthetic',
367
+ kandanThreadId: 'repair-thread', codexThreadId: 'provider-thread'}),
368
+ runnerPayload: () => ({}),
369
+ messageStateReplyShape: {kind: 'full_reply'},
370
+ log: () => {},
371
+ })['adt.terminal_attempt'];
372
+ const context = {remember: (key, value) => remembered.push({key, value}), recall: () => undefined};
373
+ const verdict = builder.replyPolicy.adjudicateReply(input.data, input.reply, context);
374
+ if (verdict.kind === 'accept') builder.onReply(input.data, input.reply, context);
375
+ return {accepted: verdict.kind === 'accept', remembered};
376
+ }
311
377
  case 'constants':
312
378
  return {
313
379
  defaultOutboxMaxInFlightPushes,
@@ -325,6 +391,8 @@ async function runCase(input) {
325
391
  return mergeSnapshotItems(input.readItems, input.sessionItems);
326
392
  case 'sessionRun':
327
393
  return runSessionCase(input);
394
+ case 'finalCheckpointIdentity':
395
+ return runFinalCheckpointIdentity(input);
328
396
  default:
329
397
  return { error: 'unknown case kind ' + String(input.kind) };
330
398
  }
@@ -41,8 +41,10 @@ import {
41
41
  mkdtempSync,
42
42
  mkdirSync,
43
43
  readdirSync,
44
+ rmSync,
44
45
  readFileSync,
45
46
  realpathSync,
47
+ renameSync,
46
48
  statSync,
47
49
  writeFileSync,
48
50
  } from 'node:fs';
@@ -95,8 +97,10 @@ import {
95
97
  import {
96
98
  outboxJournalPath,
97
99
  recoverOutboxJournal,
100
+ createOutboxJournal,
98
101
  } from './src/pipeline/outboxJournal';
99
102
  import { createThreadOutbox } from './src/pipeline/outbox';
103
+ import { createSessionPipelineBuilders, enqueueTerminalDebtRepair } from './src/pipeline/integration';
100
104
  import {
101
105
  applyResumeDispatchOutcome,
102
106
  commitLiveAcceptedSourceSeq,
@@ -484,6 +488,29 @@ function handleOutboxJournal(testCase) {
484
488
  return substitute({ recovered, events }, dir, dirPlaceholder);
485
489
  }
486
490
 
491
+ // Spec: spec/commander-recovery-rules.md rule01. Failed terminal publication
492
+ // remains evidence/debt, never ordinary replay or an acknowledged terminal.
493
+ async function handleFailedTerminalJournal(testCase) {
494
+ const dir = scratch();
495
+ const path = join(dir, 'failed-terminal.outbox.jsonl');
496
+ try {
497
+ writeFileSync(path, testCase.contents, 'utf8');
498
+ const events = [];
499
+ let recovered = recoverOutboxJournal(path, collectingLog(events, dir));
500
+ if (testCase.compact) {
501
+ const snapshot = recovered;
502
+ const journal = createOutboxJournal({path, compactionThreshold: 1,
503
+ snapshot: () => snapshot, log: collectingLog(events, dir)});
504
+ journal.appendAck(63);
505
+ await journal.settled();
506
+ await journal.close();
507
+ recovered = recoverOutboxJournal(path, collectingLog(events, dir));
508
+ }
509
+ return {pendingEntries: recovered.pendingEntries,
510
+ failedTerminalEntries: recovered.failedTerminalEntries, counters: recovered.counters, events};
511
+ } finally {rmSync(dir, {recursive: true, force: true});}
512
+ }
513
+
487
514
  // --- Outbox flow scenarios ---------------------------------------------------------------------
488
515
 
489
516
  function buildersFromSpec(spec, recordedReplies) {
@@ -548,6 +575,14 @@ async function runOutboxPhaseInner(phase, shared) {
548
575
  const events = [];
549
576
  const log = collectingLog(events, shared.dir);
550
577
  let nowMs = phase.nowMs ?? 1_000;
578
+ const builders = buildersFromSpec(phase.builders ?? shared.builders, recordedReplies);
579
+ if (phase.terminalRepairBuilder === true) {
580
+ builders['adt.terminal_attempt'] = createSessionPipelineBuilders({
581
+ instanceId: 'repair-fixture', wire: () => ({workspaceSlug: 'synthetic', channelSlug: 'synthetic',
582
+ kandanThreadId: shared.threadKey, codexThreadId: 'synthetic-provider'}),
583
+ runnerPayload: () => ({}), messageStateReplyShape: {kind: 'full_reply'}, log,
584
+ })['adt.terminal_attempt'];
585
+ }
551
586
  const outbox = createThreadOutbox({
552
587
  threadKey: phase.threadKey ?? shared.threadKey,
553
588
  push: async (event, payload, timeoutMs, onSent) => {
@@ -570,7 +605,7 @@ async function runOutboxPhaseInner(phase, shared) {
570
605
  }
571
606
  throw failure;
572
607
  },
573
- builders: buildersFromSpec(phase.builders ?? shared.builders, recordedReplies),
608
+ builders,
574
609
  persistDir: shared.persistDir,
575
610
  retryDelaysMs: phase.retryDelaysMs ?? [1, 1],
576
611
  isRetryBudgetExempt: (error) => error.name === 'ScriptedBudgetExempt',
@@ -613,6 +648,13 @@ async function runOutboxPhaseInner(phase, shared) {
613
648
  )
614
649
  );
615
650
  break;
651
+ case 'cycleReceipt': {
652
+ const receipt = outbox.recallDurableFact(action.key);
653
+ if(receipt === null || typeof receipt !== 'object') throw new Error('cyclic receipt fixture missing stored object');
654
+ receipt.cycle = receipt;
655
+ results.push({defined:false});
656
+ break;
657
+ }
616
658
  case 'pause':
617
659
  results.push(outcome(() => outbox.pause()));
618
660
  break;
@@ -654,6 +696,52 @@ async function runOutboxPhaseInner(phase, shared) {
654
696
  await outbox.journalSettled();
655
697
  results.push({ defined: false });
656
698
  break;
699
+ // Spec: spec/commander-recovery-rules.md. Only this scenario's fresh
700
+ // scratch directory is moved; a real filesystem refusal tests both implementations.
701
+ case 'blockJournalWrites':
702
+ case 'restoreJournalWrites': {
703
+ await outbox.journalSettled();
704
+ const blocked = shared.persistDir + '.blocked';
705
+ if (action.fn === 'blockJournalWrites') renameSync(shared.persistDir, blocked);
706
+ else renameSync(blocked, shared.persistDir);
707
+ results.push({defined: false});
708
+ break;
709
+ }
710
+ case 'appendUncertainTail':
711
+ case 'blockJournalCompaction':
712
+ case 'blockRekeyDestination':
713
+ case 'restoreJournalCompaction': {
714
+ await outbox.journalSettled();
715
+ const path = outboxJournalPath(shared.persistDir, shared.threadKey);
716
+ if (action.fn === 'blockRekeyDestination') mkdirSync(outboxJournalPath(shared.persistDir, action.threadKey));
717
+ else if (action.fn === 'appendUncertainTail') appendFileSync(path, '{"t":"kv","key":', 'utf8');
718
+ else if (action.fn === 'blockJournalCompaction') mkdirSync(path + '.tmp');
719
+ else renameSync(path + '.tmp', path + '.blocked-compaction');
720
+ results.push({defined: false});
721
+ break;
722
+ }
723
+ case 'failedTerminalEntries':
724
+ results.push({failedTerminalEntries: outbox.failedTerminalEntries()});
725
+ break;
726
+ case 'terminalPublicationDebt':
727
+ results.push({terminalPublicationDebt: outbox.terminalPublicationDebt()});
728
+ break;
729
+ case 'rememberDurableFact':
730
+ outbox.rememberDurableFact(action.key, action.value);
731
+ await outbox.journalSettled();
732
+ results.push({defined: false});
733
+ break;
734
+ case 'rememberWithoutSettle':
735
+ outbox.rememberDurableFact(action.key, action.value);
736
+ results.push({defined: false});
737
+ break;
738
+ case 'repairTerminalDebt':
739
+ results.push({repair: await enqueueTerminalDebtRepair(outbox, {
740
+ threadId: shared.threadKey, sourceSeq: 44, sessionId: 3600, originalLeaseEpoch: 1,
741
+ binding: action.binding, authoritativeTerminal: action.authoritativeTerminal === true,
742
+ authorize: () => true,
743
+ }, log)});
744
+ break;
657
745
  case 'rekeyJournal':
658
746
  results.push(outcome(() => outbox.rekeyJournal(action.threadKey)));
659
747
  await outbox.journalSettled();
@@ -855,6 +943,8 @@ async function handle(testCase) {
855
943
  return handleQuarantine(testCase);
856
944
  case 'outboxJournal':
857
945
  return handleOutboxJournal(testCase);
946
+ case 'failedTerminalJournal':
947
+ return await handleFailedTerminalJournal(testCase);
858
948
  case 'outboxScenario':
859
949
  return await handleOutboxScenario(testCase);
860
950
  case 'keystoneScenario':
@@ -892,6 +982,11 @@ await build({
892
982
  loader: 'ts',
893
983
  },
894
984
  bundle: true,
985
+ // Same production ESM/CJS bridge as the existing core integration oracle;
986
+ // the shared typed terminal-repair validator reaches CommanderNode bindings.
987
+ banner: {
988
+ js: "import { createRequire as __queueOracleCreateRequire } from 'node:module';\nconst require = __queueOracleCreateRequire(import.meta.url);",
989
+ },
895
990
  platform: 'node',
896
991
  target: 'node20',
897
992
  format: 'esm',