@promptbook/node 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.
package/umd/index.umd.js CHANGED
@@ -51,7 +51,7 @@
51
51
  * @generated
52
52
  * @see https://github.com/webgptorg/promptbook
53
53
  */
54
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-117';
54
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-118';
55
55
  /**
56
56
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
57
57
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -249,7 +249,9 @@
249
249
  * @private internal utility of `Book`
250
250
  */
251
251
  function parseCommitmentHeader(line) {
252
- const match = /^([A-Z][A-Z0-9]*(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
252
+ // Require at least 2 characters in the first keyword word to avoid treating common
253
+ // single-letter words (e.g. "V" in Czech, "I" or "A" in English) as commitment headers.
254
+ const match = /^([A-Z][A-Z0-9]+(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
253
255
  if (!match) {
254
256
  return null;
255
257
  }
@@ -4514,14 +4516,50 @@
4514
4516
  * Builds the prompt sent to the selected coding runner for one queued user-thread book.
4515
4517
  */
4516
4518
  function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage) {
4517
- const taskPrompt = _spaceTrim.spaceTrim(`
4518
- Answer 1 user question
4519
+ return _spaceTrim.spaceTrim((block) => `
4520
+ # Answer 1 user question
4519
4521
 
4520
- - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE @User\`
4521
- - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
4522
- - Do not modify any other file in the repository
4523
- `);
4524
- return `${taskPrompt}\n\n**This is how you should behave:**\n\n${agentSystemMessage.trim()}`;
4522
+ - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE @User\`
4523
+ - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
4524
+ - Do not modify any other file in the repository
4525
+
4526
+ ## Rules for the answering
4527
+
4528
+ ## Formatting
4529
+
4530
+ - You can use Markdown formatting in the messages like **bold** or *italic*
4531
+
4532
+ ## Sources and citations
4533
+
4534
+ Mark sources and citations like this "【https://example.com/document123.pdf 】"
4535
+
4536
+ At the same time, you can write sources naturally in the text of the answers
4537
+
4538
+ For example:
4539
+
4540
+ > According to paragraph §745b, the fee can be waived for a person over 65 years old. 【https://praha13.cz/2026/paragraph-745.doc】
4541
+
4542
+ - "paragraph §745b" fits naturally in the text.
4543
+ - "【https://praha13.cz/2026/paragraph-745.doc】" The exact format of the quote is important for further processing of the answer.
4544
+ - 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.
4545
+
4546
+ ## Quick buttons
4547
+
4548
+ If there is a meaningful follow-up procedure, use quick buttons at the end of the answer:
4549
+
4550
+ \`\`\`
4551
+ How big is the contract you are posting?
4552
+
4553
+ [Up to 50,000 CZK](?message=We are posting an order up to 50,000 CZK)
4554
+ [Up to 100,000 CZK](?message=We are posting an order up to 100,000 CZK)
4555
+ [Over 100,000 CZK](?message=We are posting an order over 100,000 CZK)
4556
+ \`\`\`
4557
+
4558
+
4559
+ ## This is how you should behave
4560
+
4561
+ ${block(agentSystemMessage)}
4562
+ `);
4525
4563
  }
4526
4564
 
4527
4565
  /**
@@ -15428,21 +15466,22 @@
15428
15466
  * @private helper of `fractalAvatarVisual`
15429
15467
  */
15430
15468
  function drawDragonCurveLayer(context, points, options) {
15431
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
15469
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
15432
15470
  const firstPoint = points[0];
15433
15471
  const lastPoint = points[points.length - 1];
15434
15472
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
15435
15473
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
15436
15474
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
15437
15475
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
15476
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
15477
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
15438
15478
  context.save();
15439
15479
  context.beginPath();
15440
15480
  tracePolyline(context, points);
15441
- context.strokeStyle = `${shadowColor}82`;
15442
- context.lineWidth = strokeWidth * 1.8;
15481
+ context.strokeStyle = `${shadowColor}48`;
15482
+ context.lineWidth = strokeWidth * 4.5;
15443
15483
  context.lineJoin = 'round';
15444
15484
  context.lineCap = 'round';
15445
- context.filter = `blur(${size * 0.022}px)`;
15446
15485
  context.stroke();
15447
15486
  context.restore();
15448
15487
  context.beginPath();
@@ -16026,11 +16065,23 @@
16026
16065
  * @private helper of `minecraft2AvatarVisual`
16027
16066
  */
16028
16067
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
16068
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
16069
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
16070
+ const rx = size * (0.16 + interaction.intensity * 0.015);
16071
+ const ry = size * 0.055;
16072
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
16029
16073
  context.save();
16030
- context.fillStyle = `${palette.shadow}66`;
16031
- context.filter = `blur(${size * 0.02}px)`;
16074
+ context.translate(cx, cy);
16075
+ context.scale(1, ry / rx);
16076
+ const blurRadius = rx * 1.4;
16077
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16078
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16079
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16080
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16081
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16082
+ context.fillStyle = shadowGradient;
16032
16083
  context.beginPath();
16033
- 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);
16084
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16034
16085
  context.fill();
16035
16086
  context.restore();
16036
16087
  }
@@ -16254,13 +16305,27 @@
16254
16305
  spotlight.addColorStop(1, `${palette.highlight}00`);
16255
16306
  context.fillStyle = spotlight;
16256
16307
  context.fillRect(0, 0, size, size);
16257
- context.save();
16258
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
16259
- context.filter = `blur(${size * 0.018}px)`;
16260
- context.beginPath();
16261
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
16262
- context.fill();
16263
- context.restore();
16308
+ {
16309
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
16310
+ const cx = size * 0.5;
16311
+ const cy = size * 0.86;
16312
+ const rx = size * 0.2;
16313
+ const ry = size * 0.06;
16314
+ const blurRadius = rx * 1.4;
16315
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16316
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
16317
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
16318
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
16319
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
16320
+ context.save();
16321
+ context.translate(cx, cy);
16322
+ context.scale(1, ry / rx);
16323
+ context.fillStyle = shadowGradient;
16324
+ context.beginPath();
16325
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16326
+ context.fill();
16327
+ context.restore();
16328
+ }
16264
16329
  drawVoxelCuboid(context, {
16265
16330
  x: bodyX,
16266
16331
  y: bodyY,
@@ -17312,6 +17377,35 @@
17312
17377
  y: -0.62,
17313
17378
  z: 0.94,
17314
17379
  });
17380
+ /**
17381
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
17382
+ *
17383
+ * @private helper of `octopus3dAvatarVisual`
17384
+ */
17385
+ const octopus3dStableStateCache = new WeakMap();
17386
+ /**
17387
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
17388
+ *
17389
+ * @private helper of `octopus3dAvatarVisual`
17390
+ */
17391
+ function getOctopus3dStableState(createRandom) {
17392
+ const cached = octopus3dStableStateCache.get(createRandom);
17393
+ if (cached !== undefined) {
17394
+ return cached;
17395
+ }
17396
+ const animationRandom = createRandom('octopus3d-animation-profile');
17397
+ const eyeRandom = createRandom('octopus3d-eye-profile');
17398
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
17399
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
17400
+ const state = {
17401
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
17402
+ animationPhase: animationRandom() * Math.PI * 2,
17403
+ leftEyePhaseOffset,
17404
+ rightEyePhaseOffset,
17405
+ };
17406
+ octopus3dStableStateCache.set(createRandom, state);
17407
+ return state;
17408
+ }
17315
17409
  /**
17316
17410
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
17317
17411
  *
@@ -17324,10 +17418,7 @@
17324
17418
  isAnimated: true,
17325
17419
  supportsPointerTracking: true,
17326
17420
  render({ context, size, palette, createRandom, timeMs, interaction }) {
17327
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
17328
- const animationRandom = createRandom('octopus3d-animation-profile');
17329
- const eyeRandom = createRandom('octopus3d-eye-profile');
17330
- const animationPhase = animationRandom() * Math.PI * 2;
17421
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
17331
17422
  const sceneCenterX = size * 0.5;
17332
17423
  const sceneCenterY = size * 0.56;
17333
17424
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -17424,12 +17515,12 @@
17424
17515
  x: -faceEyeSpacing,
17425
17516
  y: faceEyeYOffset,
17426
17517
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
17427
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
17518
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17428
17519
  drawProjectedOrganicEye(context, {
17429
17520
  x: faceEyeSpacing,
17430
17521
  y: faceEyeYOffset,
17431
17522
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
17432
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
17523
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17433
17524
  drawProjectedOrganicMouth(context, [
17434
17525
  {
17435
17526
  x: -mouthHalfWidth,
@@ -17473,14 +17564,28 @@
17473
17564
  /**
17474
17565
  * Draws the soft ground shadow below the octopus.
17475
17566
  *
17567
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
17568
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
17569
+ *
17476
17570
  * @private helper of `octopus3dAvatarVisual`
17477
17571
  */
17478
17572
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
17573
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
17574
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
17575
+ const rx = size * (0.18 + interaction.intensity * 0.02);
17576
+ const ry = size * 0.06;
17479
17577
  context.save();
17480
- context.fillStyle = `${palette.shadow}66`;
17481
- context.filter = `blur(${size * 0.022}px)`;
17578
+ context.translate(cx, cy);
17579
+ context.scale(1, ry / rx);
17580
+ const blurRadius = rx * 1.4;
17581
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
17582
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
17583
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
17584
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
17585
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
17586
+ context.fillStyle = shadowGradient;
17482
17587
  context.beginPath();
17483
- 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);
17588
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
17484
17589
  context.fill();
17485
17590
  context.restore();
17486
17591
  }
