@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/umd/index.umd.js CHANGED
@@ -30,7 +30,7 @@
30
30
  * @generated
31
31
  * @see https://github.com/webgptorg/promptbook
32
32
  */
33
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-47';
33
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-48';
34
34
  /**
35
35
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
36
36
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1392,11 +1392,12 @@
1392
1392
  * Creates the shared derived palette used by every avatar visual.
1393
1393
  *
1394
1394
  * @param avatarDefinition Stable avatar definition.
1395
+ * @param surface Surface style used by the parent UI.
1395
1396
  * @returns Derived palette.
1396
1397
  *
1397
1398
  * @private utility of the avatar rendering system
1398
1399
  */
1399
- function createAvatarPalette(avatarDefinition) {
1400
+ function createAvatarPalette(avatarDefinition, surface = 'framed') {
1400
1401
  const normalizedAvatarDefinition = normalizeAvatarDefinition(avatarDefinition);
1401
1402
  const primaryColor = Color.fromSafe(normalizedAvatarDefinition.colors[0] || PROMPTBOOK_COLOR);
1402
1403
  const secondaryColor = Color.fromSafe(normalizedAvatarDefinition.colors[1] || primaryColor.then(lighten(0.12)).then(saturate(0.16)));
@@ -1406,8 +1407,8 @@
1406
1407
  const highlightColor = Color.fromSafe(accentColor.then(lighten(0.22)).then(saturate(0.08)));
1407
1408
  const shadowColor = Color.fromSafe(primaryColor.then(darken(0.46)).then(saturate(0.14)));
1408
1409
  return {
1409
- background: backgroundColor.toHex(),
1410
- backgroundSecondary: backgroundSecondaryColor.toHex(),
1410
+ background: surface === 'transparent' ? 'transparent' : backgroundColor.toHex(),
1411
+ backgroundSecondary: surface === 'transparent' ? 'transparent' : backgroundSecondaryColor.toHex(),
1411
1412
  primary: primaryColor.toHex(),
1412
1413
  secondary: secondaryColor.toHex(),
1413
1414
  accent: accentColor.toHex(),
@@ -1426,6 +1427,9 @@
1426
1427
  * @private utility of the avatar rendering system
1427
1428
  */
1428
1429
  function drawAvatarFrame(context, size, palette) {
1430
+ if (palette.background === 'transparent' && palette.backgroundSecondary === 'transparent') {
1431
+ return;
1432
+ }
1429
1433
  const gradient = context.createLinearGradient(0, 0, size, size);
1430
1434
  gradient.addColorStop(0, palette.background);
1431
1435
  gradient.addColorStop(1, palette.backgroundSecondary);
@@ -1577,7 +1581,7 @@
1577
1581
  *
1578
1582
  * @private shared avatar contract
1579
1583
  */
1580
- const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus2';
1584
+ const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus3';
1581
1585
  /**
1582
1586
  * Resolve a base URL for relative images, preferring the provided base or browser location.
1583
1587
  *
@@ -11722,6 +11726,7 @@
11722
11726
  * Supported USE types:
11723
11727
  * - USE BROWSER: Enables the agent to use a web browser tool
11724
11728
  * - USE SEARCH ENGINE (future): Enables search engine access
11729
+ * - USE DEEPSEARCH: Enables deeper research-oriented search access
11725
11730
  * - USE FILE SYSTEM (future): Enables file system operations
11726
11731
  * - USE MCP (future): Enables MCP server connections
11727
11732
  *
@@ -11744,7 +11749,7 @@
11744
11749
  * Short one-line description of USE commitments.
11745
11750
  */
11746
11751
  get description() {
11747
- return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, etc.).';
11752
+ return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, DEEPSEARCH, etc.).';
11748
11753
  }
11749
11754
  /**
11750
11755
  * Icon for this commitment.
@@ -11765,6 +11770,7 @@
11765
11770
 
11766
11771
  - **USE BROWSER** - Enables the agent to use a web browser tool to access and retrieve information from the internet
11767
11772
  - **USE SEARCH ENGINE** (future) - Enables search engine access
11773
+ - **USE DEEPSEARCH** - Enables deeper research-oriented search access
11768
11774
  - **USE FILE SYSTEM** (future) - Enables file system operations
11769
11775
  - **USE MCP** (future) - Enables MCP server connections
11770
11776
 
@@ -11819,7 +11825,7 @@
11819
11825
  * Checks if this is a known USE type
11820
11826
  */
11821
11827
  isKnownUseType(useType) {
11822
- const knownTypes = ['BROWSER', 'SEARCH ENGINE', 'FILE SYSTEM', 'MCP'];
11828
+ const knownTypes = ['BROWSER', 'SEARCH ENGINE', 'DEEPSEARCH', 'FILE SYSTEM', 'MCP'];
11823
11829
  return knownTypes.includes(useType.toUpperCase());
11824
11830
  }
11825
11831
  }
@@ -11832,6 +11838,7 @@
11832
11838
  */
11833
11839
  const AGGREGATED_USE_COMMITMENT_TYPES = [
11834
11840
  'USE BROWSER',
11841
+ 'USE DEEPSEARCH',
11835
11842
  'USE SEARCH ENGINE',
11836
11843
  'USE TIME',
11837
11844
  ];
@@ -11911,6 +11918,15 @@
11911
11918
  - Do not tell the user you cannot search for information, YOU CAN.
11912
11919
  ${block(formatOptionalInstructionBlock('Search instructions', combinedAdditionalInstructions))}
11913
11920
  `);
11921
+ case 'USE DEEPSEARCH':
11922
+ return spacetrim.spaceTrim((block) => `
11923
+ Tool:
11924
+ - You have access to DeepSearch via the tool "deep_search".
11925
+ - Use it for broader research tasks that need multi-step investigation, comparison, or synthesis across multiple sources.
11926
+ - Prefer it over quick search when the user asks for a well-grounded brief, report, or deeper investigation.
11927
+ - Do not pretend you cannot research current information when this tool is available.
11928
+ ${block(formatOptionalInstructionBlock('DeepSearch instructions', combinedAdditionalInstructions))}
11929
+ `);
11914
11930
  }
11915
11931
  }
11916
11932
  /**
@@ -13460,6 +13476,207 @@
13460
13476
  }
13461
13477
  // Note: [💞] Ignore a discrepancy between file name and entity name
13462
13478
 
13479
+ /**
13480
+ * A search engine implementation that uses the SerpApi to fetch Google search results.
13481
+ *
13482
+ * @private <- TODO: !!!! Export via some package
13483
+ */
13484
+ class SerpSearchEngine {
13485
+ get title() {
13486
+ return 'SerpApi Search Engine';
13487
+ }
13488
+ get description() {
13489
+ return 'Search engine that uses SerpApi to fetch Google search results';
13490
+ }
13491
+ checkConfiguration() {
13492
+ if (!process.env.SERP_API_KEY) {
13493
+ throw new Error('SERP_API_KEY is not configured');
13494
+ }
13495
+ }
13496
+ async search(query, options = {}) {
13497
+ const apiKey = process.env.SERP_API_KEY;
13498
+ if (!apiKey) {
13499
+ throw new Error('SERP_API_KEY is not configured');
13500
+ }
13501
+ const url = new URL('https://serpapi.com/search');
13502
+ url.searchParams.set('api_key', apiKey);
13503
+ url.searchParams.set('engine', 'google');
13504
+ url.searchParams.set('q', query);
13505
+ for (const [key, value] of Object.entries(options)) {
13506
+ url.searchParams.set(key, String(value));
13507
+ }
13508
+ const response = await fetch(url.toString());
13509
+ if (!response.ok) {
13510
+ const body = await response.text();
13511
+ throw new Error(`SerpApi failed with status ${response.status}: ${response.statusText}\n${body}`);
13512
+ }
13513
+ const data = (await response.json());
13514
+ return (data.organic_results || []).map((item) => ({
13515
+ title: item.title,
13516
+ url: item.link,
13517
+ snippet: item.snippet || '',
13518
+ }));
13519
+ }
13520
+ }
13521
+
13522
+ /**
13523
+ * Creates one SERP-backed tool function used as a local fallback for search-like commitments.
13524
+ *
13525
+ * @param toolName - Technical tool name used for validation messages.
13526
+ * @param resultLabel - Human-readable label used in formatted results.
13527
+ * @returns Async tool function compatible with commitment tool registration.
13528
+ *
13529
+ * @private internal helper for search-like commitments
13530
+ */
13531
+ function createSerpSearchToolFunction(toolName, resultLabel) {
13532
+ return async (rawArgs) => {
13533
+ const { query, ...searchOptions } = rawArgs;
13534
+ if (typeof query !== 'string' || !query.trim()) {
13535
+ throw new Error(`${toolName} query is required`);
13536
+ }
13537
+ const searchEngine = new SerpSearchEngine();
13538
+ const results = await searchEngine.search(query, searchOptions);
13539
+ return spacetrim.spaceTrim((block) => `
13540
+ ${resultLabel} results for "${query}"${Object.keys(searchOptions).length === 0
13541
+ ? ''
13542
+ : ` with options ${JSON.stringify(searchOptions)}`}:
13543
+
13544
+ ${block(results
13545
+ .map((result) => spacetrim.spaceTrim(`
13546
+ - **${result.title}**
13547
+ ${result.url}
13548
+ ${result.snippet}
13549
+ `))
13550
+ .join('\n\n'))}
13551
+ `);
13552
+ };
13553
+ }
13554
+
13555
+ /**
13556
+ * USE DEEPSEARCH commitment definition
13557
+ *
13558
+ * The `USE DEEPSEARCH` commitment indicates that the agent should use a deeper research-oriented
13559
+ * search workflow instead of lightweight web search when it needs fresh information from the internet.
13560
+ *
13561
+ * The content following `USE DEEPSEARCH` is an arbitrary text that the agent should know
13562
+ * (e.g. search scope or research instructions).
13563
+ *
13564
+ * Example usage in agent source:
13565
+ *
13566
+ * ```book
13567
+ * USE DEEPSEARCH
13568
+ * USE DEEPSEARCH Compare official vendor documentation with independent benchmarks.
13569
+ * ```
13570
+ *
13571
+ * @private [🪔] Maybe export the commitments through some package
13572
+ */
13573
+ class UseDeepSearchCommitmentDefinition extends BaseCommitmentDefinition {
13574
+ constructor() {
13575
+ super('USE DEEPSEARCH');
13576
+ }
13577
+ get requiresContent() {
13578
+ return false;
13579
+ }
13580
+ /**
13581
+ * Short one-line description of USE DEEPSEARCH.
13582
+ */
13583
+ get description() {
13584
+ return 'Enable the agent to use DeepSearch for more thorough internet research.';
13585
+ }
13586
+ /**
13587
+ * Icon for this commitment.
13588
+ */
13589
+ get icon() {
13590
+ return '🔬';
13591
+ }
13592
+ /**
13593
+ * Markdown documentation for USE DEEPSEARCH commitment.
13594
+ */
13595
+ get documentation() {
13596
+ return spacetrim.spaceTrim(`
13597
+ # USE DEEPSEARCH
13598
+
13599
+ Enables the agent to use DeepSearch for broader, more thorough internet research than lightweight web search.
13600
+
13601
+ ## Key aspects
13602
+
13603
+ - The content following \`USE DEEPSEARCH\` is arbitrary guidance for the research workflow.
13604
+ - In Agents Server, the OpenAI Agents SDK runtime uses a nested deep-research agent for this tool.
13605
+ - Use this for investigations, comparisons, market scans, or other tasks that benefit from deeper synthesis.
13606
+ - Prefer regular \`USE SEARCH ENGINE\` when a quick factual lookup is enough.
13607
+
13608
+ ## Examples
13609
+
13610
+ \`\`\`book
13611
+ Due Diligence Researcher
13612
+
13613
+ GOAL Investigate vendors thoroughly before making recommendations.
13614
+ USE DEEPSEARCH Compare official sources with credible third-party analysis.
13615
+ RULE Cite the strongest supporting sources in the final answer.
13616
+ \`\`\`
13617
+
13618
+ \`\`\`book
13619
+ Market Analyst
13620
+
13621
+ GOAL Build concise but well-grounded research briefs.
13622
+ USE DEEPSEARCH Focus on recent public information and competing viewpoints.
13623
+ CLOSED
13624
+ \`\`\`
13625
+ `);
13626
+ }
13627
+ applyToAgentModelRequirements(requirements, content) {
13628
+ const existingTools = requirements.tools || [];
13629
+ const updatedTools = existingTools.some((tool) => tool.name === 'deep_search')
13630
+ ? existingTools
13631
+ : [
13632
+ ...existingTools,
13633
+ {
13634
+ name: 'deep_search',
13635
+ description: spacetrim.spaceTrim(`
13636
+ Research the internet deeply and synthesize a grounded answer.
13637
+ Use this tool for broader investigations, comparisons, and requests that need more than a quick search.
13638
+ `),
13639
+ parameters: {
13640
+ type: 'object',
13641
+ properties: {
13642
+ query: {
13643
+ type: 'string',
13644
+ description: 'The research question or investigation request.',
13645
+ },
13646
+ },
13647
+ required: ['query'],
13648
+ additionalProperties: false,
13649
+ },
13650
+ },
13651
+ ];
13652
+ return appendAggregatedUseCommitmentPlaceholder({
13653
+ ...requirements,
13654
+ tools: updatedTools,
13655
+ _metadata: {
13656
+ ...requirements._metadata,
13657
+ useDeepSearch: content || true,
13658
+ },
13659
+ }, this.type);
13660
+ }
13661
+ /**
13662
+ * Gets human-readable titles for tool functions provided by this commitment.
13663
+ */
13664
+ getToolTitles() {
13665
+ return {
13666
+ deep_search: 'DeepSearch',
13667
+ };
13668
+ }
13669
+ /**
13670
+ * Gets the local fallback implementation for the `deep_search` tool.
13671
+ */
13672
+ getToolFunctions() {
13673
+ return {
13674
+ deep_search: createSerpSearchToolFunction('deep_search', 'DeepSearch'),
13675
+ };
13676
+ }
13677
+ }
13678
+ // Note: [💞] Ignore a discrepancy between file name and entity name
13679
+
13463
13680
  /**
13464
13681
  * Lightweight email token matcher used for `USE EMAIL` first-line parsing.
13465
13682
  *
@@ -15691,49 +15908,6 @@
15691
15908
  }
15692
15909
  // Note: [💞] Ignore a discrepancy between file name and entity name
15693
15910
 
15694
- /**
15695
- * A search engine implementation that uses the SerpApi to fetch Google search results.
15696
- *
15697
- * @private <- TODO: !!!! Export via some package
15698
- */
15699
- class SerpSearchEngine {
15700
- get title() {
15701
- return 'SerpApi Search Engine';
15702
- }
15703
- get description() {
15704
- return 'Search engine that uses SerpApi to fetch Google search results';
15705
- }
15706
- checkConfiguration() {
15707
- if (!process.env.SERP_API_KEY) {
15708
- throw new Error('SERP_API_KEY is not configured');
15709
- }
15710
- }
15711
- async search(query, options = {}) {
15712
- const apiKey = process.env.SERP_API_KEY;
15713
- if (!apiKey) {
15714
- throw new Error('SERP_API_KEY is not configured');
15715
- }
15716
- const url = new URL('https://serpapi.com/search');
15717
- url.searchParams.set('api_key', apiKey);
15718
- url.searchParams.set('engine', 'google');
15719
- url.searchParams.set('q', query);
15720
- for (const [key, value] of Object.entries(options)) {
15721
- url.searchParams.set(key, String(value));
15722
- }
15723
- const response = await fetch(url.toString());
15724
- if (!response.ok) {
15725
- const body = await response.text();
15726
- throw new Error(`SerpApi failed with status ${response.status}: ${response.statusText}\n${body}`);
15727
- }
15728
- const data = (await response.json());
15729
- return (data.organic_results || []).map((item) => ({
15730
- title: item.title,
15731
- url: item.link,
15732
- snippet: item.snippet || '',
15733
- }));
15734
- }
15735
- }
15736
-
15737
15911
  /**
15738
15912
  * USE SEARCH ENGINE commitment definition
15739
15913
  *
@@ -15877,26 +16051,7 @@
15877
16051
  */
