@usulpro/codex-bee 0.1.1 → 0.1.3

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/cli.js CHANGED
@@ -537,7 +537,6 @@ function renderContinuationPrompt(promptTemplate, capture) {
537
537
  }
538
538
 
539
539
  // src/auto-continue.ts
540
- var DURATION_INPUT_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/i;
541
540
  var PROMPT_PREVIEW_LENGTH = 96;
542
541
  var SINGLE_LINE_WHITESPACE_PATTERN2 = /\s+/g;
543
542
  var DEFAULT_CONTINUATION_PROMPT = "continue";
@@ -561,38 +560,6 @@ function describeContinuationSource(source) {
561
560
  function toErrorMessage2(error) {
562
561
  return error instanceof Error ? error.message : String(error);
563
562
  }
564
- function parseDurationInput(value, options) {
565
- const trimmedValue = value.trim();
566
- if (!trimmedValue || /^off$/i.test(trimmedValue)) {
567
- if (options?.allowEmpty ?? true) {
568
- return null;
569
- }
570
- throw new Error("Duration is required.");
571
- }
572
- const match = trimmedValue.match(DURATION_INPUT_PATTERN);
573
- if (!match) {
574
- throw new Error(`Unsupported duration value: ${value}`);
575
- }
576
- const amount = Number.parseFloat(match[1]);
577
- const unit = (match[2] ?? "m").toLowerCase();
578
- if (!Number.isFinite(amount) || amount <= 0) {
579
- throw new Error(`Unsupported duration value: ${value}`);
580
- }
581
- const multiplier = {
582
- h: 60 * 60 * 1e3,
583
- m: 60 * 1e3,
584
- ms: 1,
585
- s: 1e3
586
- }[unit];
587
- if (multiplier === void 0) {
588
- throw new Error(`Unsupported duration value: ${value}`);
589
- }
590
- const durationMs = Math.round(amount * multiplier);
591
- if (durationMs <= 0) {
592
- throw new Error(`Unsupported duration value: ${value}`);
593
- }
594
- return durationMs;
595
- }
596
563
  function formatDurationMs(value) {
597
564
  if (value === null) {
598
565
  return "off";
@@ -815,47 +782,17 @@ bee
815
782
  Autonomous wrapper around Codex CLI.
816
783
 
817
784
  Usage:
818
- bee [options] <command> [commandArgs]
819
- bee [options] -- <command> [commandArgs]
785
+ bee [bee options] <command> [commandArgs]
786
+ bee [bee options] -- <command> [commandArgs]
820
787
 
821
- Options:
822
- -h, --help Show help
823
- --version Show version
824
- --continue-once <prompt> Inject one follow-up prompt after the first Stop event
825
- --continue-once-file <path> Load the one-shot follow-up prompt from a file
826
- --continue-once-agent-file <path>
827
- Load a one-shot bee-agent config from a file
828
- --continue-loop <prompt> Inject the same follow-up prompt after multiple Stop events
829
- --continue-loop-file <path> Load the loop follow-up prompt from a file
830
- --continue-loop-agent-file <path>
831
- Load a loop bee-agent config from a file
832
- --max-continues <count> Guardrail for loop injections (default: 3)
833
- --max-duration <duration> Session guardrail, for example 90m or 1.5h
834
- --verify-command <command> Run a verification shell command after each Stop; continue only while it fails
835
- --inject-delay-ms <milliseconds> Optional debug delay before prompt injection
788
+ Bee options:
789
+ -h, --help Show help
790
+ --version Show version
836
791
 
837
- Inline and file-based continuation prompts accept {{placeholder}} tokens from the matched Stop payload.
838
- Bee-agent continuation configs are JSON files that point to system/session prompt files and optional generator settings.
792
+ All remaining arguments are passed through to the wrapped command unchanged.
793
+ Continuation prompts, guardrails, and verification settings are configured from the in-session UI.
839
794
  `;
840
- function setContinuationSource(currentSource, nextSource, modeLabel) {
841
- if (currentSource) {
842
- throw new Error(`Use only one ${modeLabel} continuation source.`);
843
- }
844
- return nextSource;
845
- }
846
- var WRAPPER_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
847
- "--continue-loop",
848
- "--continue-loop-agent-file",
849
- "--continue-loop-file",
850
- "--continue-once",
851
- "--continue-once-agent-file",
852
- "--continue-once-file",
853
- "--inject-delay-ms",
854
- "--max-continues",
855
- "--max-duration",
856
- "--verify-command"
857
- ]);
858
- var WRAPPER_FLAGS_WITHOUT_VALUE = /* @__PURE__ */ new Set([
795
+ var WRAPPER_FLAGS = /* @__PURE__ */ new Set([
859
796
  "-h",
860
797
  "--help",
861
798
  "--version"
@@ -876,31 +813,18 @@ function parseCliArguments(rawArgs) {
876
813
  );
877
814
  }
878
815
  const wrapperArgs = [];
879
- let index = 0;
880
- while (index < rawArgs.length) {
816
+ for (let index = 0; index < rawArgs.length; index += 1) {
881
817
  const argument = rawArgs[index];
882
- if (argument === "codex") {
883
- return {
884
- command: argument,
885
- commandArgs: rawArgs.slice(index + 1),
886
- wrapperArgs
887
- };
888
- }
889
- if (WRAPPER_FLAGS_WITHOUT_VALUE.has(argument)) {
818
+ if (WRAPPER_FLAGS.has(argument)) {
890
819
  wrapperArgs.push(argument);
891
- index += 1;
892
820
  continue;
893
821
  }
894
- if (WRAPPER_FLAGS_WITH_VALUE.has(argument)) {
895
- wrapperArgs.push(argument);
896
- const value = rawArgs[index + 1];
897
- if (value !== void 0) {
898
- wrapperArgs.push(value);
899
- index += 2;
900
- } else {
901
- index += 1;
902
- }
903
- continue;
822
+ if (argument.startsWith("-")) {
823
+ return {
824
+ command: null,
825
+ commandArgs: [],
826
+ wrapperArgs: rawArgs
827
+ };
904
828
  }
905
829
  return {
906
830
  command: argument,
@@ -916,16 +840,9 @@ function parseCliArguments(rawArgs) {
916
840
  }
917
841
  function parseWrapperOptions(rawArgs) {
918
842
  const { command, commandArgs, wrapperArgs } = parseCliArguments(rawArgs);
919
- let continueLoopSource = null;
920
- let continueOnceSource = null;
921
- let injectDelayMs = 0;
922
- let maxContinues = 3;
923
- let maxDurationMs = null;
924
843
  let showHelp = false;
925
844
  let showVersion = false;
926
- let verificationCommand = null;
927
- for (let index = 0; index < wrapperArgs.length; index += 1) {
928
- const argument = wrapperArgs[index];
845
+ for (const argument of wrapperArgs) {
929
846
  if (argument === "-h" || argument === "--help") {
930
847
  showHelp = true;
931
848
  continue;
@@ -934,177 +851,18 @@ function parseWrapperOptions(rawArgs) {
934
851
  showVersion = true;
935
852
  continue;
936
853
  }
937
- if (argument === "--continue-once") {
938
- const prompt = wrapperArgs[index + 1];
939
- if (!prompt) {
940
- throw new Error("Missing value for --continue-once.");
941
- }
942
- continueOnceSource = setContinuationSource(
943
- continueOnceSource,
944
- {
945
- kind: "inline",
946
- template: prompt
947
- },
948
- "one-shot"
949
- );
950
- index += 1;
951
- continue;
952
- }
953
- if (argument === "--continue-once-file") {
954
- const path6 = wrapperArgs[index + 1];
955
- if (!path6) {
956
- throw new Error("Missing value for --continue-once-file.");
957
- }
958
- continueOnceSource = setContinuationSource(
959
- continueOnceSource,
960
- {
961
- kind: "file",
962
- path: path6
963
- },
964
- "one-shot"
965
- );
966
- index += 1;
967
- continue;
968
- }
969
- if (argument === "--continue-once-agent-file") {
970
- const path6 = wrapperArgs[index + 1];
971
- if (!path6) {
972
- throw new Error("Missing value for --continue-once-agent-file.");
973
- }
974
- continueOnceSource = setContinuationSource(
975
- continueOnceSource,
976
- {
977
- kind: "bee-agent",
978
- path: path6
979
- },
980
- "one-shot"
981
- );
982
- index += 1;
983
- continue;
984
- }
985
- if (argument === "--continue-loop") {
986
- const prompt = wrapperArgs[index + 1];
987
- if (!prompt) {
988
- throw new Error("Missing value for --continue-loop.");
989
- }
990
- continueLoopSource = setContinuationSource(
991
- continueLoopSource,
992
- {
993
- kind: "inline",
994
- template: prompt
995
- },
996
- "loop"
997
- );
998
- index += 1;
999
- continue;
1000
- }
1001
- if (argument === "--continue-loop-file") {
1002
- const path6 = wrapperArgs[index + 1];
1003
- if (!path6) {
1004
- throw new Error("Missing value for --continue-loop-file.");
1005
- }
1006
- continueLoopSource = setContinuationSource(
1007
- continueLoopSource,
1008
- {
1009
- kind: "file",
1010
- path: path6
1011
- },
1012
- "loop"
1013
- );
1014
- index += 1;
1015
- continue;
1016
- }
1017
- if (argument === "--continue-loop-agent-file") {
1018
- const path6 = wrapperArgs[index + 1];
1019
- if (!path6) {
1020
- throw new Error("Missing value for --continue-loop-agent-file.");
1021
- }
1022
- continueLoopSource = setContinuationSource(
1023
- continueLoopSource,
1024
- {
1025
- kind: "bee-agent",
1026
- path: path6
1027
- },
1028
- "loop"
854
+ if (argument.startsWith("-")) {
855
+ throw new Error(
856
+ `Unknown bee option: ${argument}. Continuation, guardrails, and verification are configured from the UI after launch.`
1029
857
  );
1030
- index += 1;
1031
- continue;
1032
- }
1033
- if (argument === "--max-continues") {
1034
- const value = wrapperArgs[index + 1];
1035
- if (!value) {
1036
- throw new Error("Missing value for --max-continues.");
1037
- }
1038
- const parsed = Number.parseInt(value, 10);
1039
- if (!Number.isFinite(parsed) || parsed < 1) {
1040
- throw new Error(`Invalid --max-continues value: ${value}`);
1041
- }
1042
- maxContinues = parsed;
1043
- index += 1;
1044
- continue;
1045
- }
1046
- if (argument === "--inject-delay-ms") {
1047
- const value = wrapperArgs[index + 1];
1048
- if (!value) {
1049
- throw new Error("Missing value for --inject-delay-ms.");
1050
- }
1051
- const parsed = Number.parseInt(value, 10);
1052
- if (!Number.isFinite(parsed) || parsed < 0) {
1053
- throw new Error(`Invalid --inject-delay-ms value: ${value}`);
1054
- }
1055
- injectDelayMs = parsed;
1056
- index += 1;
1057
- continue;
1058
- }
1059
- if (argument === "--max-duration") {
1060
- const value = wrapperArgs[index + 1];
1061
- if (!value) {
1062
- throw new Error("Missing value for --max-duration.");
1063
- }
1064
- maxDurationMs = parseDurationInput(value, {
1065
- allowEmpty: false
1066
- });
1067
- index += 1;
1068
- continue;
1069
- }
1070
- if (argument === "--verify-command") {
1071
- const value = wrapperArgs[index + 1];
1072
- if (!value) {
1073
- throw new Error("Missing value for --verify-command.");
1074
- }
1075
- verificationCommand = value;
1076
- index += 1;
1077
- continue;
1078
858
  }
1079
- throw new Error(`Unknown bee option: ${argument}`);
1080
- }
1081
- if (continueLoopSource && continueOnceSource) {
1082
- throw new Error("Use either --continue-once or --continue-loop, not both.");
1083
- }
1084
- if (!continueLoopSource && maxContinues !== 3) {
1085
- throw new Error("--max-continues requires --continue-loop.");
1086
- }
1087
- if (!continueLoopSource && !continueOnceSource && maxDurationMs !== null) {
1088
- throw new Error("--max-duration requires --continue-once or --continue-loop.");
1089
- }
1090
- if (!continueLoopSource && !continueOnceSource && verificationCommand !== null) {
1091
- throw new Error("--verify-command requires --continue-once or --continue-loop.");
1092
- }
1093
- const hasWrapperBehavior = continueLoopSource !== null || continueOnceSource !== null || injectDelayMs !== 0 || maxContinues !== 3 || maxDurationMs !== null || verificationCommand !== null;
1094
- if (!showHelp && !showVersion && command === null && hasWrapperBehavior) {
1095
- throw new Error("Missing command to wrap. Use `bee <command> [args]`.");
859
+ throw new Error(`Unexpected bee argument before the wrapped command: ${argument}`);
1096
860
  }
1097
861
  return {
1098
862
  command,
1099
863
  commandArgs,
1100
- continueLoopSource,
1101
- continueOnceSource,
1102
- injectDelayMs,
1103
- maxContinues,
1104
- maxDurationMs,
1105
864
  showHelp,
1106
- showVersion,
1107
- verificationCommand
865
+ showVersion
1108
866
  };
1109
867
  }
1110
868
 
@@ -2133,6 +1891,11 @@ var PtyProxy = class {
2133
1891
  this.#child = child;
2134
1892
  child.stdout.on("data", (chunk) => {
2135
1893
  const normalizedChunk = sanitizeTerminalOutput(chunk);
1894
+ this.#options.onOutputActivity?.({
1895
+ bytes: normalizedChunk.length,
1896
+ source: "stdout",
1897
+ timestamp: Date.now()
1898
+ });
2136
1899
  if (this.#outputMuted) {
2137
1900
  this.#bufferedStdoutChunks.push(Buffer.from(normalizedChunk));
2138
1901
  return;
@@ -2141,6 +1904,11 @@ var PtyProxy = class {
2141
1904
  });
2142
1905
  child.stderr.on("data", (chunk) => {
2143
1906
  const normalizedChunk = sanitizeTerminalOutput(chunk);
1907
+ this.#options.onOutputActivity?.({
1908
+ bytes: normalizedChunk.length,
1909
+ source: "stderr",
1910
+ timestamp: Date.now()
1911
+ });
2144
1912
  if (this.#outputMuted) {
2145
1913
  this.#bufferedStderrChunks.push(Buffer.from(normalizedChunk));
2146
1914
  return;
@@ -2585,6 +2353,7 @@ function createDefaultRuntimeSnapshot() {
2585
2353
  code: 0,
2586
2354
  ok: true
2587
2355
  },
2356
+ lastCodexOutputAt: null,
2588
2357
  stopAt: null,
2589
2358
  stopScript: "pnpm test",
2590
2359
  totalTurns: null
@@ -3097,6 +2866,7 @@ import { Box as Box2, useApp, useInput, useStdin, useStdout } from "ink";
3097
2866
  import { useEffect, useState } from "react";
3098
2867
 
3099
2868
  // src/ui/core/view-model.ts
2869
+ var CODEX_ACTIVITY_WINDOW_MS = 1800;
3100
2870
  var PROMPT_PREVIEW_LINE_LIMIT = 5;
3101
2871
  var CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
3102
2872
  function toSingleLine3(value) {
@@ -3141,18 +2911,37 @@ function formatHookStatus(snapshot) {
3141
2911
  }
3142
2912
  return `hook: ${snapshot.session.hookStatus.ok ? "\u2713" : "\u2717"} ${snapshot.session.hookStatus.code}`;
3143
2913
  }
3144
- function formatSessionInfo(snapshot, now = Date.now()) {
2914
+ function hasRecentCodexActivity(snapshot, now = Date.now()) {
2915
+ if (snapshot.connectionState === "disconnected") {
2916
+ return false;
2917
+ }
2918
+ if (snapshot.session.lastCodexOutputAt === null) {
2919
+ return false;
2920
+ }
2921
+ return now - snapshot.session.lastCodexOutputAt <= CODEX_ACTIVITY_WINDOW_MS;
2922
+ }
2923
+ function getDisplayedRunState(snapshot, now = Date.now()) {
2924
+ if (hasRecentCodexActivity(snapshot, now)) {
2925
+ return "running";
2926
+ }
2927
+ if (snapshot.runState === "running") {
2928
+ return "quiet";
2929
+ }
2930
+ return "idle";
2931
+ }
2932
+ function formatSessionInfo(snapshot, mode, now = Date.now()) {
2933
+ if (mode === "off") {
2934
+ return "Bee is in Off mode. Codex-bee stays passive and only records session stats and turn history.";
2935
+ }
3145
2936
  const parts = [];
3146
2937
  if (snapshot.session.currentTurn !== null && snapshot.session.totalTurns !== null) {
3147
2938
  parts.push(`${snapshot.session.currentTurn}/${snapshot.session.totalTurns} turns`);
3148
- } else if (snapshot.session.currentTurn !== null) {
3149
- parts.push(`${snapshot.session.currentTurn} turns`);
3150
2939
  }
3151
2940
  const timeLeftLabel = formatTimeLeft(snapshot.session.stopAt, now);
3152
2941
  if (timeLeftLabel) {
3153
2942
  parts.push(timeLeftLabel);
3154
2943
  }
3155
- const hookStatusLabel = formatHookStatus(snapshot);
2944
+ const hookStatusLabel = snapshot.session.stopScript ? formatHookStatus(snapshot) : null;
3156
2945
  if (hookStatusLabel) {
3157
2946
  parts.push(hookStatusLabel);
3158
2947
  }
@@ -3390,13 +3179,15 @@ function renderLine(line, width) {
3390
3179
  import { jsx as jsx2 } from "react/jsx-runtime";
3391
3180
  var MAX_CONTENT_WIDTH = 200;
3392
3181
  var MIN_CONTENT_WIDTH = 80;
3182
+ var LOADER_TICK_MS = 160;
3393
3183
  var PROMPT_BAR = "\u2503";
3394
3184
  var TIMESTAMP_WIDTH = 8;
3395
- var LOADER_FRAMES = [
3396
- ["\u25A0", "\u25A0", "\u25A1", "\u25A1"],
3397
- ["\u25A1", "\u25A0", "\u25A0", "\u25A1"],
3398
- ["\u25A1", "\u25A1", "\u25A0", "\u25A0"],
3399
- ["\u25A1", "\u25A0", "\u25A1", "\u25A0"]
3185
+ var LOADER_GLYPHS = ["\u25A0", "\u25A0", "\u25A0", "\u25A0"];
3186
+ var LOADER_COLOR_FRAMES = [
3187
+ ["#4F3500", "#8A5B00", "#D89200", "#FFD35A"],
3188
+ ["#8A5B00", "#D89200", "#FFD35A", "#D89200"],
3189
+ ["#D89200", "#FFD35A", "#D89200", "#8A5B00"],
3190
+ ["#FFD35A", "#D89200", "#8A5B00", "#4F3500"]
3400
3191
  ];
3401
3192
  function createPalette(stylesEnabled) {
3402
3193
  if (!stylesEnabled) {
@@ -3527,41 +3318,6 @@ function buildFigletLines(title, maxWidth) {
3527
3318
  }
3528
3319
  return [title];
3529
3320
  }
3530
- function buildAnnouncementPanelLines(state, width, palette) {
3531
- if (!state.runtime.announcement || width < 8) {
3532
- return [];
3533
- }
3534
- const innerWidth = Math.max(width - 4, 4);
3535
- const contentLines = wrapText(state.runtime.announcement.message, innerWidth);
3536
- const borderColor = palette.announcementBorder ?? palette.accent ?? palette.primary;
3537
- return [
3538
- createTextLine("announcement-top", `\u256D${"\u2500".repeat(Math.max(width - 2, 0))}\u256E`, {
3539
- backgroundColor: palette.announcementBackground,
3540
- color: borderColor
3541
- }),
3542
- ...contentLines.map((line, index) => createLine(`announcement-${index}`, [
3543
- {
3544
- backgroundColor: palette.announcementBackground,
3545
- color: borderColor,
3546
- text: "\u2502 "
3547
- },
3548
- {
3549
- backgroundColor: palette.announcementBackground,
3550
- color: palette.announcementText ?? palette.bodyText,
3551
- text: padDisplayWidth(line, innerWidth)
3552
- },
3553
- {
3554
- backgroundColor: palette.announcementBackground,
3555
- color: borderColor,
3556
- text: " \u2502"
3557
- }
3558
- ], palette.announcementBackground)),
3559
- createTextLine("announcement-bottom", `\u2570${"\u2500".repeat(Math.max(width - 2, 0))}\u256F`, {
3560
- backgroundColor: palette.announcementBackground,
3561
- color: borderColor
3562
- })
3563
- ];
3564
- }
3565
3321
  function buildHeaderColumnLines(state, title, width, palette) {
3566
3322
  const titleLines = buildFigletLines(title.toUpperCase(), Math.max(width, 16));
3567
3323
  return [
@@ -3569,8 +3325,7 @@ function buildHeaderColumnLines(state, title, width, palette) {
3569
3325
  createTextLine("versions", `codex-bee v${state.runtime.beeVersion} \u2022 codex v${state.runtime.codexVersion}`, {
3570
3326
  color: palette.muted,
3571
3327
  dimColor: !palette.muted
3572
- }),
3573
- ...buildAnnouncementPanelLines(state, width, palette)
3328
+ })
3574
3329
  ];
3575
3330
  }
3576
3331
  function buildHeaderLines(state, title, width, palette) {
@@ -3592,7 +3347,7 @@ function getLogColor(kind, palette) {
3592
3347
  return palette.muted;
3593
3348
  }
3594
3349
  function buildDividerLabel(turn, totalTurns, durationMs, width) {
3595
- const parts = [totalTurns === null ? `${turn} turns` : `${turn}/${totalTurns} turns`];
3350
+ const parts = [totalTurns === null ? `turn ${turn}` : `turn ${turn}/${totalTurns}`];
3596
3351
  if (durationMs !== null) {
3597
3352
  parts.push(formatCompactDuration(durationMs));
3598
3353
  }
@@ -3713,17 +3468,17 @@ function buildLogLines(state, width, palette) {
3713
3468
  state.runtime.logs.forEach((group, groupIndex) => {
3714
3469
  if (groupIndex > 0) {
3715
3470
  logLines.push(createSpacerLine(`divider-gap-${group.id}`, palette.logBackground));
3716
- logLines.push(createTextLine(`divider-${group.id}`, buildDividerLabel(
3717
- group.turn,
3718
- group.totalTurns,
3719
- group.durationMs,
3720
- width
3721
- ), {
3722
- backgroundColor: palette.logBackground,
3723
- color: palette.muted,
3724
- dimColor: !palette.muted
3725
- }));
3726
3471
  }
3472
+ logLines.push(createTextLine(`divider-${group.id}`, buildDividerLabel(
3473
+ group.turn,
3474
+ group.totalTurns,
3475
+ group.durationMs,
3476
+ width
3477
+ ), {
3478
+ backgroundColor: palette.logBackground,
3479
+ color: palette.muted,
3480
+ dimColor: !palette.muted
3481
+ }));
3727
3482
  group.entries.forEach((entry) => {
3728
3483
  const previewLines = wrapAndLimit(normalizeLogEntryText(entry), contentWidth, 3);
3729
3484
  previewLines.forEach((line, lineIndex) => {
@@ -3757,22 +3512,20 @@ function buildBodyLines(state, title, width, palette) {
3757
3512
  ...buildLogLines(state, width, palette)
3758
3513
  ];
3759
3514
  }
3760
- function buildLoaderSegments(palette, animationTick) {
3761
- const modeIndex = animationTick % LOADER_FRAMES.length;
3762
- const frame = LOADER_FRAMES[modeIndex] ?? LOADER_FRAMES[0];
3763
- return frame.map((glyph, index) => ({
3764
- color: ["#FFD700", palette.primary, "#FFAA00", palette.secondary][index] ?? palette.primary,
3515
+ function buildLoaderSegments(palette, animationTick, isActive) {
3516
+ const frame = LOADER_COLOR_FRAMES[animationTick % LOADER_COLOR_FRAMES.length] ?? LOADER_COLOR_FRAMES[0];
3517
+ return LOADER_GLYPHS.map((glyph, index) => ({
3518
+ color: isActive ? frame[index] ?? palette.primary : ["#A87400", "#BB8500", "#CF9600", "#E0A700"][index] ?? palette.primary,
3519
+ dimColor: !isActive,
3765
3520
  text: glyph
3766
3521
  }));
3767
3522
  }
3768
3523
  function measureSegmentsWidth(segments) {
3769
3524
  return segments.reduce((total, segment) => total + measureText(segment.text), 0);
3770
3525
  }
3771
- function buildStatusTokens(state, palette, animationTick, width) {
3772
- const loaderToken = state.runtime.runState === "running" ? buildLoaderSegments(palette, animationTick) : LOADER_FRAMES[0].map((glyph, index) => ({
3773
- color: ["#FFD700", palette.primary, "#FFAA00", palette.secondary][index] ?? palette.primary,
3774
- text: glyph
3775
- }));
3526
+ function buildStatusTokens(state, palette, animationTick, now, width) {
3527
+ const displayedRunState = getDisplayedRunState(state.runtime, now);
3528
+ const loaderToken = buildLoaderSegments(palette, animationTick, displayedRunState === "running");
3776
3529
  const connectionSpec = getConnectionTokenSpec(state.runtime.connectionState, palette);
3777
3530
  const connectionToken = [
3778
3531
  {
@@ -3786,24 +3539,10 @@ function buildStatusTokens(state, palette, animationTick, width) {
3786
3539
  text: connectionSpec.compactLabel
3787
3540
  }
3788
3541
  ];
3789
- const versionToken = [
3790
- {
3791
- color: palette.muted,
3792
- dimColor: !palette.muted,
3793
- text: `codex v${state.runtime.codexVersion}`
3794
- }
3795
- ];
3796
- const shortVersionToken = [
3797
- {
3798
- color: palette.muted,
3799
- dimColor: !palette.muted,
3800
- text: `v${state.runtime.codexVersion}`
3801
- }
3802
- ];
3803
3542
  const runStateToken = [
3804
3543
  {
3805
- color: state.runtime.runState === "running" ? palette.primary : palette.muted,
3806
- text: state.runtime.runState === "running" ? "Running" : "Idle"
3544
+ color: displayedRunState === "running" ? palette.primary : displayedRunState === "quiet" ? palette.warning : palette.muted,
3545
+ text: displayedRunState === "running" ? "Running" : displayedRunState === "quiet" ? "Quiet" : "Idle"
3807
3546
  }
3808
3547
  ];
3809
3548
  const hotkeyToken = [
@@ -3835,9 +3574,8 @@ function buildStatusTokens(state, palette, animationTick, width) {
3835
3574
  }
3836
3575
  ];
3837
3576
  const variants = [
3838
- [loaderToken, connectionToken, versionToken, runStateToken, hotkeyToken, commandsToken],
3839
- [loaderToken, connectionToken, shortVersionToken, runStateToken, hotkeyToken, commandsToken],
3840
- [loaderToken, connectionToken, shortVersionToken, runStateToken, hotkeyToken, shortCommandsToken],
3577
+ [loaderToken, connectionToken, runStateToken, hotkeyToken, commandsToken],
3578
+ [loaderToken, compactConnectionToken, runStateToken, hotkeyToken, commandsToken],
3841
3579
  [loaderToken, compactConnectionToken, runStateToken, shortHotkeyToken, shortCommandsToken]
3842
3580
  ];
3843
3581
  for (const variant of variants) {
@@ -3851,9 +3589,9 @@ function buildStatusTokens(state, palette, animationTick, width) {
3851
3589
  }
3852
3590
  return variants[variants.length - 1] ?? [];
3853
3591
  }
3854
- function buildStatusBarLine(state, palette, animationTick, width) {
3592
+ function buildStatusBarLine(state, palette, animationTick, now, width) {
3855
3593
  const backgroundColor = palette.statusBottomBackground;
3856
- const tokens = buildStatusTokens(state, palette, animationTick, width);
3594
+ const tokens = buildStatusTokens(state, palette, animationTick, now, width);
3857
3595
  const segments = tokens.flatMap((token, index) => index === 0 ? token : [
3858
3596
  {
3859
3597
  text: " \u2022 "
@@ -3933,30 +3671,42 @@ function buildDropdownLines(state, width, palette) {
3933
3671
  ], backgroundColor);
3934
3672
  });
3935
3673
  }
3936
- function buildNoticeLine(state, palette) {
3937
- if (!state.notice) {
3938
- return null;
3674
+ function buildPromptPreviewLines(state, width) {
3675
+ if (state.activeMode === "off") {
3676
+ const lengths = [
3677
+ Math.max(Math.floor(width * 0.68), 18),
3678
+ Math.max(Math.floor(width * 0.52), 14),
3679
+ Math.max(Math.floor(width * 0.6), 16)
3680
+ ];
3681
+ return lengths.map((length, index) => ({
3682
+ colorKind: "muted",
3683
+ text: "\u2591".repeat(Math.max(Math.min(length - index, width), 8))
3684
+ }));
3939
3685
  }
3940
- return createTextLine(`notice-${state.notice.id}`, `\u26A0 ${state.notice.message}`, {
3941
- backgroundColor: palette.statusBottomBackground,
3942
- color: state.notice.kind === "error" ? palette.error : state.notice.kind === "warning" ? palette.warning : palette.primary
3943
- });
3686
+ return formatPromptPreview(getActivePrompt(state), 5).flatMap((line) => wrapAndLimit(line, width, 5)).slice(0, 5).map((line) => ({
3687
+ colorKind: "body",
3688
+ text: line
3689
+ }));
3944
3690
  }
3945
3691
  function buildPromptFooterLines(state, width, palette, cursorVisible, animationTick, now) {
3946
3692
  const lines = [];
3947
3693
  const modeColor = getModeColor(state.activeMode, palette);
3948
3694
  const promptPreviewWidth = Math.max(width - 2, 16);
3949
- const promptLines = formatPromptPreview(getActivePrompt(state), 5).flatMap((line) => wrapAndLimit(line, promptPreviewWidth, 5)).slice(0, 5);
3695
+ const promptLines = buildPromptPreviewLines(state, promptPreviewWidth);
3950
3696
  const inputFieldWidth = Math.max(width - 2, 12);
3951
3697
  const inputContentWidth = Math.max(inputFieldWidth - 1, 8);
3952
3698
  const composerLines = buildComposerVisualLines(state.composerText, inputContentWidth);
3953
3699
  const cursorLineIndex = getComposerCursorLineIndex(composerLines, state.composerCursorOffset);
3954
3700
  const placeholder = getVisibleSlashPlaceholder(state);
3955
- lines.push(createTextLine("session-info", formatSessionInfo(state.runtime, now) || "Session metadata will appear after the first turn.", {
3956
- backgroundColor: palette.statusTopBackground,
3957
- color: palette.bodyText
3958
- }));
3959
- lines.push(createSpacerLine("session-gap", palette.footerBase));
3701
+ const sessionInfoText = formatSessionInfo(state.runtime, state.activeMode, now);
3702
+ if (sessionInfoText) {
3703
+ lines.push(createTextLine("session-info", sessionInfoText, {
3704
+ backgroundColor: palette.statusTopBackground,
3705
+ color: state.activeMode === "off" ? palette.muted : palette.bodyText,
3706
+ dimColor: state.activeMode === "off" && !palette.muted
3707
+ }));
3708
+ lines.push(createSpacerLine("session-gap", palette.footerBase));
3709
+ }
3960
3710
  lines.push(createTextLine("prompt-top-border", "\u2500".repeat(width), {
3961
3711
  backgroundColor: palette.promptBackground,
3962
3712
  color: palette.promptBorder ?? palette.muted
@@ -3975,8 +3725,9 @@ function buildPromptFooterLines(state, width, palette, cursorVisible, animationT
3975
3725
  },
3976
3726
  {
3977
3727
  backgroundColor: palette.promptBackground,
3978
- color: palette.bodyText,
3979
- text: padDisplayWidth(line, width - 2)
3728
+ color: line.colorKind === "muted" ? palette.muted : palette.bodyText,
3729
+ dimColor: line.colorKind === "muted",
3730
+ text: padDisplayWidth(line.text, width - 2)
3980
3731
  }
3981
3732
  ], palette.promptBackground));
3982
3733
  });
@@ -4048,7 +3799,7 @@ function buildPromptFooterLines(state, width, palette, cursorVisible, animationT
4048
3799
  color: palette.promptBorder ?? palette.muted
4049
3800
  }));
4050
3801
  lines.push(createSpacerLine("footer-gap", palette.footerBase));
4051
- lines.push(buildStatusBarLine(state, palette, animationTick, width));
3802
+ lines.push(buildStatusBarLine(state, palette, animationTick, now, width));
4052
3803
  lines.push(createSpacerLine("workspace-gap", palette.footerBase));
4053
3804
  lines.push(createTextLine("workspace", formatWorkspaceLine(state.runtime.workspace), {
4054
3805
  backgroundColor: palette.footerMutedBackground,
@@ -4060,8 +3811,7 @@ function buildPromptFooterLines(state, width, palette, cursorVisible, animationT
4060
3811
  function buildBeeScreenLines(state, title, columns, cursorVisible, palette, animationTick, now) {
4061
3812
  const bodyLines = buildBodyLines(state, title, columns, palette);
4062
3813
  const footerLines = buildPromptFooterLines(state, columns, palette, cursorVisible, animationTick, now);
4063
- const noticeLine = buildNoticeLine(state, palette);
4064
- return noticeLine ? [...bodyLines, createSpacerLine("body-footer-gap"), ...footerLines, createSpacerLine("notice-gap", palette.footerBase), noticeLine] : [...bodyLines, createSpacerLine("body-footer-gap"), ...footerLines];
3814
+ return [...bodyLines, createSpacerLine("body-footer-gap"), ...footerLines];
4065
3815
  }
4066
3816
  function buildCodexScreenLines(state, columns, palette) {
4067
3817
  const bodyLines = state.runtime.codexBufferLines.map((line, index) => createTextLine(`codex-${index}`, line, {
@@ -4083,8 +3833,7 @@ function buildCodexScreenLines(state, columns, palette) {
4083
3833
  dimColor: !palette.muted
4084
3834
  })
4085
3835
  ];
4086
- const noticeLine = buildNoticeLine(state, palette);
4087
- return noticeLine ? [...bodyLines, createSpacerLine("codex-gap"), ...footerLines, createSpacerLine("codex-notice-gap", palette.footerBase), noticeLine] : [...bodyLines, createSpacerLine("codex-gap"), ...footerLines];
3836
+ return [...bodyLines, createSpacerLine("codex-gap"), ...footerLines];
4088
3837
  }
4089
3838
  function buildNarrowScreenLines(columns, palette) {
4090
3839
  return [
@@ -4115,7 +3864,7 @@ function InkControlApp({
4115
3864
  const [isPinnedToBottom, setIsPinnedToBottom] = useState(true);
4116
3865
  const [scrollOffset, setScrollOffset] = useState(0);
4117
3866
  const [clockNow, setClockNow] = useState(() => Date.now());
4118
- const animationTick = 0;
3867
+ const animationTick = Math.floor(clockNow / LOADER_TICK_MS);
4119
3868
  const cursorVisible = true;
4120
3869
  const [viewport] = useState(() => ({
4121
3870
  columns: stdout.columns ?? 100,
@@ -4141,17 +3890,14 @@ function InkControlApp({
4141
3890
  });
4142
3891
  }, [allowQuit, controller, exit, state.exitRequested]);
4143
3892
  useEffect(() => {
4144
- if (state.runtime.session.stopAt === null) {
4145
- return;
4146
- }
4147
3893
  setClockNow(Date.now());
4148
3894
  const intervalId = setInterval(() => {
4149
3895
  setClockNow(Date.now());
4150
- }, 1e3);
3896
+ }, LOADER_TICK_MS);
4151
3897
  return () => {
4152
3898
  clearInterval(intervalId);
4153
3899
  };
4154
- }, [state.runtime.session.stopAt]);
3900
+ }, []);
4155
3901
  const palette = createPalette(stdout.isTTY !== false && !process.env.NO_COLOR && process.env.TERM !== "dumb");
4156
3902
  const contentWidth = Math.min(Math.max(viewport.columns, MIN_CONTENT_WIDTH), MAX_CONTENT_WIDTH);
4157
3903
  const renderWidth = viewport.columns < MIN_CONTENT_WIDTH ? viewport.columns : contentWidth;
@@ -4455,6 +4201,7 @@ import { execFile } from "child_process";
4455
4201
  import process7 from "process";
4456
4202
  import { promisify } from "util";
4457
4203
  var execFileAsync = promisify(execFile);
4204
+ var OUTPUT_ACTIVITY_NOTIFY_INTERVAL_MS = 250;
4458
4205
  var MAX_EVENT_LINES = 8;
4459
4206
  function cloneLogEntry(entry) {
4460
4207
  return {
@@ -4533,6 +4280,7 @@ function createInitialSnapshot(options) {
4533
4280
  session: {
4534
4281
  currentTurn: 0,
4535
4282
  hookStatus: null,
4283
+ lastCodexOutputAt: null,
4536
4284
  stopAt: getStopAt(continuationSnapshot),
4537
4285
  stopScript: options.initialVerificationCommand ?? null,
4538
4286
  totalTurns: toUiTotalTurns(continuationSnapshot.maxContinues)
@@ -4549,6 +4297,13 @@ function getNextSleepTimestamp(hours, minutes, now = Date.now()) {
4549
4297
  }
4550
4298
  return nextTarget.getTime();
4551
4299
  }
4300
+ function formatTimeLeftSuffix(stopAt) {
4301
+ const label = formatTimeLeft(stopAt);
4302
+ if (!label) {
4303
+ return "";
4304
+ }
4305
+ return ` (${label.replace(/^time left:\s*/u, "")} left)`;
4306
+ }
4552
4307
  function createLogEntry(id, kind, text, timestamp) {
4553
4308
  return {
4554
4309
  id,
@@ -4664,6 +4419,8 @@ var LiveUiRuntimeHost = class {
4664
4419
  #listeners = /* @__PURE__ */ new Set();
4665
4420
  #recentEvents = [];
4666
4421
  #entryCounter = 0;
4422
+ #handledStopFingerprints = /* @__PURE__ */ new Set();
4423
+ #lastOutputActivityNotificationAt = 0;
4667
4424
  #pendingPrompts = [];
4668
4425
  #snapshot;
4669
4426
  #verificationCommand;
@@ -4695,10 +4452,26 @@ var LiveUiRuntimeHost = class {
4695
4452
  this.#pushEventLine("Codex session disconnected.");
4696
4453
  this.#notify();
4697
4454
  }
4455
+ recordPtyOutputActivity(timestamp = Date.now()) {
4456
+ this.#snapshot = {
4457
+ ...this.#snapshot,
4458
+ session: {
4459
+ ...this.#snapshot.session,
4460
+ lastCodexOutputAt: timestamp
4461
+ }
4462
+ };
4463
+ if (timestamp - this.#lastOutputActivityNotificationAt < OUTPUT_ACTIVITY_NOTIFY_INTERVAL_MS) {
4464
+ return;
4465
+ }
4466
+ this.#lastOutputActivityNotificationAt = timestamp;
4467
+ this.#notify();
4468
+ }
4698
4469
  recordAuxiliaryHookEvent(capture) {
4699
4470
  const eventName = capture.payload?.hook_event_name ?? null;
4700
4471
  if (eventName === "SessionStart") {
4701
4472
  const source = capture.payload?.source;
4473
+ this.#handledStopFingerprints.clear();
4474
+ this.#pendingPrompts = [];
4702
4475
  this.#snapshot = {
4703
4476
  ...this.#snapshot,
4704
4477
  connectionState: "connected"
@@ -4719,9 +4492,11 @@ var LiveUiRuntimeHost = class {
4719
4492
  runState: "running",
4720
4493
  session: {
4721
4494
  ...this.#snapshot.session,
4722
- currentTurn: this.#snapshot.logs.length
4495
+ currentTurn: this.#snapshot.logs.length,
4496
+ lastCodexOutputAt: submittedAt
4723
4497
  }
4724
4498
  };
4499
+ this.#lastOutputActivityNotificationAt = submittedAt;
4725
4500
  this.#pushEventLine(
4726
4501
  promptText ? `UserPromptSubmit received. Codex turn is running: ${formatCodexEcho(promptText)}` : "UserPromptSubmit received. Codex turn is running."
4727
4502
  );
@@ -4746,6 +4521,8 @@ var LiveUiRuntimeHost = class {
4746
4521
  async executeCommand(command) {
4747
4522
  switch (command.id) {
4748
4523
  case "clear-log":
4524
+ this.#handledStopFingerprints.clear();
4525
+ this.#pendingPrompts = [];
4749
4526
  this.#snapshot = {
4750
4527
  ...this.#snapshot,
4751
4528
  logs: [],
@@ -4763,9 +4540,11 @@ var LiveUiRuntimeHost = class {
4763
4540
  maxDurationMs: command.minutes * 60 * 1e3,
4764
4541
  prompt: currentContinuation.promptText
4765
4542
  });
4543
+ const stopAt = getStopAt(updatedSnapshot);
4544
+ const timeLeftSuffix = formatTimeLeftSuffix(stopAt);
4766
4545
  this.#syncContinuationState(updatedSnapshot);
4767
- this.#pushEventLine(`Updated the session duration to ${command.minutes} minute(s).`);
4768
- return this.#createUpdate(`Set the live session duration to ${command.minutes} minute(s).`);
4546
+ this.#pushEventLine(`Updated the session duration to ${command.minutes} minute(s)${timeLeftSuffix}.`);
4547
+ return this.#createUpdate(`Set the live session duration to ${command.minutes} minute(s)${timeLeftSuffix}.`);
4769
4548
  }
4770
4549
  case "quit":
4771
4550
  return this.#createUpdate("Exiting the current codex-bee session.", {
@@ -4780,20 +4559,21 @@ var LiveUiRuntimeHost = class {
4780
4559
  maxDurationMs: Math.max(stopAt - Date.now(), 1e3),
4781
4560
  prompt: currentContinuation.promptText
4782
4561
  });
4562
+ const timeLeftSuffix = formatTimeLeftSuffix(getStopAt(updatedSnapshot));
4783
4563
  this.#syncContinuationState(updatedSnapshot);
4784
- this.#pushEventLine(`Set the absolute stop time to ${command.time}.`);
4785
- return this.#createUpdate(`Set the live stop time to ${command.time}.`);
4564
+ this.#pushEventLine(`Set the absolute stop time to ${command.time}${timeLeftSuffix}.`);
4565
+ return this.#createUpdate(`Set the live stop time to ${command.time}${timeLeftSuffix}.`);
4786
4566
  }
4787
4567
  case "steps-count": {
4788
4568
  const currentContinuation = this.#continuation.getSnapshot();
4789
4569
  const updatedSnapshot = this.#continuation.updateInteractiveDraft({
4790
- maxContinues: command.steps,
4570
+ maxContinues: currentContinuation.injectionsUsed + command.steps,
4791
4571
  maxDurationMs: currentContinuation.maxDurationMs,
4792
4572
  prompt: currentContinuation.promptText
4793
4573
  });
4794
4574
  this.#syncContinuationState(updatedSnapshot);
4795
- this.#pushEventLine(`Updated the max turns count to ${command.steps}.`);
4796
- return this.#createUpdate(`Updated the live max turns count to ${command.steps}.`);
4575
+ this.#pushEventLine(`Updated the remaining max turns count to ${command.steps}.`);
4576
+ return this.#createUpdate(`Updated the live remaining max turns count to ${command.steps}.`);
4797
4577
  }
4798
4578
  case "stop-script":
4799
4579
  this.#verificationCommand = command.command;
@@ -4878,6 +4658,12 @@ var LiveUiRuntimeHost = class {
4878
4658
  }
4879
4659
  recordStopCaptureHandled(event) {
4880
4660
  const completedAt = Date.now();
4661
+ const terminalStatusMessage = buildTerminalStatusMessage(event);
4662
+ const stopFingerprint = this.#buildStopFingerprint(event, terminalStatusMessage);
4663
+ if (this.#handledStopFingerprints.has(stopFingerprint)) {
4664
+ return;
4665
+ }
4666
+ this.#handledStopFingerprints.add(stopFingerprint);
4881
4667
  const pendingPrompt = this.#pendingPrompts.shift() ?? null;
4882
4668
  const transcriptUserMessage = readUserMessageFromCapture(event.capture);
4883
4669
  const existingGroupIndex = event.capture.payload?.turn_id ? this.#snapshot.logs.findIndex((group) => group.id === event.capture.payload?.turn_id) : -1;
@@ -4900,7 +4686,6 @@ var LiveUiRuntimeHost = class {
4900
4686
  );
4901
4687
  }
4902
4688
  entries.push(this.#nextLogEntry("hook", hookSummary.text, completedAt));
4903
- const terminalStatusMessage = buildTerminalStatusMessage(event);
4904
4689
  if (terminalStatusMessage) {
4905
4690
  entries.push(this.#nextLogEntry("status", terminalStatusMessage, completedAt));
4906
4691
  }
@@ -4910,23 +4695,26 @@ var LiveUiRuntimeHost = class {
4910
4695
  durationMs: turnStartedAt !== null ? Math.max(completedAt - turnStartedAt, 0) : existingGroup?.durationMs ?? null,
4911
4696
  entries,
4912
4697
  id: groupId,
4913
- totalTurns: toUiTotalTurns(continuationSnapshot.maxContinues),
4698
+ totalTurns: null,
4914
4699
  turn: turnNumber
4915
4700
  };
4916
4701
  const nextLogs = existingGroupIndex >= 0 ? this.#snapshot.logs.map((group, index) => index === existingGroupIndex ? nextGroup : group) : [...this.#snapshot.logs, nextGroup];
4702
+ const nextTotalTurns = this.#getUiTotalTurns(continuationSnapshot, nextLogs);
4703
+ const finalizedLogs = this.#applyTotalTurns(nextLogs, nextTotalTurns);
4917
4704
  this.#snapshot = {
4918
4705
  ...this.#snapshot,
4919
4706
  connectionState: "connected",
4920
4707
  announcement: this.#buildAnnouncement(event, terminalStatusMessage),
4921
- logs: nextLogs,
4708
+ logs: finalizedLogs,
4922
4709
  runState: "idle",
4923
4710
  session: {
4924
4711
  ...this.#snapshot.session,
4925
- currentTurn: nextLogs.length,
4712
+ currentTurn: finalizedLogs.length,
4926
4713
  hookStatus: hookSummary.hookStatus,
4714
+ lastCodexOutputAt: completedAt,
4927
4715
  stopAt: getStopAt(continuationSnapshot),
4928
4716
  stopScript: this.#verificationCommand,
4929
- totalTurns: toUiTotalTurns(continuationSnapshot.maxContinues)
4717
+ totalTurns: nextTotalTurns
4930
4718
  }
4931
4719
  };
4932
4720
  this.#pushEventLine(
@@ -4968,8 +4756,8 @@ var LiveUiRuntimeHost = class {
4968
4756
  if (!promptText || targetGroup.entries.some((entry) => entry.kind === "user")) {
4969
4757
  return;
4970
4758
  }
4971
- const nextLogs = [...this.#snapshot.logs];
4972
- nextLogs[targetIndex] = {
4759
+ const nextLogs2 = [...this.#snapshot.logs];
4760
+ nextLogs2[targetIndex] = {
4973
4761
  ...targetGroup,
4974
4762
  entries: [
4975
4763
  this.#nextLogEntry("user", promptText, startedAt),
@@ -4978,7 +4766,10 @@ var LiveUiRuntimeHost = class {
4978
4766
  };
4979
4767
  this.#snapshot = {
4980
4768
  ...this.#snapshot,
4981
- logs: nextLogs
4769
+ logs: this.#applyTotalTurns(
4770
+ nextLogs2,
4771
+ this.#getUiTotalTurns(this.#continuation.getSnapshot(), nextLogs2)
4772
+ )
4982
4773
  };
4983
4774
  return;
4984
4775
  }
@@ -4992,12 +4783,14 @@ var LiveUiRuntimeHost = class {
4992
4783
  this.#nextLogEntry("user", promptText, startedAt)
4993
4784
  ],
4994
4785
  id: turnId,
4995
- totalTurns: toUiTotalTurns(this.#continuation.getSnapshot().maxContinues),
4786
+ totalTurns: null,
4996
4787
  turn: turnNumber
4997
4788
  };
4789
+ const nextLogs = [...this.#snapshot.logs, nextGroup];
4790
+ const nextTotalTurns = this.#getUiTotalTurns(this.#continuation.getSnapshot(), nextLogs);
4998
4791
  this.#snapshot = {
4999
4792
  ...this.#snapshot,
5000
- logs: [...this.#snapshot.logs, nextGroup]
4793
+ logs: this.#applyTotalTurns(nextLogs, nextTotalTurns)
5001
4794
  };
5002
4795
  }
5003
4796
  #buildAnnouncement(event, terminalStatusMessage) {
@@ -5057,10 +4850,13 @@ var LiveUiRuntimeHost = class {
5057
4850
  }
5058
4851
  #syncContinuationState(snapshot) {
5059
4852
  const connectionState = this.#snapshot.connectionState;
5060
- const currentTurn = this.#snapshot.logs.length;
4853
+ const nextTotalTurns = this.#getUiTotalTurns(snapshot, this.#snapshot.logs);
4854
+ const nextLogs = this.#applyTotalTurns(this.#snapshot.logs, nextTotalTurns);
4855
+ const currentTurn = nextLogs.length;
5061
4856
  this.#snapshot = {
5062
4857
  ...this.#snapshot,
5063
4858
  connectionState,
4859
+ logs: nextLogs,
5064
4860
  prompts: {
5065
4861
  ...this.#snapshot.prompts,
5066
4862
  static: snapshot.promptText
@@ -5071,10 +4867,37 @@ var LiveUiRuntimeHost = class {
5071
4867
  currentTurn,
5072
4868
  stopAt: getStopAt(snapshot),
5073
4869
  stopScript: this.#verificationCommand,
5074
- totalTurns: toUiTotalTurns(snapshot.maxContinues)
4870
+ totalTurns: nextTotalTurns
5075
4871
  }
5076
4872
  };
5077
4873
  }
4874
+ #buildStopFingerprint(event, terminalStatusMessage) {
4875
+ const turnId = event.capture.payload?.turn_id?.trim();
4876
+ if (turnId) {
4877
+ return `turn:${turnId}`;
4878
+ }
4879
+ return [
4880
+ event.capture.payload?.session_id ?? "session",
4881
+ event.capture.payload?.transcript_path ?? "",
4882
+ event.capture.payload?.last_assistant_message ?? "",
4883
+ terminalStatusMessage ?? ""
4884
+ ].join("::");
4885
+ }
4886
+ #getCompletedTurnCount(logs = this.#snapshot.logs) {
4887
+ return logs.filter((group) => group.entries.some((entry) => entry.kind !== "user")).length;
4888
+ }
4889
+ #getUiTotalTurns(snapshot, logs = this.#snapshot.logs) {
4890
+ if (!Number.isFinite(snapshot.maxContinues)) {
4891
+ return null;
4892
+ }
4893
+ return Math.max(this.#getCompletedTurnCount(logs) - snapshot.injectionsUsed + snapshot.maxContinues, 0);
4894
+ }
4895
+ #applyTotalTurns(logs, totalTurns) {
4896
+ return logs.map((group) => ({
4897
+ ...group,
4898
+ totalTurns
4899
+ }));
4900
+ }
5078
4901
  };
5079
4902
 
5080
4903
  // src/cli.ts
@@ -5346,9 +5169,8 @@ async function main() {
5346
5169
  }
5347
5170
  const runId = randomUUID();
5348
5171
  const runStartedAt = Date.now();
5349
- const hasContinuation = Boolean(options.continueLoopSource || options.continueOnceSource);
5350
5172
  const executionMode = resolveExecutionMode({
5351
- hasContinuation,
5173
+ hasContinuation: false,
5352
5174
  stdinIsTTY: process8.stdin.isTTY,
5353
5175
  stdoutIsTTY: process8.stdout.isTTY
5354
5176
  });
@@ -5387,28 +5209,7 @@ async function main() {
5387
5209
  configPath: hookSetup.activeConfig?.path ?? hookSetup.globalPath
5388
5210
  });
5389
5211
  }
5390
- const initialContinuation = options.continueLoopSource ? {
5391
- enabled: true,
5392
- maxContinues: options.maxContinues,
5393
- maxDurationMs: options.maxDurationMs,
5394
- promptSource: options.continueLoopSource
5395
- } : options.continueOnceSource ? {
5396
- enabled: true,
5397
- maxContinues: 1,
5398
- maxDurationMs: options.maxDurationMs,
5399
- promptSource: options.continueOnceSource
5400
- } : null;
5401
- if (initialContinuation) {
5402
- if (isBeeAgentSource(initialContinuation.promptSource)) {
5403
- readBeeAgentConfig(initialContinuation.promptSource, {
5404
- cwd: process8.cwd()
5405
- });
5406
- } else {
5407
- readContinuationTemplate(initialContinuation.promptSource, {
5408
- cwd: process8.cwd()
5409
- });
5410
- }
5411
- }
5212
+ let runtimeHost = null;
5412
5213
  const proxy = new PtyProxy({
5413
5214
  command: wrappedCommand,
5414
5215
  commandArgs: options.commandArgs,
@@ -5416,20 +5217,21 @@ async function main() {
5416
5217
  ...process8.env,
5417
5218
  CODEX_BEE_CAPTURE_ROOT: process8.cwd(),
5418
5219
  CODEX_BEE_RUN_ID: runId
5220
+ },
5221
+ onOutputActivity: ({ timestamp }) => {
5222
+ runtimeHost?.recordPtyOutputActivity(timestamp);
5419
5223
  }
5420
5224
  });
5421
5225
  const continuation = new ContinuationController({
5422
- cwd: process8.cwd(),
5423
- initial: initialContinuation ?? void 0
5226
+ cwd: process8.cwd()
5424
5227
  });
5425
- const runtimeHost = new LiveUiRuntimeHost({
5228
+ runtimeHost = new LiveUiRuntimeHost({
5426
5229
  beeVersion: CLI_VERSION,
5427
5230
  codexCommand: wrappedCommand,
5428
5231
  continuation,
5429
5232
  cwd: process8.cwd(),
5430
5233
  initialAnnouncementMessage: startupAnnouncement,
5431
5234
  initialConnectionState,
5432
- initialVerificationCommand: options.verificationCommand,
5433
5235
  promptProxy: proxy
5434
5236
  });
5435
5237
  const controller = new UiController(runtimeHost);
@@ -5454,17 +5256,6 @@ async function main() {
5454
5256
  });
5455
5257
  await proxy.start();
5456
5258
  proxy.setInputInterceptor((chunk) => overlay.handleInput(chunk));
5457
- if (initialContinuation) {
5458
- if (initialContinuation.maxContinues > 1) {
5459
- console.error(
5460
- `[codex-bee] Auto-continue loop armed for up to ${initialContinuation.maxContinues} injections with ${continuation.getSnapshot().promptSourceLabel}.`
5461
- );
5462
- } else {
5463
- console.error(
5464
- `[codex-bee] One-shot continuation armed with ${continuation.getSnapshot().promptSourceLabel}.`
5465
- );
5466
- }
5467
- }
5468
5259
  if (initialConnectionState === "ready") {
5469
5260
  void runHookEventWatcher({
5470
5261
  cwd: process8.cwd(),
@@ -5495,7 +5286,7 @@ async function main() {
5495
5286
  continuation,
5496
5287
  cwd: process8.cwd(),
5497
5288
  getVerificationCommand: () => runtimeHost.getVerificationCommand(),
5498
- injectDelayMs: options.injectDelayMs,
5289
+ injectDelayMs: 0,
5499
5290
  logger,
5500
5291
  onPromptInjected: (event) => {
5501
5292
  runtimeHost.recordContinuationPromptInjected(event);
@@ -5508,7 +5299,7 @@ async function main() {
5508
5299
  runStartedAt,
5509
5300
  runVerificationCommand,
5510
5301
  signal: watcherAbortController.signal,
5511
- verificationCommand: options.verificationCommand
5302
+ verificationCommand: null
5512
5303
  }).catch((error) => {
5513
5304
  const message = error instanceof Error ? error.message : String(error);
5514
5305
  logger.log("continuation_watcher_stopped", {