@promptbook/cli 0.112.0-46 → 0.112.0-48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +16 -16
  2. package/esm/index.es.js +1022 -326
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/scripts/run-codex-prompts/common/waitForPause.d.ts +13 -1
  5. package/esm/scripts/run-codex-prompts/ui/buildCoderRunOctopusVisual.d.ts +13 -0
  6. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +3 -1
  7. package/esm/scripts/run-codex-prompts/ui/coderRunUiRefresh.d.ts +8 -2
  8. package/esm/scripts/run-codex-prompts/ui/coderRunUiText.d.ts +36 -0
  9. package/esm/scripts/utils/emojiTags/scanEmojiTagUsage.d.ts +51 -0
  10. package/esm/src/avatars/AvatarOrImage.d.ts +49 -0
  11. package/esm/src/avatars/avatarInteractionUtils.d.ts +81 -0
  12. package/esm/src/avatars/avatarInteractionUtils.test.d.ts +1 -0
  13. package/esm/src/avatars/avatarPointerTracking.d.ts +17 -0
  14. package/esm/src/avatars/avatarRenderingUtils.d.ts +3 -2
  15. package/esm/src/avatars/avatarRenderingUtils.test.d.ts +1 -0
  16. package/esm/src/avatars/index.d.ts +2 -1
  17. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +41 -1
  18. package/esm/src/avatars/visuals/asciiOctopusAvatarVisual.d.ts +7 -0
  19. package/esm/src/avatars/visuals/octopus3AvatarVisual.d.ts +7 -0
  20. package/esm/src/avatars/visuals/octopusAvatarVisualShared.d.ts +159 -0
  21. package/esm/src/avatars/visuals/octopusAvatarVisualShared.test.d.ts +1 -0
  22. package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +1 -1
  23. package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +1 -1
  24. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +1 -1
  25. package/esm/src/book-components/Chat/Chat/ChatToolCallModalComponents.d.ts +8 -2
  26. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.test.d.ts +2 -0
  27. package/esm/src/book-components/Chat/hooks/useChatCompleteNotification.d.ts +2 -0
  28. package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +10 -0
  29. package/esm/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +1 -1
  30. package/esm/src/commitments/USE/USE.d.ts +1 -0
  31. package/esm/src/commitments/USE/aggregateUseCommitmentSystemMessages.d.ts +1 -1
  32. package/esm/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.d.ts +47 -0
  33. package/esm/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.test.d.ts +1 -0
  34. package/esm/src/commitments/_common/createSerpSearchToolFunction.d.ts +12 -0
  35. package/esm/src/commitments/index.d.ts +2 -1
  36. package/esm/src/config.d.ts +2 -2
  37. package/esm/src/llm-providers/agent/RemoteAgent.d.ts +3 -0
  38. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.test.d.ts +1 -0
  39. package/esm/src/utils/agents/resolveAgentAvatarImageUrl.d.ts +49 -5
  40. package/esm/src/utils/agents/resolveAgentAvatarImageUrl.test.d.ts +1 -0
  41. package/esm/src/version.d.ts +1 -1
  42. package/package.json +4 -2
  43. package/umd/index.umd.js +1020 -324
  44. package/umd/index.umd.js.map +1 -1
  45. package/umd/scripts/run-codex-prompts/common/waitForPause.d.ts +13 -1
  46. package/umd/scripts/run-codex-prompts/ui/buildCoderRunOctopusVisual.d.ts +13 -0
  47. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +3 -1
  48. package/umd/scripts/run-codex-prompts/ui/coderRunUiRefresh.d.ts +8 -2
  49. package/umd/scripts/run-codex-prompts/ui/coderRunUiText.d.ts +36 -0
  50. package/umd/scripts/utils/emojiTags/scanEmojiTagUsage.d.ts +51 -0
  51. package/umd/src/avatars/AvatarOrImage.d.ts +49 -0
  52. package/umd/src/avatars/avatarInteractionUtils.d.ts +81 -0
  53. package/umd/src/avatars/avatarInteractionUtils.test.d.ts +1 -0
  54. package/umd/src/avatars/avatarPointerTracking.d.ts +17 -0
  55. package/umd/src/avatars/avatarRenderingUtils.d.ts +3 -2
  56. package/umd/src/avatars/avatarRenderingUtils.test.d.ts +1 -0
  57. package/umd/src/avatars/index.d.ts +2 -1
  58. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +41 -1
  59. package/umd/src/avatars/visuals/asciiOctopusAvatarVisual.d.ts +7 -0
  60. package/umd/src/avatars/visuals/octopus3AvatarVisual.d.ts +7 -0
  61. package/umd/src/avatars/visuals/octopusAvatarVisualShared.d.ts +159 -0
  62. package/umd/src/avatars/visuals/octopusAvatarVisualShared.test.d.ts +1 -0
  63. package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +1 -1
  64. package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +1 -1
  65. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +1 -1
  66. package/umd/src/book-components/Chat/Chat/ChatToolCallModalComponents.d.ts +8 -2
  67. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.test.d.ts +2 -0
  68. package/umd/src/book-components/Chat/hooks/useChatCompleteNotification.d.ts +2 -0
  69. package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +10 -0
  70. package/umd/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +1 -1
  71. package/umd/src/commitments/USE/USE.d.ts +1 -0
  72. package/umd/src/commitments/USE/aggregateUseCommitmentSystemMessages.d.ts +1 -1
  73. package/umd/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.d.ts +47 -0
  74. package/umd/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.test.d.ts +1 -0
  75. package/umd/src/commitments/_common/createSerpSearchToolFunction.d.ts +12 -0
  76. package/umd/src/commitments/index.d.ts +2 -1
  77. package/umd/src/config.d.ts +2 -2
  78. package/umd/src/llm-providers/agent/RemoteAgent.d.ts +3 -0
  79. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.test.d.ts +1 -0
  80. package/umd/src/utils/agents/resolveAgentAvatarImageUrl.d.ts +49 -5
  81. package/umd/src/utils/agents/resolveAgentAvatarImageUrl.test.d.ts +1 -0
  82. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -2,7 +2,7 @@ import colors from 'colors';
2
2
  import commander, { Option } from 'commander';
3
3
  import _spaceTrim, { spaceTrim as spaceTrim$1 } from 'spacetrim';
4
4
  import * as fs from 'fs';
5
- import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
5
+ import { mkdirSync, writeFileSync, statSync, readFileSync, existsSync } from 'fs';
6
6
  import { join, basename, dirname, isAbsolute, relative, extname, resolve } from 'path';
7
7
  import { readFile, writeFile, stat, mkdir, access, constants, readdir, watch, unlink, rm, rename, rmdir, appendFile } from 'fs/promises';
8
8
  import { forTime, forEver } from 'waitasecond';
@@ -41,7 +41,7 @@ import { EventEmitter } from 'events';
41
41
  import { Subject, BehaviorSubject } from 'rxjs';
42
42
  import { lookup, extension } from 'mime-types';
43
43
  import { parse, unparse } from 'papaparse';
44
- import { Agent as Agent$1, setDefaultOpenAIClient, setDefaultOpenAIKey, fileSearchTool, tool, run } from '@openai/agents';
44
+ import { Agent as Agent$1, setDefaultOpenAIClient, setDefaultOpenAIKey, fileSearchTool, tool, run, webSearchTool } from '@openai/agents';
45
45
  import OpenAI from 'openai';
46
46
 
47
47
  // ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
@@ -58,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-46';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-48';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1045,7 +1045,7 @@ const ADMIN_GITHUB_NAME = 'hejny';
1045
1045
  *
1046
1046
  * @public exported from `@promptbook/core`
1047
1047
  */
1048
- const CLAIM = `Turn your company's scattered knowledge into AI ready books`;
1048
+ const CLAIM = `Create persistent AI agents that turn your company's scattered knowledge into action`;
1049
1049
  // <- TODO: [🐊] Pick the best claim
1050
1050
  /**
1051
1051
  * Color of the Promptbook
@@ -1098,7 +1098,7 @@ Color.fromHex('#1D4ED8');
1098
1098
  *
1099
1099
  * @public exported from `@promptbook/core`
1100
1100
  */
1101
- const DEFAULT_BOOK_TITLE = `✨ Untitled Book`;
1101
+ const DEFAULT_BOOK_TITLE = `🐙 Untitled agent`;
1102
1102
  /**
1103
1103
  * When the title of task is not provided, the default title is used
1104
1104
  *
@@ -2488,37 +2488,68 @@ function buildMissingEnvVariablesBlock(variables) {
2488
2488
  }
2489
2489
  // Note: [🟡] Code for coder init environment bootstrapping [ensureCoderEnvFile](src/cli/cli-commands/coder/ensureCoderEnvFile.ts) should never be published outside of `@promptbook/cli`
2490
2490
 
2491
+ /**
2492
+ * @@@
2493
+ *
2494
+ * @public exported from `@promptbook/utils`
2495
+ */
2496
+ function escapeRegExp$1(value) {
2497
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2498
+ }
2499
+
2491
2500
  /**
2492
2501
  * Relative path to `.gitignore` in the initialized project.
2493
2502
  */
2494
2503
  const GITIGNORE_FILE_PATH = '.gitignore';
2495
2504
  /**
2496
- * `.gitignore` block required by standalone Promptbook coder projects.
2505
+ * Promptbook coder temp directory that should stay out of version control.
2506
+ */
2507
+ const CODER_TEMP_GITIGNORE_RULE = '/.tmp';
2508
+ /**
2509
+ * Promptbook coder cache directory that should stay out of version control.
2510
+ */
2511
+ const CODER_CACHE_GITIGNORE_RULE = '/.promptbook/ptbk-coder';
2512
+ /**
2513
+ * Standard header used when appending Promptbook coder rules into `.gitignore`.
2497
2514
  */
2498
- const CODER_GITIGNORE_BLOCK = spaceTrim$1(`
2499
- # Promptbook Coder
2500
- /.tmp
2501
- `);
2515
+ const CODER_GITIGNORE_HEADER = '# Promptbook Coder';
2502
2516
  /**
2503
- * Ensures `.gitignore` contains the standalone Promptbook coder cache entry.
2517
+ * Ensures `.gitignore` contains the standalone Promptbook coder temp and cache entries.
2504
2518
  *
2505
2519
  * @private function of `initializeCoderProjectConfiguration`
2506
2520
  */
