@genesislcap/ai-assistant 14.431.0 → 14.432.1

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.
@@ -963,7 +963,7 @@
963
963
  {
964
964
  "kind": "Interface",
965
965
  "canonicalReference": "@genesislcap/ai-assistant!AiChatWidget:interface",
966
- "docComment": "/**\n * Interface that AI inline interaction components can implement. The `AiChatInteractionWrapper` always sets `data` and `resolved` on the rendered element.\n *\n * @beta\n */\n",
966
+ "docComment": "/**\n * Interface that AI inline interaction components can implement. The `AiChatInteractionWrapper` always sets `data`, `resolved`, and `shouldAutoFocus` on the rendered element.\n *\n * @beta\n */\n",
967
967
  "excerptTokens": [
968
968
  {
969
969
  "kind": "Content",
@@ -1033,6 +1033,33 @@
1033
1033
  "startIndex": 1,
1034
1034
  "endIndex": 3
1035
1035
  }
1036
+ },
1037
+ {
1038
+ "kind": "PropertySignature",
1039
+ "canonicalReference": "@genesislcap/ai-assistant!AiChatWidget#shouldAutoFocus:member",
1040
+ "docComment": "/**\n * True when the widget is being mounted fresh and the assistant currently has the user's engagement (last pointer/focus interaction was inside the assistant). Widgets with input fields should use this to decide whether to call `.focus()` on mount. False when: - the user has interacted with something outside the assistant, or - this widget has already been resolved (we're re-rendering history due to a layout-lifecycle remount, dock/undock, etc.).\n */\n",
1041
+ "excerptTokens": [
1042
+ {
1043
+ "kind": "Content",
1044
+ "text": "shouldAutoFocus?: "
1045
+ },
1046
+ {
1047
+ "kind": "Content",
1048
+ "text": "boolean"
1049
+ },
1050
+ {
1051
+ "kind": "Content",
1052
+ "text": ";"
1053
+ }
1054
+ ],
1055
+ "isReadonly": false,
1056
+ "isOptional": true,
1057
+ "releaseTag": "Beta",
1058
+ "name": "shouldAutoFocus",
1059
+ "propertyTypeTokenRange": {
1060
+ "startIndex": 1,
1061
+ "endIndex": 2
1062
+ }
1036
1063
  }
1037
1064
  ],
1038
1065
  "extendsTokenRanges": []
@@ -2638,6 +2665,32 @@
2638
2665
  "endIndex": 7
2639
2666
  }
2640
2667
  },
2668
+ {
2669
+ "kind": "TypeAlias",
2670
+ "canonicalReference": "@genesislcap/ai-assistant!ChatInputDuringExecutionMode:type",
2671
+ "docComment": "/**\n * Controls how the main chat input area behaves while this agent is executing (i.e. while `state === 'loading'` — covers both LLM thinking and pending widget interactions).\n *\n * - `'disabled'` (default): input stays visible but disabled. - `'hidden'`: the entire input row (attach button, textarea, send button) is hidden. Useful for agents whose interaction widgets contain their own input affordances and where a second disabled chat input is redundant or confusing — typically long-running agents (planners) where one transition in/out is less jarring than mid-run flicker.\n *\n * @beta\n */\n",
2672
+ "excerptTokens": [
2673
+ {
2674
+ "kind": "Content",
2675
+ "text": "export type ChatInputDuringExecutionMode = "
2676
+ },
2677
+ {
2678
+ "kind": "Content",
2679
+ "text": "'disabled' | 'hidden'"
2680
+ },
2681
+ {
2682
+ "kind": "Content",
2683
+ "text": ";"
2684
+ }
2685
+ ],
2686
+ "fileUrlPath": "src/config/config.ts",
2687
+ "releaseTag": "Beta",
2688
+ "name": "ChatInputDuringExecutionMode",
2689
+ "typeTokenRange": {
2690
+ "startIndex": 1,
2691
+ "endIndex": 2
2692
+ }
2693
+ },
2641
2694
  {
2642
2695
  "kind": "Function",
2643
2696
  "canonicalReference": "@genesislcap/ai-assistant!createToolFold:function(1)",
@@ -3229,6 +3282,37 @@
3229
3282
  "isAbstract": false,
3230
3283
  "name": "chatConfigChanged"
3231
3284
  },
3285
+ {
3286
+ "kind": "Property",
3287
+ "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#chatInputEl:member",
3288
+ "docComment": "/**\n * Bound to the rapid-text-area chat input via the template ref.\n */\n",
3289
+ "excerptTokens": [
3290
+ {
3291
+ "kind": "Content",
3292
+ "text": "chatInputEl?: "
3293
+ },
3294
+ {
3295
+ "kind": "Reference",
3296
+ "text": "HTMLElement",
3297
+ "canonicalReference": "!HTMLElement:interface"
3298
+ },
3299
+ {
3300
+ "kind": "Content",
3301
+ "text": ";"
3302
+ }
3303
+ ],
3304
+ "isReadonly": false,
3305
+ "isOptional": true,
3306
+ "releaseTag": "Beta",
3307
+ "name": "chatInputEl",
3308
+ "propertyTypeTokenRange": {
3309
+ "startIndex": 1,
3310
+ "endIndex": 2
3311
+ },
3312
+ "isStatic": false,
3313
+ "isProtected": false,
3314
+ "isAbstract": false
3315
+ },
3232
3316
  {
3233
3317
  "kind": "Method",
3234
3318
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#connectedCallback:member(1)",
@@ -3563,7 +3647,16 @@
3563
3647
  },
3564
3648
  {
3565
3649
  "kind": "Content",
3566
- "text": "[];\n } | {\n toolDefinitions: import(\"../utils/tool-fold\")."
3650
+ "text": "[];\n chatInputDuringExecution?: import(\"../config/config\")."
3651
+ },
3652
+ {
3653
+ "kind": "Reference",
3654
+ "text": "ChatInputDuringExecutionMode",
3655
+ "canonicalReference": "@genesislcap/ai-assistant!ChatInputDuringExecutionMode:type"
3656
+ },
3657
+ {
3658
+ "kind": "Content",
3659
+ "text": ";\n } | {\n toolDefinitions: import(\"../utils/tool-fold\")."
3567
3660
  },
3568
3661
  {
3569
3662
  "kind": "Reference",
@@ -3590,7 +3683,16 @@
3590
3683
  },
3591
3684
  {
3592
3685
  "kind": "Content",
3593
- "text": "[];\n })[];\n activeSystemPrompt: string;\n activePrimerHistory: "
3686
+ "text": "[];\n chatInputDuringExecution?: import(\"../config/config\")."
3687
+ },
3688
+ {
3689
+ "kind": "Reference",
3690
+ "text": "ChatInputDuringExecutionMode",
3691
+ "canonicalReference": "@genesislcap/ai-assistant!ChatInputDuringExecutionMode:type"
3692
+ },
3693
+ {
3694
+ "kind": "Content",
3695
+ "text": ";\n })[];\n activeSystemPrompt: string;\n activePrimerHistory: "
3594
3696
  },
3595
3697
  {
3596
3698
  "kind": "Reference",
@@ -3609,7 +3711,7 @@
3609
3711
  "isStatic": false,
3610
3712
  "returnTypeTokenRange": {
3611
3713
  "startIndex": 1,
3612
- "endIndex": 18
3714
+ "endIndex": 22
3613
3715
  },
3614
3716
  "releaseTag": "Beta",
3615
3717
  "isProtected": false,
@@ -3947,6 +4049,36 @@
3947
4049
  "isProtected": false,
3948
4050
  "isAbstract": false
3949
4051
  },
