@promptbook/cli 0.112.0-117 → 0.112.0-119

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 (90) hide show
  1. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatSidebarDefault.tsx +5 -6
  2. package/apps/agents-server/src/app/api/page-preview/check/route.ts +31 -0
  3. package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +57 -0
  4. package/apps/agents-server/src/app/api/upload/route.ts +10 -1
  5. package/apps/agents-server/src/app/s3/[first]/[second]/[hash]/[filename]/route.ts +52 -0
  6. package/apps/agents-server/src/database/$provideClientSql.ts +37 -0
  7. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +41 -0
  8. package/apps/agents-server/src/tools/$provideCdnForServer.ts +24 -0
  9. package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +30 -2
  10. package/apps/agents-server/src/utils/cdn/utils/getUserFileCdnKey.ts +10 -3
  11. package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +17 -7
  12. package/apps/agents-server/src/utils/iframe/checkIfUrlCanBeEmbedded.ts +68 -0
  13. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +17 -7
  14. package/apps/agents-server/src/utils/userChat/createImmediateUserChatAnswerModelRequirements.ts +11 -0
  15. package/apps/agents-server/src/utils/userChat/listUserChats.ts +5 -7
  16. package/esm/index.es.js +442 -66
  17. package/esm/index.es.js.map +1 -1
  18. package/esm/scripts/run-codex-prompts/common/parseDuration.d.ts +19 -0
  19. package/esm/src/_packages/node.index.d.ts +10 -0
  20. package/esm/src/book-3.0/BookNodeAgentSource.d.ts +1 -1
  21. package/esm/src/book-3.0/CliAgent.d.ts +7 -2
  22. package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
  23. package/esm/src/book-components/BookEditor/BookEditor.d.ts +1 -1
  24. package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +1 -1
  25. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  26. package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  27. package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  28. package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
  29. package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
  30. package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
  31. package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  32. package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  33. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +2 -18
  34. package/esm/src/version.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/src/_packages/node.index.ts +10 -0
  37. package/src/avatars/avatarAnimationScheduler.ts +33 -2
  38. package/src/avatars/visuals/fractalAvatarVisual.ts +5 -4
  39. package/src/avatars/visuals/minecraft2AvatarVisual.ts +16 -11
  40. package/src/avatars/visuals/minecraftAvatarVisual.ts +21 -7
  41. package/src/avatars/visuals/octopus3d2AvatarVisual.ts +69 -17
  42. package/src/avatars/visuals/octopus3d3AvatarVisual.ts +81 -18
  43. package/src/avatars/visuals/octopus3dAvatarVisual.ts +69 -17
  44. package/src/book-3.0/Book.ts +3 -1
  45. package/src/book-3.0/BookNodeAgentSource.ts +2 -2
  46. package/src/book-3.0/CliAgent.ts +84 -6
  47. package/src/book-3.0/LiteAgent.ts +1 -1
  48. package/src/book-3.0/cliAgentEnv.ts +46 -0
  49. package/src/book-components/BookEditor/BookEditor.tsx +6 -6
  50. package/src/book-components/BookEditor/BookEditorForClient.tsx +1 -1
  51. package/src/book-components/Chat/Chat/Chat.module.css +45 -0
  52. package/src/book-components/Chat/Chat/ChatCitationModal.tsx +2 -2
  53. package/src/book-components/Chat/Chat/CitationIframePreview.tsx +83 -0
  54. package/src/book-components/_common/Dropdown/Dropdown.tsx +1 -1
  55. package/src/book-components/_common/MenuHoisting/MenuHoistingContext.tsx +1 -1
  56. package/src/book-components/_common/Modal/Modal.tsx +1 -1
  57. package/src/book-components/icons/AboutIcon.tsx +1 -1
  58. package/src/book-components/icons/DownloadIcon.tsx +1 -1
  59. package/src/book-components/icons/ExitFullscreenIcon.tsx +1 -1
  60. package/src/book-components/icons/FullscreenIcon.tsx +1 -1
  61. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +31 -1
  62. package/src/cli/cli-commands/coder/run.ts +28 -3
  63. package/src/cli/cli-commands/common/promptRunnerCliOptions.ts +9 -29
  64. package/src/execution/createPipelineExecutor/getKnowledgeForTask.ts +1 -1
  65. package/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.ts +1 -1
  66. package/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.ts +1 -1
  67. package/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.ts +1 -1
  68. package/src/other/templates/getTemplatesPipelineCollection.ts +734 -711
  69. package/src/scripting/javascript/JavascriptEvalExecutionTools.ts +1 -1
  70. package/src/version.ts +2 -2
  71. package/src/versions.txt +2 -0
  72. package/umd/index.umd.js +442 -66
  73. package/umd/index.umd.js.map +1 -1
  74. package/umd/scripts/run-codex-prompts/common/parseDuration.d.ts +19 -0
  75. package/umd/src/_packages/node.index.d.ts +10 -0
  76. package/umd/src/book-3.0/BookNodeAgentSource.d.ts +1 -1
  77. package/umd/src/book-3.0/CliAgent.d.ts +7 -2
  78. package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
  79. package/umd/src/book-components/BookEditor/BookEditor.d.ts +1 -1
  80. package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +1 -1
  81. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  82. package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  83. package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  84. package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
  85. package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
  86. package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
  87. package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  88. package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  89. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +2 -18
  90. package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js CHANGED
