@promptbook/cli 0.112.0-117 → 0.112.0-118

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.
Files changed (41) hide show
  1. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatSidebarDefault.tsx +5 -6
  2. package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +17 -7
  3. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +17 -7
  4. package/apps/agents-server/src/utils/userChat/createImmediateUserChatAnswerModelRequirements.ts +11 -0
  5. package/apps/agents-server/src/utils/userChat/listUserChats.ts +5 -7
  6. package/esm/index.es.js +417 -64
  7. package/esm/index.es.js.map +1 -1
  8. package/esm/scripts/run-codex-prompts/common/parseDuration.d.ts +19 -0
  9. package/esm/src/_packages/node.index.d.ts +10 -0
  10. package/esm/src/book-3.0/BookNodeAgentSource.d.ts +1 -1
  11. package/esm/src/book-3.0/CliAgent.d.ts +7 -2
  12. package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
  13. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +2 -18
  14. package/esm/src/version.d.ts +1 -1
  15. package/package.json +1 -1
  16. package/src/_packages/node.index.ts +10 -0
  17. package/src/avatars/avatarAnimationScheduler.ts +33 -2
  18. package/src/avatars/visuals/fractalAvatarVisual.ts +5 -4
  19. package/src/avatars/visuals/minecraft2AvatarVisual.ts +16 -11
  20. package/src/avatars/visuals/minecraftAvatarVisual.ts +21 -7
  21. package/src/avatars/visuals/octopus3d2AvatarVisual.ts +69 -17
  22. package/src/avatars/visuals/octopus3d3AvatarVisual.ts +81 -18
  23. package/src/avatars/visuals/octopus3dAvatarVisual.ts +69 -17
  24. package/src/book-3.0/Book.ts +3 -1
  25. package/src/book-3.0/BookNodeAgentSource.ts +2 -2
  26. package/src/book-3.0/CliAgent.ts +84 -6
  27. package/src/book-3.0/cliAgentEnv.ts +46 -0
  28. package/src/cli/cli-commands/coder/run.ts +28 -3
  29. package/src/cli/cli-commands/common/promptRunnerCliOptions.ts +9 -29
  30. package/src/other/templates/getTemplatesPipelineCollection.ts +713 -735
  31. package/src/version.ts +2 -2
  32. package/src/versions.txt +1 -0
  33. package/umd/index.umd.js +417 -64
  34. package/umd/index.umd.js.map +1 -1
  35. package/umd/scripts/run-codex-prompts/common/parseDuration.d.ts +19 -0
  36. package/umd/src/_packages/node.index.d.ts +10 -0
  37. package/umd/src/book-3.0/BookNodeAgentSource.d.ts +1 -1
  38. package/umd/src/book-3.0/CliAgent.d.ts +7 -2
  39. package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
  40. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +2 -18
  41. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -58,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-117';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-118';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1740,12 +1740,13 @@ function parseThinkingLevel(thinkingLevelValue) {
1740
1740
  }
1741
1741
  // Note: [🟡] Code for CLI command [run](src/cli/cli-commands/coder/run.ts) should never be published outside of `@promptbook/cli`
1742
1742
 
1743
+ // Note: [💞] Ignore a discrepancy between file name and entity name
1743
1744
  /**
1744
- * Runner identifiers supported by Promptbook CLI agent orchestration commands.
1745
+ * All CLI harness names supported by `CliAgent` and `ptbk agent exec`.
1745
1746
  *
1746
- * @private internal utility of `promptbookCli`
1747
+ * @public exported from `@promptbook/node`
1747
1748
  */
