@genesislcap/ai-assistant 14.451.1-alpha-3c3e1d3.0 → 14.451.2
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 +37 -2
- package/dist/ai-assistant.d.ts +26 -7
- package/dist/dts/components/chat-driver/chat-driver.d.ts +7 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- 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/state/debug-event-log.d.ts +34 -6
- package/dist/dts/state/debug-event-log.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-driver/chat-driver.js +66 -18
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +16 -3
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +77 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +1 -1
- package/dist/esm/main/main.js +23 -23
- package/dist/esm/main/main.template.js +1 -0
- package/dist/esm/state/debug-event-log.js +45 -10
- 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-driver/chat-driver.ts +67 -18
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +105 -0
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +17 -3
- package/src/components/orchestrating-driver/orchestrating-driver.ts +1 -1
- package/src/main/main.template.ts +1 -0
- package/src/main/main.ts +24 -22
- package/src/state/debug-event-log.ts +74 -10
- package/src/utils/message-partition.test.ts +101 -0
- package/src/utils/message-partition.ts +66 -0
|
@@ -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;
|
|
@@ -74,9 +87,9 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
|
|
|
74
87
|
this._resizeObserver = undefined;
|
|
75
88
|
this.container.replaceChildren();
|
|
76
89
|
if (!customElements.get(this.componentName)) {
|
|
77
|
-
logger.warn(`Interactive component "
|
|
90
|
+
logger.warn(`Interactive component "${this.componentName}" is not registered in the customElements registry.`);
|
|
78
91
|
const errorDiv = document.createElement('div');
|
|
79
|
-
errorDiv.textContent = `Error: Component "
|
|
92
|
+
errorDiv.textContent = `Error: Component "${this.componentName}" is not available in this application.`;
|
|
80
93
|
this.container.appendChild(errorDiv);
|
|
81
94
|
return;
|
|
82
95
|
}
|
|
@@ -108,7 +121,7 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
|
|
|
108
121
|
catch (e) {
|
|
109
122
|
logger.error(`Failed to create interactive component: \${this.componentName}`, e);
|
|
110
123
|
const errorDiv = document.createElement('div');
|
|
111
|
-
errorDiv.textContent = `Error: Could not load component
|
|
124
|
+
errorDiv.textContent = `Error: Could not load component ${this.componentName}`;
|
|
112
125
|
this.container.appendChild(errorDiv);
|
|
113
126
|
}
|
|
114
127
|
}
|
|
@@ -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();
|
|
@@ -222,7 +222,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
222
222
|
}
|
|
223
223
|
handoffs += 1;
|
|
224
224
|
if (handoffs > this.maxHandoffs) {
|
|
225
|
-
this.appendInlineMessage(`I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed
|
|
225
|
+
this.appendInlineMessage(`I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed. Please try breaking your request into smaller steps.`);
|
|
226
226
|
break;
|
|
227
227
|
}
|
|
228
228
|
handoffSummary = result.summary;
|
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;
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* there's no reason to pay immutable-reducer cost or flood the Redux DevTools
|
|
17
17
|
* action log with debug noise.
|
|
18
18
|
*
|
|
19
|
-
* Surfaced
|
|
20
|
-
*
|
|
19
|
+
* Surfaced in `getDebugLog().timeline`, merged chronologically with messages and
|
|
20
|
+
* turn snapshots. Ring-buffered — the oldest non-`high` events are evicted first
|
|
21
|
+
* while `high`-importance failure events are retained — so a long-lived session
|
|
22
|
+
* stays bounded in normal use without ever dropping a failure signal.
|
|
21
23
|
*
|
|
22
24
|
* @internal
|
|
23
25
|
*/
|
|
@@ -45,6 +47,7 @@ export const META_EVENT_IMPORTANCE = {
|
|
|
45
47
|
'state.changed': 'normal',
|
|
46
48
|
'turn.start': 'normal',
|
|
47
49
|
'turn.end': 'normal',
|
|
50
|
+
'turn.retry': 'normal',
|
|
48
51
|
'agent.handoff': 'normal',
|
|
49
52
|
'agent.pinned': 'normal',
|
|
50
53
|
'agent.unpinned': 'normal',
|
|
@@ -57,13 +60,20 @@ export const META_EVENT_IMPORTANCE = {
|
|
|
57
60
|
'panel.toggled': 'low',
|
|
58
61
|
'attachment.added': 'low',
|
|
59
62
|
};
|
|
60
|
-
/**
|
|
61
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Soft ring-buffer cap. Once exceeded, the oldest *non-`high`* event is evicted;
|
|
65
|
+
* `high`-importance events (failures and hard limits) are never dropped — they're
|
|
66
|
+
* the whole reason the log exists. So a buffer dominated by `high` events is
|
|
67
|
+
* allowed to float above this cap rather than lose a failure signal; in normal
|
|
68
|
+
* use the frequent `low`/`normal` events keep it near the cap. Entries are cheap.
|
|
69
|
+
*/
|
|
70
|
+
const DEFAULT_MAX_META_EVENTS = 400;
|
|
62
71
|
const registry = new Map();
|
|
63
72
|
/**
|
|
64
|
-
* Append a meta event to the timeline for `key`.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
73
|
+
* Append a meta event to the timeline for `key`. Once the buffer exceeds
|
|
74
|
+
* {@link DEFAULT_MAX_META_EVENTS}, evicts the oldest *non-`high`* event;
|
|
75
|
+
* `high`-importance events are never evicted. An empty `key` is bucketed under
|
|
76
|
+
* `''`, matching how drivers/stores handle an absent session identity, so
|
|
67
77
|
* callers never need to guard.
|
|
68
78
|
*/
|
|
69
79
|
export function recordMetaEvent(key, type, detail) {
|
|
@@ -81,9 +91,34 @@ export function recordMetaEvent(key, type, detail) {
|
|
|
81
91
|
});
|
|
82
92
|
buffer.next += 1;
|
|
83
93
|
if (buffer.events.length > DEFAULT_MAX_META_EVENTS) {
|
|
84
|
-
|
|
94
|
+
// Drop the oldest event that isn't `high` importance. `high` events
|
|
95
|
+
// (failures/limits) are retained even past the cap — losing a failure
|
|
96
|
+
// signal is worse than briefly exceeding the buffer size. If every retained
|
|
97
|
+
// event is `high` (all-failures session), nothing is evicted and the buffer
|
|
98
|
+
// grows; in practice frequent `low`/`normal` events keep it bounded.
|
|
99
|
+
const evictAt = buffer.events.findIndex((e) => e.importance !== 'high');
|
|
100
|
+
if (evictAt !== -1) {
|
|
101
|
+
buffer.events.splice(evictAt, 1);
|
|
102
|
+
}
|
|
85
103
|
}
|
|
86
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Record a turn-ending failure (`turn.error`, importance `high`). The `reason`
|
|
107
|
+
* is typed so the high-importance triage surface stays consistent; pass any
|
|
108
|
+
* diagnostic specifics (attempt counts, offending tool names, etc.) in `detail`.
|
|
109
|
+
*/
|
|
110
|
+
export function recordTurnError(key, reason, detail) {
|
|
111
|
+
recordMetaEvent(key, 'turn.error', Object.assign({ reason }, detail));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Record a recoverable retry *within* a turn (`turn.retry`, importance `normal`)
|
|
115
|
+
* — e.g. one malformed/empty attempt that will be retried, as distinct from the
|
|
116
|
+
* final attempt that bails out as a `turn.error`. Include `attempt`/`maxAttempts`
|
|
117
|
+
* in `detail` so the timeline shows which try this was.
|
|
118
|
+
*/
|
|
119
|
+
export function recordTurnRetry(key, reason, detail) {
|
|
120
|
+
recordMetaEvent(key, 'turn.retry', Object.assign({ reason }, detail));
|
|
121
|
+
}
|
|
87
122
|
/** Returns the meta-event timeline for `key`, or an empty array if none recorded. */
|
|
88
123
|
export function getMetaEvents(key) {
|
|
89
124
|
var _a, _b;
|
|
@@ -102,8 +137,8 @@ export const DEBUG_LOG_README = [
|
|
|
102
137
|
"kind:'turn' — one LLM call. `systemPrompt` and `toolNames` are what the model saw. A systemPrompt of '<repeated — identical to turn N>' was byte-identical to turn N and de-duplicated; the full prompt is shown whenever it changes (often because a stateful agent advanced), so prompt evolution is visible.",
|
|
103
138
|
"kind:'turn'.`agentSnapshot` — the active agent's own view of its internal state, captured at that turn. An agent opts into this by exposing a `getDebugSnapshot()` that returns JSON-serializable per-state info; stateful/flow agents wire it automatically, so you can watch a flow advance turn-by-turn (e.g. current step, cursor, collected fields, pending changes). Absent for agents that don't expose one.",
|
|
104
139
|
"kind:'event' — a meta/lifecycle event. `type` names it (see below); `detail` carries structured data. `detail.placement` is the emitting UI instance: 'bubble' (collapsed), 'panel' (popped-out), or 'standalone'.",
|
|
105
|
-
"Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, handoffs, agent/provider changes, interactions), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. 'message' and 'turn' entries carry no importance — they are the substance, always read them.",
|
|
106
|
-
'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.error (a turn failed or hit a guardrail —
|
|
140
|
+
"Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. A 'high' turn.error is often preceded by one or more 'normal' turn.retry events for the same reason — read them together to see how many attempts were made before bailing. 'message' and 'turn' entries carry no importance — they are the substance, always read them.",
|
|
141
|
+
'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations, plus reason-specific diagnostics: attempts, finishMessage, unknownTools + availableTools, iterations + limit, or name + message for exceptions), tool.failed (a tool threw), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), provider.selected (model/provider for the upcoming turns), interaction.requested/resolved (blocking user widgets — explain quiet gaps), context.updated/threshold-crossed (token + cost), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
|
|
107
142
|
"`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, token usage, session cost), activeDebugSnapshot (the active agent's `getDebugSnapshot()` taken fresh at export — reflects state NOW, which may have advanced beyond the last turn's agentSnapshot), debug (optional host-supplied debug state), host, and the export timestamp.",
|
|
108
143
|
'To debug a failure: find the last turn.error or tool.failed, then read upward for the user message, the turn(s), and the agent/provider/state events that led into it.',
|
|
109
144
|
];
|
|
@@ -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.
|
|
4
|
+
"version": "14.451.2",
|
|
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.
|
|
68
|
-
"@genesislcap/genx": "14.451.
|
|
69
|
-
"@genesislcap/rollup-builder": "14.451.
|
|
70
|
-
"@genesislcap/ts-builder": "14.451.
|
|
71
|
-
"@genesislcap/uvu-playwright-builder": "14.451.
|
|
72
|
-
"@genesislcap/vite-builder": "14.451.
|
|
73
|
-
"@genesislcap/webpack-builder": "14.451.
|
|
67
|
+
"@genesislcap/foundation-testing": "14.451.2",
|
|
68
|
+
"@genesislcap/genx": "14.451.2",
|
|
69
|
+
"@genesislcap/rollup-builder": "14.451.2",
|
|
70
|
+
"@genesislcap/ts-builder": "14.451.2",
|
|
71
|
+
"@genesislcap/uvu-playwright-builder": "14.451.2",
|
|
72
|
+
"@genesislcap/vite-builder": "14.451.2",
|
|
73
|
+
"@genesislcap/webpack-builder": "14.451.2",
|
|
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.
|
|
79
|
-
"@genesislcap/foundation-logger": "14.451.
|
|
80
|
-
"@genesislcap/foundation-redux": "14.451.
|
|
81
|
-
"@genesislcap/foundation-ui": "14.451.
|
|
82
|
-
"@genesislcap/foundation-utils": "14.451.
|
|
83
|
-
"@genesislcap/rapid-design-system": "14.451.
|
|
84
|
-
"@genesislcap/web-core": "14.451.
|
|
78
|
+
"@genesislcap/foundation-ai": "14.451.2",
|
|
79
|
+
"@genesislcap/foundation-logger": "14.451.2",
|
|
80
|
+
"@genesislcap/foundation-redux": "14.451.2",
|
|
81
|
+
"@genesislcap/foundation-ui": "14.451.2",
|
|
82
|
+
"@genesislcap/foundation-utils": "14.451.2",
|
|
83
|
+
"@genesislcap/rapid-design-system": "14.451.2",
|
|
84
|
+
"@genesislcap/web-core": "14.451.2",
|
|
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": "44a0946f3ff1f184fd6fd8f877108b0dbaf2d9e0"
|
|
97
97
|
}
|