15878
16052
  getToolFunctions() {
15879
16053
  return {
15880
- async web_search(args) {
15881
- console.log('!!!! [Tool] web_search called', { args });
15882
- const { query, ...options } = args;
15883
- if (!query) {
15884
- throw new Error('Search query is required');
15885
- }
15886
- const searchEngine = new SerpSearchEngine();
15887
- const results = await searchEngine.search(query, options);
15888
- return spacetrim.spaceTrim((block) => `
15889
- Search results for "${query}"${Object.keys(options).length === 0 ? '' : ` with options ${JSON.stringify(options)}`}:
15890
-
15891
- ${block(results
15892
- .map((result) => spacetrim.spaceTrim(`
15893
- - **${result.title}**
15894
- ${result.url}
15895
- ${result.snippet}
15896
- `))
15897
- .join('\n\n'))}
15898
- `);
15899
- },
16054
+ web_search: createSerpSearchToolFunction('web_search', 'Search'),
15900
16055
  };
15901
16056
  }
15902
16057
  }
@@ -17542,6 +17697,7 @@
17542
17697
  new ClosedCommitmentDefinition(),
17543
17698
  new TeamCommitmentDefinition(),
17544
17699
  new UseBrowserCommitmentDefinition(),
17700
+ new UseDeepSearchCommitmentDefinition(),
17545
17701
  new UseSearchEngineCommitmentDefinition(),
17546
17702
  new UseSpawnCommitmentDefinition(),
17547
17703
  new UseTimeoutCommitmentDefinition(),
@@ -17853,6 +18009,11 @@
17853
18009
  label: 'Internet',
17854
18010
  iconName: 'Search',
17855
18011
  },
18012
+ 'USE DEEPSEARCH': {
18013
+ type: 'search-engine',
18014
+ label: 'DeepSearch',
18015
+ iconName: 'Search',
18016
+ },
17856
18017
  'USE TIME': {
17857
18018
  type: 'time',
17858
18019
  label: 'Time',
@@ -25728,6 +25889,320 @@
25728
25889
  }), children: jsxRuntime.jsx(SendIcon, { size: 25 }) })] }), speechRecognition && (jsxRuntime.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 && (jsxRuntime.jsxs("div", { className: styles$5.uploadProgress, children: [jsxRuntime.jsx("div", { className: styles$5.uploadProgressBar, children: jsxRuntime.jsx("div", { className: styles$5.uploadProgressFill }) }), jsxRuntime.jsx("span", { children: "Uploading files..." })] })), isDragOver && onFileUpload && (jsxRuntime.jsx("div", { className: styles$5.dragOverlay, children: jsxRuntime.jsxs("div", { className: styles$5.dragOverlayContent, children: [jsxRuntime.jsx(AttachmentIcon, { size: 48 }), jsxRuntime.jsx("span", { children: "Drop files here to upload" })] }) }))] }));
25729
25890
  }
25730
25891
 