@@ -17711,6 +17816,35 @@
17711
17816
  y: -0.6,
17712
17817
  z: 0.98,
17713
17818
  });
17819
+ /**
17820
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
17821
+ *
17822
+ * @private helper of `octopus3d2AvatarVisual`
17823
+ */
17824
+ const octopus3d2StableStateCache = new WeakMap();
17825
+ /**
17826
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
17827
+ *
17828
+ * @private helper of `octopus3d2AvatarVisual`
17829
+ */
17830
+ function getOctopus3d2StableState(createRandom) {
17831
+ const cached = octopus3d2StableStateCache.get(createRandom);
17832
+ if (cached !== undefined) {
17833
+ return cached;
17834
+ }
17835
+ const animationRandom = createRandom('octopus3d2-animation-profile');
17836
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
17837
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
17838
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
17839
+ const state = {
17840
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
17841
+ animationPhase: animationRandom() * Math.PI * 2,
17842
+ leftEyePhaseOffset,
17843
+ rightEyePhaseOffset,
17844
+ };
17845
+ octopus3d2StableStateCache.set(createRandom, state);
17846
+ return state;
17847
+ }
17714
17848
  /**
17715
17849
  * Octopus 3D 2 avatar visual.
17716
17850
  *
@@ -17723,10 +17857,7 @@
17723
17857
  isAnimated: true,
17724
17858
  supportsPointerTracking: true,
17725
17859
  render({ context, size, palette, createRandom, timeMs, interaction }) {
17726
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
17727
- const animationRandom = createRandom('octopus3d2-animation-profile');
17728
- const eyeRandom = createRandom('octopus3d2-eye-profile');
17729
- const animationPhase = animationRandom() * Math.PI * 2;
17860
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
17730
17861
  const sceneCenterX = size * 0.5;
17731
17862
  const sceneCenterY = size * 0.575;
17732
17863
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -17779,8 +17910,8 @@
17779
17910
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
17780
17911
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
17781
17912
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
17782
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
17783
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
17913
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17914
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17784
17915
  drawProjectedOrganicMouth(context, [
17785
17916
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
17786
17917
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -17809,14 +17940,28 @@
17809
17940
  /**
17810
17941
  * Draws the soft floor shadow that anchors the single mesh in the frame.
17811
17942
  *
17943
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
17944
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
17945
+ *
17812
17946
  * @private helper of `octopus3d2AvatarVisual`
17813
17947
  */
