@genesislcap/ai-assistant 14.451.1-alpha-3c3e1d3.0 → 14.451.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.
Files changed (27) hide show
  1. package/dist/ai-assistant.api.json +36 -1
  2. package/dist/ai-assistant.d.ts +14 -3
  3. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +11 -0
  4. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
  5. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +2 -0
  6. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +1 -0
  7. package/dist/dts/main/main.d.ts +14 -3
  8. package/dist/dts/main/main.d.ts.map +1 -1
  9. package/dist/dts/main/main.template.d.ts.map +1 -1
  10. package/dist/dts/utils/message-partition.d.ts +37 -0
  11. package/dist/dts/utils/message-partition.d.ts.map +1 -0
  12. package/dist/dts/utils/message-partition.test.d.ts +2 -0
  13. package/dist/dts/utils/message-partition.test.d.ts.map +1 -0
  14. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +13 -0
  15. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +77 -0
  16. package/dist/esm/main/main.js +23 -23
  17. package/dist/esm/main/main.template.js +1 -0
  18. package/dist/esm/utils/message-partition.js +49 -0
  19. package/dist/esm/utils/message-partition.test.js +69 -0
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/package.json +16 -16
  22. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +105 -0
  23. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +14 -0
  24. package/src/main/main.template.ts +1 -0
  25. package/src/main/main.ts +24 -22
  26. package/src/utils/message-partition.test.ts +101 -0
  27. package/src/utils/message-partition.ts +66 -0
@@ -4096,6 +4096,41 @@
4096
4096
  "isProtected": false,
4097
4097
  "isAbstract": false
4098
4098
  },