25892
+ // Note: [💞] Ignore a discrepancy between file name and entity name
25893
+ /**
25894
+ * Maximum normalized eye travel used when the viewer moves across the viewport.
25895
+ *
25896
+ * @private utility of the avatar rendering system
25897
+ */
25898
+ const MAX_GAZE_OFFSET = 0.78;
25899
+ /**
25900
+ * Maximum normalized body lean used for subtle mantle response.
25901
+ *
25902
+ * @private utility of the avatar rendering system
25903
+ */
25904
+ const MAX_BODY_OFFSET = 0.28;
25905
+ /**
25906
+ * Smoothing window used while a live pointer or touch target is active.
25907
+ *
25908
+ * @private utility of the avatar rendering system
25909
+ */
25910
+ const ACTIVE_INTERACTION_SMOOTHING_MS = 90;
25911
+ /**
25912
+ * Slower smoothing window used when easing the avatar back to its idle state.
25913
+ *
25914
+ * @private utility of the avatar rendering system
25915
+ */
25916
+ const IDLE_INTERACTION_SMOOTHING_MS = 230;
25917
+ /**
25918
+ * Maximum frame delta allowed when smoothing interaction after tab stalls.
25919
+ *
25920
+ * @private utility of the avatar rendering system
25921
+ */
25922
+ const MAX_INTERACTION_FRAME_DELTA_MS = 64;
25923
+ /**
25924
+ * Extra damping used for the slower body lean compared with the quicker eye motion.
25925
+ *
25926
+ * @private utility of the avatar rendering system
25927
+ */
25928
+ const BODY_INTERACTION_SMOOTHING_MULTIPLIER = 1.2;
25929
+ /**
25930
+ * Stable zeroed interaction state used by non-interactive render paths.
25931
+ *
25932
+ * @private utility of the avatar rendering system
25933
+ */
25934
+ const IDLE_AVATAR_INTERACTION_STATE = {
25935
+ gazeX: 0,
25936
+ gazeY: 0,
25937
+ bodyOffsetX: 0,
25938
+ bodyOffsetY: 0,
25939
+ intensity: 0,
25940
+ isPointerActive: false,
25941
+ pointerType: 'idle',
25942
+ };
25943
+ /**
25944
+ * Creates one stable cache key from the meaningful avatar-definition fields.
25945
+ *
25946
+ * @param avatarDefinition Normalized or raw avatar definition.
25947
+ * @returns Stable cache key that ignores object identity churn.
25948
+ *
25949
+ * @private utility of the avatar rendering system
25950
+ */
25951
+ function createAvatarDefinitionKey(avatarDefinition) {
25952
+ const normalizedAvatarDefinition = normalizeAvatarDefinition(avatarDefinition);
25953
+ return [
25954
+ normalizedAvatarDefinition.agentName,
25955
+ normalizedAvatarDefinition.agentHash,
25956
+ normalizedAvatarDefinition.colors.join('|'),
25957
+ ].join('::');
25958
+ }
25959
+ /**
25960
+ * Returns the neutral interaction state used by static/server-side renders.
25961
+ *
25962
+ * @returns Zeroed interaction state.
25963
+ *
25964
+ * @private utility of the avatar rendering system
25965
+ */
25966
+ function createIdleAvatarInteractionState() {
25967
+ return IDLE_AVATAR_INTERACTION_STATE;
25968
+ }
25969
+ /**
25970
+ * Creates a fresh runtime state for the interactive animation loop.
25971
+ *
25972
+ * @returns Runtime interaction state with neutral values.
25973
+ *
25974
+ * @private utility of the avatar rendering system
25975
+ */
25976
+ function createAvatarInteractionRuntimeState() {
25977
+ return {
25978
+ ...IDLE_AVATAR_INTERACTION_STATE,
25979
+ lastFrameMs: null,
25980
+ };
25981
+ }
25982
+ /**
25983
+ * Converts the shared viewport pointer state into one avatar-local gaze target.
25984
+ *
25985
+ * @param avatarBounds Canvas bounds in viewport coordinates.
25986
+ * @param pointerSnapshot Latest shared pointer sample.
25987
+ * @returns Local target used to steer eyes and subtle body lean.
25988
+ *
25989
+ * @private utility of the avatar rendering system
25990
+ */
25991
+ function resolveAvatarPointerTarget(avatarBounds, pointerSnapshot) {
25992
+ if (!pointerSnapshot || !pointerSnapshot.isPointerActive) {
25993
+ return {
25994
+ ...IDLE_AVATAR_INTERACTION_STATE,
25995
+ };
25996
+ }
25997
+ const centerX = avatarBounds.left + avatarBounds.width / 2;
25998
+ const centerY = avatarBounds.top + avatarBounds.height / 2;
25999
+ const normalizedX = (pointerSnapshot.clientX - centerX) / Math.max(avatarBounds.width / 2, 1);
26000
+ const normalizedY = (pointerSnapshot.clientY - centerY) / Math.max(avatarBounds.height / 2, 1);
26001
+ const normalizedLength = Math.hypot(normalizedX, normalizedY) || 1;
26002
+ const clampedLength = Math.min(1, normalizedLength);
26003
+ const targetX = (normalizedX / normalizedLength) * clampedLength;
26004
+ const targetY = (normalizedY / normalizedLength) * clampedLength;
26005
+ const intensity = clamp01$1(clampedLength);
26006
+ return {
26007
+ gazeX: targetX * MAX_GAZE_OFFSET,
26008
+ gazeY: targetY * MAX_GAZE_OFFSET,
26009
+ bodyOffsetX: targetX * MAX_BODY_OFFSET,
26010
+ bodyOffsetY: targetY * MAX_BODY_OFFSET,
26011
+ intensity,
26012
+ isPointerActive: true,
26013
+ pointerType: pointerSnapshot.pointerType,
26014
+ };
26015
+ }
26016
+ /**
26017
+ * Advances the smoothed interaction state toward the latest pointer target.
26018
+ *
26019
+ * @param runtimeState Previous animation-frame state.
26020
+ * @param pointerTarget Latest local pointer target.
26021
+ * @param nowMs Current animation-frame timestamp.
26022
+ * @returns Next runtime state to keep in the animation loop.
26023
+ *
26024
+ * @private utility of the avatar rendering system
26025
+ */
26026
+ function stepAvatarInteractionRuntimeState(runtimeState, pointerTarget, nowMs) {
26027
+ const deltaMs = runtimeState.lastFrameMs === null
26028
+ ? 16
26029
+ : Math.min(MAX_INTERACTION_FRAME_DELTA_MS, Math.max(8, nowMs - runtimeState.lastFrameMs));
26030
+ const smoothingWindowMs = pointerTarget.isPointerActive
26031
+ ? ACTIVE_INTERACTION_SMOOTHING_MS
26032
+ : IDLE_INTERACTION_SMOOTHING_MS;
26033
+ // This exponential interpolation keeps the gaze response smooth regardless of fluctuating frame rates.
26034
+ return {
26035
+ gazeX: interpolateExponentially(runtimeState.gazeX, pointerTarget.gazeX, deltaMs, smoothingWindowMs),
26036
+ gazeY: interpolateExponentially(runtimeState.gazeY, pointerTarget.gazeY, deltaMs, smoothingWindowMs),
26037
+ bodyOffsetX: interpolateExponentially(runtimeState.bodyOffsetX, pointerTarget.bodyOffsetX, deltaMs, smoothingWindowMs * BODY_INTERACTION_SMOOTHING_MULTIPLIER),
26038
+ bodyOffsetY: interpolateExponentially(runtimeState.bodyOffsetY, pointerTarget.bodyOffsetY, deltaMs, smoothingWindowMs * BODY_INTERACTION_SMOOTHING_MULTIPLIER),
26039
+ intensity: interpolateExponentially(runtimeState.intensity, pointerTarget.intensity, deltaMs, smoothingWindowMs),
26040
+ isPointerActive: pointerTarget.isPointerActive,
26041
+ pointerType: pointerTarget.pointerType,
26042
+ lastFrameMs: nowMs,
26043
+ };
26044
+ }
26045
+ /**
26046
+ * Clamps a scalar into the inclusive `[0, 1]` range.
26047
+ *
26048
+ * @param value Arbitrary scalar.
26049
+ * @returns Clamped scalar.
26050
+ *
26051
+ * @private utility of the avatar rendering system
26052
+ */
26053
+ function clamp01$1(value) {
26054
+ return Math.min(1, Math.max(0, value));
26055
+ }
26056
+ /**
26057
+ * Interpolates between two values using frame-rate-independent exponential easing.
26058
+ *
26059
+ * @param currentValue Current smoothed value.
26060
+ * @param targetValue Target value.
26061
+ * @param deltaMs Elapsed milliseconds since the previous frame.
26062
+ * @param smoothingWindowMs Time constant controlling responsiveness.
26063
+ * @returns Smoothed next value.
26064
+ *
26065
+ * @private utility of the avatar rendering system
26066
+ */
26067
+ function interpolateExponentially(currentValue, targetValue, deltaMs, smoothingWindowMs) {
26068
+ const blend = 1 - Math.exp(-deltaMs / Math.max(1, smoothingWindowMs));
26069
+ return currentValue + (targetValue - currentValue) * blend;
26070
+ }
26071
+
26072
+ // Note: [💞] Ignore a discrepancy between file name and entity name
26073
+ /**
26074
+ * Active avatar instances currently consuming the shared pointer tracker.
26075
+ *
26076
+ * @private utility of the avatar rendering system
26077
+ */
26078
+ let avatarPointerTrackingConsumerCount = 0;
26079
+ /**
26080
+ * Latest shared viewport pointer sample.
26081
+ *
26082
+ * @private utility of the avatar rendering system
26083
+ */
26084
+ let currentAvatarPointerSnapshot = null;
26085
+ /**
26086
+ * Cleanup function for the lazily attached global listeners.
26087
+ *
26088
+ * @private utility of the avatar rendering system
26089
+ */
26090
+ let releaseAvatarPointerTrackingListeners = null;
26091
+ /**
26092
+ * Starts the shared pointer tracker and returns a disposer for the caller.
26093
+ *
26094
+ * @returns Cleanup function that releases one consumer.
26095
+ *
26096
+ * @private utility of the avatar rendering system
26097
+ */
26098
+ function retainAvatarPointerTracking() {
26099
+ avatarPointerTrackingConsumerCount++;
26100
+ if (avatarPointerTrackingConsumerCount === 1) {
26101
+ releaseAvatarPointerTrackingListeners = attachAvatarPointerTrackingListeners();
26102
+ }
26103
+ return () => {
26104
+ avatarPointerTrackingConsumerCount = Math.max(0, avatarPointerTrackingConsumerCount - 1);
26105
+ if (avatarPointerTrackingConsumerCount === 0) {
26106
+ currentAvatarPointerSnapshot = null;
26107
+ releaseAvatarPointerTrackingListeners === null || releaseAvatarPointerTrackingListeners === void 0 ? void 0 : releaseAvatarPointerTrackingListeners();
26108
+ releaseAvatarPointerTrackingListeners = null;
26109
+ }
26110
+ };
26111
+ }
26112
+ /**
26113
+ * Returns the latest shared viewport pointer sample when available.
26114
+ *
26115
+ * @returns Shared pointer snapshot or `null`.
26116
+ *
26117
+ * @private utility of the avatar rendering system
26118
+ */
26119
+ function getAvatarPointerSnapshot() {
26120
+ return currentAvatarPointerSnapshot;
26121
+ }
26122
+ /**
26123
+ * Attaches the global pointer/touch listeners used by all live avatar canvases.
26124
+ *
26125
+ * @returns Cleanup function for the attached listeners.
26126
+ *
26127
+ * @private utility of the avatar rendering system
26128
+ */
26129
+ function attachAvatarPointerTrackingListeners() {
26130
+ if (typeof window === 'undefined') {
26131
+ return () => undefined;
26132
+ }
26133
+ const clearPointerSnapshot = () => {
26134
+ currentAvatarPointerSnapshot = null;
26135
+ };
26136
+ const updatePointerSnapshot = (clientX, clientY, pointerType) => {
26137
+ currentAvatarPointerSnapshot = {
26138
+ clientX,
26139
+ clientY,
26140
+ isPointerActive: true,
26141
+ pointerType,
26142
+ };
26143
+ };
26144
+ const handlePointerMove = (event) => {
26145
+ updatePointerSnapshot(event.clientX, event.clientY, normalizeAvatarPointerType(event.pointerType));
26146
+ };
26147
+ const handlePointerDown = (event) => {
26148
+ updatePointerSnapshot(event.clientX, event.clientY, normalizeAvatarPointerType(event.pointerType));
26149
+ };
26150
+ const handlePointerUp = (event) => {
26151
+ if (normalizeAvatarPointerType(event.pointerType) !== 'mouse') {
26152
+ clearPointerSnapshot();
26153
+ }
26154
+ };
26155
+ const handleMouseOut = (event) => {
26156
+ if (!event.relatedTarget) {
26157
+ clearPointerSnapshot();
26158
+ }
26159
+ };
26160
+ const handleTouchEvent = (event) => {
26161
+ const touch = event.touches[0] || event.changedTouches[0];
26162
+ if (!touch) {
26163
+ clearPointerSnapshot();
26164
+ return;
26165
+ }
26166
+ updatePointerSnapshot(touch.clientX, touch.clientY, 'touch');
26167
+ };
26168
+ window.addEventListener('pointermove', handlePointerMove, { passive: true });
26169
+ window.addEventListener('pointerdown', handlePointerDown, { passive: true });
26170
+ window.addEventListener('pointerup', handlePointerUp, { passive: true });
26171
+ window.addEventListener('pointercancel', clearPointerSnapshot, { passive: true });
26172
+ window.addEventListener('mouseout', handleMouseOut, { passive: true });
26173
+ window.addEventListener('blur', clearPointerSnapshot);
26174
+ window.addEventListener('touchstart', handleTouchEvent, { passive: true });
26175
+ window.addEventListener('touchmove', handleTouchEvent, { passive: true });
26176
+ window.addEventListener('touchend', clearPointerSnapshot, { passive: true });
26177
+ window.addEventListener('touchcancel', clearPointerSnapshot, { passive: true });
26178
+ return () => {
26179
+ window.removeEventListener('pointermove', handlePointerMove);
26180
+ window.removeEventListener('pointerdown', handlePointerDown);
26181
+ window.removeEventListener('pointerup', handlePointerUp);
26182
+ window.removeEventListener('pointercancel', clearPointerSnapshot);
26183
+ window.removeEventListener('mouseout', handleMouseOut);
26184
+ window.removeEventListener('blur', clearPointerSnapshot);
26185
+ window.removeEventListener('touchstart', handleTouchEvent);
26186
+ window.removeEventListener('touchmove', handleTouchEvent);
26187
+ window.removeEventListener('touchend', clearPointerSnapshot);
26188
+ window.removeEventListener('touchcancel', clearPointerSnapshot);
26189
+ };
26190
+ }
26191
+ /**
26192
+ * Normalizes browser pointer-type strings into the shared avatar contract.
26193
+ *
26194
+ * @param pointerType Raw browser pointer type.
26195
+ * @returns Shared pointer type.
26196
+ *
26197
+ * @private utility of the avatar rendering system
26198
+ */
26199
+ function normalizeAvatarPointerType(pointerType) {
26200
+ if (pointerType === 'touch' || pointerType === 'pen') {
26201
+ return pointerType;
26202
+ }
26203
+ return 'mouse';
26204
+ }
26205
+
25731
26206
  /* eslint-disable no-magic-numbers */
