@codemoot/cli 0.2.12 → 0.2.14

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/dist/index.js CHANGED
@@ -424,12 +424,8 @@ async function buildReviewCommand(buildId) {
424
424
  }
425
425
  const sessionMgr = new SessionManager(db);
426
426
  const session2 = sessionMgr.resolveActive("build-review");
427
- const overflowCheck = sessionMgr.preCallOverflowCheck(session2.id);
428
- if (overflowCheck.rolled) {
429
- console.error(chalk3.yellow(` ${overflowCheck.message}`));
430
- }
431
427
  const currentSession = sessionMgr.get(session2.id);
432
- const existingSession = overflowCheck.rolled ? void 0 : currentSession?.codexThreadId ?? run.reviewCodexSession ?? void 0;
428
+ const existingSession = currentSession?.codexThreadId ?? run.reviewCodexSession ?? void 0;
433
429
  const prompt = buildHandoffEnvelope({
434
430
  command: "build-review",
435
431
  task: `Review code changes for the task: "${run.task}"
@@ -447,11 +443,23 @@ Review for:
447
443
  constraints: run.reviewCycles > 0 ? [`This is review cycle ${run.reviewCycles + 1}. Focus on whether prior issues were addressed.`] : void 0
448
444
  });
449
445
  const progress = createProgressCallbacks("build-review");
450
- const result = await adapter.callWithResume(prompt, {
451
- sessionId: existingSession,
452
- timeout: 6e5,
453
- ...progress
454
- });
446
+ let result;
447
+ try {
448
+ result = await adapter.callWithResume(prompt, {
449
+ sessionId: existingSession,
450
+ timeout: 6e5,
451
+ ...progress
452
+ });
453
+ } catch (err) {
454
+ if (existingSession) {
455
+ console.error(chalk3.yellow(" Clearing stale codex thread ID after failure."));
456
+ sessionMgr.updateThreadId(session2.id, null);
457
+ }
458
+ throw err;
459
+ }
460
+ if (existingSession && result.sessionId !== existingSession) {
461
+ sessionMgr.updateThreadId(session2.id, null);
462
+ }
455
463
  if (result.sessionId) {
456
464
  sessionMgr.updateThreadId(session2.id, result.sessionId);
457
465
  }
@@ -474,7 +482,7 @@ Review for:
474
482
  buildStore.updateWithEvent(
475
483
  buildId,
476
484
  {
477
- reviewCodexSession: result.sessionId ?? existingSession,
485
+ reviewCodexSession: result.sessionId ?? void 0,
478
486
  reviewCycles: run.reviewCycles + 1
479
487
  },
480
488
  {
@@ -538,7 +546,8 @@ import {
538
546
  preflightTokenCheck
539
547
  } from "@codemoot/core";
540
548
  import chalk4 from "chalk";
541
- import { writeFileSync } from "fs";
549
+ import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
550
+ import { join as join3 } from "path";
542
551
  async function debateStartCommand(topic, options) {
543
552
  let db;
544
553
  try {
@@ -578,6 +587,40 @@ async function debateStartCommand(topic, options) {
578
587
  process.exit(1);
579
588
  }
580
589
  }
590
+ var DEFAULT_RESPONSE_CAP = 16384;
591
+ var MAX_RESPONSE_CAP = 24576;
592
+ function buildResponseOutput(debateId, round, responseText, cap, explicitOutput) {
593
+ const text = responseText ?? "";
594
+ const byteLen = Buffer.byteLength(text, "utf-8");
595
+ const truncated = byteLen > cap;
596
+ let responseFile;
597
+ if (truncated && !explicitOutput) {
598
+ const dir = join3(process.cwd(), ".codemoot", "debates");
599
+ mkdirSync2(dir, { recursive: true, mode: 448 });
600
+ responseFile = join3(dir, `${debateId}-r${round}.txt`);
601
+ writeFileSync(responseFile, text, { encoding: "utf-8", mode: 384 });
602
+ } else if (explicitOutput && text.length > 0) {
603
+ responseFile = explicitOutput;
604
+ }
605
+ let sliced = text;
606
+ if (truncated) {
607
+ let lo = 0;
608
+ let hi = text.length;
609
+ while (lo < hi) {
610
+ const mid = lo + hi + 1 >>> 1;
611
+ if (Buffer.byteLength(text.slice(0, mid), "utf-8") <= cap) lo = mid;
612
+ else hi = mid - 1;
613
+ }
614
+ sliced = text.slice(0, lo);
615
+ }
616
+ return {
617
+ response: sliced,
618
+ responseTruncated: truncated,
619
+ responseBytes: byteLen,
620
+ responseCap: cap,
621
+ responseFile
622
+ };
623
+ }
581
624
  async function debateTurnCommand(debateId, prompt, options) {
582
625
  let db;
583
626
  try {
@@ -607,6 +650,11 @@ async function debateTurnCommand(debateId, prompt, options) {
607
650
  }
608
651
  const rawRound = options.round ?? criticRow.round + 1;
609
652
  const newRound = Number.isFinite(rawRound) && rawRound > 0 ? rawRound : criticRow.round + 1;
653
+ const responseCap = Math.min(
654
+ options.responseCap && options.responseCap > 0 ? options.responseCap : DEFAULT_RESPONSE_CAP,
655
+ MAX_RESPONSE_CAP
656
+ );
657
+ const quiet = options.quiet ?? false;
610
658
  const proposerStateForLimit = store.loadState(debateId, "proposer");
611
659
  const storedTimeout = proposerStateForLimit?.defaultTimeout;
612
660
  const rawTimeout = options.timeout ?? storedTimeout ?? 600;
@@ -631,14 +679,13 @@ async function debateTurnCommand(debateId, prompt, options) {
631
679
  }
632
680
  if (options.output && existing.responseText) {
633
681
  writeFileSync(options.output, existing.responseText, "utf-8");
634
- console.error(chalk4.dim(`Full response written to ${options.output}`));
682
+ if (!quiet) console.error(chalk4.dim(`Full response written to ${options.output}`));
635
683
  }
684
+ const responseFields = buildResponseOutput(debateId, newRound, existing.responseText, responseCap, options.output);
636
685
  const output = {
637
686
  debateId,
638
687
  round: newRound,
639
- response: existing.responseText?.slice(0, 2e3) ?? "",
640
- responseTruncated: (existing.responseText?.length ?? 0) > 2e3,
641
- responseFile: options.output ?? void 0,
688
+ ...responseFields,
642
689
  sessionId: existing.sessionId,
643
690
  resumed: false,
644
691
  cached: true,
@@ -657,7 +704,7 @@ async function debateTurnCommand(debateId, prompt, options) {
657
704
  }
658
705
  const staleThreshold = timeout + 6e4;
659
706
  const recovered = msgStore.recoverStaleForDebate(debateId, staleThreshold);
660
- if (recovered > 0) {
707
+ if (recovered > 0 && !quiet) {
661
708
  console.error(chalk4.yellow(` Recovered ${recovered} stale message(s) from prior crash.`));
662
709
  }
663
710
  const current = msgStore.getByRound(debateId, newRound, "critic");
@@ -682,14 +729,13 @@ async function debateTurnCommand(debateId, prompt, options) {
682
729
  if (recheckRow?.status === "completed") {
683
730
  if (options.output && recheckRow.responseText) {
684
731
  writeFileSync(options.output, recheckRow.responseText, "utf-8");
685
- console.error(chalk4.dim(`Full response written to ${options.output}`));
732
+ if (!quiet) console.error(chalk4.dim(`Full response written to ${options.output}`));
686
733
  }
734
+ const recheckResponseFields = buildResponseOutput(debateId, newRound, recheckRow.responseText, responseCap, options.output);
687
735
  const output = {
688
736
  debateId,
689
737
  round: newRound,
690
- response: recheckRow.responseText?.slice(0, 2e3) ?? "",
691
- responseTruncated: (recheckRow.responseText?.length ?? 0) > 2e3,
692
- responseFile: options.output ?? void 0,
738
+ ...recheckResponseFields,
693
739
  sessionId: recheckRow.sessionId,
694
740
  resumed: false,
695
741
  cached: true,
@@ -722,28 +768,32 @@ async function debateTurnCommand(debateId, prompt, options) {
722
768
  db.close();
723
769
  process.exit(1);
724
770
  } else if (preflight.shouldStop && options.force) {
725
- console.error(chalk4.yellow(` Token budget at ${Math.round(preflight.utilizationRatio * 100)}% (${preflight.totalTokensUsed}/${maxContext}) \u2014 forced past budget limit.`));
771
+ if (!quiet) console.error(chalk4.yellow(` Token budget at ${Math.round(preflight.utilizationRatio * 100)}% (${preflight.totalTokensUsed}/${maxContext}) \u2014 forced past budget limit.`));
726
772
  } else if (preflight.shouldSummarize) {
727
- console.error(chalk4.yellow(` Token budget at ${Math.round(preflight.utilizationRatio * 100)}%. Older rounds will be summarized on resume failure.`));
728
- }
729
- const overflowCheck = sessionMgr.preCallOverflowCheck(unifiedSession.id);
730
- if (overflowCheck.rolled) {
731
- console.error(chalk4.yellow(` ${overflowCheck.message}`));
732
- console.error(chalk4.yellow(" Warning: Session rolled over \u2014 previous conversation context has been lost."));
733
- sessionMgr.updateThreadId(unifiedSession.id, null);
734
- existingSessionId = void 0;
773
+ if (!quiet) console.error(chalk4.yellow(` Token budget at ${Math.round(preflight.utilizationRatio * 100)}%. Older rounds will be summarized on resume failure.`));
735
774
  }
736
775
  try {
737
776
  const progress = createProgressCallbacks("debate");
738
- let result = await adapter.callWithResume(prompt, {
739
- sessionId: existingSessionId,
740
- timeout,
741
- ...progress
742
- });
777
+ let result;
778
+ try {
779
+ result = await adapter.callWithResume(prompt, {
780
+ sessionId: existingSessionId,
781
+ timeout,
782
+ ...progress
783
+ });
784
+ } catch (err) {
785
+ if (existingSessionId) {
786
+ sessionMgr.updateThreadId(unifiedSession.id, null);
787
+ }
788
+ throw err;
789
+ }
790
+ if (existingSessionId && result.sessionId !== existingSessionId) {
791
+ sessionMgr.updateThreadId(unifiedSession.id, null);
792
+ }
743
793
  const resumed = attemptedResume && result.sessionId === existingSessionId;
744
794
  const resumeFailed = attemptedResume && !resumed;
745
795
  if (resumeFailed && result.text.length < 50) {
746
- console.error(chalk4.yellow(" Resume failed with minimal response. Reconstructing from ledger..."));
796
+ if (!quiet) console.error(chalk4.yellow(" Resume failed with minimal response. Reconstructing from ledger..."));
747
797
  const history = msgStore.getHistory(debateId);
748
798
  const reconstructed = buildReconstructionPrompt(history, prompt);
749
799
  result = await adapter.callWithResume(reconstructed, {
@@ -752,7 +802,7 @@ async function debateTurnCommand(debateId, prompt, options) {
752
802
  });
753
803
  }
754
804
  if (result.text.length < 200 && (result.durationMs ?? 0) > 6e4) {
755
- console.error(chalk4.yellow(` Warning: GPT response is only ${result.text.length} chars after ${Math.round((result.durationMs ?? 0) / 1e3)}s \u2014 possible output truncation (codex may have spent its turn on tool calls).`));
805
+ if (!quiet) console.error(chalk4.yellow(` Warning: GPT response is only ${result.text.length} chars after ${Math.round((result.durationMs ?? 0) / 1e3)}s \u2014 possible output truncation (codex may have spent its turn on tool calls).`));
756
806
  }
757
807
  const verdict = parseDebateVerdict(result.text);
758
808
  const completed = msgStore.markCompleted(msgId, {
@@ -789,7 +839,7 @@ async function debateTurnCommand(debateId, prompt, options) {
789
839
  store.upsert({
790
840
  debateId,
791
841
  role: "critic",
792
- codexSessionId: result.sessionId ?? existingSessionId,
842
+ codexSessionId: result.sessionId ?? void 0,
793
843
  round: newRound,
794
844
  status: "active"
795
845
  });
@@ -804,14 +854,13 @@ async function debateTurnCommand(debateId, prompt, options) {
804
854
  }
805
855
  if (options.output) {
806
856
  writeFileSync(options.output, result.text, "utf-8");
807
- console.error(chalk4.dim(`Full response written to ${options.output}`));
857
+ if (!quiet) console.error(chalk4.dim(`Full response written to ${options.output}`));
808
858
  }
859
+ const freshResponseFields = buildResponseOutput(debateId, newRound, result.text, responseCap, options.output);
809
860
  const output = {
810
861
  debateId,
811
862
  round: newRound,
812
- response: result.text.slice(0, 2e3),
813
- responseTruncated: result.text.length > 2e3,
814
- responseFile: options.output ?? void 0,
863
+ ...freshResponseFields,
815
864
  sessionId: result.sessionId,
816
865
  resumed,
817
866
  cached: false,
@@ -819,14 +868,16 @@ async function debateTurnCommand(debateId, prompt, options) {
819
868
  usage: result.usage,
820
869
  durationMs: result.durationMs
821
870
  };
822
- const stanceColor = verdict.stance === "SUPPORT" ? chalk4.green : verdict.stance === "OPPOSE" ? chalk4.red : chalk4.yellow;
823
- console.error(stanceColor(`
871
+ if (!quiet) {
872
+ const stanceColor = verdict.stance === "SUPPORT" ? chalk4.green : verdict.stance === "OPPOSE" ? chalk4.red : chalk4.yellow;
873
+ console.error(stanceColor(`
824
874
  Round ${newRound} \u2014 Stance: ${verdict.stance}`));
825
- const previewLines = result.text.split("\n").filter((l) => l.trim().length > 10).slice(0, 3);
826
- for (const line of previewLines) {
827
- console.error(chalk4.dim(` ${line.trim().slice(0, 120)}`));
875
+ const previewLines = result.text.split("\n").filter((l) => l.trim().length > 10).slice(0, 3);
876
+ for (const line of previewLines) {
877
+ console.error(chalk4.dim(` ${line.trim().slice(0, 120)}`));
878
+ }
879
+ console.error(chalk4.dim(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s | Output: ${result.usage?.outputTokens ?? "?"} tokens | Input: ${result.usage?.inputTokens ?? "?"} (includes sandbox) | Resumed: ${resumed}`));
828
880
  }
829
- console.error(chalk4.dim(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s | Output: ${result.usage?.outputTokens ?? "?"} tokens | Input: ${result.usage?.inputTokens ?? "?"} (includes sandbox) | Resumed: ${resumed}`));
830
881
  console.log(JSON.stringify(output, null, 2));
831
882
  } catch (error) {
832
883
  msgStore.markFailed(msgId, error instanceof Error ? error.message : String(error));
@@ -1115,10 +1166,6 @@ async function cleanupCommand(path, options) {
1115
1166
  }
1116
1167
  const sessionMgr = new SessionManager3(db);
1117
1168
  const session2 = sessionMgr.resolveActive("cleanup");
1118
- const overflowCheck = sessionMgr.preCallOverflowCheck(session2.id);
1119
- if (overflowCheck.rolled) {
1120
- console.error(chalk5.yellow(` ${overflowCheck.message}`));
1121
- }
1122
1169
  const currentSession = sessionMgr.get(session2.id);
1123
1170
  const sessionThreadId = currentSession?.codexThreadId ?? void 0;
1124
1171
  const [deterministicFindings, semanticFindings] = await Promise.all([
@@ -1310,7 +1357,18 @@ IMPORTANT KEY FORMAT: The key will be built as scope:file:symbol \u2014 use the
1310
1357
  });
1311
1358
  try {
1312
1359
  const progress = createProgressCallbacks("cleanup-scan");
1313
- const result = await adapter.callWithResume(prompt, { sessionId: sessionThreadId, timeout: timeoutSec * 1e3, ...progress });
1360
+ let result;
1361
+ try {
1362
+ result = await adapter.callWithResume(prompt, { sessionId: sessionThreadId, timeout: timeoutSec * 1e3, ...progress });
1363
+ } catch (err) {
1364
+ if (sessionThreadId && sessionMgr && sessionId) {
1365
+ sessionMgr.updateThreadId(sessionId, null);
1366
+ }
1367
+ throw err;
1368
+ }
1369
+ if (sessionThreadId && result.sessionId !== sessionThreadId && sessionMgr && sessionId) {
1370
+ sessionMgr.updateThreadId(sessionId, null);
1371
+ }
1314
1372
  if (sessionMgr && sessionId) {
1315
1373
  if (result.sessionId) {
1316
1374
  sessionMgr.updateThreadId(sessionId, result.sessionId);
@@ -1488,7 +1546,7 @@ async function costCommand(options) {
1488
1546
  // src/commands/doctor.ts
1489
1547
  import { existsSync, accessSync, constants } from "fs";
1490
1548
  import { execSync as execSync2 } from "child_process";
1491
- import { join as join3 } from "path";
1549
+ import { join as join4 } from "path";
1492
1550
  import chalk7 from "chalk";
1493
1551
  import { VERSION } from "@codemoot/core";
1494
1552
  async function doctorCommand() {
@@ -1508,7 +1566,7 @@ async function doctorCommand() {
1508
1566
  fix: "npm install -g @openai/codex"
1509
1567
  });
1510
1568
  }
1511
- const configPath = join3(cwd, ".cowork.yml");
1569
+ const configPath = join4(cwd, ".cowork.yml");
1512
1570
  if (existsSync(configPath)) {
1513
1571
  checks.push({ name: "config", status: "pass", message: ".cowork.yml found" });
1514
1572
  } else {
@@ -1519,8 +1577,8 @@ async function doctorCommand() {
1519
1577
  fix: "codemoot init"
1520
1578
  });
1521
1579
  }
1522
- const dbDir = join3(cwd, ".cowork", "db");
1523
- const dbPath = join3(dbDir, "cowork.db");
1580
+ const dbDir = join4(cwd, ".cowork", "db");
1581
+ const dbPath = join4(dbDir, "cowork.db");
1524
1582
  if (existsSync(dbDir)) {
1525
1583
  try {
1526
1584
  accessSync(dbDir, constants.W_OK);
@@ -1548,11 +1606,11 @@ async function doctorCommand() {
1548
1606
  let gitFound = false;
1549
1607
  let searchDir = cwd;
1550
1608
  while (searchDir) {
1551
- if (existsSync(join3(searchDir, ".git"))) {
1609
+ if (existsSync(join4(searchDir, ".git"))) {
1552
1610
  gitFound = true;
1553
1611
  break;
1554
1612
  }
1555
- const parent = join3(searchDir, "..");
1613
+ const parent = join4(searchDir, "..");
1556
1614
  if (parent === searchDir) break;
1557
1615
  searchDir = parent;
1558
1616
  }
@@ -1770,11 +1828,6 @@ async function fixCommand(fileOrGlob, options) {
1770
1828
  )
1771
1829
  );
1772
1830
  for (let round = 1; round <= options.maxRounds; round++) {
1773
- const overflowCheck = sessionMgr.preCallOverflowCheck(session2.id);
1774
- if (overflowCheck.rolled) {
1775
- console.error(chalk9.yellow(` ${overflowCheck.message}`));
1776
- threadId = void 0;
1777
- }
1778
1831
  const roundStart = Date.now();
1779
1832
  console.error(chalk9.dim(`
1780
1833
  \u2500\u2500 Round ${round}/${options.maxRounds} \u2500\u2500`));
@@ -2030,12 +2083,12 @@ Result: ${converged ? "CONVERGED" : "NOT CONVERGED"} (${exitReason})`));
2030
2083
 
2031
2084
  // src/commands/init.ts
2032
2085
  import { existsSync as existsSync2 } from "fs";
2033
- import { basename, join as join4 } from "path";
2086
+ import { basename, join as join5 } from "path";
2034
2087
  import { loadConfig as loadConfig5, writeConfig } from "@codemoot/core";
2035
2088
  import chalk10 from "chalk";
2036
2089
  async function initCommand(options) {
2037
2090
  const cwd = process.cwd();
2038
- const configPath = join4(cwd, ".cowork.yml");
2091
+ const configPath = join5(cwd, ".cowork.yml");
2039
2092
  if (existsSync2(configPath) && !options.force) {
2040
2093
  console.error(chalk10.red("Already initialized. Use --force to overwrite."));
2041
2094
  process.exit(1);
@@ -2067,8 +2120,8 @@ Initialized with '${presetName}' preset`));
2067
2120
  }
2068
2121
 
2069
2122
  // src/commands/install-skills.ts
2070
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2071
- import { dirname, join as join5 } from "path";
2123
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2124
+ import { dirname, join as join6 } from "path";
2072
2125
  import chalk11 from "chalk";
2073
2126
  var SKILLS = [
2074
2127
  {
@@ -2509,21 +2562,21 @@ async function installSkillsCommand(options) {
2509
2562
  console.error(chalk11.cyan("\n Installing CodeMoot integration for Claude Code\n"));
2510
2563
  console.error(chalk11.dim(" Skills & Agents:"));
2511
2564
  for (const skill of SKILLS) {
2512
- const fullPath = join5(cwd, skill.path);
2565
+ const fullPath = join6(cwd, skill.path);
2513
2566
  const dir = dirname(fullPath);
2514
2567
  if (existsSync3(fullPath) && !options.force) {
2515
2568
  console.error(chalk11.dim(` SKIP ${skill.path} (exists)`));
2516
2569
  skipped++;
2517
2570
  continue;
2518
2571
  }
2519
- mkdirSync2(dir, { recursive: true });
2572
+ mkdirSync3(dir, { recursive: true });
2520
2573
  writeFileSync3(fullPath, skill.content, "utf-8");
2521
2574
  console.error(chalk11.green(` OK ${skill.path}`));
2522
2575
  installed++;
2523
2576
  }
2524
2577
  console.error("");
2525
2578
  console.error(chalk11.dim(" CLAUDE.md:"));
2526
- const claudeMdPath = join5(cwd, "CLAUDE.md");
2579
+ const claudeMdPath = join6(cwd, "CLAUDE.md");
2527
2580
  const marker = "## CodeMoot \u2014 Multi-Model Collaboration";
2528
2581
  if (existsSync3(claudeMdPath)) {
2529
2582
  const existing = readFileSync3(claudeMdPath, "utf-8");
@@ -2555,8 +2608,8 @@ ${CLAUDE_MD_SECTION}`, "utf-8");
2555
2608
  }
2556
2609
  console.error("");
2557
2610
  console.error(chalk11.dim(" Hooks:"));
2558
- const settingsDir = join5(cwd, ".claude");
2559
- const settingsPath = join5(settingsDir, "settings.json");
2611
+ const settingsDir = join6(cwd, ".claude");
2612
+ const settingsPath = join6(settingsDir, "settings.json");
2560
2613
  if (existsSync3(settingsPath)) {
2561
2614
  try {
2562
2615
  const existing = JSON.parse(readFileSync3(settingsPath, "utf-8"));
@@ -2580,7 +2633,7 @@ ${CLAUDE_MD_SECTION}`, "utf-8");
2580
2633
  skipped++;
2581
2634
  }
2582
2635
  } else {
2583
- mkdirSync2(settingsDir, { recursive: true });
2636
+ mkdirSync3(settingsDir, { recursive: true });
2584
2637
  writeFileSync3(settingsPath, JSON.stringify(HOOKS_CONFIG, null, 2), "utf-8");
2585
2638
  console.error(chalk11.green(" OK .claude/settings.json (created with post-commit hook)"));
2586
2639
  installed++;
@@ -2633,18 +2686,12 @@ async function sessionCurrentCommand() {
2633
2686
  return;
2634
2687
  }
2635
2688
  const events = mgr.getEvents(session2.id, 5);
2636
- const overflow = mgr.getOverflowStatus(session2.id);
2637
2689
  console.log(JSON.stringify({
2638
2690
  sessionId: session2.id,
2639
2691
  name: session2.name,
2640
2692
  codexThreadId: session2.codexThreadId,
2641
2693
  status: session2.status,
2642
- tokenBudget: {
2643
- used: overflow.cumulativeTokens,
2644
- lastTurnInput: overflow.lastTurnInputTokens,
2645
- max: overflow.maxContext,
2646
- utilization: `${Math.round(overflow.utilizationRatio * 100)}%`
2647
- },
2694
+ tokenUsage: session2.tokenUsage,
2648
2695
  recentEvents: events.map((e) => ({
2649
2696
  command: e.command,
2650
2697
  subcommand: e.subcommand,
@@ -2684,20 +2731,12 @@ async function sessionStatusCommand(sessionId) {
2684
2731
  process.exit(1);
2685
2732
  }
2686
2733
  const events = mgr.getEvents(sessionId, 20);
2687
- const overflow = mgr.getOverflowStatus(sessionId);
2688
2734
  console.log(JSON.stringify({
2689
2735
  sessionId: session2.id,
2690
2736
  name: session2.name,
2691
2737
  codexThreadId: session2.codexThreadId,
2692
2738
  status: session2.status,
2693
- tokenBudget: {
2694
- used: overflow.cumulativeTokens,
2695
- lastTurnInput: overflow.lastTurnInputTokens,
2696
- max: overflow.maxContext,
2697
- utilization: `${Math.round(overflow.utilizationRatio * 100)}%`,
2698
- shouldWarn: overflow.shouldWarn,
2699
- shouldReconstruct: overflow.shouldReconstruct
2700
- },
2739
+ tokenUsage: session2.tokenUsage,
2701
2740
  eventCount: events.length,
2702
2741
  events: events.map((e) => ({
2703
2742
  command: e.command,
@@ -3037,12 +3076,8 @@ async function planReviewCommand(planFile, options) {
3037
3076
  db = openDatabase9(dbPath);
3038
3077
  const sessionMgr = new SessionManager7(db);
3039
3078
  const session2 = sessionMgr.resolveActive("plan-review");
3040
- const overflowCheck = sessionMgr.preCallOverflowCheck(session2.id);
3041
- if (overflowCheck.rolled) {
3042
- console.error(chalk15.yellow(` ${overflowCheck.message}`));
3043
- }
3044
3079
  const currentSession = sessionMgr.get(session2.id);
3045
- const threadId = overflowCheck.rolled ? void 0 : currentSession?.codexThreadId ?? void 0;
3080
+ const threadId = currentSession?.codexThreadId ?? void 0;
3046
3081
  const phaseContext = options.phase ? `
3047
3082
  This is Phase ${options.phase} of a multi-phase plan.` : "";
3048
3083
  const buildContext = options.build ? `
@@ -3076,11 +3111,23 @@ Output format:
3076
3111
  const timeoutMs = (options.timeout ?? 300) * 1e3;
3077
3112
  const progress = createProgressCallbacks("plan-review");
3078
3113
  console.error(chalk15.cyan("Sending plan to codex for review..."));
3079
- const result = await adapter.callWithResume(prompt, {
3080
- sessionId: threadId,
3081
- timeout: timeoutMs,
3082
- ...progress
3083
- });
3114
+ let result;
3115
+ try {
3116
+ result = await adapter.callWithResume(prompt, {
3117
+ sessionId: threadId,
3118
+ timeout: timeoutMs,
3119
+ ...progress
3120
+ });
3121
+ } catch (err) {
3122
+ if (threadId) {
3123
+ console.error(chalk15.yellow(" Clearing stale codex thread ID after failure."));
3124
+ sessionMgr.updateThreadId(session2.id, null);
3125
+ }
3126
+ throw err;
3127
+ }
3128
+ if (threadId && result.sessionId !== threadId) {
3129
+ sessionMgr.updateThreadId(session2.id, null);
3130
+ }
3084
3131
  if (result.sessionId) {
3085
3132
  sessionMgr.updateThreadId(session2.id, result.sessionId);
3086
3133
  }
@@ -3231,10 +3278,6 @@ async function reviewCommand(fileOrGlob, options) {
3231
3278
  db.close();
3232
3279
  process.exit(1);
3233
3280
  }
3234
- const overflowCheck = sessionMgr.preCallOverflowCheck(session2.id);
3235
- if (overflowCheck.rolled) {
3236
- console.error(chalk16.yellow(` ${overflowCheck.message}`));
3237
- }
3238
3281
  const preset = options.preset ? getReviewPreset(options.preset) : void 0;
3239
3282
  if (options.preset && !preset) {
3240
3283
  console.error(chalk16.red(`Unknown preset: ${options.preset}. Use: security-audit, performance, quick-scan, pre-commit, api-review`));
@@ -3600,7 +3643,7 @@ async function shipitCommand(options) {
3600
3643
  // src/commands/start.ts
3601
3644
  import { existsSync as existsSync5 } from "fs";
3602
3645
  import { execFileSync as execFileSync4, execSync as execSync6 } from "child_process";
3603
- import { join as join6, basename as basename2 } from "path";
3646
+ import { join as join7, basename as basename2 } from "path";
3604
3647
  import chalk19 from "chalk";
3605
3648
  import { loadConfig as loadConfig9, writeConfig as writeConfig2 } from "@codemoot/core";
3606
3649
  async function startCommand() {
@@ -3620,7 +3663,7 @@ async function startCommand() {
3620
3663
  }
3621
3664
  console.error(chalk19.green(` Codex CLI ${codexVersion} found.`));
3622
3665
  console.error(chalk19.dim(" [2/4] Checking project config..."));
3623
- const configPath = join6(cwd, ".cowork.yml");
3666
+ const configPath = join7(cwd, ".cowork.yml");
3624
3667
  if (existsSync5(configPath)) {
3625
3668
  console.error(chalk19.green(" .cowork.yml exists \u2014 using it."));
3626
3669
  } else {
@@ -3630,9 +3673,9 @@ async function startCommand() {
3630
3673
  console.error(chalk19.green(" Created .cowork.yml with cli-first preset."));
3631
3674
  }
3632
3675
  console.error(chalk19.dim(" [3/4] Detecting project..."));
3633
- const hasGit = existsSync5(join6(cwd, ".git"));
3634
- const hasSrc = existsSync5(join6(cwd, "src"));
3635
- const hasPackageJson = existsSync5(join6(cwd, "package.json"));
3676
+ const hasGit = existsSync5(join7(cwd, ".git"));
3677
+ const hasSrc = existsSync5(join7(cwd, "src"));
3678
+ const hasPackageJson = existsSync5(join7(cwd, "package.json"));
3636
3679
  let reviewTarget = "";
3637
3680
  if (hasGit) {
3638
3681
  try {
@@ -4070,8 +4113,8 @@ debate.command("start").description("Start a new debate").argument("<topic>", "D
4070
4113
  if (n <= 0) throw new InvalidArgumentError("Timeout must be a positive integer");
4071
4114
  return n;
4072
4115
  }).action(debateStartCommand);
4073
- debate.command("turn").description("Send a prompt to GPT and get critique (with session resume)").argument("<debate-id>", "Debate ID from start command").argument("<prompt>", "Prompt to send to GPT").option("--round <n>", "Round number", (v) => Number.parseInt(v, 10)).option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10)).option("--output <file>", "Write full untruncated response to file").option("--force", "Continue past token budget limit", false).action(debateTurnCommand);
4074
- debate.command("next").description("Continue debate with auto-generated prompt").argument("<debate-id>", "Debate ID").option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10)).option("--output <file>", "Write full untruncated response to file").option("--force", "Continue past token budget limit", false).action(debateNextCommand);
4116
+ debate.command("turn").description("Send a prompt to GPT and get critique (with session resume)").argument("<debate-id>", "Debate ID from start command").argument("<prompt>", "Prompt to send to GPT").option("--round <n>", "Round number", (v) => Number.parseInt(v, 10)).option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10)).option("--output <file>", "Write full untruncated response to file").option("--force", "Continue past token budget limit", false).option("--quiet", "Suppress non-error stderr output", false).option("--response-cap <bytes>", "Max response bytes in JSON output (default: 16384)", (v) => Number.parseInt(v, 10)).action(debateTurnCommand);
4117
+ debate.command("next").description("Continue debate with auto-generated prompt").argument("<debate-id>", "Debate ID").option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10)).option("--output <file>", "Write full untruncated response to file").option("--force", "Continue past token budget limit", false).option("--quiet", "Suppress non-error stderr output", false).option("--response-cap <bytes>", "Max response bytes in JSON output (default: 16384)", (v) => Number.parseInt(v, 10)).action(debateNextCommand);
4075
4118
  debate.command("status").description("Show debate status and session info").argument("<debate-id>", "Debate ID").action(debateStatusCommand);
4076
4119
  debate.command("list").description("List all debates").option("--status <status>", "Filter by status (active|completed|stale)").option("--limit <n>", "Max results", (v) => Number.parseInt(v, 10), 20).action(debateListCommand);
4077
4120
  debate.command("history").description("Show full message history with token budget").argument("<debate-id>", "Debate ID").option("--output <file>", "Write full untruncated history to file").action(debateHistoryCommand);