@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.
- package/dist/ai-assistant.api.json +36 -1
- package/dist/ai-assistant.d.ts +14 -3
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +11 -0
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +2 -0
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +14 -3
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/utils/message-partition.d.ts +37 -0
- package/dist/dts/utils/message-partition.d.ts.map +1 -0
- package/dist/dts/utils/message-partition.test.d.ts +2 -0
- package/dist/dts/utils/message-partition.test.d.ts.map +1 -0
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +13 -0
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +77 -0
- package/dist/esm/main/main.js +23 -23
- package/dist/esm/main/main.template.js +1 -0
- package/dist/esm/utils/message-partition.js +49 -0
- package/dist/esm/utils/message-partition.test.js +69 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +105 -0
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +14 -0
- package/src/main/main.template.ts +1 -0
- package/src/main/main.ts +24 -22
- package/src/utils/message-partition.test.ts +101 -0
- 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",
|
package/dist/ai-assistant.d.ts
CHANGED
|
@@ -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
|
|
1179
|
-
* Tool-related messages (those with toolCalls or toolResult) are hidden
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"chat-interaction-wrapper.test.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts"],"names":[],"mappings":""}
|
package/dist/dts/main/main.d.ts
CHANGED
|
@@ -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
|
|
224
|
-
* Tool-related messages (those with toolCalls or toolResult) are hidden
|
|
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;
|
|
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,
|
|
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 @@
|
|
|
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();
|
package/dist/esm/main/main.js
CHANGED
|
@@ -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
|
|
508
|
-
* Tool-related messages (those with toolCalls or toolResult) are hidden
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
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
|
|
593
|
-
* Tool-related messages (those with toolCalls or toolResult) are hidden
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
+
}
|