25732
26207
  /**
25733
26208
  * Builds a smoothly morphing octopus-like silhouette from deterministic parameters.
@@ -25804,8 +26279,9 @@
25804
26279
  * @private shared geometry helper of `octopus3AvatarVisual` and `asciiOctopusAvatarVisual`
25805
26280
  */
25806
26281
  function createOrganicOctopusTentacleShapes(options) {
25807
- const { size, centerX, centerY, bodyRadius, horizontalStretch, tentacleCount, shapePhase, createRandom, timeMs, saltPrefix, } = options;
26282
+ const { size, centerX, centerY, bodyRadius, horizontalStretch, tentacleCount, shapePhase, createRandom, timeMs, saltPrefix, bodyPoints } = options;
25808
26283
  const baseY = centerY + bodyRadius * 0.74;
26284
+ const lowerBodyAnchorPoints = bodyPoints ? resolveTentacleBodyAnchorPoints(bodyPoints, centerY + bodyRadius * 0.04) : null;
25809
26285
  return Array.from({ length: tentacleCount }, (_, tentacleIndex) => {
25810
26286
  const tentacleRandom = createRandom(`${saltPrefix}-tentacle-${tentacleIndex}`);
25811
26287
  const spreadProgress = tentacleCount === 1 ? 0.5 : tentacleIndex / (tentacleCount - 1);
@@ -25817,10 +26293,21 @@
25817
26293
  const curlDirection = centeredProgress === 0 ? (tentacleRandom() < 0.5 ? -1 : 1) : Math.sign(centeredProgress);
25818
26294
  const lateralReach = centeredProgress * size * (0.1 + tentacleRandom() * 0.1) + temporalSway;
25819
26295
  const tipReach = curlDirection * size * (0.025 + tentacleRandom() * 0.07);
25820
- const startPoint = {
25821
- x: centerX + centeredProgress * bodyRadius * horizontalStretch * 1.52,
25822
- y: baseY + Math.abs(centeredProgress) * size * 0.012 + tentacleRandom() * size * 0.01,
25823
- };
26296
+ const startYOffset = Math.abs(centeredProgress) * size * 0.012 + tentacleRandom() * size * 0.01;
26297
+ const startPoint = lowerBodyAnchorPoints && lowerBodyAnchorPoints.length >= 2
26298
+ ? createInsetTentacleStartPoint({
26299
+ bodyPoints: lowerBodyAnchorPoints,
26300
+ anchorProgress: spreadProgress,
26301
+ centerX,
26302
+ centerY,
26303
+ bodyRadius,
26304
+ centeredProgress,
26305
+ startYOffset,
26306
+ })
26307
+ : {
26308
+ x: centerX + centeredProgress * bodyRadius * horizontalStretch * 1.52,
26309
+ y: baseY + startYOffset,
26310
+ };
25824
26311
  const controlPointOne = {
25825
26312
  x: startPoint.x + centeredProgress * size * 0.045 + temporalSway * 0.4,
25826
26313
  y: startPoint.y + flowLength * (0.21 + tentacleRandom() * 0.08),
@@ -25850,6 +26337,67 @@
25850
26337
  };
25851
26338
  });
25852
26339
  }