@@ -63,7 +63,7 @@
63
63
  * @generated
64
64
  * @see https://github.com/webgptorg/promptbook
65
65
  */
66
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-117';
66
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-119';
67
67
  /**
68
68
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
69
69
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1745,12 +1745,13 @@
1745
1745
  }
1746
1746
  // Note: [🟡] Code for CLI command [run](src/cli/cli-commands/coder/run.ts) should never be published outside of `@promptbook/cli`
1747
1747
 
1748
+ // Note: [💞] Ignore a discrepancy between file name and entity name
1748
1749
  /**
1749
- * Runner identifiers supported by Promptbook CLI agent orchestration commands.
1750
+ * All CLI harness names supported by `CliAgent` and `ptbk agent exec`.
1750
1751
  *
1751
- * @private internal utility of `promptbookCli`
1752
+ * @public exported from `@promptbook/node`
1752
1753
  */
1753
- const PROMPT_RUNNER_HARNESS_NAMES = [
1754
+ const CLI_AGENT_HARNESS_NAMES = [
1754
1755
  'openai-codex',
1755
1756
  'github-copilot',
1756
1757
  'cline',
@@ -1759,23 +1760,33 @@
1759
1760
  'gemini',
1760
1761
  ];
1761
1762
  /**
1762
- * Environment variable used as the default runner identifier when `--harness` is omitted.
1763
+ * Environment variable used as the default runner identifier when `--harness` is omitted or not set in `CliAgent`.
1763
1764
  *
1764
- * @private internal utility of `promptbookCli`
1765
+ * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`)
1766
+ * so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
1767
+ *
1768
+ * @public exported from `@promptbook/node`
1765
1769
  */
1766
1770
  const PTBK_HARNESS_ENV = 'PTBK_HARNESS';
1767
1771
  /**
1768
- * Environment variable used as the default runner model when `--model` is omitted.
1772
+ * Environment variable used as the default runner model when `--model` is omitted or not set in `CliAgent`.
1769
1773
  *
1770
- * @private internal utility of `promptbookCli`
1774
+ * @public exported from `@promptbook/node`
1771
1775
  */
1772
1776
  const PTBK_MODEL_ENV = 'PTBK_MODEL';
1773
1777
  /**
1774
- * Environment variable used as the default runner thinking level when `--thinking-level` is omitted.
1778
+ * Environment variable used as the default thinking level when `--thinking-level` is omitted or not set in `CliAgent`.
1775
1779
  *
1776
- * @private internal utility of `promptbookCli`
1780
+ * @public exported from `@promptbook/node`
1777
1781
  */
1778
1782
  const PTBK_THINKING_LEVEL_ENV = 'PTBK_THINKING_LEVEL';
1783
+
1784
+ /**
1785
+ * Runner identifiers supported by Promptbook CLI agent orchestration commands.
1786
+ *
1787
+ * @private internal utility of `promptbookCli`
1788
+ */
1789
+ const PROMPT_RUNNER_HARNESS_NAMES = CLI_AGENT_HARNESS_NAMES;
1779
1790
  /**
1780
1791
  * Description block shared by runner-backed CLI commands.
1781
1792
  *
@@ -3266,6 +3277,16 @@
3266
3277
  * @private internal constant of `ptbk agents-server`
3267
3278
  */
3268
3279
  const DEFAULT_AGENTS_SERVER_NEXT_DIST_DIRECTORY_NAME = '.next';
3280
+ /**
3281
+ * Node.js heap size limit (in MiB) injected into `NODE_OPTIONS` for the Next.js production build.
3282
+ *
3283
+ * Next.js webpack peaks at ~1.9 GiB on a fresh install; the Node.js default cap is ~1.7 GiB,
3284
+ * which causes an OOM crash on first-run VPS installations where swap is the primary resource.
3285
+ * Raising the limit lets the build complete on the first attempt.
3286
+ *
3287
+ * @private internal constant of `ptbk agents-server`
3288
+ */
3289
+ const AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB = 4096;
3269
3290
  /**
3270
3291
  * Environment variable passed to the bundled Next app so webpack can resolve dependencies
3271
3292
  * installed beside `ptbk` even when the app sources are materialized into a project cache.
@@ -3663,7 +3684,10 @@
3663
3684
  var _a, _b;
3664
3685
  const buildProcess = child_process.spawn(process.execPath, [options.nextCliPath, 'build'], {
3665
3686
  cwd: options.appPath,
3666
- env: options.environment,
3687
+ env: {
3688
+ ...options.environment,
3689
+ NODE_OPTIONS: mergeNodeOptionsWithHeapSize(options.environment.NODE_OPTIONS, AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB),
3690
+ },
3667
3691
  stdio: ['ignore', 'pipe', 'pipe'],
3668
3692
  });
3669
3693
  (_a = buildProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => {
@@ -3682,6 +3706,16 @@
3682
3706
  });
3683
3707
  });
3684
3708
  }
3709
+ /**
3710
+ * Prepends `--max-old-space-size=<mib>` to `NODE_OPTIONS` unless the caller already set one.
3711
+ */
3712
+ function mergeNodeOptionsWithHeapSize(existingNodeOptions, maxOldSpaceMib) {
3713
+ if (existingNodeOptions !== undefined && /--max-old-space-size[= ]/u.test(existingNodeOptions)) {
3714
+ return existingNodeOptions;
3715
+ }
3716
+ const heapFlag = `--max-old-space-size=${maxOldSpaceMib}`;
3717
+ return existingNodeOptions ? `${heapFlag} ${existingNodeOptions}` : heapFlag;
3718
+ }
3685
3719
  /**
3686
3720
  * Sends one Next build output chunk through the caller handler or to the foreground terminal.
3687
3721
  */
@@ -5129,6 +5163,7 @@
5129
5163
  noUi: options.noUi,
5130
5164
  thinkingLevel: options.thinkingLevel,
5131
5165
  waitForUser: false,
5166
+ waitBetweenPrompts: 0,
5132
5167
  noCommit: options.noCommit,
5133
5168
  ignoreGitChanges: options.ignoreGitChanges,
5134
5169
  normalizeLineEndings: options.normalizeLineEndings,
@@ -10821,21 +10856,22 @@
10821
10856
  * @private helper of `fractalAvatarVisual`
10822
10857
  */
10823
10858
  function drawDragonCurveLayer(context, points, options) {
10824
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
10859
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
10825
10860
  const firstPoint = points[0];
10826
10861
  const lastPoint = points[points.length - 1];
10827
10862
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
10828
10863
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
10829
10864
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
10830
10865
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
10866
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
10867
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
10831
10868
  context.save();
10832
10869
  context.beginPath();
10833
10870
  tracePolyline(context, points);
10834
- context.strokeStyle = `${shadowColor}82`;
10835
- context.lineWidth = strokeWidth * 1.8;
10871
+ context.strokeStyle = `${shadowColor}48`;
10872
+ context.lineWidth = strokeWidth * 4.5;
10836
10873
  context.lineJoin = 'round';
10837
10874
  context.lineCap = 'round';
10838
- context.filter = `blur(${size * 0.022}px)`;
10839
10875
  context.stroke();
10840
10876
  context.restore();
10841
10877
  context.beginPath();
@@ -11419,11 +11455,23 @@
11419
11455
  * @private helper of `minecraft2AvatarVisual`
11420
11456
  */
11421
11457
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
11458
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
11459
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
11460
+ const rx = size * (0.16 + interaction.intensity * 0.015);
11461
+ const ry = size * 0.055;
11462
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
11422
11463
  context.save();
11423
- context.fillStyle = `${palette.shadow}66`;
11424
- context.filter = `blur(${size * 0.02}px)`;
11464
+ context.translate(cx, cy);
11465
+ context.scale(1, ry / rx);
11466
+ const blurRadius = rx * 1.4;
11467
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
11468
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
11469
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
11470
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
11471
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
11472
+ context.fillStyle = shadowGradient;
11425
11473
  context.beginPath();
11426
- 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);
11474
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
11427
11475
  context.fill();
11428
11476
  context.restore();
11429
11477
  }