1748
- const PROMPT_RUNNER_HARNESS_NAMES = [
1749
+ const CLI_AGENT_HARNESS_NAMES = [
1749
1750
  'openai-codex',
1750
1751
  'github-copilot',
1751
1752
  'cline',
@@ -1754,23 +1755,33 @@ const PROMPT_RUNNER_HARNESS_NAMES = [
1754
1755
  'gemini',
1755
1756
  ];
1756
1757
  /**
1757
- * Environment variable used as the default runner identifier when `--harness` is omitted.
1758
+ * Environment variable used as the default runner identifier when `--harness` is omitted or not set in `CliAgent`.
1758
1759
  *
1759
- * @private internal utility of `promptbookCli`
1760
+ * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`)
1761
+ * so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
1762
+ *
1763
+ * @public exported from `@promptbook/node`
1760
1764
  */
1761
1765
  const PTBK_HARNESS_ENV = 'PTBK_HARNESS';
1762
1766
  /**
1763
- * Environment variable used as the default runner model when `--model` is omitted.
1767
+ * Environment variable used as the default runner model when `--model` is omitted or not set in `CliAgent`.
1764
1768
  *
1765
- * @private internal utility of `promptbookCli`
1769
+ * @public exported from `@promptbook/node`
1766
1770
  */
1767
1771
  const PTBK_MODEL_ENV = 'PTBK_MODEL';
1768
1772
  /**
1769
- * Environment variable used as the default runner thinking level when `--thinking-level` is omitted.
1773
+ * Environment variable used as the default thinking level when `--thinking-level` is omitted or not set in `CliAgent`.
1770
1774
  *
1771
- * @private internal utility of `promptbookCli`
1775
+ * @public exported from `@promptbook/node`
1772
1776
  */
1773
1777
  const PTBK_THINKING_LEVEL_ENV = 'PTBK_THINKING_LEVEL';
1778
+
1779
+ /**
1780
+ * Runner identifiers supported by Promptbook CLI agent orchestration commands.
1781
+ *
1782
+ * @private internal utility of `promptbookCli`
1783
+ */
1784
+ const PROMPT_RUNNER_HARNESS_NAMES = CLI_AGENT_HARNESS_NAMES;
1774
1785
  /**
1775
1786
  * Description block shared by runner-backed CLI commands.
1776
1787
  *
@@ -5124,6 +5135,7 @@ function createCoderRunOptionsForAgent(options) {
5124
5135
  noUi: options.noUi,
5125
5136
  thinkingLevel: options.thinkingLevel,
5126
5137
  waitForUser: false,
5138
+ waitBetweenPrompts: 0,
5127
5139
  noCommit: options.noCommit,
5128
5140
  ignoreGitChanges: options.ignoreGitChanges,
5129
5141
  normalizeLineEndings: options.normalizeLineEndings,
@@ -10816,21 +10828,22 @@ function getPointBounds(points) {
10816
10828
  * @private helper of `fractalAvatarVisual`
10817
10829
  */
10818
10830
  function drawDragonCurveLayer(context, points, options) {
10819
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
10831
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
10820
10832
  const firstPoint = points[0];
10821
10833
  const lastPoint = points[points.length - 1];
10822
10834
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
10823
10835
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
10824
10836
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
10825
10837
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
10838
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
10839
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
10826
10840
  context.save();
10827
10841
  context.beginPath();
10828
10842
  tracePolyline(context, points);
10829
- context.strokeStyle = `${shadowColor}82`;
10830
- context.lineWidth = strokeWidth * 1.8;
10843
+ context.strokeStyle = `${shadowColor}48`;
10844
+ context.lineWidth = strokeWidth * 4.5;
10831
10845
  context.lineJoin = 'round';
10832
10846
  context.lineCap = 'round';
10833
- context.filter = `blur(${size * 0.022}px)`;
10834
10847
  context.stroke();
10835
10848
  context.restore();
10836
10849
  context.beginPath();
@@ -11414,11 +11427,23 @@ function drawMinecraftBackdrop(context, size, palette, sceneCenterX, spotlightY,
11414
11427
  * @private helper of `minecraft2AvatarVisual`
11415
11428
  */
11416
11429
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
11430
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
11431
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
11432
+ const rx = size * (0.16 + interaction.intensity * 0.015);
11433
+ const ry = size * 0.055;
11434
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
11417
11435
  context.save();
11418
- context.fillStyle = `${palette.shadow}66`;
11419
- context.filter = `blur(${size * 0.02}px)`;
11436
+ context.translate(cx, cy);
11437
+ context.scale(1, ry / rx);
11438
+ const blurRadius = rx * 1.4;
11439
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
11440
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
11441
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
11442
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
11443
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
11444
+ context.fillStyle = shadowGradient;
11420
11445
  context.beginPath();
11421
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.03, size * 0.85 + Math.sin(timeMs / 880) * size * 0.01, size * (0.16 + interaction.intensity * 0.015), size * 0.055, 0, 0, Math.PI * 2);
11446
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
11422
11447
  context.fill();
11423
11448
  context.restore();
11424
11449
  }
@@ -11642,13 +11667,27 @@ const minecraftAvatarVisual = {
11642
11667
  spotlight.addColorStop(1, `${palette.highlight}00`);
11643
11668
  context.fillStyle = spotlight;
11644
11669
  context.fillRect(0, 0, size, size);
11645
- context.save();
11646
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
11647
- context.filter = `blur(${size * 0.018}px)`;
11648
- context.beginPath();
11649
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
11650
- context.fill();
11651
- context.restore();
11670
+ {
11671
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
11672
+ const cx = size * 0.5;
11673
+ const cy = size * 0.86;
11674
+ const rx = size * 0.2;
11675
+ const ry = size * 0.06;
11676
+ const blurRadius = rx * 1.4;
11677
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
11678
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
11679
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
11680
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
11681
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
11682
+ context.save();
11683
+ context.translate(cx, cy);
11684
+ context.scale(1, ry / rx);
11685
+ context.fillStyle = shadowGradient;
11686
+ context.beginPath();
11687
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
11688
+ context.fill();
11689
+ context.restore();
11690
+ }
11652
11691
  drawVoxelCuboid(context, {
11653
11692
  x: bodyX,
11654
11693
  y: bodyY,
@@ -12700,6 +12739,35 @@ const LIGHT_DIRECTION$2 = normalizeVector3({
12700
12739
  y: -0.62,
12701
12740
  z: 0.94,
12702
12741
  });
12742
+ /**
12743
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
12744
+ *
12745
+ * @private helper of `octopus3dAvatarVisual`
12746
+ */
12747
+ const octopus3dStableStateCache = new WeakMap();
12748
+ /**
12749
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
12750
+ *
12751
+ * @private helper of `octopus3dAvatarVisual`
12752
+ */
12753
+ function getOctopus3dStableState(createRandom) {
12754
+ const cached = octopus3dStableStateCache.get(createRandom);
12755
+ if (cached !== undefined) {
12756
+ return cached;
12757
+ }
12758
+ const animationRandom = createRandom('octopus3d-animation-profile');
12759
+ const eyeRandom = createRandom('octopus3d-eye-profile');
12760
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
12761
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
12762
+ const state = {
12763
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
12764
+ animationPhase: animationRandom() * Math.PI * 2,
12765
+ leftEyePhaseOffset,
12766
+ rightEyePhaseOffset,
12767
+ };
12768
+ octopus3dStableStateCache.set(createRandom, state);
12769
+ return state;
12770
+ }
12703
12771
  /**
12704
12772
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
12705
12773
  *
@@ -12712,10 +12780,7 @@ const octopus3dAvatarVisual = {
12712
12780
  isAnimated: true,
12713
12781
  supportsPointerTracking: true,
12714
12782
  render({ context, size, palette, createRandom, timeMs, interaction }) {
12715
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
12716
- const animationRandom = createRandom('octopus3d-animation-profile');
12717
- const eyeRandom = createRandom('octopus3d-eye-profile');
12718
- const animationPhase = animationRandom() * Math.PI * 2;
12783
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
12719
12784
  const sceneCenterX = size * 0.5;
12720
12785
  const sceneCenterY = size * 0.56;
12721
12786
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -12812,12 +12877,12 @@ const octopus3dAvatarVisual = {
12812
12877
  x: -faceEyeSpacing,
12813
12878
  y: faceEyeYOffset,
12814
12879
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
12815
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
12880
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
12816
12881
  drawProjectedOrganicEye(context, {
12817
12882
  x: faceEyeSpacing,
12818
12883
  y: faceEyeYOffset,
12819
12884
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
12820
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
12885
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
12821
12886
  drawProjectedOrganicMouth(context, [
12822
12887
  {
12823
12888
  x: -mouthHalfWidth,
@@ -12861,14 +12926,28 @@ function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCent
12861
12926
  /**
12862
12927
  * Draws the soft ground shadow below the octopus.
12863
12928
  *
12929
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
12930
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
12931
+ *
12864
12932
  * @private helper of `octopus3dAvatarVisual`
12865
12933
  */
12866
12934
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
12935
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
12936
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
12937
+ const rx = size * (0.18 + interaction.intensity * 0.02);
12938
+ const ry = size * 0.06;
12867
12939
  context.save();
12868
- context.fillStyle = `${palette.shadow}66`;
12869
- context.filter = `blur(${size * 0.022}px)`;
12940
+ context.translate(cx, cy);
12941
+ context.scale(1, ry / rx);
12942
+ const blurRadius = rx * 1.4;
12943
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
12944
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
12945
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
12946
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
12947
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
12948
+ context.fillStyle = shadowGradient;
12870
12949
  context.beginPath();
12871
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.04, size * 0.87 + Math.sin(timeMs / 920) * size * 0.008, size * (0.18 + interaction.intensity * 0.02), size * 0.06, 0, 0, Math.PI * 2);
12950
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
12872
12951
  context.fill();
12873
12952
  context.restore();
12874
12953
  }
@@ -13099,6 +13178,35 @@ const LIGHT_DIRECTION$1 = normalizeVector3({
13099
13178
  y: -0.6,
13100
13179
  z: 0.98,
13101
13180
  });
13181
+ /**
13182
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
13183
+ *
13184
+ * @private helper of `octopus3d2AvatarVisual`
13185
+ */
13186
+ const octopus3d2StableStateCache = new WeakMap();
13187
+ /**
13188
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
13189
+ *
13190
+ * @private helper of `octopus3d2AvatarVisual`
13191
+ */
13192
+ function getOctopus3d2StableState(createRandom) {
13193
+ const cached = octopus3d2StableStateCache.get(createRandom);
13194
+ if (cached !== undefined) {
13195
+ return cached;
13196
+ }
13197
+ const animationRandom = createRandom('octopus3d2-animation-profile');
13198
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
13199
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
13200
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
13201
+ const state = {
13202
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
13203
+ animationPhase: animationRandom() * Math.PI * 2,
13204
+ leftEyePhaseOffset,
13205
+ rightEyePhaseOffset,
13206
+ };
13207
+ octopus3d2StableStateCache.set(createRandom, state);
13208
+ return state;
13209
+ }
13102
13210
  /**
13103
13211
  * Octopus 3D 2 avatar visual.
13104
13212
  *
@@ -13111,10 +13219,7 @@ const octopus3d2AvatarVisual = {
13111
13219
  isAnimated: true,
13112
13220
  supportsPointerTracking: true,
13113
13221
  render({ context, size, palette, createRandom, timeMs, interaction }) {
13114
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
13115
- const animationRandom = createRandom('octopus3d2-animation-profile');
13116
- const eyeRandom = createRandom('octopus3d2-eye-profile');
13117
- const animationPhase = animationRandom() * Math.PI * 2;
13222
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
13118
13223
  const sceneCenterX = size * 0.5;
13119
13224
  const sceneCenterY = size * 0.575;
13120
13225
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -13167,8 +13272,8 @@ const octopus3d2AvatarVisual = {
13167
13272
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
13168
13273
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
13169
13274
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
13170
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
13171
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
13275
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13276
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13172
13277
  drawProjectedOrganicMouth(context, [
13173
13278
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
13174
13279
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -13197,14 +13302,28 @@ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, scene
13197
13302
  /**
13198
13303
  * Draws the soft floor shadow that anchors the single mesh in the frame.
13199
13304
  *
13305
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
13306
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
13307
+ *
13200
13308
  * @private helper of `octopus3d2AvatarVisual`
13201
13309
  */
13202
13310
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
13311
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
13312
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
13313
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
13314
+ const ry = size * 0.062;
13203
13315
  context.save();
13204
- context.fillStyle = `${palette.shadow}66`;
13205
- context.filter = `blur(${size * 0.024}px)`;
13316
+ context.translate(cx, cy);
13317
+ context.scale(1, ry / rx);
13318
+ const blurRadius = rx * 1.4;
13319
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
13320
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
13321
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
13322
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
13323
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
13324
+ context.fillStyle = shadowGradient;
13206
13325
  context.beginPath();
13207
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.88 + Math.sin(timeMs / 940) * size * 0.008, size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018), size * 0.062, 0, 0, Math.PI * 2);
13326
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
13208
13327
  context.fill();
13209
13328
  context.restore();
13210
13329
  }
