@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.
- package/apps/agents-server/src/app/admin/{code-runners/CodeRunnersClient.tsx → harness-auth/HarnessAuthClient.tsx} +98 -65
- package/apps/agents-server/src/app/admin/{code-runners → harness-auth}/page.tsx +4 -4
- package/apps/agents-server/src/app/api/admin/{code-runners → harness-auth}/authentication/route.ts +17 -17
- package/apps/agents-server/src/app/api/admin/{code-runners → harness-auth}/route.ts +13 -13
- package/apps/agents-server/src/app/layout.tsx +16 -0
- package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +32 -24
- package/apps/agents-server/src/components/Footer/Footer.tsx +38 -15
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +5 -4
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +4 -1
- package/apps/agents-server/src/constants/harnessAuthRoutes.ts +20 -0
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -1
- package/apps/agents-server/src/languages/translations/czech.yaml +2 -2
- package/apps/agents-server/src/languages/translations/english.yaml +1 -1
- package/apps/agents-server/src/utils/{codeRunnerAuthentication.ts → harnessAuthentication.ts} +72 -56
- package/apps/agents-server/src/utils/{codeRunnerConfiguration.ts → harnessConfiguration.ts} +18 -12
- package/apps/agents-server/src/utils/taskTerminal/resolveAdminTaskTerminalSession.ts +28 -5
- package/apps/agents-server/src/utils/vpsConfiguration.ts +3 -3
- package/apps/agents-server/src/utils/vpsSelfUpdate/readAgentsServerFooterVersion.ts +335 -0
- package/apps/agents-server/src/utils/vpsSelfUpdate/vpsSelfUpdateJobHistory.ts +107 -4
- package/esm/index.es.js +697 -591
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/common/sleepWithCountdown.d.ts +1 -0
- package/esm/scripts/run-codex-prompts/common/waitUntilWorldTimeDeadline.d.ts +17 -0
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +3 -16
- package/esm/src/utils/agents/terminalAgentAvatarVisual.d.ts +94 -0
- package/esm/src/utils/agents/terminalAgentAvatarVisual.test.d.ts +1 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +790 -807
- package/src/utils/agents/terminalAgentAvatarVisual.ts +261 -0
- package/src/version.ts +2 -2
- package/src/versions.txt +1 -0
- package/umd/index.umd.js +697 -591
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/common/sleepWithCountdown.d.ts +1 -0
- package/umd/scripts/run-codex-prompts/common/waitUntilWorldTimeDeadline.d.ts +17 -0
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +3 -16
- package/umd/src/utils/agents/terminalAgentAvatarVisual.d.ts +94 -0
- package/umd/src/utils/agents/terminalAgentAvatarVisual.test.d.ts +1 -0
- 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-
|
|
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
|
|
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
|
-
|
|
28525
|
-
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
|
|
28529
|
-
|
|
28530
|
-
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
28534
|
-
|
|
28535
|
-
|
|
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
|
|
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(
|
|
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(
|
|
29540
|
-
|
|
29541
|
-
|
|
29542
|
-
|
|
29543
|
-
|
|
29544
|
-
|
|
29545
|
-
|
|
29546
|
-
|
|
29547
|
-
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
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 =
|
|
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
|
-
|
|
72149
|
-
|
|
72150
|
-
|
|
72151
|
-
|
|
72152
|
-
|
|
72153
|
-
|
|
72154
|
-
|
|
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
|
-
|
|
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:
|
|
72994
|
+
isAnimated: agentVisual.isAnimated,
|
|
72877
72995
|
renderFrame({ animationTimeMs }) {
|
|
72878
72996
|
try {
|
|
72879
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
75348
|
+
// Phase 1: pace from start of previous prompt (`--wait-between-prompts`)
|
|
75349
|
+
if (Date.now() < waitBetweenPromptsEndTime) {
|
|
75247
75350
|
await sleepWithCountdown({
|
|
75248
|
-
durationMs:
|
|
75351
|
+
durationMs: waitBetweenPrompts,
|
|
75352
|
+
deadlineTimeMs: waitBetweenPromptsEndTime,
|
|
75249
75353
|
waitKind: 'between-prompts',
|
|
75250
75354
|
isRichUiEnabled,
|
|
75251
75355
|
uiHandle,
|
|
75252
75356
|
});
|
|
75253
75357
|
}
|
|
75254
|
-
|
|
75358
|
+
// Phase 2: rest from end of previous prompt (`--wait-after-prompt`)
|
|
75359
|
+
if (Date.now() < waitAfterPromptEndTime) {
|
|
75255
75360
|
await sleepWithCountdown({
|
|
75256
|
-
durationMs:
|
|
75361
|
+
durationMs: waitAfterPrompt,
|
|
75362
|
+
deadlineTimeMs: waitAfterPromptEndTime,
|
|
75257
75363
|
waitKind: 'after-prompt',
|
|
75258
75364
|
isRichUiEnabled,
|
|
75259
75365
|
uiHandle,
|