4099
+ {
4100
+ "kind": "Property",
4101
+ "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#activeInteractionRow:member",
4102
+ "docComment": "/**\n * The trailing interaction, rendered in a pinned slot below the scrolling list rather than inside it. Reads only `messages` (never the toggle state), so a cog-menu toggle leaves this binding — and the live widget's DOM — untouched. See `trailingInteractionRow` for the rationale.\n */\n",
4103
+ "excerptTokens": [
4104
+ {
4105
+ "kind": "Content",
4106
+ "text": "get activeInteractionRow(): "
4107
+ },
4108
+ {
4109
+ "kind": "Reference",
4110
+ "text": "ChatMessage",
4111
+ "canonicalReference": "@genesislcap/foundation-ai!ChatMessage:interface"
4112
+ },
4113
+ {
4114
+ "kind": "Content",
4115
+ "text": "[]"
4116
+ },
4117
+ {
4118
+ "kind": "Content",
4119
+ "text": ";"
4120
+ }
4121
+ ],
4122
+ "isReadonly": true,
4123
+ "isOptional": false,
4124
+ "releaseTag": "Beta",
4125
+ "name": "activeInteractionRow",
4126
+ "propertyTypeTokenRange": {
4127
+ "startIndex": 1,
4128
+ "endIndex": 3
4129
+ },
4130
+ "isStatic": false,
4131
+ "isProtected": false,
4132
+ "isAbstract": false
4133
+ },
4099
4134
  {
4100
4135
  "kind": "Property",
4101
4136
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#activeModel:member",
@@ -6903,7 +6938,7 @@
6903
6938
  {
6904
6939
  "kind": "Property",
6905
6940
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#visibleMessages:member",
6906
- "docComment": "/**\n * Messages filtered by the current toggle state. Tool-related messages (those with toolCalls or toolResult) are hidden when `showToolCalls` is false.\n *\n * Marked `@volatile` because the filter branches conditionally access `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator` depending on message content. Without it, FAST would only track the observables accessed on the last evaluation and miss toggle changes that happen to hit an untracked branch.\n */\n",
6941
+ "docComment": "/**\n * Messages rendered in the scrolling list, filtered by the current toggle state. Tool-related messages (those with toolCalls or toolResult) are hidden when `showToolCalls` is false.\n *\n * Excludes the trailing interaction (see `activeInteractionRow`), which is rendered in a separate pinned slot so toggling the message filters never re-creates a live interaction widget.\n *\n * Marked `@volatile` because the filter branches conditionally access `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator` depending on message content. Without it, FAST would only track the observables accessed on the last evaluation and miss toggle changes that happen to hit an untracked branch.\n */\n",
6907
6942
  "excerptTokens": [
6908
6943
  {
6909
6944
  "kind": "Content",
@@ -1175,9 +1175,13 @@ export declare class FoundationAiAssistant extends GenesisElement {
1175
1175
  showHaloChanged(): void;
1176
1176
  private getActiveToolNames;
1177
1177
  /**
1178
- * Messages filtered by the current toggle state.
1179
- * Tool-related messages (those with toolCalls or toolResult) are hidden when
1180
- * `showToolCalls` is false.
1178
+ * Messages rendered in the scrolling list, filtered by the current toggle
1179
+ * state. Tool-related messages (those with toolCalls or toolResult) are hidden
1180
+ * when `showToolCalls` is false.
1181
+ *
1182
+ * Excludes the trailing interaction (see `activeInteractionRow`), which is
1183
+ * rendered in a separate pinned slot so toggling the message filters never
1184
+ * re-creates a live interaction widget.
1181
1185
  *
1182
1186
  * Marked `@volatile` because the filter branches conditionally access
1183
1187
  * `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator`
@@ -1186,6 +1190,13 @@ export declare class FoundationAiAssistant extends GenesisElement {
1186
1190
  * happen to hit an untracked branch.
1187
1191
  */
1188
1192
  get visibleMessages(): ChatMessage[];
1193
+ /**
1194
+ * The trailing interaction, rendered in a pinned slot below the scrolling
1195
+ * list rather than inside it. Reads only `messages` (never the toggle state),
1196
+ * so a cog-menu toggle leaves this binding — and the live widget's DOM —
1197
+ * untouched. See `trailingInteractionRow` for the rationale.
1198
+ */
1199
+ get activeInteractionRow(): ChatMessage[];
1189
1200
  agentsChanged(): void;
1190
1201
  /** Returns a stable fingerprint for an agents array based on agent names and tool handler keys. */
1191
1202
  private getAgentsKey;
@@ -27,6 +27,17 @@ export declare class AiChatInteractionWrapper extends GenesisElement {
27
27
  connectedCallback(): void;
28
28
  disconnectedCallback(): void;
29
29
  componentNameChanged(): void;
30
+ /**
31
+ * Re-render the hosted widget when the interaction identity changes. This
32
+ * ensures widgets don't go stale with them being rendered with `recycle: true`
33
+ * when messages are dynamically filtered, showing/hiding thinking/tool calls
34
+ *
35
+ * `interactionId` is a per-interaction UUID, so it changes exactly when the
36
+ * hosted interaction does. Property bindings apply in template order
37
+ * (componentName, data, interactionId), so `data` is already rebound by the
38
+ * time this fires and the fresh widget is built with the correct data.
39
+ */
40
+ interactionIdChanged(): void;
30
41
  resolvedChanged(): void;
31
42
  /**
32
43
  * Watch the rendered widget element for box-size changes. When the widget
@@ -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,qGAAqG;IACrG,OAAO,CAAC,eAAe,CAAC,CAAiB;IAEzC,iBAAiB;IAKjB,oBAAoB;IAMpB,oBAAoB;IAIpB,eAAe;IAOf;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,eAAe;CAuDxB"}
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,qGAAqG;IACrG,OAAO,CAAC,eAAe,CAAC,CAAiB;IAEzC,iBAAiB;IAKjB,oBAAoB;IAMpB,oBAAoB;IAIpB;;;;;;;;;OASG;IACH,oBAAoB;IAIpB,eAAe;IAOf;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,eAAe;CAuDxB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=chat-interaction-wrapper.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-interaction-wrapper.test.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts"],"names":[],"mappings":""}
@@ -220,9 +220,13 @@ export declare class FoundationAiAssistant extends GenesisElement {
220
220
  showHaloChanged(): void;
221
221
  private getActiveToolNames;
222
222
  /**
223
- * Messages filtered by the current toggle state.
224
- * Tool-related messages (those with toolCalls or toolResult) are hidden when
225
- * `showToolCalls` is false.
223
+ * Messages rendered in the scrolling list, filtered by the current toggle
224
+ * state. Tool-related messages (those with toolCalls or toolResult) are hidden
225
+ * when `showToolCalls` is false.
226
+ *
227
+ * Excludes the trailing interaction (see `activeInteractionRow`), which is
228
+ * rendered in a separate pinned slot so toggling the message filters never
229
+ * re-creates a live interaction widget.
226
230
  *
227
231
  * Marked `@volatile` because the filter branches conditionally access
228
232
  * `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator`
@@ -231,6 +235,13 @@ export declare class FoundationAiAssistant extends GenesisElement {
231
235
  * happen to hit an untracked branch.
232
236
  */
233
237
  get visibleMessages(): ChatMessage[];
238
+ /**
239
+ * The trailing interaction, rendered in a pinned slot below the scrolling
240
+ * list rather than inside it. Reads only `messages` (never the toggle state),
241
+ * so a cog-menu toggle leaves this binding — and the live widget's DOM —
242
+ * untouched. See `trailingInteractionRow` for the rationale.
243
+ */
244
+ get activeInteractionRow(): ChatMessage[];
234
245
  agentsChanged(): void;
235
246
  /** Returns a stable fingerprint for an agents array based on agent names and tool handler keys. */
236
247
  private getAgentsKey;
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACV,6BAA6B,EAC7B,cAAc,EACd,UAAU,EACV,4BAA4B,EAC5B,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,EAGL,cAAc,EAIf,MAAM,uBAAuB,CAAC;AAW/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAgBlC,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,cAAc,CAAC;AAmEtB;;;;;;;;;;;;;;;;GAgBG;AACH,qBAOa,qBAAsB,SAAQ,cAAc;IACnC,gBAAgB,EAAG,kBAAkB,CAAC;IAE9C,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;IAExC;;;OAGG;IACH,IAAI,WAAW,IAAI,eAAe,CAEjC;IAED;;;;;;OAMG;IACH,IACI,sBAAsB,IAAI,OAAO,CAKpC;IAEW,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,EAoBhC;IAED;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAM/B;;;;;;;;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;;;OAGG;IACH,IAAI,eAAe,IAAI,OAAO,CAE7B;IACD,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,EAEjC;IAED;;;;;;;;;OASG;IACH,IAAI,eAAe,IAAI,MAAM,GAAG,IAAI,CAEnC;IACD,IAAI,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAavC;IAED;;;;;;;;OAQG;IACH,IAAI,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAEtC;IACD,IAAI,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAK1C;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;;;OAGG;IACH,IAAI,cAAc,0DAEjB;IAED;;;;;OAKG;IACH,IACI,iCAAiC,IAAI,4BAA4B,CAKpE;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;IAED,kEAAkE;IAClE,IAAI,cAAc,IAAI,MAAM,CAE3B;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,EAE/B;IAED,gEAAgE;IAChE,IAAI,WAAW,IAAI,MAAM,GAAG,SAAS,CAEpC;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAExC;IAED,4DAA4D;IAC5D,IAAI,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAE3C;IACD,IAAI,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAE/C;IAED,+DAA+D;IAC/D,IAAI,gBAAgB,IAAI,6BAA6B,EAAE,CAEtD;IACD,IAAI,gBAAgB,CAAC,KAAK,EAAE,6BAA6B,EAAE,EAE1D;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,0FAA0F;IAC1F,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAM;IACxD,yFAAyF;IACzF,OAAO,CAAC,wBAAwB,CAAC,CAAS;IAC1C,0FAA0F;IAC1F,OAAO,CAAC,wBAAwB,CAAS;IACzC;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAE3C;IACF;;;;;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;IA4CrB,mGAAmG;IACnG,OAAO,CAAC,YAAY;IAmBpB;;;;OAIG;IACH;;;;;;OAMG;IACH,OAAO,CAAC,6BAA6B;IAoBrC,OAAO,CAAC,YAAY;IA2CpB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IA+IlB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAKpB,iBAAiB;IAyGjB,oBAAoB;YA6BN,mBAAmB;YAgBnB,oBAAoB;IAQlC,iBAAiB;IAIjB,oBAAoB;IAWpB,OAAO,CAAC,iBAAiB;IAIzB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA4E5B,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;IAUpB,4FAA4F;IAC5F,OAAO,CAAC,WAAW;IAKnB;;;;;;OAMG;IACH,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,QAAQ,CAAC,eAAe,CAQ9B;IAEF,cAAc;IAKd,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAQjC;IAEF,iBAAiB;IAKjB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO;IAWnE,gGAAgG;IAChG,IACI,kBAAkB,IAAI,OAAO,CAIhC;IAED,2FAA2F;IAC3F,IACI,eAAe,IAAI,MAAM,GAAG,SAAS,CAGxC;IAED;;;;;;;;OAQG;IACH,IACI,SAAS,IAAI,OAAO,CAEvB;IAED,iDAAiD;IACjD,IACI,gBAAgB,IAAI,MAAM,CAc7B;IAED;;;;;;OAMG;IACH,IACI,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAM1C;IAED;;;;OAIG;IACH,IACI,oBAAoB,IAAI,MAAM,CAGjC;IAED,mBAAmB;IAInB,uBAAuB;IAIvB,8BAA8B;IAI9B,oBAAoB,CAAC,UAAU,EAAE,oBAAoB,EAAE;IAIvD,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsHX,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,YAAY;YAoCZ,iBAAiB;IAe/B,eAAe;IAIf,qBAAqB,CAAC,UAAU,EAAE,MAAM;IAKxC;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,aAAa,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC;YA0C7E,gBAAgB;YA6DhB,IAAI;IAyClB,qBAAqB,CAAC,CAAC,EAAE,UAAU;IAYnC,0BAA0B,CAAC,CAAC,EAAE,KAAK;CAQpC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.template.d.ts","sourceRoot":"","sources":["../../../src/main/main.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,OAAO,EAA2B,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAoIpD,gBAAgB;AAChB,eAAO,MAAM,6BAA6B,GACxC,oBAAoB,MAAM,KACzB,YAAY,CAAC,qBAAqB,CA6hBpC,CAAC"}
1
+ {"version":3,"file":"main.template.d.ts","sourceRoot":"","sources":["../../../src/main/main.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,OAAO,EAA2B,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAoIpD,gBAAgB;AAChB,eAAO,MAAM,6BAA6B,GACxC,oBAAoB,MAAM,KACzB,YAAY,CAAC,qBAAqB,CA8hBpC,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { ChatMessage } from '@genesislcap/foundation-ai';
2
+ /**
3
+ * Toggle state that drives which messages appear in the scrolling list. Mirrors
4
+ * the cog-menu switches; `showAgentSwitchIndicator` is pre-resolved by the
5
+ * caller (it falls back to `showToolCalls` when the host hasn't configured an
6
+ * explicit agent-switch toggle).
7
+ */
8
+ export interface MessageFilterState {
9
+ showToolCalls: boolean;
10
+ showThinkingSteps: boolean;
11
+ showAgentSwitchIndicator: boolean;
12
+ }
13
+ /**
14
+ * The trailing interaction to render in a pinned slot, separate from the
15
+ * filtered list. Empty unless the most recent message carries an interaction.
16
+ *
17
+ * Pinning the trailing interaction keeps its widget out of the list's
18
+ * reconciliation: FAST's `repeat` has no key function and reconciles by array
19
+ * index, so re-filtering the list (a cog-menu toggle) would otherwise rebind an
20
+ * existing row to a different message and re-create the live widget — discarding
21
+ * any in-progress input. The slot's binding reads only `messages` and returns
22
+ * the same message object across toggles, so the widget's DOM is preserved.
23
+ *
24
+ * It is the *trailing* message (resolved or not), not "unresolved only", so that
25
+ * answering an interaction leaves it in the slot — `resolved` is forwarded in
26
+ * place with no re-render. It rejoins the list naturally once a newer message
27
+ * arrives and it is no longer last.
28
+ */
29
+ export declare function trailingInteractionRow(messages: ChatMessage[]): ChatMessage[];
30
+ /**
31
+ * Filters messages for the scrolling list per the current cog-menu toggles.
32
+ *
33
+ * The trailing interaction (see `trailingInteractionRow`) must be removed
34
+ * before calling, as it is rendered separately.
35
+ */
36
+ export declare function filterVisibleMessages(messages: ChatMessage[], toggles: MessageFilterState): ChatMessage[];
37
+ //# sourceMappingURL=message-partition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-partition.d.ts","sourceRoot":"","sources":["../../../src/utils/message-partition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,kBAAkB,GAC1B,WAAW,EAAE,CAqBf"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=message-partition.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-partition.test.d.ts","sourceRoot":"","sources":["../../../src/utils/message-partition.test.ts"],"names":[],"mappings":""}
@@ -32,6 +32,19 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
32
32
  componentNameChanged() {
33
33
  this.renderComponent();
34
34
  }
35
+ /**
36
+ * Re-render the hosted widget when the interaction identity changes. This
37
+ * ensures widgets don't go stale with them being rendered with `recycle: true`
38
+ * when messages are dynamically filtered, showing/hiding thinking/tool calls
39
+ *
40
+ * `interactionId` is a per-interaction UUID, so it changes exactly when the
41
+ * hosted interaction does. Property bindings apply in template order
42
+ * (componentName, data, interactionId), so `data` is already rebound by the
43
+ * time this fires and the fresh widget is built with the correct data.
44
+ */
45
+ interactionIdChanged() {
46
+ this.renderComponent();
47
+ }
35
48
  resolvedChanged() {
36
49
  var _a;
37
50
  const element = (_a = this.container) === null || _a === void 0 ? void 0 : _a.firstElementChild;
@@ -0,0 +1,77 @@
1
+ import { __awaiter } from "tslib";
2
+ import { assert, createComponentSuite } from '@genesislcap/foundation-testing';
3
+ import { DOM } from '@genesislcap/web-core';
4
+ import { AiChatInteractionWrapper } from './chat-interaction-wrapper';
5
+ // Hold a reference so the custom-element registration isn't tree-shaken.
6
+ AiChatInteractionWrapper;
7
+ /**
8
+ * Minimal stand-in for a real interaction widget. The wrapper instantiates the
9
+ * element named by `componentName` and assigns `data`/`resolved` onto it; the
10
+ * tests read those back to verify which interaction the rendered widget belongs
11
+ * to.
12
+ */
13
+ const FAKE_WIDGET = 'test-interaction-widget';
14
+ if (!customElements.get(FAKE_WIDGET)) {
15
+ customElements.define(FAKE_WIDGET, class extends HTMLElement {
16
+ });
17
+ }
18
+ /**
19
+ * Renders a first interaction into a freshly-mounted wrapper.
20
+ *
21
+ * `componentName` is assigned last on purpose. The suite hands us an
22
+ * already-connected element, so the only render trigger left is
23
+ * `componentNameChanged`; setting it last means that render sees `data` and
24
+ * `interactionId` already in place — reproducing production, where the initial
25
+ * widget renders in `connectedCallback` with every bound property present. This
26
+ * keeps the *first* render correct on both the patched and unpatched code so
27
+ * that the id-change rebind is the only behaviour each test isolates.
28
+ */
29
+ function renderFirstInteraction(element) {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ yield DOM.nextUpdate();
32
+ element.data = { label: 'interaction-a' };
33
+ element.interactionId = 'id-a';
34
+ element.componentName = FAKE_WIDGET;
35
+ yield DOM.nextUpdate();
36
+ });
37
+ }
38
+ const Suite = createComponentSuite('AiChatInteractionWrapper', 'ai-chat-interaction-wrapper');
39
+ /**
40
+ * Regression for the cog-menu toggle misalignment: FAST's `repeat` reconciles
41
+ * the message list by array index (it has no key function), so toggling "Tool
42
+ * calls"/"Thinking" re-filters the visible messages and rebinds an existing
43
+ * row's wrapper to a *different* interaction. When both interactions use the
44
+ * same component, `componentNameChanged` doesn't fire — so the wrapper must
45
+ * re-render on `interactionId` change, or the previous interaction's widget
46
+ * stays mounted under the new message.
47
+ */
48
+ Suite('re-renders the hosted widget when interactionId changes with the same componentName', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
49
+ yield renderFirstInteraction(element);
50
+ const firstWidget = element.container.firstElementChild;
51
+ assert.ok(firstWidget, 'a widget is rendered for the first interaction');
52
+ assert.equal(firstWidget.data, { label: 'interaction-a' });
53
+ // Rebind the reused wrapper to a different interaction that happens to use
54
+ // the same component — `data` then `interactionId`, mirroring the template's
55
+ // property-binding order. `componentName` is unchanged, so the only thing
56
+ // that can drive a re-render is the interactionId hook.
57
+ element.data = { label: 'interaction-b' };
58
+ element.interactionId = 'id-b';
59
+ yield DOM.nextUpdate();
60
+ const secondWidget = element.container.firstElementChild;
61
+ assert.is.not(secondWidget, firstWidget, 'the stale widget is replaced, not left mounted');
62
+ assert.equal(secondWidget.data, { label: 'interaction-b' }, 'the rendered widget reflects the new interaction, not the stale one');
63
+ }));
64
+ /**
65
+ * Resolving an interaction in place (the user answers it) must forward the
66
+ * result without tearing down and rebuilding the widget — only a genuine change
67
+ * of interaction identity should rebuild.
68
+ */
69
+ Suite('forwards resolved in place without rebuilding the widget', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
70
+ yield renderFirstInteraction(element);
71
+ const widget = element.container.firstElementChild;
72
+ element.resolved = { status: 'approved' };
73
+ yield DOM.nextUpdate();
74
+ assert.is(element.container.firstElementChild, widget, 'resolving in place must not rebuild the widget');
75
+ assert.equal(widget.resolved, { status: 'approved' }, 'resolved is forwarded to the widget');
76
+ }));
77
+ Suite.run();
@@ -41,6 +41,7 @@ import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } fr
41
41
  import { ChatSuggestions } from '../suggestions/chat-suggestions';
42
42
  import { AnimatedPanelToggle } from '../utils/animated-panel-toggle';
43
43
  import { logger } from '../utils/logger';
44
+ import { filterVisibleMessages, trailingInteractionRow } from '../utils/message-partition';
44
45
  import { sumCosts } from '../utils/sum-costs';
45
46
  import { expandToolTree } from '../utils/tool-fold';
46
47
  import { styles } from './main.styles';
@@ -504,9 +505,13 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
504
505
  return [];
505
506
  }
506
507
  /**
507
- * Messages filtered by the current toggle state.
508
- * Tool-related messages (those with toolCalls or toolResult) are hidden when
509
- * `showToolCalls` is false.
508
+ * Messages rendered in the scrolling list, filtered by the current toggle
509
+ * state. Tool-related messages (those with toolCalls or toolResult) are hidden
510
+ * when `showToolCalls` is false.
511
+ *
512
+ * Excludes the trailing interaction (see `activeInteractionRow`), which is
513
+ * rendered in a separate pinned slot so toggling the message filters never
514
+ * re-creates a live interaction widget.
510
515
  *
511
516
  * Marked `@volatile` because the filter branches conditionally access
512
517
  * `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator`
@@ -519,28 +524,23 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
519
524
  const showAgentSwitchIndicator = ((_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showAgentSwitchIndicator) != null
520
525
  ? this.showAgentSwitchIndicator
521
526
  : this.showToolCalls;
522
- return this.messages.filter((m) => {
523
- var _a, _b, _c;
524
- // Agent switch indicators are shown when the toggle is on (or showToolCalls implies it)
525
- if (m.role === 'system-event') {
526
- return showAgentSwitchIndicator;
527
- }
528
- // Never show tool messages to the user
529
- if (m.role === 'tool') {
530
- return false;
531
- }
532
- // Filter thinking messages based on toggle
533
- if (m.thinking && !this.showThinkingSteps) {
534
- return false;
535
- }
536
- // Filter out empty assistant messages that might have slipped through
537
- if (m.role === 'assistant' && !((_a = m.content) === null || _a === void 0 ? void 0 : _a.trim()) && !((_b = m.toolCalls) === null || _b === void 0 ? void 0 : _b.length) && !m.interaction) {
538
- return false;
539
- }
540
- const isToolRelated = !!(((_c = m.toolCalls) === null || _c === void 0 ? void 0 : _c.length) || m.toolResult);
541
- return !isToolRelated || this.showToolCalls;
527
+ const { messages } = this;
528
+ const listed = trailingInteractionRow(messages).length ? messages.slice(0, -1) : messages;
529
+ return filterVisibleMessages(listed, {
530
+ showToolCalls: this.showToolCalls,
531
+ showThinkingSteps: this.showThinkingSteps,
532
+ showAgentSwitchIndicator,
542
533
  });
543
534
  }
535
+ /**
536
+ * The trailing interaction, rendered in a pinned slot below the scrolling
537
+ * list rather than inside it. Reads only `messages` (never the toggle state),
538
+ * so a cog-menu toggle leaves this binding — and the live widget's DOM —
539
+ * untouched. See `trailingInteractionRow` for the rationale.
540
+ */
541
+ get activeInteractionRow() {
542
+ return trailingInteractionRow(this.messages);
543
+ }
544
544
  agentsChanged() {
545
545
  var _a, _b, _c;
546
546
  // Guard: driver doesn't exist yet during connectedCallback — createDriver/wireDriver handle that.
@@ -432,6 +432,7 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
432
432
 
433
433
  <div class="messages" part="messages" ${ref('messagesEl')}>
434
434
  ${repeat((x) => x.visibleMessages, messageRowTemplate)}
435
+ ${repeat((x) => x.activeInteractionRow, messageRowTemplate)}
435
436
  ${when((x) => x.liveSubAgentTrace.length > 0 && x.showToolCalls, liveSubAgentTraceTemplate)}
436
437
  ${when((x) => {
437
438
  var _a;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The trailing interaction to render in a pinned slot, separate from the
3
+ * filtered list. Empty unless the most recent message carries an interaction.
4
+ *
5
+ * Pinning the trailing interaction keeps its widget out of the list's
6
+ * reconciliation: FAST's `repeat` has no key function and reconciles by array
7
+ * index, so re-filtering the list (a cog-menu toggle) would otherwise rebind an
8
+ * existing row to a different message and re-create the live widget — discarding
9
+ * any in-progress input. The slot's binding reads only `messages` and returns
10
+ * the same message object across toggles, so the widget's DOM is preserved.
11
+ *
12
+ * It is the *trailing* message (resolved or not), not "unresolved only", so that
13
+ * answering an interaction leaves it in the slot — `resolved` is forwarded in
14
+ * place with no re-render. It rejoins the list naturally once a newer message
15
+ * arrives and it is no longer last.
16
+ */
17
+ export function trailingInteractionRow(messages) {
18
+ const last = messages[messages.length - 1];
19
+ return (last === null || last === void 0 ? void 0 : last.interaction) ? [last] : [];
20
+ }
21
+ /**
22
+ * Filters messages for the scrolling list per the current cog-menu toggles.
23
+ *
24
+ * The trailing interaction (see `trailingInteractionRow`) must be removed
25
+ * before calling, as it is rendered separately.
26
+ */
27
+ export function filterVisibleMessages(messages, toggles) {
28
+ return messages.filter((m) => {
29
+ var _a, _b, _c;
30
+ // Agent switch indicators show when their toggle is on (or showToolCalls implies it).
31
+ if (m.role === 'system-event') {
32
+ return toggles.showAgentSwitchIndicator;
33
+ }
34
+ // Tool result messages are never shown to the user.
35
+ if (m.role === 'tool') {
36
+ return false;
37
+ }
38
+ // Thinking messages follow their own toggle.
39
+ if (m.thinking && !toggles.showThinkingSteps) {
40
+ return false;
41
+ }
42
+ // Drop empty assistant messages that slipped through.
43
+ if (m.role === 'assistant' && !((_a = m.content) === null || _a === void 0 ? void 0 : _a.trim()) && !((_b = m.toolCalls) === null || _b === void 0 ? void 0 : _b.length) && !m.interaction) {
44
+ return false;
45
+ }
46
+ const isToolRelated = !!(((_c = m.toolCalls) === null || _c === void 0 ? void 0 : _c.length) || m.toolResult);
47
+ return !isToolRelated || toggles.showToolCalls;
48
+ });
49
+ }
@@ -0,0 +1,69 @@
1
+ import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
+ import { filterVisibleMessages, trailingInteractionRow, } from './message-partition';
3
+ const msg = (overrides = {}) => (Object.assign({ role: 'assistant', content: '' }, overrides));
4
+ const interaction = (interactionId, resolved = false) => (Object.assign({ interactionId, componentName: 'test-widget', data: {} }, (resolved ? { resolved: { status: 'approved' } } : {})));
5
+ const ALL_ON = {
6
+ showToolCalls: true,
7
+ showThinkingSteps: true,
8
+ showAgentSwitchIndicator: true,
9
+ };
10
+ const ALL_OFF = {
11
+ showToolCalls: false,
12
+ showThinkingSteps: false,
13
+ showAgentSwitchIndicator: false,
14
+ };
15
+ const trailing = createLogicSuite('trailingInteractionRow');
16
+ trailing('is empty when there are no messages', () => {
17
+ assert.equal(trailingInteractionRow([]), []);
18
+ });
19
+ trailing('is empty when the last message has no interaction', () => {
20
+ assert.equal(trailingInteractionRow([msg({ content: 'hi' })]), []);
21
+ });
22
+ trailing('returns the last message when it carries an interaction (pending)', () => {
23
+ const pending = msg({ interaction: interaction('id-a') });
24
+ assert.equal(trailingInteractionRow([msg({ content: 'q' }), pending]), [pending]);
25
+ });
26
+ trailing('still returns the trailing interaction once it is resolved', () => {
27
+ // Keeps it in the pinned slot so resolving forwards in place without a rebuild.
28
+ const resolved = msg({ interaction: interaction('id-a', true) });
29
+ assert.equal(trailingInteractionRow([resolved]), [resolved]);
30
+ });
31
+ trailing('returns nothing once a newer non-interaction message arrives', () => {
32
+ // The interaction is no longer last, so it rejoins the scrolling list.
33
+ const earlier = msg({ interaction: interaction('id-a', true) });
34
+ assert.equal(trailingInteractionRow([earlier, msg({ content: 'next' })]), []);
35
+ });
36
+ trailing('returns the same message reference (lets FAST short-circuit rebind)', () => {
37
+ const pending = msg({ interaction: interaction('id-a') });
38
+ assert.is(trailingInteractionRow([pending])[0], pending);
39
+ });
40
+ const filter = createLogicSuite('filterVisibleMessages');
41
+ filter('hides tool result messages regardless of toggles', () => {
42
+ const msgs = [msg({ role: 'tool', toolResult: { toolCallId: 't1', content: 'r' } })];
43
+ assert.equal(filterVisibleMessages(msgs, ALL_ON), []);
44
+ });
45
+ filter('hides tool-call messages when showToolCalls is off, shows them when on', () => {
46
+ const toolCall = msg({ toolCalls: [{ id: 't1', name: 'do', args: {} }] });
47
+ assert.equal(filterVisibleMessages([toolCall], ALL_OFF), []);
48
+ assert.equal(filterVisibleMessages([toolCall], ALL_ON), [toolCall]);
49
+ });
50
+ filter('hides thinking messages when showThinkingSteps is off', () => {
51
+ const thinking = msg({ thinking: true, content: 'hmm' });
52
+ assert.equal(filterVisibleMessages([thinking], ALL_OFF), []);
53
+ assert.equal(filterVisibleMessages([thinking], ALL_ON), [thinking]);
54
+ });
55
+ filter('hides system-event rows unless the agent-switch toggle is on', () => {
56
+ const event = msg({ role: 'system-event', content: 'switched' });
57
+ assert.equal(filterVisibleMessages([event], ALL_OFF), []);
58
+ assert.equal(filterVisibleMessages([event], ALL_ON), [event]);
59
+ });
60
+ filter('drops empty assistant messages', () => {
61
+ assert.equal(filterVisibleMessages([msg({ content: ' ' })], ALL_ON), []);
62
+ });
63
+ filter('keeps user and non-empty assistant messages with everything off', () => {
64
+ const user = msg({ role: 'user', content: 'hello' });
65
+ const reply = msg({ content: 'hi there' });
66
+ assert.equal(filterVisibleMessages([user, reply], ALL_OFF), [user, reply]);
67
+ });
68
+ trailing.run();
69
+ filter.run();
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/halo-overlay.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
1
+ {"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/halo-overlay.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
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.451.1-alpha-3c3e1d3.0",
4
+ "version": "14.451.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.451.1-alpha-3c3e1d3.0",
68
- "@genesislcap/genx": "14.451.1-alpha-3c3e1d3.0",
69
- "@genesislcap/rollup-builder": "14.451.1-alpha-3c3e1d3.0",
70
- "@genesislcap/ts-builder": "14.451.1-alpha-3c3e1d3.0",
71
- "@genesislcap/uvu-playwright-builder": "14.451.1-alpha-3c3e1d3.0",
72
- "@genesislcap/vite-builder": "14.451.1-alpha-3c3e1d3.0",
73
- "@genesislcap/webpack-builder": "14.451.1-alpha-3c3e1d3.0",
67
+ "@genesislcap/foundation-testing": "14.451.1",
68
+ "@genesislcap/genx": "14.451.1",
69
+ "@genesislcap/rollup-builder": "14.451.1",
70
+ "@genesislcap/ts-builder": "14.451.1",
71
+ "@genesislcap/uvu-playwright-builder": "14.451.1",
72
+ "@genesislcap/vite-builder": "14.451.1",
73
+ "@genesislcap/webpack-builder": "14.451.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.451.1-alpha-3c3e1d3.0",
79
- "@genesislcap/foundation-logger": "14.451.1-alpha-3c3e1d3.0",
80
- "@genesislcap/foundation-redux": "14.451.1-alpha-3c3e1d3.0",
81
- "@genesislcap/foundation-ui": "14.451.1-alpha-3c3e1d3.0",
82
- "@genesislcap/foundation-utils": "14.451.1-alpha-3c3e1d3.0",
83
- "@genesislcap/rapid-design-system": "14.451.1-alpha-3c3e1d3.0",
84
- "@genesislcap/web-core": "14.451.1-alpha-3c3e1d3.0",
78
+ "@genesislcap/foundation-ai": "14.451.1",
79
+ "@genesislcap/foundation-logger": "14.451.1",
80
+ "@genesislcap/foundation-redux": "14.451.1",
81
+ "@genesislcap/foundation-ui": "14.451.1",
82
+ "@genesislcap/foundation-utils": "14.451.1",
83
+ "@genesislcap/rapid-design-system": "14.451.1",
84
+ "@genesislcap/web-core": "14.451.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": "71f52da6801da778a24804715814985bf09b074c"
96
+ "gitHead": "63decfd7190d1701b06dadb80d4a45ef5b29c4fe"
97
97
  }
@@ -0,0 +1,105 @@
1
+ import { assert, createComponentSuite } from '@genesislcap/foundation-testing';
2
+ import { DOM } from '@genesislcap/web-core';
3
+ import { AiChatInteractionWrapper } from './chat-interaction-wrapper';
4
+
5
+ // Hold a reference so the custom-element registration isn't tree-shaken.
6
+ AiChatInteractionWrapper;
7
+
8
+ /**
9
+ * Minimal stand-in for a real interaction widget. The wrapper instantiates the
10
+ * element named by `componentName` and assigns `data`/`resolved` onto it; the
11
+ * tests read those back to verify which interaction the rendered widget belongs
12
+ * to.
13
+ */
14
+ const FAKE_WIDGET = 'test-interaction-widget';
15
+ if (!customElements.get(FAKE_WIDGET)) {
16
+ customElements.define(
17
+ FAKE_WIDGET,
18
+ class extends HTMLElement {
19
+ data: unknown;
20
+ resolved: unknown;
21
+ },
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Renders a first interaction into a freshly-mounted wrapper.
27
+ *
28
+ * `componentName` is assigned last on purpose. The suite hands us an
29
+ * already-connected element, so the only render trigger left is
30
+ * `componentNameChanged`; setting it last means that render sees `data` and
31
+ * `interactionId` already in place — reproducing production, where the initial
32
+ * widget renders in `connectedCallback` with every bound property present. This
33
+ * keeps the *first* render correct on both the patched and unpatched code so
34
+ * that the id-change rebind is the only behaviour each test isolates.
35
+ */
36
+ async function renderFirstInteraction(element: AiChatInteractionWrapper) {
37
+ await DOM.nextUpdate();
38
+ element.data = { label: 'interaction-a' };
39
+ element.interactionId = 'id-a';
40
+ element.componentName = FAKE_WIDGET;
41
+ await DOM.nextUpdate();
42
+ }
43
+
44
+ const Suite = createComponentSuite<AiChatInteractionWrapper>(
45
+ 'AiChatInteractionWrapper',
46
+ 'ai-chat-interaction-wrapper',
47
+ );
48
+
49
+ /**
50
+ * Regression for the cog-menu toggle misalignment: FAST's `repeat` reconciles
51
+ * the message list by array index (it has no key function), so toggling "Tool
52
+ * calls"/"Thinking" re-filters the visible messages and rebinds an existing
53
+ * row's wrapper to a *different* interaction. When both interactions use the
54
+ * same component, `componentNameChanged` doesn't fire — so the wrapper must
55
+ * re-render on `interactionId` change, or the previous interaction's widget
56
+ * stays mounted under the new message.
57
+ */
58
+ Suite(
59
+ 're-renders the hosted widget when interactionId changes with the same componentName',
60
+ async ({ element }) => {
61
+ await renderFirstInteraction(element);
62
+
63
+ const firstWidget = element.container.firstElementChild as HTMLElement & { data: unknown };
64
+ assert.ok(firstWidget, 'a widget is rendered for the first interaction');
65
+ assert.equal(firstWidget.data, { label: 'interaction-a' });
66
+
67
+ // Rebind the reused wrapper to a different interaction that happens to use
68
+ // the same component — `data` then `interactionId`, mirroring the template's
69
+ // property-binding order. `componentName` is unchanged, so the only thing
70
+ // that can drive a re-render is the interactionId hook.
71
+ element.data = { label: 'interaction-b' };
72
+ element.interactionId = 'id-b';
73
+ await DOM.nextUpdate();
74
+
75
+ const secondWidget = element.container.firstElementChild as HTMLElement & { data: unknown };
76
+ assert.is.not(secondWidget, firstWidget, 'the stale widget is replaced, not left mounted');
77
+ assert.equal(
78
+ secondWidget.data,
79
+ { label: 'interaction-b' },
80
+ 'the rendered widget reflects the new interaction, not the stale one',
81
+ );
82
+ },
83
+ );
84
+
85
+ /**
86
+ * Resolving an interaction in place (the user answers it) must forward the
87
+ * result without tearing down and rebuilding the widget — only a genuine change
88
+ * of interaction identity should rebuild.
89
+ */
90
+ Suite('forwards resolved in place without rebuilding the widget', async ({ element }) => {
91
+ await renderFirstInteraction(element);
92
+
93
+ const widget = element.container.firstElementChild as HTMLElement & { resolved: unknown };
94
+ element.resolved = { status: 'approved' };
95
+ await DOM.nextUpdate();
96
+
97
+ assert.is(
98
+ element.container.firstElementChild,
99
+ widget,
100
+ 'resolving in place must not rebuild the widget',
101
+ );
102
+ assert.equal(widget.resolved, { status: 'approved' }, 'resolved is forwarded to the widget');
103
+ });
104
+
105
+ Suite.run();
@@ -50,6 +50,20 @@ export class AiChatInteractionWrapper extends GenesisElement {
50
50
  this.renderComponent();
51
51
  }
52
52
 
53
+ /**
54
+ * Re-render the hosted widget when the interaction identity changes. This
55
+ * ensures widgets don't go stale with them being rendered with `recycle: true`
56
+ * when messages are dynamically filtered, showing/hiding thinking/tool calls
57
+ *
58
+ * `interactionId` is a per-interaction UUID, so it changes exactly when the
59
+ * hosted interaction does. Property bindings apply in template order
60
+ * (componentName, data, interactionId), so `data` is already rebound by the
61
+ * time this fires and the fresh widget is built with the correct data.
62
+ */
63
+ interactionIdChanged() {
64
+ this.renderComponent();
65
+ }
66
+
53
67
  resolvedChanged() {
54
68
  const element = this.container?.firstElementChild as any;
55
69
  if (element) {
@@ -574,6 +574,7 @@ ${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<stro
574
574
 
575
575
  <div class="messages" part="messages" ${ref('messagesEl')}>
576
576
  ${repeat((x) => x.visibleMessages, messageRowTemplate)}
577
+ ${repeat((x) => x.activeInteractionRow, messageRowTemplate)}
577
578
  ${when((x) => x.liveSubAgentTrace.length > 0 && x.showToolCalls, liveSubAgentTraceTemplate)}
578
579
  ${when(
579
580
  (x) =>
package/src/main/main.ts CHANGED
@@ -66,6 +66,7 @@ import {
66
66
  import { ChatSuggestions } from '../suggestions/chat-suggestions';
67
67
  import { AnimatedPanelToggle } from '../utils/animated-panel-toggle';
68
68
  import { logger } from '../utils/logger';
69
+ import { filterVisibleMessages, trailingInteractionRow } from '../utils/message-partition';
69
70
  import { sumCosts } from '../utils/sum-costs';
70
71
  import { expandToolTree } from '../utils/tool-fold';
71
72
  import { styles } from './main.styles';
@@ -589,9 +590,13 @@ export class FoundationAiAssistant extends GenesisElement {
589
590
  }
590
591
 
591
592
  /**
592
- * Messages filtered by the current toggle state.
593
- * Tool-related messages (those with toolCalls or toolResult) are hidden when
594
- * `showToolCalls` is false.
593
+ * Messages rendered in the scrolling list, filtered by the current toggle
594
+ * state. Tool-related messages (those with toolCalls or toolResult) are hidden
595
+ * when `showToolCalls` is false.
596
+ *
597
+ * Excludes the trailing interaction (see `activeInteractionRow`), which is
598
+ * rendered in a separate pinned slot so toggling the message filters never
599
+ * re-creates a live interaction widget.
595
600
  *
596
601
  * Marked `@volatile` because the filter branches conditionally access
597
602
  * `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator`
@@ -606,28 +611,25 @@ export class FoundationAiAssistant extends GenesisElement {
606
611
  ? this.showAgentSwitchIndicator
607
612
  : this.showToolCalls;
608
613
 
609
- return this.messages.filter((m) => {
610
- // Agent switch indicators are shown when the toggle is on (or showToolCalls implies it)
611
- if (m.role === 'system-event') {
612
- return showAgentSwitchIndicator;
613
- }
614
- // Never show tool messages to the user
615
- if (m.role === 'tool') {
616
- return false;
617
- }
618
- // Filter thinking messages based on toggle
619
- if (m.thinking && !this.showThinkingSteps) {
620
- return false;
621
- }
622
- // Filter out empty assistant messages that might have slipped through
623
- if (m.role === 'assistant' && !m.content?.trim() && !m.toolCalls?.length && !m.interaction) {
624
- return false;
625
- }
626
- const isToolRelated = !!(m.toolCalls?.length || m.toolResult);
627
- return !isToolRelated || this.showToolCalls;
614
+ const { messages } = this;
615
+ const listed = trailingInteractionRow(messages).length ? messages.slice(0, -1) : messages;
616
+ return filterVisibleMessages(listed, {
617
+ showToolCalls: this.showToolCalls,
618
+ showThinkingSteps: this.showThinkingSteps,
619
+ showAgentSwitchIndicator,
628
620
  });
629
621
  }
630
622
 
623
+ /**
624
+ * The trailing interaction, rendered in a pinned slot below the scrolling
625
+ * list rather than inside it. Reads only `messages` (never the toggle state),
626
+ * so a cog-menu toggle leaves this binding — and the live widget's DOM —
627
+ * untouched. See `trailingInteractionRow` for the rationale.
628
+ */
629
+ get activeInteractionRow(): ChatMessage[] {
630
+ return trailingInteractionRow(this.messages);
631
+ }
632
+
631
633
  agentsChanged(): void {
632
634
  // Guard: driver doesn't exist yet during connectedCallback — createDriver/wireDriver handle that.
633
635
  if (!this.driver) return;
@@ -0,0 +1,101 @@
1
+ import type { ChatInteraction, ChatMessage } from '@genesislcap/foundation-ai';
2
+ import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
+ import {
4
+ filterVisibleMessages,
5
+ trailingInteractionRow,
6
+ type MessageFilterState,
7
+ } from './message-partition';
8
+
9
+ const msg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
10
+ role: 'assistant',
11
+ content: '',
12
+ ...overrides,
13
+ });
14
+
15
+ const interaction = (interactionId: string, resolved = false): ChatInteraction => ({
16
+ interactionId,
17
+ componentName: 'test-widget',
18
+ data: {},
19
+ ...(resolved ? { resolved: { status: 'approved' } } : {}),
20
+ });
21
+
22
+ const ALL_ON: MessageFilterState = {
23
+ showToolCalls: true,
24
+ showThinkingSteps: true,
25
+ showAgentSwitchIndicator: true,
26
+ };
27
+ const ALL_OFF: MessageFilterState = {
28
+ showToolCalls: false,
29
+ showThinkingSteps: false,
30
+ showAgentSwitchIndicator: false,
31
+ };
32
+
33
+ const trailing = createLogicSuite('trailingInteractionRow');
34
+
35
+ trailing('is empty when there are no messages', () => {
36
+ assert.equal(trailingInteractionRow([]), []);
37
+ });
38
+
39
+ trailing('is empty when the last message has no interaction', () => {
40
+ assert.equal(trailingInteractionRow([msg({ content: 'hi' })]), []);
41
+ });
42
+
43
+ trailing('returns the last message when it carries an interaction (pending)', () => {
44
+ const pending = msg({ interaction: interaction('id-a') });
45
+ assert.equal(trailingInteractionRow([msg({ content: 'q' }), pending]), [pending]);
46
+ });
47
+
48
+ trailing('still returns the trailing interaction once it is resolved', () => {
49
+ // Keeps it in the pinned slot so resolving forwards in place without a rebuild.
50
+ const resolved = msg({ interaction: interaction('id-a', true) });
51
+ assert.equal(trailingInteractionRow([resolved]), [resolved]);
52
+ });
53
+
54
+ trailing('returns nothing once a newer non-interaction message arrives', () => {
55
+ // The interaction is no longer last, so it rejoins the scrolling list.
56
+ const earlier = msg({ interaction: interaction('id-a', true) });
57
+ assert.equal(trailingInteractionRow([earlier, msg({ content: 'next' })]), []);
58
+ });
59
+
60
+ trailing('returns the same message reference (lets FAST short-circuit rebind)', () => {
61
+ const pending = msg({ interaction: interaction('id-a') });
62
+ assert.is(trailingInteractionRow([pending])[0], pending);
63
+ });
64
+
65
+ const filter = createLogicSuite('filterVisibleMessages');
66
+
67
+ filter('hides tool result messages regardless of toggles', () => {
68
+ const msgs = [msg({ role: 'tool', toolResult: { toolCallId: 't1', content: 'r' } })];
69
+ assert.equal(filterVisibleMessages(msgs, ALL_ON), []);
70
+ });
71
+
72
+ filter('hides tool-call messages when showToolCalls is off, shows them when on', () => {
73
+ const toolCall = msg({ toolCalls: [{ id: 't1', name: 'do', args: {} } as any] });
74
+ assert.equal(filterVisibleMessages([toolCall], ALL_OFF), []);
75
+ assert.equal(filterVisibleMessages([toolCall], ALL_ON), [toolCall]);
76
+ });
77
+
78
+ filter('hides thinking messages when showThinkingSteps is off', () => {
79
+ const thinking = msg({ thinking: true, content: 'hmm' });
80
+ assert.equal(filterVisibleMessages([thinking], ALL_OFF), []);
81
+ assert.equal(filterVisibleMessages([thinking], ALL_ON), [thinking]);
82
+ });
83
+
84
+ filter('hides system-event rows unless the agent-switch toggle is on', () => {
85
+ const event = msg({ role: 'system-event', content: 'switched' });
86
+ assert.equal(filterVisibleMessages([event], ALL_OFF), []);
87
+ assert.equal(filterVisibleMessages([event], ALL_ON), [event]);
88
+ });
89
+
90
+ filter('drops empty assistant messages', () => {
91
+ assert.equal(filterVisibleMessages([msg({ content: ' ' })], ALL_ON), []);
92
+ });
93
+
94
+ filter('keeps user and non-empty assistant messages with everything off', () => {
95
+ const user = msg({ role: 'user', content: 'hello' });
96
+ const reply = msg({ content: 'hi there' });
97
+ assert.equal(filterVisibleMessages([user, reply], ALL_OFF), [user, reply]);
98
+ });
99
+
100
+ trailing.run();
101
+ filter.run();
@@ -0,0 +1,66 @@
1
+ import type { ChatMessage } from '@genesislcap/foundation-ai';
2
+
3
+ /**
4
+ * Toggle state that drives which messages appear in the scrolling list. Mirrors
5
+ * the cog-menu switches; `showAgentSwitchIndicator` is pre-resolved by the
6
+ * caller (it falls back to `showToolCalls` when the host hasn't configured an
7
+ * explicit agent-switch toggle).
8
+ */
9
+ export interface MessageFilterState {
10
+ showToolCalls: boolean;
11
+ showThinkingSteps: boolean;
12
+ showAgentSwitchIndicator: boolean;
13
+ }
14
+
15
+ /**
16
+ * The trailing interaction to render in a pinned slot, separate from the
17
+ * filtered list. Empty unless the most recent message carries an interaction.
18
+ *
19
+ * Pinning the trailing interaction keeps its widget out of the list's
20
+ * reconciliation: FAST's `repeat` has no key function and reconciles by array
21
+ * index, so re-filtering the list (a cog-menu toggle) would otherwise rebind an
22
+ * existing row to a different message and re-create the live widget — discarding
23
+ * any in-progress input. The slot's binding reads only `messages` and returns
24
+ * the same message object across toggles, so the widget's DOM is preserved.
25
+ *
26
+ * It is the *trailing* message (resolved or not), not "unresolved only", so that
27
+ * answering an interaction leaves it in the slot — `resolved` is forwarded in
28
+ * place with no re-render. It rejoins the list naturally once a newer message
29
+ * arrives and it is no longer last.
30
+ */
31
+ export function trailingInteractionRow(messages: ChatMessage[]): ChatMessage[] {
32
+ const last = messages[messages.length - 1];
33
+ return last?.interaction ? [last] : [];
34
+ }
35
+
36
+ /**
37
+ * Filters messages for the scrolling list per the current cog-menu toggles.
38
+ *
39
+ * The trailing interaction (see `trailingInteractionRow`) must be removed
40
+ * before calling, as it is rendered separately.
41
+ */
42
+ export function filterVisibleMessages(
43
+ messages: ChatMessage[],
44
+ toggles: MessageFilterState,
45
+ ): ChatMessage[] {
46
+ return messages.filter((m) => {
47
+ // Agent switch indicators show when their toggle is on (or showToolCalls implies it).
48
+ if (m.role === 'system-event') {
49
+ return toggles.showAgentSwitchIndicator;
50
+ }
51
+ // Tool result messages are never shown to the user.
52
+ if (m.role === 'tool') {
53
+ return false;
54
+ }
55
+ // Thinking messages follow their own toggle.
56
+ if (m.thinking && !toggles.showThinkingSteps) {
57
+ return false;
58
+ }
59
+ // Drop empty assistant messages that slipped through.
60
+ if (m.role === 'assistant' && !m.content?.trim() && !m.toolCalls?.length && !m.interaction) {
61
+ return false;
62
+ }
63
+ const isToolRelated = !!(m.toolCalls?.length || m.toolResult);
64
+ return !isToolRelated || toggles.showToolCalls;
65
+ });
66
+ }