@usulpro/codex-bee 0.1.1 → 0.1.2

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,6 +2911,24 @@ function formatHookStatus(snapshot) {
3141
2911
  }
3142
2912
  return `hook: ${snapshot.session.hookStatus.ok ? "\u2713" : "\u2717"} ${snapshot.session.hookStatus.code}`;
3143
2913
  }
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
+ }
3144
2932
  function formatSessionInfo(snapshot, now = Date.now()) {
3145
2933
  const parts = [];
3146
2934
  if (snapshot.session.currentTurn !== null && snapshot.session.totalTurns !== null) {
@@ -3390,13 +3178,15 @@ function renderLine(line, width) {
3390
3178
  import { jsx as jsx2 } from "react/jsx-runtime";
3391
3179
  var MAX_CONTENT_WIDTH = 200;
3392
3180
  var MIN_CONTENT_WIDTH = 80;
3181
+ var LOADER_TICK_MS = 160;
3393
3182
  var PROMPT_BAR = "\u2503";
3394
3183
  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"]
3184
+ var LOADER_GLYPHS = ["\u25A0", "\u25A0", "\u25A0", "\u25A0"];
3185
+ var LOADER_COLOR_FRAMES = [
3186
+ ["#4F3500", "#8A5B00", "#D89200", "#FFD35A"],
3187
+ ["#8A5B00", "#D89200", "#FFD35A", "#D89200"],
3188
+ ["#D89200", "#FFD35A", "#D89200", "#8A5B00"],
3189
+ ["#FFD35A", "#D89200", "#8A5B00", "#4F3500"]
3400
3190
  ];
3401
3191
  function createPalette(stylesEnabled) {
3402
3192
  if (!stylesEnabled) {
@@ -3757,22 +3547,20 @@ function buildBodyLines(state, title, width, palette) {
3757
3547
  ...buildLogLines(state, width, palette)
3758
3548
  ];
3759
3549
  }
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,
3550
+ function buildLoaderSegments(palette, animationTick, isActive) {
3551
+ const frame = LOADER_COLOR_FRAMES[animationTick % LOADER_COLOR_FRAMES.length] ?? LOADER_COLOR_FRAMES[0];
3552
+ return LOADER_GLYPHS.map((glyph, index) => ({
3553
+ color: isActive ? frame[index] ?? palette.primary : ["#A87400", "#BB8500", "#CF9600", "#E0A700"][index] ?? palette.primary,
3554
+ dimColor: !isActive,
3765
3555
  text: glyph
3766
3556
  }));
3767
3557
  }
3768
3558
  function measureSegmentsWidth(segments) {
3769
3559
  return segments.reduce((total, segment) => total + measureText(segment.text), 0);
3770
3560
  }
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
- }));
3561
+ function buildStatusTokens(state, palette, animationTick, now, width) {
3562
+ const displayedRunState = getDisplayedRunState(state.runtime, now);
3563
+ const loaderToken = buildLoaderSegments(palette, animationTick, displayedRunState === "running");
3776
3564
  const connectionSpec = getConnectionTokenSpec(state.runtime.connectionState, palette);
3777
3565
  const connectionToken = [
3778
3566
  {
@@ -3802,8 +3590,8 @@ function buildStatusTokens(state, palette, animationTick, width) {
3802
3590
  ];
3803
3591
  const runStateToken = [
3804
3592
  {
3805
- color: state.runtime.runState === "running" ? palette.primary : palette.muted,
3806
- text: state.runtime.runState === "running" ? "Running" : "Idle"
3593
+ color: displayedRunState === "running" ? palette.primary : displayedRunState === "quiet" ? palette.warning : palette.muted,
3594
+ text: displayedRunState === "running" ? "Running" : displayedRunState === "quiet" ? "Quiet" : "Idle"
3807
3595
  }
3808
3596
  ];
3809
3597
  const hotkeyToken = [
@@ -3851,9 +3639,9 @@ function buildStatusTokens(state, palette, animationTick, width) {
3851
3639
  }
3852
3640
  return variants[variants.length - 1] ?? [];
3853
3641
  }
3854
- function buildStatusBarLine(state, palette, animationTick, width) {
3642
+ function buildStatusBarLine(state, palette, animationTick, now, width) {
3855
3643
  const backgroundColor = palette.statusBottomBackground;
3856
- const tokens = buildStatusTokens(state, palette, animationTick, width);
3644
+ const tokens = buildStatusTokens(state, palette, animationTick, now, width);
3857
3645
  const segments = tokens.flatMap((token, index) => index === 0 ? token : [
3858
3646
  {
3859
3647
  text: " \u2022 "
@@ -4048,7 +3836,7 @@ function buildPromptFooterLines(state, width, palette, cursorVisible, animationT
4048
3836
  color: palette.promptBorder ?? palette.muted
4049
3837
  }));
4050
3838
  lines.push(createSpacerLine("footer-gap", palette.footerBase));
4051
- lines.push(buildStatusBarLine(state, palette, animationTick, width));
3839
+ lines.push(buildStatusBarLine(state, palette, animationTick, now, width));
4052
3840
  lines.push(createSpacerLine("workspace-gap", palette.footerBase));
4053
3841
  lines.push(createTextLine("workspace", formatWorkspaceLine(state.runtime.workspace), {
4054
3842
  backgroundColor: palette.footerMutedBackground,
@@ -4115,7 +3903,7 @@ function InkControlApp({
4115
3903
  const [isPinnedToBottom, setIsPinnedToBottom] = useState(true);
4116
3904
  const [scrollOffset, setScrollOffset] = useState(0);
4117
3905
  const [clockNow, setClockNow] = useState(() => Date.now());
4118
- const animationTick = 0;
3906
+ const animationTick = Math.floor(clockNow / LOADER_TICK_MS);
4119
3907
  const cursorVisible = true;
4120
3908
  const [viewport] = useState(() => ({
4121
3909
  columns: stdout.columns ?? 100,
@@ -4141,17 +3929,14 @@ function InkControlApp({
4141
3929
  });
4142
3930
  }, [allowQuit, controller, exit, state.exitRequested]);
4143
3931
  useEffect(() => {
4144
- if (state.runtime.session.stopAt === null) {
4145
- return;
4146
- }
4147
3932
  setClockNow(Date.now());
4148
3933
  const intervalId = setInterval(() => {
4149
3934
  setClockNow(Date.now());
4150
- }, 1e3);
3935
+ }, LOADER_TICK_MS);
4151
3936
  return () => {
4152
3937
  clearInterval(intervalId);
4153
3938
  };
4154
- }, [state.runtime.session.stopAt]);
3939
+ }, []);
4155
3940
  const palette = createPalette(stdout.isTTY !== false && !process.env.NO_COLOR && process.env.TERM !== "dumb");
4156
3941
  const contentWidth = Math.min(Math.max(viewport.columns, MIN_CONTENT_WIDTH), MAX_CONTENT_WIDTH);
4157
3942
  const renderWidth = viewport.columns < MIN_CONTENT_WIDTH ? viewport.columns : contentWidth;
@@ -4455,6 +4240,7 @@ import { execFile } from "child_process";
4455
4240
  import process7 from "process";
4456
4241
  import { promisify } from "util";
4457
4242
  var execFileAsync = promisify(execFile);
4243
+ var OUTPUT_ACTIVITY_NOTIFY_INTERVAL_MS = 250;
4458
4244
  var MAX_EVENT_LINES = 8;
4459
4245
  function cloneLogEntry(entry) {
4460
4246
  return {
@@ -4533,6 +4319,7 @@ function createInitialSnapshot(options) {
4533
4319
  session: {
4534
4320
  currentTurn: 0,
4535
4321
  hookStatus: null,
4322
+ lastCodexOutputAt: null,
4536
4323
  stopAt: getStopAt(continuationSnapshot),
4537
4324
  stopScript: options.initialVerificationCommand ?? null,
4538
4325
  totalTurns: toUiTotalTurns(continuationSnapshot.maxContinues)
@@ -4549,6 +4336,13 @@ function getNextSleepTimestamp(hours, minutes, now = Date.now()) {
4549
4336
  }
4550
4337
  return nextTarget.getTime();
4551
4338
  }
4339
+ function formatTimeLeftSuffix(stopAt) {
4340
+ const label = formatTimeLeft(stopAt);
4341
+ if (!label) {
4342
+ return "";
4343
+ }
4344
+ return ` (${label.replace(/^time left:\s*/u, "")} left)`;
4345
+ }
4552
4346
  function createLogEntry(id, kind, text, timestamp) {
4553
4347
  return {
4554
4348
  id,
@@ -4664,6 +4458,7 @@ var LiveUiRuntimeHost = class {
4664
4458
  #listeners = /* @__PURE__ */ new Set();
4665
4459
  #recentEvents = [];
4666
4460
  #entryCounter = 0;
4461
+ #lastOutputActivityNotificationAt = 0;
4667
4462
  #pendingPrompts = [];
4668
4463
  #snapshot;
4669
4464
  #verificationCommand;
@@ -4695,6 +4490,20 @@ var LiveUiRuntimeHost = class {
4695
4490
  this.#pushEventLine("Codex session disconnected.");
4696
4491
  this.#notify();
4697
4492
  }
4493
+ recordPtyOutputActivity(timestamp = Date.now()) {
4494
+ this.#snapshot = {
4495
+ ...this.#snapshot,
4496
+ session: {
4497
+ ...this.#snapshot.session,
4498
+ lastCodexOutputAt: timestamp
4499
+ }
4500
+ };
4501
+ if (timestamp - this.#lastOutputActivityNotificationAt < OUTPUT_ACTIVITY_NOTIFY_INTERVAL_MS) {
4502
+ return;
4503
+ }
4504
+ this.#lastOutputActivityNotificationAt = timestamp;
4505
+ this.#notify();
4506
+ }
4698
4507
  recordAuxiliaryHookEvent(capture) {
4699
4508
  const eventName = capture.payload?.hook_event_name ?? null;
4700
4509
  if (eventName === "SessionStart") {
@@ -4719,9 +4528,11 @@ var LiveUiRuntimeHost = class {
4719
4528
  runState: "running",
4720
4529
  session: {
4721
4530
  ...this.#snapshot.session,
4722
- currentTurn: this.#snapshot.logs.length
4531
+ currentTurn: this.#snapshot.logs.length,
4532
+ lastCodexOutputAt: submittedAt
4723
4533
  }
4724
4534
  };
4535
+ this.#lastOutputActivityNotificationAt = submittedAt;
4725
4536
  this.#pushEventLine(
4726
4537
  promptText ? `UserPromptSubmit received. Codex turn is running: ${formatCodexEcho(promptText)}` : "UserPromptSubmit received. Codex turn is running."
4727
4538
  );
@@ -4763,9 +4574,11 @@ var LiveUiRuntimeHost = class {
4763
4574
  maxDurationMs: command.minutes * 60 * 1e3,
4764
4575
  prompt: currentContinuation.promptText
4765
4576
  });
4577
+ const stopAt = getStopAt(updatedSnapshot);
4578
+ const timeLeftSuffix = formatTimeLeftSuffix(stopAt);
4766
4579
  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).`);
4580
+ this.#pushEventLine(`Updated the session duration to ${command.minutes} minute(s)${timeLeftSuffix}.`);
4581
+ return this.#createUpdate(`Set the live session duration to ${command.minutes} minute(s)${timeLeftSuffix}.`);
4769
4582
  }
4770
4583
  case "quit":
4771
4584
  return this.#createUpdate("Exiting the current codex-bee session.", {
@@ -4780,9 +4593,10 @@ var LiveUiRuntimeHost = class {
4780
4593
  maxDurationMs: Math.max(stopAt - Date.now(), 1e3),
4781
4594
  prompt: currentContinuation.promptText
4782
4595
  });
4596
+ const timeLeftSuffix = formatTimeLeftSuffix(getStopAt(updatedSnapshot));
4783
4597
  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}.`);
4598
+ this.#pushEventLine(`Set the absolute stop time to ${command.time}${timeLeftSuffix}.`);
4599
+ return this.#createUpdate(`Set the live stop time to ${command.time}${timeLeftSuffix}.`);
4786
4600
  }
4787
4601
  case "steps-count": {
4788
4602
  const currentContinuation = this.#continuation.getSnapshot();
@@ -4924,6 +4738,7 @@ var LiveUiRuntimeHost = class {
4924
4738
  ...this.#snapshot.session,
4925
4739
  currentTurn: nextLogs.length,
4926
4740
  hookStatus: hookSummary.hookStatus,
4741
+ lastCodexOutputAt: completedAt,
4927
4742
  stopAt: getStopAt(continuationSnapshot),
4928
4743
  stopScript: this.#verificationCommand,
4929
4744
  totalTurns: toUiTotalTurns(continuationSnapshot.maxContinues)
@@ -5346,9 +5161,8 @@ async function main() {
5346
5161
  }
5347
5162
  const runId = randomUUID();
5348
5163
  const runStartedAt = Date.now();
5349
- const hasContinuation = Boolean(options.continueLoopSource || options.continueOnceSource);
5350
5164
  const executionMode = resolveExecutionMode({
5351
- hasContinuation,
5165
+ hasContinuation: false,
5352
5166
  stdinIsTTY: process8.stdin.isTTY,
5353
5167
  stdoutIsTTY: process8.stdout.isTTY
5354
5168
  });
@@ -5387,28 +5201,7 @@ async function main() {
5387
5201
  configPath: hookSetup.activeConfig?.path ?? hookSetup.globalPath
5388
5202
  });
5389
5203
  }
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
- }
5204
+ let runtimeHost = null;
5412
5205
  const proxy = new PtyProxy({
5413
5206
  command: wrappedCommand,
5414
5207
  commandArgs: options.commandArgs,
@@ -5416,20 +5209,21 @@ async function main() {
5416
5209
  ...process8.env,
5417
5210
  CODEX_BEE_CAPTURE_ROOT: process8.cwd(),
5418
5211
  CODEX_BEE_RUN_ID: runId
5212
+ },
5213
+ onOutputActivity: ({ timestamp }) => {
5214
+ runtimeHost?.recordPtyOutputActivity(timestamp);
5419
5215
  }
5420
5216
  });
5421
5217
  const continuation = new ContinuationController({
5422
- cwd: process8.cwd(),
5423
- initial: initialContinuation ?? void 0
5218
+ cwd: process8.cwd()
5424
5219
  });
5425
- const runtimeHost = new LiveUiRuntimeHost({
5220
+ runtimeHost = new LiveUiRuntimeHost({
5426
5221
  beeVersion: CLI_VERSION,
5427
5222
  codexCommand: wrappedCommand,
5428
5223
  continuation,
5429
5224
  cwd: process8.cwd(),
5430
5225
  initialAnnouncementMessage: startupAnnouncement,
5431
5226
  initialConnectionState,
5432
- initialVerificationCommand: options.verificationCommand,
5433
5227
  promptProxy: proxy
5434
5228
  });
5435
5229
  const controller = new UiController(runtimeHost);
@@ -5454,17 +5248,6 @@ async function main() {
5454
5248
  });
5455
5249
  await proxy.start();
5456
5250
  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
5251
  if (initialConnectionState === "ready") {
5469
5252
  void runHookEventWatcher({
5470
5253
  cwd: process8.cwd(),
@@ -5495,7 +5278,7 @@ async function main() {
5495
5278
  continuation,
5496
5279
  cwd: process8.cwd(),
5497
5280
  getVerificationCommand: () => runtimeHost.getVerificationCommand(),
5498
- injectDelayMs: options.injectDelayMs,
5281
+ injectDelayMs: 0,
5499
5282
  logger,
5500
5283
  onPromptInjected: (event) => {
5501
5284
  runtimeHost.recordContinuationPromptInjected(event);
@@ -5508,7 +5291,7 @@ async function main() {
5508
5291
  runStartedAt,
5509
5292
  runVerificationCommand,
5510
5293
  signal: watcherAbortController.signal,
5511
- verificationCommand: options.verificationCommand
5294
+ verificationCommand: null
5512
5295
  }).catch((error) => {
5513
5296
  const message = error instanceof Error ? error.message : String(error);
5514
5297
  logger.log("continuation_watcher_stopped", {