@@ -11647,13 +11695,27 @@
11647
11695
  spotlight.addColorStop(1, `${palette.highlight}00`);
11648
11696
  context.fillStyle = spotlight;
11649
11697
  context.fillRect(0, 0, size, size);
11650
- context.save();
11651
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
11652
- context.filter = `blur(${size * 0.018}px)`;
11653
- context.beginPath();
11654
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
11655
- context.fill();
11656
- context.restore();
11698
+ {
11699
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
11700
+ const cx = size * 0.5;
11701
+ const cy = size * 0.86;
11702
+ const rx = size * 0.2;
11703
+ const ry = size * 0.06;
11704
+ const blurRadius = rx * 1.4;
11705
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
11706
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
11707
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
11708
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
11709
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
11710
+ context.save();
11711
+ context.translate(cx, cy);
11712
+ context.scale(1, ry / rx);
11713
+ context.fillStyle = shadowGradient;
11714
+ context.beginPath();
11715
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
11716
+ context.fill();
11717
+ context.restore();
11718
+ }
11657
11719
  drawVoxelCuboid(context, {
11658
11720
  x: bodyX,
11659
11721
  y: bodyY,
@@ -12705,6 +12767,35 @@
12705
12767
  y: -0.62,
12706
12768
  z: 0.94,
12707
12769
  });
