@promptbook/cli 0.113.0-10 → 0.113.0-11

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 (40) hide show
  1. package/apps/agents-server/src/app/admin/{code-runners/CodeRunnersClient.tsx → harness-auth/HarnessAuthClient.tsx} +98 -65
  2. package/apps/agents-server/src/app/admin/{code-runners → harness-auth}/page.tsx +4 -4
  3. package/apps/agents-server/src/app/api/admin/{code-runners → harness-auth}/authentication/route.ts +17 -17
  4. package/apps/agents-server/src/app/api/admin/{code-runners → harness-auth}/route.ts +13 -13
  5. package/apps/agents-server/src/app/layout.tsx +16 -0
  6. package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +32 -24
  7. package/apps/agents-server/src/components/Footer/Footer.tsx +38 -15
  8. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +5 -4
  9. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +4 -1
  10. package/apps/agents-server/src/constants/harnessAuthRoutes.ts +20 -0
  11. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -1
  12. package/apps/agents-server/src/languages/translations/czech.yaml +2 -2
  13. package/apps/agents-server/src/languages/translations/english.yaml +1 -1
  14. package/apps/agents-server/src/utils/{codeRunnerAuthentication.ts → harnessAuthentication.ts} +72 -56
  15. package/apps/agents-server/src/utils/{codeRunnerConfiguration.ts → harnessConfiguration.ts} +18 -12
  16. package/apps/agents-server/src/utils/taskTerminal/resolveAdminTaskTerminalSession.ts +28 -5
  17. package/apps/agents-server/src/utils/vpsConfiguration.ts +3 -3
  18. package/apps/agents-server/src/utils/vpsSelfUpdate/readAgentsServerFooterVersion.ts +335 -0
  19. package/apps/agents-server/src/utils/vpsSelfUpdate/vpsSelfUpdateJobHistory.ts +107 -4
  20. package/esm/index.es.js +697 -591
  21. package/esm/index.es.js.map +1 -1
  22. package/esm/scripts/run-codex-prompts/common/sleepWithCountdown.d.ts +1 -0
  23. package/esm/scripts/run-codex-prompts/common/waitUntilWorldTimeDeadline.d.ts +17 -0
  24. package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +3 -16
  25. package/esm/src/utils/agents/terminalAgentAvatarVisual.d.ts +94 -0
  26. package/esm/src/utils/agents/terminalAgentAvatarVisual.test.d.ts +1 -0
  27. package/esm/src/version.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/src/other/templates/getTemplatesPipelineCollection.ts +790 -807
  30. package/src/utils/agents/terminalAgentAvatarVisual.ts +261 -0
  31. package/src/version.ts +2 -2
  32. package/src/versions.txt +1 -0
  33. package/umd/index.umd.js +697 -591
  34. package/umd/index.umd.js.map +1 -1
  35. package/umd/scripts/run-codex-prompts/common/sleepWithCountdown.d.ts +1 -0
  36. package/umd/scripts/run-codex-prompts/common/waitUntilWorldTimeDeadline.d.ts +17 -0
  37. package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +3 -16
  38. package/umd/src/utils/agents/terminalAgentAvatarVisual.d.ts +94 -0
  39. package/umd/src/utils/agents/terminalAgentAvatarVisual.test.d.ts +1 -0
  40. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -59,7 +59,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
59
59
  * @generated
60
60
  * @see https://github.com/webgptorg/promptbook
61
61
  */
62
- const PROMPTBOOK_ENGINE_VERSION = '0.113.0-10';
62
+ const PROMPTBOOK_ENGINE_VERSION = '0.113.0-11';
63
63
  /**
64
64
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
65
65
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -28105,6 +28105,47 @@ async function $runGoScriptWithOutput(options) {
28105
28105
  });
28106
28106
  }
28107
28107
 
28108
+ /**
28109
+ * Minimum timer duration used to avoid a zero-millisecond polling loop.
28110
+ */
28111
+ const MINIMUM_WORLD_TIME_WAIT_POLL_INTERVAL_MS = 1;
28112
+ /**
28113
+ * Waits until one wall-clock deadline has passed.
28114
+ *
28115
+ * The remaining time is recalculated from `Date.now()` after every poll and after every tick callback.
28116
+ * This makes waits elapse while the process is paused at a checkpoint or the computer is asleep.
28117
+ *
28118
+ * @private internal utility of `ptbk coder` wait handling
28119
+ */
28120
+ async function waitUntilWorldTimeDeadline(options) {
28121
+ const { deadlineTimeMs, pollIntervalMs, onTick } = options;
28122
+ const normalizedPollIntervalMs = Math.max(MINIMUM_WORLD_TIME_WAIT_POLL_INTERVAL_MS, pollIntervalMs);
28123
+ while (true) {
28124
+ const remainingDurationMs = getRemainingDurationMs(deadlineTimeMs);
28125
+ if (remainingDurationMs <= 0) {
28126
+ return;
28127
+ }
28128
+ await (onTick === null || onTick === void 0 ? void 0 : onTick(remainingDurationMs));
28129
+ const remainingDurationAfterTickMs = getRemainingDurationMs(deadlineTimeMs);
28130
+ if (remainingDurationAfterTickMs <= 0) {
28131
+ return;
28132
+ }
28133
+ await waitForMilliseconds(Math.min(normalizedPollIntervalMs, remainingDurationAfterTickMs));
28134
+ }
28135
+ }
28136
+ /**
28137
+ * Returns the remaining wall-clock duration until a timestamp.
28138
+ */
28139
+ function getRemainingDurationMs(deadlineTimeMs) {
28140
+ return Math.max(0, deadlineTimeMs - Date.now());
28141
+ }
28142
+ /**
28143
+ * Waits for one short polling interval.
28144
+ */
28145
+ async function waitForMilliseconds(durationMs) {
28146
+ await new Promise((resolve) => setTimeout(resolve, durationMs));
28147
+ }
28148
+
28108
28149
  /**
28109
28150
  * Base delimiter used for passing large prompts through stdin.
28110
28151
  */