4052
+ {
4053
+ "kind": "Property",
4054
+ "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#isEngaged:member",
4055
+ "docComment": "/**\n * True when the user's last interaction (pointer or keyboard focus) was inside the assistant. Defaults to true so the assistant claims focus on first mount. Auto-focus features (chat input on idle, widget inputs on mount) gate on this to avoid stealing focus from elsewhere on the page.\n */\n",
4056
+ "excerptTokens": [
4057
+ {
4058
+ "kind": "Content",
4059
+ "text": "isEngaged: "
4060
+ },
4061
+ {
4062
+ "kind": "Content",
4063
+ "text": "boolean"
4064
+ },
4065
+ {
4066
+ "kind": "Content",
4067
+ "text": ";"
4068
+ }
4069
+ ],
4070
+ "isReadonly": false,
4071
+ "isOptional": false,
4072
+ "releaseTag": "Beta",
4073
+ "name": "isEngaged",
4074
+ "propertyTypeTokenRange": {
4075
+ "startIndex": 1,
4076
+ "endIndex": 2
4077
+ },
4078
+ "isStatic": false,
4079
+ "isProtected": false,
4080
+ "isAbstract": false
4081
+ },
3950
4082
  {
3951
4083
  "kind": "Property",
3952
4084
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#liveSubAgentName:member",
@@ -159,7 +159,8 @@ export declare class AiChatMarkdown extends GenesisElement {
159
159
 
160
160
  /**
161
161
  * Interface that AI inline interaction components can implement.
162
- * The `AiChatInteractionWrapper` always sets `data` and `resolved` on the rendered element.
162
+ * The `AiChatInteractionWrapper` always sets `data`, `resolved`, and
163
+ * `shouldAutoFocus` on the rendered element.
163
164
  *
164
165
  * @beta
165
166
  */
@@ -167,6 +168,16 @@ export declare interface AiChatWidget {
167
168
  data: unknown;
168
169
  /** The result when the interaction has been resolved. Truthy = resolved; value contains status/payload for display. */
169
170
  resolved?: InteractionResult<unknown>;
171
+ /**
172
+ * True when the widget is being mounted fresh and the assistant currently has
173
+ * the user's engagement (last pointer/focus interaction was inside the
174
+ * assistant). Widgets with input fields should use this to decide whether to
175
+ * call `.focus()` on mount. False when:
176
+ * - the user has interacted with something outside the assistant, or
177
+ * - this widget has already been resolved (we're re-rendering history due
178
+ * to a layout-lifecycle remount, dock/undock, etc.).
179
+ */
180
+ shouldAutoFocus?: boolean;
170
181
  }
171
182
 
172
183
  /**
@@ -294,6 +305,11 @@ declare interface BaseAgentConfig {
294
305
  * Sub-agents available to this agent's tool handlers via `requestSubAgent`.
295
306
  */
296
307
  subAgents?: AgentConfig[];
308
+ /**
309
+ * How the main chat input area behaves while this agent is executing.
310
+ * Defaults to `'disabled'`. See {@link ChatInputDuringExecutionMode}.
311
+ */
312
+ chatInputDuringExecution?: ChatInputDuringExecutionMode;
297
313
  }
298
314
 
299
315
  /**
@@ -433,6 +449,22 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
433
449
  */
434
450
  export declare type ChatHistoryUpdatedEvent = CustomEvent<ReadonlyArray<ChatMessage>>;
435
451
 
452
+ /**
453
+ * Controls how the main chat input area behaves while this agent is executing
454
+ * (i.e. while `state === 'loading'` — covers both LLM thinking and pending
455
+ * widget interactions).
456
+ *
457
+ * - `'disabled'` (default): input stays visible but disabled.
458
+ * - `'hidden'`: the entire input row (attach button, textarea, send button) is
459
+ * hidden. Useful for agents whose interaction widgets contain their own
460
+ * input affordances and where a second disabled chat input is redundant or
461
+ * confusing — typically long-running agents (planners) where one transition
462
+ * in/out is less jarring than mid-run flicker.
463
+ *
464
+ * @beta
465
+ */
466
+ export declare type ChatInputDuringExecutionMode = 'disabled' | 'hidden';
467
+
436
468
  /**
437
469
  * Creates a tool fold — a facade that hides a group of related tools behind a single
438
470
  * named entry, revealing them progressively when the model invokes the facade.
@@ -581,6 +613,16 @@ export declare class FoundationAiAssistant extends GenesisElement {
581
613
  set messages(value: ChatMessage[]);
582
614
  get state(): AiAssistantState;
583
615
  set state(value: AiAssistantState);
616
+ /**
617
+ * Focus the main chat input if the user is still engaged with the assistant.
618
+ * Deferred to the next frame so any conditional re-render (e.g. unhiding the
619
+ * input row when an agent's `chatInputDuringExecution: 'hidden'` execution
620
+ * ends) has committed before we call `.focus()`.
621
+ *
622
+ * `rapid-text-area` does not delegate focus to its inner native `<textarea>`,
623
+ * so we drill into its shadow root and focus the textarea directly.
624
+ */
625
+ private maybeAutoFocusChatInput;
584
626
  get activeAgent(): Omit<AgentConfig, 'toolHandlers'> | undefined;
585
627
  set activeAgent(value: AgentConfig | undefined);
586
628
  get suggestionsState(): SuggestionsState;
@@ -627,6 +669,16 @@ export declare class FoundationAiAssistant extends GenesisElement {
627
669
  private _driverAgentsKey?;
628
670
  /** Bound to the messages container via the template ref — assigned by FAST before connectedCallback logic runs. */
629
671
  messagesEl?: HTMLElement;
672
+ /** Bound to the rapid-text-area chat input via the template ref. */
673
+ chatInputEl?: HTMLElement;
674
+ /**
675
+ * True when the user's last interaction (pointer or keyboard focus) was inside
676
+ * the assistant. Defaults to true so the assistant claims focus on first mount.
677
+ * Auto-focus features (chat input on idle, widget inputs on mount) gate on this
678
+ * to avoid stealing focus from elsewhere on the page.
679
+ */
680
+ isEngaged: boolean;
681
+ private _handleGlobalInteraction;
630
682
  /** True when the user has intentionally scrolled away from the bottom — suppresses auto-scroll. */
631
683
  private _userScrolledAway;
632
684
  private _scrollListener?;
@@ -718,6 +770,7 @@ export declare class FoundationAiAssistant extends GenesisElement {
718
770
  systemPrompt?: string;
719
771
  primerHistory?: ChatMessage[];
720
772
  subAgents?: AgentConfig[];
773
+ chatInputDuringExecution?: ChatInputDuringExecutionMode;
721
774
  } | {
722
775
  toolDefinitions: ToolTreeNode[];
723
776
  toolHandlers: any;
@@ -727,6 +780,7 @@ export declare class FoundationAiAssistant extends GenesisElement {
727
780
  systemPrompt?: string;
728
781
  primerHistory?: ChatMessage[];
729
782
  subAgents?: AgentConfig[];
783
+ chatInputDuringExecution?: ChatInputDuringExecutionMode;
730
784
  })[];
731
785
  activeSystemPrompt: string;
732
786
  activePrimerHistory: ChatMessage[];
@@ -746,7 +800,6 @@ export declare class FoundationAiAssistant extends GenesisElement {
746
800
  handleSuggestionClick(suggestion: string): void;
747
801
  private fetchSuggestions;
748
802
  private send;
749
- private restoreFocusIfAppropriate;
750
803
  onChatHeaderMouseDown(e: MouseEvent): void;
751
804
  handleInteractionCompleted(e: Event): void;
752
805
  }
@@ -1 +1 @@
1
- {"version":3,"file":"chat-interaction-wrapper.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAiB,cAAc,EAAc,MAAM,uBAAuB,CAAC;AAKlF,MAAM,MAAM,wBAAwB,GAAG;IACrC,uBAAuB,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CACjF,CAAC;AAEF;;;;;GAKG;AACH,qBAKa,wBAAyB,SAAQ,cAAc;IAC1D,gBAAgB;IACJ,aAAa,EAAE,MAAM,CAAM;IACvC,gBAAgB;IACJ,IAAI,EAAE,GAAG,CAAC;IACtB,gBAAgB;IACJ,aAAa,EAAE,MAAM,CAAM;IACvC,6GAA6G;IACjG,QAAQ,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAa;IAEzE,gBAAgB;IAChB,SAAS,EAAG,WAAW,CAAC;IAExB,iBAAiB;IAKjB,oBAAoB;IAIpB,eAAe;IAOf,OAAO,CAAC,eAAe;CA8CxB"}
1
+ {"version":3,"file":"chat-interaction-wrapper.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAiB,cAAc,EAAc,MAAM,uBAAuB,CAAC;AAKlF,MAAM,MAAM,wBAAwB,GAAG;IACrC,uBAAuB,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CACjF,CAAC;AAEF;;;;;GAKG;AACH,qBAKa,wBAAyB,SAAQ,cAAc;IAC1D,gBAAgB;IACJ,aAAa,EAAE,MAAM,CAAM;IACvC,gBAAgB;IACJ,IAAI,EAAE,GAAG,CAAC;IACtB,gBAAgB;IACJ,aAAa,EAAE,MAAM,CAAM;IACvC,6GAA6G;IACjG,QAAQ,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAa;IAEzE,gBAAgB;IAChB,SAAS,EAAG,WAAW,CAAC;IAExB,iBAAiB;IAKjB,oBAAoB;IAIpB,eAAe;IAOf,OAAO,CAAC,eAAe;CAoDxB"}
@@ -1,4 +1,19 @@
1
1
  import type { ChatMessage, ChatToolDefinition, ChatToolHandlers } from '@genesislcap/foundation-ai';
2
+ /**
3
+ * Controls how the main chat input area behaves while this agent is executing
4
+ * (i.e. while `state === 'loading'` — covers both LLM thinking and pending
5
+ * widget interactions).
6
+ *
7
+ * - `'disabled'` (default): input stays visible but disabled.
8
+ * - `'hidden'`: the entire input row (attach button, textarea, send button) is
9
+ * hidden. Useful for agents whose interaction widgets contain their own
10
+ * input affordances and where a second disabled chat input is redundant or
11
+ * confusing — typically long-running agents (planners) where one transition
12
+ * in/out is less jarring than mid-run flicker.
13
+ *
14
+ * @beta
15
+ */
16
+ export type ChatInputDuringExecutionMode = 'disabled' | 'hidden';
2
17
  interface BaseAgentConfig {
3
18
  /**
4
19
  * Display name shown in the chat header when this agent is active.
@@ -25,6 +40,11 @@ interface BaseAgentConfig {
25
40
  * Sub-agents available to this agent's tool handlers via `requestSubAgent`.
26
41
  */
27
42
  subAgents?: AgentConfig[];
43
+ /**
44
+ * How the main chat input area behaves while this agent is executing.
45
+ * Defaults to `'disabled'`. See {@link ChatInputDuringExecutionMode}.
46
+ */
47
+ chatInputDuringExecution?: ChatInputDuringExecutionMode;
28
48
  }
29
49
  /**
30
50
  * Configuration for a specialist agent.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEpG,UAAU,eAAe;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,eAAe,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACvC;;OAEG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC;;;OAGG;IACH,aAAa,CAAC,EAAE,WAAW,EAAE,CAAC;IAC9B;;OAEG;IACH,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D;;OAEG;IACH,QAAQ,EAAE,IAAI,CAAC;IACf,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AAEH,wBAAgB,WAAW,CAAC,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAErE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEpG;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,4BAA4B,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEjE,UAAU,eAAe;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,eAAe,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACvC;;OAEG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC;;;OAGG;IACH,aAAa,CAAC,EAAE,WAAW,EAAE,CAAC;IAC9B;;OAEG;IACH,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;IAC1B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D;;OAEG;IACH,QAAQ,EAAE,IAAI,CAAC;IACf,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AAEH,wBAAgB,WAAW,CAAC,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAErE"}
@@ -44,6 +44,16 @@ export declare class FoundationAiAssistant extends GenesisElement {
44
44
  set messages(value: ChatMessage[]);
45
45
  get state(): AiAssistantState;
46
46
  set state(value: AiAssistantState);
47
+ /**
48
+ * Focus the main chat input if the user is still engaged with the assistant.
49
+ * Deferred to the next frame so any conditional re-render (e.g. unhiding the
50
+ * input row when an agent's `chatInputDuringExecution: 'hidden'` execution
51
+ * ends) has committed before we call `.focus()`.
52
+ *
53
+ * `rapid-text-area` does not delegate focus to its inner native `<textarea>`,
54
+ * so we drill into its shadow root and focus the textarea directly.
55
+ */
56
+ private maybeAutoFocusChatInput;
47
57
  get activeAgent(): Omit<AgentConfig, 'toolHandlers'> | undefined;
48
58
  set activeAgent(value: AgentConfig | undefined);
49
59
  get suggestionsState(): SuggestionsState;
@@ -90,6 +100,16 @@ export declare class FoundationAiAssistant extends GenesisElement {
90
100
  private _driverAgentsKey?;
91
101
  /** Bound to the messages container via the template ref — assigned by FAST before connectedCallback logic runs. */
92
102
  messagesEl?: HTMLElement;
103
+ /** Bound to the rapid-text-area chat input via the template ref. */
104
+ chatInputEl?: HTMLElement;
105
+ /**
106
+ * True when the user's last interaction (pointer or keyboard focus) was inside
107
+ * the assistant. Defaults to true so the assistant claims focus on first mount.
108
+ * Auto-focus features (chat input on idle, widget inputs on mount) gate on this
109
+ * to avoid stealing focus from elsewhere on the page.
110
+ */
111
+ isEngaged: boolean;
112
+ private _handleGlobalInteraction;
93
113
  /** True when the user has intentionally scrolled away from the bottom — suppresses auto-scroll. */
94
114
  private _userScrolledAway;
95
115
  private _scrollListener?;
@@ -181,6 +201,7 @@ export declare class FoundationAiAssistant extends GenesisElement {
181
201
  systemPrompt?: string;
182
202
  primerHistory?: ChatMessage[];
183
203
  subAgents?: AgentConfig[];
204
+ chatInputDuringExecution?: import("../config/config").ChatInputDuringExecutionMode;
184
205
  } | {
185
206
  toolDefinitions: import("../utils/tool-fold").ToolTreeNode[];
186
207
  toolHandlers: any;
@@ -190,6 +211,7 @@ export declare class FoundationAiAssistant extends GenesisElement {
190
211
  systemPrompt?: string;
191
212
  primerHistory?: ChatMessage[];
192
213
  subAgents?: AgentConfig[];
214
+ chatInputDuringExecution?: import("../config/config").ChatInputDuringExecutionMode;
193
215
  })[];
194
216
  activeSystemPrompt: string;
195
217
  activePrimerHistory: ChatMessage[];
@@ -209,7 +231,6 @@ export declare class FoundationAiAssistant extends GenesisElement {
209
231
  handleSuggestionClick(suggestion: string): void;
210
232
  private fetchSuggestions;
211
233
  private send;
212
- private restoreFocusIfAppropriate;
213
234
  onChatHeaderMouseDown(e: MouseEvent): void;
214
235
  handleInteractionCompleted(e: Event): void;
215
236
  }
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE1F,OAAO,EAGL,cAAc,EAIf,MAAM,uBAAuB,CAAC;AAU/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AASpD,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EACjB,MAAM,cAAc,CAAC;AA6BtB;;;;;;;;;;;;;GAaG;AACH,qBAOa,qBAAsB,SAAQ,cAAc;IAC3C,UAAU,EAAG,UAAU,CAAC;IAExB,kBAAkB,EAAE,MAAM,CAAW;IACZ,WAAW,EAAE,MAAM,CAAuB;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC5C,WAAW,EAAE,MAAM,CAA0B;IACrD;;;;;OAKG;IACiC,UAAU,CAAC,EAAE,UAAU,CAAC;IAC5D;;;OAGG;IACS,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,UAAU,EAAE,UAAU,CAAM;IAC5B,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;IAC9C,0EAA0E;IACrB,UAAU,UAAS;IAIxE,OAAO,CAAC,WAAW,CAAC,CAAqB;IAEzC,IAAI,QAAQ,IAAI,WAAW,EAAE,CAE5B;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,EAGhC;IAED,IAAI,KAAK,IAAI,gBAAgB,CAE5B;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAUhC;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,uBAAuB;IAQ/B,IAAI,WAAW,IAAI,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAE/D;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAM7C;IAED,IAAI,gBAAgB,IAAI,gBAAgB,CAEvC;IACD,IAAI,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAE3C;IAED,iEAAiE;IACjE,IAAI,aAAa,IAAI,OAAO,CAE3B;IACD,IAAI,aAAa,CAAC,KAAK,EAAE,OAAO,EAE/B;IAED,qEAAqE;IACrE,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IACD,IAAI,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAEnC;IAED,8EAA8E;IAC9E,IAAI,wBAAwB,IAAI,OAAO,CAEtC;IACD,IAAI,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAE1C;IAED,oCAAoC;IACpC,IAAI,iBAAiB,IAAI,oBAAoB,EAAE,CAE9C;IACD,IAAI,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,EAAE,EAElD;IAED,IAAI,iBAAiB,IAAI,WAAW,EAAE,CAErC;IACD,IAAI,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,EAEzC;IAED,IAAI,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEpC;IACD,IAAI,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAExC;IAED,yEAAyE;IACzE,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IACD,IAAI,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAE1C;IAED,0DAA0D;IAC1D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAErC;IACD,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAEzC;IAID,OAAO,CAAC,sBAAsB,CAAK;IAEnC,IAAI,UAAU,IAAI,MAAM,CAEvB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,MAAM,EAE3B;IACW,WAAW,EAAE,cAAc,EAAE,CAAM;IACnC,gBAAgB,EAAE,MAAM,EAAE,CAAM;IAC5C,+FAA+F;IACnF,oBAAoB,UAAS;IACzC,0CAA0C;IAC9B,YAAY,UAAS;IACjC,6IAA6I;IACjI,aAAa,UAAS;IAElC,OAAO,CAAC,MAAM,CAAC,CAAW;IAC1B,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,kBAAkB,CAAS;IACnC,yHAAyH;IACzH,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAClC,mHAAmH;IACnH,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,oEAAoE;IACpE,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;OAKG;IACS,SAAS,EAAE,OAAO,CAAQ;IACtC,OAAO,CAAC,wBAAwB,CAI9B;IACF,mGAAmG;IACnG,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAM;IACxD;;;;;OAKG;IACH,OAAO,CAAC,yBAAyB,CAAC,CAAa;IAEnC,QAAQ,EAAE,IAAI,GAAG,eAAe,GAAG,OAAO,CAAQ;IAE9D,OAAO,CAAC,YAAY;IAapB,0FAA0F;IAC1F,IAAI,2BAA2B,IAAI,OAAO,CAIzC;IAED,eAAe;IAWf,OAAO,CAAC,kBAAkB;IAU1B;;;;;;;;;;OAUG;IACH,IACI,eAAe,IAAI,WAAW,EAAE,CA0BnC;IAED,aAAa,IAAI,IAAI;IAyBrB,mGAAmG;IACnG,OAAO,CAAC,YAAY;IAYpB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IA2BpB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAyDlB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAKpB,iBAAiB;IAuFjB,oBAAoB;YAqBN,mBAAmB;IAWjC,iBAAiB;IAIjB,oBAAoB;IAWpB,OAAO,CAAC,iBAAiB;IAIzB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAuC5B,2BAA2B;IAQ3B,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAK;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAK;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAE7C,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,gBAAgB;IAKxB,qDAAqD;IACrD,YAAY,IAAI,IAAI;IAQpB,4FAA4F;IAC5F,OAAO,CAAC,WAAW;IAKnB,cAAc;IAqBd,mBAAmB;IAInB,uBAAuB;IAIvB,8BAA8B;IAI9B,oBAAoB,CAAC,UAAU,EAAE,oBAAoB,EAAE;IAIvD,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAqBX,gBAAgB;IAehB,gBAAgB,IAAI,IAAI;IAIxB,gBAAgB,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI;IAIhC,gBAAgB,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI;IAIlD,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAU5C,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,cAAc;YASR,iBAAiB;IAmC/B,eAAe;IAIf,qBAAqB,CAAC,UAAU,EAAE,MAAM;YAK1B,gBAAgB;YAuDhB,IAAI;IAqClB,qBAAqB,CAAC,CAAC,EAAE,UAAU;IAYnC,0BAA0B,CAAC,CAAC,EAAE,KAAK;CAQpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.template.d.ts","sourceRoot":"","sources":["../../../src/main/main.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAA2B,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAiIpD,gBAAgB;AAChB,eAAO,MAAM,6BAA6B,GACxC,oBAAoB,MAAM,KACzB,YAAY,CAAC,qBAAqB,CA0ZpC,CAAC"}
1
+ {"version":3,"file":"main.template.d.ts","sourceRoot":"","sources":["../../../src/main/main.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAA2B,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAiIpD,gBAAgB;AAChB,eAAO,MAAM,6BAA6B,GACxC,oBAAoB,MAAM,KACzB,YAAY,CAAC,qBAAqB,CA+ZpC,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import type { InteractionResult } from '@genesislcap/foundation-ai';
2
2
  /**
3
3
  * Interface that AI inline interaction components can implement.
4
- * The `AiChatInteractionWrapper` always sets `data` and `resolved` on the rendered element.
4
+ * The `AiChatInteractionWrapper` always sets `data`, `resolved`, and
5
+ * `shouldAutoFocus` on the rendered element.
5
6
  *
6
7
  * @beta
7
8
  */
@@ -9,5 +10,15 @@ export interface AiChatWidget {
9
10
  data: unknown;
10
11
  /** The result when the interaction has been resolved. Truthy = resolved; value contains status/payload for display. */
11
12
  resolved?: InteractionResult<unknown>;
13
+ /**
14
+ * True when the widget is being mounted fresh and the assistant currently has
15
+ * the user's engagement (last pointer/focus interaction was inside the
16
+ * assistant). Widgets with input fields should use this to decide whether to
17
+ * call `.focus()` on mount. False when:
18
+ * - the user has interacted with something outside the assistant, or
19
+ * - this widget has already been resolved (we're re-rendering history due
20
+ * to a layout-lifecycle remount, dock/undock, etc.).
21
+ */
22
+ shouldAutoFocus?: boolean;
12
23
  }
13
24
  //# sourceMappingURL=ai-chat-widget.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai-chat-widget.d.ts","sourceRoot":"","sources":["../../../src/types/ai-chat-widget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,uHAAuH;IACvH,QAAQ,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;CACvC"}
1
+ {"version":3,"file":"ai-chat-widget.d.ts","sourceRoot":"","sources":["../../../src/types/ai-chat-widget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,uHAAuH;IACvH,QAAQ,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACtC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B"}
@@ -52,6 +52,12 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
52
52
  element.data = this.data;
53
53
  }
54
54
  element.resolved = this.resolved;
55
+ // Pierce the wrapper's shadow boundary to read engagement from the
56
+ // assistant host. False if the widget is already resolved — re-renders
57
+ // from history (layout remount, dock/undock) must never steal focus.
58
+ const root = this.getRootNode();
59
+ const host = root instanceof ShadowRoot ? root.host : null;
60
+ element.shouldAutoFocus = !this.resolved && !!(host === null || host === void 0 ? void 0 : host.isEngaged);
55
61
  // Handle completion from the inner component.
56
62
  // Guard against re-emission if the interaction was already resolved
57
63
  // (e.g. auto-resolve components fire on connectedCallback when rendering history).
@@ -91,6 +91,19 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
91
91
  /** Whether the splash overlay is currently showing (no messages and showSplash is enabled). Reflected as a boolean attribute on the host. */
92
92
  this.showingSplash = false;
93
93
  this.haloStartPublished = false;
94
+ /**
95
+ * True when the user's last interaction (pointer or keyboard focus) was inside
96
+ * the assistant. Defaults to true so the assistant claims focus on first mount.
97
+ * Auto-focus features (chat input on idle, widget inputs on mount) gate on this
98
+ * to avoid stealing focus from elsewhere on the page.
99
+ */
100
+ this.isEngaged = true;
101
+ this._handleGlobalInteraction = (e) => {
102
+ const next = e.composedPath().includes(this);
103
+ if (next === this.isEngaged)
104
+ return;
105
+ this.isEngaged = next;
106
+ };
94
107
  /** True when the user has intentionally scrolled away from the bottom — suppresses auto-scroll. */
95
108
  this._userScrolledAway = false;
96
109
  this.showHalo = 'no';
@@ -109,9 +122,35 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
109
122
  return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.state) !== null && _b !== void 0 ? _b : 'idle';
110
123
  }
111
124
  set state(value) {
112
- var _a;
113
- (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setState(value);
125
+ var _a, _b;
126
+ const prev = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.state;
127
+ (_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setState(value);
114
128
  this.syncShowHalo();
129
+ // When the agent finishes (loading → !loading) the input row reappears (or
130
+ // becomes enabled) — refocus it so the user can type immediately, but only
131
+ // if they haven't moved on to something else on the page.
132
+ if (prev === 'loading' && value !== 'loading') {
133
+ this.maybeAutoFocusChatInput();
134
+ }
135
+ }
136
+ /**
137
+ * Focus the main chat input if the user is still engaged with the assistant.
138
+ * Deferred to the next frame so any conditional re-render (e.g. unhiding the
139
+ * input row when an agent's `chatInputDuringExecution: 'hidden'` execution
140
+ * ends) has committed before we call `.focus()`.
141
+ *
142
+ * `rapid-text-area` does not delegate focus to its inner native `<textarea>`,
143
+ * so we drill into its shadow root and focus the textarea directly.
144
+ */
145
+ maybeAutoFocusChatInput() {
146
+ if (!this.isEngaged)
147
+ return;
148
+ requestAnimationFrame(() => {
149
+ var _a, _b, _c;
150
+ if (!this.isEngaged || !this.isConnected)
151
+ return;
152
+ (_c = (_b = (_a = this.chatInputEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('textarea')) === null || _c === void 0 ? void 0 : _c.focus();
153
+ });
115
154
  }
116
155
  get activeAgent() {
117
156
  var _a;
@@ -474,6 +513,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
474
513
  this.stopLoadingTimer();
475
514
  this.syncShowHalo();
476
515
  this.fetchSuggestions();
516
+ this.maybeAutoFocusChatInput();
477
517
  (_b = this._executionCompletionUnsub) === null || _b === void 0 ? void 0 : _b.call(this);
478
518
  this._executionCompletionUnsub = undefined;
479
519
  }
@@ -489,6 +529,14 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
489
529
  };
490
530
  this.messagesEl.addEventListener('scroll', this._scrollListener);
491
531
  }
532
+ // Track whether the user's most recent interaction was inside this assistant.
533
+ // Capture phase + composedPath() pierces shadow boundaries reliably.
534
+ document.addEventListener('pointerdown', this._handleGlobalInteraction, true);
535
+ document.addEventListener('focusin', this._handleGlobalInteraction, true);
536
+ // Initial focus on mount when idle (skips during a mid-lifecycle reattach
537
+ // where state is loading and the input may be hidden or disabled).
538
+ if (this.state !== 'loading')
539
+ this.maybeAutoFocusChatInput();
492
540
  logger.debug('FoundationAiAssistant connected');
493
541
  }
494
542
  disconnectedCallback() {
@@ -506,6 +554,8 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
506
554
  }
507
555
  this._scrollListener = undefined;
508
556
  this._userScrolledAway = false;
557
+ document.removeEventListener('pointerdown', this._handleGlobalInteraction, true);
558
+ document.removeEventListener('focusin', this._handleGlobalInteraction, true);
509
559
  // Clear local references only — driver and store stay in their registries.
510
560
  this.driver = undefined;
511
561
  this._sessionRef = undefined;
@@ -869,19 +919,13 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
869
919
  capturedSessionRef === null || capturedSessionRef === void 0 ? void 0 : capturedSessionRef.actions.aiAssistant.setState('idle');
870
920
  this.syncShowHalo();
871
921
  this.fetchSuggestions();
872
- this.restoreFocusIfAppropriate();
922
+ // setState was dispatched directly via the captured ref (not via the
923
+ // state setter) so the setter's auto-focus path doesn't fire here. Call
924
+ // it explicitly so the chat input refocuses after the run completes.
925
+ this.maybeAutoFocusChatInput();
873
926
  }
874
927
  });
875
928
  }