2507
2521
  async function ensureCoderGitignoreFile(projectPath) {
2508
2522
  const gitignorePath = join(projectPath, GITIGNORE_FILE_PATH);
2509
2523
  const currentGitignoreContent = await readTextFileIfExists(gitignorePath);
2510
- if (currentGitignoreContent !== undefined && hasTmpGitignoreRule(currentGitignoreContent)) {
2524
+ const missingRules = getMissingCoderGitignoreRules(currentGitignoreContent || '');
2525
+ if (currentGitignoreContent !== undefined && missingRules.length === 0) {
2511
2526
  return 'unchanged';
2512
2527
  }
2513
- const nextGitignoreContent = appendBlock(currentGitignoreContent || '', CODER_GITIGNORE_BLOCK);
2528
+ const nextGitignoreContent = appendBlock(currentGitignoreContent || '', buildCoderGitignoreBlock(missingRules));
2514
2529
  await writeFile(gitignorePath, nextGitignoreContent, 'utf-8');
2515
2530
  return currentGitignoreContent === undefined ? 'created' : 'updated';
2516
2531
  }
2517
2532
  /**
2518
- * Detects whether `.gitignore` already covers the standalone coder temp directory.
2533
+ * Returns the Promptbook coder gitignore rules that still need to be added.
2519
2534
  */
2520
- function hasTmpGitignoreRule(gitignoreContent) {
2521
- return /(^|[\r\n])\/?\.tmp(?:[\r\n]|$)/u.test(gitignoreContent);
2535
+ function getMissingCoderGitignoreRules(gitignoreContent) {
2536
+ const requiredRules = [CODER_TEMP_GITIGNORE_RULE, CODER_CACHE_GITIGNORE_RULE];
2537
+ return requiredRules.filter((rule) => !hasGitignoreRule(gitignoreContent, rule));
2538
+ }
2539
+ /**
2540
+ * Builds the Promptbook coder `.gitignore` block for the missing rules only.
2541
+ */
2542
+ function buildCoderGitignoreBlock(missingRules) {
2543
+ return [CODER_GITIGNORE_HEADER, ...missingRules].join('\n');
2544
+ }
2545
+ /**
2546
+ * Detects whether `.gitignore` already covers one exact rule.
2547
+ */
2548
+ function hasGitignoreRule(gitignoreContent, rule) {
2549
+ const normalizedRulePattern = rule.startsWith('/')
2550
+ ? `/?${escapeRegExp$1(rule.slice(1))}`
2551
+ : escapeRegExp$1(rule);
2552
+ return new RegExp(`(^|[\\r\\n])${normalizedRulePattern}(?:[\\r\\n]|$)`, 'u').test(gitignoreContent);
2522
2553
  }
2523
2554
  // Note: [🟡] Code for coder init gitignore bootstrapping [ensureCoderGitignoreFile](src/cli/cli-commands/coder/ensureCoderGitignoreFile.ts) should never be published outside of `@promptbook/cli`
2524
2555
 
@@ -2655,7 +2686,7 @@ async function parseJsonObjectFile(relativeFilePath, fileContent) {
2655
2686
  ${typescript.flattenDiagnosticMessageText(parsedFile.error.messageText, '\n')}
2656
2687
  `));
2657
2688
  }
2658
- if (!isPlainObject(parsedFile.config)) {
2689
+ if (!isPlainObject$1(parsedFile.config)) {
2659
2690
  throw new ParseError(spaceTrim$1(`
2660
2691
  File \`${relativeFilePath}\` must contain one top-level JSON object.
2661
2692
  `));
@@ -2669,7 +2700,7 @@ function getStringRecordOrDefault(value, relativeFilePath, fieldPath) {
2669
2700
  if (value === undefined) {
2670
2701
  return {};
2671
2702
  }
2672
- if (!isPlainObject(value)) {
2703
+ if (!isPlainObject$1(value)) {
2673
2704
  throw new ParseError(spaceTrim$1(`
2674
2705
  File \`${relativeFilePath}\` contains invalid \`${fieldPath}\`.
2675
2706
 
@@ -2714,7 +2745,7 @@ function detectJsonFileFormatting(fileContent) {
2714
2745
  /**
2715
2746
  * Checks whether one parsed JSON value is a plain object.
2716
2747
  */
2717
- function isPlainObject(value) {
2748
+ function isPlainObject$1(value) {
2718
2749
  return typeof value === 'object' && value !== null && !Array.isArray(value);
2719
2750
  }
2720
2751
  // Note: [🟡] Code for coder init JSON merging [mergeStringRecordJsonFile](src/cli/cli-commands/coder/mergeStringRecordJsonFile.ts) should never be published outside of `@promptbook/cli`
@@ -6194,15 +6225,6 @@ function prompt(strings, ...values) {
6194
6225
  // TODO: [🧠][🈴] Where is the best location for this file
6195
6226
  // Note: [💞] Ignore a discrepancy between file name and entity name
6196
6227
 
6197
- /**
6198
- * @@@
6199
- *
6200
- * @public exported from `@promptbook/utils`
6201
- */
6202
- function escapeRegExp$1(value) {
6203
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
6204
- }
6205
-
6206
6228
  /**
6207
6229
  * HTTP header used by Promptbook clients to advertise their release version.
6208
6230
  *
@@ -23197,6 +23219,7 @@ class TemplateCommitmentDefinition extends BaseCommitmentDefinition {
23197
23219
  * Supported USE types:
23198
23220
  * - USE BROWSER: Enables the agent to use a web browser tool
23199
23221
  * - USE SEARCH ENGINE (future): Enables search engine access
23222
+ * - USE DEEPSEARCH: Enables deeper research-oriented search access
23200
23223
  * - USE FILE SYSTEM (future): Enables file system operations
23201
23224
  * - USE MCP (future): Enables MCP server connections
23202
23225
  *
@@ -23219,7 +23242,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
23219
23242
  * Short one-line description of USE commitments.
23220
23243
  */
23221
23244
  get description() {
23222
- return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, etc.).';
23245
+ return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, DEEPSEARCH, etc.).';
23223
23246
  }
23224
23247
  /**
23225
23248
  * Icon for this commitment.
@@ -23240,6 +23263,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
23240
23263
 
23241
23264
  - **USE BROWSER** - Enables the agent to use a web browser tool to access and retrieve information from the internet
23242
23265
  - **USE SEARCH ENGINE** (future) - Enables search engine access
23266
+ - **USE DEEPSEARCH** - Enables deeper research-oriented search access
23243
23267
  - **USE FILE SYSTEM** (future) - Enables file system operations
23244
23268
  - **USE MCP** (future) - Enables MCP server connections
23245
23269
 
@@ -23294,7 +23318,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
23294
23318
  * Checks if this is a known USE type
23295
23319
  */
23296
23320
  isKnownUseType(useType) {
23297
- const knownTypes = ['BROWSER', 'SEARCH ENGINE', 'FILE SYSTEM', 'MCP'];
23321
+ const knownTypes = ['BROWSER', 'SEARCH ENGINE', 'DEEPSEARCH', 'FILE SYSTEM', 'MCP'];
23298
23322
  return knownTypes.includes(useType.toUpperCase());
23299
23323
  }
23300
23324
  }
@@ -23307,6 +23331,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
23307
23331
  */
23308
23332
  const AGGREGATED_USE_COMMITMENT_TYPES = [
23309
23333
  'USE BROWSER',
23334
+ 'USE DEEPSEARCH',
23310
23335
  'USE SEARCH ENGINE',
23311
23336
  'USE TIME',
23312
23337
  ];
@@ -23386,6 +23411,15 @@ function createAggregatedUseCommitmentSystemMessage(type, additionalInstructions
23386
23411
  - Do not tell the user you cannot search for information, YOU CAN.
23387
23412
  ${block(formatOptionalInstructionBlock('Search instructions', combinedAdditionalInstructions))}
23388
23413
  `);
23414
+ case 'USE DEEPSEARCH':
23415
+ return spaceTrim$1((block) => `
23416
+ Tool:
23417
+ - You have access to DeepSearch via the tool "deep_search".
23418
+ - Use it for broader research tasks that need multi-step investigation, comparison, or synthesis across multiple sources.
23419
+ - Prefer it over quick search when the user asks for a well-grounded brief, report, or deeper investigation.
23420
+ - Do not pretend you cannot research current information when this tool is available.
23421
+ ${block(formatOptionalInstructionBlock('DeepSearch instructions', combinedAdditionalInstructions))}
23422
+ `);
23389
23423
  }
23390
23424
  }
23391
23425
  /**
@@ -24935,6 +24969,207 @@ function addConfiguredCalendarIfMissing(configuredCalendars, calendarReference)
24935
24969
  }
24936
24970
  // Note: [💞] Ignore a discrepancy between file name and entity name
24937
24971
 
24972
+ /**
24973
+ * A search engine implementation that uses the SerpApi to fetch Google search results.
24974
+ *
24975
+ * @private <- TODO: !!!! Export via some package
24976
+ */
24977
+ class SerpSearchEngine {
24978
+ get title() {
24979
+ return 'SerpApi Search Engine';
24980
+ }
24981
+ get description() {
24982
+ return 'Search engine that uses SerpApi to fetch Google search results';
24983
+ }
24984
+ checkConfiguration() {
24985
+ if (!process.env.SERP_API_KEY) {
24986
+ throw new Error('SERP_API_KEY is not configured');
24987
+ }
24988
+ }
24989
+ async search(query, options = {}) {
24990
+ const apiKey = process.env.SERP_API_KEY;
24991
+ if (!apiKey) {
24992
+ throw new Error('SERP_API_KEY is not configured');
24993
+ }
24994
+ const url = new URL('https://serpapi.com/search');
24995
+ url.searchParams.set('api_key', apiKey);
24996
+ url.searchParams.set('engine', 'google');
24997
+ url.searchParams.set('q', query);
24998
+ for (const [key, value] of Object.entries(options)) {
24999
+ url.searchParams.set(key, String(value));
25000
+ }
25001
+ const response = await fetch(url.toString());
25002
+ if (!response.ok) {
25003
+ const body = await response.text();
25004
+ throw new Error(`SerpApi failed with status ${response.status}: ${response.statusText}\n${body}`);
25005
+ }
25006
+ const data = (await response.json());
25007
+ return (data.organic_results || []).map((item) => ({
25008
+ title: item.title,
25009
+ url: item.link,
25010
+ snippet: item.snippet || '',
25011
+ }));
25012
+ }
25013
+ }
25014
+
25015
+ /**
25016
+ * Creates one SERP-backed tool function used as a local fallback for search-like commitments.
25017
+ *
25018
+ * @param toolName - Technical tool name used for validation messages.
25019
+ * @param resultLabel - Human-readable label used in formatted results.
25020
+ * @returns Async tool function compatible with commitment tool registration.
25021
+ *
25022
+ * @private internal helper for search-like commitments
25023
+ */
25024
+ function createSerpSearchToolFunction(toolName, resultLabel) {
25025
+ return async (rawArgs) => {
25026
+ const { query, ...searchOptions } = rawArgs;
25027
+ if (typeof query !== 'string' || !query.trim()) {
25028
+ throw new Error(`${toolName} query is required`);
25029
+ }
25030
+ const searchEngine = new SerpSearchEngine();
25031
+ const results = await searchEngine.search(query, searchOptions);
25032
+ return spaceTrim$1((block) => `
25033
+ ${resultLabel} results for "${query}"${Object.keys(searchOptions).length === 0
25034
+ ? ''
25035
+ : ` with options ${JSON.stringify(searchOptions)}`}:
25036
+
25037
+ ${block(results
25038
+ .map((result) => spaceTrim$1(`
25039
+ - **${result.title}**
25040
+ ${result.url}
25041
+ ${result.snippet}
25042
+ `))
25043
+ .join('\n\n'))}
25044
+ `);
25045
+ };
25046
+ }
25047
+
25048
+ /**
25049
+ * USE DEEPSEARCH commitment definition
25050
+ *
25051
+ * The `USE DEEPSEARCH` commitment indicates that the agent should use a deeper research-oriented
25052
+ * search workflow instead of lightweight web search when it needs fresh information from the internet.
25053
+ *
25054
+ * The content following `USE DEEPSEARCH` is an arbitrary text that the agent should know
25055
+ * (e.g. search scope or research instructions).
25056
+ *
25057
+ * Example usage in agent source:
25058
+ *
25059
+ * ```book
25060
+ * USE DEEPSEARCH
25061
+ * USE DEEPSEARCH Compare official vendor documentation with independent benchmarks.
25062
+ * ```
25063
+ *
25064
+ * @private [🪔] Maybe export the commitments through some package
25065
+ */
25066
+ class UseDeepSearchCommitmentDefinition extends BaseCommitmentDefinition {
25067
+ constructor() {
25068
+ super('USE DEEPSEARCH');
25069
+ }
25070
+ get requiresContent() {
25071
+ return false;
25072
+ }
25073
+ /**
25074
+ * Short one-line description of USE DEEPSEARCH.
25075
+ */
25076
+ get description() {
25077
+ return 'Enable the agent to use DeepSearch for more thorough internet research.';
25078
+ }
25079
+ /**
25080
+ * Icon for this commitment.
25081
+ */
25082
+ get icon() {
25083
+ return '🔬';
25084
+ }
25085
+ /**
25086
+ * Markdown documentation for USE DEEPSEARCH commitment.
25087
+ */
25088
+ get documentation() {
25089
+ return spaceTrim$1(`
25090
+ # USE DEEPSEARCH
25091
+
25092
+ Enables the agent to use DeepSearch for broader, more thorough internet research than lightweight web search.
25093
+
25094
+ ## Key aspects
25095
+
25096
+ - The content following \`USE DEEPSEARCH\` is arbitrary guidance for the research workflow.
25097
+ - In Agents Server, the OpenAI Agents SDK runtime uses a nested deep-research agent for this tool.
25098
+ - Use this for investigations, comparisons, market scans, or other tasks that benefit from deeper synthesis.
25099
+ - Prefer regular \`USE SEARCH ENGINE\` when a quick factual lookup is enough.
25100
+
25101
+ ## Examples
25102
+
25103
+ \`\`\`book
25104
+ Due Diligence Researcher
25105
+
25106
+ GOAL Investigate vendors thoroughly before making recommendations.
25107
+ USE DEEPSEARCH Compare official sources with credible third-party analysis.
25108
+ RULE Cite the strongest supporting sources in the final answer.
25109
+ \`\`\`
25110
+
25111
+ \`\`\`book
25112
+ Market Analyst
25113
+
25114
+ GOAL Build concise but well-grounded research briefs.
25115
+ USE DEEPSEARCH Focus on recent public information and competing viewpoints.
25116
+ CLOSED
25117
+ \`\`\`
25118
+ `);
25119
+ }
25120
+ applyToAgentModelRequirements(requirements, content) {
25121
+ const existingTools = requirements.tools || [];
25122
+ const updatedTools = existingTools.some((tool) => tool.name === 'deep_search')
25123
+ ? existingTools
25124
+ : [
25125
+ ...existingTools,
25126
+ {
25127
+ name: 'deep_search',
25128
+ description: spaceTrim$1(`
25129
+ Research the internet deeply and synthesize a grounded answer.
25130
+ Use this tool for broader investigations, comparisons, and requests that need more than a quick search.
25131
+ `),
25132
+ parameters: {
25133
+ type: 'object',
25134
+ properties: {
25135
+ query: {
25136
+ type: 'string',
25137
+ description: 'The research question or investigation request.',
25138
+ },
25139
+ },
25140
+ required: ['query'],
25141
+ additionalProperties: false,
25142
+ },
25143
+ },
25144
+ ];
25145
+ return appendAggregatedUseCommitmentPlaceholder({
25146
+ ...requirements,
25147
+ tools: updatedTools,
25148
+ _metadata: {
25149
+ ...requirements._metadata,
25150
+ useDeepSearch: content || true,
25151
+ },
25152
+ }, this.type);
25153
+ }
25154
+ /**
25155
+ * Gets human-readable titles for tool functions provided by this commitment.
25156
+ */
25157
+ getToolTitles() {
25158
+ return {
25159
+ deep_search: 'DeepSearch',
25160
+ };
25161
+ }
25162
+ /**
25163
+ * Gets the local fallback implementation for the `deep_search` tool.
25164
+ */
25165
+ getToolFunctions() {
25166
+ return {
25167
+ deep_search: createSerpSearchToolFunction('deep_search', 'DeepSearch'),
25168
+ };
25169
+ }
25170
+ }
25171
+ // Note: [💞] Ignore a discrepancy between file name and entity name
25172
+
24938
25173
  /**
24939
25174
  * Lightweight email token matcher used for `USE EMAIL` first-line parsing.
24940
25175
  *
@@ -27322,49 +27557,6 @@ function addConfiguredProjectIfMissing(configuredProjects, repositoryReference)
27322
27557
  }
27323
27558
  // Note: [💞] Ignore a discrepancy between file name and entity name
27324
27559
 
27325
- /**
27326
- * A search engine implementation that uses the SerpApi to fetch Google search results.
27327
- *
27328
- * @private <- TODO: !!!! Export via some package
27329
- */
27330
- class SerpSearchEngine {
27331
- get title() {
27332
- return 'SerpApi Search Engine';
27333
- }
27334
- get description() {
27335
- return 'Search engine that uses SerpApi to fetch Google search results';
27336
- }
27337
- checkConfiguration() {
27338
- if (!process.env.SERP_API_KEY) {
27339
- throw new Error('SERP_API_KEY is not configured');
27340
- }
27341
- }
27342
- async search(query, options = {}) {
27343
- const apiKey = process.env.SERP_API_KEY;
27344
- if (!apiKey) {
27345
- throw new Error('SERP_API_KEY is not configured');
27346
- }
27347
- const url = new URL('https://serpapi.com/search');
27348
- url.searchParams.set('api_key', apiKey);
27349
- url.searchParams.set('engine', 'google');
27350
- url.searchParams.set('q', query);
27351
- for (const [key, value] of Object.entries(options)) {
27352
- url.searchParams.set(key, String(value));
27353
- }
27354
- const response = await fetch(url.toString());
27355
- if (!response.ok) {
27356
- const body = await response.text();
27357
- throw new Error(`SerpApi failed with status ${response.status}: ${response.statusText}\n${body}`);
27358
- }
27359
- const data = (await response.json());
27360
- return (data.organic_results || []).map((item) => ({
27361
- title: item.title,
27362
- url: item.link,
27363
- snippet: item.snippet || '',
27364
- }));
27365
- }
27366
- }
27367
-
27368
27560
  /**
27369
27561
  * USE SEARCH ENGINE commitment definition
27370
27562
  *
@@ -27508,26 +27700,7 @@ class UseSearchEngineCommitmentDefinition extends BaseCommitmentDefinition {
27508
27700
  */
27509
27701
  getToolFunctions() {
27510
27702
  return {
27511
- async web_search(args) {
27512
- console.log('!!!! [Tool] web_search called', { args });
27513
- const { query, ...options } = args;
27514
- if (!query) {
27515
- throw new Error('Search query is required');
27516
- }
27517
- const searchEngine = new SerpSearchEngine();
27518
- const results = await searchEngine.search(query, options);
27519
- return spaceTrim$1((block) => `
27520
- Search results for "${query}"${Object.keys(options).length === 0 ? '' : ` with options ${JSON.stringify(options)}`}:
27521
-
27522
- ${block(results
27523
- .map((result) => spaceTrim$1(`
27524
- - **${result.title}**
27525
- ${result.url}
27526
- ${result.snippet}
27527
- `))
27528
- .join('\n\n'))}
27529
- `);
27530
- },
27703
+ web_search: createSerpSearchToolFunction('web_search', 'Search'),
27531
27704
  };
27532
27705
  }
27533
27706
  }
@@ -29173,6 +29346,7 @@ const COMMITMENT_REGISTRY = [
29173
29346
  new ClosedCommitmentDefinition(),
29174
29347
  new TeamCommitmentDefinition(),
29175
29348
  new UseBrowserCommitmentDefinition(),
29349
+ new UseDeepSearchCommitmentDefinition(),
29176
29350
  new UseSearchEngineCommitmentDefinition(),
29177
29351
  new UseSpawnCommitmentDefinition(),
29178
29352
  new UseTimeoutCommitmentDefinition(),
@@ -42226,6 +42400,11 @@ const SIMPLE_CAPABILITY_BY_COMMITMENT_TYPE = {
42226
42400
  label: 'Internet',
42227
42401
  iconName: 'Search',
42228
42402
  },
42403
+ 'USE DEEPSEARCH': {
42404
+ type: 'search-engine',
42405
+ label: 'DeepSearch',
42406
+ iconName: 'Search',
42407
+ },
42229
42408
  'USE TIME': {
42230
42409
  type: 'time',
42231
42410
  label: 'Time',
@@ -43289,6 +43468,246 @@ const EMOJIS_OF_SINGLE_PICTOGRAM = new Set(Array.from(EMOJIS).filter((emoji) =>
43289
43468
  // TODO: Mirror from Collboard or some common package
43290
43469
  // Note: [💞] Ignore a discrepancy between file name and entity name
43291
43470
 
43471
+ /**
43472
+ * Default file globs scanned for emoji tags.
43473
+ */
43474
+ const DEFAULT_INCLUDE_GLOBS = ['**/*.{ts,tsx,js,jsx,json,md,txt}'];
43475
+ /**
43476
+ * Default ignored paths while scanning the repository.
43477
+ */
43478
+ const DEFAULT_IGNORE_GLOBS = ['**/node_modules/**', '**/.git/**', '**/.promptbook/ptbk-coder/**'];
43479
+ /**
43480
+ * Directory used for Promptbook coder runtime caches.
43481
+ */
43482
+ const PTBK_CODER_CACHE_DIRECTORY_PATH = '.promptbook/ptbk-coder';
43483
+ /**
43484
+ * Relative cache file path storing per-file emoji-tag scan results.
43485
+ */
43486
+ const EMOJI_TAG_SCAN_CACHE_FILE_PATH = `${PTBK_CODER_CACHE_DIRECTORY_PATH}/emoji-tag-scan-cache.json`;
43487
+ /**
43488
+ * Current schema version of the persisted emoji-tag scan cache.
43489
+ */
43490
+ const EMOJI_TAG_SCAN_CACHE_VERSION = 1;
43491
+ /**
43492
+ * Scans repository files for bracketed emoji tags while reusing per-file cache entries for unchanged files.
43493
+ */
43494
+ async function scanEmojiTagUsage(options) {
43495
+ var _a, _b, _c, _d;
43496
+ const rootDir = (_a = options.rootDir) !== null && _a !== void 0 ? _a : process.cwd();
43497
+ const includeGlobs = (_b = options.includeGlobs) !== null && _b !== void 0 ? _b : DEFAULT_INCLUDE_GLOBS;
43498
+ const ignoreGlobs = (_c = options.ignoreGlobs) !== null && _c !== void 0 ? _c : DEFAULT_IGNORE_GLOBS;
43499
+ const tagPrefix = (_d = options.tagPrefix) !== null && _d !== void 0 ? _d : '';
43500
+ const filesToScan = await findFilesToScan(rootDir, includeGlobs, ignoreGlobs);
43501
+ const matcher = buildEmojiTagMatcher(options.candidateEmojis, tagPrefix);
43502
+ const usedEmojis = new Set();
43503
+ const existingCache = await readEmojiTagScanCache(rootDir);
43504
+ const nextCacheFiles = { ...existingCache.files };
43505
+ let isCacheDirty = false;
43506
+ let scannedFileCount = 0;
43507
+ let reusedFileCount = 0;
43508
+ for (const filePath of filesToScan) {
43509
+ try {
43510
+ const fileStats = statSync(filePath);
43511
+ const cacheKey = toCacheKey(rootDir, filePath);
43512
+ const cachedFile = nextCacheFiles[cacheKey];
43513
+ const cachedEmojis = getCachedFileEmojis(cachedFile, fileStats.mtimeMs, fileStats.size, tagPrefix);
43514
+ if (cachedEmojis) {
43515
+ reusedFileCount += 1;
43516
+ addEmojis(usedEmojis, cachedEmojis);
43517
+ continue;
43518
+ }
43519
+ const fileContent = readFileSync(filePath, 'utf-8'); /* Note: sync file reads are fine for local tooling. */
43520
+ const scannedEmojis = scanFileForEmojiTags(fileContent, matcher);
43521
+ addEmojis(usedEmojis, scannedEmojis);
43522
+ nextCacheFiles[cacheKey] = updateCachedFile(cachedFile, fileStats.mtimeMs, fileStats.size, tagPrefix, scannedEmojis);
43523
+ scannedFileCount += 1;
43524
+ isCacheDirty = true;
43525
+ }
43526
+ catch (error) {
43527
+ if (options.onFileError) {
43528
+ options.onFileError(normalizeError(error), filePath);
43529
+ continue;
43530
+ }
43531
+ throw error;
43532
+ }
43533
+ }
43534
+ if (isCacheDirty) {
43535
+ await writeEmojiTagScanCache(rootDir, {
43536
+ version: EMOJI_TAG_SCAN_CACHE_VERSION,
43537
+ files: nextCacheFiles,
43538
+ });
43539
+ }
43540
+ return {
43541
+ usedEmojis,
43542
+ scannedFileCount,
43543
+ reusedFileCount,
43544
+ };
43545
+ }
43546
+ /**
43547
+ * Resolves files to scan for matching emoji tags.
43548
+ */
43549
+ async function findFilesToScan(rootDir, includeGlobs, ignoreGlobs) {
43550
+ const files = new Set();
43551
+ for (const pattern of includeGlobs) {
43552
+ const matches = await glob(pattern, {
43553
+ cwd: rootDir,
43554
+ ignore: Array.from(new Set([...ignoreGlobs, ...DEFAULT_IGNORE_GLOBS])),
43555
+ nodir: true,
43556
+ absolute: true,
43557
+ });
43558
+ for (const match of matches) {
43559
+ files.add(match);
43560
+ }
43561
+ }
43562
+ return Array.from(files);
43563
+ }
43564
+ /**
43565
+ * Builds a regex that matches one exact bracketed emoji-tag form.
43566
+ */
43567
+ function buildEmojiTagMatcher(candidateEmojis, tagPrefix) {
43568
+ const escapedEmojiAlternatives = Array.from(candidateEmojis)
43569
+ .sort((leftEmoji, rightEmoji) => rightEmoji.length - leftEmoji.length)
43570
+ .map((emoji) => escapeRegExp$1(emoji))
43571
+ .join('|');
43572
+ if (escapedEmojiAlternatives === '') {
43573
+ return /$^/u;
43574
+ }
43575
+ return new RegExp(`\\[${escapeRegExp$1(tagPrefix)}(?<emoji>${escapedEmojiAlternatives})\\]`, 'gu');
43576
+ }
43577
+ /**
43578
+ * Extracts matching emojis from one file content.
43579
+ */
43580
+ function scanFileForEmojiTags(fileContent, matcher) {
43581
+ var _a;
43582
+ matcher.lastIndex = 0;
43583
+ const matchedEmojis = new Set();
43584
+ for (const match of fileContent.matchAll(matcher)) {
43585
+ const emoji = (_a = match.groups) === null || _a === void 0 ? void 0 : _a.emoji;
43586
+ if (emoji) {
43587
+ matchedEmojis.add(emoji);
43588
+ }
43589
+ }
43590
+ return Array.from(matchedEmojis);
43591
+ }
43592
+ /**
43593
+ * Returns cached emojis when the file metadata still matches the stored cache entry.
43594
+ */
43595
+ function getCachedFileEmojis(cachedFile, mtimeMs, size, tagPrefix) {
43596
+ if (!cachedFile || cachedFile.mtimeMs !== mtimeMs || cachedFile.size !== size) {
43597
+ return undefined;
43598
+ }
43599
+ const cachedEmojis = cachedFile.tagsByPrefix[tagPrefix];
43600
+ return Array.isArray(cachedEmojis) ? cachedEmojis : undefined;
43601
+ }
43602
+ /**
43603
+ * Stores the latest scan result for one file while preserving other prefix caches when the file revision is unchanged.
43604
+ */
43605
+ function updateCachedFile(cachedFile, mtimeMs, size, tagPrefix, emojis) {
43606
+ const isSameFileRevision = (cachedFile === null || cachedFile === void 0 ? void 0 : cachedFile.mtimeMs) === mtimeMs && cachedFile.size === size;
43607
+ return {
43608
+ mtimeMs,
43609
+ size,
43610
+ tagsByPrefix: {
43611
+ ...(isSameFileRevision ? cachedFile.tagsByPrefix : {}),
43612
+ [tagPrefix]: [...emojis],
43613
+ },
43614
+ };
43615
+ }
43616
+ /**
43617
+ * Loads the persisted emoji-tag scan cache and falls back to an empty cache when it is missing or invalid.
43618
+ */
43619
+ async function readEmojiTagScanCache(rootDir) {
43620
+ try {
43621
+ const cacheContent = await readFile(join(rootDir, EMOJI_TAG_SCAN_CACHE_FILE_PATH), 'utf-8');
43622
+ return normalizeEmojiTagScanCache(JSON.parse(cacheContent));
43623
+ }
43624
+ catch (_a) {
43625
+ return createEmptyEmojiTagScanCache();
43626
+ }
43627
+ }
43628
+ /**
43629
+ * Persists the updated emoji-tag scan cache as a best-effort optimization.
43630
+ */
43631
+ async function writeEmojiTagScanCache(rootDir, cache) {
43632
+ try {
43633
+ await mkdir(join(rootDir, PTBK_CODER_CACHE_DIRECTORY_PATH), { recursive: true });
43634
+ await writeFile(join(rootDir, EMOJI_TAG_SCAN_CACHE_FILE_PATH), `${JSON.stringify(cache, null, 2)}\n`, 'utf-8');
43635
+ }
43636
+ catch (_a) {
43637
+ // Note: Cache writes are only an optimization; scanning still succeeds when the cache cannot be written.
43638
+ }
43639
+ }
43640
+ /**
43641
+ * Normalizes one parsed cache payload into the current typed cache shape.
43642
+ */
43643
+ function normalizeEmojiTagScanCache(value) {
43644
+ if (!isPlainObject(value) || value.version !== EMOJI_TAG_SCAN_CACHE_VERSION || !isPlainObject(value.files)) {
43645
+ return createEmptyEmojiTagScanCache();
43646
+ }
43647
+ const files = {};
43648
+ for (const [filePath, cachedValue] of Object.entries(value.files)) {
43649
+ if (!isPlainObject(cachedValue)) {
43650
+ continue;
43651
+ }
43652
+ const { mtimeMs, size, tagsByPrefix } = cachedValue;
43653
+ if (typeof mtimeMs !== 'number' || typeof size !== 'number' || !isPlainObject(tagsByPrefix)) {
43654
+ continue;
43655
+ }
43656
+ const normalizedTagsByPrefix = {};
43657
+ for (const [tagPrefix, cachedEmojis] of Object.entries(tagsByPrefix)) {
43658
+ if (!Array.isArray(cachedEmojis)) {
43659
+ continue;
43660
+ }
43661
+ normalizedTagsByPrefix[tagPrefix] = cachedEmojis.filter((emoji) => typeof emoji === 'string');
43662
+ }
43663
+ files[filePath] = {
43664
+ mtimeMs,
43665
+ size,
43666
+ tagsByPrefix: normalizedTagsByPrefix,
43667
+ };
43668
+ }
43669
+ return {
43670
+ version: EMOJI_TAG_SCAN_CACHE_VERSION,
43671
+ files,
43672
+ };
43673
+ }
43674
+ /**
43675
+ * Creates an empty cache payload for emoji-tag scans.
43676
+ */
43677
+ function createEmptyEmojiTagScanCache() {
43678
+ return {
43679
+ version: EMOJI_TAG_SCAN_CACHE_VERSION,
43680
+ files: {},
43681
+ };
43682
+ }
43683
+ /**
43684
+ * Converts an absolute file path into a stable cache key relative to the scanned root.
43685
+ */
43686
+ function toCacheKey(rootDir, filePath) {
43687
+ return relative(rootDir, filePath).replace(/\\/gu, '/');
43688
+ }
43689
+ /**
43690
+ * Adds multiple emojis into one target set.
43691
+ */
43692
+ function addEmojis(target, emojis) {
43693
+ for (const emoji of emojis) {
43694
+ target.add(emoji);
43695
+ }
43696
+ }
43697
+ /**
43698
+ * Checks whether one unknown JSON value is a plain object.
43699
+ */
43700
+ function isPlainObject(value) {
43701
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
43702
+ }
43703
+ /**
43704
+ * Normalizes thrown values into proper `Error` objects for optional callbacks.
43705
+ */
43706
+ function normalizeError(error) {
43707
+ return error instanceof Error ? error : new Error(String(error));
43708
+ }
43709
+ // Note: [?] Code in this file should never be published in any package
43710
+
43292
43711
  // find-fresh-emoji-tags.ts
43293
43712
  // Note: When run as a standalone script, call the exported function
43294
43713
  if (require.main === module) {
@@ -43318,29 +43737,16 @@ function initializeFindFreshEmojiTagRun() {
43318
43737
  async function findFreshEmojiTag() {
43319
43738
  initializeFindFreshEmojiTagRun();
43320
43739
  console.info(`🤪 Find fresh emoji tag`);
43321
- const allFiles = await glob('**/*.{ts,tsx,js,jsx,json,md,txt}', {
43322
- ignore: '**/node_modules/**', // <- TODO: [🚰] Ignore also hidden folders like *(`.promptbook`, `.next`, `.git`,...)*
43323
- });
43324
43740
  const allEmojis = EMOJIS_OF_SINGLE_PICTOGRAM;
43325
- // const allEmojis = new Set<string_char_emoji>(['🧎' as string_char_emoji, '🥎' as string_char_emoji]);
43326
- const usedEmojis = new Set();
43327
- for (const file of allFiles) {
43328
- try {
43329
- const content = readFileSync(file, 'utf-8'); /* <- Note: Its OK to use sync in tooling for scripts */
43330
- for (const emoji of allEmojis) {
43331
- const tag = `[${emoji}]`;
43332
- if (content.includes(tag)) {
43333
- usedEmojis.add(emoji);
43334
- }
43335
- }
43336
- }
43337
- catch (error) {
43338
- console.error(colors.red('Error in checking file file /' + file));
43741
+ const { usedEmojis } = await scanEmojiTagUsage({
43742
+ candidateEmojis: allEmojis,
43743
+ tagPrefix: '',
43744
+ onFileError: (error, filePath) => {
43745
+ console.error(colors.red('Error in checking file /' + filePath));
43339
43746
  console.error(error);
43340
- }
43341
- }
43342
- //console.info({ usedEmojis });
43343
- const freshEmojis = difference(allEmojis, usedEmojis);
43747
+ },
43748
+ });
43749
+ const freshEmojis = new Set(Array.from(allEmojis).filter((emoji) => !usedEmojis.has(emoji)));
43344
43750
  console.info(colors.green(`Avialable fresh tags:`));
43345
43751
  const randomEmojis = [...$shuffleItems(...Array.from(freshEmojis))].splice(0, 10);
43346
43752
  // const randomEmojis = freshEmojis;
@@ -43948,9 +44354,14 @@ async function getFreshPromptEmojiTags(options) {
43948
44354
  const includeGlobs = (_b = options.includeGlobs) !== null && _b !== void 0 ? _b : ['**/*.{ts,tsx,js,jsx,json,md,txt}'];
43949
44355
  const ignoreGlobs = (_c = options.ignoreGlobs) !== null && _c !== void 0 ? _c : ['**/node_modules/**'];
43950
44356
  const tagPrefix = (_d = options.tagPrefix) !== null && _d !== void 0 ? _d : PROMPT_EMOJI_TAG_PREFIX;
43951
- const filesToScan = await findFilesToScan(rootDir, includeGlobs, ignoreGlobs);
43952
- const usedEmojis = collectUsedPromptEmojis(filesToScan, tagPrefix);
43953
- const freshEmojis = difference(EMOJIS_OF_SINGLE_PICTOGRAM, usedEmojis);
44357
+ const { usedEmojis } = await scanEmojiTagUsage({
44358
+ rootDir,
44359
+ includeGlobs,
44360
+ ignoreGlobs,
44361
+ tagPrefix,
44362
+ candidateEmojis: EMOJIS_OF_SINGLE_PICTOGRAM,
44363
+ });
44364
+ const freshEmojis = new Set(Array.from(EMOJIS_OF_SINGLE_PICTOGRAM).filter((emoji) => !usedEmojis.has(emoji)));
43954
44365
  const shuffledEmojis = $shuffleItems(...Array.from(freshEmojis));
43955
44366
  const selectedEmojis = shuffledEmojis.slice(0, count);
43956
44367
  if (selectedEmojis.length < count) {
@@ -43962,40 +44373,6 @@ async function getFreshPromptEmojiTags(options) {
43962
44373
  tagPrefix,
43963
44374
  };
43964
44375
  }
43965
- /**
43966
- * Resolves files to scan for existing prompt emoji tags.
43967
- */
43968
- async function findFilesToScan(rootDir, includeGlobs, ignoreGlobs) {
43969
- const files = new Set();
43970
- for (const pattern of includeGlobs) {
43971
- const matches = await glob(pattern, {
43972
- cwd: rootDir,
43973
- ignore: ignoreGlobs,
43974
- nodir: true,
43975
- absolute: true,
43976
- });
43977
- for (const match of matches) {
43978
- files.add(match);
43979
- }
43980
- }
43981
- return Array.from(files);
43982
- }
43983
- /**
43984
- * Collects emojis already used with the configured prompt prefix.
43985
- */
43986
- function collectUsedPromptEmojis(filePaths, tagPrefix) {
43987
- const usedEmojis = new Set();
43988
- for (const file of filePaths) {
43989
- const content = readFileSync(file, 'utf-8'); /* Note: sync read is fine for script tooling. */
43990
- for (const emoji of EMOJIS_OF_SINGLE_PICTOGRAM) {
43991
- const tag = formatPromptEmojiTag(emoji, tagPrefix);
43992
- if (content.includes(tag)) {
43993
- usedEmojis.add(emoji);
43994
- }
43995
- }
43996
- }
43997
- return usedEmojis;
43998
- }
43999
44376
  // Note: [?] Code in this file should never be published in any package
44000
44377
 
44001
44378
  var promptEmojiTags = /*#__PURE__*/Object.freeze({
@@ -45024,6 +45401,27 @@ async function waitForEnter(prompt) {
45024
45401
  * Current pause state.
45025
45402
  */
45026
45403
  let pauseState = 'RUNNING';
45404
+ /**
45405
+ * Stores one new pause state in the shared runner controller.
45406
+ */
45407
+ function setPauseState(nextPauseState) {
45408
+ pauseState = nextPauseState;
45409
+ }
45410
+ /**
45411
+ * Applies the same three-state toggle used by the `P` hotkey.
45412
+ */
45413
+ function togglePauseState() {
45414
+ if (pauseState === 'RUNNING') {
45415
+ setPauseState('PAUSING');
45416
+ return 'REQUESTED_PAUSE';
45417
+ }
45418
+ if (pauseState === 'PAUSING') {
45419
+ setPauseState('RUNNING');
45420
+ return 'CANCELLED_PAUSE';
45421
+ }
45422
+ setPauseState('RUNNING');
45423
+ return 'RESUMED';
45424
+ }
45027
45425
  /**
45028
45426
  * Listens for the "p" key to pause and resume.
45029
45427
  */
@@ -45038,19 +45436,13 @@ function listenForPause() {
45038
45436
  process.exit();
45039
45437
  }
45040
45438
  if (key.name === 'p') {
45041
- if (pauseState === 'RUNNING') {
45042
- pauseState = 'PAUSING';
45439
+ const toggleResult = togglePauseState();
45440
+ if (toggleResult === 'REQUESTED_PAUSE') {
45043
45441
  // Note: Using console.log here which adds a new line.
45044
45442
  // This is intentional to prevent the message from being overwritten.
45045
45443
  console.log(colors.bgWhite('Pausing...'));
45046
45444
  }
45047
- else if (pauseState === 'PAUSED') {
45048
- pauseState = 'RUNNING';
45049
- // The checkPause loop will terminate.
45050
- }
45051
- else if (pauseState === 'PAUSING') {
45052
- // If user presses 'p' again while pausing, cancel the pause.
45053
- pauseState = 'RUNNING';
45445
+ else if (toggleResult === 'CANCELLED_PAUSE') {
45054
45446
  console.log(colors.green('Pause cancelled. Resuming...'));
45055
45447
  }
45056
45448
  }
@@ -45066,12 +45458,12 @@ function listenForPause() {
45066
45458
  async function checkPause(options) {
45067
45459
  var _a, _b;
45068
45460
  if (pauseState === 'PAUSING') {
45069
- pauseState = 'PAUSED';
45461
+ setPauseState('PAUSED');
45070
45462
  if (!(options === null || options === void 0 ? void 0 : options.silent)) {
45071
45463
  console.log(colors.bgWhite.black('Paused') + colors.gray(' (Press "p" to resume)'));
45072
45464
  }
45073
45465
  (_a = options === null || options === void 0 ? void 0 : options.onPaused) === null || _a === void 0 ? void 0 : _a.call(options);
45074
- while (pauseState === 'PAUSED') {
45466
+ while (getPauseState() === 'PAUSED') {
45075
45467
  await new Promise((resolve) => setTimeout(resolve, 100));
45076
45468
  }
45077
45469
  (_b = options === null || options === void 0 ? void 0 : options.onResumed) === null || _b === void 0 ? void 0 : _b.call(options);
@@ -45086,20 +45478,6 @@ async function checkPause(options) {
45086
45478
  function getPauseState() {
45087
45479
  return pauseState;
45088
45480
  }
45089
- /**
45090
- * Requests a pause from an external controller (e.g. the Ink UI).
45091
- */
45092
- function requestPause() {
45093
- if (pauseState === 'RUNNING') {
45094
- pauseState = 'PAUSING';
45095
- }
45096
- }
45097
- /**
45098
- * Resumes execution from an external controller after a pause.
45099
- */
45100
- function requestResume() {
45101
- pauseState = 'RUNNING';
45102
- }
45103
45481
 
45104
45482
  /**
45105
45483
  * Environment variable that configures the name used for agent commits.
@@ -48739,49 +49117,215 @@ function buildPromptTestScriptPath(scriptPath) {
48739
49117
  return `${scriptPath}.test.sh`;
48740
49118
  }
48741
49119
 
49120
+ /**
49121
+ * Centers an ANSI-colored line within the available frame width.
49122
+ *
49123
+ * @private internal utility of coder run UI
49124
+ */
49125
+ function centerAnsiText(text, width) {
49126
+ const paddingWidth = Math.max(0, Math.floor((width - visibleLength(text)) / 2));
49127
+ return `${' '.repeat(paddingWidth)}${text}`;
49128
+ }
49129
+ /**
49130
+ * Pads or truncates a possibly ANSI-colored line to the target visible width.
49131
+ *
49132
+ * @private internal utility of coder run UI
49133
+ */
49134
+ function padAnsiText(text, width) {
49135
+ const fittedText = fitAnsiText(text, width);
49136
+ return fittedText + ' '.repeat(Math.max(0, width - visibleLength(fittedText)));
49137
+ }
49138
+ /**
49139
+ * Truncates a possibly ANSI-colored line to the target visible width.
49140
+ *
49141
+ * @private internal utility of coder run UI
49142
+ */
49143
+ function fitAnsiText(text, width) {
49144
+ if (visibleLength(text) <= width) {
49145
+ return text;
49146
+ }
49147
+ return fitPlainText(stripAnsi(text), width);
49148
+ }
49149
+ /**
49150
+ * Truncates a plain-text line to the target width with an ellipsis.
49151
+ *
49152
+ * @private internal utility of coder run UI
49153
+ */
49154
+ function fitPlainText(text, width) {
49155
+ if (text.length <= width) {
49156
+ return text;
49157
+ }
49158
+ if (width <= 3) {
49159
+ return '.'.repeat(width);
49160
+ }
49161
+ return `${text.slice(0, width - 3)}...`;
49162
+ }
49163
+ /**
49164
+ * Measures visible string width by stripping ANSI escape codes.
49165
+ *
49166
+ * @private internal utility of coder run UI
49167
+ */
49168
+ function visibleLength(text) {
49169
+ return stripAnsi(text).length;
49170
+ }
49171
+ /**
49172
+ * Strips ANSI escape codes from a string.
49173
+ *
49174
+ * @private internal utility of coder run UI
49175
+ */
49176
+ function stripAnsi(text) {
49177
+ // eslint-disable-next-line no-control-regex
49178
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
49179
+ }
49180
+
49181
+ /**
49182
+ * Fixed left-side head used across all octopus frames.
49183
+ */
49184
+ const OCTOPUS_HEAD_LINES = [
49185
+ colors.magenta.bold(' .-""""-.'),
49186
+ colors.magenta.bold(" .' .-. '."),
49187
+ `${colors.magenta.bold(' / (')}${colors.yellow.bold('o o')}${colors.magenta.bold(') \\')}`,
49188
+ colors.magenta.bold(' | ^ |'),
49189
+ colors.magenta.bold(' | \\___/ |'),
49190
+ colors.magenta.bold(' \\___________/'),
49191
+ ];
49192
+ /**
49193
+ * Gap between the head silhouette and the animated tentacles.
49194
+ */
49195
+ const OCTOPUS_HEAD_TO_TENTACLE_GAP = ' ';
49196
+ /**
49197
+ * Animated right-side tentacle poses.
49198
+ */
49199
+ const OCTOPUS_TENTACLE_FRAMES = [
49200
+ [
49201
+ ` ${colors.green.bold('ptbk.io')}`,
49202
+ colors.cyan(' __ __'),
49203
+ colors.cyan(' _/\\/\\/ \\_/\\/ \\_/\\_'),
49204
+ colors.cyan('_/\\/ _ /\\/ _ /\\/\\__'),
49205
+ colors.cyan('\\__/ /_/\\/ \\_/ \\_/ \\_/'),
49206
+ colors.cyan(' /_/ \\__/ \\__/ /'),
49207
+ ],
49208
+ [
49209
+ ` ${colors.green.bold('ptbk.io')}`,
49210
+ colors.cyan(' __ __'),
49211
+ colors.cyan(' _/\\/ \\_/\\/\\_ \\_/\\_'),
49212
+ colors.cyan('_/\\/ _ /\\/ _ \\/\\/ \\__'),
49213
+ colors.cyan('\\__/ /_/\\/ \\_/ \\_ /\\_/'),
49214
+ colors.cyan(' /_/ \\__/ \\__/ /'),
49215
+ ],
49216
+ [
49217
+ ` ${colors.green.bold('ptbk.io')}`,
49218
+ colors.cyan(' __ __'),
49219
+ colors.cyan(' _/\\/\\/ \\_/\\_ \\_/\\_'),
49220
+ colors.cyan('_/\\/ _ /\\/ _ /\\/ \\_'),
49221
+ colors.cyan('\\__/ /_/\\/ \\_/ \\_/ _/'),
49222
+ colors.cyan(' /_/ \\__/ \\__/'),
49223
+ ],
49224
+ [
49225
+ ` ${colors.green.bold('ptbk.io')}`,
49226
+ colors.cyan(' __ __'),
49227
+ colors.cyan(' _/\\/ \\_/\\/ \\_/\\/\\_'),
49228
+ colors.cyan('_/\\/ _ /\\/ _ /\\/\\ \\'),
49229
+ colors.cyan('\\__/ /_/\\/ \\_/ \\_ \\_/'),
49230
+ colors.cyan(' /_/ \\__/ \\__\\_/'),
49231
+ ],
49232
+ ];
49233
+ /**
49234
+ * Builds the horizontal octopus illustration shown above the coder-run dashboard.
49235
+ *
49236
+ * @private internal utility of coder run UI
49237
+ */
49238
+ function buildCoderRunOctopusVisual(options) {
49239
+ const tentacleFrame = OCTOPUS_TENTACLE_FRAMES[((options.animationFrame % OCTOPUS_TENTACLE_FRAMES.length) + OCTOPUS_TENTACLE_FRAMES.length) %
49240
+ OCTOPUS_TENTACLE_FRAMES.length];
49241
+ const visualLines = OCTOPUS_HEAD_LINES.map((headLine, lineIndex) => `${headLine}${OCTOPUS_HEAD_TO_TENTACLE_GAP}${tentacleFrame[lineIndex]}`);
49242
+ const visualWidth = visualLines.reduce((maxWidth, line) => Math.max(maxWidth, visibleLength(line)), 0);
49243
+ visualLines.map((line) => centerAnsiText(padAnsiText(line, visualWidth), options.totalWidth));
49244
+ /*
49245
+ Note: Octopus art should look better, now using just text
49246
+ https://www.google.com/search?sca_esv=52e84acb78c558cc&sxsrf=ANbL-n7DVKf71T1HSPRpM-2skfMss0jh7w:1776693767588&udm=2&fbs=ADc_l-ZseckkBJUFopaGDNYa-HGjo4_b6b_a7pIHTL5Y9QnExg6xJqXbG7aOLcH8CWqOtkzCrjxXWZVmrIhYPvZzFDVUIb7oTJfuJ6idsCc5GA1j5KGoi2q3sW0uDBWWfYgbuxGWTQPZMetvj33BdP833wZm47mxW-6rC3bTQWluwJdOsgloPieyQvTfF2uNgIZ_K0KZ-WzpL1An8GuRrKqHdvl8T306FA&q=octopus&sa=X&ved=2ahUKEwil7ZCHzPyTAxUghP0HHY-_Js8QtKgLegQIOhAB&biw=1745&bih=903&dpr=1.1#sv=CAMSVhoyKhBlLVl1bHNmeVhneml6a1dNMg5ZdWxzZnlYZ3ppemtXTToOTVJvdXhTdk5STkZ4d00gBCocCgZtb3NhaWMSEGUtWXVsc2Z5WGd6aXprV00YADABGAcg46LBxA9KCBABGAEgASgB
49247
+ https://www.mrgoodfish.com/wp-content/uploads/2022/09/Eledone_moschata__.png
49248
+ https://fish-commercial-names.ec.europa.eu/fish-names/jakarta.faces.resource/pictograms/octopus_vulgaris.jpg.xhtml?ln=images
49249
+ https://www.asciiart.eu/image-to-ascii
49250
+ */
49251
+ // Note: Created by https://patorjk.com/software/taag/#p=display&f=ANSI+Compact&t=ptbk.io&x=none&v=4&h=4&w=80&we=false
49252
+ return _spaceTrim(`
49253
+
49254
+ ▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄ ▄▄ ▄▄ ▄▄ ▄▄▄
49255
+ ██▄█▀ ██ ██▄██ ██▄█▀ ██ ██▀██
49256
+ ██ ██ ██▄█▀ ██ ██ ▄ ██ ▀███▀
49257
+
49258
+ `)
49259
+ .split('\n')
49260
+ .map((line) => centerAnsiText(line, options.totalWidth));
49261
+ }
49262
+
49263
+ /**
49264
+ * Refresh cadence used only while the rich coder UI needs animated updates.
49265
+ *
49266
+ * @private internal constant of coder run UI
49267
+ */
49268
+ const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = 300;
49269
+ /**
49270
+ * Phases that still benefit from automatic refreshes because the frame can change
49271
+ * over time even without new runner output.
49272
+ *
49273
+ * @private internal constant of coder run UI
49274
+ */
49275
+ const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
49276
+ /**
49277
+ * Returns whether the rich coder UI should keep animating on its own.
49278
+ *
49279
+ * @private internal utility of coder run UI
49280
+ */
49281
+ function isCoderRunUiAutoRefreshing(phase, pauseState) {
49282
+ // `PAUSING` still means the current task is winding down, so keep active
49283
+ // animations/timers running until the runner reaches the fully paused state.
49284
+ if (pauseState === 'PAUSED') {
49285
+ return false;
49286
+ }
49287
+ return AUTO_REFRESH_PHASES.includes(phase);
49288
+ }
49289
+ /**
49290
+ * Returns the automatic refresh interval for the current UI state.
49291
+ *
49292
+ * Waiting, paused, and completed states return `undefined` so the rich UI stays
49293
+ * perfectly still until actual state changes arrive.
49294
+ *
49295
+ * @private internal utility of coder run UI
49296
+ */
49297
+ function getCoderRunUiAutoRefreshInterval(phase, pauseState) {
49298
+ return isCoderRunUiAutoRefreshing(phase, pauseState) ? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS : undefined;
49299
+ }
49300
+
48742
49301
  /**
48743
49302
  * Maximum number of output lines reserved for agent output in the UI.
48744
49303
  */
48745
49304
  const MAX_VISIBLE_OUTPUT_LINES = 8;
49305
+ /**
49306
+ * Minimum width used for the rich coder-run frame.
49307
+ */
49308
+ const MIN_FRAME_WIDTH = 56;
49309
+ /**
49310
+ * Maximum width used for the rich coder-run frame.
49311
+ */
49312
+ const MAX_FRAME_WIDTH = 96;
49313
+ /**
49314
+ * Visible width reserved for aligned labels in the session box.
49315
+ */
49316
+ const SESSION_LABEL_WIDTH = 8;
48746
49317
  /**
48747
49318
  * Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
48748
49319
  */
48749
49320
  function buildCoderRunUiFrame(options) {
48750
- const totalWidth = Math.max(56, Math.min(options.terminalWidth, 96));
49321
+ const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
48751
49322
  const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
48752
49323
  const promptStatusPrefix = isPromptActive ? `${colors.yellow(`${options.spinner} `)}` : '';
48753
- const sessionScopeLine = options.progress.sessionTotal > 0
48754
- ? `Working on ${options.progress.currentPromptIndex}/${options.progress.sessionTotal} prompts with Priority ≥${options.config.priority}`
48755
- : `No runnable prompts with Priority ≥${options.config.priority}`;
48756
- const sessionCountLine = `Done ${options.progress.sessionDone}/${options.progress.sessionTotal} this run · Repo total ${options.progress.totalPrompts}`;
48757
- const sessionQueueParts = [];
48758
- if (options.progress.skippedPrompts > 0) {
48759
- sessionQueueParts.push(`Skipping ${formatPromptCount(options.progress.skippedPrompts)} with Priority <${options.config.priority}`);
48760
- }
48761
- if (options.progress.toBeWrittenPrompts > 0) {
48762
- sessionQueueParts.push(`Write first ${formatPromptCount(options.progress.toBeWrittenPrompts)}`);
48763
- }
48764
- const sessionLines = [
48765
- `${buildPhaseBadge(options.phase, options.pauseState)} ${fitPlainText(options.statusMessage, totalWidth - 18)}`,
48766
- sessionScopeLine,
48767
- sessionCountLine,
48768
- ...(sessionQueueParts.length > 0 ? [sessionQueueParts.join(' · ')] : []),
48769
- `Elapsed ${options.progress.elapsedText} · Est. total ${options.progress.estimatedTotalText} · Est. done ${options.progress.estimatedLabel}`,
48770
- buildProgressBar(options.progress.percentage, totalWidth - 6, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
48771
- ];
48772
- const metadataParts = [options.config.agentName || 'No agent selected'];
48773
- if (options.config.modelName) {
48774
- metadataParts.push(options.config.modelName);
48775
- }
48776
- if (options.config.thinkingLevel) {
48777
- metadataParts.push(`thinking ${options.config.thinkingLevel}`);
48778
- }
48779
- const runnerDetails = [
48780
- [`${colors.bgCyan.black(' PTBK ')}`, colors.bgBlue.white(' CODER '), colors.bold.white(' Promptbook Coder')]
48781
- .join(''),
48782
- metadataParts.join(' · '),
48783
- buildConfigSummaryLine(options.config),
48784
- ];
49324
+ const octopusAnimationFrame = isCoderRunUiAutoRefreshing(options.phase, options.pauseState)
49325
+ ? options.animationFrame
49326
+ : 0;
49327
+ const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.statusMessage);
49328
+ const sessionLines = buildSessionLines(options, totalWidth, pausePresentation);
48785
49329
  const currentTaskLines = options.currentPromptLabel
48786
49330
  ? [
48787
49331
  `${promptStatusPrefix}${colors.bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
@@ -48790,9 +49334,10 @@ function buildCoderRunUiFrame(options) {
48790
49334
  ]
48791
49335
  : [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
48792
49336
  const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
48793
- const controls = buildControlPills(options.pauseState, options.pendingEnterLabel).join(' ');
49337
+ const controls = buildControlPills(pausePresentation.pauseControl, options.pendingEnterLabel).join(' ');
48794
49338
  const frame = [
48795
- ...renderBox('Brand', runnerDetails, totalWidth, colors.cyan.bold),
49339
+ ...buildCoderRunOctopusVisual({ totalWidth, animationFrame: octopusAnimationFrame }),
49340
+ '',
48796
49341
  ...renderBox('Session', sessionLines, totalWidth, colors.yellow.bold),
48797
49342
  ...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors.magenta.bold),
48798
49343
  ...renderBox('Live output', visibleOutputLines, totalWidth, colors.green.bold),
@@ -48803,13 +49348,67 @@ function buildCoderRunUiFrame(options) {
48803
49348
  frame.push(...renderBox('Controls', [controls], totalWidth, colors.white.bold));
48804
49349
  return frame;
48805
49350
  }
49351
+ /**
49352
+ * Builds the structured session lines that combine state, runner, queue, and timing metadata.
49353
+ */
49354
+ function buildSessionLines(options, totalWidth, pausePresentation) {
49355
+ const bodyWidth = Math.max(10, totalWidth - 4);
49356
+ return buildSessionRows(options, bodyWidth, pausePresentation).map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
49357
+ }
49358
+ /**
49359
+ * Builds the session rows so the renderer can keep one consistent structure without duplicating labels.
49360
+ */
49361
+ function buildSessionRows(options, bodyWidth, pausePresentation) {
49362
+ const runnerParts = [options.config.agentName || 'No agent selected'];
49363
+ if (options.config.modelName) {
49364
+ runnerParts.push(options.config.modelName);
49365
+ }
49366
+ if (options.config.thinkingLevel) {
49367
+ runnerParts.push(`thinking ${options.config.thinkingLevel}`);
49368
+ }
49369
+ const configurationRows = [
49370
+ ...buildOptionalSessionRow('Context', options.config.context),
49371
+ ...buildOptionalSessionRow('Test', options.config.testCommand),
49372
+ ];
49373
+ return [
49374
+ {
49375
+ label: 'State',
49376
+ value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
49377
+ },
49378
+ {
49379
+ label: 'Runner',
49380
+ value: runnerParts.join(' · '),
49381
+ },
49382
+ ...configurationRows,
49383
+ {
49384
+ label: 'This run',
49385
+ value: buildThisRunSummary(options.progress),
49386
+ },
49387
+ {
49388
+ label: 'Backlog',
49389
+ value: buildBacklogSummary(options.progress),
49390
+ },
49391
+ {
49392
+ label: 'Scope',
49393
+ value: buildScopeSummary(options.progress, options.config),
49394
+ },
49395
+ {
49396
+ label: 'Timing',
49397
+ value: buildTimingSummary(options.progress),
49398
+ },
49399
+ {
49400
+ label: 'Progress',
49401
+ value: buildProgressBar(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
49402
+ },
49403
+ ];
49404
+ }
48806
49405
  /**
48807
49406
  * Builds the fixed-height live output section so streaming updates do not keep resizing the frame.
48808
49407
  */
48809
49408
  function buildVisibleOutputLines(agentOutputLines) {
48810
49409
  const visibleOutputLines = agentOutputLines.length > 0
48811
49410
  ? agentOutputLines.slice(-MAX_VISIBLE_OUTPUT_LINES).map((line) => `› ${stripAnsi(line)}`)
48812
- : ['No live agent output yet.'];
49411
+ : [colors.gray('No live agent output yet.')];
48813
49412
  while (visibleOutputLines.length < MAX_VISIBLE_OUTPUT_LINES) {
48814
49413
  visibleOutputLines.push('');
48815
49414
  }
@@ -48832,25 +49431,84 @@ function renderBox(title, lines, totalWidth, colorizeTitle) {
48832
49431
  return [topBorder, ...body, bottomBorder];
48833
49432
  }
48834
49433
  /**
48835
- * Builds the compact config summary line shown in the branding box.
49434
+ * Builds one aligned labeled line inside the session box.
48836
49435
  */
48837
- function buildConfigSummaryLine(config) {
48838
- const parts = [`Priority ≥${config.priority}`];
48839
- if (config.context) {
48840
- parts.unshift(`Context ${config.context}`);
49436
+ function buildLabeledSessionLine(label, value, bodyWidth) {
49437
+ const formattedLabel = colors.gray(label.padEnd(SESSION_LABEL_WIDTH));
49438
+ return `${formattedLabel} ${fitAnsiText(value, bodyWidth - SESSION_LABEL_WIDTH - 1)}`;
49439
+ }
49440
+ /**
49441
+ * Builds zero or one structured session row for optional metadata.
49442
+ */
49443
+ function buildOptionalSessionRow(label, value) {
49444
+ if (!value) {
49445
+ return [];
48841
49446
  }
48842
- if (config.testCommand) {
48843
- parts.push(`Test ${config.testCommand}`);
49447
+ return [{ label, value }];
49448
+ }
49449
+ /**
49450
+ * Builds the active-session summary shown in the session box.
49451
+ */
49452
+ function buildThisRunSummary(progress) {
49453
+ if (progress.sessionTotal === 0) {
49454
+ return 'No runnable prompts in current scope';
49455
+ }
49456
+ return `Task ${progress.currentPromptIndex}/${progress.sessionTotal} · ${progress.sessionDone} done · ${progress.sessionRemaining} left`;
49457
+ }
49458
+ /**
49459
+ * Builds the backlog/filter summary shown in the session box.
49460
+ */
49461
+ function buildBacklogSummary(progress) {
49462
+ const parts = [`Repo ${progress.totalPrompts} total`];
49463
+ if (progress.skippedPrompts > 0) {
49464
+ parts.push(`${formatPromptCount(progress.skippedPrompts)} below priority`);
49465
+ }
49466
+ return parts.join(' · ');
49467
+ }
49468
+ /**
49469
+ * Builds the priority/write-order summary shown in the session box.
49470
+ */
49471
+ function buildScopeSummary(progress, config) {
49472
+ const parts = [`Priority ≥${config.priority}`];
49473
+ if (progress.toBeWrittenPrompts > 0) {
49474
+ parts.push(`Write ${formatPromptCount(progress.toBeWrittenPrompts)} first`);
48844
49475
  }
48845
49476
  return parts.join(' · ');
48846
49477
  }
49478
+ /**
49479
+ * Builds the elapsed/estimate summary shown in the session box.
49480
+ */
49481
+ function buildTimingSummary(progress) {
49482
+ return `Elapsed ${progress.elapsedText} · Total ${progress.estimatedTotalText} · ETA ${progress.estimatedLabel}`;
49483
+ }
48847
49484
  /**
48848
49485
  * Builds the colored phase badge shown in the session box.
48849
49486
  */
48850
- function buildPhaseBadge(phase, pauseState) {
48851
- if (pauseState !== 'RUNNING' || phase === 'paused') {
48852
- return colors.bgYellow.black(' PAUSED ');
49487
+ function buildPausePresentation(phase, pauseState, statusMessage) {
49488
+ if (pauseState === 'PAUSING') {
49489
+ return {
49490
+ badge: colors.bgYellow.black(' PAUSING '),
49491
+ stateMessage: 'Pausing before the next task',
49492
+ pauseControl: colors.bgMagenta.white(' P ') + colors.white(' Cancel pause'),
49493
+ };
49494
+ }
49495
+ if (pauseState === 'PAUSED') {
49496
+ return {
49497
+ badge: colors.bgWhite.black(' PAUSED '),
49498
+ stateMessage: 'Paused until resumed',
49499
+ pauseControl: colors.bgGreen.black(' P ') + colors.white(' Resume'),
49500
+ };
48853
49501
  }
49502
+ return {
49503
+ badge: buildRunningPhaseBadge(phase),
49504
+ stateMessage: statusMessage,
49505
+ pauseControl: colors.bgYellow.black(' P ') + colors.white(' Pause'),
49506
+ };
49507
+ }
49508
+ /**
49509
+ * Builds the active phase badge shown in the session box while the runner is not paused.
49510
+ */
49511
+ function buildRunningPhaseBadge(phase) {
48854
49512
  switch (phase) {
48855
49513
  case 'loading':
48856
49514
  case 'initializing':
@@ -48865,6 +49523,8 @@ function buildPhaseBadge(phase, pauseState) {
48865
49523
  return colors.bgGreen.black(' DONE ');
48866
49524
  case 'error':
48867
49525
  return colors.bgRed.white(' ERROR ');
49526
+ case 'paused':
49527
+ return colors.bgWhite.black(' READY ');
48868
49528
  default:
48869
49529
  return colors.bgWhite.black(' READY ');
48870
49530
  }
@@ -48877,7 +49537,7 @@ function buildProgressBar(percentage, availableWidth, label) {
48877
49537
  const barWidth = Math.max(10, availableWidth - percentageLabel.length - 1);
48878
49538
  const filledWidth = Math.round((percentage / 100) * barWidth);
48879
49539
  const emptyWidth = Math.max(0, barWidth - filledWidth);
48880
- return `${colors.green('█'.repeat(filledWidth))}${colors.gray('░'.repeat(emptyWidth))} ${percentageLabel}`;
49540
+ return `${colors.green('█'.repeat(filledWidth))}${colors.blue('░'.repeat(emptyWidth))} ${percentageLabel}`;
48881
49541
  }
48882
49542
  /**
48883
49543
  * Formats a prompt count with singular/plural wording.
@@ -48888,58 +49548,15 @@ function formatPromptCount(count) {
48888
49548
  /**
48889
49549
  * Builds the control pills shown in the footer box.
48890
49550
  */
48891
- function buildControlPills(pauseState, pendingEnterLabel) {
49551
+ function buildControlPills(pauseControl, pendingEnterLabel) {
48892
49552
  const pills = [];
48893
49553
  if (pendingEnterLabel) {
48894
49554
  pills.push(colors.bgWhite.black(' ENTER ') + colors.white(` ${pendingEnterLabel}`));
48895
49555
  }
48896
- pills.push(pauseState === 'RUNNING'
48897
- ? colors.bgYellow.black(' P ') + colors.white(' Pause')
48898
- : colors.bgYellow.black(' P ') + colors.white(' Resume'));
49556
+ pills.push(pauseControl);
48899
49557
  pills.push(colors.bgRed.white(' CTRL+C ') + colors.white(' Exit'));
48900
49558
  return pills;
48901
49559
  }
48902
- /**
48903
- * Pads or truncates a possibly ANSI-colored line to the target visible width.
48904
- */
48905
- function padAnsiText(text, width) {
48906
- const fittedText = fitAnsiText(text, width);
48907
- return fittedText + ' '.repeat(Math.max(0, width - visibleLength(fittedText)));
48908
- }
48909
- /**
48910
- * Truncates a possibly ANSI-colored line to the target visible width.
48911
- */
48912
- function fitAnsiText(text, width) {
48913
- if (visibleLength(text) <= width) {
48914
- return text;
48915
- }
48916
- return fitPlainText(stripAnsi(text), width);
48917
- }
48918
- /**
48919
- * Truncates a plain-text line to the target width with an ellipsis.
48920
- */
48921
- function fitPlainText(text, width) {
48922
- if (text.length <= width) {
48923
- return text;
48924
- }
48925
- if (width <= 3) {
48926
- return '.'.repeat(width);
48927
- }
48928
- return `${text.slice(0, width - 3)}...`;
48929
- }
48930
- /**
48931
- * Measures visible string width by stripping ANSI escape codes.
48932
- */
48933
- function visibleLength(text) {
48934
- return stripAnsi(text).length;
48935
- }
48936
- /**
48937
- * Strips ANSI escape codes from a string.
48938
- */
48939
- function stripAnsi(text) {
48940
- // eslint-disable-next-line no-control-regex
48941
- return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
48942
- }
48943
49560
 
48944
49561
  /**
48945
49562
  * Maximum number of agent output lines kept in the scrolling output area.
@@ -48968,7 +49585,9 @@ class CoderRunUiState extends EventEmitter {
48968
49585
  this.statusMessage = 'Initializing...';
48969
49586
  this.errors = [];
48970
49587
  this.stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
48971
- this.timer = new CoderRunTimer(startTime, true);
49588
+ // Match the plain CLI progress header and count active run time from startup.
49589
+ // Explicit waits/pauses are excluded later through `pauseTimer()` / `resumeTimer()`.
49590
+ this.timer = new CoderRunTimer(startTime);
48972
49591
  }
48973
49592
  /**
48974
49593
  * Pauses the elapsed timer (e.g. while waiting for user input or paused state).
@@ -49080,34 +49699,6 @@ class CoderRunUiState extends EventEmitter {
49080
49699
  }
49081
49700
  }
49082
49701
 
49083
- /**
49084
- * Refresh cadence used only while the rich coder UI needs animated updates.
49085
- *
49086
- * @private internal constant of coder run UI
49087
- */
49088
- const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = 1000;
49089
- /**
49090
- * Phases that still benefit from automatic refreshes because the frame can change
49091
- * over time even without new runner output.
49092
- *
49093
- * @private internal constant of coder run UI
49094
- */
49095
- const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
49096
- /**
49097
- * Returns the automatic refresh interval for the current UI state.
49098
- *
49099
- * Waiting, paused, and completed states return `undefined` so the rich UI stays
49100
- * perfectly still until actual state changes arrive.
49101
- *
49102
- * @private internal utility of coder run UI
49103
- */
49104
- function getCoderRunUiAutoRefreshInterval(phase, pauseState) {
49105
- if (pauseState !== 'RUNNING') {
49106
- return undefined;
49107
- }
49108
- return AUTO_REFRESH_PHASES.includes(phase) ? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS : undefined;
49109
- }
49110
-
49111
49702
  /**
49112
49703
  * Spinner animation frames.
49113
49704
  *
@@ -49286,6 +49877,7 @@ function renderCoderRunUi(startTime) {
49286
49877
  function buildFrameLines() {
49287
49878
  return buildCoderRunUiFrame({
49288
49879
  terminalWidth: getTerminalWidth(),
49880
+ animationFrame: spinnerFrame,
49289
49881
  spinner: SPINNER_FRAMES[spinnerFrame],
49290
49882
  pauseState: getPauseState(),
49291
49883
  config: state.config,
@@ -49333,12 +49925,7 @@ function renderCoderRunUi(startTime) {
49333
49925
  process.exit(0);
49334
49926
  }
49335
49927
  if (key.name === 'p') {
49336
- if (getPauseState() === 'RUNNING') {
49337
- requestPause();
49338
- }
49339
- else {
49340
- requestResume();
49341
- }
49928
+ togglePauseState();
49342
49929
  scheduleRender();
49343
49930
  return;
49344
49931
  }
@@ -49474,6 +50061,23 @@ async function runCodexPrompts(providedOptions) {
49474
50061
  const isRichUiEnabled = !options.dryRun && !options.noUi && Boolean(process.stdout.isTTY);
49475
50062
  const progressDisplay = options.dryRun || options.noUi || isRichUiEnabled ? undefined : new CliProgressDisplay(runStartDate, options.priority);
49476
50063
  const uiHandle = isRichUiEnabled ? renderCoderRunUi(runStartDate) : undefined;
50064
+ const waitForRequestedPause = async () => {
50065
+ await checkPause({
50066
+ silent: isRichUiEnabled,
50067
+ onPaused: () => {
50068
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
50069
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
50070
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('paused');
50071
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Paused');
50072
+ },
50073
+ onResumed: () => {
50074
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
50075
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
50076
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
50077
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Resuming...');
50078
+ },
50079
+ });
50080
+ };
49477
50081
  // When the Ink UI is active it handles keyboard input itself, so skip the raw stdin listener.
49478
50082
  if (!isRichUiEnabled) {
49479
50083
  listenForPause();
@@ -49590,19 +50194,7 @@ async function runCodexPrompts(providedOptions) {
49590
50194
  let hasShownUpcomingTasks = false;
49591
50195
  let hasWaitedForStart = false;
49592
50196
  while (just(true)) {
49593
- await checkPause({
49594
- silent: isRichUiEnabled,
49595
- onPaused: () => {
49596
- progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49597
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49598
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('paused');
49599
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Paused');
49600
- },
49601
- onResumed: () => {
49602
- progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49603
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49604
- },
49605
- });
50197
+ await waitForRequestedPause();
49606
50198
  if (isRichUiEnabled) {
49607
50199
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
49608
50200
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Loading prompts...');
@@ -49670,6 +50262,7 @@ async function runCodexPrompts(providedOptions) {
49670
50262
  const commitMessage = buildCommitMessage(nextPrompt.file, nextPrompt.section);
49671
50263
  const codexPrompt = appendCoderContext(buildCodexPrompt(nextPrompt.file, nextPrompt.section), resolvedCoderContext);
49672
50264
  const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
50265
+ await waitForRequestedPause();
49673
50266
  if (isRichUiEnabled) {
49674
50267
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
49675
50268
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
@@ -51380,6 +51973,14 @@ function promptbookifyAiText(text) {
51380
51973
  * Constant for default agent kit model name.
51381
51974
  */
51382
51975
  const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.4-mini';
51976
+ /**
51977
+ * Default model used for nested DeepSearch tool invocations.
51978
+ */
51979
+ const DEFAULT_DEEP_SEARCH_MODEL_NAME = 'o4-mini-deep-research';
51980
+ /**
51981
+ * Tool name used by the Book commitment-backed DeepSearch capability.
51982
+ */
51983
+ const DEEP_SEARCH_TOOL_NAME = 'deep_search';
51383
51984
  /**
51384
51985
  * Creates one structured log entry for streamed tool-call updates.
51385
51986
  *
@@ -51422,6 +52023,98 @@ function resolveFinalToolCallState(options) {
51422
52023
  }
51423
52024
  return 'COMPLETE';
51424
52025
  }
52026
+ /**
52027
+ * Returns true when one tool definition represents the dedicated DeepSearch capability.
52028
+ *
52029
+ * @param toolDefinition - Tool definition from compiled model requirements.
52030
+ * @returns `true` when the tool should be backed by a nested deep-research agent.
52031
+ *
52032
+ * @private helper of `OpenAiAgentKitExecutionTools`
52033
+ */
52034
+ function isDeepSearchToolDefinition(toolDefinition) {
52035
+ return toolDefinition.name === DEEP_SEARCH_TOOL_NAME;
52036
+ }
52037
+ /**
52038
+ * Normalizes Promptbook JSON-schema tool parameters for AgentKit function tools.
52039
+ *
52040
+ * @param parameters - Promptbook tool parameters.
52041
+ * @returns AgentKit-compatible JSON schema or `undefined`.
52042
+ *
52043
+ * @private helper of `OpenAiAgentKitExecutionTools`
52044
+ */
52045
+ function normalizeAgentKitToolParameters(parameters) {
52046
+ var _a, _b;
52047
+ if (!parameters) {
52048
+ return undefined;
52049
+ }
52050
+ return {
52051
+ ...parameters,
52052
+ additionalProperties: (_a = parameters.additionalProperties) !== null && _a !== void 0 ? _a : false,
52053
+ required: (_b = parameters.required) !== null && _b !== void 0 ? _b : [],
52054
+ };
52055
+ }
52056
+ /**
52057
+ * Creates instructions for the nested DeepSearch specialist agent.
52058
+ *
52059
+ * @param toolDescription - Model-facing description from the original tool definition.
52060
+ * @returns System instructions for the nested deep-research agent.
52061
+ *
52062
+ * @private helper of `OpenAiAgentKitExecutionTools`
52063
+ */
52064
+ function createDeepSearchAgentInstructions(toolDescription) {
52065
+ const normalizedDescription = toolDescription.trim();
52066
+ return spaceTrim$1((block) => `
52067
+ You are a DeepSearch specialist working as a tool for another agent.
52068
+ Perform thorough, source-grounded public-web research based on the provided request.
52069
+ Use web search to gather current information, compare relevant viewpoints, and synthesize a concise research brief.
52070
+ Do not ask follow-up questions. If the request is not specific enough, state the assumptions you had to make.
52071
+ Include citations in the research brief whenever sources were used.
52072
+ ${block(normalizedDescription ? `Tool guidance:\n${normalizedDescription}` : '')}
52073
+ `);
52074
+ }
52075
+ /**
52076
+ * Builds the nested DeepSearch prompt from structured tool arguments.
52077
+ *
52078
+ * @param rawInput - Parsed function-tool arguments provided by the outer agent.
52079
+ * @returns Prompt text passed to the nested deep-research agent.
52080
+ *
52081
+ * @private helper of `OpenAiAgentKitExecutionTools`
52082
+ */
52083
+ function buildDeepSearchToolInput(rawInput) {
52084
+ const input = rawInput && typeof rawInput === 'object' ? rawInput : {};
52085
+ const query = typeof input.query === 'string' ? input.query.trim() : '';
52086
+ const additionalHints = Object.entries(input)
52087
+ .filter(([key, value]) => key !== 'query' && value !== undefined && value !== null && String(value).trim() !== '')
52088
+ .map(([key, value]) => `- ${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`);
52089
+ return spaceTrim$1((block) => `
52090
+ Research request:
52091
+ ${query || JSON.stringify(input)}
52092
+ ${block(additionalHints.length > 0 ? `Execution hints:\n${additionalHints.join('\n')}` : '')}
52093
+ `);
52094
+ }
52095
+ /**
52096
+ * Creates the native Agent SDK tool used for `USE DEEPSEARCH`.
52097
+ *
52098
+ * @param toolDefinition - Promptbook tool definition for `deep_search`.
52099
+ * @returns AgentKit tool backed by a nested deep-research agent.
52100
+ *
52101
+ * @private helper of `OpenAiAgentKitExecutionTools`
52102
+ */
52103
+ function createDeepSearchAgentKitTool(toolDefinition) {
52104
+ const deepSearchAgent = new Agent$1({
52105
+ name: 'DeepSearch',
52106
+ model: DEFAULT_DEEP_SEARCH_MODEL_NAME,
52107
+ instructions: createDeepSearchAgentInstructions(toolDefinition.description),
52108
+ tools: [webSearchTool({ searchContextSize: 'high' })],
52109
+ });
52110
+ return deepSearchAgent.asTool({
52111
+ toolName: toolDefinition.name,
52112
+ toolDescription: toolDefinition.description,
52113
+ parameters: normalizeAgentKitToolParameters(toolDefinition.parameters),
52114
+ inputBuilder: ({ params }) => buildDeepSearchToolInput(params),
52115
+ customOutputExtractor: (result) => { var _a; return typeof result.finalOutput === 'string' ? result.finalOutput : JSON.stringify((_a = result.finalOutput) !== null && _a !== void 0 ? _a : ''); },
52116
+ });
52117
+ }
51425
52118
  /**
51426
52119
  * Constant for default JSON schema name.
51427
52120
  */
@@ -51762,25 +52455,23 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
51762
52455
  * Builds the tool list for AgentKit, including hosted file search when applicable.
51763
52456
  */
51764
52457
  buildAgentKitTools(options) {
51765
- var _a;
51766
52458
  const { tools, vectorStoreId } = options;
51767
52459
  const agentKitTools = [];
51768
52460
  if (vectorStoreId) {
51769
52461
  agentKitTools.push(fileSearchTool(vectorStoreId));
51770
52462
  }
51771
52463
  if (tools && tools.length > 0) {
51772
- const scriptTools = this.resolveScriptTools();
52464
+ let scriptTools = null;
51773
52465
  for (const toolDefinition of tools) {
52466
+ if (isDeepSearchToolDefinition(toolDefinition)) {
52467
+ agentKitTools.push(createDeepSearchAgentKitTool(toolDefinition));
52468
+ continue;
52469
+ }
52470
+ scriptTools !== null && scriptTools !== void 0 ? scriptTools : (scriptTools = this.resolveScriptTools());
51774
52471
  agentKitTools.push(tool({
51775
52472
  name: toolDefinition.name,
51776
52473
  description: toolDefinition.description,
51777
- parameters: toolDefinition.parameters
51778
- ? {
51779
- ...toolDefinition.parameters,
51780
- additionalProperties: false,
51781
- required: (_a = toolDefinition.parameters.required) !== null && _a !== void 0 ? _a : [],
51782
- }
51783
- : undefined,
52474
+ parameters: normalizeAgentKitToolParameters(toolDefinition.parameters),
51784
52475
  strict: false,
51785
52476
  execute: async (input, runContext, details) => {
51786
52477
  var _a, _b, _c, _d;
@@ -53734,9 +54425,10 @@ function formatMetaLine(label, value) {
53734
54425
  * Build a minimal agent source snapshot for remote agents.
53735
54426
  */
53736
54427
  function buildRemoteAgentSource(profile, meta) {
54428
+ const isMetaImageExplicit = profile.isMetaImageExplicit !== false;
53737
54429
  const metaLines = [
53738
54430
  formatMetaLine('FULLNAME', meta === null || meta === void 0 ? void 0 : meta.fullname),
53739
- formatMetaLine('IMAGE', meta === null || meta === void 0 ? void 0 : meta.image),
54431
+ formatMetaLine('IMAGE', isMetaImageExplicit ? meta === null || meta === void 0 ? void 0 : meta.image : undefined),
53740
54432
  formatMetaLine('DESCRIPTION', meta === null || meta === void 0 ? void 0 : meta.description),
53741
54433
  formatMetaLine('COLOR', meta === null || meta === void 0 ? void 0 : meta.color),
53742
54434
  formatMetaLine('FONT', meta === null || meta === void 0 ? void 0 : meta.font),
@@ -53835,6 +54527,8 @@ class RemoteAgent extends Agent {
53835
54527
  remoteAgent._isVoiceCallingEnabled = profile.isVoiceCallingEnabled === true; // [✨✷] Store voice calling status
53836
54528
  remoteAgent._isVoiceTtsSttEnabled = profile.isVoiceTtsSttEnabled !== false;
53837
54529
  remoteAgent.knowledgeSources = profile.knowledgeSources || [];
54530
+ remoteAgent.isMetaImageExplicit = profile.isMetaImageExplicit !== false;
54531
+ remoteAgent.avatarVisualId = profile.avatarVisualId;
53838
54532
  return remoteAgent;
53839
54533
  }
53840
54534
  /**
@@ -53850,6 +54544,8 @@ class RemoteAgent extends Agent {
53850
54544
  this.toolTitles = {};
53851
54545
  this._isVoiceCallingEnabled = false; // [✨✷] Track voice calling status
53852
54546
  this._isVoiceTtsSttEnabled = true;
54547
+ this.isMetaImageExplicit = true;
54548
+ this.avatarVisualId = undefined;
53853
54549
  this.knowledgeSources = [];
53854
54550
  this.agentUrl = options.agentUrl;
53855
54551
  }