17814
17948
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
17949
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
17950
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
17951
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
17952
+ const ry = size * 0.062;
17815
17953
  context.save();
17816
- context.fillStyle = `${palette.shadow}66`;
17817
- context.filter = `blur(${size * 0.024}px)`;
17954
+ context.translate(cx, cy);
17955
+ context.scale(1, ry / rx);
17956
+ const blurRadius = rx * 1.4;
17957
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
17958
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
17959
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
17960
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
17961
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
17962
+ context.fillStyle = shadowGradient;
17818
17963
  context.beginPath();
17819
- 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);
17964
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
17820
17965
  context.fill();
17821
17966
  context.restore();
17822
17967
  }
@@ -17972,6 +18117,40 @@
17972
18117
  * @private helper of `octopus3d3AvatarVisual`
17973
18118
  */
17974
18119
  const OCTOPUS_TENTACLE_COUNT = 8;
18120
+ /**
18121
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
18122
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
18123
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
18124
+ *
18125
+ * @private helper of `octopus3d3AvatarVisual`
18126
+ */
18127
+ const stableStateCache = new WeakMap();
18128
+ /**
18129
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
18130
+ * result on every subsequent call within the same `<Avatar/>` mount.
18131
+ *
18132
+ * @private helper of `octopus3d3AvatarVisual`
18133
+ */
18134
+ function getOctopus3d3StableState(createRandom) {
18135
+ const cached = stableStateCache.get(createRandom);
18136
+ if (cached !== undefined) {
18137
+ return cached;
18138
+ }
18139
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
18140
+ const animationRandom = createRandom('octopus3d3-animation-profile');
18141
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
18142
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
18143
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
18144
+ const state = {
18145
+ morphologyProfile,
18146
+ animationPhase: animationRandom() * Math.PI * 2,
18147
+ leftEyePhaseOffset,
18148
+ rightEyePhaseOffset,
18149
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
18150
+ };
18151
+ stableStateCache.set(createRandom, state);
18152
+ return state;
18153
+ }
17975
18154
  /**
17976
18155
  * Octopus 3D 3 avatar visual.
17977
18156
  *
@@ -17984,11 +18163,7 @@
17984
18163
  isAnimated: true,
17985
18164
  supportsPointerTracking: true,
17986
18165
  render({ context, size, palette, createRandom, timeMs, interaction }) {
17987
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
17988
- const animationRandom = createRandom('octopus3d3-animation-profile');
17989
- const eyeRandom = createRandom('octopus3d3-eye-profile');
17990
- const animationPhase = animationRandom() * Math.PI * 2;
17991
- const tentacleProfiles = createContinuousTentacleProfiles(createRandom, morphologyProfile);
18166
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
17992
18167
  const sceneCenterX = size * 0.5;
17993
18168
  const sceneCenterY = size * 0.535;
17994
18169
  const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
@@ -18065,8 +18240,8 @@
18065
18240
  size,
18066
18241
  palette,
18067
18242
  });
18068
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
18069
- 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);
18243
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
18244
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
18070
18245
  drawProjectedOrganicMouth(context, [
18071
18246
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
18072
18247
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -18117,14 +18292,30 @@
18117
18292
  /**
18118
18293
  * Draws the soft lower shadow that anchors the octopus in the avatar frame.
18119
18294
  *
18295
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
18296
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
18297
+ *
18120
18298
  * @private helper of `octopus3d3AvatarVisual`
18121
18299
  */
