@promptbook/components 0.112.0-47 → 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 (43) hide show
  1. package/esm/index.es.js +885 -143
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/avatars/AvatarOrImage.d.ts +5 -1
  4. package/esm/src/avatars/avatarInteractionUtils.d.ts +81 -0
  5. package/esm/src/avatars/avatarInteractionUtils.test.d.ts +1 -0
  6. package/esm/src/avatars/avatarPointerTracking.d.ts +17 -0
  7. package/esm/src/avatars/avatarRenderingUtils.d.ts +3 -2
  8. package/esm/src/avatars/avatarRenderingUtils.test.d.ts +1 -0
  9. package/esm/src/avatars/index.d.ts +1 -1
  10. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +35 -0
  11. package/esm/src/avatars/visuals/octopusAvatarVisualShared.d.ts +34 -0
  12. package/esm/src/avatars/visuals/octopusAvatarVisualShared.test.d.ts +1 -0
  13. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.test.d.ts +2 -0
  14. package/esm/src/commitments/USE/USE.d.ts +1 -0
  15. package/esm/src/commitments/USE/aggregateUseCommitmentSystemMessages.d.ts +1 -1
  16. package/esm/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.d.ts +47 -0
  17. package/esm/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.test.d.ts +1 -0
  18. package/esm/src/commitments/_common/createSerpSearchToolFunction.d.ts +12 -0
  19. package/esm/src/commitments/index.d.ts +2 -1
  20. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.test.d.ts +1 -0
  21. package/esm/src/version.d.ts +1 -1
  22. package/package.json +1 -1
  23. package/umd/index.umd.js +884 -142
  24. package/umd/index.umd.js.map +1 -1
  25. package/umd/src/avatars/AvatarOrImage.d.ts +5 -1
  26. package/umd/src/avatars/avatarInteractionUtils.d.ts +81 -0
  27. package/umd/src/avatars/avatarInteractionUtils.test.d.ts +1 -0
  28. package/umd/src/avatars/avatarPointerTracking.d.ts +17 -0
  29. package/umd/src/avatars/avatarRenderingUtils.d.ts +3 -2
  30. package/umd/src/avatars/avatarRenderingUtils.test.d.ts +1 -0
  31. package/umd/src/avatars/index.d.ts +1 -1
  32. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +35 -0
  33. package/umd/src/avatars/visuals/octopusAvatarVisualShared.d.ts +34 -0
  34. package/umd/src/avatars/visuals/octopusAvatarVisualShared.test.d.ts +1 -0
  35. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.test.d.ts +2 -0
  36. package/umd/src/commitments/USE/USE.d.ts +1 -0
  37. package/umd/src/commitments/USE/aggregateUseCommitmentSystemMessages.d.ts +1 -1
  38. package/umd/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.d.ts +47 -0
  39. package/umd/src/commitments/USE_DEEPSEARCH/USE_DEEPSEARCH.test.d.ts +1 -0
  40. package/umd/src/commitments/_common/createSerpSearchToolFunction.d.ts +12 -0
  41. package/umd/src/commitments/index.d.ts +2 -1
  42. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.test.d.ts +1 -0
  43. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -21,7 +21,7 @@ import { forTime } from 'waitasecond';
21
21
  import sha256 from 'crypto-js/sha256';
22
22
  import { parse, unparse } from 'papaparse';
23
23
  import colors from 'colors';
24
- import { Agent as Agent$1, setDefaultOpenAIClient, setDefaultOpenAIKey, fileSearchTool, tool, run } from '@openai/agents';
24
+ import { Agent as Agent$1, setDefaultOpenAIClient, setDefaultOpenAIKey, fileSearchTool, tool, run, webSearchTool } from '@openai/agents';
25
25
  import Bottleneck from 'bottleneck';
26
26
  import OpenAI from 'openai';
27
27
  import QRCode from 'qrcode';
@@ -40,7 +40,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
40
40
  * @generated
41
41
  * @see https://github.com/webgptorg/promptbook
42
42
  */
43
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-47';
43
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-48';
44
44
  /**
45
45
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
46
46
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1402,11 +1402,12 @@ function createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation)
1402
1402
  * Creates the shared derived palette used by every avatar visual.
1403
1403
  *
1404
1404
  * @param avatarDefinition Stable avatar definition.
1405
+ * @param surface Surface style used by the parent UI.
1405
1406
  * @returns Derived palette.
1406
1407
  *
1407
1408
  * @private utility of the avatar rendering system
1408
1409
  */