@@ -28514,36 +28555,32 @@ class ClaudeCodeRunner {
28514
28555
  * Waits until the Claude Code session can be resumed, keeping terminal status clear.
28515
28556
  */
28516
28557
  async function waitForClaudeCodeSessionLimitReset(sessionLimit, resurrectionCount, options) {
28517
- var _a, _b, _c;
28558
+ var _a, _b;
28518
28559
  const delayMs = getClaudeCodeSessionLimitDelayMs(sessionLimit);
28560
+ const resetDeadlineTimeMs = Date.now() + delayMs;
28519
28561
  const sessionLabel = formatClaudeCodeSessionIdForDisplay(sessionLimit.sessionId);
28520
28562
  const resetSummary = formatClaudeCodeSessionLimitForDisplay(sessionLimit);
28521
28563
  if ((_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true) {
28522
28564
  console.warn(colors.yellow(`[claude-code] Session limit detected for ${sessionLimit.sessionId}. Resurrection #${resurrectionCount} will resume with --resume after ${formatDurationMs(delayMs)}. ${resetSummary}`));
28523
28565
  }
28524
- let remainingDelayMs = delayMs;
28525
- while (remainingDelayMs > 0) {
28526
- await ((_b = options.waitForPauseCheckpoint) === null || _b === void 0 ? void 0 : _b.call(options, {
28527
- checkpointLabel: 'the Claude Code session limit reset',
28528
- phase: 'waiting',
28529
- statusMessage: `Claude Code session ${sessionLabel} hit its limit; resurrection #${resurrectionCount} resumes in ${formatDurationMs(remainingDelayMs)}`,
28530
- }));
28531
- const currentDelayMs = Math.min(CLAUDE_CODE_SESSION_RESURRECTION_POLL_MS, remainingDelayMs);
28532
- await waitFor$1(currentDelayMs);
28533
- remainingDelayMs -= currentDelayMs;
28534
- }
28535
- await ((_c = options.waitForPauseCheckpoint) === null || _c === void 0 ? void 0 : _c.call(options, {
28566
+ await waitUntilWorldTimeDeadline({
28567
+ deadlineTimeMs: resetDeadlineTimeMs,
28568
+ pollIntervalMs: CLAUDE_CODE_SESSION_RESURRECTION_POLL_MS,
28569
+ onTick: async (remainingDelayMs) => {
28570
+ var _a;
28571
+ await ((_a = options.waitForPauseCheckpoint) === null || _a === void 0 ? void 0 : _a.call(options, {
28572
+ checkpointLabel: 'the Claude Code session limit reset',
28573
+ phase: 'waiting',
28574
+ statusMessage: `Claude Code session ${sessionLabel} hit its limit; resurrection #${resurrectionCount} resumes in ${formatDurationMs(Math.min(remainingDelayMs, delayMs))}`,
28575
+ }));
28576
+ },
28577
+ });
28578
+ await ((_b = options.waitForPauseCheckpoint) === null || _b === void 0 ? void 0 : _b.call(options, {
28536
28579
  checkpointLabel: 'resurrecting the Claude Code session with --resume',
28537
28580
  phase: 'running',
28538
28581
  statusMessage: `Resurrecting Claude Code session ${sessionLabel} with --resume`,
28539
28582
  }));
28540
28583
  }
28541
- /**
28542
- * Waits for a fixed amount of milliseconds.
28543
- */
28544
- async function waitFor$1(delayMs) {
28545
- await new Promise((resolve) => setTimeout(resolve, delayMs));
28546
- }
28547
28584
  /**
28548
28585
  * Formats a Claude Code session id for compact terminal status lines.
28549
28586
  */
@@ -29427,12 +29464,6 @@ const RATE_LIMIT_BACKOFF_MAX_MS = 30 * 60 * 1000;
29427
29464
  * Randomized delay proportion added/subtracted for retry jitter.
29428
29465
  */
29429
29466
  const RATE_LIMIT_BACKOFF_JITTER_RATIO = 0.15;
29430
- /**
29431
- * Waits for one given amount of milliseconds.
29432
- */
29433
- async function waitFor(delayMs) {
29434
- await new Promise((resolve) => setTimeout(resolve, delayMs));
29435
- }
29436
29467
  /**
29437
29468
  * Formats a delay value into a concise `xh ym zs` style label.
29438
29469
  */
@@ -29522,13 +29553,18 @@ class OpenAiCodexRunner {
29522
29553
  throw error;
29523
29554
  }
29524
29555
  const delayMs = this.rateLimitBackoff.nextDelayMs();
29525
- const retryAt = new Date(Date.now() + delayMs).toISOString();
29556
+ const retryDeadlineTimeMs = Date.now() + delayMs;
29557
+ const retryAt = new Date(retryDeadlineTimeMs).toISOString();
29526
29558
  const retryIndex = this.rateLimitBackoff.retryCount;
29527
29559
  const summary = extractFailureSummary(details);
29528
29560
  if ((_b = options.shouldPrintLiveOutput) !== null && _b !== void 0 ? _b : true) {
29529
29561
  console.warn(colors.yellow(`[codex] Rate limit/quota detected (${summary}). Retry #${retryIndex} in ${formatDelay(delayMs)} at ${retryAt}.`));
29530
29562
  }
29531
- await waitForRetryDelay(delayMs, options);
29563
+ await waitForRetryDelay({
29564
+ delayMs,
29565
+ retryDeadlineTimeMs,
29566
+ promptRunOptions: options,
29567
+ });
29532
29568
  }
29533
29569
  }
29534
29570
  }
@@ -29536,20 +29572,21 @@ class OpenAiCodexRunner {
29536
29572
  /**
29537
29573
  * Waits for the next Codex retry while polling for requested pause checkpoints.
29538
29574
  */
29539
- async function waitForRetryDelay(delayMs, options) {
29540
- var _a;
29541
- let remainingDelayMs = delayMs;
29542
- while (remainingDelayMs > 0) {
29543
- const remainingDelayLabel = formatDelay(remainingDelayMs);
29544
- await ((_a = options.waitForPauseCheckpoint) === null || _a === void 0 ? void 0 : _a.call(options, {
29545
- checkpointLabel: 'the next OpenAI Codex retry after rate limit',
29546
- phase: 'running',
29547
- statusMessage: `Waiting ${remainingDelayLabel} before retrying OpenAI Codex`,
29548
- }));
29549
- const currentDelayMs = Math.min(RATE_LIMIT_BACKOFF_POLL_MS, remainingDelayMs);
29550
- await waitFor(currentDelayMs);
29551
- remainingDelayMs -= currentDelayMs;
29552
- }
29575
+ async function waitForRetryDelay(options) {
29576
+ const { delayMs, retryDeadlineTimeMs, promptRunOptions } = options;
29577
+ await waitUntilWorldTimeDeadline({
29578
+ deadlineTimeMs: retryDeadlineTimeMs,
29579
+ pollIntervalMs: RATE_LIMIT_BACKOFF_POLL_MS,
29580
+ onTick: async (remainingDelayMs) => {
29581
+ var _a;
29582
+ const remainingDelayLabel = formatDelay(Math.min(remainingDelayMs, delayMs));
29583
+ await ((_a = promptRunOptions.waitForPauseCheckpoint) === null || _a === void 0 ? void 0 : _a.call(promptRunOptions, {
29584
+ checkpointLabel: 'the next OpenAI Codex retry after rate limit',
29585
+ phase: 'running',
29586
+ statusMessage: `Waiting ${remainingDelayLabel} before retrying OpenAI Codex`,
29587
+ }));
29588
+ },
29589
+ });
29553
29590
  }
29554
29591
 
29555
29592
  /**
@@ -30050,12 +30087,597 @@ function buildCoderRunOctopusVisual(options) {
30050
30087
  .map((line) => centerAnsiText(line, options.totalWidth));
30051
30088
  }
30052
30089
 
30090
+ /**
30091
+ * Stable zeroed interaction state used by non-interactive render paths.
30092
+ *
30093
+ * @private utility of the avatar rendering system
30094
+ */
30095
+ const IDLE_AVATAR_INTERACTION_STATE = {
30096
+ gazeX: 0,
30097
+ gazeY: 0,
30098
+ bodyOffsetX: 0,
30099
+ bodyOffsetY: 0,
30100
+ intensity: 0,
30101
+ isPointerActive: false,
30102
+ pointerType: 'idle',
30103
+ };
30104
+ /**
30105
+ * Returns the neutral interaction state used by static/server-side renders.
30106
+ *
30107
+ * @returns Zeroed interaction state.
30108
+ *
30109
+ * @private utility of the avatar rendering system
30110
+ */
30111
+ function createIdleAvatarInteractionState() {
30112
+ return IDLE_AVATAR_INTERACTION_STATE;
30113
+ }
30114
+
30115
+ /**
30116
+ * Resolves the stable avatar render inputs reused across multiple frames.
30117
+ *
30118
+ * @param options Avatar identity and visual selection.
30119
+ * @returns Stable render data ready to be used by `renderAvatarVisual`.
30120
+ *
30121
+ * @private shared helper for canvas avatar rendering
30122
+ */
30123
+ function resolveAvatarRenderDefinition(options) {
30124
+ const avatarDefinition = normalizeAvatarDefinition(options.avatarDefinition);
30125
+ const surface = options.surface || 'framed';
30126
+ return {
30127
+ avatarDefinition,
30128
+ avatarVisual: getAvatarVisualById(options.visualId),
30129
+ surface,
30130
+ palette: createAvatarPalette(avatarDefinition, surface),
30131
+ createRandom: createAvatarRandomFactory(avatarDefinition),
30132
+ };
30133
+ }
30134
+ /**
30135
+ * Renders one deterministic avatar frame into the provided canvas.
30136
+ *
30137
+ * @param options Rendering options.
30138
+ * @param resolvedAvatarRenderDefinition Optional stable render data reused between frames.
30139
+ *
30140
+ * @private shared helper for canvas avatar rendering
30141
+ */
30142
+ function renderAvatarVisual(options, resolvedAvatarRenderDefinition) {
30143
+ const resolvedRenderDefinition = resolvedAvatarRenderDefinition ||
30144
+ resolveAvatarRenderDefinition({
30145
+ avatarDefinition: options.avatarDefinition,
30146
+ visualId: options.visualId,
30147
+ surface: options.surface,
30148
+ });
30149
+ const context = options.canvas.getContext('2d');
30150
+ if (!context) {
30151
+ throw new Error('2D canvas rendering context is unavailable.');
30152
+ }
30153
+ prepareAvatarCanvas(options.canvas, context, options.size, options.devicePixelRatio || 1);
30154
+ resolvedRenderDefinition.avatarVisual.render({
30155
+ canvas: options.canvas,
30156
+ context,
30157
+ size: options.size,
30158
+ devicePixelRatio: options.devicePixelRatio || 1,
30159
+ timeMs: options.timeMs,
30160
+ avatarDefinition: resolvedRenderDefinition.avatarDefinition,
30161
+ palette: resolvedRenderDefinition.palette,
30162
+ createRandom: resolvedRenderDefinition.createRandom,
30163
+ surface: resolvedRenderDefinition.surface,
30164
+ interaction: options.interaction || createIdleAvatarInteractionState(),
30165
+ });
30166
+ }
30167
+
30168
+ /**
30169
+ * Default alpha channel value below which a half-cell is rendered as terminal background.
30170
+ *
30171
+ * @private within the repository
30172
+ */
30173
+ const DEFAULT_ALPHA_THRESHOLD = 32;
30174
+ /**
30175
+ * Number of channels per pixel in an RGBA buffer.
30176
+ *
30177
+ * @private within the repository
30178
+ */
30179
+ const RGBA_CHANNEL_COUNT = 4;
30180
+ /**
30181
+ * Upper half block character - foreground paints the top pixel, background paints the bottom pixel.
30182
+ *
30183
+ * @private within the repository
30184
+ */
30185
+ const UPPER_HALF_BLOCK = '▀'; // <- ▀
30186
+ /**
30187
+ * Lower half block character - foreground paints the bottom pixel while the top pixel stays transparent.
30188
+ *
30189
+ * @private within the repository
30190
+ */
30191
+ const LOWER_HALF_BLOCK = '▄'; // <- ▄
30192
+ /**
30193
+ * ANSI escape sequence that resets all colors and attributes.
30194
+ *
30195
+ * @private within the repository
30196
+ */
30197
+ const ANSI_RESET = '\u001b[0m';
30198
+ /**
30199
+ * Maximum spread between RGB channels for a color to be treated as (nearly) achromatic gray.
30200
+ *
30201
+ * @private within the repository
30202
+ */
30203
+ const ANSI_256_ACHROMATIC_CHANNEL_SPREAD = 12;
30204
+ /**
30205
+ * Gray level above which an achromatic color maps to the pure white color-cube entry.
30206
+ *
30207
+ * @private within the repository
30208
+ */
30209
+ const ANSI_256_NEAR_WHITE_GRAY_LEVEL = 246;
30210
+ /**
30211
+ * Index of pure white inside the 6×6×6 ANSI color cube.
30212
+ *
30213
+ * @private within the repository
30214
+ */
30215
+ const ANSI_256_WHITE_INDEX = 231;
30216
+ /**
30217
+ * Brightness of the lightest entry of the ANSI 256 grayscale ramp.
30218
+ *
30219
+ * @private within the repository
30220
+ */
30221
+ const ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL = 238;
30222
+ /**
30223
+ * Number of grayscale ramp steps above its first entry (ANSI indexes 232-255).
30224
+ *
30225
+ * @private within the repository
30226
+ */
30227
+ const ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN = 23;
30228
+ /**
30229
+ * Converts raw RGBA image pixels into colored ASCII art for ANSI terminals.
30230
+ *
30231
+ * This is the universal image-to-terminal technique used across the repository:
30232
+ * every output character cell covers a rectangular region of source pixels which is
30233
+ * split into a top and bottom half; each half is area-averaged and rendered with
30234
+ * half-block characters (`▀` / `▄`) so one character shows two "pixels" vertically.
30235
+ * Transparent halves keep the terminal background so non-rectangular images
30236
+ * (for example rounded avatar cards) compose naturally into any terminal UI.
30237
+ *
30238
+ * @param options Source pixels, output grid size, and ANSI color depth.
30239
+ * @returns One ANSI-colored string per output row, each ending with a color reset.
30240
+ *
30241
+ * @private within the repository
30242
+ */
30243
+ function convertImageDataToAsciiArt(options) {
30244
+ const { imageData, columns, rows, colorDepth = 'TRUE_COLOR', alphaThreshold = DEFAULT_ALPHA_THRESHOLD } = options;
30245
+ if (!Number.isInteger(columns) || columns <= 0 || !Number.isInteger(rows) || rows <= 0) {
30246
+ throw new UnexpectedError(spaceTrim$1(`
30247
+ ASCII-art grid size is invalid.
30248
+
30249
+ Both \`columns\` and \`rows\` must be positive integers but \`${columns}\` × \`${rows}\` was requested.
30250
+ `));
30251
+ }
30252
+ if (imageData.width <= 0 ||
30253
+ imageData.height <= 0 ||
30254
+ imageData.data.length < imageData.width * imageData.height * RGBA_CHANNEL_COUNT) {
30255
+ throw new UnexpectedError(spaceTrim$1(`
30256
+ ASCII-art source image data is invalid.
30257
+
30258
+ Expected a RGBA buffer of at least \`${imageData.width} × ${imageData.height} × ${RGBA_CHANNEL_COUNT}\` bytes
30259
+ but got \`${imageData.data.length}\` bytes.
30260
+ `));
30261
+ }
30262
+ const halfCellRowCount = rows * 2;
30263
+ const asciiArtLines = [];
30264
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
30265
+ let line = '';
30266
+ let currentForegroundCode = undefined;
30267
+ let currentBackgroundCode = undefined;
30268
+ for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
30269
+ const topHalfColor = computeHalfCellColor(imageData, columnIndex, rowIndex * 2, columns, halfCellRowCount, alphaThreshold);
30270
+ const bottomHalfColor = computeHalfCellColor(imageData, columnIndex, rowIndex * 2 + 1, columns, halfCellRowCount, alphaThreshold);
30271
+ let character;
30272
+ let nextForegroundCode;
30273
+ let nextBackgroundCode;
30274
+ if (topHalfColor.isOpaque && bottomHalfColor.isOpaque) {
30275
+ character = UPPER_HALF_BLOCK;
30276
+ nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
30277
+ nextBackgroundCode = createBackgroundColorCode(bottomHalfColor, colorDepth);
30278
+ }
30279
+ else if (topHalfColor.isOpaque) {
30280
+ character = UPPER_HALF_BLOCK;
30281
+ nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
30282
+ nextBackgroundCode = undefined;
30283
+ }
30284
+ else if (bottomHalfColor.isOpaque) {
30285
+ character = LOWER_HALF_BLOCK;
30286
+ nextForegroundCode = createForegroundColorCode(bottomHalfColor, colorDepth);
30287
+ nextBackgroundCode = undefined;
30288
+ }
30289
+ else {
30290
+ character = ' ';
30291
+ nextForegroundCode = undefined;
30292
+ nextBackgroundCode = undefined;
30293
+ }
30294
+ if (nextForegroundCode !== currentForegroundCode || nextBackgroundCode !== currentBackgroundCode) {
30295
+ // Note: A reset is required whenever a previously set color must be cleared,
30296
+ // otherwise stale background color would bleed into transparent cells.
30297
+ const isResetNeeded = (currentForegroundCode !== undefined && nextForegroundCode === undefined) ||
30298
+ (currentBackgroundCode !== undefined && nextBackgroundCode === undefined);
30299
+ if (isResetNeeded) {
30300
+ line += ANSI_RESET;
30301
+ currentForegroundCode = undefined;
30302
+ currentBackgroundCode = undefined;
30303
+ }
30304
+ if (nextForegroundCode !== undefined && nextForegroundCode !== currentForegroundCode) {
30305
+ line += nextForegroundCode;
30306
+ }
30307
+ if (nextBackgroundCode !== undefined && nextBackgroundCode !== currentBackgroundCode) {
30308
+ line += nextBackgroundCode;
30309
+ }
30310
+ currentForegroundCode = nextForegroundCode;
30311
+ currentBackgroundCode = nextBackgroundCode;
30312
+ }
30313
+ line += character;
30314
+ }
30315
+ if (currentForegroundCode !== undefined || currentBackgroundCode !== undefined) {
30316
+ line += ANSI_RESET;
30317
+ }
30318
+ asciiArtLines.push(line);
30319
+ }
30320
+ return asciiArtLines;
30321
+ }
30322
+ /**
30323
+ * Computes the area-averaged color of one half-cell of the output grid.
30324
+ *
30325
+ * Color channels are alpha-weighted so semi-transparent edge pixels do not darken towards black.
30326
+ *
30327
+ * @private helper of `convertImageDataToAsciiArt`
30328
+ */
30329
+ function computeHalfCellColor(imageData, columnIndex, halfCellRowIndex, columns, halfCellRowCount, alphaThreshold) {
30330
+ const startX = Math.floor((columnIndex * imageData.width) / columns);
30331
+ const endX = Math.max(startX + 1, Math.floor(((columnIndex + 1) * imageData.width) / columns));
30332
+ const startY = Math.floor((halfCellRowIndex * imageData.height) / halfCellRowCount);
30333
+ const endY = Math.max(startY + 1, Math.floor(((halfCellRowIndex + 1) * imageData.height) / halfCellRowCount));
30334
+ let redSum = 0;
30335
+ let greenSum = 0;
30336
+ let blueSum = 0;
30337
+ let alphaSum = 0;
30338
+ let sampledPixelCount = 0;
30339
+ for (let y = startY; y < endY && y < imageData.height; y++) {
30340
+ for (let x = startX; x < endX && x < imageData.width; x++) {
30341
+ const pixelOffset = (y * imageData.width + x) * RGBA_CHANNEL_COUNT;
30342
+ const alpha = imageData.data[pixelOffset + 3];
30343
+ redSum += imageData.data[pixelOffset] * alpha;
30344
+ greenSum += imageData.data[pixelOffset + 1] * alpha;
30345
+ blueSum += imageData.data[pixelOffset + 2] * alpha;
30346
+ alphaSum += alpha;
30347
+ sampledPixelCount++;
30348
+ }
30349
+ }
30350
+ const averageAlpha = sampledPixelCount === 0 ? 0 : alphaSum / sampledPixelCount;
30351
+ if (averageAlpha < alphaThreshold || alphaSum === 0) {
30352
+ return { red: 0, green: 0, blue: 0, isOpaque: false };
30353
+ }
30354
+ return {
30355
+ red: Math.round(redSum / alphaSum),
30356
+ green: Math.round(greenSum / alphaSum),
30357
+ blue: Math.round(blueSum / alphaSum),
30358
+ isOpaque: true,
30359
+ };
30360
+ }
30361
+ /**
30362
+ * Creates the ANSI escape code that sets the foreground color of following characters.
30363
+ *
30364
+ * @private helper of `convertImageDataToAsciiArt`
30365
+ */
30366
+ function createForegroundColorCode(color, colorDepth) {
30367
+ if (colorDepth === 'TRUE_COLOR') {
30368
+ return `\u001b[38;2;${color.red};${color.green};${color.blue}m`;
30369
+ }
30370
+ return `\u001b[38;5;${mapColorToAnsi256(color)}m`;
30371
+ }
30372
+ /**
30373
+ * Creates the ANSI escape code that sets the background color of following characters.
30374
+ *
30375
+ * @private helper of `convertImageDataToAsciiArt`
30376
+ */
30377
+ function createBackgroundColorCode(color, colorDepth) {
30378
+ if (colorDepth === 'TRUE_COLOR') {
30379
+ return `\u001b[48;2;${color.red};${color.green};${color.blue}m`;
30380
+ }
30381
+ return `\u001b[48;5;${mapColorToAnsi256(color)}m`;
30382
+ }
30383
+ /**
30384
+ * Maps a 24-bit color onto the closest entry of the 256-color ANSI palette.
30385
+ *
30386
+ * Uses the 6×6×6 color cube (entries 16-231) and the grayscale ramp (entries 232-255).
30387
+ *
30388
+ * @private helper of `convertImageDataToAsciiArt`
30389
+ */
30390
+ function mapColorToAnsi256(color) {
30391
+ const { red, green, blue } = color;
30392
+ // Note: Prefer the finer grayscale ramp when the color is (nearly) achromatic
30393
+ const maxChannel = Math.max(red, green, blue);
30394
+ const minChannel = Math.min(red, green, blue);
30395
+ if (maxChannel - minChannel < ANSI_256_ACHROMATIC_CHANNEL_SPREAD) {
30396
+ const gray = Math.round((red + green + blue) / 3);
30397
+ if (gray < 4) {
30398
+ return 16; // <- Note: Pure black lives in the color cube
30399
+ }
30400
+ if (gray > ANSI_256_NEAR_WHITE_GRAY_LEVEL) {
30401
+ return ANSI_256_WHITE_INDEX; // <- Note: Pure white lives in the color cube
30402
+ }
30403
+ return 232 + Math.round(((gray - 8) / ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL) * ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN);
30404
+ }
30405
+ const redIndex = Math.round((red / 255) * 5);
30406
+ const greenIndex = Math.round((green / 255) * 5);
30407
+ const blueIndex = Math.round((blue / 255) * 5);
30408
+ return 16 + 36 * redIndex + 6 * greenIndex + blueIndex;
30409
+ }
30410
+
30411
+ /**
30412
+ * Default output width of the ASCII avatar in terminal character cells.
30413
+ *
30414
+ * @private within the repository
30415
+ */
30416
+ const DEFAULT_AVATAR_ASCII_ART_COLUMNS = 32;
30417
+ /**
30418
+ * Stable animation timestamp used when rasterizing animated avatar visuals into a static ASCII frame.
30419
+ *
30420
+ * @private within the repository
30421
+ */
30422
+ const STATIC_AVATAR_ASCII_ART_FRAME_TIME_MS = 840;
30423
+ /**
30424
+ * Default source canvas width used before the pixels are converted to ASCII art.
30425
+ *
30426
+ * @private within the repository
30427
+ */
30428
+ const DEFAULT_AVATAR_ASCII_ART_CANVAS_WIDTH = DEFAULT_AVATAR_SIZE;
30429
+ /**
30430
+ * Default source canvas height used before the pixels are converted to ASCII art.
30431
+ *
30432
+ * @private within the repository
30433
+ */
30434
+ const DEFAULT_AVATAR_ASCII_ART_CANVAS_HEIGHT = DEFAULT_AVATAR_SIZE;
30435
+ /**
30436
+ * Renders one built-in avatar visual into ANSI-colored ASCII art for terminal display.
30437
+ *
30438
+ * This is the universal bridge between the canvas avatar visuals shown on the website and
30439
+ * text-based terminal UIs: the visual is rasterized through the exact same `renderAvatarVisual`
30440
+ * pipeline the web uses, then the resulting pixels are converted into colored half-block
30441
+ * characters by `convertImageDataToAsciiArt`.
30442
+ *
30443
+ * @param options Avatar identity, visual selection, output grid size, and the platform canvas factory.
30444
+ * @returns One ANSI-colored string per output row.
30445
+ *
30446
+ * @private within the repository
30447
+ */
30448
+ function renderAvatarVisualAsciiArt(options) {
30449
+ var _a, _b, _c, _d;
30450
+ const columns = (_a = options.columns) !== null && _a !== void 0 ? _a : DEFAULT_AVATAR_ASCII_ART_COLUMNS;
30451
+ const rows = (_b = options.rows) !== null && _b !== void 0 ? _b : Math.round(columns / 2);
30452
+ const canvasWidth = (_c = options.canvasWidth) !== null && _c !== void 0 ? _c : DEFAULT_AVATAR_ASCII_ART_CANVAS_WIDTH;
30453
+ const canvasHeight = (_d = options.canvasHeight) !== null && _d !== void 0 ? _d : DEFAULT_AVATAR_ASCII_ART_CANVAS_HEIGHT;
30454
+ assertPositiveCanvasDimension(canvasWidth, 'canvasWidth');
30455
+ assertPositiveCanvasDimension(canvasHeight, 'canvasHeight');
30456
+ const imageData = renderAvatarVisualAsciiArtImageData(options, canvasWidth, canvasHeight);
30457
+ return convertImageDataToAsciiArt({
30458
+ imageData,
30459
+ columns,
30460
+ rows,
30461
+ colorDepth: options.colorDepth,
30462
+ });
30463
+ }
30464
+ /**
30465
+ * Renders one avatar visual frame into source pixels ready for ASCII conversion.
30466
+ *
30467
+ * @private helper of `renderAvatarVisualAsciiArt`
30468
+ */
30469
+ function renderAvatarVisualAsciiArtImageData(options, canvasWidth, canvasHeight) {
30470
+ var _a;
30471
+ const avatarSize = Math.min(canvasWidth, canvasHeight);
30472
+ const avatarCanvas = createCanvasWithBrowserShape(options.createCanvas, avatarSize, avatarSize);
30473
+ renderAvatarVisual({
30474
+ canvas: avatarCanvas,
30475
+ avatarDefinition: options.avatarDefinition,
30476
+ visualId: options.visualId,
30477
+ surface: options.surface,
30478
+ size: avatarSize,
30479
+ timeMs: (_a = options.timeMs) !== null && _a !== void 0 ? _a : STATIC_AVATAR_ASCII_ART_FRAME_TIME_MS,
30480
+ devicePixelRatio: 1,
30481
+ }, options.resolvedAvatarRenderDefinition);
30482
+ if (canvasWidth === avatarSize && canvasHeight === avatarSize) {
30483
+ return getCanvas2dContext(avatarCanvas).getImageData(0, 0, avatarSize, avatarSize);
30484
+ }
30485
+ const canvas = createCanvasWithBrowserShape(options.createCanvas, canvasWidth, canvasHeight);
30486
+ const context = getCanvas2dContext(canvas);
30487
+ const avatarLeft = (canvasWidth - avatarSize) / 2;
30488
+ const avatarTop = (canvasHeight - avatarSize) / 2;
30489
+ context.clearRect(0, 0, canvasWidth, canvasHeight);
30490
+ context.drawImage(avatarCanvas, avatarLeft, avatarTop, avatarSize, avatarSize);
30491
+ return context.getImageData(0, 0, canvasWidth, canvasHeight);
30492
+ }
30493
+ /**
30494
+ * Creates a canvas and adds the small browser-shape compatibility shim expected by avatar rendering.
30495
+ *
30496
+ * @private helper of `renderAvatarVisualAsciiArt`
30497
+ */
30498
+ function createCanvasWithBrowserShape(createCanvas, width, height) {
30499
+ const canvas = createCanvas(width, height);
30500
+ if (!canvas.style) {
30501
+ // Note: `renderAvatarVisual` expects a browser canvas shape; Node.js canvases only need this tiny compatibility shim.
30502
+ canvas.style = {};
30503
+ }
30504
+ return canvas;
30505
+ }
30506
+ /**
30507
+ * Reads a 2D rendering context or throws a branded environment error.
30508
+ *
30509
+ * @private helper of `renderAvatarVisualAsciiArt`
30510
+ */
30511
+ function getCanvas2dContext(canvas) {
30512
+ const context = canvas.getContext('2d');
30513
+ if (!context) {
30514
+ throw new EnvironmentMismatchError(spaceTrim$1(`
30515
+ 2D canvas rendering context is unavailable while converting the avatar visual to ASCII art.
30516
+
30517
+ Provide a \`createCanvas\` factory whose canvases support \`getContext('2d')\`,
30518
+ for example \`createCanvas\` of \`@napi-rs/canvas\` in Node.js.
30519
+ `));
30520
+ }
30521
+ return context;
30522
+ }
30523
+ /**
30524
+ * Validates one source canvas dimension.
30525
+ *
30526
+ * @private helper of `renderAvatarVisualAsciiArt`
30527
+ */
30528
+ function assertPositiveCanvasDimension(value, dimensionName) {
30529
+ if (Number.isInteger(value) && value > 0) {
30530
+ return;
30531
+ }
30532
+ throw new UnexpectedError(spaceTrim$1(`
30533
+ Avatar ASCII-art source canvas dimension is invalid.
30534
+
30535
+ \`${dimensionName}\` must be a positive integer but \`${value}\` was requested.
30536
+ `));
30537
+ }
30538
+
30539
+ /**
30540
+ * Default built-in avatar visual used when an agent does not define `META IMAGE`, `META AVATAR`, or `META VISUAL`.
30541
+ *
30542
+ * @private shared avatar contract
30543
+ */
30544
+ const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus3d4';
30545
+ /**
30546
+ * Resolves the avatar visual preferred by an agent, then falls back to a federated server default
30547
+ * and finally to the caller/server default.
30548
+ *
30549
+ * @param agent Agent metadata and optional remote-profile visual id.
30550
+ * @param defaultAvatarVisualId Optional metadata-resolved server default.
30551
+ * @returns Supported avatar visual id.
30552
+ *
30553
+ * @private shared avatar contract
30554
+ */
30555
+ function resolveAgentAvatarVisualId(agent, defaultAvatarVisualId = DEFAULT_AGENT_AVATAR_VISUAL_ID) {
30556
+ var _a;
30557
+ return (resolveAvatarVisualId((_a = agent.meta) === null || _a === void 0 ? void 0 : _a.avatar) ||
30558
+ agent.avatarVisualId ||
30559
+ agent.defaultAgentAvatarVisualId ||
30560
+ defaultAvatarVisualId);
30561
+ }
30562
+
30563
+ // Note: [💞] Ignore a discrepancy between file name and entity name
30564
+ /**
30565
+ * Output width of the terminal agent avatar visual in character cells.
30566
+ *
30567
+ * @private shared helper for terminal avatar rendering
30568
+ */
30569
+ const TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS = 48;
30570
+ /**
30571
+ * Output height of the terminal agent avatar visual in character cells.
30572
+ *
30573
+ * @private shared helper for terminal avatar rendering
30574
+ */
30575
+ const TERMINAL_AGENT_AVATAR_VISUAL_ROWS = 12;
30576
+ /**
30577
+ * Refresh cadence used while a terminal agent avatar visual is animated.
30578
+ *
30579
+ * @private shared helper for terminal avatar rendering
30580
+ */
30581
+ const TERMINAL_AGENT_AVATAR_VISUAL_REFRESH_INTERVAL_MS = 300;
30582
+ /**
30583
+ * Aspect ratio of the source canvas used for the terminal avatar variant.
30584
+ *
30585
+ * @private shared helper for terminal avatar rendering
30586
+ */
30587
+ const TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_ASPECT_RATIO = 2;
30588
+ /**
30589
+ * Source canvas width used before the avatar is converted to ASCII art.
30590
+ *
30591
+ * @private shared helper for terminal avatar rendering
30592
+ */
30593
+ const TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_WIDTH = DEFAULT_AVATAR_SIZE * TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_ASPECT_RATIO;
30594
+ /**
30595
+ * Source canvas height used before the avatar is converted to ASCII art.
30596
+ *
30597
+ * @private shared helper for terminal avatar rendering
30598
+ */
30599
+ const TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_HEIGHT = DEFAULT_AVATAR_SIZE;
30600
+ /**
30601
+ * Creates an ANSI ASCII-art avatar renderer for terminal UIs.
30602
+ *
30603
+ * The agent's avatar visual is resolved the same way as on the website: `META AVATAR`
30604
+ * / `META VISUAL` wins, then the provided fallback visual, then the shared default visual.
30605
+ * The terminal variant uses a transparent horizontal canvas instead of the website's framed square surface.
30606
+ *
30607
+ * @param options Agent source, canvas factory, and optional terminal color settings.
30608
+ * @returns Runtime terminal avatar visual renderer.
30609
+ *
30610
+ * @private shared helper for terminal avatar rendering
30611
+ */
30612
+ function createTerminalAgentAvatarVisual(options) {
30613
+ const renderInputs = createTerminalAgentAvatarVisualRenderInputs(options.agentSource, options.defaultAvatarVisualId || DEFAULT_AGENT_AVATAR_VISUAL_ID);
30614
+ return {
30615
+ isAnimated: renderInputs.resolvedAvatarRenderDefinition.avatarVisual.isAnimated,
30616
+ renderFrame({ animationTimeMs }) {
30617
+ return renderTerminalAgentAvatarVisualFrame({
30618
+ ...renderInputs,
30619
+ animationTimeMs,
30620
+ colorDepth: options.colorDepth,
30621
+ createCanvas: options.createCanvas,
30622
+ });
30623
+ },
30624
+ };
30625
+ }
30626
+ /**
30627
+ * Creates stable avatar render inputs from one agent source.
30628
+ *
30629
+ * @private shared helper for terminal avatar rendering
30630
+ */
30631
+ function createTerminalAgentAvatarVisualRenderInputs(agentSource, defaultAvatarVisualId) {
30632
+ const agentBasicInformation = parseAgentSource(agentSource);
30633
+ const avatarDefinition = createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation);
30634
+ const avatarVisualId = resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId);
30635
+ const resolvedAvatarRenderDefinition = resolveAvatarRenderDefinition({
30636
+ avatarDefinition,
30637
+ visualId: avatarVisualId,
30638
+ surface: 'transparent',
30639
+ });
30640
+ return {
30641
+ avatarDefinition,
30642
+ avatarVisualId,
30643
+ resolvedAvatarRenderDefinition,
30644
+ };
30645
+ }
30646
+ /**
30647
+ * Resolves the terminal avatar visual id from already parsed agent information.
30648
+ *
30649
+ * @private shared helper for terminal avatar rendering
30650
+ */
30651
+ function resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId) {
30652
+ return resolveAgentAvatarVisualId(agentBasicInformation, resolveAvatarVisualId(agentBasicInformation.meta.visual) || defaultAvatarVisualId);
30653
+ }
30654
+ /**
30655
+ * Renders one terminal avatar frame through the shared avatar-to-ASCII pipeline.
30656
+ *
30657
+ * @private shared helper for terminal avatar rendering
30658
+ */
30659
+ function renderTerminalAgentAvatarVisualFrame(options) {
30660
+ return renderAvatarVisualAsciiArt({
30661
+ avatarDefinition: options.avatarDefinition,
30662
+ visualId: options.avatarVisualId,
30663
+ surface: 'transparent',
30664
+ columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
30665
+ rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
30666
+ canvasWidth: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_WIDTH,
30667
+ canvasHeight: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_HEIGHT,
30668
+ colorDepth: options.colorDepth,
30669
+ timeMs: options.animationTimeMs,
30670
+ createCanvas: options.createCanvas,
30671
+ resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
30672
+ });
30673
+ }
30674
+
30053
30675
  /**
30054
30676
  * Refresh cadence used only while the rich coder UI needs animated updates.
30055
30677
  *
30056
30678
  * @private internal constant of coder run UI
30057
30679
  */