18122
18300
  function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
18301
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
18302
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
18303
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
18304
+ const ry = size * 0.06;
18305
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
18306
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
18123
18307
  context.save();
18124
- context.fillStyle = `${palette.shadow}66`;
18125
- context.filter = `blur(${size * 0.025}px)`;
18308
+ context.translate(cx, cy);
18309
+ context.scale(1, ry / rx);
18310
+ const blurRadius = rx * 1.4;
18311
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
18312
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
18313
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
18314
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
18315
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
18316
+ context.fillStyle = shadowGradient;
18126
18317
  context.beginPath();
18127
- 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);
18318
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
18128
18319
  context.fill();
18129
18320
  context.restore();
18130
18321
  }
@@ -30500,6 +30691,7 @@
30500
30691
  noUi: options.noUi,
30501
30692
  thinkingLevel: options.thinkingLevel,
30502
30693
  waitForUser: false,
30694
+ waitBetweenPrompts: 0,
30503
30695
  noCommit: true,
30504
30696
  ignoreGitChanges: true,
30505
30697
  normalizeLineEndings: false,
@@ -30630,6 +30822,48 @@
30630
30822
  return Book.parse(agentSource).agentName.trim() || 'Agent';
30631
30823
  }
30632
30824
 
30825
+ // Note: [💞] Ignore a discrepancy between file name and entity name
30826
+ /**
30827
+ * All CLI harness names supported by `CliAgent` and `ptbk agent exec`.
30828
+ *
30829
+ * @public exported from `@promptbook/node`
30830
+ */
30831
+ const CLI_AGENT_HARNESS_NAMES = [
30832
+ 'openai-codex',
30833
+ 'github-copilot',
30834
+ 'cline',
30835
+ 'claude-code',
30836
+ 'opencode',
30837
+ 'gemini',
30838
+ ];
30839
+ /**
30840
+ * All supported thinking-level values for CLI coding-agent runners.
30841
+ *
30842
+ * @public exported from `@promptbook/node`
30843
+ */
30844
+ const CLI_AGENT_THINKING_LEVEL_VALUES = ['low', 'medium', 'high', 'xhigh'];
30845
+ /**
30846
+ * Environment variable used as the default runner identifier when `--harness` is omitted or not set in `CliAgent`.
30847
+ *
30848
+ * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`)
30849
+ * so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
30850
+ *
30851
+ * @public exported from `@promptbook/node`
30852
+ */
30853
+ const PTBK_HARNESS_ENV = 'PTBK_HARNESS';
30854
+ /**
30855
+ * Environment variable used as the default runner model when `--model` is omitted or not set in `CliAgent`.
30856
+ *
30857
+ * @public exported from `@promptbook/node`
30858
+ */
30859
+ const PTBK_MODEL_ENV = 'PTBK_MODEL';
30860
+ /**
30861
+ * Environment variable used as the default thinking level when `--thinking-level` is omitted or not set in `CliAgent`.
30862
+ *
30863
+ * @public exported from `@promptbook/node`
30864
+ */
30865
+ const PTBK_THINKING_LEVEL_ENV = 'PTBK_THINKING_LEVEL';
30866
+
30633
30867
  /**
30634
30868
  * Default non-interactive mode used by `CliAgent`.
30635
30869
  *
@@ -30642,6 +30876,9 @@
30642
30876
  * It uses the same harnesses and execution path as `ptbk agent exec`, running the runner
30643
30877
  * in-process instead of spawning a separate CLI process.
30644
30878
  *
30879
+ * When no `harness` is provided in the constructor or per-run options, `CliAgent` falls back
30880
+ * to the `PTBK_HARNESS` environment variable, mirroring `ptbk agent exec` behavior.
30881
+ *
30645
30882
  * @public exported from `@promptbook/node`
30646
30883
  */