@@ -13360,6 +13479,40 @@ const LIGHT_DIRECTION = normalizeVector3({
13360
13479
  * @private helper of `octopus3d3AvatarVisual`
13361
13480
  */
13362
13481
  const OCTOPUS_TENTACLE_COUNT = 8;
13482
+ /**
13483
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
13484
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
13485
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
13486
+ *
13487
+ * @private helper of `octopus3d3AvatarVisual`
13488
+ */
13489
+ const stableStateCache = new WeakMap();
13490
+ /**
13491
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
13492
+ * result on every subsequent call within the same `<Avatar/>` mount.
13493
+ *
13494
+ * @private helper of `octopus3d3AvatarVisual`
13495
+ */
13496
+ function getOctopus3d3StableState(createRandom) {
13497
+ const cached = stableStateCache.get(createRandom);
13498
+ if (cached !== undefined) {
13499
+ return cached;
13500
+ }
13501
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
13502
+ const animationRandom = createRandom('octopus3d3-animation-profile');
13503
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
13504
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
13505
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
13506
+ const state = {
13507
+ morphologyProfile,
13508
+ animationPhase: animationRandom() * Math.PI * 2,
13509
+ leftEyePhaseOffset,
13510
+ rightEyePhaseOffset,
13511
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
13512
+ };
13513
+ stableStateCache.set(createRandom, state);
13514
+ return state;
13515
+ }
13363
13516
  /**
13364
13517
  * Octopus 3D 3 avatar visual.
13365
13518
  *
@@ -13372,11 +13525,7 @@ const octopus3d3AvatarVisual = {
13372
13525
  isAnimated: true,
13373
13526
  supportsPointerTracking: true,
13374
13527
  render({ context, size, palette, createRandom, timeMs, interaction }) {
13375
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
13376
- const animationRandom = createRandom('octopus3d3-animation-profile');
13377
- const eyeRandom = createRandom('octopus3d3-eye-profile');
13378
- const animationPhase = animationRandom() * Math.PI * 2;
13379
- const tentacleProfiles = createContinuousTentacleProfiles(createRandom, morphologyProfile);
13528
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
13380
13529
  const sceneCenterX = size * 0.5;
13381
13530
  const sceneCenterY = size * 0.535;
13382
13531
  const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
@@ -13453,8 +13602,8 @@ const octopus3d3AvatarVisual = {
13453
13602
  size,
13454
13603
  palette,
13455
13604
  });
13456
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
13457
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
13605
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13606
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13458
13607
  drawProjectedOrganicMouth(context, [
13459
13608
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
13460
13609
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -13505,14 +13654,30 @@ function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, s
13505
13654
  /**
13506
13655
  * Draws the soft lower shadow that anchors the octopus in the avatar frame.
13507
13656
  *
13657
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
13658
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
13659
+ *
13508
13660
  * @private helper of `octopus3d3AvatarVisual`
13509
13661
  */
13510
13662
  function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
13663
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
13664
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
13665
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
13666
+ const ry = size * 0.06;
13667
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
13668
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
13511
13669
  context.save();
13512
- context.fillStyle = `${palette.shadow}66`;
13513
- context.filter = `blur(${size * 0.025}px)`;
13670
+ context.translate(cx, cy);
13671
+ context.scale(1, ry / rx);
13672
+ const blurRadius = rx * 1.4;
13673
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
13674
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
13675
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
13676
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
13677
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
13678
+ context.fillStyle = shadowGradient;
13514
13679
  context.beginPath();
13515
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.9 + Math.sin(timeMs / 980) * size * 0.007, size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02), size * 0.06, 0, 0, Math.PI * 2);
13680
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
13516
13681
  context.fill();