12770
+ /**
12771
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
12772
+ *
12773
+ * @private helper of `octopus3dAvatarVisual`
12774
+ */
12775
+ const octopus3dStableStateCache = new WeakMap();
12776
+ /**
12777
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
12778
+ *
12779
+ * @private helper of `octopus3dAvatarVisual`
12780
+ */
12781
+ function getOctopus3dStableState(createRandom) {
12782
+ const cached = octopus3dStableStateCache.get(createRandom);
12783
+ if (cached !== undefined) {
12784
+ return cached;
12785
+ }
12786
+ const animationRandom = createRandom('octopus3d-animation-profile');
12787
+ const eyeRandom = createRandom('octopus3d-eye-profile');
12788
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
12789
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
12790
+ const state = {
12791
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
12792
+ animationPhase: animationRandom() * Math.PI * 2,
12793
+ leftEyePhaseOffset,
12794
+ rightEyePhaseOffset,
12795
+ };
12796
+ octopus3dStableStateCache.set(createRandom, state);
12797
+ return state;
12798
+ }
12708
12799
  /**
12709
12800
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
12710
12801
  *
@@ -12717,10 +12808,7 @@
12717
12808
  isAnimated: true,
12718
12809
  supportsPointerTracking: true,
12719
12810
  render({ context, size, palette, createRandom, timeMs, interaction }) {
12720
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
12721
- const animationRandom = createRandom('octopus3d-animation-profile');
12722
- const eyeRandom = createRandom('octopus3d-eye-profile');
12723
- const animationPhase = animationRandom() * Math.PI * 2;
12811
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
12724
12812
  const sceneCenterX = size * 0.5;
12725
12813
  const sceneCenterY = size * 0.56;
12726
12814
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -12817,12 +12905,12 @@
12817
12905
  x: -faceEyeSpacing,
12818
12906
  y: faceEyeYOffset,
12819
12907
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
12820
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
12908
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
12821
12909
  drawProjectedOrganicEye(context, {
12822
12910
  x: faceEyeSpacing,
12823
12911
  y: faceEyeYOffset,
12824
12912
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
12825
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
12913
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
12826
12914
  drawProjectedOrganicMouth(context, [
12827
12915
  {
12828
12916
  x: -mouthHalfWidth,
@@ -12866,14 +12954,28 @@
12866
12954
  /**
12867
12955
  * Draws the soft ground shadow below the octopus.
12868
12956
  *
12957
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
12958
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
12959
+ *
12869
12960
  * @private helper of `octopus3dAvatarVisual`
12870
12961
  */
12871
12962
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
12963
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
12964
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
12965
+ const rx = size * (0.18 + interaction.intensity * 0.02);
12966
+ const ry = size * 0.06;
12872
12967
  context.save();
12873
- context.fillStyle = `${palette.shadow}66`;
12874
- context.filter = `blur(${size * 0.022}px)`;
12968
+ context.translate(cx, cy);
12969
+ context.scale(1, ry / rx);
12970
+ const blurRadius = rx * 1.4;
12971
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
12972
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
12973
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
12974
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
12975
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
12976
+ context.fillStyle = shadowGradient;
12875
12977
  context.beginPath();
12876
- 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);
12978
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
12877
12979
  context.fill();
12878
12980
  context.restore();
12879
12981
  }
@@ -13104,6 +13206,35 @@
13104
13206
  y: -0.6,
13105
13207
  z: 0.98,
13106
13208
  });
13209
+ /**
13210
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
13211
+ *
13212
+ * @private helper of `octopus3d2AvatarVisual`
13213
+ */
13214
+ const octopus3d2StableStateCache = new WeakMap();
13215
+ /**
13216
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
13217
+ *
13218
+ * @private helper of `octopus3d2AvatarVisual`
13219
+ */
13220
+ function getOctopus3d2StableState(createRandom) {
13221
+ const cached = octopus3d2StableStateCache.get(createRandom);
13222
+ if (cached !== undefined) {
13223
+ return cached;
13224
+ }
13225
+ const animationRandom = createRandom('octopus3d2-animation-profile');
13226
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
13227
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
13228
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
13229
+ const state = {
13230
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
13231
+ animationPhase: animationRandom() * Math.PI * 2,
13232
+ leftEyePhaseOffset,
13233
+ rightEyePhaseOffset,
13234
+ };
13235
+ octopus3d2StableStateCache.set(createRandom, state);
13236
+ return state;
13237
+ }
13107
13238
  /**
13108
13239
  * Octopus 3D 2 avatar visual.
13109
13240
  *
@@ -13116,10 +13247,7 @@
13116
13247
  isAnimated: true,
13117
13248
  supportsPointerTracking: true,
13118
13249
  render({ context, size, palette, createRandom, timeMs, interaction }) {
13119
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
13120
- const animationRandom = createRandom('octopus3d2-animation-profile');
13121
- const eyeRandom = createRandom('octopus3d2-eye-profile');
13122
- const animationPhase = animationRandom() * Math.PI * 2;
13250
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
13123
13251
  const sceneCenterX = size * 0.5;
13124
13252
  const sceneCenterY = size * 0.575;
13125
13253
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -13172,8 +13300,8 @@
13172
13300
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
13173
13301
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
13174
13302
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
13175
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
13176
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
13303
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13304
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13177
13305
  drawProjectedOrganicMouth(context, [
13178
13306
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
13179
13307
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -13202,14 +13330,28 @@
13202
13330
  /**
13203
13331
  * Draws the soft floor shadow that anchors the single mesh in the frame.
13204
13332
  *
13333
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
13334
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
13335
+ *
13205
13336
  * @private helper of `octopus3d2AvatarVisual`
13206
13337
  */