1409
- function createAvatarPalette(avatarDefinition) {
1410
+ function createAvatarPalette(avatarDefinition, surface = 'framed') {
1410
1411
  const normalizedAvatarDefinition = normalizeAvatarDefinition(avatarDefinition);
1411
1412
  const primaryColor = Color.fromSafe(normalizedAvatarDefinition.colors[0] || PROMPTBOOK_COLOR);
1412
1413
  const secondaryColor = Color.fromSafe(normalizedAvatarDefinition.colors[1] || primaryColor.then(lighten(0.12)).then(saturate(0.16)));
@@ -1416,8 +1417,8 @@ function createAvatarPalette(avatarDefinition) {
1416
1417
  const highlightColor = Color.fromSafe(accentColor.then(lighten(0.22)).then(saturate(0.08)));
1417
1418
  const shadowColor = Color.fromSafe(primaryColor.then(darken(0.46)).then(saturate(0.14)));
1418
1419
  return {
1419
- background: backgroundColor.toHex(),
1420
- backgroundSecondary: backgroundSecondaryColor.toHex(),
1420
+ background: surface === 'transparent' ? 'transparent' : backgroundColor.toHex(),
1421
+ backgroundSecondary: surface === 'transparent' ? 'transparent' : backgroundSecondaryColor.toHex(),
1421
1422
  primary: primaryColor.toHex(),
1422
1423
  secondary: secondaryColor.toHex(),
1423
1424
  accent: accentColor.toHex(),
@@ -1436,6 +1437,9 @@ function createAvatarPalette(avatarDefinition) {
1436
1437
  * @private utility of the avatar rendering system
1437
1438
  */
1438
1439
  function drawAvatarFrame(context, size, palette) {
1440
+ if (palette.background === 'transparent' && palette.backgroundSecondary === 'transparent') {
1441
+ return;
1442
+ }
1439
1443
  const gradient = context.createLinearGradient(0, 0, size, size);
1440
1444
  gradient.addColorStop(0, palette.background);
1441
1445
  gradient.addColorStop(1, palette.backgroundSecondary);
@@ -1587,7 +1591,7 @@ function generatePlaceholderAgentProfileImageUrl(agentIdOrName, agentsServerUrl)
1587
1591
  *
1588
1592
  * @private shared avatar contract
1589
1593
  */
1590
- const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus2';
1594
+ const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus3';
1591
1595
  /**
1592
1596
  * Resolve a base URL for relative images, preferring the provided base or browser location.
1593
1597
  *
@@ -11732,6 +11736,7 @@ class TemplateCommitmentDefinition extends BaseCommitmentDefinition {
11732
11736
  * Supported USE types:
11733
11737
  * - USE BROWSER: Enables the agent to use a web browser tool
11734
11738
  * - USE SEARCH ENGINE (future): Enables search engine access
11739
+ * - USE DEEPSEARCH: Enables deeper research-oriented search access
11735
11740
  * - USE FILE SYSTEM (future): Enables file system operations
11736
11741
  * - USE MCP (future): Enables MCP server connections
11737
11742
  *
@@ -11754,7 +11759,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
11754
11759
  * Short one-line description of USE commitments.
11755
11760
  */
11756
11761
  get description() {
11757
- return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, etc.).';
11762
+ return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, DEEPSEARCH, etc.).';
11758
11763
  }
11759
11764
  /**
11760
11765
  * Icon for this commitment.
@@ -11775,6 +11780,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
11775
11780
 
11776
11781
  - **USE BROWSER** - Enables the agent to use a web browser tool to access and retrieve information from the internet
11777
11782
  - **USE SEARCH ENGINE** (future) - Enables search engine access
11783
+ - **USE DEEPSEARCH** - Enables deeper research-oriented search access
11778
11784
  - **USE FILE SYSTEM** (future) - Enables file system operations
11779
11785
  - **USE MCP** (future) - Enables MCP server connections
11780
11786
 
@@ -11829,7 +11835,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
11829
11835
  * Checks if this is a known USE type
11830
11836
  */
11831
11837
  isKnownUseType(useType) {
11832
- const knownTypes = ['BROWSER', 'SEARCH ENGINE', 'FILE SYSTEM', 'MCP'];
11838
+ const knownTypes = ['BROWSER', 'SEARCH ENGINE', 'DEEPSEARCH', 'FILE SYSTEM', 'MCP'];
11833
11839
  return knownTypes.includes(useType.toUpperCase());
11834
11840
  }
11835
11841
  }
@@ -11842,6 +11848,7 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
11842
11848
  */
11843
11849
  const AGGREGATED_USE_COMMITMENT_TYPES = [
11844
11850
  'USE BROWSER',
11851
+ 'USE DEEPSEARCH',
11845
11852
  'USE SEARCH ENGINE',
11846
11853
  'USE TIME',
11847
11854
  ];
@@ -11921,6 +11928,15 @@ function createAggregatedUseCommitmentSystemMessage(type, additionalInstructions
11921
11928
  - Do not tell the user you cannot search for information, YOU CAN.
11922
11929
  ${block(formatOptionalInstructionBlock('Search instructions', combinedAdditionalInstructions))}
11923
11930
  `);
11931
+ case 'USE DEEPSEARCH':
11932
+ return spaceTrim$1((block) => `
11933
+ Tool:
11934
+ - You have access to DeepSearch via the tool "deep_search".
11935
+ - Use it for broader research tasks that need multi-step investigation, comparison, or synthesis across multiple sources.
11936
+ - Prefer it over quick search when the user asks for a well-grounded brief, report, or deeper investigation.
11937
+ - Do not pretend you cannot research current information when this tool is available.
11938
+ ${block(formatOptionalInstructionBlock('DeepSearch instructions', combinedAdditionalInstructions))}
11939
+ `);
11924
11940
  }
11925
11941
  }
11926
11942
  /**
@@ -13470,6 +13486,207 @@ function addConfiguredCalendarIfMissing(configuredCalendars, calendarReference)
13470
13486
  }
13471
13487
  // Note: [💞] Ignore a discrepancy between file name and entity name
13472
13488
 
13489
+ /**
13490
+ * A search engine implementation that uses the SerpApi to fetch Google search results.
13491
+ *
13492
+ * @private <- TODO: !!!! Export via some package
13493
+ */
13494
+ class SerpSearchEngine {
13495
+ get title() {
13496
+ return 'SerpApi Search Engine';
13497
+ }
13498
+ get description() {
13499
+ return 'Search engine that uses SerpApi to fetch Google search results';
13500
+ }
13501
+ checkConfiguration() {
13502
+ if (!process.env.SERP_API_KEY) {
13503
+ throw new Error('SERP_API_KEY is not configured');
13504
+ }
13505
+ }
13506
+ async search(query, options = {}) {
13507
+ const apiKey = process.env.SERP_API_KEY;
13508
+ if (!apiKey) {
13509
+ throw new Error('SERP_API_KEY is not configured');
13510
+ }
13511
+ const url = new URL('https://serpapi.com/search');
13512
+ url.searchParams.set('api_key', apiKey);
13513
+ url.searchParams.set('engine', 'google');
13514
+ url.searchParams.set('q', query);
13515
+ for (const [key, value] of Object.entries(options)) {
13516
+ url.searchParams.set(key, String(value));
13517
+ }
13518
+ const response = await fetch(url.toString());
13519
+ if (!response.ok) {
13520
+ const body = await response.text();
13521
+ throw new Error(`SerpApi failed with status ${response.status}: ${response.statusText}\n${body}`);
13522
+ }
13523
+ const data = (await response.json());
13524
+ return (data.organic_results || []).map((item) => ({
13525
+ title: item.title,
13526
+ url: item.link,
13527
+ snippet: item.snippet || '',
13528
+ }));
13529
+ }
13530
+ }
13531
+
13532
+ /**
13533
+ * Creates one SERP-backed tool function used as a local fallback for search-like commitments.
13534
+ *
13535
+ * @param toolName - Technical tool name used for validation messages.
13536
+ * @param resultLabel - Human-readable label used in formatted results.
13537
+ * @returns Async tool function compatible with commitment tool registration.
13538
+ *
13539
+ * @private internal helper for search-like commitments
13540
+ */
13541
+ function createSerpSearchToolFunction(toolName, resultLabel) {
13542
+ return async (rawArgs) => {
13543
+ const { query, ...searchOptions } = rawArgs;
13544
+ if (typeof query !== 'string' || !query.trim()) {
13545
+ throw new Error(`${toolName} query is required`);
13546
+ }
13547
+ const searchEngine = new SerpSearchEngine();
13548
+ const results = await searchEngine.search(query, searchOptions);
13549
+ return spaceTrim$1((block) => `
13550
+ ${resultLabel} results for "${query}"${Object.keys(searchOptions).length === 0
13551
+ ? ''
13552
+ : ` with options ${JSON.stringify(searchOptions)}`}:
13553
+
13554
+ ${block(results
13555
+ .map((result) => spaceTrim$1(`
13556
+ - **${result.title}**
13557
+ ${result.url}
13558
+ ${result.snippet}
13559
+ `))
13560
+ .join('\n\n'))}
13561
+ `);
13562
+ };
13563
+ }
13564
+
13565
+ /**
13566
+ * USE DEEPSEARCH commitment definition
13567
+ *
13568
+ * The `USE DEEPSEARCH` commitment indicates that the agent should use a deeper research-oriented
13569
+ * search workflow instead of lightweight web search when it needs fresh information from the internet.
13570
+ *
13571
+ * The content following `USE DEEPSEARCH` is an arbitrary text that the agent should know
13572
+ * (e.g. search scope or research instructions).
13573
+ *
13574
+ * Example usage in agent source:
13575
+ *
13576
+ * ```book
13577
+ * USE DEEPSEARCH
13578
+ * USE DEEPSEARCH Compare official vendor documentation with independent benchmarks.
13579
+ * ```
13580
+ *
13581
+ * @private [🪔] Maybe export the commitments through some package
13582
+ */
13583
+ class UseDeepSearchCommitmentDefinition extends BaseCommitmentDefinition {
13584
+ constructor() {
13585
+ super('USE DEEPSEARCH');
13586
+ }
13587
+ get requiresContent() {
13588
+ return false;
13589
+ }
13590
+ /**
13591
+ * Short one-line description of USE DEEPSEARCH.
13592
+ */
13593
+ get description() {
13594
+ return 'Enable the agent to use DeepSearch for more thorough internet research.';
13595
+ }
13596
+ /**
13597
+ * Icon for this commitment.
13598
+ */
13599
+ get icon() {
13600
+ return '🔬';
13601
+ }
13602
+ /**
13603
+ * Markdown documentation for USE DEEPSEARCH commitment.
13604
+ */
13605
+ get documentation() {
13606
+ return spaceTrim$1(`
13607
+ # USE DEEPSEARCH
13608
+
13609
+ Enables the agent to use DeepSearch for broader, more thorough internet research than lightweight web search.
13610
+
13611
+ ## Key aspects
13612
+
13613
+ - The content following \`USE DEEPSEARCH\` is arbitrary guidance for the research workflow.
13614
+ - In Agents Server, the OpenAI Agents SDK runtime uses a nested deep-research agent for this tool.
13615
+ - Use this for investigations, comparisons, market scans, or other tasks that benefit from deeper synthesis.
13616
+ - Prefer regular \`USE SEARCH ENGINE\` when a quick factual lookup is enough.
13617
+
13618
+ ## Examples
13619
+
13620
+ \`\`\`book
13621
+ Due Diligence Researcher
13622
+
13623
+ GOAL Investigate vendors thoroughly before making recommendations.
13624
+ USE DEEPSEARCH Compare official sources with credible third-party analysis.
13625
+ RULE Cite the strongest supporting sources in the final answer.
13626
+ \`\`\`
13627
+
13628
+ \`\`\`book
13629
+ Market Analyst
13630
+
13631
+ GOAL Build concise but well-grounded research briefs.
13632
+ USE DEEPSEARCH Focus on recent public information and competing viewpoints.
13633
+ CLOSED
13634
+ \`\`\`
13635
+ `);
13636
+ }
13637
+ applyToAgentModelRequirements(requirements, content) {
13638
+ const existingTools = requirements.tools || [];
13639
+ const updatedTools = existingTools.some((tool) => tool.name === 'deep_search')
13640
+ ? existingTools
13641
+ : [
13642
+ ...existingTools,
13643
+ {
13644
+ name: 'deep_search',
13645
+ description: spaceTrim$1(`
13646
+ Research the internet deeply and synthesize a grounded answer.
13647
+ Use this tool for broader investigations, comparisons, and requests that need more than a quick search.
13648
+ `),
13649
+ parameters: {
13650
+ type: 'object',
13651
+ properties: {
13652
+ query: {
13653
+ type: 'string',
13654
+ description: 'The research question or investigation request.',
13655
+ },
13656
+ },
13657
+ required: ['query'],
13658
+ additionalProperties: false,
13659
+ },
13660
+ },
13661
+ ];
13662
+ return appendAggregatedUseCommitmentPlaceholder({
13663
+ ...requirements,
13664
+ tools: updatedTools,
13665
+ _metadata: {
13666
+ ...requirements._metadata,
13667
+ useDeepSearch: content || true,
13668
+ },
13669
+ }, this.type);
13670
+ }
13671
+ /**
13672
+ * Gets human-readable titles for tool functions provided by this commitment.
13673
+ */
13674
+ getToolTitles() {
13675
+ return {
13676
+ deep_search: 'DeepSearch',
13677
+ };
13678
+ }
13679
+ /**
13680
+ * Gets the local fallback implementation for the `deep_search` tool.
13681
+ */
13682
+ getToolFunctions() {
13683
+ return {
13684
+ deep_search: createSerpSearchToolFunction('deep_search', 'DeepSearch'),
13685
+ };
13686
+ }
13687
+ }
13688
+ // Note: [💞] Ignore a discrepancy between file name and entity name
13689
+
13473
13690
  /**
13474
13691
  * Lightweight email token matcher used for `USE EMAIL` first-line parsing.
13475
13692
  *
@@ -15701,49 +15918,6 @@ function addConfiguredProjectIfMissing(configuredProjects, repositoryReference)
15701
15918
  }
15702
15919
  // Note: [💞] Ignore a discrepancy between file name and entity name
15703
15920
 
15704
- /**
15705
- * A search engine implementation that uses the SerpApi to fetch Google search results.
15706
- *
15707
- * @private <- TODO: !!!! Export via some package
15708
- */
15709
- class SerpSearchEngine {
15710
- get title() {
15711
- return 'SerpApi Search Engine';
15712
- }
15713
- get description() {
15714
- return 'Search engine that uses SerpApi to fetch Google search results';
15715
- }
15716
- checkConfiguration() {
15717
- if (!process.env.SERP_API_KEY) {
15718
- throw new Error('SERP_API_KEY is not configured');
15719
- }
15720
- }
15721
- async search(query, options = {}) {
15722
- const apiKey = process.env.SERP_API_KEY;
15723
- if (!apiKey) {
15724
- throw new Error('SERP_API_KEY is not configured');
15725
- }
15726
- const url = new URL('https://serpapi.com/search');
15727
- url.searchParams.set('api_key', apiKey);
15728
- url.searchParams.set('engine', 'google');
15729
- url.searchParams.set('q', query);
15730
- for (const [key, value] of Object.entries(options)) {
15731
- url.searchParams.set(key, String(value));
15732
- }
15733
- const response = await fetch(url.toString());
15734
- if (!response.ok) {
15735
- const body = await response.text();
15736
- throw new Error(`SerpApi failed with status ${response.status}: ${response.statusText}\n${body}`);
15737
- }
15738
- const data = (await response.json());
15739
- return (data.organic_results || []).map((item) => ({
15740
- title: item.title,
15741
- url: item.link,
15742
- snippet: item.snippet || '',
15743
- }));
15744
- }
15745
- }
15746
-
15747
15921
  /**
15748
15922
  * USE SEARCH ENGINE commitment definition
15749
15923
  *
@@ -15887,26 +16061,7 @@ class UseSearchEngineCommitmentDefinition extends BaseCommitmentDefinition {
15887
16061
  */
15888
16062
  getToolFunctions() {
15889
16063
  return {
15890
- async web_search(args) {
15891
- console.log('!!!! [Tool] web_search called', { args });
15892
- const { query, ...options } = args;
15893
- if (!query) {
15894
- throw new Error('Search query is required');
15895
- }
15896
- const searchEngine = new SerpSearchEngine();
15897
- const results = await searchEngine.search(query, options);
15898
- return spaceTrim$1((block) => `
15899
- Search results for "${query}"${Object.keys(options).length === 0 ? '' : ` with options ${JSON.stringify(options)}`}:
15900
-
15901
- ${block(results
15902
- .map((result) => spaceTrim$1(`
15903
- - **${result.title}**
15904
- ${result.url}
15905
- ${result.snippet}
15906
- `))
15907
- .join('\n\n'))}
15908
- `);
15909
- },
16064
+ web_search: createSerpSearchToolFunction('web_search', 'Search'),
15910
16065
  };
15911
16066
  }
15912
16067
  }