30058
- const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = 300;
30680
+ const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = TERMINAL_AGENT_AVATAR_VISUAL_REFRESH_INTERVAL_MS;
30059
30681
  /**
30060
30682
  * Phases that still benefit from automatic refreshes because the frame can change
30061
30683
  * over time even without new runner output.
@@ -72141,23 +72763,25 @@ function describeCoderRunWait(waitKind, remainingMs, totalMs) {
72141
72763
  * @public exported from `@promptbook/cli`
72142
72764
  */
72143
72765
  async function sleepWithCountdown(options) {
72766
+ var _a;
72144
72767
  const { durationMs, waitKind, isRichUiEnabled, uiHandle } = options;
72145
72768
  if (durationMs <= 0) {
72146
72769
  return;
72147
72770
  }
72148
- let remaining = durationMs;
72149
- while (remaining > 0) {
72150
- const statusMessage = describeCoderRunWait(waitKind, remaining, durationMs);
72151
- if (!isRichUiEnabled) {
72152
- console.info(colors.gray(`${statusMessage}...`));
72153
- }
72154
- else {
72771
+ const deadlineTimeMs = (_a = options.deadlineTimeMs) !== null && _a !== void 0 ? _a : Date.now() + durationMs;
72772
+ await waitUntilWorldTimeDeadline({
72773
+ deadlineTimeMs,
72774
+ pollIntervalMs: WAIT_COUNTDOWN_UPDATE_INTERVAL_MS,
72775
+ onTick: (remainingDurationMs) => {
72776
+ const visibleRemainingDurationMs = Math.min(remainingDurationMs, durationMs);
72777
+ const statusMessage = describeCoderRunWait(waitKind, visibleRemainingDurationMs, durationMs);
72778
+ if (!isRichUiEnabled) {
72779
+ console.info(colors.gray(`${statusMessage}...`));
72780
+ return;
72781
+ }
72155
72782
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`${statusMessage}...`);
72156
- }
72157
- const sleepMs = Math.min(WAIT_COUNTDOWN_UPDATE_INTERVAL_MS, remaining);
72158
- await new Promise((resolve) => setTimeout(resolve, sleepMs));
72159
- remaining -= sleepMs;
72160
- }
72783
+ },
72784
+ });
72161
72785
  }