13207
13338
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
13339
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
13340
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
13341
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
13342
+ const ry = size * 0.062;
13208
13343
  context.save();
13209
- context.fillStyle = `${palette.shadow}66`;
13210
- context.filter = `blur(${size * 0.024}px)`;
13344
+ context.translate(cx, cy);
13345
+ context.scale(1, ry / rx);
13346
+ const blurRadius = rx * 1.4;
13347
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
13348
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
13349
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
13350
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
13351
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
13352
+ context.fillStyle = shadowGradient;
13211
13353
  context.beginPath();
13212
- 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);
13354
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
13213
13355
  context.fill();
13214
13356
  context.restore();
13215
13357
  }
@@ -13365,6 +13507,40 @@
13365
13507
  * @private helper of `octopus3d3AvatarVisual`
13366
13508
  */
13367
13509
  const OCTOPUS_TENTACLE_COUNT = 8;
13510
+ /**
13511
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
13512
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
13513
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
13514
+ *
13515
+ * @private helper of `octopus3d3AvatarVisual`
13516
+ */
13517
+ const stableStateCache = new WeakMap();
13518
+ /**
13519
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
13520
+ * result on every subsequent call within the same `<Avatar/>` mount.
13521
+ *
13522
+ * @private helper of `octopus3d3AvatarVisual`
13523
+ */
13524
+ function getOctopus3d3StableState(createRandom) {
13525
+ const cached = stableStateCache.get(createRandom);
13526
+ if (cached !== undefined) {
13527
+ return cached;
13528
+ }
13529
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
13530
+ const animationRandom = createRandom('octopus3d3-animation-profile');
13531
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
13532
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
13533
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
13534
+ const state = {
13535
+ morphologyProfile,
13536
+ animationPhase: animationRandom() * Math.PI * 2,
13537
+ leftEyePhaseOffset,
13538
+ rightEyePhaseOffset,
13539
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
13540
+ };
13541
+ stableStateCache.set(createRandom, state);
13542
+ return state;
13543
+ }
13368
13544
  /**
13369
13545
  * Octopus 3D 3 avatar visual.
13370
13546
  *
@@ -13377,11 +13553,7 @@
13377
13553
  isAnimated: true,
13378
13554
  supportsPointerTracking: true,
13379
13555
  render({ context, size, palette, createRandom, timeMs, interaction }) {
13380
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
13381
- const animationRandom = createRandom('octopus3d3-animation-profile');
13382
- const eyeRandom = createRandom('octopus3d3-eye-profile');
13383
- const animationPhase = animationRandom() * Math.PI * 2;
13384
- const tentacleProfiles = createContinuousTentacleProfiles(createRandom, morphologyProfile);
13556
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
13385
13557
  const sceneCenterX = size * 0.5;
13386
13558
  const sceneCenterY = size * 0.535;
13387
13559
  const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
@@ -13458,8 +13630,8 @@
13458
13630
  size,
13459
13631
  palette,
13460
13632
  });
13461
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
13462
- 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);
13633
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13634
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
13463
13635
  drawProjectedOrganicMouth(context, [
13464
13636
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
13465
13637
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -13510,14 +13682,30 @@
13510
13682
  /**
13511
13683
  * Draws the soft lower shadow that anchors the octopus in the avatar frame.
13512
13684
  *
13685
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
13686
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
13687
+ *
13513
13688
  * @private helper of `octopus3d3AvatarVisual`
13514
13689
  */
13515
13690
  function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
13691
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
13692
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
13693
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
13694
+ const ry = size * 0.06;
13695
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
13696
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
13516
13697
  context.save();
13517
- context.fillStyle = `${palette.shadow}66`;
13518
- context.filter = `blur(${size * 0.025}px)`;
13698
+ context.translate(cx, cy);
13699
+ context.scale(1, ry / rx);
13700
+ const blurRadius = rx * 1.4;
13701
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
13702
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
13703
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
13704
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
13705
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
13706
+ context.fillStyle = shadowGradient;
13519
13707
  context.beginPath();
13520
- 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);
13708
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
13521
13709
  context.fill();
13522
13710
  context.restore();
13523
13711
  }
@@ -28742,14 +28930,50 @@
28742
28930
  * Builds the prompt sent to the selected coding runner for one queued user-thread book.
28743
28931
  */
