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