@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/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-
|
|
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
|
|
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
|
-
|
|
28529
|
-
|
|
28530
|
-
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
28534
|
-
|
|
28535
|
-
|
|
28536
|
-
|
|
28537
|
-
|
|
28538
|
-
|
|
28539
|
-
|
|
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
|
|
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(
|
|
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(
|
|
29544
|
-
|
|
29545
|
-
|
|
29546
|
-
|
|
29547
|
-
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
|
|
29555
|
-
|
|
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 =
|
|
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
|
-
|
|
72153
|
-
|
|
72154
|
-
|
|
72155
|
-
|
|
72156
|
-
|
|
72157
|
-
|
|
72158
|
-
|
|
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
|
-
|
|
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:
|
|
72998
|
+
isAnimated: agentVisual.isAnimated,
|
|
72881
72999
|
renderFrame({ animationTimeMs }) {
|
|
72882
73000
|
try {
|
|
72883
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
75352
|
+
// Phase 1: pace from start of previous prompt (`--wait-between-prompts`)
|
|
75353
|
+
if (Date.now() < waitBetweenPromptsEndTime) {
|
|
75251
75354
|
await sleepWithCountdown({
|
|
75252
|
-
durationMs:
|
|
75355
|
+
durationMs: waitBetweenPrompts,
|
|
75356
|
+
deadlineTimeMs: waitBetweenPromptsEndTime,
|
|
75253
75357
|
waitKind: 'between-prompts',
|
|
75254
75358
|
isRichUiEnabled,
|
|
75255
75359
|
uiHandle,
|
|
75256
75360
|
});
|
|
75257
75361
|
}
|
|
75258
|
-
|
|
75362
|
+
// Phase 2: rest from end of previous prompt (`--wait-after-prompt`)
|
|
75363
|
+
if (Date.now() < waitAfterPromptEndTime) {
|
|
75259
75364
|
await sleepWithCountdown({
|
|
75260
|
-
durationMs:
|
|
75365
|
+
durationMs: waitAfterPrompt,
|
|
75366
|
+
deadlineTimeMs: waitAfterPromptEndTime,
|
|
75261
75367
|
waitKind: 'after-prompt',
|
|
75262
75368
|
isRichUiEnabled,
|
|
75263
75369
|
uiHandle,
|