13517
13682
  context.restore();
13518
13683
  }
@@ -28737,14 +28902,50 @@ function buildAgentMessageCommitMessage(messageFile) {
28737
28902
  * Builds the prompt sent to the selected coding runner for one queued user-thread book.
28738
28903
  */
28739
28904
  function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage) {
28740
- const taskPrompt = spaceTrim$1(`
28741
- Answer 1 user question
28905
+ return spaceTrim$1((block) => `
28906
+ # Answer 1 user question
28742
28907
 
28743
- - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE @User\`
28744
- - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
28745
- - Do not modify any other file in the repository
28746
- `);
28747
- return `${taskPrompt}\n\n**This is how you should behave:**\n\n${agentSystemMessage.trim()}`;
28908
+ - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE @User\`
28909
+ - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
28910
+ - Do not modify any other file in the repository
28911
+
28912
+ ## Rules for the answering
28913
+
28914
+ ## Formatting
28915
+
28916
+ - You can use Markdown formatting in the messages like **bold** or *italic*
28917
+
28918
+ ## Sources and citations
28919
+
28920
+ Mark sources and citations like this "【https://example.com/document123.pdf 】"
28921
+
28922
+ At the same time, you can write sources naturally in the text of the answers
28923
+
28924
+ For example:
28925
+
28926
+ > According to paragraph §745b, the fee can be waived for a person over 65 years old. 【https://praha13.cz/2026/paragraph-745.doc】
28927
+
28928
+ - "paragraph §745b" fits naturally in the text.
28929
+ - "【https://praha13.cz/2026/paragraph-745.doc】" The exact format of the quote is important for further processing of the answer.
28930
+ - The "【" and "】" symbols are used to mark the source and will be parsed, inside should be valid URL used as a source for the answer.
28931
+
28932
+ ## Quick buttons
28933
+
28934
+ If there is a meaningful follow-up procedure, use quick buttons at the end of the answer:
28935
+
28936
+ \`\`\`
28937
+ How big is the contract you are posting?
28938
+
28939
+ [Up to 50,000 CZK](?message=We are posting an order up to 50,000 CZK)
28940
+ [Up to 100,000 CZK](?message=We are posting an order up to 100,000 CZK)
28941
+ [Over 100,000 CZK](?message=We are posting an order over 100,000 CZK)
28942
+ \`\`\`
28943
+
28944
+
28945
+ ## This is how you should behave
28946
+
28947
+ ${block(agentSystemMessage)}
28948
+ `);
28748
28949
  }