28744
28932
  function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage) {
28745
- const taskPrompt = _spaceTrim.spaceTrim(`
28746
- Answer 1 user question
28933
+ return _spaceTrim.spaceTrim((block) => `
28934
+ # Answer 1 user question
28747
28935
 
28748
- - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE @User\`
28749
- - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
28750
- - Do not modify any other file in the repository
28751
- `);
28752
- return `${taskPrompt}\n\n**This is how you should behave:**\n\n${agentSystemMessage.trim()}`;
28936
+ - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE @User\`
28937
+ - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
28938
+ - Do not modify any other file in the repository
28939
+
28940
+ ## Rules for the answering
28941
+
28942
+ ## Formatting
28943
+
28944
+ - You can use Markdown formatting in the messages like **bold** or *italic*
28945
+
28946
+ ## Sources and citations
28947
+
28948
+ Mark sources and citations like this "【https://example.com/document123.pdf 】"
28949
+
28950
+ At the same time, you can write sources naturally in the text of the answers
28951
+
28952
+ For example:
28953
+
28954
+ > According to paragraph §745b, the fee can be waived for a person over 65 years old. 【https://praha13.cz/2026/paragraph-745.doc】
28955
+
28956
+ - "paragraph §745b" fits naturally in the text.
28957
+ - "【https://praha13.cz/2026/paragraph-745.doc】" The exact format of the quote is important for further processing of the answer.
28958
+ - 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.
28959
+
28960
+ ## Quick buttons
28961
+
28962
+ If there is a meaningful follow-up procedure, use quick buttons at the end of the answer:
28963
+
28964
+ \`\`\`
28965
+ How big is the contract you are posting?
28966
+
28967
+ [Up to 50,000 CZK](?message=We are posting an order up to 50,000 CZK)
28968
+ [Up to 100,000 CZK](?message=We are posting an order up to 100,000 CZK)
28969
+ [Over 100,000 CZK](?message=We are posting an order over 100,000 CZK)
28970
+ \`\`\`
28971
+
28972
+
28973
+ ## This is how you should behave
28974
+
28975
+ ${block(agentSystemMessage)}
28976
+ `);
28753
28977
  }
28754
28978
 
28755
28979
  /**
@@ -35894,7 +36118,9 @@
35894
36118
  * @private internal utility of `Book`
35895
36119
  */