26340
+ /**
26341
+ * Narrows the body contour to lower anchor points that can safely host tentacle roots.
26342
+ *
26343
+ * @param bodyPoints Generated closed-loop body points.
26344
+ * @param lowerBodyThresholdY Minimum Y coordinate considered part of the lower mantle.
26345
+ * @returns Body points sorted from left to right across the lower silhouette.
26346
+ *
26347
+ * @private shared geometry helper of `octopus3AvatarVisual`
26348
+ */
26349
+ function resolveTentacleBodyAnchorPoints(bodyPoints, lowerBodyThresholdY) {
26350
+ const lowerBodyPoints = bodyPoints.filter((bodyPoint) => bodyPoint.y >= lowerBodyThresholdY).sort((leftPoint, rightPoint) => leftPoint.x - rightPoint.x);
26351
+ if (lowerBodyPoints.length >= 2) {
26352
+ return lowerBodyPoints;
26353
+ }
26354
+ return [...bodyPoints].sort((leftPoint, rightPoint) => leftPoint.x - rightPoint.x);
26355
+ }
26356
+ /**
26357
+ * Resolves one tentacle root from the provided lower body contour and nudges it inside the mantle.
26358
+ *
26359
+ * @param options Tentacle anchor options.
26360
+ * @returns Tentacle start point safely embedded inside the body silhouette.
26361
+ *
26362
+ * @private shared geometry helper of `octopus3AvatarVisual`
26363
+ */
26364
+ function createInsetTentacleStartPoint(options) {
26365
+ const { bodyPoints, anchorProgress, centerX, centerY, bodyRadius, centeredProgress, startYOffset } = options;
26366
+ const clampedAnchorProgress = Math.min(0.94, Math.max(0.06, anchorProgress));
26367
+ const bodyAnchorPoint = interpolatePointAlongTentacleAnchors(bodyPoints, clampedAnchorProgress);
26368
+ const inwardX = centerX - bodyAnchorPoint.x;
26369
+ const inwardY = centerY + bodyRadius * 0.08 - bodyAnchorPoint.y;
26370
+ const inwardLength = Math.hypot(inwardX, inwardY) || 1;
26371
+ const insetDistance = bodyRadius * (0.12 + Math.abs(centeredProgress) * 0.05) + startYOffset * 0.32;
26372
+ return {
26373
+ x: bodyAnchorPoint.x + (inwardX / inwardLength) * insetDistance,
26374
+ y: bodyAnchorPoint.y + (inwardY / inwardLength) * insetDistance,
26375
+ };
26376
+ }
26377
+ /**
26378
+ * Interpolates one left-to-right anchor point along the prepared lower body contour.
26379
+ *
26380
+ * @param bodyPoints Lower body contour points sorted from left to right.
26381
+ * @param progress Interpolation progress in the range `[0, 1]`.
26382
+ * @returns Interpolated anchor point.
26383
+ *
26384
+ * @private shared geometry helper of `octopus3AvatarVisual`
26385
+ */
26386
+ function interpolatePointAlongTentacleAnchors(bodyPoints, progress) {
26387
+ if (bodyPoints.length === 1) {
26388
+ return bodyPoints[0];
26389
+ }
26390
+ const anchorIndex = progress * (bodyPoints.length - 1);
26391
+ const startIndex = Math.floor(anchorIndex);
26392
+ const endIndex = Math.min(bodyPoints.length - 1, startIndex + 1);
26393
+ const blend = anchorIndex - startIndex;
26394
+ const startPoint = bodyPoints[startIndex];
26395
+ const endPoint = bodyPoints[endIndex];
26396
+ return {
26397
+ x: startPoint.x + (endPoint.x - startPoint.x) * blend,
26398
+ y: startPoint.y + (endPoint.y - startPoint.y) * blend,
26399
+ };
26400
+ }
25853
26401
  /**
25854
26402
  * Samples the cubic tentacle centerline and offsets normals to build a filled ribbon.
25855
26403
  *
@@ -25879,6 +26427,26 @@
25879
26427
  };
25880
26428
  });
25881
26429
  }
26430
+ /**
26431
+ * Resolves smooth pupil offsets that blend autonomous idle drift with live viewer tracking.
26432
+ *
26433
+ * @param options Eye motion options.
26434
+ * @returns Resolved pupil offsets.
26435
+ *
26436
+ * @private shared geometry helper of octopus avatar visuals
26437
+ */
26438
+ function resolveOrganicEyeMotion(options) {
26439
+ const { radiusX, radiusY, timeMs, phase, interaction, autonomousDriftRatioX = 0.12, autonomousDriftRatioY = 0.08, } = options;
26440
+ const autonomousOffsetX = Math.sin(timeMs / 1280 + phase) * radiusX * autonomousDriftRatioX;
26441
+ const autonomousOffsetY = Math.cos(timeMs / 940 + phase) * radiusY * autonomousDriftRatioY;
26442
+ const interactionBlend = Math.min(1, interaction.intensity * 0.9);
26443
+ return {
26444
+ pupilOffsetX: autonomousOffsetX * (1 - interactionBlend) +
26445
+ interaction.gazeX * radiusX * (0.18 + interactionBlend * 0.18),
26446
+ pupilOffsetY: autonomousOffsetY * (1 - interactionBlend) +
26447
+ interaction.gazeY * radiusY * (0.16 + interactionBlend * 0.16),
26448
+ };
26449
+ }
25882
26450
  /**
25883
26451
  * Samples one point on a cubic Bezier curve.
25884
26452
  *
@@ -25932,13 +26500,14 @@
25932
26500
  const asciiOctopusAvatarVisual = {
25933
26501
  id: 'ascii-octopus',
25934
26502
  title: 'AsciiOctopus',
25935
- description: 'Morphing alien octopus translated into animated ASCII glyphs with deterministic blob and tentacle geometry.',
26503
+ description: 'Morphing alien octopus translated into animated ASCII glyphs with responsive eyes and seeded geometry.',
25936
26504
  isAnimated: true,
25937
- render({ context, size, palette, createRandom, timeMs }) {
26505
+ supportsPointerTracking: true,
26506
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
25938
26507
  const gridRandom = createRandom('ascii-octopus-grid');
25939
26508
  const staticRandom = createRandom('ascii-octopus-static');
25940
26509
  const gridMetrics = createAsciiGridMetrics(size, gridRandom);
25941
- const layout = createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom);
26510
+ const layout = createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom, interaction);
25942
26511
  drawAvatarFrame(context, size, palette);
25943
26512
  drawAsciiBackdrop(context, size, palette, layout, timeMs);
25944
26513
  context.save();
@@ -26012,7 +26581,8 @@
26012
26581
  */
26013
26582
  function resolveAsciiGlyph(options) {
26014
26583
  const { point, layout, palette, cellWidth, cellHeight, noise, timeMs } = options;
26015
- const eyeGlyphDescriptor = resolveEyeGlyph(point, layout.leftEye, palette, timeMs) || resolveEyeGlyph(point, layout.rightEye, palette, timeMs);
26584
+ const eyeGlyphDescriptor = resolveEyeGlyph(point, layout.leftEye, layout.interaction, palette, timeMs) ||
26585
+ resolveEyeGlyph(point, layout.rightEye, layout.interaction, palette, timeMs);
26016
26586
  if (eyeGlyphDescriptor) {
26017
26587
  return eyeGlyphDescriptor;
26018
26588
  }
@@ -26057,9 +26627,14 @@
26057
26627
  *
26058
26628
  * @private helper of `asciiOctopusAvatarVisual`
26059
26629
  */
26060
- function resolveEyeGlyph(point, eyeFeature, palette, timeMs) {
26061
- const pupilOffsetX = Math.sin(timeMs / 1280 + eyeFeature.phase) * eyeFeature.radiusX * 0.12;
26062
- const pupilOffsetY = Math.cos(timeMs / 940 + eyeFeature.phase) * eyeFeature.radiusY * 0.08;
26630
+ function resolveEyeGlyph(point, eyeFeature, interaction, palette, timeMs) {
26631
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
26632
+ radiusX: eyeFeature.radiusX,
26633
+ radiusY: eyeFeature.radiusY,
26634
+ timeMs,
26635
+ phase: eyeFeature.phase,
26636
+ interaction,
26637
+ });
26063
26638
  const scleraDistance = measureRotatedEllipseDistance(point, eyeFeature.centerX, eyeFeature.centerY, eyeFeature.radiusX, eyeFeature.radiusY, eyeFeature.rotation);
26064
26639
  if (scleraDistance > 1.08) {
26065
26640
  return null;
@@ -26233,9 +26808,9 @@
26233
26808
  *
26234
26809
  * @private helper of `asciiOctopusAvatarVisual`
26235
26810
  */
26236
- function createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom) {
26237
- const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02);
26238
- const centerY = size * (0.41 + staticRandom() * 0.05);
26811
+ function createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom, interaction) {
26812
+ const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02) + interaction.bodyOffsetX * size * 0.05;
26813
+ const centerY = size * (0.41 + staticRandom() * 0.05) + interaction.bodyOffsetY * size * 0.035;
26239
26814
  const bodyRadius = size * (0.195 + staticRandom() * 0.05);
26240
26815
  const horizontalStretch = 1.08 + staticRandom() * 0.22;
26241
26816
  const verticalStretch = 0.88 + staticRandom() * 0.14;
@@ -26275,6 +26850,7 @@
26275
26850
  createRandom,
26276
26851
  timeMs,
26277
26852
  saltPrefix: 'ascii-octopus',
26853
+ bodyPoints,
26278
26854
  });
26279
26855
  const sampledTentacles = tentacleShapes.map(sampleOrganicTentacleRibbonPoints);
26280
26856
  const leftEye = {
@@ -26295,7 +26871,7 @@
26295
26871
  };
26296
26872
  const mouthPoints = sampleQuadraticBezierPoints({ x: centerX - size * 0.074, y: centerY + size * 0.092 }, {
26297
26873
  x: centerX,
26298
- y: centerY + size * (0.142 + Math.sin(timeMs / 620 + shapePhase) * 0.016),
26874
+ y: centerY + size * (0.142 + Math.sin(timeMs / 620 + shapePhase) * 0.016) + interaction.gazeY * size * 0.012,
26299
26875
  }, { x: centerX + size * 0.074, y: centerY + size * 0.092 }, 12);
26300
26876
  let leftBound = Number.POSITIVE_INFINITY;
26301
26877
  let rightBound = Number.NEGATIVE_INFINITY;
@@ -26321,6 +26897,7 @@
26321
26897
  bodyRadius,
26322
26898
  horizontalStretch,
26323
26899
  shapePhase,
26900
+ interaction,
26324
26901
  bodyPoints,
26325
26902
  sampledTentacles,
26326
26903
  leftEye,