@@ -17552,6 +17707,7 @@ const COMMITMENT_REGISTRY = [
17552
17707
  new ClosedCommitmentDefinition(),
17553
17708
  new TeamCommitmentDefinition(),
17554
17709
  new UseBrowserCommitmentDefinition(),
17710
+ new UseDeepSearchCommitmentDefinition(),
17555
17711
  new UseSearchEngineCommitmentDefinition(),
17556
17712
  new UseSpawnCommitmentDefinition(),
17557
17713
  new UseTimeoutCommitmentDefinition(),
@@ -17863,6 +18019,11 @@ const SIMPLE_CAPABILITY_BY_COMMITMENT_TYPE = {
17863
18019
  label: 'Internet',
17864
18020
  iconName: 'Search',
17865
18021
  },
18022
+ 'USE DEEPSEARCH': {
18023
+ type: 'search-engine',
18024
+ label: 'DeepSearch',
18025
+ iconName: 'Search',
18026
+ },
17866
18027
  'USE TIME': {
17867
18028
  type: 'time',
17868
18029
  label: 'Time',
@@ -25738,6 +25899,320 @@ function ChatInputArea(props) {
25738
25899
  }), children: jsx(SendIcon, { size: 25 }) })] }), speechRecognition && (jsx(ChatInputAreaDictationPanel, { bubbleText: speechRecognitionUiDescriptor.bubbleText, bubbleTone: speechRecognitionUiDescriptor.bubbleTone, shouldShowPanel: shouldShowDictationPanel, isExpanded: isDictationPanelExpanded, interimText: dictationInterimText, error: dictationError, lastFinalChunk: dictationLastFinalChunk, editableChunk: dictationEditableChunk, canBacktrack: canBacktrack, dictationSettings: dictationSettings, isBrowserSpeechFallbackSupported: isBrowserSpeechFallbackSupported, canOpenBrowserSettings: canOpenBrowserSettings, onToggleExpanded: toggleDictationPanel, onExpand: expandDictationPanel, onEditableChunkChange: setDictationEditableChunk, onRetryPermissionRequest: handleRetryPermissionRequest, onOpenBrowserSettings: handleOpenBrowserSettings, onApplyCorrection: handleApplyCorrection, onBacktrackLastChunk: handleBacktrackLastChunk, onDictationSettingChange: handleDictationSettingChange })), isUploading && (jsxs("div", { className: styles$5.uploadProgress, children: [jsx("div", { className: styles$5.uploadProgressBar, children: jsx("div", { className: styles$5.uploadProgressFill }) }), jsx("span", { children: "Uploading files..." })] })), isDragOver && onFileUpload && (jsx("div", { className: styles$5.dragOverlay, children: jsxs("div", { className: styles$5.dragOverlayContent, children: [jsx(AttachmentIcon, { size: 48 }), jsx("span", { children: "Drop files here to upload" })] }) }))] }));
25739
25900
  }
25740
25901
 
25902
+ // Note: [💞] Ignore a discrepancy between file name and entity name
25903
+ /**
25904
+ * Maximum normalized eye travel used when the viewer moves across the viewport.
25905
+ *
25906
+ * @private utility of the avatar rendering system
25907
+ */
25908
+ const MAX_GAZE_OFFSET = 0.78;
25909
+ /**
25910
+ * Maximum normalized body lean used for subtle mantle response.
25911
+ *
25912
+ * @private utility of the avatar rendering system
25913
+ */
25914
+ const MAX_BODY_OFFSET = 0.28;
25915
+ /**
25916
+ * Smoothing window used while a live pointer or touch target is active.
25917
+ *
25918
+ * @private utility of the avatar rendering system
25919
+ */
25920
+ const ACTIVE_INTERACTION_SMOOTHING_MS = 90;
25921
+ /**
25922
+ * Slower smoothing window used when easing the avatar back to its idle state.
25923
+ *
25924
+ * @private utility of the avatar rendering system
25925
+ */
25926
+ const IDLE_INTERACTION_SMOOTHING_MS = 230;
25927
+ /**
25928
+ * Maximum frame delta allowed when smoothing interaction after tab stalls.
25929
+ *
25930
+ * @private utility of the avatar rendering system
25931
+ */
25932
+ const MAX_INTERACTION_FRAME_DELTA_MS = 64;
25933
+ /**
25934
+ * Extra damping used for the slower body lean compared with the quicker eye motion.
25935
+ *
25936
+ * @private utility of the avatar rendering system
25937
+ */
25938
+ const BODY_INTERACTION_SMOOTHING_MULTIPLIER = 1.2;
25939
+ /**
25940
+ * Stable zeroed interaction state used by non-interactive render paths.
25941
+ *
25942
+ * @private utility of the avatar rendering system
25943
+ */
25944
+ const IDLE_AVATAR_INTERACTION_STATE = {
25945
+ gazeX: 0,
25946
+ gazeY: 0,
25947
+ bodyOffsetX: 0,
25948
+ bodyOffsetY: 0,
25949
+ intensity: 0,
25950
+ isPointerActive: false,
25951
+ pointerType: 'idle',
25952
+ };
25953
+ /**
25954
+ * Creates one stable cache key from the meaningful avatar-definition fields.
25955
+ *
25956
+ * @param avatarDefinition Normalized or raw avatar definition.
25957
+ * @returns Stable cache key that ignores object identity churn.
25958
+ *
25959
+ * @private utility of the avatar rendering system
25960
+ */
25961
+ function createAvatarDefinitionKey(avatarDefinition) {
25962
+ const normalizedAvatarDefinition = normalizeAvatarDefinition(avatarDefinition);
25963
+ return [
25964
+ normalizedAvatarDefinition.agentName,
25965
+ normalizedAvatarDefinition.agentHash,
25966
+ normalizedAvatarDefinition.colors.join('|'),
25967
+ ].join('::');
25968
+ }
25969
+ /**
25970
+ * Returns the neutral interaction state used by static/server-side renders.
25971
+ *
25972
+ * @returns Zeroed interaction state.
25973
+ *
25974
+ * @private utility of the avatar rendering system
25975
+ */
25976
+ function createIdleAvatarInteractionState() {
25977
+ return IDLE_AVATAR_INTERACTION_STATE;
25978
+ }
25979
+ /**
25980
+ * Creates a fresh runtime state for the interactive animation loop.
25981
+ *
25982
+ * @returns Runtime interaction state with neutral values.
25983
+ *
25984
+ * @private utility of the avatar rendering system
25985
+ */
25986
+ function createAvatarInteractionRuntimeState() {
25987
+ return {
25988
+ ...IDLE_AVATAR_INTERACTION_STATE,
25989
+ lastFrameMs: null,
25990
+ };
25991
+ }
25992
+ /**
25993
+ * Converts the shared viewport pointer state into one avatar-local gaze target.
25994
+ *
25995
+ * @param avatarBounds Canvas bounds in viewport coordinates.
25996
+ * @param pointerSnapshot Latest shared pointer sample.
25997
+ * @returns Local target used to steer eyes and subtle body lean.
25998
+ *
25999
+ * @private utility of the avatar rendering system
26000
+ */
26001
+ function resolveAvatarPointerTarget(avatarBounds, pointerSnapshot) {
26002
+ if (!pointerSnapshot || !pointerSnapshot.isPointerActive) {
26003
+ return {
26004
+ ...IDLE_AVATAR_INTERACTION_STATE,
26005
+ };
26006
+ }
26007
+ const centerX = avatarBounds.left + avatarBounds.width / 2;
26008
+ const centerY = avatarBounds.top + avatarBounds.height / 2;
26009
+ const normalizedX = (pointerSnapshot.clientX - centerX) / Math.max(avatarBounds.width / 2, 1);
26010
+ const normalizedY = (pointerSnapshot.clientY - centerY) / Math.max(avatarBounds.height / 2, 1);
26011
+ const normalizedLength = Math.hypot(normalizedX, normalizedY) || 1;
26012
+ const clampedLength = Math.min(1, normalizedLength);
26013
+ const targetX = (normalizedX / normalizedLength) * clampedLength;
26014
+ const targetY = (normalizedY / normalizedLength) * clampedLength;
26015
+ const intensity = clamp01$1(clampedLength);
26016
+ return {
26017
+ gazeX: targetX * MAX_GAZE_OFFSET,
26018
+ gazeY: targetY * MAX_GAZE_OFFSET,
26019
+ bodyOffsetX: targetX * MAX_BODY_OFFSET,
26020
+ bodyOffsetY: targetY * MAX_BODY_OFFSET,
26021
+ intensity,
26022
+ isPointerActive: true,
26023
+ pointerType: pointerSnapshot.pointerType,
26024
+ };
26025
+ }
26026
+ /**
26027
+ * Advances the smoothed interaction state toward the latest pointer target.
26028
+ *
26029
+ * @param runtimeState Previous animation-frame state.
26030
+ * @param pointerTarget Latest local pointer target.
26031
+ * @param nowMs Current animation-frame timestamp.
26032
+ * @returns Next runtime state to keep in the animation loop.
26033
+ *
26034
+ * @private utility of the avatar rendering system
26035
+ */
26036
+ function stepAvatarInteractionRuntimeState(runtimeState, pointerTarget, nowMs) {
26037
+ const deltaMs = runtimeState.lastFrameMs === null
26038
+ ? 16
26039
+ : Math.min(MAX_INTERACTION_FRAME_DELTA_MS, Math.max(8, nowMs - runtimeState.lastFrameMs));
26040
+ const smoothingWindowMs = pointerTarget.isPointerActive
26041
+ ? ACTIVE_INTERACTION_SMOOTHING_MS
26042
+ : IDLE_INTERACTION_SMOOTHING_MS;
26043
+ // This exponential interpolation keeps the gaze response smooth regardless of fluctuating frame rates.
26044
+ return {
26045
+ gazeX: interpolateExponentially(runtimeState.gazeX, pointerTarget.gazeX, deltaMs, smoothingWindowMs),
26046
+ gazeY: interpolateExponentially(runtimeState.gazeY, pointerTarget.gazeY, deltaMs, smoothingWindowMs),
26047
+ bodyOffsetX: interpolateExponentially(runtimeState.bodyOffsetX, pointerTarget.bodyOffsetX, deltaMs, smoothingWindowMs * BODY_INTERACTION_SMOOTHING_MULTIPLIER),
26048
+ bodyOffsetY: interpolateExponentially(runtimeState.bodyOffsetY, pointerTarget.bodyOffsetY, deltaMs, smoothingWindowMs * BODY_INTERACTION_SMOOTHING_MULTIPLIER),
26049
+ intensity: interpolateExponentially(runtimeState.intensity, pointerTarget.intensity, deltaMs, smoothingWindowMs),
26050
+ isPointerActive: pointerTarget.isPointerActive,
26051
+ pointerType: pointerTarget.pointerType,
26052
+ lastFrameMs: nowMs,
26053
+ };
26054
+ }
26055
+ /**
26056
+ * Clamps a scalar into the inclusive `[0, 1]` range.
26057
+ *
26058
+ * @param value Arbitrary scalar.
26059
+ * @returns Clamped scalar.
26060
+ *
26061
+ * @private utility of the avatar rendering system
26062
+ */
26063
+ function clamp01$1(value) {
26064
+ return Math.min(1, Math.max(0, value));
26065
+ }
26066
+ /**
26067
+ * Interpolates between two values using frame-rate-independent exponential easing.
26068
+ *
26069
+ * @param currentValue Current smoothed value.
26070
+ * @param targetValue Target value.
26071
+ * @param deltaMs Elapsed milliseconds since the previous frame.
26072
+ * @param smoothingWindowMs Time constant controlling responsiveness.
26073
+ * @returns Smoothed next value.
26074
+ *
26075
+ * @private utility of the avatar rendering system
26076
+ */
26077
+ function interpolateExponentially(currentValue, targetValue, deltaMs, smoothingWindowMs) {
26078
+ const blend = 1 - Math.exp(-deltaMs / Math.max(1, smoothingWindowMs));
26079
+ return currentValue + (targetValue - currentValue) * blend;
26080
+ }
26081
+
26082
+ // Note: [💞] Ignore a discrepancy between file name and entity name
26083
+ /**
26084
+ * Active avatar instances currently consuming the shared pointer tracker.
26085
+ *
26086
+ * @private utility of the avatar rendering system
26087
+ */
26088
+ let avatarPointerTrackingConsumerCount = 0;
26089
+ /**
26090
+ * Latest shared viewport pointer sample.
26091
+ *
26092
+ * @private utility of the avatar rendering system
26093
+ */
26094
+ let currentAvatarPointerSnapshot = null;
26095
+ /**
26096
+ * Cleanup function for the lazily attached global listeners.
26097
+ *
26098
+ * @private utility of the avatar rendering system
26099
+ */
26100
+ let releaseAvatarPointerTrackingListeners = null;
26101
+ /**
26102
+ * Starts the shared pointer tracker and returns a disposer for the caller.
26103
+ *
26104
+ * @returns Cleanup function that releases one consumer.
26105
+ *
26106
+ * @private utility of the avatar rendering system
26107
+ */
26108
+ function retainAvatarPointerTracking() {
26109
+ avatarPointerTrackingConsumerCount++;
26110
+ if (avatarPointerTrackingConsumerCount === 1) {
26111
+ releaseAvatarPointerTrackingListeners = attachAvatarPointerTrackingListeners();
26112
+ }
26113
+ return () => {
26114
+ avatarPointerTrackingConsumerCount = Math.max(0, avatarPointerTrackingConsumerCount - 1);
26115
+ if (avatarPointerTrackingConsumerCount === 0) {
26116
+ currentAvatarPointerSnapshot = null;
26117
+ releaseAvatarPointerTrackingListeners === null || releaseAvatarPointerTrackingListeners === void 0 ? void 0 : releaseAvatarPointerTrackingListeners();
26118
+ releaseAvatarPointerTrackingListeners = null;
26119
+ }
26120
+ };
26121
+ }
26122
+ /**
26123
+ * Returns the latest shared viewport pointer sample when available.
26124
+ *
26125
+ * @returns Shared pointer snapshot or `null`.
26126
+ *
26127
+ * @private utility of the avatar rendering system
26128
+ */
26129
+ function getAvatarPointerSnapshot() {
26130
+ return currentAvatarPointerSnapshot;
26131
+ }
26132
+ /**
26133
+ * Attaches the global pointer/touch listeners used by all live avatar canvases.
26134
+ *
26135
+ * @returns Cleanup function for the attached listeners.
26136
+ *
26137
+ * @private utility of the avatar rendering system
26138
+ */
26139
+ function attachAvatarPointerTrackingListeners() {
26140
+ if (typeof window === 'undefined') {
26141
+ return () => undefined;
26142
+ }
26143
+ const clearPointerSnapshot = () => {
26144
+ currentAvatarPointerSnapshot = null;
26145
+ };
26146
+ const updatePointerSnapshot = (clientX, clientY, pointerType) => {
26147
+ currentAvatarPointerSnapshot = {
26148
+ clientX,
26149
+ clientY,
26150
+ isPointerActive: true,
26151
+ pointerType,
26152
+ };
26153
+ };
26154
+ const handlePointerMove = (event) => {
26155
+ updatePointerSnapshot(event.clientX, event.clientY, normalizeAvatarPointerType(event.pointerType));
26156
+ };
26157
+ const handlePointerDown = (event) => {
26158
+ updatePointerSnapshot(event.clientX, event.clientY, normalizeAvatarPointerType(event.pointerType));
26159
+ };
26160
+ const handlePointerUp = (event) => {
26161
+ if (normalizeAvatarPointerType(event.pointerType) !== 'mouse') {
26162
+ clearPointerSnapshot();
26163
+ }
26164
+ };
26165
+ const handleMouseOut = (event) => {
26166
+ if (!event.relatedTarget) {
26167
+ clearPointerSnapshot();
26168
+ }
26169
+ };
26170
+ const handleTouchEvent = (event) => {
26171
+ const touch = event.touches[0] || event.changedTouches[0];
26172
+ if (!touch) {
26173
+ clearPointerSnapshot();
26174
+ return;
26175
+ }
26176
+ updatePointerSnapshot(touch.clientX, touch.clientY, 'touch');
26177
+ };
26178
+ window.addEventListener('pointermove', handlePointerMove, { passive: true });
26179
+ window.addEventListener('pointerdown', handlePointerDown, { passive: true });
26180
+ window.addEventListener('pointerup', handlePointerUp, { passive: true });
26181
+ window.addEventListener('pointercancel', clearPointerSnapshot, { passive: true });
26182
+ window.addEventListener('mouseout', handleMouseOut, { passive: true });
26183
+ window.addEventListener('blur', clearPointerSnapshot);
26184
+ window.addEventListener('touchstart', handleTouchEvent, { passive: true });
26185
+ window.addEventListener('touchmove', handleTouchEvent, { passive: true });
26186
+ window.addEventListener('touchend', clearPointerSnapshot, { passive: true });
26187
+ window.addEventListener('touchcancel', clearPointerSnapshot, { passive: true });
26188
+ return () => {
26189
+ window.removeEventListener('pointermove', handlePointerMove);
26190
+ window.removeEventListener('pointerdown', handlePointerDown);
26191
+ window.removeEventListener('pointerup', handlePointerUp);
26192
+ window.removeEventListener('pointercancel', clearPointerSnapshot);
26193
+ window.removeEventListener('mouseout', handleMouseOut);
26194
+ window.removeEventListener('blur', clearPointerSnapshot);
26195
+ window.removeEventListener('touchstart', handleTouchEvent);
26196
+ window.removeEventListener('touchmove', handleTouchEvent);
26197
+ window.removeEventListener('touchend', clearPointerSnapshot);
26198
+ window.removeEventListener('touchcancel', clearPointerSnapshot);
26199
+ };
26200
+ }
26201
+ /**
26202
+ * Normalizes browser pointer-type strings into the shared avatar contract.
26203
+ *
26204
+ * @param pointerType Raw browser pointer type.
26205
+ * @returns Shared pointer type.
26206
+ *
26207
+ * @private utility of the avatar rendering system
26208
+ */
26209
+ function normalizeAvatarPointerType(pointerType) {
26210
+ if (pointerType === 'touch' || pointerType === 'pen') {
26211
+ return pointerType;
26212
+ }
26213
+ return 'mouse';
26214
+ }
26215
+
25741
26216
  /* eslint-disable no-magic-numbers */
