@promptbook/cli 0.112.0-65 → 0.112.0-66
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/esm/index.es.js +1233 -298
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-agent-messages/ui/buildAgentRunInitialsVisual.d.ts +4 -0
- package/esm/scripts/run-agent-messages/ui/buildAgentRunUiFrame.d.ts +5 -0
- package/esm/scripts/run-agent-messages/ui/loadAgentRunUiMetadata.d.ts +16 -0
- package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +6 -0
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +1 -0
- package/esm/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +49 -0
- package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +4 -1
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/minecraft2AvatarVisual.d.ts +7 -0
- package/esm/src/avatars/visuals/minecraftAvatarVisualShared.d.ts +48 -0
- package/esm/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +1 -1
- package/esm/src/config.d.ts +3 -3
- package/esm/src/utils/files/getPromptbookTempPath.d.ts +24 -0
- package/esm/src/utils/files/getPromptbookTempPath.test.d.ts +1 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +1232 -297
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-agent-messages/ui/buildAgentRunInitialsVisual.d.ts +4 -0
- package/umd/scripts/run-agent-messages/ui/buildAgentRunUiFrame.d.ts +5 -0
- package/umd/scripts/run-agent-messages/ui/loadAgentRunUiMetadata.d.ts +16 -0
- package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +6 -0
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +1 -0
- package/umd/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +49 -0
- package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +4 -1
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/minecraft2AvatarVisual.d.ts +7 -0
- package/umd/src/avatars/visuals/minecraftAvatarVisualShared.d.ts +48 -0
- package/umd/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +1 -1
- package/umd/src/config.d.ts +3 -3
- package/umd/src/utils/files/getPromptbookTempPath.d.ts +24 -0
- package/umd/src/utils/files/getPromptbookTempPath.test.d.ts +1 -0
- package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
* @generated
|
|
61
61
|
* @see https://github.com/webgptorg/promptbook
|
|
62
62
|
*/
|
|
63
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
63
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-66';
|
|
64
64
|
/**
|
|
65
65
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
66
66
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -993,6 +993,45 @@
|
|
|
993
993
|
}
|
|
994
994
|
// TODO: Maybe implement by mix+hsl
|
|
995
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Relative directory name without the `./` prefix for Git ignore rules and glob patterns.
|
|
998
|
+
*
|
|
999
|
+
* @private internal utility for Promptbook-owned temp files
|
|
1000
|
+
*/
|
|
1001
|
+
const PROMPTBOOK_TEMP_DIRECTORY_NAME = '.promptbook';
|
|
1002
|
+
/**
|
|
1003
|
+
* Builds one project-relative path inside the shared Promptbook working directory.
|
|
1004
|
+
*
|
|
1005
|
+
* @private internal utility for Promptbook-owned temp files
|
|
1006
|
+
*/
|
|
1007
|
+
function getPromptbookTempPath(...pathSegments) {
|
|
1008
|
+
return `./${getPromptbookTempPosixPath(...pathSegments)}`;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Builds one absolute filesystem path inside the shared Promptbook working directory for a project root.
|
|
1012
|
+
*
|
|
1013
|
+
* @private internal utility for Promptbook-owned temp files
|
|
1014
|
+
*/
|
|
1015
|
+
function resolvePromptbookTempPath(projectPath, ...pathSegments) {
|
|
1016
|
+
return path.join(projectPath, PROMPTBOOK_TEMP_DIRECTORY_NAME, ...pathSegments);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Builds one POSIX path fragment inside the shared Promptbook working directory for globs and generated text files.
|
|
1020
|
+
*
|
|
1021
|
+
* @private internal utility for Promptbook-owned temp files
|
|
1022
|
+
*/
|
|
1023
|
+
function getPromptbookTempPosixPath(...pathSegments) {
|
|
1024
|
+
return path.posix.join(PROMPTBOOK_TEMP_DIRECTORY_NAME, ...pathSegments);
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Builds one rooted `.gitignore` rule targeting a path inside the shared Promptbook working directory.
|
|
1028
|
+
*
|
|
1029
|
+
* @private internal utility for Promptbook-owned temp files
|
|
1030
|
+
*/
|
|
1031
|
+
function getPromptbookTempGitignoreRule(...pathSegments) {
|
|
1032
|
+
return `/${getPromptbookTempPosixPath(...pathSegments)}`;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
996
1035
|
/**
|
|
997
1036
|
* Returns the same value that is passed as argument.
|
|
998
1037
|
* No side effects.
|
|
@@ -1235,7 +1274,6 @@
|
|
|
1235
1274
|
*/
|
|
1236
1275
|
const DEFAULT_AGENTS_DIRNAME = './agents';
|
|
1237
1276
|
// <- TODO: [🕝] Make also `AGENTS_DIRNAME_ALTERNATIVES`
|
|
1238
|
-
// TODO: Just `.promptbook` in config, hardcode subfolders like `download-cache` or `execution-cache`
|
|
1239
1277
|
/**
|
|
1240
1278
|
* Where to store the temporary downloads
|
|
1241
1279
|
*
|
|
@@ -1243,7 +1281,7 @@
|
|
|
1243
1281
|
*
|
|
1244
1282
|
* @public exported from `@promptbook/core`
|
|
1245
1283
|
*/
|
|
1246
|
-
const DEFAULT_DOWNLOAD_CACHE_DIRNAME = '
|
|
1284
|
+
const DEFAULT_DOWNLOAD_CACHE_DIRNAME = getPromptbookTempPath('download-cache');
|
|
1247
1285
|
/**
|
|
1248
1286
|
* Where to store the cache of executions for promptbook CLI
|
|
1249
1287
|
*
|
|
@@ -1251,7 +1289,7 @@
|
|
|
1251
1289
|
*
|
|
1252
1290
|
* @public exported from `@promptbook/core`
|
|
1253
1291
|
*/
|
|
1254
|
-
const DEFAULT_EXECUTION_CACHE_DIRNAME = '
|
|
1292
|
+
const DEFAULT_EXECUTION_CACHE_DIRNAME = getPromptbookTempPath('execution-cache');
|
|
1255
1293
|
/**
|
|
1256
1294
|
* Where to store the scrape cache
|
|
1257
1295
|
*
|
|
@@ -1259,7 +1297,7 @@
|
|
|
1259
1297
|
*
|
|
1260
1298
|
* @public exported from `@promptbook/core`
|
|
1261
1299
|
*/
|
|
1262
|
-
const DEFAULT_SCRAPE_CACHE_DIRNAME = '
|
|
1300
|
+
const DEFAULT_SCRAPE_CACHE_DIRNAME = getPromptbookTempPath('scrape-cache');
|
|
1263
1301
|
/**
|
|
1264
1302
|
* Id of application for the CLI when using remote server
|
|
1265
1303
|
*
|
|
@@ -2178,9 +2216,8 @@
|
|
|
2178
2216
|
'**/.git/**',
|
|
2179
2217
|
'**/.idea/**',
|
|
2180
2218
|
'**/.vscode/**',
|
|
2181
|
-
|
|
2219
|
+
`**/${getPromptbookTempPosixPath()}/**`,
|
|
2182
2220
|
'**/.next/**',
|
|
2183
|
-
'**/.tmp/**',
|
|
2184
2221
|
'**/tmp/**',
|
|
2185
2222
|
'**/coverage/**',
|
|
2186
2223
|
'**/dist/**',
|
|
@@ -3000,13 +3037,9 @@
|
|
|
3000
3037
|
*/
|
|
3001
3038
|
const GITIGNORE_FILE_PATH = '.gitignore';
|
|
3002
3039
|
/**
|
|
3003
|
-
* Promptbook
|
|
3040
|
+
* Shared Promptbook working directory that should stay out of version control.
|
|
3004
3041
|
*/
|
|
3005
|
-
const CODER_TEMP_GITIGNORE_RULE =
|
|
3006
|
-
/**
|
|
3007
|
-
* Promptbook coder cache directory that should stay out of version control.
|
|
3008
|
-
*/
|
|
3009
|
-
const CODER_CACHE_GITIGNORE_RULE = '/.promptbook/ptbk-coder';
|
|
3042
|
+
const CODER_TEMP_GITIGNORE_RULE = getPromptbookTempGitignoreRule();
|
|
3010
3043
|
/**
|
|
3011
3044
|
* Promptbook coder environment file that should stay out of version control.
|
|
3012
3045
|
*/
|
|
@@ -3016,7 +3049,7 @@
|
|
|
3016
3049
|
*/
|
|
3017
3050
|
const CODER_GITIGNORE_HEADER = '# Promptbook Coder';
|
|
3018
3051
|
/**
|
|
3019
|
-
* Ensures `.gitignore` contains the
|
|
3052
|
+
* Ensures `.gitignore` contains the shared Promptbook working-directory entry.
|
|
3020
3053
|
*
|
|
3021
3054
|
* @private function of `initializeCoderProjectConfiguration`
|
|
3022
3055
|
*/
|
|
@@ -3035,7 +3068,7 @@
|
|
|
3035
3068
|
* Returns the Promptbook coder gitignore rules that still need to be added.
|
|
3036
3069
|
*/
|
|
3037
3070
|
function getMissingCoderGitignoreRules(gitignoreContent) {
|
|
3038
|
-
const requiredRules = [CODER_TEMP_GITIGNORE_RULE,
|
|
3071
|
+
const requiredRules = [CODER_TEMP_GITIGNORE_RULE, CODER_ENV_GITIGNORE_RULE];
|
|
3039
3072
|
return requiredRules.filter((rule) => !hasGitignoreRule(gitignoreContent, rule));
|
|
3040
3073
|
}
|
|
3041
3074
|
/**
|
|
@@ -22691,6 +22724,177 @@
|
|
|
22691
22724
|
context.restore();
|
|
22692
22725
|
}
|
|
22693
22726
|
|
|
22727
|
+
/* eslint-disable no-magic-numbers */
|
|
22728
|
+
/**
|
|
22729
|
+
* Builds the seeded six-face texture pack used by the Minecraft-style head cuboid.
|
|
22730
|
+
*
|
|
22731
|
+
* @param random Seeded random generator.
|
|
22732
|
+
* @param palette Derived avatar palette.
|
|
22733
|
+
* @param hasHeadband Whether the generated avatar should include a colored headband.
|
|
22734
|
+
* @returns Head cuboid textures.
|
|
22735
|
+
*
|
|
22736
|
+
* @private helper of the Minecraft avatar visuals
|
|
22737
|
+
*/
|
|
22738
|
+
function createMinecraftHeadTextures(random, palette, hasHeadband) {
|
|
22739
|
+
const faceTexture = createMinecraftFaceTexture(random, palette, hasHeadband);
|
|
22740
|
+
const hairColor = random() < 0.5 ? palette.primary : palette.secondary;
|
|
22741
|
+
const skinColor = palette.highlight;
|
|
22742
|
+
const headbandColor = hasHeadband ? palette.accent : hairColor;
|
|
22743
|
+
const sideTexture = createFilledTexture(skinColor);
|
|
22744
|
+
const backTexture = createFilledTexture(skinColor);
|
|
22745
|
+
const topTexture = createFilledTexture(hairColor);
|
|
22746
|
+
const bottomTexture = createFilledTexture(`${palette.shadow}cc`);
|
|
22747
|
+
fillTextureRect(sideTexture, 0, 0, 8, 3, hairColor);
|
|
22748
|
+
fillTextureRect(backTexture, 0, 0, 8, 5, hairColor);
|
|
22749
|
+
fillTextureRect(backTexture, 1, 5, 6, 1, hairColor);
|
|
22750
|
+
if (hasHeadband) {
|
|
22751
|
+
fillTextureRect(sideTexture, 0, 2, 8, 1, headbandColor);
|
|
22752
|
+
fillTextureRect(backTexture, 0, 2, 8, 1, headbandColor);
|
|
22753
|
+
fillTextureRect(topTexture, 0, 4, 8, 1, headbandColor);
|
|
22754
|
+
}
|
|
22755
|
+
sideTexture[4][4] = `${palette.shadow}99`;
|
|
22756
|
+
sideTexture[5][4] = `${palette.shadow}cc`;
|
|
22757
|
+
backTexture[6][2] = `${palette.shadow}99`;
|
|
22758
|
+
backTexture[6][5] = `${palette.shadow}99`;
|
|
22759
|
+
return {
|
|
22760
|
+
front: faceTexture,
|
|
22761
|
+
back: backTexture,
|
|
22762
|
+
left: sideTexture,
|
|
22763
|
+
right: mirrorMinecraftTexture(sideTexture),
|
|
22764
|
+
top: topTexture,
|
|
22765
|
+
bottom: bottomTexture,
|
|
22766
|
+
};
|
|
22767
|
+
}
|
|
22768
|
+
/**
|
|
22769
|
+
* Builds the seeded six-face texture pack used by the Minecraft-style torso cuboid.
|
|
22770
|
+
*
|
|
22771
|
+
* @param random Seeded random generator.
|
|
22772
|
+
* @param palette Derived avatar palette.
|
|
22773
|
+
* @returns Torso cuboid textures.
|
|
22774
|
+
*
|
|
22775
|
+
* @private helper of the Minecraft avatar visuals
|
|
22776
|
+
*/
|
|
22777
|
+
function createMinecraftTorsoTextures(random, palette) {
|
|
22778
|
+
const frontTexture = createMinecraftShirtTexture(random, palette);
|
|
22779
|
+
const sideTexture = createFilledTexture(palette.primary);
|
|
22780
|
+
const backTexture = createFilledTexture(palette.primary);
|
|
22781
|
+
const topTexture = createFilledTexture(`${palette.highlight}dd`);
|
|
22782
|
+
const bottomTexture = createFilledTexture(`${palette.shadow}dd`);
|
|
22783
|
+
const stripeColor = random() < 0.5 ? palette.secondary : palette.highlight;
|
|
22784
|
+
fillTextureRect(sideTexture, 0, 0, 8, 2, palette.shadow);
|
|
22785
|
+
fillTextureRect(backTexture, 0, 0, 8, 2, palette.shadow);
|
|
22786
|
+
fillTextureRect(backTexture, 3, 2, 2, 6, stripeColor);
|
|
22787
|
+
fillTextureRect(sideTexture, 4, 2, 1, 6, stripeColor);
|
|
22788
|
+
fillTextureRect(topTexture, 0, 0, 8, 2, palette.shadow);
|
|
22789
|
+
fillTextureRect(topTexture, 2, 2, 4, 4, stripeColor);
|
|
22790
|
+
return {
|
|
22791
|
+
front: frontTexture,
|
|
22792
|
+
back: backTexture,
|
|
22793
|
+
left: sideTexture,
|
|
22794
|
+
right: mirrorMinecraftTexture(sideTexture),
|
|
22795
|
+
top: topTexture,
|
|
22796
|
+
bottom: bottomTexture,
|
|
22797
|
+
};
|
|
22798
|
+
}
|
|
22799
|
+
/**
|
|
22800
|
+
* Mirrors one Minecraft texture horizontally.
|
|
22801
|
+
*
|
|
22802
|
+
* @param texture Source texture.
|
|
22803
|
+
* @returns Mirrored texture copy.
|
|
22804
|
+
*
|
|
22805
|
+
* @private helper of the Minecraft avatar visuals
|
|
22806
|
+
*/
|
|
22807
|
+
function mirrorMinecraftTexture(texture) {
|
|
22808
|
+
return texture.map((row) => [...row].reverse());
|
|
22809
|
+
}
|
|
22810
|
+
/**
|
|
22811
|
+
* Creates the front-face pixel texture for the cube head.
|
|
22812
|
+
*
|
|
22813
|
+
* @param random Seeded random generator.
|
|
22814
|
+
* @param palette Derived avatar palette.
|
|
22815
|
+
* @param hasHeadband Whether the avatar should render a headband row.
|
|
22816
|
+
* @returns 8x8 pixel texture.
|
|
22817
|
+
*
|
|
22818
|
+
* @private helper of the Minecraft avatar visuals
|
|
22819
|
+
*/
|
|
22820
|
+
function createMinecraftFaceTexture(random, palette, hasHeadband) {
|
|
22821
|
+
const texture = createFilledTexture(palette.highlight);
|
|
22822
|
+
const hairlineColor = random() < 0.5 ? palette.primary : palette.secondary;
|
|
22823
|
+
const cheekColor = random() < 0.5 ? `${palette.accent}bb` : `${palette.secondary}bb`;
|
|
22824
|
+
fillTextureRect(texture, 0, 0, 8, 2, hairlineColor);
|
|
22825
|
+
texture[2][0] = hairlineColor;
|
|
22826
|
+
texture[2][7] = hairlineColor;
|
|
22827
|
+
texture[3][0] = hairlineColor;
|
|
22828
|
+
texture[3][7] = hairlineColor;
|
|
22829
|
+
if (hasHeadband) {
|
|
22830
|
+
fillTextureRect(texture, 0, 2, 8, 1, palette.accent);
|
|
22831
|
+
}
|
|
22832
|
+
texture[3][2] = palette.ink;
|
|
22833
|
+
texture[3][5] = palette.ink;
|
|
22834
|
+
texture[4][2] = '#ffffff';
|
|
22835
|
+
texture[4][5] = '#ffffff';
|
|
22836
|
+
texture[5][1] = cheekColor;
|
|
22837
|
+
texture[5][6] = cheekColor;
|
|
22838
|
+
texture[5][3] = palette.shadow;
|
|
22839
|
+
texture[5][4] = palette.shadow;
|
|
22840
|
+
texture[6][3] = palette.shadow;
|
|
22841
|
+
texture[6][4] = palette.shadow;
|
|
22842
|
+
return texture;
|
|
22843
|
+
}
|
|
22844
|
+
/**
|
|
22845
|
+
* Creates the front-face pixel texture for the torso.
|
|
22846
|
+
*
|
|
22847
|
+
* @param random Seeded random generator.
|
|
22848
|
+
* @param palette Derived avatar palette.
|
|
22849
|
+
* @returns 8x8 torso texture.
|
|
22850
|
+
*
|
|
22851
|
+
* @private helper of the Minecraft avatar visuals
|
|
22852
|
+
*/
|
|
22853
|
+
function createMinecraftShirtTexture(random, palette) {
|
|
22854
|
+
const texture = createFilledTexture(palette.primary);
|
|
22855
|
+
const stripeColor = random() < 0.5 ? palette.secondary : palette.highlight;
|
|
22856
|
+
fillTextureRect(texture, 0, 0, 8, 2, palette.shadow);
|
|
22857
|
+
for (let rowIndex = 2; rowIndex < 8; rowIndex++) {
|
|
22858
|
+
texture[rowIndex][3] = stripeColor;
|
|
22859
|
+
texture[rowIndex][4] = stripeColor;
|
|
22860
|
+
}
|
|
22861
|
+
texture[4][1] = palette.accent;
|
|
22862
|
+
texture[4][6] = palette.accent;
|
|
22863
|
+
texture[5][2] = palette.highlight;
|
|
22864
|
+
texture[5][5] = palette.highlight;
|
|
22865
|
+
return texture;
|
|
22866
|
+
}
|
|
22867
|
+
/**
|
|
22868
|
+
* Creates one solid-color 8x8 Minecraft texture.
|
|
22869
|
+
*
|
|
22870
|
+
* @param color Fill color.
|
|
22871
|
+
* @returns Filled 8x8 texture.
|
|
22872
|
+
*
|
|
22873
|
+
* @private helper of the Minecraft avatar visuals
|
|
22874
|
+
*/
|
|
22875
|
+
function createFilledTexture(color) {
|
|
22876
|
+
return Array.from({ length: 8 }, () => Array.from({ length: 8 }, () => color));
|
|
22877
|
+
}
|
|
22878
|
+
/**
|
|
22879
|
+
* Fills one rectangular area inside a mutable Minecraft texture.
|
|
22880
|
+
*
|
|
22881
|
+
* @param texture Mutable target texture.
|
|
22882
|
+
* @param x Left texture coordinate.
|
|
22883
|
+
* @param y Top texture coordinate.
|
|
22884
|
+
* @param width Rectangle width.
|
|
22885
|
+
* @param height Rectangle height.
|
|
22886
|
+
* @param color Fill color.
|
|
22887
|
+
*
|
|
22888
|
+
* @private helper of the Minecraft avatar visuals
|
|
22889
|
+
*/
|
|
22890
|
+
function fillTextureRect(texture, x, y, width, height, color) {
|
|
22891
|
+
for (let rowIndex = y; rowIndex < y + height; rowIndex++) {
|
|
22892
|
+
for (let columnIndex = x; columnIndex < x + width; columnIndex++) {
|
|
22893
|
+
texture[rowIndex][columnIndex] = color;
|
|
22894
|
+
}
|
|
22895
|
+
}
|
|
22896
|
+
}
|
|
22897
|
+
|
|
22694
22898
|
/* eslint-disable no-magic-numbers */
|
|
22695
22899
|
/**
|
|
22696
22900
|
* Minecraft-style 3D avatar visual.
|
|
@@ -22715,8 +22919,8 @@
|
|
|
22715
22919
|
const bodyX = size * 0.33;
|
|
22716
22920
|
const bodyY = headY + headSize * 0.96;
|
|
22717
22921
|
const hasHeadband = random() < 0.5;
|
|
22718
|
-
const
|
|
22719
|
-
const
|
|
22922
|
+
const headTextures = createMinecraftHeadTextures(createRandom('minecraft-face'), palette, hasHeadband);
|
|
22923
|
+
const torsoTextures = createMinecraftTorsoTextures(createRandom('minecraft-shirt'), palette);
|
|
22720
22924
|
drawAvatarFrame(context, size, palette);
|
|
22721
22925
|
const spotlight = context.createRadialGradient(size * 0.5, size * 0.18, size * 0.05, size * 0.5, size * 0.18, size * 0.5);
|
|
22722
22926
|
spotlight.addColorStop(0, `${palette.highlight}66`);
|
|
@@ -22736,7 +22940,7 @@
|
|
|
22736
22940
|
width: bodyWidth,
|
|
22737
22941
|
height: bodyHeight,
|
|
22738
22942
|
depth: bodyDepth,
|
|
22739
|
-
frontTexture:
|
|
22943
|
+
frontTexture: torsoTextures.front,
|
|
22740
22944
|
topColor: `${palette.highlight}cc`,
|
|
22741
22945
|
sideColor: `${palette.secondary}dd`,
|
|
22742
22946
|
outlineColor: `${palette.shadow}aa`,
|
|
@@ -22747,7 +22951,7 @@
|
|
|
22747
22951
|
width: headSize,
|
|
22748
22952
|
height: headSize,
|
|
22749
22953
|
depth,
|
|
22750
|
-
frontTexture:
|
|
22954
|
+
frontTexture: headTextures.front,
|
|
22751
22955
|
topColor: `${palette.highlight}ee`,
|
|
22752
22956
|
sideColor: `${palette.secondary}ee`,
|
|
22753
22957
|
outlineColor: `${palette.shadow}cc`,
|
|
@@ -22812,72 +23016,505 @@
|
|
|
22812
23016
|
context.closePath();
|
|
22813
23017
|
context.stroke();
|
|
22814
23018
|
}
|
|
23019
|
+
|
|
23020
|
+
/* eslint-disable no-magic-numbers */
|
|
22815
23021
|
/**
|
|
22816
|
-
*
|
|
23022
|
+
* Fixed scene camera distance used for the proper-3D projection.
|
|
22817
23023
|
*
|
|
22818
|
-
* @
|
|
23024
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23025
|
+
*/
|
|
23026
|
+
const CAMERA_DISTANCE_RATIO = 1.4;
|
|
23027
|
+
/**
|
|
23028
|
+
* Shared light direction used to shade projected cuboid faces.
|
|
23029
|
+
*
|
|
23030
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23031
|
+
*/
|
|
23032
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
23033
|
+
x: 0.4,
|
|
23034
|
+
y: -0.65,
|
|
23035
|
+
z: 0.92,
|
|
23036
|
+
});
|
|
23037
|
+
/**
|
|
23038
|
+
* Minecraft 3D 2 avatar visual.
|
|
23039
|
+
*
|
|
23040
|
+
* @private built-in avatar visual
|
|
23041
|
+
*/
|
|
23042
|
+
const minecraft2AvatarVisual = {
|
|
23043
|
+
id: 'minecraft2',
|
|
23044
|
+
title: 'Minecraft 3D 2',
|
|
23045
|
+
description: 'Proper 3D Minecraft-style portrait with textured cuboids and pointer-driven head turns.',
|
|
23046
|
+
isAnimated: true,
|
|
23047
|
+
supportsPointerTracking: true,
|
|
23048
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
23049
|
+
const spotlightY = size * 0.22;
|
|
23050
|
+
const headRandom = createRandom('minecraft2-head');
|
|
23051
|
+
const hasHeadband = headRandom() < 0.5;
|
|
23052
|
+
const headTextures = createMinecraftHeadTextures(createRandom('minecraft2-head-textures'), palette, hasHeadband);
|
|
23053
|
+
const torsoTextures = createMinecraftTorsoTextures(createRandom('minecraft2-body-textures'), palette);
|
|
23054
|
+
const bob = Math.sin(timeMs / 880) * size * 0.014;
|
|
23055
|
+
const bodyYaw = -0.24 + Math.sin(timeMs / 2300) * 0.06 + interaction.bodyOffsetX * 0.16;
|
|
23056
|
+
const bodyPitch = -0.12 + Math.cos(timeMs / 2800) * 0.02 - interaction.bodyOffsetY * 0.06;
|
|
23057
|
+
const headYaw = -0.18 + Math.sin(timeMs / 1900 + 0.6) * 0.05 + interaction.gazeX * 0.62;
|
|
23058
|
+
const headPitch = -0.12 + Math.cos(timeMs / 2400 + 1.1) * 0.03 - interaction.gazeY * 0.38;
|
|
23059
|
+
const sceneCenterX = size * 0.5;
|
|
23060
|
+
const sceneCenterY = size * 0.57;
|
|
23061
|
+
const bodyWidth = size * 0.225;
|
|
23062
|
+
const bodyHeight = size * 0.245;
|
|
23063
|
+
const bodyDepth = size * 0.145;
|
|
23064
|
+
const headSize = size * 0.24;
|
|
23065
|
+
const headLift = size * 0.205;
|
|
23066
|
+
const headForwardShift = interaction.intensity * size * 0.018;
|
|
23067
|
+
const sceneCuboids = [
|
|
23068
|
+
{
|
|
23069
|
+
center: {
|
|
23070
|
+
x: interaction.bodyOffsetX * size * 0.026,
|
|
23071
|
+
y: size * 0.05 + interaction.bodyOffsetY * size * 0.018 + bob,
|
|
23072
|
+
z: 0,
|
|
23073
|
+
},
|
|
23074
|
+
width: bodyWidth,
|
|
23075
|
+
height: bodyHeight,
|
|
23076
|
+
depth: bodyDepth,
|
|
23077
|
+
rotationX: bodyPitch,
|
|
23078
|
+
rotationY: bodyYaw,
|
|
23079
|
+
textures: torsoTextures,
|
|
23080
|
+
outlineColor: `${palette.shadow}cc`,
|
|
23081
|
+
},
|
|
23082
|
+
{
|
|
23083
|
+
center: {
|
|
23084
|
+
x: interaction.bodyOffsetX * size * 0.018 + interaction.gazeX * size * 0.016,
|
|
23085
|
+
y: -headLift + bob * 1.15,
|
|
23086
|
+
z: headForwardShift,
|
|
23087
|
+
},
|
|
23088
|
+
width: headSize,
|
|
23089
|
+
height: headSize,
|
|
23090
|
+
depth: headSize,
|
|
23091
|
+
rotationX: headPitch,
|
|
23092
|
+
rotationY: headYaw,
|
|
23093
|
+
textures: headTextures,
|
|
23094
|
+
outlineColor: `${palette.shadow}dd`,
|
|
23095
|
+
},
|
|
23096
|
+
];
|
|
23097
|
+
const visibleFaces = sceneCuboids
|
|
23098
|
+
.flatMap((cuboid) => resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY))
|
|
23099
|
+
.sort((firstFace, secondFace) => firstFace.averageDepth - secondFace.averageDepth);
|
|
23100
|
+
drawAvatarFrame(context, size, palette);
|
|
23101
|
+
drawMinecraftBackdrop(context, size, palette, sceneCenterX, spotlightY, interaction, timeMs);
|
|
23102
|
+
drawMinecraftShadow(context, size, palette, interaction, timeMs);
|
|
23103
|
+
for (const visibleFace of visibleFaces) {
|
|
23104
|
+
drawTexturedProjectedFace(context, visibleFace);
|
|
23105
|
+
}
|
|
23106
|
+
},
|
|
23107
|
+
};
|
|
23108
|
+
/**
|
|
23109
|
+
* Draws the shared background atmosphere behind the Minecraft 3D 2 portrait.
|
|
23110
|
+
*
|
|
23111
|
+
* @param context Canvas 2D context.
|
|
23112
|
+
* @param size Canvas size in CSS pixels.
|
|
22819
23113
|
* @param palette Derived avatar palette.
|
|
22820
|
-
* @param
|
|
22821
|
-
* @
|
|
23114
|
+
* @param sceneCenterX Horizontal scene center.
|
|
23115
|
+
* @param spotlightY Vertical spotlight anchor.
|
|
23116
|
+
* @param interaction Smoothed pointer-aware interaction state.
|
|
23117
|
+
* @param timeMs Current animation time in milliseconds.
|
|
22822
23118
|
*
|
|
22823
|
-
* @private helper of `
|
|
23119
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
22824
23120
|
*/
|
|
22825
|
-
function
|
|
22826
|
-
const
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
23121
|
+
function drawMinecraftBackdrop(context, size, palette, sceneCenterX, spotlightY, interaction, timeMs) {
|
|
23122
|
+
const spotlightGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.08, spotlightY + interaction.gazeY * size * 0.05, size * 0.03, sceneCenterX, spotlightY, size * 0.52);
|
|
23123
|
+
spotlightGradient.addColorStop(0, `${palette.highlight}66`);
|
|
23124
|
+
spotlightGradient.addColorStop(0.42, `${palette.accent}1d`);
|
|
23125
|
+
spotlightGradient.addColorStop(1, `${palette.highlight}00`);
|
|
23126
|
+
context.fillStyle = spotlightGradient;
|
|
23127
|
+
context.fillRect(0, 0, size, size);
|
|
23128
|
+
const rimGradient = context.createLinearGradient(0, size * 0.14, 0, size * 0.92);
|
|
23129
|
+
rimGradient.addColorStop(0, `${palette.highlight}12`);
|
|
23130
|
+
rimGradient.addColorStop(0.55, `${palette.secondary}0a`);
|
|
23131
|
+
rimGradient.addColorStop(1, `${palette.shadow}00`);
|
|
23132
|
+
context.fillStyle = rimGradient;
|
|
23133
|
+
context.fillRect(0, 0, size, size);
|
|
23134
|
+
context.save();
|
|
23135
|
+
context.globalAlpha = 0.08 + interaction.intensity * 0.04;
|
|
23136
|
+
context.fillStyle = palette.highlight;
|
|
23137
|
+
context.beginPath();
|
|
23138
|
+
context.arc(size * 0.72 + Math.sin(timeMs / 1600) * size * 0.03, size * 0.2 + Math.cos(timeMs / 1400) * size * 0.018, size * 0.025, 0, Math.PI * 2);
|
|
23139
|
+
context.fill();
|
|
23140
|
+
context.restore();
|
|
23141
|
+
}
|
|
23142
|
+
/**
|
|
23143
|
+
* Draws the soft floor shadow used to anchor the cuboids in the frame.
|
|
23144
|
+
*
|
|
23145
|
+
* @param context Canvas 2D context.
|
|
23146
|
+
* @param size Canvas size in CSS pixels.
|
|
23147
|
+
* @param palette Derived avatar palette.
|
|
23148
|
+
* @param interaction Smoothed pointer-aware interaction state.
|
|
23149
|
+
* @param timeMs Current animation time in milliseconds.
|
|
23150
|
+
*
|
|
23151
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23152
|
+
*/
|
|
23153
|
+
function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
|
|
23154
|
+
context.save();
|
|
23155
|
+
context.fillStyle = `${palette.shadow}66`;
|
|
23156
|
+
context.filter = `blur(${size * 0.02}px)`;
|
|
23157
|
+
context.beginPath();
|
|
23158
|
+
context.ellipse(size * 0.5 + interaction.gazeX * size * 0.03, size * 0.85 + Math.sin(timeMs / 880) * size * 0.01, size * (0.16 + interaction.intensity * 0.015), size * 0.055, 0, 0, Math.PI * 2);
|
|
23159
|
+
context.fill();
|
|
23160
|
+
context.restore();
|
|
23161
|
+
}
|
|
23162
|
+
/**
|
|
23163
|
+
* Resolves all visible projected faces for one scene cuboid.
|
|
23164
|
+
*
|
|
23165
|
+
* @param cuboid Scene cuboid definition.
|
|
23166
|
+
* @param size Canvas size in CSS pixels.
|
|
23167
|
+
* @param sceneCenterX Horizontal scene center.
|
|
23168
|
+
* @param sceneCenterY Vertical scene center.
|
|
23169
|
+
* @returns Visible faces sorted later by depth.
|
|
23170
|
+
*
|
|
23171
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23172
|
+
*/
|
|
23173
|
+
function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
|
|
23174
|
+
const halfWidth = cuboid.width / 2;
|
|
23175
|
+
const halfHeight = cuboid.height / 2;
|
|
23176
|
+
const halfDepth = cuboid.depth / 2;
|
|
23177
|
+
const faceDefinitions = [
|
|
23178
|
+
{
|
|
23179
|
+
texture: cuboid.textures.front,
|
|
23180
|
+
corners: [
|
|
23181
|
+
{ x: -halfWidth, y: -halfHeight, z: halfDepth },
|
|
23182
|
+
{ x: halfWidth, y: -halfHeight, z: halfDepth },
|
|
23183
|
+
{ x: halfWidth, y: halfHeight, z: halfDepth },
|
|
23184
|
+
{ x: -halfWidth, y: halfHeight, z: halfDepth },
|
|
23185
|
+
],
|
|
23186
|
+
},
|
|
23187
|
+
{
|
|
23188
|
+
texture: cuboid.textures.back,
|
|
23189
|
+
corners: [
|
|
23190
|
+
{ x: halfWidth, y: -halfHeight, z: -halfDepth },
|
|
23191
|
+
{ x: -halfWidth, y: -halfHeight, z: -halfDepth },
|
|
23192
|
+
{ x: -halfWidth, y: halfHeight, z: -halfDepth },
|
|
23193
|
+
{ x: halfWidth, y: halfHeight, z: -halfDepth },
|
|
23194
|
+
],
|
|
23195
|
+
},
|
|
23196
|
+
{
|
|
23197
|
+
texture: cuboid.textures.right,
|
|
23198
|
+
corners: [
|
|
23199
|
+
{ x: halfWidth, y: -halfHeight, z: halfDepth },
|
|
23200
|
+
{ x: halfWidth, y: -halfHeight, z: -halfDepth },
|
|
23201
|
+
{ x: halfWidth, y: halfHeight, z: -halfDepth },
|
|
23202
|
+
{ x: halfWidth, y: halfHeight, z: halfDepth },
|
|
23203
|
+
],
|
|
23204
|
+
},
|
|
23205
|
+
{
|
|
23206
|
+
texture: cuboid.textures.left,
|
|
23207
|
+
corners: [
|
|
23208
|
+
{ x: -halfWidth, y: -halfHeight, z: -halfDepth },
|
|
23209
|
+
{ x: -halfWidth, y: -halfHeight, z: halfDepth },
|
|
23210
|
+
{ x: -halfWidth, y: halfHeight, z: halfDepth },
|
|
23211
|
+
{ x: -halfWidth, y: halfHeight, z: -halfDepth },
|
|
23212
|
+
],
|
|
23213
|
+
},
|
|
23214
|
+
{
|
|
23215
|
+
texture: cuboid.textures.top,
|
|
23216
|
+
corners: [
|
|
23217
|
+
{ x: -halfWidth, y: -halfHeight, z: -halfDepth },
|
|
23218
|
+
{ x: halfWidth, y: -halfHeight, z: -halfDepth },
|
|
23219
|
+
{ x: halfWidth, y: -halfHeight, z: halfDepth },
|
|
23220
|
+
{ x: -halfWidth, y: -halfHeight, z: halfDepth },
|
|
23221
|
+
],
|
|
23222
|
+
},
|
|
23223
|
+
{
|
|
23224
|
+
texture: cuboid.textures.bottom,
|
|
23225
|
+
corners: [
|
|
23226
|
+
{ x: -halfWidth, y: halfHeight, z: halfDepth },
|
|
23227
|
+
{ x: halfWidth, y: halfHeight, z: halfDepth },
|
|
23228
|
+
{ x: halfWidth, y: halfHeight, z: -halfDepth },
|
|
23229
|
+
{ x: -halfWidth, y: halfHeight, z: -halfDepth },
|
|
23230
|
+
],
|
|
23231
|
+
},
|
|
23232
|
+
];
|
|
23233
|
+
const visibleFaces = faceDefinitions
|
|
23234
|
+
.map((faceDefinition) => {
|
|
23235
|
+
const transformedCorners = faceDefinition.corners.map((corner) => transformScenePoint(corner, cuboid.center, cuboid.rotationX, cuboid.rotationY));
|
|
23236
|
+
const faceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
23237
|
+
if (faceNormal.z <= 0.02) {
|
|
23238
|
+
return null;
|
|
22832
23239
|
}
|
|
23240
|
+
const projectedCorners = transformedCorners.map((corner) => projectScenePoint(corner, size, sceneCenterX, sceneCenterY));
|
|
23241
|
+
return {
|
|
23242
|
+
corners: projectedCorners,
|
|
23243
|
+
texture: faceDefinition.texture,
|
|
23244
|
+
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
23245
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION), -1, 1),
|
|
23246
|
+
outlineColor: cuboid.outlineColor,
|
|
23247
|
+
};
|
|
23248
|
+
});
|
|
23249
|
+
return visibleFaces.filter((visibleFace) => visibleFace !== null);
|
|
23250
|
+
}
|
|
23251
|
+
/**
|
|
23252
|
+
* Draws one projected textured face by tessellating its texture cells into quads.
|
|
23253
|
+
*
|
|
23254
|
+
* @param context Canvas 2D context.
|
|
23255
|
+
* @param face Visible projected face.
|
|
23256
|
+
*
|
|
23257
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23258
|
+
*/
|
|
23259
|
+
function drawTexturedProjectedFace(context, face) {
|
|
23260
|
+
var _a;
|
|
23261
|
+
const rows = face.texture.length;
|
|
23262
|
+
const columns = ((_a = face.texture[0]) === null || _a === void 0 ? void 0 : _a.length) || 0;
|
|
23263
|
+
if (rows === 0 || columns === 0) {
|
|
23264
|
+
return;
|
|
22833
23265
|
}
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
23266
|
+
for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
|
|
23267
|
+
for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
|
|
23268
|
+
const startX = columnIndex / columns;
|
|
23269
|
+
const endX = (columnIndex + 1) / columns;
|
|
23270
|
+
const startY = rowIndex / rows;
|
|
23271
|
+
const endY = (rowIndex + 1) / rows;
|
|
23272
|
+
drawProjectedQuad(context, [
|
|
23273
|
+
interpolateProjectedQuad(face.corners, startX, startY),
|
|
23274
|
+
interpolateProjectedQuad(face.corners, endX, startY),
|
|
23275
|
+
interpolateProjectedQuad(face.corners, endX, endY),
|
|
23276
|
+
interpolateProjectedQuad(face.corners, startX, endY),
|
|
23277
|
+
], face.texture[rowIndex][columnIndex]);
|
|
22841
23278
|
}
|
|
22842
23279
|
}
|
|
22843
|
-
|
|
22844
|
-
|
|
22845
|
-
|
|
22846
|
-
|
|
22847
|
-
|
|
22848
|
-
|
|
22849
|
-
|
|
22850
|
-
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
23280
|
+
if (face.lightIntensity > 0) {
|
|
23281
|
+
drawProjectedQuad(context, face.corners, `rgba(255, 255, 255, ${0.15 * face.lightIntensity})`);
|
|
23282
|
+
}
|
|
23283
|
+
else if (face.lightIntensity < 0) {
|
|
23284
|
+
drawProjectedQuad(context, face.corners, `rgba(0, 0, 0, ${0.22 * Math.abs(face.lightIntensity)})`);
|
|
23285
|
+
}
|
|
23286
|
+
context.save();
|
|
23287
|
+
context.beginPath();
|
|
23288
|
+
context.moveTo(face.corners[0].x, face.corners[0].y);
|
|
23289
|
+
for (let cornerIndex = 1; cornerIndex < face.corners.length; cornerIndex++) {
|
|
23290
|
+
context.lineTo(face.corners[cornerIndex].x, face.corners[cornerIndex].y);
|
|
23291
|
+
}
|
|
23292
|
+
context.closePath();
|
|
23293
|
+
context.strokeStyle = face.outlineColor;
|
|
23294
|
+
context.lineWidth = Math.max(1.1, getProjectedQuadPerimeter(face.corners) * 0.0045);
|
|
23295
|
+
context.lineJoin = 'round';
|
|
23296
|
+
context.stroke();
|
|
23297
|
+
context.restore();
|
|
22854
23298
|
}
|
|
22855
23299
|
/**
|
|
22856
|
-
*
|
|
23300
|
+
* Draws one filled projected quad.
|
|
22857
23301
|
*
|
|
22858
|
-
* @param
|
|
22859
|
-
* @param
|
|
22860
|
-
* @
|
|
23302
|
+
* @param context Canvas 2D context.
|
|
23303
|
+
* @param corners Quad corners in clockwise order.
|
|
23304
|
+
* @param fillStyle CSS fill style.
|
|
22861
23305
|
*
|
|
22862
|
-
* @private helper of `
|
|
23306
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
22863
23307
|
*/
|
|
22864
|
-
function
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
22870
|
-
|
|
23308
|
+
function drawProjectedQuad(context, corners, fillStyle) {
|
|
23309
|
+
context.beginPath();
|
|
23310
|
+
context.moveTo(corners[0].x, corners[0].y);
|
|
23311
|
+
context.lineTo(corners[1].x, corners[1].y);
|
|
23312
|
+
context.lineTo(corners[2].x, corners[2].y);
|
|
23313
|
+
context.lineTo(corners[3].x, corners[3].y);
|
|
23314
|
+
context.closePath();
|
|
23315
|
+
context.fillStyle = fillStyle;
|
|
23316
|
+
context.fill();
|
|
23317
|
+
}
|
|
23318
|
+
/**
|
|
23319
|
+
* Interpolates one point inside a projected quad across both quad axes.
|
|
23320
|
+
*
|
|
23321
|
+
* @param corners Quad corners in clockwise order.
|
|
23322
|
+
* @param horizontalRatio Horizontal ratio in the range `[0, 1]`.
|
|
23323
|
+
* @param verticalRatio Vertical ratio in the range `[0, 1]`.
|
|
23324
|
+
* @returns Interpolated projected point.
|
|
23325
|
+
*
|
|
23326
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23327
|
+
*/
|
|
23328
|
+
function interpolateProjectedQuad(corners, horizontalRatio, verticalRatio) {
|
|
23329
|
+
const topPoint = interpolateProjectedPoint(corners[0], corners[1], horizontalRatio);
|
|
23330
|
+
const bottomPoint = interpolateProjectedPoint(corners[3], corners[2], horizontalRatio);
|
|
23331
|
+
return interpolateProjectedPoint(topPoint, bottomPoint, verticalRatio);
|
|
23332
|
+
}
|
|
23333
|
+
/**
|
|
23334
|
+
* Interpolates between two projected points.
|
|
23335
|
+
*
|
|
23336
|
+
* @param startPoint Start point.
|
|
23337
|
+
* @param endPoint End point.
|
|
23338
|
+
* @param ratio Interpolation ratio in the range `[0, 1]`.
|
|
23339
|
+
* @returns Interpolated projected point.
|
|
23340
|
+
*
|
|
23341
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23342
|
+
*/
|
|
23343
|
+
function interpolateProjectedPoint(startPoint, endPoint, ratio) {
|
|
23344
|
+
return {
|
|
23345
|
+
x: startPoint.x + (endPoint.x - startPoint.x) * ratio,
|
|
23346
|
+
y: startPoint.y + (endPoint.y - startPoint.y) * ratio,
|
|
23347
|
+
z: startPoint.z + (endPoint.z - startPoint.z) * ratio,
|
|
23348
|
+
};
|
|
23349
|
+
}
|
|
23350
|
+
/**
|
|
23351
|
+
* Projects one rotated scene point into canvas coordinates.
|
|
23352
|
+
*
|
|
23353
|
+
* @param point Scene point.
|
|
23354
|
+
* @param size Canvas size in CSS pixels.
|
|
23355
|
+
* @param sceneCenterX Horizontal scene center.
|
|
23356
|
+
* @param sceneCenterY Vertical scene center.
|
|
23357
|
+
* @returns Projected point.
|
|
23358
|
+
*
|
|
23359
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23360
|
+
*/
|
|
23361
|
+
function projectScenePoint(point, size, sceneCenterX, sceneCenterY) {
|
|
23362
|
+
const cameraDistance = size * CAMERA_DISTANCE_RATIO;
|
|
23363
|
+
const perspectiveScale = cameraDistance / Math.max(cameraDistance - point.z, cameraDistance * 0.35);
|
|
23364
|
+
return {
|
|
23365
|
+
x: sceneCenterX + point.x * perspectiveScale,
|
|
23366
|
+
y: sceneCenterY + point.y * perspectiveScale,
|
|
23367
|
+
z: point.z,
|
|
23368
|
+
};
|
|
23369
|
+
}
|
|
23370
|
+
/**
|
|
23371
|
+
* Applies the local cuboid rotations and translation to one scene point.
|
|
23372
|
+
*
|
|
23373
|
+
* @param localPoint Point in cuboid-local space.
|
|
23374
|
+
* @param center Cuboid center in scene space.
|
|
23375
|
+
* @param rotationX Cuboid pitch in radians.
|
|
23376
|
+
* @param rotationY Cuboid yaw in radians.
|
|
23377
|
+
* @returns Transformed scene-space point.
|
|
23378
|
+
*
|
|
23379
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23380
|
+
*/
|
|
23381
|
+
function transformScenePoint(localPoint, center, rotationX, rotationY) {
|
|
23382
|
+
const yawedPoint = rotatePointAroundY(localPoint, rotationY);
|
|
23383
|
+
const pitchedPoint = rotatePointAroundX(yawedPoint, rotationX);
|
|
23384
|
+
return {
|
|
23385
|
+
x: center.x + pitchedPoint.x,
|
|
23386
|
+
y: center.y + pitchedPoint.y,
|
|
23387
|
+
z: center.z + pitchedPoint.z,
|
|
23388
|
+
};
|
|
23389
|
+
}
|
|
23390
|
+
/**
|
|
23391
|
+
* Rotates one point around the local Y axis.
|
|
23392
|
+
*
|
|
23393
|
+
* @param point Source point.
|
|
23394
|
+
* @param angle Rotation angle in radians.
|
|
23395
|
+
* @returns Rotated point.
|
|
23396
|
+
*
|
|
23397
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23398
|
+
*/
|
|
23399
|
+
function rotatePointAroundY(point, angle) {
|
|
23400
|
+
const cosine = Math.cos(angle);
|
|
23401
|
+
const sine = Math.sin(angle);
|
|
23402
|
+
return {
|
|
23403
|
+
x: point.x * cosine + point.z * sine,
|
|
23404
|
+
y: point.y,
|
|
23405
|
+
z: -point.x * sine + point.z * cosine,
|
|
23406
|
+
};
|
|
23407
|
+
}
|
|
23408
|
+
/**
|
|
23409
|
+
* Rotates one point around the local X axis.
|
|
23410
|
+
*
|
|
23411
|
+
* @param point Source point.
|
|
23412
|
+
* @param angle Rotation angle in radians.
|
|
23413
|
+
* @returns Rotated point.
|
|
23414
|
+
*
|
|
23415
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23416
|
+
*/
|
|
23417
|
+
function rotatePointAroundX(point, angle) {
|
|
23418
|
+
const cosine = Math.cos(angle);
|
|
23419
|
+
const sine = Math.sin(angle);
|
|
23420
|
+
return {
|
|
23421
|
+
x: point.x,
|
|
23422
|
+
y: point.y * cosine - point.z * sine,
|
|
23423
|
+
z: point.y * sine + point.z * cosine,
|
|
23424
|
+
};
|
|
23425
|
+
}
|
|
23426
|
+
/**
|
|
23427
|
+
* Subtracts one 3D point from another.
|
|
23428
|
+
*
|
|
23429
|
+
* @param leftPoint Left point.
|
|
23430
|
+
* @param rightPoint Right point.
|
|
23431
|
+
* @returns Difference vector.
|
|
23432
|
+
*
|
|
23433
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23434
|
+
*/
|
|
23435
|
+
function subtractPoint3D(leftPoint, rightPoint) {
|
|
23436
|
+
return {
|
|
23437
|
+
x: leftPoint.x - rightPoint.x,
|
|
23438
|
+
y: leftPoint.y - rightPoint.y,
|
|
23439
|
+
z: leftPoint.z - rightPoint.z,
|
|
23440
|
+
};
|
|
23441
|
+
}
|
|
23442
|
+
/**
|
|
23443
|
+
* Computes the 3D cross product of two vectors.
|
|
23444
|
+
*
|
|
23445
|
+
* @param leftVector Left vector.
|
|
23446
|
+
* @param rightVector Right vector.
|
|
23447
|
+
* @returns Cross product.
|
|
23448
|
+
*
|
|
23449
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23450
|
+
*/
|
|
23451
|
+
function crossProduct3D(leftVector, rightVector) {
|
|
23452
|
+
return {
|
|
23453
|
+
x: leftVector.y * rightVector.z - leftVector.z * rightVector.y,
|
|
23454
|
+
y: leftVector.z * rightVector.x - leftVector.x * rightVector.z,
|
|
23455
|
+
z: leftVector.x * rightVector.y - leftVector.y * rightVector.x,
|
|
23456
|
+
};
|
|
23457
|
+
}
|
|
23458
|
+
/**
|
|
23459
|
+
* Computes the 3D dot product of two vectors.
|
|
23460
|
+
*
|
|
23461
|
+
* @param leftVector Left vector.
|
|
23462
|
+
* @param rightVector Right vector.
|
|
23463
|
+
* @returns Dot product.
|
|
23464
|
+
*
|
|
23465
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23466
|
+
*/
|
|
23467
|
+
function dotProduct3D(leftVector, rightVector) {
|
|
23468
|
+
return leftVector.x * rightVector.x + leftVector.y * rightVector.y + leftVector.z * rightVector.z;
|
|
23469
|
+
}
|
|
23470
|
+
/**
|
|
23471
|
+
* Normalizes one 3D vector while keeping zero vectors stable.
|
|
23472
|
+
*
|
|
23473
|
+
* @param vector Source vector.
|
|
23474
|
+
* @returns Normalized vector.
|
|
23475
|
+
*
|
|
23476
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23477
|
+
*/
|
|
23478
|
+
function normalizeVector3(vector) {
|
|
23479
|
+
const length = Math.hypot(vector.x, vector.y, vector.z);
|
|
23480
|
+
if (length === 0) {
|
|
23481
|
+
return vector;
|
|
22871
23482
|
}
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
|
|
23483
|
+
return {
|
|
23484
|
+
x: vector.x / length,
|
|
23485
|
+
y: vector.y / length,
|
|
23486
|
+
z: vector.z / length,
|
|
23487
|
+
};
|
|
23488
|
+
}
|
|
23489
|
+
/**
|
|
23490
|
+
* Clamps one number into the provided range.
|
|
23491
|
+
*
|
|
23492
|
+
* @param value Input value.
|
|
23493
|
+
* @param minimumValue Inclusive lower bound.
|
|
23494
|
+
* @param maximumValue Inclusive upper bound.
|
|
23495
|
+
* @returns Clamped value.
|
|
23496
|
+
*
|
|
23497
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23498
|
+
*/
|
|
23499
|
+
function clampNumber$1(value, minimumValue, maximumValue) {
|
|
23500
|
+
return Math.min(maximumValue, Math.max(minimumValue, value));
|
|
23501
|
+
}
|
|
23502
|
+
/**
|
|
23503
|
+
* Measures the perimeter of one projected quad.
|
|
23504
|
+
*
|
|
23505
|
+
* @param corners Quad corners.
|
|
23506
|
+
* @returns Perimeter length.
|
|
23507
|
+
*
|
|
23508
|
+
* @private helper of `minecraft2AvatarVisual`
|
|
23509
|
+
*/
|
|
23510
|
+
function getProjectedQuadPerimeter(corners) {
|
|
23511
|
+
let perimeter = 0;
|
|
23512
|
+
for (let cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
|
|
23513
|
+
const currentCorner = corners[cornerIndex];
|
|
23514
|
+
const nextCorner = corners[(cornerIndex + 1) % corners.length];
|
|
23515
|
+
perimeter += Math.hypot(nextCorner.x - currentCorner.x, nextCorner.y - currentCorner.y);
|
|
22875
23516
|
}
|
|
22876
|
-
|
|
22877
|
-
texture[4][6] = palette.accent;
|
|
22878
|
-
texture[5][2] = palette.highlight;
|
|
22879
|
-
texture[5][5] = palette.highlight;
|
|
22880
|
-
return texture;
|
|
23517
|
+
return perimeter;
|
|
22881
23518
|
}
|
|
22882
23519
|
|
|
22883
23520
|
/* eslint-disable no-magic-numbers */
|
|
@@ -24482,6 +25119,7 @@
|
|
|
24482
25119
|
octopus3AvatarVisual,
|
|
24483
25120
|
asciiOctopusAvatarVisual,
|
|
24484
25121
|
minecraftAvatarVisual,
|
|
25122
|
+
minecraft2AvatarVisual,
|
|
24485
25123
|
fractalAvatarVisual,
|
|
24486
25124
|
orbAvatarVisual,
|
|
24487
25125
|
];
|
|
@@ -26690,7 +27328,10 @@
|
|
|
26690
27328
|
*/
|
|
26691
27329
|
function buildTeamToolDescription(entry) {
|
|
26692
27330
|
const detailLines = collectTeamEntryDetails(entry).map(({ label, content }) => `${label}: ${content}`);
|
|
26693
|
-
return
|
|
27331
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
27332
|
+
Consult teammate ${entry.teammate.label}
|
|
27333
|
+
${block(detailLines.join('\n'))}
|
|
27334
|
+
`);
|
|
26694
27335
|
}
|
|
26695
27336
|
/**
|
|
26696
27337
|
* Collects structured teammate details that should stay visible to the model.
|
|
@@ -32187,7 +32828,10 @@
|
|
|
32187
32828
|
if (hiddenCount > 0) {
|
|
32188
32829
|
summaryRows.push(`...and ${hiddenCount} more.`);
|
|
32189
32830
|
}
|
|
32190
|
-
return
|
|
32831
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
32832
|
+
Found ${options.total} ${options.total === 1 ? 'timeout' : 'timeouts'}:
|
|
32833
|
+
${block(summaryRows.join('\n'))}
|
|
32834
|
+
`);
|
|
32191
32835
|
}
|
|
32192
32836
|
/**
|
|
32193
32837
|
* Formats one timeout row for assistant-visible timeout listings.
|
|
@@ -37178,8 +37822,10 @@
|
|
|
37178
37822
|
**Application mode:** ${serverInfo.isApplicationModeAllowed ? 'enabled' : 'disabled'}
|
|
37179
37823
|
${block(!runtime.configuration.isApplicationModeAllowed || runtime.configuration.collection === null
|
|
37180
37824
|
? ''
|
|
37181
|
-
:
|
|
37182
|
-
|
|
37825
|
+
: _spaceTrim.spaceTrim((nestedBlock) => `
|
|
37826
|
+
**Pipelines in collection:**
|
|
37827
|
+
${nestedBlock(serverInfo.pipelines.map((pipelineUrl) => `- ${pipelineUrl}`).join('\n'))}
|
|
37828
|
+
`))}
|
|
37183
37829
|
**Running executions:** ${serverInfo.runningExecutions}
|
|
37184
37830
|
|
|
37185
37831
|
---
|
|
@@ -50908,7 +51554,7 @@
|
|
|
50908
51554
|
*/
|
|
50909
51555
|
async function commitChanges(message, options) {
|
|
50910
51556
|
const projectPath = process.cwd();
|
|
50911
|
-
const commitMessagePath =
|
|
51557
|
+
const commitMessagePath = resolvePromptbookTempPath(projectPath, 'scripts', 'codex-prompts', `COMMIT_MESSAGE_${Date.now()}.txt`);
|
|
50912
51558
|
await promises.mkdir(path.dirname(commitMessagePath), { recursive: true });
|
|
50913
51559
|
await promises.writeFile(commitMessagePath, message, 'utf-8');
|
|
50914
51560
|
try {
|
|
@@ -51876,17 +52522,18 @@
|
|
|
51876
52522
|
|
|
51877
52523
|
unset GITHUB_TOKEN
|
|
51878
52524
|
|
|
51879
|
-
|
|
51880
|
-
|
|
51881
|
-
${block(options.prompt)}
|
|
51882
|
-
|
|
51883
|
-
${delimiter}
|
|
51884
|
-
)" \\
|
|
52525
|
+
# Avoid passing the prompt as one CLI argument because large agent prompts can exceed Windows/MSYS limits.
|
|
52526
|
+
copilot \\
|
|
51885
52527
|
--yolo \\
|
|
51886
52528
|
--no-ask-user \\
|
|
51887
52529
|
--no-color \\
|
|
51888
52530
|
--output-format json \\
|
|
51889
|
-
--stream off${modelArgument}${thinkingLevelArgument}
|
|
52531
|
+
--stream off${modelArgument}${thinkingLevelArgument} \\
|
|
52532
|
+
<<'${delimiter}'
|
|
52533
|
+
|
|
52534
|
+
${block(options.prompt)}
|
|
52535
|
+
|
|
52536
|
+
${delimiter}
|
|
51890
52537
|
`);
|
|
51891
52538
|
}
|
|
51892
52539
|
|
|
@@ -53165,148 +53812,14 @@
|
|
|
53165
53812
|
.map((line) => centerAnsiText(line, options.totalWidth));
|
|
53166
53813
|
}
|
|
53167
53814
|
|
|
53168
|
-
/**
|
|
53169
|
-
* Refresh cadence used only while the rich coder UI needs animated updates.
|
|
53170
|
-
*
|
|
53171
|
-
* @private internal constant of coder run UI
|
|
53172
|
-
*/
|
|
53173
|
-
const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = 300;
|
|
53174
|
-
/**
|
|
53175
|
-
* Phases that still benefit from automatic refreshes because the frame can change
|
|
53176
|
-
* over time even without new runner output.
|
|
53177
|
-
*
|
|
53178
|
-
* @private internal constant of coder run UI
|
|
53179
|
-
*/
|
|
53180
|
-
const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
|
|
53181
|
-
/**
|
|
53182
|
-
* Returns whether the rich coder UI should keep animating on its own.
|
|
53183
|
-
*
|
|
53184
|
-
* @private internal utility of coder run UI
|
|
53185
|
-
*/
|
|
53186
|
-
function isCoderRunUiAutoRefreshing(phase, pauseState) {
|
|
53187
|
-
// `PAUSING` still means the current task is winding down, so keep active
|
|
53188
|
-
// animations/timers running until the runner reaches the fully paused state.
|
|
53189
|
-
if (pauseState === 'PAUSED') {
|
|
53190
|
-
return false;
|
|
53191
|
-
}
|
|
53192
|
-
return AUTO_REFRESH_PHASES.includes(phase);
|
|
53193
|
-
}
|
|
53194
|
-
/**
|
|
53195
|
-
* Returns the automatic refresh interval for the current UI state.
|
|
53196
|
-
*
|
|
53197
|
-
* Waiting, paused, and completed states return `undefined` so the rich UI stays
|
|
53198
|
-
* perfectly still until actual state changes arrive.
|
|
53199
|
-
*
|
|
53200
|
-
* @private internal utility of coder run UI
|
|
53201
|
-
*/
|
|
53202
|
-
function getCoderRunUiAutoRefreshInterval(phase, pauseState) {
|
|
53203
|
-
return isCoderRunUiAutoRefreshing(phase, pauseState) ? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS : undefined;
|
|
53204
|
-
}
|
|
53205
|
-
|
|
53206
53815
|
/**
|
|
53207
53816
|
* Maximum number of output lines reserved for agent output in the UI.
|
|
53208
53817
|
*/
|
|
53209
53818
|
const MAX_VISIBLE_OUTPUT_LINES = 8;
|
|
53210
|
-
/**
|
|
53211
|
-
* Minimum width used for the rich coder-run frame.
|
|
53212
|
-
*/
|
|
53213
|
-
const MIN_FRAME_WIDTH = 56;
|
|
53214
|
-
/**
|
|
53215
|
-
* Maximum width used for the rich coder-run frame.
|
|
53216
|
-
*/
|
|
53217
|
-
const MAX_FRAME_WIDTH = 96;
|
|
53218
53819
|
/**
|
|
53219
53820
|
* Visible width reserved for aligned labels in the session box.
|
|
53220
53821
|
*/
|
|
53221
53822
|
const SESSION_LABEL_WIDTH = 8;
|
|
53222
|
-
/**
|
|
53223
|
-
* Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
|
|
53224
|
-
*/
|
|
53225
|
-
function buildCoderRunUiFrame(options) {
|
|
53226
|
-
const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
|
|
53227
|
-
const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
|
|
53228
|
-
const promptStatusPrefix = isPromptActive ? `${colors__default["default"].yellow(`${options.spinner} `)}` : '';
|
|
53229
|
-
const octopusAnimationFrame = isCoderRunUiAutoRefreshing(options.phase, options.pauseState)
|
|
53230
|
-
? options.animationFrame
|
|
53231
|
-
: 0;
|
|
53232
|
-
const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.statusMessage);
|
|
53233
|
-
const sessionLines = buildSessionLines(options, totalWidth, pausePresentation);
|
|
53234
|
-
const currentTaskLines = options.currentPromptLabel
|
|
53235
|
-
? [
|
|
53236
|
-
`${promptStatusPrefix}${colors__default["default"].bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
|
|
53237
|
-
`Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
|
|
53238
|
-
...options.detailLines.map((detailLine) => `• ${detailLine}`),
|
|
53239
|
-
]
|
|
53240
|
-
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
53241
|
-
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
53242
|
-
const controls = buildControlPills(pausePresentation.pauseControl, options.pendingEnterLabel).join(' ');
|
|
53243
|
-
const frame = [
|
|
53244
|
-
...buildCoderRunOctopusVisual({ totalWidth, animationFrame: octopusAnimationFrame }),
|
|
53245
|
-
'',
|
|
53246
|
-
...renderBox('Session', sessionLines, totalWidth, colors__default["default"].yellow.bold),
|
|
53247
|
-
...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors__default["default"].magenta.bold),
|
|
53248
|
-
...renderBox('Live output', visibleOutputLines, totalWidth, colors__default["default"].green.bold),
|
|
53249
|
-
];
|
|
53250
|
-
if (options.errors.length > 0) {
|
|
53251
|
-
frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors__default["default"].red('✗')} ${errorLine}`), totalWidth, colors__default["default"].red.bold));
|
|
53252
|
-
}
|
|
53253
|
-
frame.push(...renderBox('Controls', [controls], totalWidth, colors__default["default"].white.bold));
|
|
53254
|
-
return frame;
|
|
53255
|
-
}
|
|
53256
|
-
/**
|
|
53257
|
-
* Builds the structured session lines that combine state, runner, queue, and timing metadata.
|
|
53258
|
-
*/
|
|
53259
|
-
function buildSessionLines(options, totalWidth, pausePresentation) {
|
|
53260
|
-
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
53261
|
-
return buildSessionRows(options, bodyWidth, pausePresentation).map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
|
|
53262
|
-
}
|
|
53263
|
-
/**
|
|
53264
|
-
* Builds the session rows so the renderer can keep one consistent structure without duplicating labels.
|
|
53265
|
-
*/
|
|
53266
|
-
function buildSessionRows(options, bodyWidth, pausePresentation) {
|
|
53267
|
-
const runnerParts = [options.config.agentName || 'No agent selected'];
|
|
53268
|
-
if (options.config.modelName) {
|
|
53269
|
-
runnerParts.push(options.config.modelName);
|
|
53270
|
-
}
|
|
53271
|
-
if (options.config.thinkingLevel) {
|
|
53272
|
-
runnerParts.push(`thinking ${options.config.thinkingLevel}`);
|
|
53273
|
-
}
|
|
53274
|
-
const configurationRows = [
|
|
53275
|
-
...buildOptionalSessionRow('Context', options.config.context),
|
|
53276
|
-
...buildOptionalSessionRow('Test', options.config.testCommand),
|
|
53277
|
-
];
|
|
53278
|
-
return [
|
|
53279
|
-
{
|
|
53280
|
-
label: 'State',
|
|
53281
|
-
value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
|
|
53282
|
-
},
|
|
53283
|
-
{
|
|
53284
|
-
label: 'Runner',
|
|
53285
|
-
value: runnerParts.join(' · '),
|
|
53286
|
-
},
|
|
53287
|
-
...configurationRows,
|
|
53288
|
-
{
|
|
53289
|
-
label: 'This run',
|
|
53290
|
-
value: buildThisRunSummary(options.progress),
|
|
53291
|
-
},
|
|
53292
|
-
{
|
|
53293
|
-
label: 'Backlog',
|
|
53294
|
-
value: buildBacklogSummary(options.progress),
|
|
53295
|
-
},
|
|
53296
|
-
{
|
|
53297
|
-
label: 'Scope',
|
|
53298
|
-
value: buildScopeSummary(options.progress, options.config),
|
|
53299
|
-
},
|
|
53300
|
-
{
|
|
53301
|
-
label: 'Timing',
|
|
53302
|
-
value: buildTimingSummary(options.progress),
|
|
53303
|
-
},
|
|
53304
|
-
{
|
|
53305
|
-
label: 'Progress',
|
|
53306
|
-
value: buildProgressBar$1(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
|
|
53307
|
-
},
|
|
53308
|
-
];
|
|
53309
|
-
}
|
|
53310
53823
|
/**
|
|
53311
53824
|
* Builds the fixed-height live output section so streaming updates do not keep resizing the frame.
|
|
53312
53825
|
*/
|
|
@@ -53342,50 +53855,6 @@
|
|
|
53342
53855
|
const formattedLabel = colors__default["default"].gray(label.padEnd(SESSION_LABEL_WIDTH));
|
|
53343
53856
|
return `${formattedLabel} ${fitAnsiText(value, bodyWidth - SESSION_LABEL_WIDTH - 1)}`;
|
|
53344
53857
|
}
|
|
53345
|
-
/**
|
|
53346
|
-
* Builds zero or one structured session row for optional metadata.
|
|
53347
|
-
*/
|
|
53348
|
-
function buildOptionalSessionRow(label, value) {
|
|
53349
|
-
if (!value) {
|
|
53350
|
-
return [];
|
|
53351
|
-
}
|
|
53352
|
-
return [{ label, value }];
|
|
53353
|
-
}
|
|
53354
|
-
/**
|
|
53355
|
-
* Builds the active-session summary shown in the session box.
|
|
53356
|
-
*/
|
|
53357
|
-
function buildThisRunSummary(progress) {
|
|
53358
|
-
if (progress.sessionTotal === 0) {
|
|
53359
|
-
return 'No runnable prompts in current scope';
|
|
53360
|
-
}
|
|
53361
|
-
return `Task ${progress.currentPromptIndex}/${progress.sessionTotal} · ${progress.sessionDone} done · ${progress.sessionRemaining} left`;
|
|
53362
|
-
}
|
|
53363
|
-
/**
|
|
53364
|
-
* Builds the backlog/filter summary shown in the session box.
|
|
53365
|
-
*/
|
|
53366
|
-
function buildBacklogSummary(progress) {
|
|
53367
|
-
const parts = [`Repo ${progress.totalPrompts} total`];
|
|
53368
|
-
if (progress.skippedPrompts > 0) {
|
|
53369
|
-
parts.push(`${formatPromptCount$1(progress.skippedPrompts)} below priority`);
|
|
53370
|
-
}
|
|
53371
|
-
return parts.join(' · ');
|
|
53372
|
-
}
|
|
53373
|
-
/**
|
|
53374
|
-
* Builds the priority/write-order summary shown in the session box.
|
|
53375
|
-
*/
|
|
53376
|
-
function buildScopeSummary(progress, config) {
|
|
53377
|
-
const parts = [`Priority ≥${config.priority}`];
|
|
53378
|
-
if (progress.toBeWrittenPrompts > 0) {
|
|
53379
|
-
parts.push(`Write ${formatPromptCount$1(progress.toBeWrittenPrompts)} first`);
|
|
53380
|
-
}
|
|
53381
|
-
return parts.join(' · ');
|
|
53382
|
-
}
|
|
53383
|
-
/**
|
|
53384
|
-
* Builds the elapsed/estimate summary shown in the session box.
|
|
53385
|
-
*/
|
|
53386
|
-
function buildTimingSummary(progress) {
|
|
53387
|
-
return `Elapsed ${progress.elapsedText} · Total ${progress.estimatedTotalText} · ETA ${progress.estimatedLabel}`;
|
|
53388
|
-
}
|
|
53389
53858
|
/**
|
|
53390
53859
|
* Builds the colored phase badge shown in the session box.
|
|
53391
53860
|
*/
|
|
@@ -53410,6 +53879,28 @@
|
|
|
53410
53879
|
pauseControl: colors__default["default"].bgYellow.black(' P ') + colors__default["default"].white(' Pause'),
|
|
53411
53880
|
};
|
|
53412
53881
|
}
|
|
53882
|
+
/**
|
|
53883
|
+
* Builds the progress bar shown in the session box.
|
|
53884
|
+
*/
|
|
53885
|
+
function buildProgressBar$1(percentage, availableWidth, label) {
|
|
53886
|
+
const percentageLabel = label;
|
|
53887
|
+
const barWidth = Math.max(10, availableWidth - percentageLabel.length - 1);
|
|
53888
|
+
const filledWidth = Math.round((percentage / 100) * barWidth);
|
|
53889
|
+
const emptyWidth = Math.max(0, barWidth - filledWidth);
|
|
53890
|
+
return `${colors__default["default"].green('█'.repeat(filledWidth))}${colors__default["default"].blue('░'.repeat(emptyWidth))} ${percentageLabel}`;
|
|
53891
|
+
}
|
|
53892
|
+
/**
|
|
53893
|
+
* Builds the control pills shown in the footer box.
|
|
53894
|
+
*/
|
|
53895
|
+
function buildControlPills(pauseControl, pendingEnterLabel) {
|
|
53896
|
+
const pills = [];
|
|
53897
|
+
if (pendingEnterLabel) {
|
|
53898
|
+
pills.push(colors__default["default"].bgWhite.black(' ENTER ') + colors__default["default"].white(` ${pendingEnterLabel}`));
|
|
53899
|
+
}
|
|
53900
|
+
pills.push(pauseControl);
|
|
53901
|
+
pills.push(colors__default["default"].bgRed.white(' CTRL+C ') + colors__default["default"].white(' Exit'));
|
|
53902
|
+
return pills;
|
|
53903
|
+
}
|
|
53413
53904
|
/**
|
|
53414
53905
|
* Builds the active phase badge shown in the session box while the runner is not paused.
|
|
53415
53906
|
*/
|
|
@@ -53434,33 +53925,190 @@
|
|
|
53434
53925
|
return colors__default["default"].bgWhite.black(' READY ');
|
|
53435
53926
|
}
|
|
53436
53927
|
}
|
|
53928
|
+
|
|
53437
53929
|
/**
|
|
53438
|
-
*
|
|
53930
|
+
* Refresh cadence used only while the rich coder UI needs animated updates.
|
|
53931
|
+
*
|
|
53932
|
+
* @private internal constant of coder run UI
|
|
53439
53933
|
*/
|
|
53440
|
-
|
|
53441
|
-
|
|
53442
|
-
|
|
53443
|
-
|
|
53444
|
-
|
|
53445
|
-
|
|
53934
|
+
const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = 300;
|
|
53935
|
+
/**
|
|
53936
|
+
* Phases that still benefit from automatic refreshes because the frame can change
|
|
53937
|
+
* over time even without new runner output.
|
|
53938
|
+
*
|
|
53939
|
+
* @private internal constant of coder run UI
|
|
53940
|
+
*/
|
|
53941
|
+
const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
|
|
53942
|
+
/**
|
|
53943
|
+
* Returns whether the rich coder UI should keep animating on its own.
|
|
53944
|
+
*
|
|
53945
|
+
* @private internal utility of coder run UI
|
|
53946
|
+
*/
|
|
53947
|
+
function isCoderRunUiAutoRefreshing(phase, pauseState) {
|
|
53948
|
+
// `PAUSING` still means the current task is winding down, so keep active
|
|
53949
|
+
// animations/timers running until the runner reaches the fully paused state.
|
|
53950
|
+
if (pauseState === 'PAUSED') {
|
|
53951
|
+
return false;
|
|
53952
|
+
}
|
|
53953
|
+
return AUTO_REFRESH_PHASES.includes(phase);
|
|
53446
53954
|
}
|
|
53447
53955
|
/**
|
|
53448
|
-
*
|
|
53956
|
+
* Returns the automatic refresh interval for the current UI state.
|
|
53957
|
+
*
|
|
53958
|
+
* Waiting, paused, and completed states return `undefined` so the rich UI stays
|
|
53959
|
+
* perfectly still until actual state changes arrive.
|
|
53960
|
+
*
|
|
53961
|
+
* @private internal utility of coder run UI
|
|
53449
53962
|
*/
|
|
53450
|
-
function
|
|
53451
|
-
return
|
|
53963
|
+
function getCoderRunUiAutoRefreshInterval(phase, pauseState) {
|
|
53964
|
+
return isCoderRunUiAutoRefreshing(phase, pauseState) ? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS : undefined;
|
|
53452
53965
|
}
|
|
53966
|
+
|
|
53453
53967
|
/**
|
|
53454
|
-
*
|
|
53968
|
+
* Minimum width used for the rich coder-run frame.
|
|
53455
53969
|
*/
|
|
53456
|
-
|
|
53457
|
-
|
|
53458
|
-
|
|
53459
|
-
|
|
53970
|
+
const MIN_FRAME_WIDTH$1 = 56;
|
|
53971
|
+
/**
|
|
53972
|
+
* Maximum width used for the rich coder-run frame.
|
|
53973
|
+
*/
|
|
53974
|
+
const MAX_FRAME_WIDTH$1 = 96;
|
|
53975
|
+
/**
|
|
53976
|
+
* Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
|
|
53977
|
+
*/
|
|
53978
|
+
function buildCoderRunUiFrame(options) {
|
|
53979
|
+
const totalWidth = Math.max(MIN_FRAME_WIDTH$1, Math.min(options.terminalWidth, MAX_FRAME_WIDTH$1));
|
|
53980
|
+
const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
|
|
53981
|
+
const promptStatusPrefix = isPromptActive ? `${colors__default["default"].yellow(`${options.spinner} `)}` : '';
|
|
53982
|
+
const octopusAnimationFrame = isCoderRunUiAutoRefreshing(options.phase, options.pauseState)
|
|
53983
|
+
? options.animationFrame
|
|
53984
|
+
: 0;
|
|
53985
|
+
const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.statusMessage);
|
|
53986
|
+
const sessionLines = buildSessionLines$1(options, totalWidth, pausePresentation);
|
|
53987
|
+
const currentTaskLines = options.currentPromptLabel
|
|
53988
|
+
? [
|
|
53989
|
+
`${promptStatusPrefix}${colors__default["default"].bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
|
|
53990
|
+
`Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
|
|
53991
|
+
...options.detailLines.map((detailLine) => `• ${detailLine}`),
|
|
53992
|
+
]
|
|
53993
|
+
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
53994
|
+
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
53995
|
+
const controls = buildControlPills(pausePresentation.pauseControl, options.pendingEnterLabel).join(' ');
|
|
53996
|
+
const frame = [
|
|
53997
|
+
...buildCoderRunOctopusVisual({ totalWidth, animationFrame: octopusAnimationFrame }),
|
|
53998
|
+
'',
|
|
53999
|
+
...renderBox('Session', sessionLines, totalWidth, colors__default["default"].yellow.bold),
|
|
54000
|
+
...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors__default["default"].magenta.bold),
|
|
54001
|
+
...renderBox('Live output', visibleOutputLines, totalWidth, colors__default["default"].green.bold),
|
|
54002
|
+
];
|
|
54003
|
+
if (options.errors.length > 0) {
|
|
54004
|
+
frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors__default["default"].red('✗')} ${errorLine}`), totalWidth, colors__default["default"].red.bold));
|
|
53460
54005
|
}
|
|
53461
|
-
|
|
53462
|
-
|
|
53463
|
-
|
|
54006
|
+
frame.push(...renderBox('Controls', [controls], totalWidth, colors__default["default"].white.bold));
|
|
54007
|
+
return frame;
|
|
54008
|
+
}
|
|
54009
|
+
/**
|
|
54010
|
+
* Builds the structured session lines that combine state, runner, queue, and timing metadata.
|
|
54011
|
+
*/
|
|
54012
|
+
function buildSessionLines$1(options, totalWidth, pausePresentation) {
|
|
54013
|
+
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
54014
|
+
return buildSessionRows(options, bodyWidth, pausePresentation).map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
|
|
54015
|
+
}
|
|
54016
|
+
/**
|
|
54017
|
+
* Builds the session rows so the renderer can keep one consistent structure without duplicating labels.
|
|
54018
|
+
*/
|
|
54019
|
+
function buildSessionRows(options, bodyWidth, pausePresentation) {
|
|
54020
|
+
const runnerParts = [options.config.agentName || 'No agent selected'];
|
|
54021
|
+
if (options.config.modelName) {
|
|
54022
|
+
runnerParts.push(options.config.modelName);
|
|
54023
|
+
}
|
|
54024
|
+
if (options.config.thinkingLevel) {
|
|
54025
|
+
runnerParts.push(`thinking ${options.config.thinkingLevel}`);
|
|
54026
|
+
}
|
|
54027
|
+
const configurationRows = [
|
|
54028
|
+
...buildOptionalSessionRow('Context', options.config.context),
|
|
54029
|
+
...buildOptionalSessionRow('Test', options.config.testCommand),
|
|
54030
|
+
];
|
|
54031
|
+
return [
|
|
54032
|
+
{
|
|
54033
|
+
label: 'State',
|
|
54034
|
+
value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
|
|
54035
|
+
},
|
|
54036
|
+
{
|
|
54037
|
+
label: 'Runner',
|
|
54038
|
+
value: runnerParts.join(' · '),
|
|
54039
|
+
},
|
|
54040
|
+
...configurationRows,
|
|
54041
|
+
{
|
|
54042
|
+
label: 'This run',
|
|
54043
|
+
value: buildThisRunSummary(options.progress),
|
|
54044
|
+
},
|
|
54045
|
+
{
|
|
54046
|
+
label: 'Backlog',
|
|
54047
|
+
value: buildBacklogSummary(options.progress),
|
|
54048
|
+
},
|
|
54049
|
+
{
|
|
54050
|
+
label: 'Scope',
|
|
54051
|
+
value: buildScopeSummary(options.progress, options.config),
|
|
54052
|
+
},
|
|
54053
|
+
{
|
|
54054
|
+
label: 'Timing',
|
|
54055
|
+
value: buildTimingSummary(options.progress),
|
|
54056
|
+
},
|
|
54057
|
+
{
|
|
54058
|
+
label: 'Progress',
|
|
54059
|
+
value: buildProgressBar$1(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
|
|
54060
|
+
},
|
|
54061
|
+
];
|
|
54062
|
+
}
|
|
54063
|
+
/**
|
|
54064
|
+
* Builds zero or one structured session row for optional metadata.
|
|
54065
|
+
*/
|
|
54066
|
+
function buildOptionalSessionRow(label, value) {
|
|
54067
|
+
if (!value) {
|
|
54068
|
+
return [];
|
|
54069
|
+
}
|
|
54070
|
+
return [{ label, value }];
|
|
54071
|
+
}
|
|
54072
|
+
/**
|
|
54073
|
+
* Builds the active-session summary shown in the session box.
|
|
54074
|
+
*/
|
|
54075
|
+
function buildThisRunSummary(progress) {
|
|
54076
|
+
if (progress.sessionTotal === 0) {
|
|
54077
|
+
return 'No runnable prompts in current scope';
|
|
54078
|
+
}
|
|
54079
|
+
return `Task ${progress.currentPromptIndex}/${progress.sessionTotal} · ${progress.sessionDone} done · ${progress.sessionRemaining} left`;
|
|
54080
|
+
}
|
|
54081
|
+
/**
|
|
54082
|
+
* Builds the backlog/filter summary shown in the session box.
|
|
54083
|
+
*/
|
|
54084
|
+
function buildBacklogSummary(progress) {
|
|
54085
|
+
const parts = [`Repo ${progress.totalPrompts} total`];
|
|
54086
|
+
if (progress.skippedPrompts > 0) {
|
|
54087
|
+
parts.push(`${formatPromptCount$1(progress.skippedPrompts)} below priority`);
|
|
54088
|
+
}
|
|
54089
|
+
return parts.join(' · ');
|
|
54090
|
+
}
|
|
54091
|
+
/**
|
|
54092
|
+
* Builds the priority/write-order summary shown in the session box.
|
|
54093
|
+
*/
|
|
54094
|
+
function buildScopeSummary(progress, config) {
|
|
54095
|
+
const parts = [`Priority ≥${config.priority}`];
|
|
54096
|
+
if (progress.toBeWrittenPrompts > 0) {
|
|
54097
|
+
parts.push(`Write ${formatPromptCount$1(progress.toBeWrittenPrompts)} first`);
|
|
54098
|
+
}
|
|
54099
|
+
return parts.join(' · ');
|
|
54100
|
+
}
|
|
54101
|
+
/**
|
|
54102
|
+
* Builds the elapsed/estimate summary shown in the session box.
|
|
54103
|
+
*/
|
|
54104
|
+
function buildTimingSummary(progress) {
|
|
54105
|
+
return `Elapsed ${progress.elapsedText} · Total ${progress.estimatedTotalText} · ETA ${progress.estimatedLabel}`;
|
|
54106
|
+
}
|
|
54107
|
+
/**
|
|
54108
|
+
* Formats a prompt count with singular/plural wording.
|
|
54109
|
+
*/
|
|
54110
|
+
function formatPromptCount$1(count) {
|
|
54111
|
+
return `${count} prompt${count === 1 ? '' : 's'}`;
|
|
53464
54112
|
}
|
|
53465
54113
|
|
|
53466
54114
|
/**
|
|
@@ -53603,6 +54251,7 @@
|
|
|
53603
54251
|
this.currentAttempt = 1;
|
|
53604
54252
|
this.maxAttempts = 3;
|
|
53605
54253
|
this.detailLines = [];
|
|
54254
|
+
this.messagePreviewLines = [];
|
|
53606
54255
|
this.agentOutputLines = [];
|
|
53607
54256
|
this.phase = 'initializing';
|
|
53608
54257
|
this.statusMessage = 'Initializing...';
|
|
@@ -53656,6 +54305,7 @@
|
|
|
53656
54305
|
setCurrentPrompt(label) {
|
|
53657
54306
|
this.currentPromptLabel = label;
|
|
53658
54307
|
this.detailLines = [];
|
|
54308
|
+
this.messagePreviewLines = [];
|
|
53659
54309
|
this.pendingEnterLabel = undefined;
|
|
53660
54310
|
this.agentOutputLines = [];
|
|
53661
54311
|
this.currentAttempt = 1;
|
|
@@ -53703,6 +54353,13 @@
|
|
|
53703
54353
|
this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
|
|
53704
54354
|
this.emitChange();
|
|
53705
54355
|
}
|
|
54356
|
+
/**
|
|
54357
|
+
* Replaces the exact user-message preview lines shown in agent-specific panels.
|
|
54358
|
+
*/
|
|
54359
|
+
setMessagePreviewLines(messagePreviewLines) {
|
|
54360
|
+
this.messagePreviewLines = [...messagePreviewLines];
|
|
54361
|
+
this.emitChange();
|
|
54362
|
+
}
|
|
53706
54363
|
/**
|
|
53707
54364
|
* Sets or clears the Enter-key action label shown in the controls panel.
|
|
53708
54365
|
*/
|
|
@@ -53760,8 +54417,9 @@
|
|
|
53760
54417
|
*
|
|
53761
54418
|
* @private internal entry point of coder run UI
|
|
53762
54419
|
*/
|
|
53763
|
-
function renderCoderRunUi(startTime) {
|
|
54420
|
+
function renderCoderRunUi(startTime, options = {}) {
|
|
53764
54421
|
const state = new CoderRunUiState(startTime);
|
|
54422
|
+
const buildFrameLinesFromState = options.buildFrameLines || buildCoderRunUiFrame;
|
|
53765
54423
|
if (!process.stdout.isTTY) {
|
|
53766
54424
|
return {
|
|
53767
54425
|
state,
|
|
@@ -53898,7 +54556,7 @@
|
|
|
53898
54556
|
* Builds the current frame snapshot from the latest state.
|
|
53899
54557
|
*/
|
|
53900
54558
|
function buildFrameLines() {
|
|
53901
|
-
return
|
|
54559
|
+
return buildFrameLinesFromState({
|
|
53902
54560
|
terminalWidth: getTerminalWidth(),
|
|
53903
54561
|
animationFrame: spinnerFrame,
|
|
53904
54562
|
spinner: SPINNER_FRAMES[spinnerFrame],
|
|
@@ -53910,6 +54568,7 @@
|
|
|
53910
54568
|
maxAttempts: state.maxAttempts,
|
|
53911
54569
|
statusMessage: state.statusMessage,
|
|
53912
54570
|
detailLines: state.detailLines,
|
|
54571
|
+
messagePreviewLines: state.messagePreviewLines,
|
|
53913
54572
|
pendingEnterLabel: state.pendingEnterLabel,
|
|
53914
54573
|
agentOutputLines: state.agentOutputLines,
|
|
53915
54574
|
errors: state.errors,
|
|
@@ -58990,7 +59649,7 @@
|
|
|
58990
59649
|
*/
|
|
58991
59650
|
function buildAgentMessageScriptPath(projectPath, messageFile) {
|
|
58992
59651
|
const scriptFileName = `${messageFile.fileName.replace(/\.[^.]+$/u, '')}.sh`;
|
|
58993
|
-
return
|
|
59652
|
+
return resolvePromptbookTempPath(projectPath, 'scripts', 'agent-messages', scriptFileName);
|
|
58994
59653
|
}
|
|
58995
59654
|
|
|
58996
59655
|
/**
|
|
@@ -59046,6 +59705,242 @@
|
|
|
59046
59705
|
(error.code === 'ENOENT' || error.code === 'ENOTDIR'));
|
|
59047
59706
|
}
|
|
59048
59707
|
|
|
59708
|
+
/**
|
|
59709
|
+
* Compact 3x5 block font used for agent-name initials in the terminal dashboard.
|
|
59710
|
+
*/
|
|
59711
|
+
const BLOCK_FONT = {
|
|
59712
|
+
A: ['███', '█ █', '███', '█ █', '█ █'],
|
|
59713
|
+
B: ['██ ', '█ █', '██ ', '█ █', '██ '],
|
|
59714
|
+
C: ['███', '█ ', '█ ', '█ ', '███'],
|
|
59715
|
+
D: ['██ ', '█ █', '█ █', '█ █', '██ '],
|
|
59716
|
+
E: ['███', '█ ', '██ ', '█ ', '███'],
|
|
59717
|
+
F: ['███', '█ ', '██ ', '█ ', '█ '],
|
|
59718
|
+
G: ['███', '█ ', '█ ██', '█ █', '████'],
|
|
59719
|
+
H: ['█ █', '█ █', '███', '█ █', '█ █'],
|
|
59720
|
+
I: ['███', ' █ ', ' █ ', ' █ ', '███'],
|
|
59721
|
+
J: ['███', ' █', ' █', '█ █', '██ '],
|
|
59722
|
+
K: ['█ █', '█ █', '██ ', '█ █', '█ █'],
|
|
59723
|
+
L: ['█ ', '█ ', '█ ', '█ ', '███'],
|
|
59724
|
+
M: ['█ █', '███', '███', '█ █', '█ █'],
|
|
59725
|
+
N: ['█ █', '███', '███', '███', '█ █'],
|
|
59726
|
+
O: ['███', '█ █', '█ █', '█ █', '███'],
|
|
59727
|
+
P: ['███', '█ █', '███', '█ ', '█ '],
|
|
59728
|
+
Q: ['███', '█ █', '█ █', '███', ' █'],
|
|
59729
|
+
R: ['███', '█ █', '███', '██ ', '█ █'],
|
|
59730
|
+
S: ['███', '█ ', '███', ' █', '███'],
|
|
59731
|
+
T: ['███', ' █ ', ' █ ', ' █ ', ' █ '],
|
|
59732
|
+
U: ['█ █', '█ █', '█ █', '█ █', '███'],
|
|
59733
|
+
V: ['█ █', '█ █', '█ █', '█ █', ' █ '],
|
|
59734
|
+
W: ['█ █', '█ █', '███', '███', '█ █'],
|
|
59735
|
+
X: ['█ █', '█ █', ' █ ', '█ █', '█ █'],
|
|
59736
|
+
Y: ['█ █', '█ █', ' █ ', ' █ ', ' █ '],
|
|
59737
|
+
Z: ['███', ' █', ' █ ', '█ ', '███'],
|
|
59738
|
+
0: ['███', '█ █', '█ █', '█ █', '███'],
|
|
59739
|
+
1: [' ██', ' █', ' █', ' █', '███'],
|
|
59740
|
+
2: ['███', ' █', '███', '█ ', '███'],
|
|
59741
|
+
3: ['███', ' █', ' ██', ' █', '███'],
|
|
59742
|
+
4: ['█ █', '█ █', '███', ' █', ' █'],
|
|
59743
|
+
5: ['███', '█ ', '███', ' █', '███'],
|
|
59744
|
+
6: ['███', '█ ', '███', '█ █', '███'],
|
|
59745
|
+
7: ['███', ' █', ' █', ' █', ' █'],
|
|
59746
|
+
8: ['███', '█ █', '███', '█ █', '███'],
|
|
59747
|
+
9: ['███', '█ █', '███', ' █', '███'],
|
|
59748
|
+
};
|
|
59749
|
+
/**
|
|
59750
|
+
* Fallback glyph used when the initials contain unsupported characters.
|
|
59751
|
+
*/
|
|
59752
|
+
const UNKNOWN_LETTER = ['███', ' █', ' ██', ' ', ' ██'];
|
|
59753
|
+
/**
|
|
59754
|
+
* Builds a compact centered initials banner for `ptbk agent run`.
|
|
59755
|
+
*/
|
|
59756
|
+
function buildAgentRunInitialsVisual(agentName, totalWidth) {
|
|
59757
|
+
const initials = extractAgentInitials(agentName);
|
|
59758
|
+
const glyphRows = Array.from({ length: 5 }, () => '');
|
|
59759
|
+
for (const initial of initials) {
|
|
59760
|
+
const glyph = BLOCK_FONT[initial] || UNKNOWN_LETTER;
|
|
59761
|
+
for (let rowIndex = 0; rowIndex < glyph.length; rowIndex++) {
|
|
59762
|
+
glyphRows[rowIndex] = `${glyphRows[rowIndex]}${glyph[rowIndex]} `;
|
|
59763
|
+
}
|
|
59764
|
+
}
|
|
59765
|
+
const trimmedGlyphRows = glyphRows.map((glyphRow) => glyphRow.trimEnd());
|
|
59766
|
+
const visualWidth = trimmedGlyphRows.reduce((maxWidth, glyphRow) => Math.max(maxWidth, visibleLength(glyphRow)), 0);
|
|
59767
|
+
return trimmedGlyphRows.map((glyphRow, rowIndex) => {
|
|
59768
|
+
const coloredRow = rowIndex === 2 ? colors__default["default"].cyan.bold(glyphRow) : rowIndex === 0 ? colors__default["default"].blue.bold(glyphRow) : colors__default["default"].white.bold(glyphRow);
|
|
59769
|
+
return centerAnsiText(padAnsiText(coloredRow, visualWidth), totalWidth);
|
|
59770
|
+
});
|
|
59771
|
+
}
|
|
59772
|
+
/**
|
|
59773
|
+
* Extracts readable initials from the local agent title.
|
|
59774
|
+
*/
|
|
59775
|
+
function extractAgentInitials(agentName) {
|
|
59776
|
+
const normalizedAlphanumericName = agentName.replace(/[^A-Za-z0-9]/gu, '').toUpperCase();
|
|
59777
|
+
const words = agentName
|
|
59778
|
+
.trim()
|
|
59779
|
+
.split(/[^A-Za-z0-9]+/u)
|
|
59780
|
+
.filter(Boolean)
|
|
59781
|
+
.map((word) => word[0].toUpperCase());
|
|
59782
|
+
if (words.length > 1) {
|
|
59783
|
+
return words.slice(0, 3);
|
|
59784
|
+
}
|
|
59785
|
+
const fallbackLetters = normalizedAlphanumericName.slice(0, 2).split('');
|
|
59786
|
+
return fallbackLetters.length > 0 ? fallbackLetters : ['A'];
|
|
59787
|
+
}
|
|
59788
|
+
|
|
59789
|
+
/**
|
|
59790
|
+
* Minimum width used for the rich agent-run frame.
|
|
59791
|
+
*/
|
|
59792
|
+
const MIN_FRAME_WIDTH = 56;
|
|
59793
|
+
/**
|
|
59794
|
+
* Maximum width used for the rich agent-run frame.
|
|
59795
|
+
*/
|
|
59796
|
+
const MAX_FRAME_WIDTH = 96;
|
|
59797
|
+
/**
|
|
59798
|
+
* Maximum number of source-message lines shown in the dedicated preview box.
|
|
59799
|
+
*/
|
|
59800
|
+
const MAX_MESSAGE_PREVIEW_LINES = 6;
|
|
59801
|
+
/**
|
|
59802
|
+
* Builds the complete boxed terminal frame for the rich `ptbk agent run` UI.
|
|
59803
|
+
*/
|
|
59804
|
+
function buildAgentRunUiFrame(options) {
|
|
59805
|
+
const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
|
|
59806
|
+
const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
|
|
59807
|
+
const promptStatusPrefix = isPromptActive ? `${colors__default["default"].yellow(`${options.spinner} `)}` : '';
|
|
59808
|
+
const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.statusMessage);
|
|
59809
|
+
const sessionLines = buildSessionLines(options, totalWidth, pausePresentation);
|
|
59810
|
+
const currentTaskLines = options.currentPromptLabel
|
|
59811
|
+
? [
|
|
59812
|
+
`${promptStatusPrefix}${colors__default["default"].bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
|
|
59813
|
+
`Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
|
|
59814
|
+
...options.detailLines.map((detailLine) => `• ${detailLine}`),
|
|
59815
|
+
]
|
|
59816
|
+
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
59817
|
+
const userMessageLines = buildUserMessagePreviewLines(options.messagePreviewLines, totalWidth);
|
|
59818
|
+
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
59819
|
+
const controls = buildControlPills(pausePresentation.pauseControl, options.pendingEnterLabel).join(' ');
|
|
59820
|
+
const frame = [
|
|
59821
|
+
...buildAgentRunInitialsVisual(options.config.localAgentName || 'Local Agent', totalWidth),
|
|
59822
|
+
'',
|
|
59823
|
+
...renderBox('Session', sessionLines, totalWidth, colors__default["default"].yellow.bold),
|
|
59824
|
+
...renderBox('Current task', currentTaskLines, totalWidth, colors__default["default"].magenta.bold),
|
|
59825
|
+
...renderBox('User message', userMessageLines, totalWidth, colors__default["default"].cyan.bold),
|
|
59826
|
+
...renderBox('Live output', visibleOutputLines, totalWidth, colors__default["default"].green.bold),
|
|
59827
|
+
];
|
|
59828
|
+
if (options.errors.length > 0) {
|
|
59829
|
+
frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors__default["default"].red('✗')} ${errorLine}`), totalWidth, colors__default["default"].red.bold));
|
|
59830
|
+
}
|
|
59831
|
+
frame.push(...renderBox('Controls', [controls], totalWidth, colors__default["default"].white.bold));
|
|
59832
|
+
return frame;
|
|
59833
|
+
}
|
|
59834
|
+
/**
|
|
59835
|
+
* Builds the structured session lines for the agent-specific dashboard.
|
|
59836
|
+
*/
|
|
59837
|
+
function buildSessionLines(options, totalWidth, pausePresentation) {
|
|
59838
|
+
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
59839
|
+
const finishedMessages = options.progress.sessionDone;
|
|
59840
|
+
const queuedMessages = options.progress.sessionRemaining;
|
|
59841
|
+
const totalMessages = options.progress.sessionTotal;
|
|
59842
|
+
const localAgentName = options.config.localAgentName || 'Local Agent';
|
|
59843
|
+
const runnerParts = [options.config.agentName || 'No runner selected'];
|
|
59844
|
+
if (options.config.modelName) {
|
|
59845
|
+
runnerParts.push(options.config.modelName);
|
|
59846
|
+
}
|
|
59847
|
+
if (options.config.thinkingLevel) {
|
|
59848
|
+
runnerParts.push(`thinking ${options.config.thinkingLevel}`);
|
|
59849
|
+
}
|
|
59850
|
+
const sessionRows = [
|
|
59851
|
+
{
|
|
59852
|
+
label: 'State',
|
|
59853
|
+
value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
|
|
59854
|
+
},
|
|
59855
|
+
{
|
|
59856
|
+
label: 'Agent',
|
|
59857
|
+
value: localAgentName,
|
|
59858
|
+
},
|
|
59859
|
+
{
|
|
59860
|
+
label: 'Runner',
|
|
59861
|
+
value: runnerParts.join(' · '),
|
|
59862
|
+
},
|
|
59863
|
+
{
|
|
59864
|
+
label: 'Queue',
|
|
59865
|
+
value: `${totalMessages} total · ${finishedMessages} finished · ${queuedMessages} queued`,
|
|
59866
|
+
},
|
|
59867
|
+
{
|
|
59868
|
+
label: 'Timing',
|
|
59869
|
+
value: `Elapsed ${options.progress.elapsedText} · Total ${options.progress.estimatedTotalText} · ETA ${options.progress.estimatedLabel}`,
|
|
59870
|
+
},
|
|
59871
|
+
{
|
|
59872
|
+
label: 'Progress',
|
|
59873
|
+
value: buildProgressBar$1(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${finishedMessages}/${totalMessages || 0} finished)`),
|
|
59874
|
+
},
|
|
59875
|
+
];
|
|
59876
|
+
return sessionRows.map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
|
|
59877
|
+
}
|
|
59878
|
+
/**
|
|
59879
|
+
* Fits the most recent user message into a fixed-height panel while preserving line breaks.
|
|
59880
|
+
*/
|
|
59881
|
+
function buildUserMessagePreviewLines(messagePreviewLines, totalWidth) {
|
|
59882
|
+
const previewWidth = Math.max(10, totalWidth - 4);
|
|
59883
|
+
const rawLines = messagePreviewLines && messagePreviewLines.length > 0
|
|
59884
|
+
? messagePreviewLines.map((messagePreviewLine) => messagePreviewLine.replace(/\t/gu, ' '))
|
|
59885
|
+
: ['No `MESSAGE @User` content found in the queued message.'];
|
|
59886
|
+
const visibleLines = rawLines.slice(0, MAX_MESSAGE_PREVIEW_LINES).map((line) => fitPlainText(line, previewWidth));
|
|
59887
|
+
if (rawLines.length > MAX_MESSAGE_PREVIEW_LINES) {
|
|
59888
|
+
visibleLines[MAX_MESSAGE_PREVIEW_LINES - 1] = fitPlainText(`${visibleLines[MAX_MESSAGE_PREVIEW_LINES - 1]} …`, previewWidth);
|
|
59889
|
+
}
|
|
59890
|
+
while (visibleLines.length < MAX_MESSAGE_PREVIEW_LINES) {
|
|
59891
|
+
visibleLines.push('');
|
|
59892
|
+
}
|
|
59893
|
+
return visibleLines;
|
|
59894
|
+
}
|
|
59895
|
+
|
|
59896
|
+
/**
|
|
59897
|
+
* Reads the local agent title and latest queued user message for the rich agent dashboard.
|
|
59898
|
+
*/
|
|
59899
|
+
async function loadAgentRunUiMetadata(projectPath, queuedMessage) {
|
|
59900
|
+
const [localAgentName, queuedMessageContent] = await Promise.all([
|
|
59901
|
+
readLocalAgentName(projectPath),
|
|
59902
|
+
promises.readFile(queuedMessage.absolutePath, 'utf-8'),
|
|
59903
|
+
]);
|
|
59904
|
+
return {
|
|
59905
|
+
localAgentName,
|
|
59906
|
+
latestUserMessageLines: extractLatestUserMessageLines(queuedMessageContent),
|
|
59907
|
+
};
|
|
59908
|
+
}
|
|
59909
|
+
/**
|
|
59910
|
+
* Reads the local `agent.book` title and falls back to a stable generic name when unavailable.
|
|
59911
|
+
*/
|
|
59912
|
+
async function readLocalAgentName(projectPath) {
|
|
59913
|
+
try {
|
|
59914
|
+
const agentSource = await promises.readFile(path.join(projectPath, AGENT_BOOK_FILE_PATH), 'utf-8');
|
|
59915
|
+
return parseAgentSourceWithCommitments(agentSource).agentName || 'Local Agent';
|
|
59916
|
+
}
|
|
59917
|
+
catch (error) {
|
|
59918
|
+
if (error &&
|
|
59919
|
+
typeof error === 'object' &&
|
|
59920
|
+
'code' in error &&
|
|
59921
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
|
|
59922
|
+
return 'Local Agent';
|
|
59923
|
+
}
|
|
59924
|
+
throw error;
|
|
59925
|
+
}
|
|
59926
|
+
}
|
|
59927
|
+
/**
|
|
59928
|
+
* Extracts the latest `MESSAGE @User` block while preserving the original line breaks.
|
|
59929
|
+
*/
|
|
59930
|
+
function extractLatestUserMessageLines(messageContent) {
|
|
59931
|
+
const messageBlockPattern = /^MESSAGE\s+@User\b[^\n]*\n([\s\S]*?)(?=^MESSAGE\s+@|(?![\s\S]))/gmu;
|
|
59932
|
+
let latestMatch = null;
|
|
59933
|
+
while (true) {
|
|
59934
|
+
const currentMatch = messageBlockPattern.exec(messageContent);
|
|
59935
|
+
if (!currentMatch) {
|
|
59936
|
+
break;
|
|
59937
|
+
}
|
|
59938
|
+
latestMatch = currentMatch;
|
|
59939
|
+
}
|
|
59940
|
+
const latestUserMessageContent = ((latestMatch === null || latestMatch === void 0 ? void 0 : latestMatch[1]) || messageContent).trim();
|
|
59941
|
+
return latestUserMessageContent.length > 0 ? latestUserMessageContent.split(/\r?\n/gu) : [];
|
|
59942
|
+
}
|
|
59943
|
+
|
|
59049
59944
|
/**
|
|
59050
59945
|
* Converts `ptbk agent` options into the shared runner option shape.
|
|
59051
59946
|
*/
|
|
@@ -59094,7 +59989,8 @@
|
|
|
59094
59989
|
async function tickAgentMessages(options, tickOptions = {}) {
|
|
59095
59990
|
validateAgentRunOptions(options);
|
|
59096
59991
|
const projectPath = process.cwd();
|
|
59097
|
-
let
|
|
59992
|
+
let queueSnapshot = await loadAgentMessageQueueSnapshot(projectPath);
|
|
59993
|
+
let queuedMessage = queueSnapshot.queuedMessages[0];
|
|
59098
59994
|
if (!queuedMessage) {
|
|
59099
59995
|
announceNoQueuedMessages(tickOptions);
|
|
59100
59996
|
return { isMessageProcessed: false };
|
|
@@ -59103,7 +59999,8 @@
|
|
|
59103
59999
|
await ensureCleanQueueIfNeeded(projectPath, options);
|
|
59104
60000
|
console.info(colors__default["default"].gray('Pulling latest changes before answering the next message...'));
|
|
59105
60001
|
await pullLatestChanges();
|
|
59106
|
-
|
|
60002
|
+
queueSnapshot = await loadAgentMessageQueueSnapshot(projectPath);
|
|
60003
|
+
queuedMessage = queueSnapshot.queuedMessages[0];
|
|
59107
60004
|
if (!queuedMessage) {
|
|
59108
60005
|
announceNoQueuedMessages(tickOptions);
|
|
59109
60006
|
return { isMessageProcessed: false };
|
|
@@ -59112,7 +60009,8 @@
|
|
|
59112
60009
|
await ensureCleanQueueIfNeeded(projectPath, options);
|
|
59113
60010
|
const sharedRunOptions = createCoderRunOptionsForAgent(options);
|
|
59114
60011
|
const { runner, actualRunnerModel } = resolvePromptRunner(sharedRunOptions);
|
|
59115
|
-
const
|
|
60012
|
+
const agentUiMetadata = await loadAgentRunUiMetadata(projectPath, queuedMessage);
|
|
60013
|
+
const uiHandle = createAgentRunUiHandle(options, runner, actualRunnerModel, queuedMessage, queueSnapshot, agentUiMetadata);
|
|
59116
60014
|
try {
|
|
59117
60015
|
const finishedMessage = await runQueuedAgentMessage({
|
|
59118
60016
|
projectPath,
|
|
@@ -59121,6 +60019,10 @@
|
|
|
59121
60019
|
queuedMessage,
|
|
59122
60020
|
uiHandle,
|
|
59123
60021
|
});
|
|
60022
|
+
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.updateProgress(createAgentQueueProgressSnapshot({
|
|
60023
|
+
finishedMessageCount: queueSnapshot.finishedMessageCount + 1,
|
|
60024
|
+
queuedMessages: queueSnapshot.queuedMessages.slice(1),
|
|
60025
|
+
}));
|
|
59124
60026
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Message answered');
|
|
59125
60027
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
|
|
59126
60028
|
return {
|
|
@@ -59189,24 +60091,21 @@
|
|
|
59189
60091
|
/**
|
|
59190
60092
|
* Creates and seeds the rich terminal UI for an agent message run.
|
|
59191
60093
|
*/
|
|
59192
|
-
function createAgentRunUiHandle(options, runner, actualRunnerModel, queuedMessage) {
|
|
60094
|
+
function createAgentRunUiHandle(options, runner, actualRunnerModel, queuedMessage, queueSnapshot, agentUiMetadata) {
|
|
59193
60095
|
if (options.noUi || !process.stdout.isTTY) {
|
|
59194
60096
|
return undefined;
|
|
59195
60097
|
}
|
|
59196
|
-
const uiHandle = renderCoderRunUi(moment__default["default"]());
|
|
60098
|
+
const uiHandle = renderCoderRunUi(moment__default["default"](), { buildFrameLines: buildAgentRunUiFrame });
|
|
59197
60099
|
uiHandle.state.setConfig({
|
|
59198
60100
|
agentName: runner.name,
|
|
60101
|
+
localAgentName: agentUiMetadata.localAgentName,
|
|
59199
60102
|
modelName: actualRunnerModel,
|
|
59200
60103
|
thinkingLevel: options.thinkingLevel,
|
|
59201
60104
|
priority: 0,
|
|
59202
60105
|
});
|
|
59203
|
-
uiHandle.state.updateProgress(
|
|
59204
|
-
done: 0,
|
|
59205
|
-
forAgent: 1,
|
|
59206
|
-
belowMinimumPriority: 0,
|
|
59207
|
-
toBeWritten: 0,
|
|
59208
|
-
});
|
|
60106
|
+
uiHandle.state.updateProgress(createAgentQueueProgressSnapshot(queueSnapshot));
|
|
59209
60107
|
uiHandle.state.setCurrentPrompt(queuedMessage.relativePath);
|
|
60108
|
+
uiHandle.state.setMessagePreviewLines([...agentUiMetadata.latestUserMessageLines]);
|
|
59210
60109
|
uiHandle.state.setPhase('loading');
|
|
59211
60110
|
uiHandle.state.setStatusMessage('Preparing message');
|
|
59212
60111
|
return uiHandle;
|
|
@@ -59236,11 +60135,15 @@
|
|
|
59236
60135
|
return [finishedMessage.relativePath];
|
|
59237
60136
|
}
|
|
59238
60137
|
/**
|
|
59239
|
-
*
|
|
60138
|
+
* Converts agent queue counts into the prompt-style snapshot used by the shared rich UI state.
|
|
59240
60139
|
*/
|
|
59241
|
-
|
|
59242
|
-
|
|
59243
|
-
|
|
60140
|
+
function createAgentQueueProgressSnapshot(queueSnapshot) {
|
|
60141
|
+
return {
|
|
60142
|
+
done: queueSnapshot.finishedMessageCount,
|
|
60143
|
+
forAgent: queueSnapshot.queuedMessages.length,
|
|
60144
|
+
belowMinimumPriority: 0,
|
|
60145
|
+
toBeWritten: 0,
|
|
60146
|
+
};
|
|
59244
60147
|
}
|
|
59245
60148
|
/**
|
|
59246
60149
|
* Runs the clean working tree guard unless the user explicitly disabled it.
|
|
@@ -59281,6 +60184,38 @@
|
|
|
59281
60184
|
}
|
|
59282
60185
|
console.info(colors__default["default"].gray('No queued agent messages.'));
|
|
59283
60186
|
}
|
|
60187
|
+
/**
|
|
60188
|
+
* Reads current queued and finished message counts for the agent dashboard.
|
|
60189
|
+
*/
|
|
60190
|
+
async function loadAgentMessageQueueSnapshot(projectPath) {
|
|
60191
|
+
const [queuedMessages, finishedMessageCount] = await Promise.all([
|
|
60192
|
+
listQueuedAgentMessages(projectPath),
|
|
60193
|
+
countMarkdownFiles(path.join(projectPath, AGENT_FINISHED_MESSAGES_DIRECTORY_PATH)),
|
|
60194
|
+
]);
|
|
60195
|
+
return {
|
|
60196
|
+
queuedMessages,
|
|
60197
|
+
finishedMessageCount,
|
|
60198
|
+
};
|
|
60199
|
+
}
|
|
60200
|
+
/**
|
|
60201
|
+
* Counts markdown files inside one queue directory and treats a missing directory as empty.
|
|
60202
|
+
*/
|
|
60203
|
+
async function countMarkdownFiles(directoryPath) {
|
|
60204
|
+
try {
|
|
60205
|
+
const directoryEntries = await promises.readdir(directoryPath, { withFileTypes: true });
|
|
60206
|
+
return directoryEntries.filter((directoryEntry) => directoryEntry.isFile() && /\.m(?:d|arkdown)$/iu.test(directoryEntry.name))
|
|
60207
|
+
.length;
|
|
60208
|
+
}
|
|
60209
|
+
catch (error) {
|
|
60210
|
+
if (error &&
|
|
60211
|
+
typeof error === 'object' &&
|
|
60212
|
+
'code' in error &&
|
|
60213
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
|
|
60214
|
+
return 0;
|
|
60215
|
+
}
|
|
60216
|
+
throw error;
|
|
60217
|
+
}
|
|
60218
|
+
}
|
|
59284
60219
|
|
|
59285
60220
|
var tickAgentMessages$1 = /*#__PURE__*/Object.freeze({
|
|
59286
60221
|
__proto__: null,
|
|
@@ -59409,11 +60344,11 @@
|
|
|
59409
60344
|
/**
|
|
59410
60345
|
* Default ignored paths while scanning the repository.
|
|
59411
60346
|
*/
|
|
59412
|
-
const DEFAULT_IGNORE_GLOBS = ['**/node_modules/**', '**/.git/**', '
|
|
60347
|
+
const DEFAULT_IGNORE_GLOBS = ['**/node_modules/**', '**/.git/**', `**/${getPromptbookTempPosixPath('ptbk-coder')}/**`];
|
|
59413
60348
|
/**
|
|
59414
60349
|
* Directory used for Promptbook coder runtime caches.
|
|
59415
60350
|
*/
|
|
59416
|
-
const PTBK_CODER_CACHE_DIRECTORY_PATH = '
|
|
60351
|
+
const PTBK_CODER_CACHE_DIRECTORY_PATH = getPromptbookTempPath('ptbk-coder');
|
|
59417
60352
|
/**
|
|
59418
60353
|
* Relative cache file path storing per-file emoji-tag scan results.
|
|
59419
60354
|
*/
|