@@ -27041,15 +27618,16 @@
27041
27618
  const octopusAvatarVisual = {
27042
27619
  id: 'octopus',
27043
27620
  title: 'Octopus',
27044
- description: 'Playful underwater mascot with animated tentacles, bubbles, and seed-based markings.',
27621
+ description: 'Playful underwater mascot with cursor-following eyes, animated tentacles, bubbles, and seeded markings.',
27045
27622
  isAnimated: true,
27046
- render({ context, size, palette, createRandom, timeMs }) {
27623
+ supportsPointerTracking: true,
27624
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
27047
27625
  const staticRandom = createRandom('octopus-static');
27048
27626
  const bubbleRandom = createRandom('octopus-bubbles');
27049
27627
  const bubbleCount = 8;
27050
27628
  const bubbleRadiusBase = size * 0.02;
27051
- const centerX = size * 0.5;
27052
- const centerY = size * 0.42;
27629
+ const centerX = size * 0.5 + interaction.bodyOffsetX * size * 0.035;
27630
+ const centerY = size * 0.42 + interaction.bodyOffsetY * size * 0.024;
27053
27631
  const headRadius = size * (0.19 + staticRandom() * 0.03);
27054
27632
  const mantleHeight = headRadius * 1.18;
27055
27633
  const tentacleLength = size * (0.18 + staticRandom() * 0.06);
@@ -27129,11 +27707,9 @@
27129
27707
  }
27130
27708
  const eyeOffsetX = headRadius * 0.42;
27131
27709
  const eyeY = centerY + headRadius * 0.04;
27132
- const pupilDriftX = Math.sin(timeMs / 850) * headRadius * 0.05;
27133
- const pupilDriftY = Math.cos(timeMs / 930) * headRadius * 0.03;
27134
27710
  const eyeRadius = headRadius * 0.22;
27135
- drawEye(context, centerX - eyeOffsetX, eyeY, eyeRadius, palette, pupilDriftX, pupilDriftY);
27136
- drawEye(context, centerX + eyeOffsetX, eyeY, eyeRadius, palette, pupilDriftX, pupilDriftY);
27711
+ drawEye(context, centerX - eyeOffsetX, eyeY, eyeRadius, palette, timeMs, interaction, 0);
27712
+ drawEye(context, centerX + eyeOffsetX, eyeY, eyeRadius, palette, timeMs, interaction, Math.PI / 5);
27137
27713
  context.beginPath();
27138
27714
  context.arc(centerX - headRadius * 0.28, centerY + headRadius * 0.3, headRadius * 0.12, 0, Math.PI * 2);
27139
27715
  context.arc(centerX + headRadius * 0.28, centerY + headRadius * 0.3, headRadius * 0.12, 0, Math.PI * 2);
@@ -27156,22 +27732,32 @@
27156
27732
  * @param centerY Eye center Y coordinate.
27157
27733
  * @param radius Eye radius.
27158
27734
  * @param palette Derived avatar palette.
27159
- * @param pupilDriftX Horizontal pupil drift.
27160
- * @param pupilDriftY Vertical pupil drift.
27735
+ * @param timeMs Current animation time in milliseconds.
27736
+ * @param interaction Smoothed avatar interaction state.
27737
+ * @param phase Seed-based phase offset.
27161
27738
  *
27162
27739
  * @private helper of `octopusAvatarVisual`
27163
27740
  */
27164
- function drawEye(context, centerX, centerY, radius, palette, pupilDriftX, pupilDriftY) {
27741
+ function drawEye(context, centerX, centerY, radius, palette, timeMs, interaction, phase) {
27742
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
27743
+ radiusX: radius,
27744
+ radiusY: radius,
27745
+ timeMs,
27746
+ phase,
27747
+ interaction,
27748
+ autonomousDriftRatioX: 0.05,
27749
+ autonomousDriftRatioY: 0.03,
27750
+ });
27165
27751
  context.beginPath();
27166
27752
  context.arc(centerX, centerY, radius, 0, Math.PI * 2);
27167
27753
  context.fillStyle = '#ffffff';
27168
27754
  context.fill();
27169
27755
  context.beginPath();
27170
- context.arc(centerX + pupilDriftX, centerY + pupilDriftY, radius * 0.45, 0, Math.PI * 2);
27756
+ context.arc(centerX + pupilOffsetX, centerY + pupilOffsetY, radius * 0.45, 0, Math.PI * 2);
27171
27757
  context.fillStyle = palette.ink;
27172
27758
  context.fill();
27173
27759
  context.beginPath();
27174
- context.arc(centerX + pupilDriftX - radius * 0.12, centerY + pupilDriftY - radius * 0.12, radius * 0.15, 0, Math.PI * 2);
27760
+ context.arc(centerX + pupilOffsetX - radius * 0.12, centerY + pupilOffsetY - radius * 0.12, radius * 0.15, 0, Math.PI * 2);
27175
27761
  context.fillStyle = '#ffffff';
27176
27762
  context.fill();
27177
27763
  context.beginPath();
@@ -27190,12 +27776,13 @@
27190
27776
  const octopus2AvatarVisual = {
27191
27777
  id: 'octopus2',
27192
27778
  title: 'Octopus2',
27193
- description: 'Organic alien octopus rendered as one continuously morphing blob with luminous eyes and soft inner motion.',
27779
+ description: 'Organic alien octopus rendered as one continuously morphing blob with responsive luminous eyes.',
27194
27780
  isAnimated: true,
27195
- render({ context, size, palette, createRandom, timeMs }) {
27781
+ supportsPointerTracking: true,
27782
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
27196
27783
  const staticRandom = createRandom('octopus2-static');
27197
- const centerX = size * 0.5;
27198
- const centerY = size * (0.48 + staticRandom() * 0.03);
27784
+ const centerX = size * 0.5 + interaction.bodyOffsetX * size * 0.042;
27785
+ const centerY = size * (0.48 + staticRandom() * 0.03) + interaction.bodyOffsetY * size * 0.028;
27199
27786
  const bodyRadius = size * (0.25 + staticRandom() * 0.035);
27200
27787
  const horizontalStretch = 1.04 + staticRandom() * 0.16;
27201
27788
  const verticalStretch = 0.94 + staticRandom() * 0.12;
@@ -27266,11 +27853,11 @@
27266
27853
  const eyeCenterY = centerY - size * 0.02;
27267
27854
  const eyeRadiusX = size * 0.072;
27268
27855
  const eyeRadiusY = size * 0.086;
27269
- drawAlienEye(context, centerX - eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase);
27270
- drawAlienEye(context, centerX + eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase + Math.PI / 5);
27856
+ drawAlienEye(context, centerX - eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase, interaction);
27857
+ drawAlienEye(context, centerX + eyeOffsetX, eyeCenterY, eyeRadiusX, eyeRadiusY, palette, timeMs, shapePhase + Math.PI / 5, interaction);
27271
27858
  context.beginPath();
27272
27859
  context.moveTo(centerX - size * 0.08, centerY + size * 0.12);
27273
- context.quadraticCurveTo(centerX, centerY + size * (0.175 + Math.sin(timeMs / 520 + shapePhase) * 0.012), centerX + size * 0.08, centerY + size * 0.12);
27860
+ 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);
27274
27861
  context.strokeStyle = `${palette.ink}b3`;
27275
27862
  context.lineWidth = size * 0.013;
27276
27863
  context.lineCap = 'round';
@@ -27348,12 +27935,19 @@
27348
27935
  * @param palette Derived avatar palette.
27349
27936
  * @param timeMs Current animation time in milliseconds.
27350
27937
  * @param phase Seed-based animation phase.
27938
+ * @param interaction Smoothed avatar interaction state.
27351
27939
  *
27352
27940
  * @private helper of `octopus2AvatarVisual`
27353
27941
  */
27354
- function drawAlienEye(context, centerX, centerY, radiusX, radiusY, palette, timeMs, phase) {
27355
- const pupilOffsetX = Math.sin(timeMs / 1300 + phase) * radiusX * 0.12;
27356
- const pupilOffsetY = Math.cos(timeMs / 970 + phase) * radiusY * 0.1;
27942
+ function drawAlienEye(context, centerX, centerY, radiusX, radiusY, palette, timeMs, phase, interaction) {
27943
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
27944
+ radiusX,
27945
+ radiusY,
27946
+ timeMs,
27947
+ phase,
27948
+ interaction,
27949
+ autonomousDriftRatioY: 0.1,
27950
+ });
27357
27951
  context.save();
27358
27952
  context.beginPath();
27359
27953
  context.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
@@ -27393,12 +27987,13 @@
27393
27987
  const octopus3AvatarVisual = {
27394
27988
  id: 'octopus3',
27395
27989
  title: 'Octopus3',
27396
- description: 'Gelatinous alien octopus with a morphing mantle, visible ribbon tentacles, and seeded facial features.',
27990
+ description: 'Gelatinous alien octopus with a morphing mantle, responsive eyes, and visible ribbon tentacles.',
27397
27991
  isAnimated: true,
27398
- render({ context, size, palette, createRandom, timeMs }) {
27992
+ supportsPointerTracking: true,
27993
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
27399
27994
  const staticRandom = createRandom('octopus3-static');
27400
- const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02);
27401
- const centerY = size * (0.41 + staticRandom() * 0.05);
27995
+ const centerX = size * (0.5 + (staticRandom() - 0.5) * 0.02) + interaction.bodyOffsetX * size * 0.05;
27996
+ const centerY = size * (0.41 + staticRandom() * 0.05) + interaction.bodyOffsetY * size * 0.035;
27402
27997
  const bodyRadius = size * (0.2 + staticRandom() * 0.045);
27403
27998
  const horizontalStretch = 1.08 + staticRandom() * 0.22;
27404
27999
  const verticalStretch = 0.9 + staticRandom() * 0.12;
@@ -27438,6 +28033,7 @@
27438
28033
  createRandom,
27439
28034
  timeMs,
27440
28035
  saltPrefix: 'octopus3',
28036
+ bodyPoints,
27441
28037
  });
27442
28038
  drawAvatarFrame(context, size, palette);
27443
28039
  drawOctopus3Atmosphere(context, size, palette, centerX, centerY, timeMs, shapePhase);
@@ -27483,11 +28079,11 @@
27483
28079
  context.ellipse(centerX, centerY - size * 0.14, size * 0.18, size * 0.062, 0, Math.PI, Math.PI * 2);
27484
28080
  context.fillStyle = `${palette.highlight}3d`;
27485
28081
  context.fill();
27486
- drawSeededEye(context, centerX - eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase);
27487
- drawSeededEye(context, centerX + eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase + Math.PI / 4);
28082
+ drawSeededEye(context, centerX - eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase, interaction);
28083
+ drawSeededEye(context, centerX + eyeSpacing, centerY - size * 0.01, eyeRadiusX, eyeRadiusY, (staticRandom() - 0.5) * 0.28, palette, timeMs, shapePhase + Math.PI / 4, interaction);
27488
28084
  context.beginPath();
27489
28085
  context.moveTo(centerX - size * 0.07, centerY + size * 0.09);
27490
- context.quadraticCurveTo(centerX, centerY + size * (0.14 + Math.sin(timeMs / 620 + shapePhase) * 0.016), centerX + size * 0.07, centerY + size * 0.09);
28086
+ 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);
27491
28087
  context.strokeStyle = `${palette.ink}b3`;
27492
28088
  context.lineWidth = size * 0.012;
27493
28089
  context.lineCap = 'round';
@@ -27675,12 +28271,18 @@
27675
28271
  * @param palette Derived avatar palette.
27676
28272
  * @param timeMs Current animation time in milliseconds.
27677
28273
  * @param phase Seed-based animation phase.
28274
+ * @param interaction Smoothed avatar interaction state.
27678
28275
  *
27679
28276
  * @private helper of `octopus3AvatarVisual`
27680
28277
  */
27681
- function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, palette, timeMs, phase) {
27682
- const pupilOffsetX = Math.sin(timeMs / 1280 + phase) * radiusX * 0.12;
27683
- const pupilOffsetY = Math.cos(timeMs / 940 + phase) * radiusY * 0.08;
28278
+ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, palette, timeMs, phase, interaction) {
28279
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
28280
+ radiusX,
28281
+ radiusY,
28282
+ timeMs,
28283
+ phase,
28284
+ interaction,
28285
+ });
27684
28286
  context.save();
27685
28287
  context.translate(centerX, centerY);
27686
28288
  context.rotate(rotation);
@@ -27716,7 +28318,7 @@
27716
28318
  context.stroke();
27717
28319
  context.beginPath();
27718
28320
  context.moveTo(-radiusX * 0.88, -radiusY * 0.08);
27719
- context.quadraticCurveTo(0, -radiusY * 0.9, radiusX * 0.88, -radiusY * 0.08);
28321
+ context.quadraticCurveTo(0, -radiusY * (0.9 - interaction.gazeY * 0.16 + interaction.intensity * 0.08), radiusX * 0.88, -radiusY * 0.08);
27720
28322
  context.strokeStyle = `${palette.shadow}73`;