25742
26217
  /**
25743
26218
  * Builds a smoothly morphing octopus-like silhouette from deterministic parameters.
@@ -25814,8 +26289,9 @@ function traceSmoothClosedPath(context, points) {
25814
26289
  * @private shared geometry helper of `octopus3AvatarVisual` and `asciiOctopusAvatarVisual`
25815
26290
  */
25816
26291
  function createOrganicOctopusTentacleShapes(options) {
25817
- const { size, centerX, centerY, bodyRadius, horizontalStretch, tentacleCount, shapePhase, createRandom, timeMs, saltPrefix, } = options;
26292
+ const { size, centerX, centerY, bodyRadius, horizontalStretch, tentacleCount, shapePhase, createRandom, timeMs, saltPrefix, bodyPoints } = options;
25818
26293
  const baseY = centerY + bodyRadius * 0.74;
26294
+ const lowerBodyAnchorPoints = bodyPoints ? resolveTentacleBodyAnchorPoints(bodyPoints, centerY + bodyRadius * 0.04) : null;
25819
26295
  return Array.from({ length: tentacleCount }, (_, tentacleIndex) => {
25820
26296
  const tentacleRandom = createRandom(`${saltPrefix}-tentacle-${tentacleIndex}`);
25821
26297
  const spreadProgress = tentacleCount === 1 ? 0.5 : tentacleIndex / (tentacleCount - 1);
@@ -25827,10 +26303,21 @@ function createOrganicOctopusTentacleShapes(options) {
25827
26303
  const curlDirection = centeredProgress === 0 ? (tentacleRandom() < 0.5 ? -1 : 1) : Math.sign(centeredProgress);
25828
26304
  const lateralReach = centeredProgress * size * (0.1 + tentacleRandom() * 0.1) + temporalSway;
25829
26305
  const tipReach = curlDirection * size * (0.025 + tentacleRandom() * 0.07);
25830
- const startPoint = {
25831
- x: centerX + centeredProgress * bodyRadius * horizontalStretch * 1.52,
25832
- y: baseY + Math.abs(centeredProgress) * size * 0.012 + tentacleRandom() * size * 0.01,
25833
- };
26306
+ const startYOffset = Math.abs(centeredProgress) * size * 0.012 + tentacleRandom() * size * 0.01;
26307
+ const startPoint = lowerBodyAnchorPoints && lowerBodyAnchorPoints.length >= 2
26308
+ ? createInsetTentacleStartPoint({
26309
+ bodyPoints: lowerBodyAnchorPoints,
26310
+ anchorProgress: spreadProgress,
26311
+ centerX,
26312
+ centerY,
26313
+ bodyRadius,
26314
+ centeredProgress,
26315
+ startYOffset,
26316
+ })
26317
+ : {
26318
+ x: centerX + centeredProgress * bodyRadius * horizontalStretch * 1.52,
26319
+ y: baseY + startYOffset,
26320
+ };
25834
26321
  const controlPointOne = {
25835
26322
  x: startPoint.x + centeredProgress * size * 0.045 + temporalSway * 0.4,
25836
26323
  y: startPoint.y + flowLength * (0.21 + tentacleRandom() * 0.08),
@@ -25860,6 +26347,67 @@ function createOrganicOctopusTentacleShapes(options) {
25860
26347
  };
25861
26348
  });
25862
26349
  }
26350
+ /**
26351
+ * Narrows the body contour to lower anchor points that can safely host tentacle roots.
26352
+ *
26353
+ * @param bodyPoints Generated closed-loop body points.
26354
+ * @param lowerBodyThresholdY Minimum Y coordinate considered part of the lower mantle.
26355
+ * @returns Body points sorted from left to right across the lower silhouette.
26356
+ *
26357
+ * @private shared geometry helper of `octopus3AvatarVisual`
26358
+ */
26359
+ function resolveTentacleBodyAnchorPoints(bodyPoints, lowerBodyThresholdY) {
26360
+ const lowerBodyPoints = bodyPoints.filter((bodyPoint) => bodyPoint.y >= lowerBodyThresholdY).sort((leftPoint, rightPoint) => leftPoint.x - rightPoint.x);
26361
+ if (lowerBodyPoints.length >= 2) {
26362
+ return lowerBodyPoints;
26363
+ }
26364
+ return [...bodyPoints].sort((leftPoint, rightPoint) => leftPoint.x - rightPoint.x);
26365
+ }
26366
+ /**
26367
+ * Resolves one tentacle root from the provided lower body contour and nudges it inside the mantle.
26368
+ *
26369
+ * @param options Tentacle anchor options.
26370
+ * @returns Tentacle start point safely embedded inside the body silhouette.
26371
+ *
26372
+ * @private shared geometry helper of `octopus3AvatarVisual`
26373
+ */
26374
+ function createInsetTentacleStartPoint(options) {
26375
+ const { bodyPoints, anchorProgress, centerX, centerY, bodyRadius, centeredProgress, startYOffset } = options;
26376
+ const clampedAnchorProgress = Math.min(0.94, Math.max(0.06, anchorProgress));
26377
+ const bodyAnchorPoint = interpolatePointAlongTentacleAnchors(bodyPoints, clampedAnchorProgress);
26378
+ const inwardX = centerX - bodyAnchorPoint.x;
26379
+ const inwardY = centerY + bodyRadius * 0.08 - bodyAnchorPoint.y;
26380
+ const inwardLength = Math.hypot(inwardX, inwardY) || 1;
26381
+ const insetDistance = bodyRadius * (0.12 + Math.abs(centeredProgress) * 0.05) + startYOffset * 0.32;
26382
+ return {
26383
+ x: bodyAnchorPoint.x + (inwardX / inwardLength) * insetDistance,
26384
+ y: bodyAnchorPoint.y + (inwardY / inwardLength) * insetDistance,
26385
+ };
26386
+ }
26387
+ /**
26388
+ * Interpolates one left-to-right anchor point along the prepared lower body contour.
26389
+ *
26390
+ * @param bodyPoints Lower body contour points sorted from left to right.
26391
+ * @param progress Interpolation progress in the range `[0, 1]`.
26392
+ * @returns Interpolated anchor point.
26393
+ *
26394
+ * @private shared geometry helper of `octopus3AvatarVisual`
26395
+ */
26396
+ function interpolatePointAlongTentacleAnchors(bodyPoints, progress) {
26397
+ if (bodyPoints.length === 1) {
26398
+ return bodyPoints[0];
26399
+ }
26400
+ const anchorIndex = progress * (bodyPoints.length - 1);
26401
+ const startIndex = Math.floor(anchorIndex);
26402
+ const endIndex = Math.min(bodyPoints.length - 1, startIndex + 1);
26403
+ const blend = anchorIndex - startIndex;
26404
+ const startPoint = bodyPoints[startIndex];
26405
+ const endPoint = bodyPoints[endIndex];
26406
+ return {
26407
+ x: startPoint.x + (endPoint.x - startPoint.x) * blend,
26408
+ y: startPoint.y + (endPoint.y - startPoint.y) * blend,
26409
+ };
26410
+ }
25863
26411
  /**
25864
26412
  * Samples the cubic tentacle centerline and offsets normals to build a filled ribbon.
25865
26413
  *
@@ -25889,6 +26437,26 @@ function sampleOrganicTentacleRibbonPoints(tentacleShape) {
25889
26437
  };
25890
26438
  });
25891
26439
  }
26440
+ /**
26441
+ * Resolves smooth pupil offsets that blend autonomous idle drift with live viewer tracking.
26442
+ *
26443
+ * @param options Eye motion options.
26444
+ * @returns Resolved pupil offsets.
26445
+ *
26446
+ * @private shared geometry helper of octopus avatar visuals
26447
+ */
26448
+ function resolveOrganicEyeMotion(options) {
26449
+ const { radiusX, radiusY, timeMs, phase, interaction, autonomousDriftRatioX = 0.12, autonomousDriftRatioY = 0.08, } = options;
26450
+ const autonomousOffsetX = Math.sin(timeMs / 1280 + phase) * radiusX * autonomousDriftRatioX;
26451
+ const autonomousOffsetY = Math.cos(timeMs / 940 + phase) * radiusY * autonomousDriftRatioY;
26452
+ const interactionBlend = Math.min(1, interaction.intensity * 0.9);
26453
+ return {
26454
+ pupilOffsetX: autonomousOffsetX * (1 - interactionBlend) +
26455
+ interaction.gazeX * radiusX * (0.18 + interactionBlend * 0.18),
26456
+ pupilOffsetY: autonomousOffsetY * (1 - interactionBlend) +
26457
+ interaction.gazeY * radiusY * (0.16 + interactionBlend * 0.16),
26458
+ };
26459
+ }
25892
26460
  /**
25893
26461
  * Samples one point on a cubic Bezier curve.
25894
26462
  *
@@ -25942,13 +26510,14 @@ const ATMOSPHERE_GLYPHS = ['.', ':', '\'', '`'];
25942
26510
  const asciiOctopusAvatarVisual = {
25943
26511
  id: 'ascii-octopus',
25944
26512
  title: 'AsciiOctopus',
25945
- description: 'Morphing alien octopus translated into animated ASCII glyphs with deterministic blob and tentacle geometry.',
26513
+ description: 'Morphing alien octopus translated into animated ASCII glyphs with responsive eyes and seeded geometry.',
25946
26514
  isAnimated: true,
25947
- render({ context, size, palette, createRandom, timeMs }) {
26515
+ supportsPointerTracking: true,
26516
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
25948
26517
  const gridRandom = createRandom('ascii-octopus-grid');
25949
26518
  const staticRandom = createRandom('ascii-octopus-static');
25950
26519
  const gridMetrics = createAsciiGridMetrics(size, gridRandom);
25951
- const layout = createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom);
26520
+ const layout = createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom, interaction);
25952
26521
  drawAvatarFrame(context, size, palette);
25953
26522
  drawAsciiBackdrop(context, size, palette, layout, timeMs);
25954
26523
  context.save();
@@ -26022,7 +26591,8 @@ function drawAsciiBackdrop(context, size, palette, layout, timeMs) {
26022
26591
  */
26023
26592
  function resolveAsciiGlyph(options) {
26024
26593
  const { point, layout, palette, cellWidth, cellHeight, noise, timeMs } = options;
26025
- const eyeGlyphDescriptor = resolveEyeGlyph(point, layout.leftEye, palette, timeMs) || resolveEyeGlyph(point, layout.rightEye, palette, timeMs);
26594
+ const eyeGlyphDescriptor = resolveEyeGlyph(point, layout.leftEye, layout.interaction, palette, timeMs) ||
26595
+ resolveEyeGlyph(point, layout.rightEye, layout.interaction, palette, timeMs);
26026
26596
  if (eyeGlyphDescriptor) {
26027
26597
  return eyeGlyphDescriptor;
26028
26598
  }
@@ -26067,9 +26637,14 @@ function resolveAsciiGlyph(options) {
26067
26637
  *
26068
26638
  * @private helper of `asciiOctopusAvatarVisual`
26069
26639
  */
26070
- function resolveEyeGlyph(point, eyeFeature, palette, timeMs) {
26071
- const pupilOffsetX = Math.sin(timeMs / 1280 + eyeFeature.phase) * eyeFeature.radiusX * 0.12;
26072
- const pupilOffsetY = Math.cos(timeMs / 940 + eyeFeature.phase) * eyeFeature.radiusY * 0.08;
26640
+ function resolveEyeGlyph(point, eyeFeature, interaction, palette, timeMs) {
26641
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
26642
+ radiusX: eyeFeature.radiusX,
26643
+ radiusY: eyeFeature.radiusY,
26644
+ timeMs,
26645
+ phase: eyeFeature.phase,
26646
+ interaction,
26647
+ });
26073
26648
  const scleraDistance = measureRotatedEllipseDistance(point, eyeFeature.centerX, eyeFeature.centerY, eyeFeature.radiusX, eyeFeature.radiusY, eyeFeature.rotation);
26074
26649
  if (scleraDistance > 1.08) {
26075
26650
  return null;
@@ -26243,9 +26818,9 @@ function createAsciiGridMetrics(size, staticRandom) {
26243
26818
  *
26244
26819
  * @private helper of `asciiOctopusAvatarVisual`
26245
26820
  */
26246
- function createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom) {
26247
- const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02);
26248
- const centerY = size * (0.41 + staticRandom() * 0.05);
26821
+ function createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom, interaction) {
26822
+ const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02) + interaction.bodyOffsetX * size * 0.05;
26823
+ const centerY = size * (0.41 + staticRandom() * 0.05) + interaction.bodyOffsetY * size * 0.035;
26249
26824
  const bodyRadius = size * (0.195 + staticRandom() * 0.05);
26250
26825
  const horizontalStretch = 1.08 + staticRandom() * 0.22;
26251
26826
  const verticalStretch = 0.88 + staticRandom() * 0.14;
@@ -26285,6 +26860,7 @@ function createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom) {
26285
26860
  createRandom,
26286
26861
  timeMs,
26287
26862
  saltPrefix: 'ascii-octopus',
26863
+ bodyPoints,
26288
26864
  });
26289
26865
  const sampledTentacles = tentacleShapes.map(sampleOrganicTentacleRibbonPoints);
26290
26866
  const leftEye = {
@@ -26305,7 +26881,7 @@ function createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom) {
26305
26881
  };
26306
26882
  const mouthPoints = sampleQuadraticBezierPoints({ x: centerX - size * 0.074, y: centerY + size * 0.092 }, {
26307
26883
  x: centerX,
26308
- y: centerY + size * (0.142 + Math.sin(timeMs / 620 + shapePhase) * 0.016),
26884
+ y: centerY + size * (0.142 + Math.sin(timeMs / 620 + shapePhase) * 0.016) + interaction.gazeY * size * 0.012,
26309
26885
  }, { x: centerX + size * 0.074, y: centerY + size * 0.092 }, 12);
26310
26886
  let leftBound = Number.POSITIVE_INFINITY;
26311
26887
  let rightBound = Number.NEGATIVE_INFINITY;
@@ -26331,6 +26907,7 @@ function createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom) {
26331
26907
  bodyRadius,
26332
26908
  horizontalStretch,
26333
26909
  shapePhase,
26910
+ interaction,
26334
26911
  bodyPoints,
26335
26912
  sampledTentacles,
26336
26913
  leftEye,
@@ -27051,15 +27628,16 @@ function createMinecraftShirtTexture(random, palette) {
27051
27628
  const octopusAvatarVisual = {
27052
27629
  id: 'octopus',
27053
27630
  title: 'Octopus',
27054
- description: 'Playful underwater mascot with animated tentacles, bubbles, and seed-based markings.',
27631
+ description: 'Playful underwater mascot with cursor-following eyes, animated tentacles, bubbles, and seeded markings.',
27055
27632
  isAnimated: true,
27056
- render({ context, size, palette, createRandom, timeMs }) {
27633
+ supportsPointerTracking: true,
27634
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
27057
27635
  const staticRandom = createRandom('octopus-static');
27058
27636
  const bubbleRandom = createRandom('octopus-bubbles');
27059
27637
  const bubbleCount = 8;
27060
27638
  const bubbleRadiusBase = size * 0.02;
27061
- const centerX = size * 0.5;
27062
- const centerY = size * 0.42;
27639
+ const centerX = size * 0.5 + interaction.bodyOffsetX * size * 0.035;
27640
+ const centerY = size * 0.42 + interaction.bodyOffsetY * size * 0.024;
27063
27641
  const headRadius = size * (0.19 + staticRandom() * 0.03);
27064
27642
  const mantleHeight = headRadius * 1.18;
27065
27643
  const tentacleLength = size * (0.18 + staticRandom() * 0.06);
@@ -27139,11 +27717,9 @@ const octopusAvatarVisual = {
27139
27717
  }
27140
27718
  const eyeOffsetX = headRadius * 0.42;
27141
27719
  const eyeY = centerY + headRadius * 0.04;
27142
- const pupilDriftX = Math.sin(timeMs / 850) * headRadius * 0.05;
27143
- const pupilDriftY = Math.cos(timeMs / 930) * headRadius * 0.03;
27144
27720
  const eyeRadius = headRadius * 0.22;
27145
- drawEye(context, centerX - eyeOffsetX, eyeY, eyeRadius, palette, pupilDriftX, pupilDriftY);
27146
- drawEye(context, centerX + eyeOffsetX, eyeY, eyeRadius, palette, pupilDriftX, pupilDriftY);
27721
+ drawEye(context, centerX - eyeOffsetX, eyeY, eyeRadius, palette, timeMs, interaction, 0);
27722
+ drawEye(context, centerX + eyeOffsetX, eyeY, eyeRadius, palette, timeMs, interaction, Math.PI / 5);
27147
27723
  context.beginPath();
27148
27724
  context.arc(centerX - headRadius * 0.28, centerY + headRadius * 0.3, headRadius * 0.12, 0, Math.PI * 2);
27149
27725
  context.arc(centerX + headRadius * 0.28, centerY + headRadius * 0.3, headRadius * 0.12, 0, Math.PI * 2);
@@ -27166,22 +27742,32 @@ const octopusAvatarVisual = {
27166
27742
  * @param centerY Eye center Y coordinate.
27167
27743
  * @param radius Eye radius.
27168
27744
  * @param palette Derived avatar palette.
27169
- * @param pupilDriftX Horizontal pupil drift.
27170
- * @param pupilDriftY Vertical pupil drift.
27745
+ * @param timeMs Current animation time in milliseconds.
27746
+ * @param interaction Smoothed avatar interaction state.
27747
+ * @param phase Seed-based phase offset.
27171
27748
  *
27172
27749
  * @private helper of `octopusAvatarVisual`
27173
27750
  */
27174
- function drawEye(context, centerX, centerY, radius, palette, pupilDriftX, pupilDriftY) {
27751
+ function drawEye(context, centerX, centerY, radius, palette, timeMs, interaction, phase) {
27752
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
27753
+ radiusX: radius,
27754
+ radiusY: radius,
27755
+ timeMs,
27756
+ phase,
27757
+ interaction,
27758
+ autonomousDriftRatioX: 0.05,
27759
+ autonomousDriftRatioY: 0.03,
27760
+ });
27175
27761
  context.beginPath();
27176
27762
  context.arc(centerX, centerY, radius, 0, Math.PI * 2);
27177
27763
  context.fillStyle = '#ffffff';
27178
27764
  context.fill();
27179
27765
  context.beginPath();
27180
- context.arc(centerX + pupilDriftX, centerY + pupilDriftY, radius * 0.45, 0, Math.PI * 2);
27766
+ context.arc(centerX + pupilOffsetX, centerY + pupilOffsetY, radius * 0.45, 0, Math.PI * 2);
27181
27767
  context.fillStyle = palette.ink;
27182
27768
  context.fill();
27183
27769
  context.beginPath();
27184
- context.arc(centerX + pupilDriftX - radius * 0.12, centerY + pupilDriftY - radius * 0.12, radius * 0.15, 0, Math.PI * 2);
27770
+ context.arc(centerX + pupilOffsetX - radius * 0.12, centerY + pupilOffsetY - radius * 0.12, radius * 0.15, 0, Math.PI * 2);
27185
27771
  context.fillStyle = '#ffffff';
27186
27772
  context.fill();
27187
27773
  context.beginPath();
@@ -27200,12 +27786,13 @@ function drawEye(context, centerX, centerY, radius, palette, pupilDriftX, pupilD
27200
27786
  const octopus2AvatarVisual = {
27201
27787
  id: 'octopus2',
27202
27788
  title: 'Octopus2',
27203
- description: 'Organic alien octopus rendered as one continuously morphing blob with luminous eyes and soft inner motion.',
27789
+ description: 'Organic alien octopus rendered as one continuously morphing blob with responsive luminous eyes.',
27204
27790
  isAnimated: true,
27205
- render({ context, size, palette, createRandom, timeMs }) {
27791
+ supportsPointerTracking: true,
27792
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
27206
27793
  const staticRandom = createRandom('octopus2-static');
27207
- const centerX = size * 0.5;
27208
- const centerY = size * (0.48 + staticRandom() * 0.03);
27794
+ const centerX = size * 0.5 + interaction.bodyOffsetX * size * 0.042;
27795
+ const centerY = size * (0.48 + staticRandom() * 0.03) + interaction.bodyOffsetY * size * 0.028;
27209
27796
  const bodyRadius = size * (0.25 + staticRandom() * 0.035);
27210
27797
  const horizontalStretch = 1.04 + staticRandom() * 0.16;
27211
27798
  const verticalStretch = 0.94 + staticRandom() * 0.12;
@@ -27276,11 +27863,11 @@ const octopus2AvatarVisual = {
27276
27863
  const eyeCenterY = centerY - size * 0.02;
27277
27864
  const eyeRadiusX = size * 0.072;
27278
27865
  const eyeRadiusY = size * 0.086;
27279
- drawAlienEye(context, centerX - eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase);
27280
- drawAlienEye(context, centerX + eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase + Math.PI / 5);
27866
+ drawAlienEye(context, centerX - eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase, interaction);
27867
+ drawAlienEye(context, centerX + eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase + Math.PI / 5, interaction);
27281
27868
  context.beginPath();
27282
27869
  context.moveTo(centerX - size * 0.08, centerY + size * 0.12);
27283
- context.quadraticCurveTo(centerX, centerY + size * (0.175 + Math.sin(timeMs / 520 + shapePhase) * 0.012), centerX + size * 0.08, centerY + size * 0.12);
27870
+ context.quadraticCurveTo(centerX, centerY + size * (0.175 + Math.sin(timeMs / 520 + shapePhase) * 0.012) + interaction.gazeY * size * 0.01, centerX + size * 0.08, centerY + size * 0.12);
27284
27871
  context.strokeStyle = `${palette.ink}b3`;
27285
27872
  context.lineWidth = size * 0.013;
27286
27873
  context.lineCap = 'round';
@@ -27358,12 +27945,19 @@ function drawLowerSuckers(context, centerX, centerY, size, palette, createRandom
27358
27945
  * @param palette Derived avatar palette.
27359
27946
  * @param timeMs Current animation time in milliseconds.
27360
27947
  * @param phase Seed-based animation phase.
27948
+ * @param interaction Smoothed avatar interaction state.
27361
27949
  *
27362
27950
  * @private helper of `octopus2AvatarVisual`
27363
27951
  */
27364
- function drawAlienEye(context, centerX, centerY, radiusX, radiusY, palette, timeMs, phase) {
27365
- const pupilOffsetX = Math.sin(timeMs / 1300 + phase) * radiusX * 0.12;
27366
- const pupilOffsetY = Math.cos(timeMs / 970 + phase) * radiusY * 0.1;
27952
+ function drawAlienEye(context, centerX, centerY, radiusX, radiusY, palette, timeMs, phase, interaction) {
27953
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
27954
+ radiusX,
27955
+ radiusY,
27956
+ timeMs,
27957
+ phase,
27958
+ interaction,
27959
+ autonomousDriftRatioY: 0.1,
27960
+ });
27367
27961
  context.save();
27368
27962
  context.beginPath();
27369
27963
  context.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
@@ -27403,12 +27997,13 @@ function drawAlienEye(context, centerX, centerY, radiusX, radiusY, palette, time
27403
27997
  const octopus3AvatarVisual = {
27404
27998
  id: 'octopus3',
27405
27999
  title: 'Octopus3',
27406
- description: 'Gelatinous alien octopus with a morphing mantle, visible ribbon tentacles, and seeded facial features.',
28000
+ description: 'Gelatinous alien octopus with a morphing mantle, responsive eyes, and visible ribbon tentacles.',
27407
28001
  isAnimated: true,
27408
- render({ context, size, palette, createRandom, timeMs }) {
28002
+ supportsPointerTracking: true,
28003
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
27409
28004
  const staticRandom = createRandom('octopus3-static');
27410
- const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02);
27411
- const centerY = size * (0.41 + staticRandom() * 0.05);
28005
+ const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02) + interaction.bodyOffsetX * size * 0.05;
28006
+ const centerY = size * (0.41 + staticRandom() * 0.05) + interaction.bodyOffsetY * size * 0.035;
27412
28007
  const bodyRadius = size * (0.2 + staticRandom() * 0.045);
27413
28008
  const horizontalStretch = 1.08 + staticRandom() * 0.22;
27414
28009
  const verticalStretch = 0.9 + staticRandom() * 0.12;
@@ -27448,6 +28043,7 @@ const octopus3AvatarVisual = {
27448
28043
  createRandom,
27449
28044
  timeMs,
27450
28045
  saltPrefix: 'octopus3',
28046
+ bodyPoints,
27451
28047
  });
27452
28048
  drawAvatarFrame(context, size, palette);
27453
28049
  drawOctopus3Atmosphere(context, size, palette, centerX, centerY, timeMs, shapePhase);
@@ -27493,11 +28089,11 @@ const octopus3AvatarVisual = {
27493
28089
  context.ellipse(centerX, centerY - size * 0.14, size * 0.18, size * 0.062, 0, Math.PI, Math.PI * 2);
27494
28090
  context.fillStyle = `${palette.highlight}3d`;
27495
28091
  context.fill();
27496
- drawSeededEye(context, centerX - eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase);
27497
- drawSeededEye(context, centerX + eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase + Math.PI / 4);
28092
+ drawSeededEye(context, centerX - eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase, interaction);
28093
+ drawSeededEye(context, centerX + eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase + Math.PI / 4, interaction);
27498
28094
  context.beginPath();
27499
28095
  context.moveTo(centerX - size * 0.07, centerY + size * 0.09);
27500
- context.quadraticCurveTo(centerX, centerY + size * (0.14 + Math.sin(timeMs / 620 + shapePhase) * 0.016), centerX + size * 0.07, centerY + size * 0.09);
28096
+ context.quadraticCurveTo(centerX, centerY + size * (0.14 + Math.sin(timeMs / 620 + shapePhase) * 0.016) + interaction.gazeY * size * 0.012, centerX + size * 0.07, centerY + size * 0.09);
27501
28097
  context.strokeStyle = `${palette.ink}b3`;
27502
28098
  context.lineWidth = size * 0.012;
27503
28099
  context.lineCap = 'round';
@@ -27685,12 +28281,18 @@ function drawMantleNodes(context, centerX, centerY, size, palette, createRandom)
27685
28281
  * @param palette Derived avatar palette.
27686
28282
  * @param timeMs Current animation time in milliseconds.
27687
28283
  * @param phase Seed-based animation phase.
28284
+ * @param interaction Smoothed avatar interaction state.
27688
28285
  *
27689
28286
  * @private helper of `octopus3AvatarVisual`
27690
28287
  */
27691
- function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, palette, timeMs, phase) {
27692
- const pupilOffsetX = Math.sin(timeMs / 1280 + phase) * radiusX * 0.12;
27693
- const pupilOffsetY = Math.cos(timeMs / 940 + phase) * radiusY * 0.08;
28288
+ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, palette, timeMs, phase, interaction) {
28289
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
28290
+ radiusX,
28291
+ radiusY,
28292
+ timeMs,
28293
+ phase,
28294
+ interaction,
28295
+ });
27694
28296
  context.save();
27695
28297
  context.translate(centerX, centerY);
27696
28298
  context.rotate(rotation);
@@ -27726,7 +28328,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
27726
28328
  context.stroke();
27727
28329
  context.beginPath();
27728
28330
  context.moveTo(-radiusX * 0.88, -radiusY * 0.08);
27729
- context.quadraticCurveTo(0, -radiusY * 0.9, radiusX * 0.88, -radiusY * 0.08);
28331
+ context.quadraticCurveTo(0, -radiusY * (0.9 - interaction.gazeY * 0.16 + interaction.intensity * 0.08), radiusX * 0.88, -radiusY * 0.08);
27730
28332
  context.strokeStyle = `${palette.shadow}73`;
27731
28333
  context.lineWidth = radiusX * 0.16;
27732
28334
  context.lineCap = 'round';
@@ -27874,6 +28476,7 @@ function getAvatarVisualById(visualId) {
27874
28476
  function renderAvatarVisual(options) {
27875
28477
  const normalizedAvatarDefinition = normalizeAvatarDefinition(options.avatarDefinition);
27876
28478
  const avatarVisual = getAvatarVisualById(options.visualId);
28479
+ const surface = options.surface || 'framed';
27877
28480
  const context = options.canvas.getContext('2d');
27878
28481
  if (!context) {
27879
28482
  throw new Error('2D canvas rendering context is unavailable.');
@@ -27886,8 +28489,10 @@ function renderAvatarVisual(options) {
27886
28489
  devicePixelRatio: options.devicePixelRatio || 1,
27887
28490
  timeMs: options.timeMs,
27888
28491
  avatarDefinition: normalizedAvatarDefinition,
27889
- palette: createAvatarPalette(normalizedAvatarDefinition),
28492
+ palette: createAvatarPalette(normalizedAvatarDefinition, surface),
27890
28493
  createRandom: createAvatarRandomFactory(normalizedAvatarDefinition),
28494
+ surface,
28495
+ interaction: options.interaction || createIdleAvatarInteractionState(),
27891
28496
  });
27892
28497
  }
27893
28498
 
@@ -27903,42 +28508,74 @@ const AVATAR_CANVAS_RADIUS_RATIO = 0.18;
27903
28508
  * @private shared component for in-repository avatar previews
27904
28509
  */
27905
28510
  function Avatar(props) {
27906
- const { avatarDefinition, visualId, size = DEFAULT_AVATAR_SIZE, title, className, style } = props;
28511
+ const { avatarDefinition, visualId, surface = 'framed', size = DEFAULT_AVATAR_SIZE, title, className, style } = props;
27907
28512
  const canvasRef = useRef(null);
27908
- const normalizedAvatarDefinition = useMemo(() => normalizeAvatarDefinition(avatarDefinition), [avatarDefinition]);
28513
+ const animationStartRef = useRef(null);
28514
+ const interactionRuntimeStateRef = useRef(createAvatarInteractionRuntimeState());
28515
+ const avatarColorsKey = avatarDefinition.colors.join('|');
28516
+ const normalizedAvatarDefinition = useMemo(() => normalizeAvatarDefinition(avatarDefinition), [avatarDefinition.agentHash, avatarDefinition.agentName, avatarColorsKey]);
28517
+ const avatarDefinitionKey = useMemo(() => createAvatarDefinitionKey(normalizedAvatarDefinition), [normalizedAvatarDefinition.agentHash, normalizedAvatarDefinition.agentName, normalizedAvatarDefinition.colors.join('|')]);
27909
28518
  const avatarVisual = useMemo(() => getAvatarVisualById(visualId), [visualId]);
28519
+ useEffect(() => {
28520
+ interactionRuntimeStateRef.current = createAvatarInteractionRuntimeState();
28521
+ }, [avatarDefinitionKey, visualId]);
27910
28522
  useEffect(() => {
27911
28523
  const canvas = canvasRef.current;
27912
28524
  if (!canvas) {
27913
28525
  throw new Error('Avatar canvas is not mounted.');
27914
28526
  }
28527
+ const isDynamicAvatar = avatarVisual.isAnimated || avatarVisual.supportsPointerTracking === true;
28528
+ const releasePointerTracking = avatarVisual.supportsPointerTracking ? retainAvatarPointerTracking() : null;
27915
28529
  let animationFrameId = null;
27916
- const animationStart = performance.now();
28530
+ if (animationStartRef.current === null) {
28531
+ animationStartRef.current = performance.now();
28532
+ }
27917
28533
  const renderFrame = (now) => {
28534
+ const pointerSnapshot = avatarVisual.supportsPointerTracking ? getAvatarPointerSnapshot() : null;
28535
+ let interactionState = createIdleAvatarInteractionState();
28536
+ if (avatarVisual.supportsPointerTracking && pointerSnapshot) {
28537
+ interactionRuntimeStateRef.current = stepAvatarInteractionRuntimeState(interactionRuntimeStateRef.current, resolveAvatarPointerTarget(canvas.getBoundingClientRect(), pointerSnapshot), now);
28538
+ interactionState = interactionRuntimeStateRef.current;
28539
+ }
28540
+ else if (avatarVisual.supportsPointerTracking) {
28541
+ interactionRuntimeStateRef.current = stepAvatarInteractionRuntimeState(interactionRuntimeStateRef.current, createIdleAvatarInteractionState(), now);
28542
+ interactionState = interactionRuntimeStateRef.current;
28543
+ }
27918
28544
  renderAvatarVisual({
27919
28545
  canvas,
27920
28546
  avatarDefinition: normalizedAvatarDefinition,
27921
28547
  visualId,
28548
+ surface,
27922
28549
  size,
27923
- timeMs: now - animationStart,
28550
+ timeMs: now - animationStartRef.current,
27924
28551
  devicePixelRatio: window.devicePixelRatio || 1,
28552
+ interaction: interactionState,
27925
28553
  });
27926
- if (avatarVisual.isAnimated) {
28554
+ if (isDynamicAvatar) {
27927
28555
  animationFrameId = window.requestAnimationFrame(renderFrame);
27928
28556
  }
27929
28557
  };
27930
- renderFrame(animationStart);
28558
+ renderFrame(performance.now());
27931
28559
  return () => {
27932
28560
  if (animationFrameId !== null) {
27933
28561
  window.cancelAnimationFrame(animationFrameId);
27934
28562
  }
28563
+ releasePointerTracking === null || releasePointerTracking === void 0 ? void 0 : releasePointerTracking();
27935
28564
  };
27936
- }, [avatarVisual.isAnimated, normalizedAvatarDefinition, size, visualId]);
28565
+ }, [
28566
+ avatarDefinitionKey,
28567
+ avatarVisual.isAnimated,
28568
+ avatarVisual.supportsPointerTracking,
28569
+ normalizedAvatarDefinition,
28570
+ size,
28571
+ surface,
28572
+ visualId,
28573
+ ]);
27937
28574
  return (jsx("canvas", { ref: canvasRef, title: title || `${normalizedAvatarDefinition.agentName} avatar`, className: className, style: {
27938
28575
  width: size,
27939
28576
  height: size,
27940
28577
  display: 'block',
27941
- borderRadius: size * AVATAR_CANVAS_RADIUS_RATIO,
28578
+ borderRadius: surface === 'transparent' ? 0 : size * AVATAR_CANVAS_RADIUS_RATIO,
27942
28579
  ...style,
27943
28580
  } }));
27944
28581
  }
@@ -27949,9 +28586,9 @@ function Avatar(props) {
27949
28586
  * @private shared component for avatar media rendering
27950
28587
  */
27951
28588
  function AvatarOrImage(props) {
27952
- const { imageUrl, avatarDefinition, visualId, size, alt, className, style } = props;
28589
+ const { imageUrl, avatarDefinition, visualId, surface, size, alt, className, style } = props;
27953
28590
  if (avatarDefinition && visualId) {
27954
- return (jsx(Avatar, { avatarDefinition: avatarDefinition, visualId: visualId, size: size, title: alt, className: className, style: style }));
28591
+ return (jsx(Avatar, { avatarDefinition: avatarDefinition, visualId: visualId, surface: surface, size: size, title: alt, className: className, style: style }));
27955
28592
  }
27956
28593
  if (!imageUrl) {
27957
28594
  return null;
@@ -38559,6 +39196,14 @@ class OpenAiVectorStoreHandler extends OpenAiExecutionTools {
38559
39196
  * Constant for default agent kit model name.
38560
39197
  */
38561
39198
  const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.4-mini';
39199
+ /**
39200
+ * Default model used for nested DeepSearch tool invocations.
39201
+ */
39202
+ const DEFAULT_DEEP_SEARCH_MODEL_NAME = 'o4-mini-deep-research';
39203
+ /**
39204
+ * Tool name used by the Book commitment-backed DeepSearch capability.
39205
+ */
39206
+ const DEEP_SEARCH_TOOL_NAME = 'deep_search';
38562
39207
  /**
38563
39208
  * Creates one structured log entry for streamed tool-call updates.
38564
39209
  *
@@ -38601,6 +39246,98 @@ function resolveFinalToolCallState$1(options) {
38601
39246
  }
38602
39247
  return 'COMPLETE';
38603
39248
  }
39249
+ /**
39250
+ * Returns true when one tool definition represents the dedicated DeepSearch capability.
39251
+ *
39252
+ * @param toolDefinition - Tool definition from compiled model requirements.
39253
+ * @returns `true` when the tool should be backed by a nested deep-research agent.
39254
+ *
39255
+ * @private helper of `OpenAiAgentKitExecutionTools`
39256
+ */
39257
+ function isDeepSearchToolDefinition(toolDefinition) {
39258
+ return toolDefinition.name === DEEP_SEARCH_TOOL_NAME;
39259
+ }
39260
+ /**
39261
+ * Normalizes Promptbook JSON-schema tool parameters for AgentKit function tools.
39262
+ *
39263
+ * @param parameters - Promptbook tool parameters.
39264
+ * @returns AgentKit-compatible JSON schema or `undefined`.
39265
+ *
39266
+ * @private helper of `OpenAiAgentKitExecutionTools`
39267
+ */
39268
+ function normalizeAgentKitToolParameters(parameters) {
39269
+ var _a, _b;
39270
+ if (!parameters) {
39271
+ return undefined;
39272
+ }
39273
+ return {
39274
+ ...parameters,
39275
+ additionalProperties: (_a = parameters.additionalProperties) !== null && _a !== void 0 ? _a : false,
39276
+ required: (_b = parameters.required) !== null && _b !== void 0 ? _b : [],
39277
+ };
39278
+ }
39279
+ /**
39280
+ * Creates instructions for the nested DeepSearch specialist agent.
39281
+ *
39282
+ * @param toolDescription - Model-facing description from the original tool definition.
39283
+ * @returns System instructions for the nested deep-research agent.
39284
+ *
39285
+ * @private helper of `OpenAiAgentKitExecutionTools`
39286
+ */
39287
+ function createDeepSearchAgentInstructions(toolDescription) {
39288
+ const normalizedDescription = toolDescription.trim();
39289
+ return spaceTrim$1((block) => `
39290
+ You are a DeepSearch specialist working as a tool for another agent.
39291
+ Perform thorough, source-grounded public-web research based on the provided request.
39292
+ Use web search to gather current information, compare relevant viewpoints, and synthesize a concise research brief.
39293
+ Do not ask follow-up questions. If the request is not specific enough, state the assumptions you had to make.
39294
+ Include citations in the research brief whenever sources were used.
39295
+ ${block(normalizedDescription ? `Tool guidance:\n${normalizedDescription}` : '')}
39296
+ `);
39297
+ }
39298
+ /**
39299
+ * Builds the nested DeepSearch prompt from structured tool arguments.
39300
+ *
39301
+ * @param rawInput - Parsed function-tool arguments provided by the outer agent.
39302
+ * @returns Prompt text passed to the nested deep-research agent.
39303
+ *
39304
+ * @private helper of `OpenAiAgentKitExecutionTools`
39305
+ */
39306
+ function buildDeepSearchToolInput(rawInput) {
39307
+ const input = rawInput && typeof rawInput === 'object' ? rawInput : {};
39308
+ const query = typeof input.query === 'string' ? input.query.trim() : '';
39309
+ const additionalHints = Object.entries(input)
39310
+ .filter(([key, value]) => key !== 'query' && value !== undefined && value !== null && String(value).trim() !== '')
39311
+ .map(([key, value]) => `- ${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`);
39312
+ return spaceTrim$1((block) => `
39313
+ Research request:
39314
+ ${query || JSON.stringify(input)}
39315
+ ${block(additionalHints.length > 0 ? `Execution hints:\n${additionalHints.join('\n')}` : '')}
39316
+ `);
39317
+ }
39318
+ /**
39319
+ * Creates the native Agent SDK tool used for `USE DEEPSEARCH`.
39320
+ *
39321
+ * @param toolDefinition - Promptbook tool definition for `deep_search`.
39322
+ * @returns AgentKit tool backed by a nested deep-research agent.
39323
+ *
39324
+ * @private helper of `OpenAiAgentKitExecutionTools`
39325
+ */
39326
+ function createDeepSearchAgentKitTool(toolDefinition) {
39327
+ const deepSearchAgent = new Agent$1({
39328
+ name: 'DeepSearch',
39329
+ model: DEFAULT_DEEP_SEARCH_MODEL_NAME,
39330
+ instructions: createDeepSearchAgentInstructions(toolDefinition.description),
39331
+ tools: [webSearchTool({ searchContextSize: 'high' })],
39332
+ });
39333
+ return deepSearchAgent.asTool({
39334
+ toolName: toolDefinition.name,
39335
+ toolDescription: toolDefinition.description,
39336
+ parameters: normalizeAgentKitToolParameters(toolDefinition.parameters),
39337
+ inputBuilder: ({ params }) => buildDeepSearchToolInput(params),
39338
+ customOutputExtractor: (result) => { var _a; return typeof result.finalOutput === 'string' ? result.finalOutput : JSON.stringify((_a = result.finalOutput) !== null && _a !== void 0 ? _a : ''); },
39339
+ });
39340
+ }
38604
39341
  /**
38605
39342
  * Constant for default JSON schema name.
38606
39343
  */
@@ -38941,25 +39678,23 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
38941
39678
  * Builds the tool list for AgentKit, including hosted file search when applicable.
38942
39679
  */
38943
39680
  buildAgentKitTools(options) {
38944
- var _a;
38945
39681
  const { tools, vectorStoreId } = options;
38946
39682
  const agentKitTools = [];
38947
39683
  if (vectorStoreId) {
38948
39684
  agentKitTools.push(fileSearchTool(vectorStoreId));
38949
39685
  }
38950
39686
  if (tools && tools.length > 0) {
38951
- const scriptTools = this.resolveScriptTools();
39687
+ let scriptTools = null;
38952
39688
  for (const toolDefinition of tools) {
39689
+ if (isDeepSearchToolDefinition(toolDefinition)) {
39690
+ agentKitTools.push(createDeepSearchAgentKitTool(toolDefinition));
39691
+ continue;
39692
+ }
39693
+ scriptTools !== null && scriptTools !== void 0 ? scriptTools : (scriptTools = this.resolveScriptTools());
38953
39694
  agentKitTools.push(tool({
38954
39695
  name: toolDefinition.name,
38955
39696
  description: toolDefinition.description,
38956
- parameters: toolDefinition.parameters
38957
- ? {
38958
- ...toolDefinition.parameters,
38959
- additionalProperties: false,
38960
- required: (_a = toolDefinition.parameters.required) !== null && _a !== void 0 ? _a : [],
38961
- }
38962
- : undefined,
39697
+ parameters: normalizeAgentKitToolParameters(toolDefinition.parameters),
38963
39698
  strict: false,
38964
39699
  execute: async (input, runContext, details) => {
38965
39700
  var _a, _b, _c, _d;
@@ -45146,6 +45881,7 @@ const TOOL_TITLES = {
45146
45881
  delete_wallet_record: { title: 'Deleting wallet', emoji: '👛' },
45147
45882
  request_wallet_record: { title: 'Requesting wallet', emoji: '👛' },
45148
45883
  web_search: { title: 'Searching the web', emoji: '🔎' },
45884
+ deep_search: { title: 'Deep research', emoji: '🔬' },
45149
45885
  useSearchEngine: { title: 'Searching the web', emoji: '🔎' },
45150
45886
  search: { title: 'Searching the web', emoji: '🔎' },
45151
45887
  useBrowser: { title: 'Browsing the web', emoji: '🌐' },
@@ -48107,7 +48843,7 @@ function renderGenericToolCallDetails(options) {
48107
48843
  * @private function of ChatToolCallModal
48108
48844
  */
48109
48845
  function isSearchToolCallName(toolName) {
48110
- return toolName === 'web_search' || toolName === 'useSearchEngine' || toolName === 'search';
48846
+ return toolName === 'web_search' || toolName === 'deep_search' || toolName === 'useSearchEngine' || toolName === 'search';
48111
48847
  }
48112
48848
  /**
48113
48849
  * Checks whether a tool name should use the time renderer.
@@ -48807,6 +49543,12 @@ function TeamToolCallModalContent(options) {
48807
49543
  });
48808
49544
  }
48809
49545
  }
49546
+ const staticConversationDelayConfig = {
49547
+ ...FAST_FLOW,
49548
+ beforeFirstMessage: 0,
49549
+ // Show the full internal exchange immediately so the modal never opens as a blank panel.
49550
+ showIntermediateMessages: messages.length,
49551
+ };
48810
49552
  const agentName = ((_c = (_b = teamResult.conversation) === null || _b === void 0 ? void 0 : _b.find((entry) => entry.sender === 'AGENT' || entry.role === 'AGENT')) === null || _c === void 0 ? void 0 : _c.name) || 'Agent';
48811
49553
  const teammateConversationName = ((_e = (_d = teamResult.conversation) === null || _d === void 0 ? void 0 : _d.find((entry) => entry.sender === 'TEAMMATE' || entry.role === 'TEAMMATE')) === null || _e === void 0 ? void 0 : _e.name) || '';
48812
49554
  const teammateProfile = teammateUrl ? teamProfiles[teammateUrl] : undefined;
@@ -48843,7 +49585,7 @@ function TeamToolCallModalContent(options) {
48843
49585
  avatarSrc: resolvedTeammateAvatar || undefined,
48844
49586
  },
48845
49587
  ];
48846
- return (jsxs(Fragment, { children: [jsx("div", { className: classNames(styles$5.searchModalHeader, styles$5.teamModalHeader), children: jsxs("div", { className: styles$5.teamHeaderParticipants, children: [jsx(TeamHeaderProfile, { label: resolvedAgentLabel, avatarSrc: resolvedAgentAvatar, avatarDefinition: resolvedAgentAvatarDefinition, avatarVisualId: resolvedAgentAvatarVisualId, fallbackColor: resolvedAgentHeaderColor }), jsx("span", { className: styles$5.teamHeaderDivider, children: "talking with" }), jsx(TeamHeaderProfile, { label: resolvedTeammateLabel, avatarSrc: resolvedTeammateAvatar, fallbackColor: "#0ea5e9", href: teammateLink })] }) }), jsxs("div", { className: styles$5.searchModalContent, children: [messages.length > 0 ? (jsx("div", { className: styles$5.teamChatContainer, children: jsx(MockedChat, { title: `Chat between ${resolvedAgentLabel} and ${resolvedTeammateLabel}`, messages: messages, participants: participants, isResettable: false, isPausable: false, isSaveButtonEnabled: false, isCopyButtonEnabled: false, visual: "STANDALONE", delayConfig: FAST_FLOW }) })) : (jsx("div", { className: styles$5.noResults, children: "No teammate conversation available." })), (hasTeamToolCalls || hasTeamCitations) && (jsxs("div", { className: styles$5.teamToolCallSection, children: [hasTeamToolCalls && (jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsx("div", { className: styles$5.teamToolCallHeading, children: "Actions" }), jsx("div", { className: styles$5.teamToolCallChips, children: teamToolCalls.map((toolCallEntry, index) => {
49588
+ return (jsxs(Fragment, { children: [jsx("div", { className: classNames(styles$5.searchModalHeader, styles$5.teamModalHeader), children: jsxs("div", { className: styles$5.teamHeaderParticipants, children: [jsx(TeamHeaderProfile, { label: resolvedAgentLabel, avatarSrc: resolvedAgentAvatar, avatarDefinition: resolvedAgentAvatarDefinition, avatarVisualId: resolvedAgentAvatarVisualId, fallbackColor: resolvedAgentHeaderColor }), jsx("span", { className: styles$5.teamHeaderDivider, children: "talking with" }), jsx(TeamHeaderProfile, { label: resolvedTeammateLabel, avatarSrc: resolvedTeammateAvatar, fallbackColor: "#0ea5e9", href: teammateLink })] }) }), jsxs("div", { className: styles$5.searchModalContent, children: [messages.length > 0 ? (jsx("div", { className: styles$5.teamChatContainer, children: jsx(MockedChat, { title: `Chat between ${resolvedAgentLabel} and ${resolvedTeammateLabel}`, messages: messages, participants: participants, isResettable: false, isPausable: false, isSaveButtonEnabled: false, isCopyButtonEnabled: false, visual: "STANDALONE", delayConfig: staticConversationDelayConfig }) })) : (jsx("div", { className: styles$5.noResults, children: "No teammate conversation available." })), (hasTeamToolCalls || hasTeamCitations) && (jsxs("div", { className: styles$5.teamToolCallSection, children: [hasTeamToolCalls && (jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsx("div", { className: styles$5.teamToolCallHeading, children: "Actions" }), jsx("div", { className: styles$5.teamToolCallChips, children: teamToolCalls.map((toolCallEntry, index) => {
48847
49589
  const chipletInfo = getToolCallChipletInfo(toolCallEntry.toolCall, undefined, toolTitles);
48848
49590
  const chipletText = buildToolCallChipText(chipletInfo);
48849
49591
  return (jsxs("button", { className: styles$5.completedToolCall, onClick: () => {