30647
30884
  class CliAgent {
@@ -30657,7 +30894,7 @@
30657
30894
  * @returns Final agent answer.
30658
30895
  */
30659
30896
  async run(message, options = {}) {
30660
- var _a, _b;
30897
+ var _a, _b, _c, _d, _e, _f;
30661
30898
  const normalizedMessage = message.trim();
30662
30899
  if (!normalizedMessage) {
30663
30900
  throw new NotAllowed('Pass a non-empty message to `CliAgent.run(...)`.');
@@ -30665,15 +30902,26 @@
30665
30902
  const resolvedSource = await resolveBookNodeAgentSource(this.options);
30666
30903
  const agentPath = await this.resolveExecutableAgentPath(resolvedSource);
30667
30904
  const mergedOptions = mergeCliAgentRunOptions(this.options, options);
30905
+ const harness = (_a = mergedOptions.harness) !== null && _a !== void 0 ? _a : resolveCliAgentHarnessFromEnv();
30906
+ if (!harness) {
30907
+ throw new NotAllowed(spaceTrim(`
30908
+ No harness specified for \`CliAgent\`. Pass \`harness\` in the constructor options or per-run options,
30909
+ or set the \`${PTBK_HARNESS_ENV}\` environment variable.
30910
+
30911
+ Available harnesses: ${CLI_AGENT_HARNESS_NAMES.join(', ')}
30912
+
30913
+ Example: \`PTBK_HARNESS=claude-code\`
30914
+ `));
30915
+ }
30668
30916
  const result = await executeAgentChatTurn({
30669
30917
  agentPath,
30670
30918
  messages: [{ sender: 'USER', content: normalizedMessage }],
30671
- agentName: mergedOptions.harness,
30672
- model: mergedOptions.model,
30673
- noUi: (_a = mergedOptions.noUi) !== null && _a !== void 0 ? _a : DEFAULT_CLI_AGENT_IS_NO_UI,
30674
- thinkingLevel: mergedOptions.thinkingLevel,
30675
- allowCredits: (_b = mergedOptions.allowCredits) !== null && _b !== void 0 ? _b : false,
30676
- isVerbose: false,
30919
+ agentName: harness,
30920
+ model: (_b = mergedOptions.model) !== null && _b !== void 0 ? _b : process.env[PTBK_MODEL_ENV],
30921
+ noUi: (_c = mergedOptions.noUi) !== null && _c !== void 0 ? _c : DEFAULT_CLI_AGENT_IS_NO_UI,
30922
+ thinkingLevel: (_d = mergedOptions.thinkingLevel) !== null && _d !== void 0 ? _d : resolveCliAgentThinkingLevelFromEnv(),
30923
+ allowCredits: (_e = mergedOptions.allowCredits) !== null && _e !== void 0 ? _e : false,
30924
+ isVerbose: (_f = mergedOptions.isVerbose) !== null && _f !== void 0 ? _f : false,
30677
30925
  context: mergedOptions.context,
30678
30926
  currentWorkingDirectory: resolvedSource.currentWorkingDirectory,
30679
30927
  });
@@ -30702,16 +30950,55 @@
30702
30950
  * @private internal utility of `CliAgent`
30703
30951
  */
30704
30952
  function mergeCliAgentRunOptions(defaults, overrides) {
30705
- var _a, _b, _c, _d, _e, _f;
30953
+ var _a, _b, _c, _d, _e, _f, _g;
30706
30954
  return {
30707
30955
  allowCredits: (_a = overrides.allowCredits) !== null && _a !== void 0 ? _a : defaults.allowCredits,
30708
30956
  context: (_b = overrides.context) !== null && _b !== void 0 ? _b : defaults.context,
30709
30957
  harness: (_c = overrides.harness) !== null && _c !== void 0 ? _c : defaults.harness,
30710
- model: (_d = overrides.model) !== null && _d !== void 0 ? _d : defaults.model,
30711
- noUi: (_e = overrides.noUi) !== null && _e !== void 0 ? _e : defaults.noUi,
30712
- thinkingLevel: (_f = overrides.thinkingLevel) !== null && _f !== void 0 ? _f : defaults.thinkingLevel,
30958
+ isVerbose: (_d = overrides.isVerbose) !== null && _d !== void 0 ? _d : defaults.isVerbose,
30959
+ model: (_e = overrides.model) !== null && _e !== void 0 ? _e : defaults.model,
30960
+ noUi: (_f = overrides.noUi) !== null && _f !== void 0 ? _f : defaults.noUi,
30961
+ thinkingLevel: (_g = overrides.thinkingLevel) !== null && _g !== void 0 ? _g : defaults.thinkingLevel,
30713
30962
  };
30714
30963
  }
30964
+ /**
30965
+ * Reads and validates the harness name from the `PTBK_HARNESS` environment variable.
30966
+ *
30967
+ * @private internal utility of `CliAgent`
30968
+ */
30969
+ function resolveCliAgentHarnessFromEnv() {
30970
+ const envValue = process.env[PTBK_HARNESS_ENV];
30971
+ if (!envValue) {
30972
+ return undefined;
30973
+ }
30974
+ if (!CLI_AGENT_HARNESS_NAMES.includes(envValue)) {
30975
+ throw new NotAllowed(spaceTrim(`
30976
+ Invalid value for \`${PTBK_HARNESS_ENV}\` environment variable: \`${envValue}\`
30977
+
30978
+ Must be one of: ${CLI_AGENT_HARNESS_NAMES.join(', ')}
30979
+ `));
30980
+ }
30981
+ return envValue;
30982
+ }
30983
+ /**
30984
+ * Reads and validates the thinking level from the `PTBK_THINKING_LEVEL` environment variable.
30985
+ *
30986
+ * @private internal utility of `CliAgent`
30987
+ */
30988
+ function resolveCliAgentThinkingLevelFromEnv() {
30989
+ const envValue = process.env[PTBK_THINKING_LEVEL_ENV];
30990
+ if (!envValue) {
30991
+ return undefined;
30992
+ }
30993
+ if (!CLI_AGENT_THINKING_LEVEL_VALUES.includes(envValue)) {
30994
+ throw new NotAllowed(spaceTrim(`
30995
+ Invalid value for \`${PTBK_THINKING_LEVEL_ENV}\` environment variable: \`${envValue}\`
30996
+
30997
+ Must be one of: ${CLI_AGENT_THINKING_LEVEL_VALUES.join(', ')}
30998
+ `));
30999
+ }
31000
+ return envValue;
31001
+ }
30715
31002
  /**
30716
31003
  * Creates the stable temporary path used when `CliAgent` is initialized from in-memory Book source.
30717
31004
  *
@@ -47646,10 +47933,15 @@
47646
47933
  exports.AGENT_MESSAGES_DIRECTORY_PATH = AGENT_MESSAGES_DIRECTORY_PATH;
47647
47934
  exports.AGENT_QUEUED_MESSAGES_DIRECTORY_PATH = AGENT_QUEUED_MESSAGES_DIRECTORY_PATH;
47648
47935
  exports.BOOK_LANGUAGE_VERSION = BOOK_LANGUAGE_VERSION;
47936
+ exports.CLI_AGENT_HARNESS_NAMES = CLI_AGENT_HARNESS_NAMES;
47937
+ exports.CLI_AGENT_THINKING_LEVEL_VALUES = CLI_AGENT_THINKING_LEVEL_VALUES;
47649
47938
  exports.CliAgent = CliAgent;
47650
47939
  exports.FileCacheStorage = FileCacheStorage;
47651
47940
  exports.LiteAgent = LiteAgent;
47652
47941
  exports.PROMPTBOOK_ENGINE_VERSION = PROMPTBOOK_ENGINE_VERSION;
47942
+ exports.PTBK_HARNESS_ENV = PTBK_HARNESS_ENV;
47943
+ exports.PTBK_MODEL_ENV = PTBK_MODEL_ENV;
47944
+ exports.PTBK_THINKING_LEVEL_ENV = PTBK_THINKING_LEVEL_ENV;
47653
47945
  exports.createPipelineCollectionFromDirectory = createPipelineCollectionFromDirectory;
47654
47946
  exports.getAllCommitmentsToolFunctionsForNode = getAllCommitmentsToolFunctionsForNode;
47655
47947