72162
72786
 
72163
72787
  /**
@@ -72303,479 +72927,6 @@ async function waitForPromptStart(file, section, isFirstPrompt) {
72303
72927
  await waitForEnter(colors.bgWhite(`Press Enter to start the ${label}...`));
72304
72928
  }
72305
72929
 
72306
- /**
72307
- * Stable zeroed interaction state used by non-interactive render paths.
72308
- *
72309
- * @private utility of the avatar rendering system
72310
- */
72311
- const IDLE_AVATAR_INTERACTION_STATE = {
72312
- gazeX: 0,
72313
- gazeY: 0,
72314
- bodyOffsetX: 0,
72315
- bodyOffsetY: 0,
72316
- intensity: 0,
72317
- isPointerActive: false,
72318
- pointerType: 'idle',
72319
- };
72320
- /**
72321
- * Returns the neutral interaction state used by static/server-side renders.
72322
- *
72323
- * @returns Zeroed interaction state.
72324
- *
72325
- * @private utility of the avatar rendering system
72326
- */
72327
- function createIdleAvatarInteractionState() {
72328
- return IDLE_AVATAR_INTERACTION_STATE;
72329
- }
72330
-
72331
- /**
72332
- * Resolves the stable avatar render inputs reused across multiple frames.
72333
- *
72334
- * @param options Avatar identity and visual selection.
72335
- * @returns Stable render data ready to be used by `renderAvatarVisual`.
72336
- *
72337
- * @private shared helper for canvas avatar rendering
72338
- */
72339
- function resolveAvatarRenderDefinition(options) {
72340
- const avatarDefinition = normalizeAvatarDefinition(options.avatarDefinition);
72341
- const surface = options.surface || 'framed';
72342
- return {
72343
- avatarDefinition,
72344
- avatarVisual: getAvatarVisualById(options.visualId),
72345
- surface,
72346
- palette: createAvatarPalette(avatarDefinition, surface),
72347
- createRandom: createAvatarRandomFactory(avatarDefinition),
72348
- };
72349
- }
72350
- /**
72351
- * Renders one deterministic avatar frame into the provided canvas.
72352
- *
72353
- * @param options Rendering options.
72354
- * @param resolvedAvatarRenderDefinition Optional stable render data reused between frames.
72355
- *
72356
- * @private shared helper for canvas avatar rendering
72357
- */
72358
- function renderAvatarVisual(options, resolvedAvatarRenderDefinition) {
72359
- const resolvedRenderDefinition = resolvedAvatarRenderDefinition ||
72360
- resolveAvatarRenderDefinition({
72361
- avatarDefinition: options.avatarDefinition,
72362
- visualId: options.visualId,
72363
- surface: options.surface,
72364
- });
72365
- const context = options.canvas.getContext('2d');
72366
- if (!context) {
72367
- throw new Error('2D canvas rendering context is unavailable.');
72368
- }
72369
- prepareAvatarCanvas(options.canvas, context, options.size, options.devicePixelRatio || 1);
72370
- resolvedRenderDefinition.avatarVisual.render({
72371
- canvas: options.canvas,
72372
- context,
72373
- size: options.size,
72374
- devicePixelRatio: options.devicePixelRatio || 1,
72375
- timeMs: options.timeMs,
72376
- avatarDefinition: resolvedRenderDefinition.avatarDefinition,
72377
- palette: resolvedRenderDefinition.palette,
72378
- createRandom: resolvedRenderDefinition.createRandom,
72379
- surface: resolvedRenderDefinition.surface,
72380
- interaction: options.interaction || createIdleAvatarInteractionState(),
72381
- });
72382
- }
72383
-
72384
- /**
72385
- * Default alpha channel value below which a half-cell is rendered as terminal background.
72386
- *
72387
- * @private within the repository
72388
- */
72389
- const DEFAULT_ALPHA_THRESHOLD = 32;
72390
- /**
72391
- * Number of channels per pixel in an RGBA buffer.
72392
- *
72393
- * @private within the repository
72394
- */
72395
- const RGBA_CHANNEL_COUNT = 4;
72396
- /**
72397
- * Upper half block character - foreground paints the top pixel, background paints the bottom pixel.
72398
- *
72399
- * @private within the repository
72400
- */
72401
- const UPPER_HALF_BLOCK = '▀'; // <- ▀
72402
- /**
72403
- * Lower half block character - foreground paints the bottom pixel while the top pixel stays transparent.
72404
- *
72405
- * @private within the repository
72406
- */
72407
- const LOWER_HALF_BLOCK = '▄'; // <- ▄
72408
- /**
72409
- * ANSI escape sequence that resets all colors and attributes.
72410
- *
72411
- * @private within the repository
72412
- */
72413
- const ANSI_RESET = '\u001b[0m';
72414
- /**
72415
- * Maximum spread between RGB channels for a color to be treated as (nearly) achromatic gray.
72416
- *
72417
- * @private within the repository
72418
- */
72419
- const ANSI_256_ACHROMATIC_CHANNEL_SPREAD = 12;
72420
- /**
72421
- * Gray level above which an achromatic color maps to the pure white color-cube entry.
72422
- *
72423
- * @private within the repository
72424
- */
72425
- const ANSI_256_NEAR_WHITE_GRAY_LEVEL = 246;
72426
- /**
72427
- * Index of pure white inside the 6×6×6 ANSI color cube.
72428
- *
72429
- * @private within the repository
72430
- */
72431
- const ANSI_256_WHITE_INDEX = 231;
72432
- /**
72433
- * Brightness of the lightest entry of the ANSI 256 grayscale ramp.
72434
- *
72435
- * @private within the repository
72436
- */
72437
- const ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL = 238;
72438
- /**
72439
- * Number of grayscale ramp steps above its first entry (ANSI indexes 232-255).
72440
- *
72441
- * @private within the repository
72442
- */
72443
- const ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN = 23;
72444
- /**
72445
- * Converts raw RGBA image pixels into colored ASCII art for ANSI terminals.
72446
- *
72447
- * This is the universal image-to-terminal technique used across the repository:
72448
- * every output character cell covers a rectangular region of source pixels which is
72449
- * split into a top and bottom half; each half is area-averaged and rendered with
72450
- * half-block characters (`▀` / `▄`) so one character shows two "pixels" vertically.
72451
- * Transparent halves keep the terminal background so non-rectangular images
72452
- * (for example rounded avatar cards) compose naturally into any terminal UI.
72453
- *
72454
- * @param options Source pixels, output grid size, and ANSI color depth.
72455
- * @returns One ANSI-colored string per output row, each ending with a color reset.
72456
- *
72457
- * @private within the repository
72458
- */
72459
- function convertImageDataToAsciiArt(options) {
72460
- const { imageData, columns, rows, colorDepth = 'TRUE_COLOR', alphaThreshold = DEFAULT_ALPHA_THRESHOLD } = options;
72461
- if (!Number.isInteger(columns) || columns <= 0 || !Number.isInteger(rows) || rows <= 0) {
72462
- throw new UnexpectedError(spaceTrim$1(`
72463
- ASCII-art grid size is invalid.
72464
-
72465
- Both \`columns\` and \`rows\` must be positive integers but \`${columns}\` × \`${rows}\` was requested.
72466
- `));
72467
- }
72468
- if (imageData.width <= 0 ||
72469
- imageData.height <= 0 ||
72470
- imageData.data.length < imageData.width * imageData.height * RGBA_CHANNEL_COUNT) {
72471
- throw new UnexpectedError(spaceTrim$1(`
72472
- ASCII-art source image data is invalid.
72473
-
72474
- Expected a RGBA buffer of at least \`${imageData.width} × ${imageData.height} × ${RGBA_CHANNEL_COUNT}\` bytes
72475
- but got \`${imageData.data.length}\` bytes.
72476
- `));
72477
- }
72478
- const halfCellRowCount = rows * 2;
72479
- const asciiArtLines = [];
72480
- for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
72481
- let line = '';
72482
- let currentForegroundCode = undefined;
72483
- let currentBackgroundCode = undefined;
72484
- for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
72485
- const topHalfColor = computeHalfCellColor(imageData, columnIndex, rowIndex * 2, columns, halfCellRowCount, alphaThreshold);
72486
- const bottomHalfColor = computeHalfCellColor(imageData, columnIndex, rowIndex * 2 + 1, columns, halfCellRowCount, alphaThreshold);
72487
- let character;
72488
- let nextForegroundCode;
72489
- let nextBackgroundCode;
72490
- if (topHalfColor.isOpaque && bottomHalfColor.isOpaque) {
72491
- character = UPPER_HALF_BLOCK;
72492
- nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
72493
- nextBackgroundCode = createBackgroundColorCode(bottomHalfColor, colorDepth);
72494
- }
72495
- else if (topHalfColor.isOpaque) {
72496
- character = UPPER_HALF_BLOCK;
72497
- nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
72498
- nextBackgroundCode = undefined;
72499
- }
72500
- else if (bottomHalfColor.isOpaque) {
72501
- character = LOWER_HALF_BLOCK;
72502
- nextForegroundCode = createForegroundColorCode(bottomHalfColor, colorDepth);
72503
- nextBackgroundCode = undefined;
72504
- }
72505
- else {
72506
- character = ' ';
72507
- nextForegroundCode = undefined;
72508
- nextBackgroundCode = undefined;
72509
- }
72510
- if (nextForegroundCode !== currentForegroundCode || nextBackgroundCode !== currentBackgroundCode) {
72511
- // Note: A reset is required whenever a previously set color must be cleared,
72512
- // otherwise stale background color would bleed into transparent cells.
72513
- const isResetNeeded = (currentForegroundCode !== undefined && nextForegroundCode === undefined) ||
72514
- (currentBackgroundCode !== undefined && nextBackgroundCode === undefined);
72515
- if (isResetNeeded) {
72516
- line += ANSI_RESET;
72517
- currentForegroundCode = undefined;
72518
- currentBackgroundCode = undefined;
72519
- }
72520
- if (nextForegroundCode !== undefined && nextForegroundCode !== currentForegroundCode) {
72521
- line += nextForegroundCode;
72522
- }
72523
- if (nextBackgroundCode !== undefined && nextBackgroundCode !== currentBackgroundCode) {
72524
- line += nextBackgroundCode;
72525
- }
72526
- currentForegroundCode = nextForegroundCode;
72527
- currentBackgroundCode = nextBackgroundCode;
72528
- }
72529
- line += character;
72530
- }
72531
- if (currentForegroundCode !== undefined || currentBackgroundCode !== undefined) {
72532
- line += ANSI_RESET;
72533
- }
72534
- asciiArtLines.push(line);
72535
- }
72536
- return asciiArtLines;
72537
- }
72538
- /**
72539
- * Computes the area-averaged color of one half-cell of the output grid.
72540
- *
72541
- * Color channels are alpha-weighted so semi-transparent edge pixels do not darken towards black.
72542
- *
72543
- * @private helper of `convertImageDataToAsciiArt`
72544
- */
72545
- function computeHalfCellColor(imageData, columnIndex, halfCellRowIndex, columns, halfCellRowCount, alphaThreshold) {
72546
- const startX = Math.floor((columnIndex * imageData.width) / columns);
72547
- const endX = Math.max(startX + 1, Math.floor(((columnIndex + 1) * imageData.width) / columns));
72548
- const startY = Math.floor((halfCellRowIndex * imageData.height) / halfCellRowCount);
72549
- const endY = Math.max(startY + 1, Math.floor(((halfCellRowIndex + 1) * imageData.height) / halfCellRowCount));
72550
- let redSum = 0;
72551
- let greenSum = 0;
72552
- let blueSum = 0;
72553
- let alphaSum = 0;
72554
- let sampledPixelCount = 0;
72555
- for (let y = startY; y < endY && y < imageData.height; y++) {
72556
- for (let x = startX; x < endX && x < imageData.width; x++) {
72557
- const pixelOffset = (y * imageData.width + x) * RGBA_CHANNEL_COUNT;
72558
- const alpha = imageData.data[pixelOffset + 3];
72559
- redSum += imageData.data[pixelOffset] * alpha;
72560
- greenSum += imageData.data[pixelOffset + 1] * alpha;
72561
- blueSum += imageData.data[pixelOffset + 2] * alpha;
72562
- alphaSum += alpha;
72563
- sampledPixelCount++;
72564
- }
72565
- }
72566
- const averageAlpha = sampledPixelCount === 0 ? 0 : alphaSum / sampledPixelCount;
72567
- if (averageAlpha < alphaThreshold || alphaSum === 0) {
72568
- return { red: 0, green: 0, blue: 0, isOpaque: false };
72569
- }
72570
- return {
72571
- red: Math.round(redSum / alphaSum),
72572
- green: Math.round(greenSum / alphaSum),
72573
- blue: Math.round(blueSum / alphaSum),
72574
- isOpaque: true,
72575
- };
72576
- }
72577
- /**
72578
- * Creates the ANSI escape code that sets the foreground color of following characters.
72579
- *
72580
- * @private helper of `convertImageDataToAsciiArt`
72581
- */
72582
- function createForegroundColorCode(color, colorDepth) {
72583
- if (colorDepth === 'TRUE_COLOR') {
72584
- return `\u001b[38;2;${color.red};${color.green};${color.blue}m`;
72585
- }
72586
- return `\u001b[38;5;${mapColorToAnsi256(color)}m`;
72587
- }
72588
- /**
72589
- * Creates the ANSI escape code that sets the background color of following characters.
72590
- *
72591
- * @private helper of `convertImageDataToAsciiArt`
72592
- */
72593
- function createBackgroundColorCode(color, colorDepth) {
72594
- if (colorDepth === 'TRUE_COLOR') {
72595
- return `\u001b[48;2;${color.red};${color.green};${color.blue}m`;
72596
- }
72597
- return `\u001b[48;5;${mapColorToAnsi256(color)}m`;
72598
- }
72599
- /**
72600
- * Maps a 24-bit color onto the closest entry of the 256-color ANSI palette.
72601
- *
72602
- * Uses the 6×6×6 color cube (entries 16-231) and the grayscale ramp (entries 232-255).
72603
- *
72604
- * @private helper of `convertImageDataToAsciiArt`
72605
- */
72606
- function mapColorToAnsi256(color) {
72607
- const { red, green, blue } = color;
72608
- // Note: Prefer the finer grayscale ramp when the color is (nearly) achromatic
72609
- const maxChannel = Math.max(red, green, blue);
72610
- const minChannel = Math.min(red, green, blue);
72611
- if (maxChannel - minChannel < ANSI_256_ACHROMATIC_CHANNEL_SPREAD) {
72612
- const gray = Math.round((red + green + blue) / 3);
72613
- if (gray < 4) {
72614
- return 16; // <- Note: Pure black lives in the color cube
72615
- }
72616
- if (gray > ANSI_256_NEAR_WHITE_GRAY_LEVEL) {
72617
- return ANSI_256_WHITE_INDEX; // <- Note: Pure white lives in the color cube
72618
- }
72619
- return 232 + Math.round(((gray - 8) / ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL) * ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN);
72620
- }
72621
- const redIndex = Math.round((red / 255) * 5);
72622
- const greenIndex = Math.round((green / 255) * 5);
72623
- const blueIndex = Math.round((blue / 255) * 5);
72624
- return 16 + 36 * redIndex + 6 * greenIndex + blueIndex;
72625
- }
72626
-
72627
- /**
72628
- * Default output width of the ASCII avatar in terminal character cells.
72629
- *
72630
- * @private within the repository
72631
- */
72632
- const DEFAULT_AVATAR_ASCII_ART_COLUMNS = 32;
72633
- /**
72634
- * Stable animation timestamp used when rasterizing animated avatar visuals into a static ASCII frame.
72635
- *
72636
- * @private within the repository
72637
- */
72638
- const STATIC_AVATAR_ASCII_ART_FRAME_TIME_MS = 840;
72639
- /**
72640
- * Default source canvas width used before the pixels are converted to ASCII art.
72641
- *
72642
- * @private within the repository
72643
- */
72644
- const DEFAULT_AVATAR_ASCII_ART_CANVAS_WIDTH = DEFAULT_AVATAR_SIZE;
72645
- /**
72646
- * Default source canvas height used before the pixels are converted to ASCII art.
72647
- *
72648
- * @private within the repository
72649
- */
72650
- const DEFAULT_AVATAR_ASCII_ART_CANVAS_HEIGHT = DEFAULT_AVATAR_SIZE;
72651
- /**
72652
- * Renders one built-in avatar visual into ANSI-colored ASCII art for terminal display.
72653
- *
72654
- * This is the universal bridge between the canvas avatar visuals shown on the website and
72655
- * text-based terminal UIs: the visual is rasterized through the exact same `renderAvatarVisual`
72656
- * pipeline the web uses, then the resulting pixels are converted into colored half-block
72657
- * characters by `convertImageDataToAsciiArt`.
72658
- *
72659
- * @param options Avatar identity, visual selection, output grid size, and the platform canvas factory.
72660
- * @returns One ANSI-colored string per output row.
72661
- *
72662
- * @private within the repository
72663
- */
72664
- function renderAvatarVisualAsciiArt(options) {
72665
- var _a, _b, _c, _d;
72666
- const columns = (_a = options.columns) !== null && _a !== void 0 ? _a : DEFAULT_AVATAR_ASCII_ART_COLUMNS;
72667
- const rows = (_b = options.rows) !== null && _b !== void 0 ? _b : Math.round(columns / 2);
72668
- const canvasWidth = (_c = options.canvasWidth) !== null && _c !== void 0 ? _c : DEFAULT_AVATAR_ASCII_ART_CANVAS_WIDTH;
72669
- const canvasHeight = (_d = options.canvasHeight) !== null && _d !== void 0 ? _d : DEFAULT_AVATAR_ASCII_ART_CANVAS_HEIGHT;
72670
- assertPositiveCanvasDimension(canvasWidth, 'canvasWidth');
72671
- assertPositiveCanvasDimension(canvasHeight, 'canvasHeight');
72672
- const imageData = renderAvatarVisualAsciiArtImageData(options, canvasWidth, canvasHeight);
72673
- return convertImageDataToAsciiArt({
72674
- imageData,
72675
- columns,
72676
- rows,
72677
- colorDepth: options.colorDepth,
72678
- });
72679
- }
72680
- /**
72681
- * Renders one avatar visual frame into source pixels ready for ASCII conversion.
72682
- *
72683
- * @private helper of `renderAvatarVisualAsciiArt`
72684
- */
72685
- function renderAvatarVisualAsciiArtImageData(options, canvasWidth, canvasHeight) {
72686
- var _a;
72687
- const avatarSize = Math.min(canvasWidth, canvasHeight);
72688
- const avatarCanvas = createCanvasWithBrowserShape(options.createCanvas, avatarSize, avatarSize);
72689
- renderAvatarVisual({
72690
- canvas: avatarCanvas,
72691
- avatarDefinition: options.avatarDefinition,
72692
- visualId: options.visualId,
72693
- surface: options.surface,
72694
- size: avatarSize,
72695
- timeMs: (_a = options.timeMs) !== null && _a !== void 0 ? _a : STATIC_AVATAR_ASCII_ART_FRAME_TIME_MS,
72696
- devicePixelRatio: 1,
72697
- }, options.resolvedAvatarRenderDefinition);
72698
- if (canvasWidth === avatarSize && canvasHeight === avatarSize) {
72699
- return getCanvas2dContext(avatarCanvas).getImageData(0, 0, avatarSize, avatarSize);
72700
- }
72701
- const canvas = createCanvasWithBrowserShape(options.createCanvas, canvasWidth, canvasHeight);
72702
- const context = getCanvas2dContext(canvas);
72703
- const avatarLeft = (canvasWidth - avatarSize) / 2;
72704
- const avatarTop = (canvasHeight - avatarSize) / 2;
72705
- context.clearRect(0, 0, canvasWidth, canvasHeight);
72706
- context.drawImage(avatarCanvas, avatarLeft, avatarTop, avatarSize, avatarSize);
72707
- return context.getImageData(0, 0, canvasWidth, canvasHeight);
72708
- }
72709
- /**
72710
- * Creates a canvas and adds the small browser-shape compatibility shim expected by avatar rendering.
72711
- *
72712
- * @private helper of `renderAvatarVisualAsciiArt`
72713
- */
72714
- function createCanvasWithBrowserShape(createCanvas, width, height) {
72715
- const canvas = createCanvas(width, height);
72716
- if (!canvas.style) {
72717
- // Note: `renderAvatarVisual` expects a browser canvas shape; Node.js canvases only need this tiny compatibility shim.
72718
- canvas.style = {};
72719
- }
72720
- return canvas;
72721
- }
72722
- /**
72723
- * Reads a 2D rendering context or throws a branded environment error.
72724
- *
72725
- * @private helper of `renderAvatarVisualAsciiArt`
72726
- */
72727
- function getCanvas2dContext(canvas) {
72728
- const context = canvas.getContext('2d');
72729
- if (!context) {
72730
- throw new EnvironmentMismatchError(spaceTrim$1(`
72731
- 2D canvas rendering context is unavailable while converting the avatar visual to ASCII art.
72732
-
72733
- Provide a \`createCanvas\` factory whose canvases support \`getContext('2d')\`,
72734
- for example \`createCanvas\` of \`@napi-rs/canvas\` in Node.js.
72735
- `));
72736
- }
72737
- return context;
72738
- }
72739
- /**
72740
- * Validates one source canvas dimension.
72741
- *
72742
- * @private helper of `renderAvatarVisualAsciiArt`
72743
- */
72744
- function assertPositiveCanvasDimension(value, dimensionName) {
72745
- if (Number.isInteger(value) && value > 0) {
72746
- return;
72747
- }
72748
- throw new UnexpectedError(spaceTrim$1(`
72749
- Avatar ASCII-art source canvas dimension is invalid.
72750
-
72751
- \`${dimensionName}\` must be a positive integer but \`${value}\` was requested.
72752
- `));
72753
- }
72754
-
72755
- /**
72756
- * Default built-in avatar visual used when an agent does not define `META IMAGE`, `META AVATAR`, or `META VISUAL`.
72757
- *
72758
- * @private shared avatar contract
72759
- */
72760
- const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus3d4';
72761
- /**
72762
- * Resolves the avatar visual preferred by an agent, then falls back to a federated server default
72763
- * and finally to the caller/server default.
72764
- *
72765
- * @param agent Agent metadata and optional remote-profile visual id.
72766
- * @param defaultAvatarVisualId Optional metadata-resolved server default.
72767
- * @returns Supported avatar visual id.
72768
- *
72769
- * @private shared avatar contract
72770
- */
72771
- function resolveAgentAvatarVisualId(agent, defaultAvatarVisualId = DEFAULT_AGENT_AVATAR_VISUAL_ID) {
72772
- var _a;
72773
- return (resolveAvatarVisualId((_a = agent.meta) === null || _a === void 0 ? void 0 : _a.avatar) ||
72774
- agent.avatarVisualId ||
72775
- agent.defaultAgentAvatarVisualId ||
72776
- defaultAvatarVisualId);
72777
- }
72778
-
72779
72930
  /**
72780
72931
  * Detects the ANSI color depth supported by the current terminal.
72781
72932
  *
@@ -72814,36 +72965,6 @@ function $detectTerminalAnsiColorDepth() {
72814
72965
  return 'ANSI_256';
72815
72966
  }
72816
72967
 
72817
- /**
72818
- * Output width of the coder-run agent visual in terminal character cells.
72819
- *
72820
- * @private internal constant of coder run UI
72821
- */
72822
- const CODER_RUN_AGENT_VISUAL_COLUMNS = 48;
72823
- /**
72824
- * Output height of the coder-run agent visual in terminal character cells.
72825
- *
72826
- * @private internal constant of coder run UI
72827
- */
72828
- const CODER_RUN_AGENT_VISUAL_ROWS = 12;
72829
- /**
72830
- * Aspect ratio of the source canvas used for the terminal variant.
72831
- *
72832
- * @private internal constant of coder run UI
72833
- */
72834
- const CODER_RUN_AGENT_VISUAL_CANVAS_ASPECT_RATIO = 2;
72835
- /**
72836
- * Source canvas width used before the avatar is converted to ASCII art.
72837
- *
72838
- * @private internal constant of coder run UI
72839
- */
72840
- const CODER_RUN_AGENT_VISUAL_CANVAS_WIDTH = DEFAULT_AVATAR_SIZE * CODER_RUN_AGENT_VISUAL_CANVAS_ASPECT_RATIO;
72841
- /**
72842
- * Source canvas height used before the avatar is converted to ASCII art.
72843
- *
72844
- * @private internal constant of coder run UI
72845
- */
72846
- const CODER_RUN_AGENT_VISUAL_CANVAS_HEIGHT = DEFAULT_AVATAR_SIZE;
72847
72968
  /**
72848
72969
  * Builds the ANSI ASCII-art visual of the `--agent` book shown above the coder-run dashboard.
72849
72970
  *
@@ -72862,33 +72983,18 @@ async function buildCoderRunAgentVisual(agentSource) {
72862
72983
  try {
72863
72984
  // Note: `@napi-rs/canvas` is an optional native module, so it is imported dynamically and lazily
72864
72985
  const { createCanvas } = await import('@napi-rs/canvas');
72865
- const agentBasicInformation = parseAgentSource(agentSource);
72866
- const avatarDefinition = createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation);
72867
- const avatarVisualId = resolveAgentAvatarVisualId(agentBasicInformation, resolveAvatarVisualId(agentBasicInformation.meta.visual) || DEFAULT_AGENT_AVATAR_VISUAL_ID);
72868
- const resolvedAvatarRenderDefinition = resolveAvatarRenderDefinition({
72869
- avatarDefinition,
72870
- visualId: avatarVisualId,
72871
- surface: 'transparent',
72872
- });
72873
72986
  const colorDepth = $detectTerminalAnsiColorDepth();
72874
72987
  const createCanvasForAsciiArt = (width, height) => createCanvas(width, height);
72988
+ const agentVisual = createTerminalAgentAvatarVisual({
72989
+ agentSource,
72990
+ colorDepth,
72991
+ createCanvas: createCanvasForAsciiArt,
72992
+ });
72875
72993
  return {
72876
- isAnimated: resolvedAvatarRenderDefinition.avatarVisual.isAnimated,
72994
+ isAnimated: agentVisual.isAnimated,
72877
72995
  renderFrame({ animationTimeMs }) {
72878
72996
  try {
72879
- return renderAvatarVisualAsciiArt({
72880
- avatarDefinition,
72881
- visualId: avatarVisualId,
72882
- surface: 'transparent',
72883
- columns: CODER_RUN_AGENT_VISUAL_COLUMNS,
72884
- rows: CODER_RUN_AGENT_VISUAL_ROWS,
72885
- canvasWidth: CODER_RUN_AGENT_VISUAL_CANVAS_WIDTH,
72886
- canvasHeight: CODER_RUN_AGENT_VISUAL_CANVAS_HEIGHT,
72887
- colorDepth,
72888
- timeMs: animationTimeMs,
72889
- createCanvas: createCanvasForAsciiArt,
72890
- resolvedAvatarRenderDefinition,
72891
- });
72997
+ return agentVisual.renderFrame({ animationTimeMs });
72892
72998
  }
72893
72999
  catch (error) {
72894
73000
  keepUnused(error);
@@ -74625,6 +74731,7 @@ async function waitAfterErrorBeforeRetry(options) {
74625
74731
  if (!isRichUiEnabled) {
74626
74732
  console.warn(colors.yellow(`Prompt round failed (retry ${attemptedRetries}/${MAX_RETRY_ATTEMPTS_AFTER_ERROR}): ${errorMessage}`));
74627
74733
  }
74734
+ const retryDeadlineTimeMs = Date.now() + runOptions.waitAfterError;
74628
74735
  await waitForRequestedPause({
74629
74736
  checkpointLabel: 'waiting after error before retrying the prompt',
74630
74737
  phase: 'waiting',
@@ -74634,6 +74741,7 @@ async function waitAfterErrorBeforeRetry(options) {
74634
74741
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
74635
74742
  await sleepWithCountdown({
74636
74743
  durationMs: runOptions.waitAfterError,
74744
+ deadlineTimeMs: retryDeadlineTimeMs,
74637
74745
  waitKind: 'after-error',
74638
74746
  isRichUiEnabled,
74639
74747
  uiHandle,
@@ -75229,31 +75337,29 @@ async function waitBetweenPromptRoundsIfNeeded(options) {
75229
75337
  if (waitAfterPrompt <= 0 && waitBetweenPrompts <= 0) {
75230
75338
  return;
75231
75339
  }
75232
- const now = Date.now();
75233
75340
  const waitBetweenPromptsEndTime = previousRoundStartTime + waitBetweenPrompts;
75234
75341
  const waitAfterPromptEndTime = previousRoundEndTime + waitAfterPrompt;
75235
- // Phase 1: pace from start of previous prompt (`--wait-between-prompts`)
75236
- const phase1Duration = Math.max(0, waitBetweenPromptsEndTime - now);
75237
- // Phase 2: rest from end of previous prompt (`--wait-after-prompt`)
75238
- const phase2StartTime = Math.max(now, waitBetweenPromptsEndTime);
75239
- const phase2Duration = Math.max(0, waitAfterPromptEndTime - phase2StartTime);
75240
- if (phase1Duration <= 0 && phase2Duration <= 0) {
75342
+ if (Date.now() >= waitBetweenPromptsEndTime && Date.now() >= waitAfterPromptEndTime) {
75241
75343
  return;
75242
75344
  }
75243
75345
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
75244
75346
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
75245
75347
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
75246
- if (phase1Duration > 0) {
75348
+ // Phase 1: pace from start of previous prompt (`--wait-between-prompts`)
75349
+ if (Date.now() < waitBetweenPromptsEndTime) {
75247
75350
  await sleepWithCountdown({
75248
- durationMs: phase1Duration,
75351
+ durationMs: waitBetweenPrompts,
75352
+ deadlineTimeMs: waitBetweenPromptsEndTime,
75249
75353
  waitKind: 'between-prompts',
75250
75354
  isRichUiEnabled,
75251
75355
  uiHandle,
75252
75356
  });
75253
75357
  }
75254
- if (phase2Duration > 0) {
75358
+ // Phase 2: rest from end of previous prompt (`--wait-after-prompt`)
75359
+ if (Date.now() < waitAfterPromptEndTime) {
75255
75360
  await sleepWithCountdown({
75256
- durationMs: phase2Duration,
75361
+ durationMs: waitAfterPrompt,
75362
+ deadlineTimeMs: waitAfterPromptEndTime,
75257
75363
  waitKind: 'after-prompt',
75258
75364
  isRichUiEnabled,
75259
75365
  uiHandle,