27721
28323
  context.lineWidth = radiusX * 0.16;
27722
28324
  context.lineCap = 'round';
@@ -27864,6 +28466,7 @@
27864
28466
  function renderAvatarVisual(options) {
27865
28467
  const normalizedAvatarDefinition = normalizeAvatarDefinition(options.avatarDefinition);
27866
28468
  const avatarVisual = getAvatarVisualById(options.visualId);
28469
+ const surface = options.surface || 'framed';
27867
28470
  const context = options.canvas.getContext('2d');
27868
28471
  if (!context) {
27869
28472
  throw new Error('2D canvas rendering context is unavailable.');
@@ -27876,8 +28479,10 @@
27876
28479
  devicePixelRatio: options.devicePixelRatio || 1,
27877
28480
  timeMs: options.timeMs,
27878
28481
  avatarDefinition: normalizedAvatarDefinition,
27879
- palette: createAvatarPalette(normalizedAvatarDefinition),
28482
+ palette: createAvatarPalette(normalizedAvatarDefinition, surface),
27880
28483
  createRandom: createAvatarRandomFactory(normalizedAvatarDefinition),
28484
+ surface,
28485
+ interaction: options.interaction || createIdleAvatarInteractionState(),
27881
28486
  });
27882
28487
  }
27883
28488
 
@@ -27893,42 +28498,74 @@
27893
28498
  * @private shared component for in-repository avatar previews
27894
28499
  */
27895
28500
  function Avatar(props) {
27896
- const { avatarDefinition, visualId, size = DEFAULT_AVATAR_SIZE, title, className, style } = props;
28501
+ const { avatarDefinition, visualId, surface = 'framed', size = DEFAULT_AVATAR_SIZE, title, className, style } = props;
27897
28502
  const canvasRef = react.useRef(null);
27898
- const normalizedAvatarDefinition = react.useMemo(() => normalizeAvatarDefinition(avatarDefinition), [avatarDefinition]);
28503
+ const animationStartRef = react.useRef(null);
28504
+ const interactionRuntimeStateRef = react.useRef(createAvatarInteractionRuntimeState());
28505
+ const avatarColorsKey = avatarDefinition.colors.join('|');
28506
+ const normalizedAvatarDefinition = react.useMemo(() => normalizeAvatarDefinition(avatarDefinition), [avatarDefinition.agentHash, avatarDefinition.agentName, avatarColorsKey]);
28507
+ const avatarDefinitionKey = react.useMemo(() => createAvatarDefinitionKey(normalizedAvatarDefinition), [normalizedAvatarDefinition.agentHash, normalizedAvatarDefinition.agentName, normalizedAvatarDefinition.colors.join('|')]);
27899
28508
  const avatarVisual = react.useMemo(() => getAvatarVisualById(visualId), [visualId]);
28509
+ react.useEffect(() => {
28510
+ interactionRuntimeStateRef.current = createAvatarInteractionRuntimeState();
28511
+ }, [avatarDefinitionKey, visualId]);
27900
28512
  react.useEffect(() => {
27901
28513
  const canvas = canvasRef.current;
27902
28514
  if (!canvas) {
27903
28515
  throw new Error('Avatar canvas is not mounted.');
27904
28516
  }
28517
+ const isDynamicAvatar = avatarVisual.isAnimated || avatarVisual.supportsPointerTracking === true;
28518
+ const releasePointerTracking = avatarVisual.supportsPointerTracking ? retainAvatarPointerTracking() : null;
27905
28519
  let animationFrameId = null;
27906
- const animationStart = performance.now();
28520
+ if (animationStartRef.current === null) {
28521
+ animationStartRef.current = performance.now();
28522
+ }
27907
28523
  const renderFrame = (now) => {
28524
+ const pointerSnapshot = avatarVisual.supportsPointerTracking ? getAvatarPointerSnapshot() : null;
28525
+ let interactionState = createIdleAvatarInteractionState();
28526
+ if (avatarVisual.supportsPointerTracking && pointerSnapshot) {
28527
+ interactionRuntimeStateRef.current = stepAvatarInteractionRuntimeState(interactionRuntimeStateRef.current, resolveAvatarPointerTarget(canvas.getBoundingClientRect(), pointerSnapshot), now);
28528
+ interactionState = interactionRuntimeStateRef.current;
28529
+ }
28530
+ else if (avatarVisual.supportsPointerTracking) {
28531
+ interactionRuntimeStateRef.current = stepAvatarInteractionRuntimeState(interactionRuntimeStateRef.current, createIdleAvatarInteractionState(), now);
28532
+ interactionState = interactionRuntimeStateRef.current;
28533
+ }
27908
28534
  renderAvatarVisual({
27909
28535
  canvas,
27910
28536
  avatarDefinition: normalizedAvatarDefinition,
27911
28537
  visualId,
28538
+ surface,
27912
28539
  size,
27913
- timeMs: now - animationStart,
28540
+ timeMs: now - animationStartRef.current,
27914
28541
  devicePixelRatio: window.devicePixelRatio || 1,
28542
+ interaction: interactionState,
27915
28543
  });
27916
- if (avatarVisual.isAnimated) {
28544
+ if (isDynamicAvatar) {
27917
28545
  animationFrameId = window.requestAnimationFrame(renderFrame);
27918
28546
  }
27919
28547
  };
27920
- renderFrame(animationStart);
28548
+ renderFrame(performance.now());
27921
28549
  return () => {
27922
28550
  if (animationFrameId !== null) {
27923
28551
  window.cancelAnimationFrame(animationFrameId);
27924
28552
  }
28553
+ releasePointerTracking === null || releasePointerTracking === void 0 ? void 0 : releasePointerTracking();
27925
28554
  };
27926
- }, [avatarVisual.isAnimated, normalizedAvatarDefinition, size, visualId]);
28555
+ }, [
28556
+ avatarDefinitionKey,
28557
+ avatarVisual.isAnimated,
28558
+ avatarVisual.supportsPointerTracking,
28559
+ normalizedAvatarDefinition,
28560
+ size,
28561
+ surface,
28562
+ visualId,
28563
+ ]);
27927
28564
  return (jsxRuntime.jsx("canvas", { ref: canvasRef, title: title || `${normalizedAvatarDefinition.agentName} avatar`, className: className, style: {
27928
28565
  width: size,
27929
28566
  height: size,
27930
28567
  display: 'block',
27931
- borderRadius: size * AVATAR_CANVAS_RADIUS_RATIO,
28568
+ borderRadius: surface === 'transparent' ? 0 : size * AVATAR_CANVAS_RADIUS_RATIO,
27932
28569
  ...style,
27933
28570
  } }));
27934
28571
  }
@@ -27939,9 +28576,9 @@
27939
28576
  * @private shared component for avatar media rendering
27940
28577
  */