35896
36120
  function parseCommitmentHeader(line) {
35897
- const match = /^([A-Z][A-Z0-9]*(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
36121
+ // Require at least 2 characters in the first keyword word to avoid treating common
36122
+ // single-letter words (e.g. "V" in Czech, "I" or "A" in English) as commitment headers.
36123
+ const match = /^([A-Z][A-Z0-9]+(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
35898
36124
  if (!match) {
35899
36125
  return null;
35900
36126
  }
@@ -39308,6 +39534,57 @@
39308
39534
  // Note: [🟡] Code for CLI command [init](src/cli/cli-commands/coder/init.ts) should never be published outside of `@promptbook/cli`
39309
39535
  // Note: [💞] Ignore a discrepancy between file name and entity name
39310
39536
 
39537
+ /**
39538
+ * Pattern that matches durations like "1h", "30m", "5s", "1h30m", "1h30m5s".
39539
+ */
39540
+ const DURATION_PATTERN = /^(?:(\d+)h)?(?:(\d+)m(?:in)?)?(?:(\d+)s)?$/;
39541
+ /**
39542
+ * Parses a human-readable duration string into milliseconds.
39543
+ *
39544
+ * Supported formats: `Xh`, `Xm`, `Xs`, and combinations like `1h30m`, `1h30m5s`.
39545
+ *
39546
+ * @returns Duration in milliseconds
39547
+ * @throws When the string does not match any supported format
39548
+ *
39549
+ * @private internal utility of `ptbk coder run`
39550
+ */
39551
+ function parseDuration(durationString) {
39552
+ var _a, _b, _c;
39553
+ const trimmed = durationString.trim();
39554
+ if (!trimmed) {
39555
+ throw new Error(`Invalid duration: empty string. Expected a format like "1h", "30m", "5s", or combinations like "1h30m".`);
39556
+ }
39557
+ const match = trimmed.match(DURATION_PATTERN);
39558
+ if (!match || (match[1] === undefined && match[2] === undefined && match[3] === undefined)) {
39559
+ throw new Error(`Invalid duration: "${durationString}". Expected a format like "1h", "30m", "5s", or combinations like "1h30m5s".`);
39560
+ }
39561
+ const hours = parseInt((_a = match[1]) !== null && _a !== void 0 ? _a : '0', 10);
39562
+ const minutes = parseInt((_b = match[2]) !== null && _b !== void 0 ? _b : '0', 10);
39563
+ const seconds = parseInt((_c = match[3]) !== null && _c !== void 0 ? _c : '0', 10);
39564
+ return (hours * 3600 + minutes * 60 + seconds) * 1000;
39565
+ }
39566
+ /**
39567
+ * Formats a duration in milliseconds into a compact human-readable string.
39568
+ *
39569
+ * Examples: `3600000` → `"1h"`, `90000` → `"1m 30s"`, `5000` → `"5s"`.
39570
+ *
39571
+ * @private internal utility of `ptbk coder run`
39572
+ */
39573
+ function formatDurationMs(ms) {
39574
+ const totalSeconds = Math.ceil(ms / 1000);
39575
+ const hours = Math.floor(totalSeconds / 3600);
39576
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
39577
+ const seconds = totalSeconds % 60;
39578
+ const parts = [];
39579
+ if (hours > 0)
39580
+ parts.push(`${hours}h`);
39581
+ if (minutes > 0)
39582
+ parts.push(`${minutes}m`);
39583
+ if (seconds > 0 || parts.length === 0)
39584
+ parts.push(`${seconds}s`);
39585
+ return parts.join(' ');
39586
+ }
39587
+
39311
39588
  /**
39312
39589
  * Initializes `coder run` command for Promptbook CLI utilities
39313
39590
  *
@@ -39340,7 +39617,13 @@
39340
39617
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
39341
39618
  addPromptRunnerExecutionOptions(command);
39342
39619
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
39343
- command.option('--no-wait', 'Skip user prompts between processing');
39620
+ command.option('--wait [duration]', _spaceTrim.spaceTrim(`
39621
+ Wait between prompt rounds.
39622
+ Without a value (default): waits for user confirmation before each prompt (interactive mode).
39623
+ With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
39624
+ `), true);
39625
+ // Note: --no-wait disables the default interactive wait-for-user behaviour
39626
+ command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
39344
39627
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
39345
39628
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
39346
39629
  command.action(handleActionErrors(async (cliOptions) => {
@@ -39349,10 +39632,23 @@
39349
39632
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
39350
39633
  isAgentRequired: !dryRun,
39351
39634
  });
39635
+ // [1] Parse the --wait option:
39636
+ // true (default or --wait without value): wait for user confirmation
39637
+ // false (--no-wait): no waiting at all
39638
+ // string (--wait 1h): wait that long between prompt rounds
39639
+ let waitForUser = false;
39640
+ let waitBetweenPrompts = 0;
39641
+ if (wait === true) {
39642
+ waitForUser = true;
39643
+ }
39644
+ else if (typeof wait === 'string' && wait !== '') {
39645
+ waitBetweenPrompts = parseDuration(wait);
39646
+ }
39352
39647
  // Convert commander options to RunOptions format
39353
39648
  const runOptions = {
39354
39649
  dryRun,
39355
- waitForUser: wait,
39650
+ waitForUser,
39651
+ waitBetweenPrompts,
39356
39652
  noCommit: runnerOptions.noCommit,
39357
39653
  ignoreGitChanges: runnerOptions.ignoreGitChanges,
39358
39654
  agentName: runnerOptions.agentName,
@@ -58191,7 +58487,7 @@
58191
58487
  return { skippedReason: 'invalid_data_url' };
58192
58488
  }
58193
58489
  return {
58194
- file: new File([parsed.buffer], parsed.filename, {
58490
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
58195
58491
  type: parsed.mimeType,
58196
58492
  }),
58197
58493
  sizeBytes: parsed.buffer.length,
@@ -62795,6 +63091,7 @@
62795
63091
  noUi: options.noUi,
62796
63092
  thinkingLevel: options.thinkingLevel,
62797
63093
  waitForUser: false,
63094
+ waitBetweenPrompts: 0,
62798
63095
  noCommit: true,
62799
63096
  ignoreGitChanges: true,
62800
63097
  normalizeLineEndings: false,
@@ -68329,7 +68626,7 @@
68329
68626
  /**
68330
68627
  * CLI usage text for this script.
68331
68628
  */
68332
- 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]';
68629
+ 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]';
68333
68630
  /**
68334
68631
  * Top-level flags supported by this command.
68335
68632
  */
@@ -68346,6 +68643,7 @@
68346
68643
  '--allow-credits',
68347
68644
  '--auto-migrate',
68348
68645
  '--allow-destructive-auto-migrate',
68646
+ '--wait',
68349
68647
  '--no-wait',
68350
68648
  '--no-commit',
68351
68649
  '--ignore-git-changes',
@@ -68384,6 +68682,22 @@
68384
68682
  const allowDestructiveAutoMigrate = args.includes('--allow-destructive-auto-migrate');
68385
68683
  const autoPush = args.includes('--auto-push');
68386
68684
  const autoPull = args.includes('--auto-pull');
68685
+ // [1] Parse --wait [duration]:
68686
+ // absent or --no-wait: no waiting
68687
+ // --wait (no value): wait for user confirmation
68688
+ // --wait 1h: wait 1h between prompt rounds to avoid rate limits
68689
+ const waitOptionalValue = readOptionalOptionValue(args, '--wait');
68690
+ let waitForUser = false;
68691
+ let waitBetweenPrompts = 0;
68692
+ if (waitOptionalValue === null) {
68693
+ // --wait present without a duration value: interactive mode
68694
+ waitForUser = true;
68695
+ }
68696
+ else if (waitOptionalValue !== undefined) {
68697
+ // --wait <duration>: time-based wait between rounds
68698
+ waitBetweenPrompts = parseDuration(waitOptionalValue);
68699
+ }
68700
+ // --no-wait (or no flag): both waitForUser and waitBetweenPrompts remain false/0
68387
68701
  let thinkingLevel;
68388
68702
  if (hasTestCommandFlag && testCommand === undefined) {
68389
68703
  exitWithUsageError('Missing value for --test. Use a shell command such as `npm run test` and quote it when it contains top-level CLI flags.');
@@ -68402,7 +68716,8 @@
68402
68716
  }
68403
68717
  return {
68404
68718
  dryRun,
68405
- waitForUser: !args.includes('--no-wait'),
68719
+ waitForUser,
68720
+ waitBetweenPrompts,
68406
68721
  noCommit,
68407
68722
  ignoreGitChanges,
68408
68723
  normalizeLineEndings,
@@ -68421,6 +68736,23 @@
68421
68736
  priority,
68422
68737
  };
68423
68738
  }
68739
+ /**
68740
+ * Reads an optional value for a flag that may appear with or without a following value.
68741
+ *
68742
+ * Returns `undefined` when the flag is absent, `null` when the flag is present but has no value
68743
+ * (the next token is another flag or the end of args), and the value string otherwise.
68744
+ */
68745
+ function readOptionalOptionValue(args, flag) {
68746
+ if (!args.includes(flag)) {
68747
+ return undefined;
68748
+ }
68749
+ const index = args.indexOf(flag);
68750
+ const nextArg = args[index + 1];
68751
+ if (nextArg === undefined || nextArg.startsWith('-')) {
68752
+ return null;
68753
+ }
68754
+ return nextArg;
68755
+ }
68424
68756
  /**
68425
68757
  * Reads a value of a CLI option that follows a given flag.
68426
68758
  */
@@ -70876,6 +71208,7 @@
70876
71208
  initializeRunUi(uiHandle, runner.name, actualRunnerModel, options);
70877
71209
  let hasShownUpcomingTasks = false;
70878
71210
  let hasWaitedForStart = false;
71211
+ let hasRunAtLeastOneRound = false;
70879
71212
  while (just(true)) {
70880
71213
  if (options.autoPull && !options.dryRun) {
70881
71214
  await waitForRequestedPause({
@@ -70911,6 +71244,16 @@
70911
71244
  }
70912
71245
  const nextPrompt = promptQueueSnapshot.nextPrompt;
70913
71246
  const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
71247
+ // Wait between prompt rounds (skipped for the first round)
71248
+ if (hasRunAtLeastOneRound) {
71249
+ await waitBetweenPromptRoundsIfNeeded({
71250
+ options,
71251
+ isRichUiEnabled,
71252
+ progressDisplay,
71253
+ uiHandle,
71254
+ });
71255
+ }
71256
+ hasRunAtLeastOneRound = true;
70914
71257
  hasWaitedForStart = await waitForPromptConfirmationIfNeeded({
70915
71258
  options,
70916
71259
  nextPrompt,
@@ -71146,6 +71489,39 @@
71146
71489
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
71147
71490
  return true;
71148
71491
  }
71492
+ /**
71493
+ * Countdown update interval for the between-rounds wait display.
71494
+ */
71495
+ const WAIT_COUNTDOWN_UPDATE_INTERVAL_MS = 30000;
71496
+ /**
71497
+ * Waits the configured time between prompt rounds to avoid hitting harness rate limits.
71498
+ * Does nothing when `waitBetweenPrompts` is zero.
71499
+ */
71500
+ async function waitBetweenPromptRoundsIfNeeded(options) {
71501
+ const { options: runOptions, isRichUiEnabled, progressDisplay, uiHandle } = options;
71502
+ const { waitBetweenPrompts } = runOptions;
71503
+ if (waitBetweenPrompts <= 0) {
71504
+ return;
71505
+ }
71506
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
71507
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
71508
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
71509
+ let remaining = waitBetweenPrompts;
71510
+ while (remaining > 0) {
71511
+ const statusMessage = `Waiting ${formatDurationMs(remaining)} before next prompt to avoid rate limits...`;
71512
+ if (!isRichUiEnabled) {
71513
+ console.info(colors__default["default"].gray(statusMessage));
71514
+ }
71515
+ else {
71516
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(statusMessage);
71517
+ }
71518
+ const sleepMs = Math.min(WAIT_COUNTDOWN_UPDATE_INTERVAL_MS, remaining);
71519
+ await new Promise((resolve) => setTimeout(resolve, sleepMs));
71520
+ remaining -= sleepMs;
71521
+ }
71522
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
71523
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
71524
+ }
71149
71525
  /**
71150
71526
  * Stops active displays and prints the git identity tip for real runs.
71151
71527
  */