28749
28950
 
28750
28951
  /**
@@ -35889,7 +36090,9 @@ function parseBookMessageHeader(line) {
35889
36090
  * @private internal utility of `Book`
35890
36091
  */
35891
36092
  function parseCommitmentHeader(line) {
35892
- const match = /^([A-Z][A-Z0-9]*(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
36093
+ // Require at least 2 characters in the first keyword word to avoid treating common
36094
+ // single-letter words (e.g. "V" in Czech, "I" or "A" in English) as commitment headers.
36095
+ const match = /^([A-Z][A-Z0-9]+(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
35893
36096
  if (!match) {
35894
36097
  return null;
35895
36098
  }
@@ -39303,6 +39506,57 @@ function listDefaultCoderProjectPromptTemplateDisplayPaths() {
39303
39506
  // Note: [🟡] Code for CLI command [init](src/cli/cli-commands/coder/init.ts) should never be published outside of `@promptbook/cli`
39304
39507
  // Note: [💞] Ignore a discrepancy between file name and entity name
39305
39508
 
39509
+ /**
39510
+ * Pattern that matches durations like "1h", "30m", "5s", "1h30m", "1h30m5s".
39511
+ */
39512
+ const DURATION_PATTERN = /^(?:(\d+)h)?(?:(\d+)m(?:in)?)?(?:(\d+)s)?$/;
39513
+ /**
39514
+ * Parses a human-readable duration string into milliseconds.
39515
+ *
39516
+ * Supported formats: `Xh`, `Xm`, `Xs`, and combinations like `1h30m`, `1h30m5s`.
39517
+ *
39518
+ * @returns Duration in milliseconds
39519
+ * @throws When the string does not match any supported format
39520
+ *
39521
+ * @private internal utility of `ptbk coder run`
39522
+ */
39523
+ function parseDuration(durationString) {
39524
+ var _a, _b, _c;
39525
+ const trimmed = durationString.trim();
39526
+ if (!trimmed) {
39527
+ throw new Error(`Invalid duration: empty string. Expected a format like "1h", "30m", "5s", or combinations like "1h30m".`);
39528
+ }
39529
+ const match = trimmed.match(DURATION_PATTERN);
39530
+ if (!match || (match[1] === undefined && match[2] === undefined && match[3] === undefined)) {
39531
+ throw new Error(`Invalid duration: "${durationString}". Expected a format like "1h", "30m", "5s", or combinations like "1h30m5s".`);
39532
+ }
39533
+ const hours = parseInt((_a = match[1]) !== null && _a !== void 0 ? _a : '0', 10);
39534
+ const minutes = parseInt((_b = match[2]) !== null && _b !== void 0 ? _b : '0', 10);
39535
+ const seconds = parseInt((_c = match[3]) !== null && _c !== void 0 ? _c : '0', 10);
39536
+ return (hours * 3600 + minutes * 60 + seconds) * 1000;
39537
+ }
39538
+ /**
39539
+ * Formats a duration in milliseconds into a compact human-readable string.
39540
+ *
39541
+ * Examples: `3600000` → `"1h"`, `90000` → `"1m 30s"`, `5000` → `"5s"`.
39542
+ *
39543
+ * @private internal utility of `ptbk coder run`
39544
+ */
39545
+ function formatDurationMs(ms) {
39546
+ const totalSeconds = Math.ceil(ms / 1000);
39547
+ const hours = Math.floor(totalSeconds / 3600);
39548
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
39549
+ const seconds = totalSeconds % 60;
39550
+ const parts = [];
39551
+ if (hours > 0)
39552
+ parts.push(`${hours}h`);
39553
+ if (minutes > 0)
39554
+ parts.push(`${minutes}m`);
39555
+ if (seconds > 0 || parts.length === 0)
39556
+ parts.push(`${seconds}s`);
39557
+ return parts.join(' ');
39558
+ }
39559
+
39306
39560
  /**
39307
39561
  * Initializes `coder run` command for Promptbook CLI utilities
39308
39562
  *
@@ -39335,7 +39589,13 @@ function $initializeCoderRunCommand(program) {
39335
39589
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
39336
39590
  addPromptRunnerExecutionOptions(command);
39337
39591
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
39338
- command.option('--no-wait', 'Skip user prompts between processing');
39592
+ command.option('--wait [duration]', spaceTrim$1(`
39593
+ Wait between prompt rounds.
39594
+ Without a value (default): waits for user confirmation before each prompt (interactive mode).
39595
+ With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
39596
+ `), true);
39597
+ // Note: --no-wait disables the default interactive wait-for-user behaviour
39598
+ command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
39339
39599
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
39340
39600
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
39341
39601
  command.action(handleActionErrors(async (cliOptions) => {
@@ -39344,10 +39604,23 @@ function $initializeCoderRunCommand(program) {
39344
39604
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
39345
39605
  isAgentRequired: !dryRun,
39346
39606
  });
39607
+ // [1] Parse the --wait option:
39608
+ // true (default or --wait without value): wait for user confirmation
39609
+ // false (--no-wait): no waiting at all
39610
+ // string (--wait 1h): wait that long between prompt rounds
39611
+ let waitForUser = false;
39612
+ let waitBetweenPrompts = 0;
39613
+ if (wait === true) {
39614
+ waitForUser = true;
39615
+ }
39616
+ else if (typeof wait === 'string' && wait !== '') {
39617
+ waitBetweenPrompts = parseDuration(wait);
39618
+ }
39347
39619
  // Convert commander options to RunOptions format
39348
39620
  const runOptions = {
39349
39621
  dryRun,
39350
- waitForUser: wait,
39622
+ waitForUser,
39623
+ waitBetweenPrompts,
39351
39624
  noCommit: runnerOptions.noCommit,
39352
39625
  ignoreGitChanges: runnerOptions.ignoreGitChanges,
39353
39626
  agentName: runnerOptions.agentName,
@@ -62790,6 +63063,7 @@ function createPromptRunnerOptions(options) {
62790
63063
  noUi: options.noUi,
62791
63064
  thinkingLevel: options.thinkingLevel,
62792
63065
  waitForUser: false,
63066
+ waitBetweenPrompts: 0,
62793
63067
  noCommit: true,
62794
63068
  ignoreGitChanges: true,
62795
63069
  normalizeLineEndings: false,
@@ -68324,7 +68598,7 @@ var findRefactorCandidates$1 = /*#__PURE__*/Object.freeze({
68324
68598
  /**
68325
68599
  * CLI usage text for this script.
68326
68600
  */
68327
- const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--no-wait] [--no-commit] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
68601
+ const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait [duration]] [--no-wait] [--no-commit] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
68328
68602
  /**
68329
68603
  * Top-level flags supported by this command.
68330
68604
  */
@@ -68341,6 +68615,7 @@ const KNOWN_OPTION_FLAGS = new Set([
68341
68615
  '--allow-credits',
68342
68616
  '--auto-migrate',
68343
68617
  '--allow-destructive-auto-migrate',
68618
+ '--wait',
68344
68619
  '--no-wait',
68345
68620
  '--no-commit',
68346
68621
  '--ignore-git-changes',
@@ -68379,6 +68654,22 @@ function parseRunOptions(args) {
68379
68654
  const allowDestructiveAutoMigrate = args.includes('--allow-destructive-auto-migrate');
68380
68655
  const autoPush = args.includes('--auto-push');
68381
68656
  const autoPull = args.includes('--auto-pull');
68657
+ // [1] Parse --wait [duration]:
68658
+ // absent or --no-wait: no waiting
68659
+ // --wait (no value): wait for user confirmation
68660
+ // --wait 1h: wait 1h between prompt rounds to avoid rate limits
68661
+ const waitOptionalValue = readOptionalOptionValue(args, '--wait');
68662
+ let waitForUser = false;
68663
+ let waitBetweenPrompts = 0;
68664
+ if (waitOptionalValue === null) {
68665
+ // --wait present without a duration value: interactive mode
68666
+ waitForUser = true;
68667
+ }
68668
+ else if (waitOptionalValue !== undefined) {
68669
+ // --wait <duration>: time-based wait between rounds
68670
+ waitBetweenPrompts = parseDuration(waitOptionalValue);
68671
+ }
68672
+ // --no-wait (or no flag): both waitForUser and waitBetweenPrompts remain false/0
68382
68673
  let thinkingLevel;
68383
68674
  if (hasTestCommandFlag && testCommand === undefined) {
68384
68675
  exitWithUsageError('Missing value for --test. Use a shell command such as `npm run test` and quote it when it contains top-level CLI flags.');
@@ -68397,7 +68688,8 @@ function parseRunOptions(args) {
68397
68688
  }
68398
68689
  return {
68399
68690
  dryRun,
68400
- waitForUser: !args.includes('--no-wait'),
68691
+ waitForUser,
68692
+ waitBetweenPrompts,
68401
68693
  noCommit,
68402
68694
  ignoreGitChanges,
68403
68695
  normalizeLineEndings,
@@ -68416,6 +68708,23 @@ function parseRunOptions(args) {
68416
68708
  priority,
68417
68709
  };
68418
68710
  }
68711
+ /**
68712
+ * Reads an optional value for a flag that may appear with or without a following value.
68713
+ *
68714
+ * Returns `undefined` when the flag is absent, `null` when the flag is present but has no value
68715
+ * (the next token is another flag or the end of args), and the value string otherwise.
68716
+ */
68717
+ function readOptionalOptionValue(args, flag) {
68718
+ if (!args.includes(flag)) {
68719
+ return undefined;
68720
+ }
68721
+ const index = args.indexOf(flag);
68722
+ const nextArg = args[index + 1];
68723
+ if (nextArg === undefined || nextArg.startsWith('-')) {
68724
+ return null;
68725
+ }
68726
+ return nextArg;
68727
+ }
68419
68728
  /**
68420
68729
  * Reads a value of a CLI option that follows a given flag.
68421
68730
  */
@@ -70871,6 +71180,7 @@ async function runCodexPrompts(providedOptions) {
70871
71180
  initializeRunUi(uiHandle, runner.name, actualRunnerModel, options);
70872
71181
  let hasShownUpcomingTasks = false;
70873
71182
  let hasWaitedForStart = false;
71183
+ let hasRunAtLeastOneRound = false;
70874
71184
  while (just(true)) {
70875
71185
  if (options.autoPull && !options.dryRun) {
70876
71186
  await waitForRequestedPause({
@@ -70906,6 +71216,16 @@ async function runCodexPrompts(providedOptions) {
70906
71216
  }
70907
71217
  const nextPrompt = promptQueueSnapshot.nextPrompt;
70908
71218
  const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
71219
+ // Wait between prompt rounds (skipped for the first round)
71220
+ if (hasRunAtLeastOneRound) {
71221
+ await waitBetweenPromptRoundsIfNeeded({
71222
+ options,
71223
+ isRichUiEnabled,
71224
+ progressDisplay,
71225
+ uiHandle,
71226
+ });
71227
+ }
71228
+ hasRunAtLeastOneRound = true;
70909
71229
  hasWaitedForStart = await waitForPromptConfirmationIfNeeded({
70910
71230
  options,
70911
71231
  nextPrompt,
@@ -71141,6 +71461,39 @@ async function waitForPromptConfirmationIfNeeded(options) {
71141
71461
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
71142
71462
  return true;
71143
71463
  }
71464
+ /**
71465
+ * Countdown update interval for the between-rounds wait display.
71466
+ */
71467
+ const WAIT_COUNTDOWN_UPDATE_INTERVAL_MS = 30000;
71468
+ /**
71469
+ * Waits the configured time between prompt rounds to avoid hitting harness rate limits.
71470
+ * Does nothing when `waitBetweenPrompts` is zero.
71471
+ */
71472
+ async function waitBetweenPromptRoundsIfNeeded(options) {
71473
+ const { options: runOptions, isRichUiEnabled, progressDisplay, uiHandle } = options;
71474
+ const { waitBetweenPrompts } = runOptions;
71475
+ if (waitBetweenPrompts <= 0) {
71476
+ return;
71477
+ }
71478
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
71479
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
71480
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
71481
+ let remaining = waitBetweenPrompts;
71482
+ while (remaining > 0) {
71483
+ const statusMessage = `Waiting ${formatDurationMs(remaining)} before next prompt to avoid rate limits...`;
71484
+ if (!isRichUiEnabled) {
71485
+ console.info(colors.gray(statusMessage));
71486
+ }
71487
+ else {
71488
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(statusMessage);
71489
+ }
71490
+ const sleepMs = Math.min(WAIT_COUNTDOWN_UPDATE_INTERVAL_MS, remaining);
71491
+ await new Promise((resolve) => setTimeout(resolve, sleepMs));
71492
+ remaining -= sleepMs;
71493
+ }
71494
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
71495
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
71496
+ }
71144
71497
  /**
71145
71498
  * Stops active displays and prints the git identity tip for real runs.
71146
71499
  */