876
- restoreFocusIfAppropriate() {
877
- const active = document.activeElement;
878
- if (active !== document.body && active !== this)
879
- return;
880
- requestAnimationFrame(() => {
881
- var _a, _b;
882
- (_b = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.chat-input')) === null || _b === void 0 ? void 0 : _b.focus();
883
- });
884
- }
885
929
  onChatHeaderMouseDown(e) {
886
930
  if (this.popoutMode === 'collapse')
887
931
  return;
@@ -951,6 +995,9 @@ __decorate([
951
995
  __decorate([
952
996
  observable
953
997
  ], FoundationAiAssistant.prototype, "showingSplash", void 0);
998
+ __decorate([
999
+ observable
1000
+ ], FoundationAiAssistant.prototype, "isEngaged", void 0);
954
1001
  __decorate([
955
1002
  observable
956
1003
  ], FoundationAiAssistant.prototype, "showHalo", void 0);
@@ -376,33 +376,33 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
376
376
  `)}
377
377
  </div>
378
378
  `)}
379
-
380
379
  ${when((x) => { var _a; return ((_a = x.chatConfig.suggestions) === null || _a === void 0 ? void 0 : _a.behavior) !== 'never'; }, html `
381
380
  <chat-suggestions
382
381
  :state="${(x) => x.suggestionsState}"
383
382
  @suggestion-clicked="${(x, c) => x.handleSuggestionClick(c.event.detail)}"
384
383
  ></chat-suggestions>
385
384
  `)}
386
-
387
- <div class="input-row" part="input-row">
388
- ${when((x) => { var _a; return !!((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.acceptedFiles); }, html `
389
- <${buttonTag}
390
- class="attach-button"
391
- part="attach-button"
392
- appearance="stealth"
393
- title=${(x) => { var _a; return `Attach file (${(_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.acceptedFiles})`; }}
385
+ ${when((x) => { var _a; return !(x.state === 'loading' && ((_a = x.activeAgent) === null || _a === void 0 ? void 0 : _a.chatInputDuringExecution) === 'hidden'); }, html `
386
+ <div class="input-row" part="input-row">
387
+ ${when((x) => { var _a; return !!((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.acceptedFiles); }, html `
388
+ <${buttonTag}
389
+ class="attach-button"
390
+ part="attach-button"
391
+ appearance="stealth"
392
+ title=${(x) => { var _a; return `Attach file (${(_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.acceptedFiles})`; }}
393
+ ?disabled=${(x) => x.state === 'loading'}
394
+ @click=${(x) => x.triggerFileInput()}
395
+ ><${iconTag} name="paperclip"></${iconTag}></${buttonTag}>
396
+ `)}
397
+ <${textareaTag}
398
+ ${ref('chatInputEl')}
399
+ class="chat-input"
400
+ part="input"
401
+ placeholder=${(x) => x.placeholder}
402
+ :value=${(x) => x.inputValue}
394
403
  ?disabled=${(x) => x.state === 'loading'}
395
- @click=${(x) => x.triggerFileInput()}
396
- ><${iconTag} name="paperclip"></${iconTag}></${buttonTag}>
397
- `)}
398
- <${textareaTag}
399
- class="chat-input"
400
- part="input"
401
- placeholder=${(x) => x.placeholder}
402
- :value=${(x) => x.inputValue}
403
- ?disabled=${(x) => x.state === 'loading'}
404
- @input=${(x, c) => (x.inputValue = c.event.target.value)}
405
- @keydown=${(x, c) => {
404
+ @input=${(x, c) => (x.inputValue = c.event.target.value)}
405
+ @keydown=${(x, c) => {
406
406
  if (c.event.key === 'Enter' &&
407
407
  !c.event.shiftKey) {
408
408
  c.event.preventDefault();
@@ -411,18 +411,19 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
411
411
  }
412
412
  return true;
413
413
  }}
414
- ></${textareaTag}>
415
- <${buttonTag}
416
- class="send-button"
417
- part="send-button"
418
- ?disabled=${(x) => x.state === 'loading' || (!x.inputValue.trim() && !x.attachments.length)}
419
- @click=${(x) => x.handleSendClick()}
420
- >Send</${buttonTag}>
421
- </div>
414
+ ></${textareaTag}>
415
+ <${buttonTag}
416
+ class="send-button"
417
+ part="send-button"
418
+ ?disabled=${(x) => x.state === 'loading' || (!x.inputValue.trim() && !x.attachments.length)}
419
+ @click=${(x) => x.handleSendClick()}
420
+ >Send</${buttonTag}>
421
+ </div>
422
+ `)}
422
423
  <ai-halo-overlay
423
424
  part="halo-overlay"
424
425
  ?active=${(x) => x.showHalo !== 'no' && x.enabledAnimations.includes('halo')}
425
- :speed="${(x) => (x.showHalo === 'orchestrating' ? HALO_SPEED_ORCHESTRATING : HALO_SPEED_DEFAULT)}"
426
+ :speed="${(x) => x.showHalo === 'orchestrating' ? HALO_SPEED_ORCHESTRATING : HALO_SPEED_DEFAULT}"
426
427
  direction="${(x) => (x.showHalo === 'orchestrating' ? 'ccw' : 'cw')}"
427
428
  :borderSize="${(x) => (x.showHalo === 'orchestrating' ? 2 : HALO_BORDER_SIZE_DEFAULT)}"
428
429
  ></ai-halo-overlay>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/ai-assistant",
3
3
  "description": "Genesis AI Assistant micro-frontend",
4
- "version": "14.431.0",
4
+ "version": "14.432.1",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -64,24 +64,24 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
- "@genesislcap/foundation-testing": "14.431.0",
68
- "@genesislcap/genx": "14.431.0",
69
- "@genesislcap/rollup-builder": "14.431.0",
70
- "@genesislcap/ts-builder": "14.431.0",
71
- "@genesislcap/uvu-playwright-builder": "14.431.0",
72
- "@genesislcap/vite-builder": "14.431.0",
73
- "@genesislcap/webpack-builder": "14.431.0",
67
+ "@genesislcap/foundation-testing": "14.432.1",
68
+ "@genesislcap/genx": "14.432.1",
69
+ "@genesislcap/rollup-builder": "14.432.1",
70
+ "@genesislcap/ts-builder": "14.432.1",
71
+ "@genesislcap/uvu-playwright-builder": "14.432.1",
72
+ "@genesislcap/vite-builder": "14.432.1",
73
+ "@genesislcap/webpack-builder": "14.432.1",
74
74
  "@types/dompurify": "^3.0.5",
75
75
  "@types/marked": "^5.0.2"
76
76
  },
77
77
  "dependencies": {
78
- "@genesislcap/foundation-ai": "14.431.0",
79
- "@genesislcap/foundation-logger": "14.431.0",
80
- "@genesislcap/foundation-redux": "14.431.0",
81
- "@genesislcap/foundation-ui": "14.431.0",
82
- "@genesislcap/foundation-utils": "14.431.0",
83
- "@genesislcap/rapid-design-system": "14.431.0",
84
- "@genesislcap/web-core": "14.431.0",
78
+ "@genesislcap/foundation-ai": "14.432.1",
79
+ "@genesislcap/foundation-logger": "14.432.1",
80
+ "@genesislcap/foundation-redux": "14.432.1",
81
+ "@genesislcap/foundation-ui": "14.432.1",
82
+ "@genesislcap/foundation-utils": "14.432.1",
83
+ "@genesislcap/rapid-design-system": "14.432.1",
84
+ "@genesislcap/web-core": "14.432.1",
85
85
  "dompurify": "^3.3.1",
86
86
  "marked": "^17.0.3"
87
87
  },
@@ -93,5 +93,5 @@
93
93
  "publishConfig": {
94
94
  "access": "public"
95
95
  },
96
- "gitHead": "4e523b6a77df321a9c70fcdf7c12a4ffcb98769a"
96
+ "gitHead": "db069baa1e602aba41b0a8eb54a5d48823b962de"
97
97
  }
@@ -72,6 +72,12 @@ export class AiChatInteractionWrapper extends GenesisElement {
72
72
  element.data = this.data;
73
73
  }
74
74
  element.resolved = this.resolved;
75
+ // Pierce the wrapper's shadow boundary to read engagement from the
76
+ // assistant host. False if the widget is already resolved — re-renders
77
+ // from history (layout remount, dock/undock) must never steal focus.
78
+ const root = this.getRootNode();
79
+ const host = root instanceof ShadowRoot ? (root.host as { isEngaged?: boolean }) : null;
80
+ element.shouldAutoFocus = !this.resolved && !!host?.isEngaged;
75
81
 
76
82
  // Handle completion from the inner component.
77
83
  // Guard against re-emission if the interaction was already resolved
@@ -1,5 +1,21 @@
1
1
  import type { ChatMessage, ChatToolDefinition, ChatToolHandlers } from '@genesislcap/foundation-ai';
2
2
 
3
+ /**
4
+ * Controls how the main chat input area behaves while this agent is executing
5
+ * (i.e. while `state === 'loading'` — covers both LLM thinking and pending
6
+ * widget interactions).
7
+ *
8
+ * - `'disabled'` (default): input stays visible but disabled.
9
+ * - `'hidden'`: the entire input row (attach button, textarea, send button) is
10
+ * hidden. Useful for agents whose interaction widgets contain their own
11
+ * input affordances and where a second disabled chat input is redundant or
12
+ * confusing — typically long-running agents (planners) where one transition
13
+ * in/out is less jarring than mid-run flicker.
14
+ *
15
+ * @beta
16
+ */
17
+ export type ChatInputDuringExecutionMode = 'disabled' | 'hidden';
18
+
3
19
  interface BaseAgentConfig {
4
20
  /**
5
21
  * Display name shown in the chat header when this agent is active.
@@ -26,6 +42,11 @@ interface BaseAgentConfig {
26
42
  * Sub-agents available to this agent's tool handlers via `requestSubAgent`.
27
43
  */
28
44
  subAgents?: AgentConfig[];
45
+ /**
46
+ * How the main chat input area behaves while this agent is executing.
47
+ * Defaults to `'disabled'`. See {@link ChatInputDuringExecutionMode}.
48
+ */
49
+ chatInputDuringExecution?: ChatInputDuringExecutionMode;
29
50
  }
30
51
 
31
52
  /**
@@ -499,7 +499,6 @@ ${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<stro
499
499
  </div>
500
500
  `,
501
501
  )}
502
-
503
502
  ${when(
504
503
  (x) => x.chatConfig.suggestions?.behavior !== 'never',
505
504
  html<FoundationAiAssistant>`
@@ -510,51 +509,57 @@ ${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<stro
510
509
  ></chat-suggestions>
511
510
  `,
512
511
  )}
513
-
514
- <div class="input-row" part="input-row">
515
- ${when(
516
- (x) => !!x.chatConfig.ui?.acceptedFiles,
517
- html<FoundationAiAssistant>`
518
- <${buttonTag}
519
- class="attach-button"
520
- part="attach-button"
521
- appearance="stealth"
522
- title=${(x) => `Attach file (${x.chatConfig.ui?.acceptedFiles})`}
512
+ ${when(
513
+ (x) => !(x.state === 'loading' && x.activeAgent?.chatInputDuringExecution === 'hidden'),
514
+ html<FoundationAiAssistant>`
515
+ <div class="input-row" part="input-row">
516
+ ${when(
517
+ (x) => !!x.chatConfig.ui?.acceptedFiles,
518
+ html<FoundationAiAssistant>`
519
+ <${buttonTag}
520
+ class="attach-button"
521
+ part="attach-button"
522
+ appearance="stealth"
523
+ title=${(x) => `Attach file (${x.chatConfig.ui?.acceptedFiles})`}
524
+ ?disabled=${(x) => x.state === 'loading'}
525
+ @click=${(x) => x.triggerFileInput()}
526
+ ><${iconTag} name="paperclip"></${iconTag}></${buttonTag}>
527
+ `,
528
+ )}
529
+ <${textareaTag}
530
+ ${ref('chatInputEl')}
531
+ class="chat-input"
532
+ part="input"
533
+ placeholder=${(x) => x.placeholder}
534
+ :value=${(x) => x.inputValue}
523
535
  ?disabled=${(x) => x.state === 'loading'}
524
- @click=${(x) => x.triggerFileInput()}
525
- ><${iconTag} name="paperclip"></${iconTag}></${buttonTag}>
526
- `,
527
- )}
528
- <${textareaTag}
529
- class="chat-input"
530
- part="input"
531
- placeholder=${(x) => x.placeholder}
532
- :value=${(x) => x.inputValue}
533
- ?disabled=${(x) => x.state === 'loading'}
534
- @input=${(x, c) => (x.inputValue = (c.event.target as any).value)}
535
- @keydown=${(x, c) => {
536
- if (
537
- (c.event as KeyboardEvent).key === 'Enter' &&
538
- !(c.event as KeyboardEvent).shiftKey
539
- ) {
540
- c.event.preventDefault();
541
- x.handleSendClick();
542
- return false;
543
- }
544
- return true;
545
- }}
546
- ></${textareaTag}>
547
- <${buttonTag}
548
- class="send-button"
549
- part="send-button"
550
- ?disabled=${(x) => x.state === 'loading' || (!x.inputValue.trim() && !x.attachments.length)}
551
- @click=${(x) => x.handleSendClick()}
552
- >Send</${buttonTag}>
553
- </div>
536
+ @input=${(x, c) => (x.inputValue = (c.event.target as any).value)}
537
+ @keydown=${(x, c) => {
538
+ if (
539
+ (c.event as KeyboardEvent).key === 'Enter' &&
540
+ !(c.event as KeyboardEvent).shiftKey
541
+ ) {
542
+ c.event.preventDefault();
543
+ x.handleSendClick();
544
+ return false;
545
+ }
546
+ return true;
547
+ }}
548
+ ></${textareaTag}>
549
+ <${buttonTag}
550
+ class="send-button"
551
+ part="send-button"
552
+ ?disabled=${(x) => x.state === 'loading' || (!x.inputValue.trim() && !x.attachments.length)}
553
+ @click=${(x) => x.handleSendClick()}
554
+ >Send</${buttonTag}>
555
+ </div>
556
+ `,
557
+ )}
554
558
  <ai-halo-overlay
555
559
  part="halo-overlay"
556
560
  ?active=${(x) => x.showHalo !== 'no' && x.enabledAnimations.includes('halo')}
557
- :speed="${(x) => (x.showHalo === 'orchestrating' ? HALO_SPEED_ORCHESTRATING : HALO_SPEED_DEFAULT)}"
561
+ :speed="${(x) =>
562
+ x.showHalo === 'orchestrating' ? HALO_SPEED_ORCHESTRATING : HALO_SPEED_DEFAULT}"
558
563
  direction="${(x) => (x.showHalo === 'orchestrating' ? 'ccw' : 'cw')}"
559
564
  :borderSize="${(x) => (x.showHalo === 'orchestrating' ? 2 : HALO_BORDER_SIZE_DEFAULT)}"
560
565
  ></ai-halo-overlay>
package/src/main/main.ts CHANGED
@@ -145,8 +145,32 @@ export class FoundationAiAssistant extends GenesisElement {
145
145
  return this._sessionRef?.store.aiAssistant.state ?? 'idle';
146
146
  }
147
147
  set state(value: AiAssistantState) {
148
+ const prev = this._sessionRef?.store.aiAssistant.state;
148
149
  this._sessionRef?.actions.aiAssistant.setState(value);
149
150
  this.syncShowHalo();
151
+ // When the agent finishes (loading → !loading) the input row reappears (or
152
+ // becomes enabled) — refocus it so the user can type immediately, but only
153
+ // if they haven't moved on to something else on the page.
154
+ if (prev === 'loading' && value !== 'loading') {
155
+ this.maybeAutoFocusChatInput();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Focus the main chat input if the user is still engaged with the assistant.
161
+ * Deferred to the next frame so any conditional re-render (e.g. unhiding the
162
+ * input row when an agent's `chatInputDuringExecution: 'hidden'` execution
163
+ * ends) has committed before we call `.focus()`.
164
+ *
165
+ * `rapid-text-area` does not delegate focus to its inner native `<textarea>`,
166
+ * so we drill into its shadow root and focus the textarea directly.
167
+ */
168
+ private maybeAutoFocusChatInput() {
169
+ if (!this.isEngaged) return;
170
+ requestAnimationFrame(() => {
171
+ if (!this.isEngaged || !this.isConnected) return;
172
+ this.chatInputEl?.shadowRoot?.querySelector<HTMLTextAreaElement>('textarea')?.focus();
173
+ });
150
174
  }
151
175
 
152
176
  get activeAgent(): Omit<AgentConfig, 'toolHandlers'> | undefined {
@@ -257,6 +281,20 @@ export class FoundationAiAssistant extends GenesisElement {
257
281
  private _driverAgentsKey?: string;
258
282
  /** Bound to the messages container via the template ref — assigned by FAST before connectedCallback logic runs. */
259
283
  messagesEl?: HTMLElement;
284
+ /** Bound to the rapid-text-area chat input via the template ref. */
285
+ chatInputEl?: HTMLElement;
286
+ /**
287
+ * True when the user's last interaction (pointer or keyboard focus) was inside
288
+ * the assistant. Defaults to true so the assistant claims focus on first mount.
289
+ * Auto-focus features (chat input on idle, widget inputs on mount) gate on this
290
+ * to avoid stealing focus from elsewhere on the page.
291
+ */
292
+ @observable isEngaged: boolean = true;
293
+ private _handleGlobalInteraction = (e: Event) => {
294
+ const next = e.composedPath().includes(this);
295
+ if (next === this.isEngaged) return;
296
+ this.isEngaged = next;
297
+ };
260
298
  /** True when the user has intentionally scrolled away from the bottom — suppresses auto-scroll. */
261
299
  private _userScrolledAway = false;
262
300
  private _scrollListener?: () => void;
@@ -553,6 +591,7 @@ export class FoundationAiAssistant extends GenesisElement {
553
591
  this.stopLoadingTimer();
554
592
  this.syncShowHalo();
555
593
  this.fetchSuggestions();
594
+ this.maybeAutoFocusChatInput();
556
595
  this._executionCompletionUnsub?.();
557
596
  this._executionCompletionUnsub = undefined;
558
597
  }
@@ -569,6 +608,13 @@ export class FoundationAiAssistant extends GenesisElement {
569
608
  };
570
609
  this.messagesEl.addEventListener('scroll', this._scrollListener);
571
610
  }
611
+ // Track whether the user's most recent interaction was inside this assistant.
612
+ // Capture phase + composedPath() pierces shadow boundaries reliably.
613
+ document.addEventListener('pointerdown', this._handleGlobalInteraction, true);
614
+ document.addEventListener('focusin', this._handleGlobalInteraction, true);
615
+ // Initial focus on mount when idle (skips during a mid-lifecycle reattach
616
+ // where state is loading and the input may be hidden or disabled).
617
+ if (this.state !== 'loading') this.maybeAutoFocusChatInput();
572
618
  logger.debug('FoundationAiAssistant connected');
573
619
  }
574
620
 
@@ -586,6 +632,8 @@ export class FoundationAiAssistant extends GenesisElement {
586
632
  }
587
633
  this._scrollListener = undefined;
588
634
  this._userScrolledAway = false;
635
+ document.removeEventListener('pointerdown', this._handleGlobalInteraction, true);
636
+ document.removeEventListener('focusin', this._handleGlobalInteraction, true);
589
637
  // Clear local references only — driver and store stay in their registries.
590
638
  this.driver = undefined;
591
639
  this._sessionRef = undefined;
@@ -972,18 +1020,13 @@ export class FoundationAiAssistant extends GenesisElement {
972
1020
  capturedSessionRef?.actions.aiAssistant.setState('idle');
973
1021
  this.syncShowHalo();
974
1022
  this.fetchSuggestions();
975
- this.restoreFocusIfAppropriate();
1023
+ // setState was dispatched directly via the captured ref (not via the
1024
+ // state setter) so the setter's auto-focus path doesn't fire here. Call
1025
+ // it explicitly so the chat input refocuses after the run completes.
1026
+ this.maybeAutoFocusChatInput();
976
1027
  }
977
1028
  }
978
1029
 
979
- private restoreFocusIfAppropriate() {
980
- const active = document.activeElement;
981
- if (active !== document.body && active !== this) return;
982
- requestAnimationFrame(() => {
983
- this.shadowRoot?.querySelector<HTMLElement>('.chat-input')?.focus();
984
- });
985
- }
986
-
987
1030
  onChatHeaderMouseDown(e: MouseEvent) {
988
1031
  if (this.popoutMode === 'collapse') return;
989
1032
  e.preventDefault();
@@ -2,7 +2,8 @@ import type { InteractionResult } from '@genesislcap/foundation-ai';
2
2
 
3
3
  /**
4
4
  * Interface that AI inline interaction components can implement.
5
- * The `AiChatInteractionWrapper` always sets `data` and `resolved` on the rendered element.
5
+ * The `AiChatInteractionWrapper` always sets `data`, `resolved`, and
6
+ * `shouldAutoFocus` on the rendered element.
6
7
  *
7
8
  * @beta
8
9
  */
@@ -10,4 +11,14 @@ export interface AiChatWidget {
10
11
  data: unknown;
11
12
  /** The result when the interaction has been resolved. Truthy = resolved; value contains status/payload for display. */
12
13
  resolved?: InteractionResult<unknown>;
14
+ /**
15
+ * True when the widget is being mounted fresh and the assistant currently has
16
+ * the user's engagement (last pointer/focus interaction was inside the
17
+ * assistant). Widgets with input fields should use this to decide whether to
18
+ * call `.focus()` on mount. False when:
19
+ * - the user has interacted with something outside the assistant, or
20
+ * - this widget has already been resolved (we're re-rendering history due
21
+ * to a layout-lifecycle remount, dock/undock, etc.).
22
+ */
23
+ shouldAutoFocus?: boolean;
13
24
  }