27941
28578
  function AvatarOrImage(props) {
27942
- const { imageUrl, avatarDefinition, visualId, size, alt, className, style } = props;
28579
+ const { imageUrl, avatarDefinition, visualId, surface, size, alt, className, style } = props;
27943
28580
  if (avatarDefinition && visualId) {
27944
- return (jsxRuntime.jsx(Avatar, { avatarDefinition: avatarDefinition, visualId: visualId, size: size, title: alt, className: className, style: style }));
28581
+ return (jsxRuntime.jsx(Avatar, { avatarDefinition: avatarDefinition, visualId: visualId, surface: surface, size: size, title: alt, className: className, style: style }));
27945
28582
  }
27946
28583
  if (!imageUrl) {
27947
28584
  return null;
@@ -38549,6 +39186,14 @@
38549
39186
  * Constant for default agent kit model name.
38550
39187
  */
38551
39188
  const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.4-mini';
39189
+ /**
39190
+ * Default model used for nested DeepSearch tool invocations.
39191
+ */
39192
+ const DEFAULT_DEEP_SEARCH_MODEL_NAME = 'o4-mini-deep-research';
39193
+ /**
39194
+ * Tool name used by the Book commitment-backed DeepSearch capability.
39195
+ */
39196
+ const DEEP_SEARCH_TOOL_NAME = 'deep_search';
38552
39197
  /**
38553
39198
  * Creates one structured log entry for streamed tool-call updates.
38554
39199
  *
@@ -38591,6 +39236,98 @@
38591
39236
  }
38592
39237
  return 'COMPLETE';
38593
39238
  }
39239
+ /**
39240
+ * Returns true when one tool definition represents the dedicated DeepSearch capability.
39241
+ *
39242
+ * @param toolDefinition - Tool definition from compiled model requirements.
39243
+ * @returns `true` when the tool should be backed by a nested deep-research agent.
39244
+ *
39245
+ * @private helper of `OpenAiAgentKitExecutionTools`
39246
+ */
39247
+ function isDeepSearchToolDefinition(toolDefinition) {
39248
+ return toolDefinition.name === DEEP_SEARCH_TOOL_NAME;
39249
+ }
39250
+ /**
39251
+ * Normalizes Promptbook JSON-schema tool parameters for AgentKit function tools.
39252
+ *
39253
+ * @param parameters - Promptbook tool parameters.
39254
+ * @returns AgentKit-compatible JSON schema or `undefined`.
39255
+ *
39256
+ * @private helper of `OpenAiAgentKitExecutionTools`
39257
+ */
39258
+ function normalizeAgentKitToolParameters(parameters) {
39259
+ var _a, _b;
39260
+ if (!parameters) {
39261
+ return undefined;
39262
+ }
39263
+ return {
39264
+ ...parameters,
39265
+ additionalProperties: (_a = parameters.additionalProperties) !== null && _a !== void 0 ? _a : false,
39266
+ required: (_b = parameters.required) !== null && _b !== void 0 ? _b : [],
39267
+ };
39268
+ }
39269
+ /**
39270
+ * Creates instructions for the nested DeepSearch specialist agent.
39271
+ *
39272
+ * @param toolDescription - Model-facing description from the original tool definition.
39273
+ * @returns System instructions for the nested deep-research agent.
39274
+ *
39275
+ * @private helper of `OpenAiAgentKitExecutionTools`
39276
+ */
39277
+ function createDeepSearchAgentInstructions(toolDescription) {
39278
+ const normalizedDescription = toolDescription.trim();
39279
+ return spacetrim.spaceTrim((block) => `
39280
+ You are a DeepSearch specialist working as a tool for another agent.
39281
+ Perform thorough, source-grounded public-web research based on the provided request.
39282
+ Use web search to gather current information, compare relevant viewpoints, and synthesize a concise research brief.
39283
+ Do not ask follow-up questions. If the request is not specific enough, state the assumptions you had to make.
39284
+ Include citations in the research brief whenever sources were used.
39285
+ ${block(normalizedDescription ? `Tool guidance:\n${normalizedDescription}` : '')}
39286
+ `);
39287
+ }
39288
+ /**
39289
+ * Builds the nested DeepSearch prompt from structured tool arguments.
39290
+ *
39291
+ * @param rawInput - Parsed function-tool arguments provided by the outer agent.
39292
+ * @returns Prompt text passed to the nested deep-research agent.
39293
+ *
39294
+ * @private helper of `OpenAiAgentKitExecutionTools`
39295
+ */
39296
+ function buildDeepSearchToolInput(rawInput) {
39297
+ const input = rawInput && typeof rawInput === 'object' ? rawInput : {};
39298
+ const query = typeof input.query === 'string' ? input.query.trim() : '';
39299
+ const additionalHints = Object.entries(input)
39300
+ .filter(([key, value]) => key !== 'query' && value !== undefined && value !== null && String(value).trim() !== '')
39301
+ .map(([key, value]) => `- ${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`);
39302
+ return spacetrim.spaceTrim((block) => `
39303
+ Research request:
39304
+ ${query || JSON.stringify(input)}
39305
+ ${block(additionalHints.length > 0 ? `Execution hints:\n${additionalHints.join('\n')}` : '')}
39306
+ `);
39307
+ }
39308
+ /**
39309
+ * Creates the native Agent SDK tool used for `USE DEEPSEARCH`.
39310
+ *
39311
+ * @param toolDefinition - Promptbook tool definition for `deep_search`.
39312
+ * @returns AgentKit tool backed by a nested deep-research agent.
39313
+ *
39314
+ * @private helper of `OpenAiAgentKitExecutionTools`
39315
+ */
39316
+ function createDeepSearchAgentKitTool(toolDefinition) {
39317
+ const deepSearchAgent = new agents.Agent({
39318
+ name: 'DeepSearch',
39319
+ model: DEFAULT_DEEP_SEARCH_MODEL_NAME,
39320
+ instructions: createDeepSearchAgentInstructions(toolDefinition.description),
39321
+ tools: [agents.webSearchTool({ searchContextSize: 'high' })],
39322
+ });
39323
+ return deepSearchAgent.asTool({
39324
+ toolName: toolDefinition.name,
39325
+ toolDescription: toolDefinition.description,
39326
+ parameters: normalizeAgentKitToolParameters(toolDefinition.parameters),
39327
+ inputBuilder: ({ params }) => buildDeepSearchToolInput(params),
39328
+ customOutputExtractor: (result) => { var _a; return typeof result.finalOutput === 'string' ? result.finalOutput : JSON.stringify((_a = result.finalOutput) !== null && _a !== void 0 ? _a : ''); },
39329
+ });
39330
+ }
38594
39331
  /**
38595
39332
  * Constant for default JSON schema name.
38596
39333
  */
@@ -38931,25 +39668,23 @@
38931
39668
  * Builds the tool list for AgentKit, including hosted file search when applicable.
38932
39669
  */
38933
39670
  buildAgentKitTools(options) {
38934
- var _a;
38935
39671
  const { tools, vectorStoreId } = options;
38936
39672
  const agentKitTools = [];
38937
39673
  if (vectorStoreId) {
38938
39674
  agentKitTools.push(agents.fileSearchTool(vectorStoreId));
38939
39675
  }
38940
39676
  if (tools && tools.length > 0) {
38941
- const scriptTools = this.resolveScriptTools();
39677
+ let scriptTools = null;
38942
39678
  for (const toolDefinition of tools) {
39679
+ if (isDeepSearchToolDefinition(toolDefinition)) {
39680
+ agentKitTools.push(createDeepSearchAgentKitTool(toolDefinition));
39681
+ continue;
39682
+ }
39683
+ scriptTools !== null && scriptTools !== void 0 ? scriptTools : (scriptTools = this.resolveScriptTools());
38943
39684
  agentKitTools.push(agents.tool({
38944
39685
  name: toolDefinition.name,
38945
39686
  description: toolDefinition.description,
38946
- parameters: toolDefinition.parameters
38947
- ? {
38948
- ...toolDefinition.parameters,
38949
- additionalProperties: false,
38950
- required: (_a = toolDefinition.parameters.required) !== null && _a !== void 0 ? _a : [],
38951
- }
38952
- : undefined,
39687
+ parameters: normalizeAgentKitToolParameters(toolDefinition.parameters),
38953
39688
  strict: false,
38954
39689
  execute: async (input, runContext, details) => {
38955
39690
  var _a, _b, _c, _d;
@@ -45136,6 +45871,7 @@
45136
45871
  delete_wallet_record: { title: 'Deleting wallet', emoji: '👛' },
45137
45872
  request_wallet_record: { title: 'Requesting wallet', emoji: '👛' },
45138
45873
  web_search: { title: 'Searching the web', emoji: '🔎' },
45874
+ deep_search: { title: 'Deep research', emoji: '🔬' },
45139
45875
  useSearchEngine: { title: 'Searching the web', emoji: '🔎' },
45140
45876
  search: { title: 'Searching the web', emoji: '🔎' },
45141
45877
  useBrowser: { title: 'Browsing the web', emoji: '🌐' },
@@ -48097,7 +48833,7 @@
48097
48833
  * @private function of ChatToolCallModal
48098
48834
  */
48099
48835
  function isSearchToolCallName(toolName) {
48100
- return toolName === 'web_search' || toolName === 'useSearchEngine' || toolName === 'search';
48836
+ return toolName === 'web_search' || toolName === 'deep_search' || toolName === 'useSearchEngine' || toolName === 'search';
48101
48837
  }
48102
48838
  /**
48103
48839
  * Checks whether a tool name should use the time renderer.
@@ -48797,6 +49533,12 @@
48797
49533
  });
48798
49534
  }
48799
49535
  }
49536
+ const staticConversationDelayConfig = {
49537
+ ...FAST_FLOW,
49538
+ beforeFirstMessage: 0,
49539
+ // Show the full internal exchange immediately so the modal never opens as a blank panel.
49540
+ showIntermediateMessages: messages.length,
49541
+ };
48800
49542
  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';
48801
49543
  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) || '';
48802
49544
  const teammateProfile = teammateUrl ? teamProfiles[teammateUrl] : undefined;
@@ -48833,7 +49575,7 @@
48833
49575
  avatarSrc: resolvedTeammateAvatar || undefined,
48834
49576
  },
48835
49577
  ];
48836
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: classNames(styles$5.searchModalHeader, styles$5.teamModalHeader), children: jsxRuntime.jsxs("div", { className: styles$5.teamHeaderParticipants, children: [jsxRuntime.jsx(TeamHeaderProfile, { label: resolvedAgentLabel, avatarSrc: resolvedAgentAvatar, avatarDefinition: resolvedAgentAvatarDefinition, avatarVisualId: resolvedAgentAvatarVisualId, fallbackColor: resolvedAgentHeaderColor }), jsxRuntime.jsx("span", { className: styles$5.teamHeaderDivider, children: "talking with" }), jsxRuntime.jsx(TeamHeaderProfile, { label: resolvedTeammateLabel, avatarSrc: resolvedTeammateAvatar, fallbackColor: "#0ea5e9", href: teammateLink })] }) }), jsxRuntime.jsxs("div", { className: styles$5.searchModalContent, children: [messages.length > 0 ? (jsxRuntime.jsx("div", { className: styles$5.teamChatContainer, children: jsxRuntime.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 }) })) : (jsxRuntime.jsx("div", { className: styles$5.noResults, children: "No teammate conversation available." })), (hasTeamToolCalls || hasTeamCitations) && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallSection, children: [hasTeamToolCalls && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$5.teamToolCallHeading, children: "Actions" }), jsxRuntime.jsx("div", { className: styles$5.teamToolCallChips, children: teamToolCalls.map((toolCallEntry, index) => {
49578
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: classNames(styles$5.searchModalHeader, styles$5.teamModalHeader), children: jsxRuntime.jsxs("div", { className: styles$5.teamHeaderParticipants, children: [jsxRuntime.jsx(TeamHeaderProfile, { label: resolvedAgentLabel, avatarSrc: resolvedAgentAvatar, avatarDefinition: resolvedAgentAvatarDefinition, avatarVisualId: resolvedAgentAvatarVisualId, fallbackColor: resolvedAgentHeaderColor }), jsxRuntime.jsx("span", { className: styles$5.teamHeaderDivider, children: "talking with" }), jsxRuntime.jsx(TeamHeaderProfile, { label: resolvedTeammateLabel, avatarSrc: resolvedTeammateAvatar, fallbackColor: "#0ea5e9", href: teammateLink })] }) }), jsxRuntime.jsxs("div", { className: styles$5.searchModalContent, children: [messages.length > 0 ? (jsxRuntime.jsx("div", { className: styles$5.teamChatContainer, children: jsxRuntime.jsx(MockedChat, { title: `Chat between ${resolvedAgentLabel} and ${resolvedTeammateLabel}`, messages: messages, participants: participants, isResettable: false, isPausable: false, isSaveButtonEnabled: false, isCopyButtonEnabled: false, visual: "STANDALONE", delayConfig: staticConversationDelayConfig }) })) : (jsxRuntime.jsx("div", { className: styles$5.noResults, children: "No teammate conversation available." })), (hasTeamToolCalls || hasTeamCitations) && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallSection, children: [hasTeamToolCalls && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$5.teamToolCallHeading, children: "Actions" }), jsxRuntime.jsx("div", { className: styles$5.teamToolCallChips, children: teamToolCalls.map((toolCallEntry, index) => {
48837
49579
  const chipletInfo = getToolCallChipletInfo(toolCallEntry.toolCall, undefined, toolTitles);
48838
49580
  const chipletText = buildToolCallChipText(chipletInfo);
48839
49581
  return (jsxRuntime.jsxs("button", { className: styles$5.completedToolCall, onClick: () => {