@genesislcap/ai-assistant 14.467.1 → 14.467.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 +39 -53
- package/dist/ai-assistant.d.ts +20 -25
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +1 -20
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.d.ts +16 -0
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.test.d.ts +2 -0
- package/dist/dts/state/debug-event-log.test.d.ts.map +1 -0
- package/dist/dts/utils/flatten-sub-agent-messages.d.ts +51 -0
- package/dist/dts/utils/flatten-sub-agent-messages.d.ts.map +1 -0
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +2 -0
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +1 -0
- package/dist/dts/utils/strip-agent-handlers.d.ts +29 -0
- package/dist/dts/utils/strip-agent-handlers.d.ts.map +1 -0
- package/dist/dts/utils/strip-agent-handlers.test.d.ts +2 -0
- package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +48 -12
- package/dist/esm/components/chat-driver/chat-driver.test.js +29 -0
- package/dist/esm/main/main.js +14 -38
- package/dist/esm/state/debug-event-log.js +47 -0
- package/dist/esm/state/debug-event-log.test.js +67 -0
- package/dist/esm/utils/flatten-sub-agent-messages.js +49 -0
- package/dist/esm/utils/flatten-sub-agent-messages.test.js +139 -0
- package/dist/esm/utils/strip-agent-handlers.js +51 -0
- package/dist/esm/utils/strip-agent-handlers.test.js +81 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/chat-driver/chat-driver.test.ts +43 -0
- package/src/components/chat-driver/chat-driver.ts +64 -10
- package/src/index.ts +1 -0
- package/src/main/main.ts +16 -37
- package/src/state/debug-event-log.test.ts +89 -0
- package/src/state/debug-event-log.ts +48 -0
- package/src/utils/flatten-sub-agent-messages.test.ts +163 -0
- package/src/utils/flatten-sub-agent-messages.ts +88 -0
- package/src/utils/strip-agent-handlers.test.ts +99 -0
- package/src/utils/strip-agent-handlers.ts +52 -0
package/dist/esm/main/main.js
CHANGED
|
@@ -45,8 +45,10 @@ import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } fr
|
|
|
45
45
|
import { ChatSuggestions } from '../suggestions/chat-suggestions';
|
|
46
46
|
import { AnimatedPanelToggle } from '../utils/animated-panel-toggle';
|
|
47
47
|
import { resolveExclusiveLoadingStyle } from '../utils/animation-exclusivity';
|
|
48
|
+
import { flattenSubAgentMessages } from '../utils/flatten-sub-agent-messages';
|
|
48
49
|
import { logger } from '../utils/logger';
|
|
49
50
|
import { filterVisibleMessages, trailingInteractionRow } from '../utils/message-partition';
|
|
51
|
+
import { stripAgentHandlers } from '../utils/strip-agent-handlers';
|
|
50
52
|
import { sumCosts } from '../utils/sum-costs';
|
|
51
53
|
import { expandToolTree } from '../utils/tool-fold';
|
|
52
54
|
import { styles } from './main.styles';
|
|
@@ -95,37 +97,6 @@ const COMPOSER_MAX_HEIGHT_PX = 400;
|
|
|
95
97
|
const COMPOSER_MIN_MESSAGES_PX = 80;
|
|
96
98
|
// Register supporting components when the main component module is imported.
|
|
97
99
|
avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiWavesIndicator, AiFlowingWavesIndicator, AiPlasmaOrbIndicator, AiChatBubble, AiActivityHalo, ChatSuggestions, AgentPicker);
|
|
98
|
-
/**
|
|
99
|
-
* Recursively strips non-serializable fields from an agent before storing in
|
|
100
|
-
* Redux. Drops **every function-valued property** — `toolHandlers`, the
|
|
101
|
-
* lifecycle/dispatch hooks (`onActivate`, `onDeactivate`, `getDebugSnapshot`,
|
|
102
|
-
* `onUnresolvedTool`), and the function form of the per-turn resolvers
|
|
103
|
-
* (`systemPrompt`, `toolDefinitions`, `displayName`, `provider`, `temperature`,
|
|
104
|
-
* `toolChoice`). Static forms (string / number / array / plain object) pass
|
|
105
|
-
* through unchanged; `subAgents` are stripped recursively.
|
|
106
|
-
*
|
|
107
|
-
* Filtering by *value* (any function) rather than by an explicit field list
|
|
108
|
-
* means a new function-valued field added to `AgentConfig` is handled
|
|
109
|
-
* automatically and can never leak a live function into serialized store
|
|
110
|
-
* state — no denylist to keep in sync. The live config on the driver stays the
|
|
111
|
-
* source of truth; the slice only holds this serializable projection, and
|
|
112
|
-
* functions are never read back from it.
|
|
113
|
-
*/
|
|
114
|
-
function stripHandlers(agent) {
|
|
115
|
-
var _a;
|
|
116
|
-
const serializable = {};
|
|
117
|
-
for (const [key, value] of Object.entries(agent)) {
|
|
118
|
-
// `subAgents` is handled separately (recursively, below); drop everything
|
|
119
|
-
// function-valued.
|
|
120
|
-
if (key === 'subAgents' || typeof value === 'function')
|
|
121
|
-
continue;
|
|
122
|
-
serializable[key] = value;
|
|
123
|
-
}
|
|
124
|
-
if ((_a = agent.subAgents) === null || _a === void 0 ? void 0 : _a.length) {
|
|
125
|
-
serializable.subAgents = agent.subAgents.map(stripHandlers);
|
|
126
|
-
}
|
|
127
|
-
return serializable;
|
|
128
|
-
}
|
|
129
100
|
/**
|
|
130
101
|
* Foundation AI Assistant component.
|
|
131
102
|
*
|
|
@@ -342,7 +313,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
342
313
|
// and Redux serializable-state middleware will warn. toolHandlers are never read
|
|
343
314
|
// back from the store; they are always sourced from this.agents when the driver
|
|
344
315
|
// is built.
|
|
345
|
-
(_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setActiveAgent(value ?
|
|
316
|
+
(_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setActiveAgent(value ? stripAgentHandlers(value) : undefined);
|
|
346
317
|
}
|
|
347
318
|
get suggestionsState() {
|
|
348
319
|
var _a, _b;
|
|
@@ -1431,9 +1402,13 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1431
1402
|
turn: 1,
|
|
1432
1403
|
message: 2,
|
|
1433
1404
|
};
|
|
1405
|
+
// Sub-agent conversations are stored nested on the parent tool call's
|
|
1406
|
+
// `subAgentTrace`; `flattenSubAgentMessages` hoists them to top-level
|
|
1407
|
+
// `kind: 'message'` entries (breadcrumbed + correlated, the nested copy moved
|
|
1408
|
+
// out — not duplicated) so the timeline reads as one chronological sequence.
|
|
1434
1409
|
const messages = (_h = (_g = (_f = this.driver) === null || _f === void 0 ? void 0 : _f.getRawHistory) === null || _g === void 0 ? void 0 : _g.call(_f)) !== null && _h !== void 0 ? _h : this.messages;
|
|
1435
1410
|
const timeline = [
|
|
1436
|
-
...messages
|
|
1411
|
+
...flattenSubAgentMessages(messages),
|
|
1437
1412
|
...turns,
|
|
1438
1413
|
...(stateKey ? getMetaEvents(stateKey) : []).map((e) => (Object.assign({ kind: 'event' }, e))),
|
|
1439
1414
|
].sort((a, b) => {
|
|
@@ -1456,13 +1431,14 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1456
1431
|
meta: {
|
|
1457
1432
|
timestamp,
|
|
1458
1433
|
host: window.location.host,
|
|
1459
|
-
//
|
|
1460
|
-
// hooks, onUnresolvedTool, function-form resolvers)
|
|
1461
|
-
//
|
|
1462
|
-
//
|
|
1434
|
+
// stripAgentHandlers drops every function-valued field (handlers, lifecycle
|
|
1435
|
+
// hooks, onUnresolvedTool, function-form resolvers) AND object handler bags
|
|
1436
|
+
// like an object-form `toolHandlers`, and recurses subAgents — no manual
|
|
1437
|
+
// exclusion list to keep in sync. We only override toolDefinitions
|
|
1438
|
+
// afterwards to expand the fold tree for the log.
|
|
1463
1439
|
agentSummary: (_j = this.agents) === null || _j === void 0 ? void 0 : _j.map((a) => {
|
|
1464
1440
|
var _a;
|
|
1465
|
-
return (Object.assign(Object.assign({},
|
|
1441
|
+
return (Object.assign(Object.assign({}, stripAgentHandlers(a)), { toolDefinitions: Array.isArray(a.toolDefinitions)
|
|
1466
1442
|
? typeof a.toolHandlers === 'function'
|
|
1467
1443
|
? // Static defs + dynamic handlers — can't walk fold tree
|
|
1468
1444
|
// because the handler map isn't materialized at log time.
|
|
@@ -128,6 +128,51 @@ export function getMetaEvents(key) {
|
|
|
128
128
|
var _a, _b;
|
|
129
129
|
return (_b = (_a = registry.get(key)) === null || _a === void 0 ? void 0 : _a.events) !== null && _b !== void 0 ? _b : [];
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Merge pre-built meta events into the timeline for `targetKey`, **preserving each
|
|
133
|
+
* event's original `timestamp`** (so it interleaves chronologically on export)
|
|
134
|
+
* while re-indexing onto the target buffer's monotonic counter. Used to fold a
|
|
135
|
+
* sub-agent's harvested events into the parent session — see
|
|
136
|
+
* `ChatDriver.invokeSubAgent`. Unlike {@link recordMetaEvent} it does not stamp a
|
|
137
|
+
* fresh timestamp; the same non-`high` eviction policy is applied once after the
|
|
138
|
+
* batch so the buffer stays bounded.
|
|
139
|
+
*/
|
|
140
|
+
export function mergeMetaEvents(targetKey, events) {
|
|
141
|
+
if (events.length === 0)
|
|
142
|
+
return;
|
|
143
|
+
let buffer = registry.get(targetKey);
|
|
144
|
+
if (!buffer) {
|
|
145
|
+
buffer = { events: [], next: 0 };
|
|
146
|
+
registry.set(targetKey, buffer);
|
|
147
|
+
}
|
|
148
|
+
for (const event of events) {
|
|
149
|
+
buffer.events.push(Object.assign(Object.assign({}, event), { index: buffer.next }));
|
|
150
|
+
buffer.next += 1;
|
|
151
|
+
}
|
|
152
|
+
// Evict oldest non-`high` events until back under the cap — same policy as
|
|
153
|
+
// recordMetaEvent; `high` events (failures/limits) are never dropped. Single
|
|
154
|
+
// pass: take the overflow count, then drop that many of the oldest non-`high`
|
|
155
|
+
// events in order (if there aren't enough non-`high`, the buffer floats above
|
|
156
|
+
// the cap rather than losing a failure signal).
|
|
157
|
+
let toEvict = buffer.events.length - DEFAULT_MAX_META_EVENTS;
|
|
158
|
+
if (toEvict > 0) {
|
|
159
|
+
buffer.events = buffer.events.filter((e) => {
|
|
160
|
+
if (toEvict > 0 && e.importance !== 'high') {
|
|
161
|
+
toEvict -= 1;
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Drop the entire timeline for `key`. Used to discard a sub-agent's transient
|
|
170
|
+
* per-invocation session once its events have been harvested into the parent, so
|
|
171
|
+
* the module registry doesn't accumulate one orphaned bucket per sub-agent run.
|
|
172
|
+
*/
|
|
173
|
+
export function clearSession(key) {
|
|
174
|
+
registry.delete(key);
|
|
175
|
+
}
|
|
131
176
|
/**
|
|
132
177
|
* Human/agent-facing guide emitted as the first key of the exported debug log,
|
|
133
178
|
* so whoever opens the JSON (often an AI agent) knows how to read it without
|
|
@@ -139,11 +184,13 @@ export const DEBUG_LOG_README = [
|
|
|
139
184
|
'`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
|
|
140
185
|
'Timestamps are millisecond-resolution; entries that share the same millisecond are ordered by a fixed kind rank (event, then turn, then message), which is a heuristic and may not reflect exact causal order within that millisecond — e.g. a user message and the turn it triggered, or a final assistant message and its turn.end event, can appear in either order depending on whether they landed in the same millisecond. Read the logical structure of a turn rather than over-interpreting the micro-ordering of co-timestamped entries of different kinds.',
|
|
141
186
|
"kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, and `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). On model-produced assistant messages, `model` is the concrete model id that generated it (e.g. 'gemini-2.5-flash-lite') and `providerName` is the registry slot it resolved under (e.g. a tier name like 'high'/'low', or the default); together they attribute the message — and any tool calls it carries — to an exact model even across a mid-session vendor/tier switch, where one slot name can map to different models before and after the switch. Both are undefined on any entry that is NOT an LLM response: non-assistant roles (user/tool/system-event) and 'synthetic-user' echoes; assistant interaction/widget entries (empty content carrying an `interaction` — a rendered widget, not a model turn); driver-authored assistant fallbacks (the timeout, repeated-malformed-call, and empty-response apology messages); and messages restored from a session persisted before these fields existed. One partial case: on a genuine model turn whose provider exposes no `getStatus` (or reports no model), `providerName` is still set but `model` alone is undefined. A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result.",
|
|
187
|
+
"Sub-agent messages appear inline. When a tool delegates to a sub-agent (via `requestSubAgent`), the sub-agent's whole conversation — its own assistant/tool messages, each with their own `content`/`thinking`/`toolCalls`/`toolResult` and per-message `model`/`providerName`/`inputTokens`/`outputTokens`/`cost` — is hoisted into the timeline as ordinary kind:'message' entries, interleaved by timestamp right after the tool call that spawned them (so you read the delegation top-to-bottom). A hoisted entry is marked: `subAgentDepth` is its delegation depth (1 for a sub-agent, 2 for a sub-agent's sub-agent, …), `subAgentOf` is the id of the parent tool call that spawned it (correlates it back even when two sub-agents run in one parent turn), `subAgentName` is the sub-agent's own name, and `agentName` is rewritten to a `\"<parent> › <sub-agent>\"` breadcrumb (composing when nested, e.g. `\"UI Builder › Planner › Grounding\"`). The sub-agent's per-LLM-call snapshots also surface as kind:'turn' entries with an N-M `turnIndex`, and subagent.started/completed (or subagent.failed) events bracket the run. Per-message `cost` on hoisted entries is already part of the session total (it is summed from the un-flattened history), so summing the top-level timeline does NOT double-count.",
|
|
142
188
|
"kind:'turn' — one LLM call. `turnIndex` is a string: a top-level turn is the bare counter ('0', '1', …); a sub-agent's turns are numbered under the parent turn that activated them ('3-1', '3-2', …, and a nested sub-agent contributes '3-2-1', …), and `agentName` names the agent that ran the turn. `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.",
|
|
143
189
|
"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.",
|
|
144
190
|
"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'.",
|
|
145
191
|
"Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, subagent.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions, sub-agent start/complete), 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.",
|
|
146
192
|
'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; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), 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 (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout), 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; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver), context.updated/threshold-crossed (token + cost), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
|
|
193
|
+
'Sub-agent meta events: a sub-agent\'s own turn.retry/turn.error/tool.failed/tool.unresolved events are merged into this same timeline, tagged with `detail.subAgent` — a `"<parent> › <sub-agent>"` breadcrumb that composes when nested (e.g. `"UI Builder › Planner › Grounding"`) — and interleaved by their original timestamps within the subagent.started→completed/failed bracket. These are the per-attempt/per-failure signals that do NOT appear among the sub-agent\'s (hoisted) messages: a malformed/empty attempt that gets retried produces no message, and the stale-vs-hallucinated split and streak counts live only on the event. A sub-agent\'s high-volume, message-derivable events (turn.start/turn.end, provider.selected, context.updated) are intentionally NOT merged — read its hoisted messages for model/tokens/cost and turn-by-turn activity, and the bracketing subagent.* events for the run\'s span.',
|
|
147
194
|
"`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.",
|
|
148
195
|
'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.',
|
|
149
196
|
];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
+
import { clearMetaEventRegistry, clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, } from './debug-event-log';
|
|
3
|
+
const event = (overrides = {}) => (Object.assign({ index: 0, timestamp: '2026-06-19T16:00:00.000Z', type: 'turn.retry', importance: 'normal' }, overrides));
|
|
4
|
+
const suite = createLogicSuite('debug-event-log merge/clear');
|
|
5
|
+
suite('mergeMetaEvents is a no-op for an empty batch', () => {
|
|
6
|
+
clearMetaEventRegistry();
|
|
7
|
+
mergeMetaEvents('k', []);
|
|
8
|
+
assert.is(getMetaEvents('k').length, 0);
|
|
9
|
+
});
|
|
10
|
+
suite('mergeMetaEvents preserves each event timestamp and re-indexes monotonically', () => {
|
|
11
|
+
clearMetaEventRegistry();
|
|
12
|
+
mergeMetaEvents('k', [
|
|
13
|
+
event({ index: 7, timestamp: '2026-06-19T16:00:01.000Z', detail: { a: 1 } }),
|
|
14
|
+
event({ index: 99, timestamp: '2026-06-19T16:00:02.000Z', detail: { b: 2 } }),
|
|
15
|
+
]);
|
|
16
|
+
const out = getMetaEvents('k');
|
|
17
|
+
assert.is(out.length, 2);
|
|
18
|
+
// original timestamps kept (not re-stamped) ...
|
|
19
|
+
assert.is(out[0].timestamp, '2026-06-19T16:00:01.000Z');
|
|
20
|
+
assert.is(out[1].timestamp, '2026-06-19T16:00:02.000Z');
|
|
21
|
+
// ... indices re-assigned from the target's counter, not carried from source.
|
|
22
|
+
assert.is(out[0].index, 0);
|
|
23
|
+
assert.is(out[1].index, 1);
|
|
24
|
+
assert.equal(out[0].detail, { a: 1 });
|
|
25
|
+
});
|
|
26
|
+
suite('mergeMetaEvents appends after existing events with a continuing index', () => {
|
|
27
|
+
clearMetaEventRegistry();
|
|
28
|
+
recordMetaEvent('k', 'turn.start'); // index 0, real timestamp
|
|
29
|
+
mergeMetaEvents('k', [event({ type: 'turn.error', importance: 'high' })]);
|
|
30
|
+
const out = getMetaEvents('k');
|
|
31
|
+
assert.is(out.length, 2);
|
|
32
|
+
assert.is(out[0].type, 'turn.start');
|
|
33
|
+
assert.is(out[1].type, 'turn.error');
|
|
34
|
+
assert.is(out[1].index, 1); // continues the monotonic counter
|
|
35
|
+
});
|
|
36
|
+
suite('mergeMetaEvents into a fresh key creates the bucket', () => {
|
|
37
|
+
clearMetaEventRegistry();
|
|
38
|
+
mergeMetaEvents('brand-new', [event()]);
|
|
39
|
+
assert.is(getMetaEvents('brand-new').length, 1);
|
|
40
|
+
});
|
|
41
|
+
suite('mergeMetaEvents evicts oldest non-high events past the cap but keeps every high', () => {
|
|
42
|
+
clearMetaEventRegistry();
|
|
43
|
+
const highStamp = '2026-06-19T16:30:00.000Z';
|
|
44
|
+
// One `high` failure first, then a flood of `normal` events well past the cap.
|
|
45
|
+
const batch = [
|
|
46
|
+
event({ type: 'turn.error', importance: 'high', timestamp: highStamp }),
|
|
47
|
+
];
|
|
48
|
+
for (let i = 0; i < 2000; i += 1) {
|
|
49
|
+
batch.push(event({ type: 'turn.retry', importance: 'normal' }));
|
|
50
|
+
}
|
|
51
|
+
mergeMetaEvents('k', batch);
|
|
52
|
+
const out = getMetaEvents('k');
|
|
53
|
+
// Eviction ran (bounded below what we merged) ...
|
|
54
|
+
assert.ok(out.length < 2001);
|
|
55
|
+
// ... and the lone high-importance failure was never dropped.
|
|
56
|
+
assert.is(out.filter((e) => e.importance === 'high').length, 1);
|
|
57
|
+
assert.ok(out.some((e) => e.timestamp === highStamp));
|
|
58
|
+
});
|
|
59
|
+
suite('clearSession drops one key without touching others', () => {
|
|
60
|
+
clearMetaEventRegistry();
|
|
61
|
+
recordMetaEvent('child', 'turn.retry');
|
|
62
|
+
recordMetaEvent('parent', 'turn.start');
|
|
63
|
+
clearSession('child');
|
|
64
|
+
assert.is(getMetaEvents('child').length, 0);
|
|
65
|
+
assert.is(getMetaEvents('parent').length, 1);
|
|
66
|
+
});
|
|
67
|
+
suite.run();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flatten sub-agent conversations into a single top-level message stream for the
|
|
3
|
+
* debug-log timeline.
|
|
4
|
+
*
|
|
5
|
+
* A tool call that delegates to a sub-agent carries the sub-agent's entire
|
|
6
|
+
* conversation on `toolCall.subAgentTrace`. The live UI reads that nested shape,
|
|
7
|
+
* but a top-to-bottom timeline reads far better with those messages inline next to
|
|
8
|
+
* the turn that produced them. This walks the list and, for every tool call with a
|
|
9
|
+
* `subAgentTrace`, hoists the sub-agent's messages up as their own timeline
|
|
10
|
+
* entries while **removing** the nested copy from the emitted parent tool call — a
|
|
11
|
+
* move, not a copy. The data is relocated, never duplicated: the export stays the
|
|
12
|
+
* same size and a naive `sum(timeline[].cost)` does not double-count (the
|
|
13
|
+
* canonical `sumCosts` total still reads the un-flattened history).
|
|
14
|
+
*
|
|
15
|
+
* Hoisted entries are:
|
|
16
|
+
* - **breadcrumbed**: `agentName` becomes `"<parent> › <sub-agent>"`, composing for
|
|
17
|
+
* nested sub-agents (`"<parent> › <sub> › <subsub>"`); the raw sub-agent name is
|
|
18
|
+
* preserved on `subAgentName`.
|
|
19
|
+
* - **depth-tagged** (`subAgentDepth`) and **correlated** to the spawning tool call
|
|
20
|
+
* (`subAgentOf`), so the delegation tree is reconstructable even when two
|
|
21
|
+
* sub-agents run within the same parent turn.
|
|
22
|
+
*
|
|
23
|
+
* Top-level messages (depth 0) are emitted unchanged apart from the trace strip.
|
|
24
|
+
* Order is preserved within each list; the caller sorts the whole timeline by
|
|
25
|
+
* timestamp afterwards, and every sub-agent message carries its own timestamp, so
|
|
26
|
+
* hoisted entries interleave chronologically between the parent's tool-call and
|
|
27
|
+
* tool-result messages.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export function flattenSubAgentMessages(messages, prefix = '', depth = 0, subAgentOf) {
|
|
32
|
+
return messages.flatMap((m) => {
|
|
33
|
+
var _a, _b, _c;
|
|
34
|
+
const breadcrumb = prefix ? `${prefix} › ${(_a = m.agentName) !== null && _a !== void 0 ? _a : '?'}` : m.agentName;
|
|
35
|
+
const traceCalls = (_c = (_b = m.toolCalls) === null || _b === void 0 ? void 0 : _b.filter((tc) => { var _a; return (_a = tc.subAgentTrace) === null || _a === void 0 ? void 0 : _a.length; })) !== null && _c !== void 0 ? _c : [];
|
|
36
|
+
const entry = Object.assign(Object.assign(Object.assign({ kind: 'message' }, m), (traceCalls.length > 0 && {
|
|
37
|
+
toolCalls: m.toolCalls.map((tc) => tc.subAgentTrace ? Object.assign(Object.assign({}, tc), { subAgentTrace: undefined }) : tc),
|
|
38
|
+
})), (depth > 0 && {
|
|
39
|
+
agentName: breadcrumb,
|
|
40
|
+
subAgentName: m.agentName,
|
|
41
|
+
subAgentDepth: depth,
|
|
42
|
+
subAgentOf,
|
|
43
|
+
}));
|
|
44
|
+
if (traceCalls.length === 0)
|
|
45
|
+
return [entry];
|
|
46
|
+
const hoisted = traceCalls.flatMap((tc) => flattenSubAgentMessages(tc.subAgentTrace, breadcrumb !== null && breadcrumb !== void 0 ? breadcrumb : '', depth + 1, tc.id));
|
|
47
|
+
return [entry, ...hoisted];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
+
import { flattenSubAgentMessages } from './flatten-sub-agent-messages';
|
|
3
|
+
const msg = (overrides = {}) => (Object.assign({ role: 'assistant', content: '' }, overrides));
|
|
4
|
+
/** A parent assistant message whose single tool call delegated to a sub-agent. */
|
|
5
|
+
const delegating = (id, agentName, trace) => msg({
|
|
6
|
+
agentName,
|
|
7
|
+
toolCalls: [{ id, name: 'requestSubAgent', args: {}, subAgentTrace: trace }],
|
|
8
|
+
});
|
|
9
|
+
const suite = createLogicSuite('flattenSubAgentMessages');
|
|
10
|
+
suite('returns an empty array for an empty list', () => {
|
|
11
|
+
assert.equal(flattenSubAgentMessages([]), []);
|
|
12
|
+
});
|
|
13
|
+
suite('emits top-level messages unchanged and tags none as sub-agent', () => {
|
|
14
|
+
const out = flattenSubAgentMessages([
|
|
15
|
+
msg({ role: 'user', content: 'hi', agentName: 'UI Builder' }),
|
|
16
|
+
msg({ content: 'hello', agentName: 'UI Builder' }),
|
|
17
|
+
]);
|
|
18
|
+
assert.is(out.length, 2);
|
|
19
|
+
assert.is(out[0].kind, 'message');
|
|
20
|
+
assert.is(out[0].agentName, 'UI Builder');
|
|
21
|
+
// No depth/marker fields on top-level entries.
|
|
22
|
+
assert.is(out[0].subAgentDepth, undefined);
|
|
23
|
+
assert.is(out[0].subAgentName, undefined);
|
|
24
|
+
assert.is(out[1].subAgentOf, undefined);
|
|
25
|
+
});
|
|
26
|
+
suite('hoists a sub-agent trace to top-level entries after the spawning message', () => {
|
|
27
|
+
const trace = [
|
|
28
|
+
msg({
|
|
29
|
+
agentName: 'UI Architecture Planner',
|
|
30
|
+
content: '',
|
|
31
|
+
toolCalls: [{ id: 'g', name: 'grep_source', args: {} }],
|
|
32
|
+
}),
|
|
33
|
+
msg({
|
|
34
|
+
role: 'tool',
|
|
35
|
+
agentName: 'UI Architecture Planner',
|
|
36
|
+
toolResult: { toolCallId: 'g', content: 'result' },
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
39
|
+
const out = flattenSubAgentMessages([
|
|
40
|
+
delegating('tc1', 'UI Builder', trace),
|
|
41
|
+
msg({
|
|
42
|
+
role: 'tool',
|
|
43
|
+
agentName: 'UI Builder',
|
|
44
|
+
toolResult: { toolCallId: 'tc1', content: 'plan done' },
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
// parent tool-call msg, then 2 hoisted children, then the parent tool-result msg.
|
|
48
|
+
assert.is(out.length, 4);
|
|
49
|
+
assert.is(out[0].agentName, 'UI Builder'); // parent unchanged
|
|
50
|
+
assert.is(out[1].agentName, 'UI Builder › UI Architecture Planner');
|
|
51
|
+
assert.is(out[2].agentName, 'UI Builder › UI Architecture Planner');
|
|
52
|
+
assert.is(out[3].agentName, 'UI Builder'); // parent tool-result, back at top level
|
|
53
|
+
});
|
|
54
|
+
suite('marks hoisted entries with depth, raw name, and the spawning tool-call id', () => {
|
|
55
|
+
const out = flattenSubAgentMessages([
|
|
56
|
+
delegating('tc1', 'UI Builder', [msg({ agentName: 'UI Architecture Planner', content: 'x' })]),
|
|
57
|
+
]);
|
|
58
|
+
const child = out[1];
|
|
59
|
+
assert.is(child.subAgentDepth, 1);
|
|
60
|
+
assert.is(child.subAgentName, 'UI Architecture Planner');
|
|
61
|
+
assert.is(child.subAgentOf, 'tc1');
|
|
62
|
+
});
|
|
63
|
+
suite('moves the trace out — the emitted parent tool call no longer carries it (no duplication)', () => {
|
|
64
|
+
const trace = [msg({ agentName: 'Planner', content: 'only once' })];
|
|
65
|
+
const out = flattenSubAgentMessages([delegating('tc1', 'UI Builder', trace)]);
|
|
66
|
+
// The trace is stripped from the emitted tool call ...
|
|
67
|
+
assert.is(out[0].toolCalls[0].subAgentTrace, undefined);
|
|
68
|
+
// ... and surfaces exactly once, as a hoisted top-level entry.
|
|
69
|
+
const hoisted = out.filter((e) => e.subAgentDepth === 1);
|
|
70
|
+
assert.is(hoisted.length, 1);
|
|
71
|
+
assert.is(hoisted[0].content, 'only once');
|
|
72
|
+
});
|
|
73
|
+
suite('preserves per-message usage fields on hoisted entries', () => {
|
|
74
|
+
const out = flattenSubAgentMessages([
|
|
75
|
+
delegating('tc1', 'UI Builder', [
|
|
76
|
+
msg({
|
|
77
|
+
agentName: 'Planner',
|
|
78
|
+
model: 'claude-sonnet-4-6',
|
|
79
|
+
inputTokens: 100,
|
|
80
|
+
outputTokens: 20,
|
|
81
|
+
cost: 0.5,
|
|
82
|
+
}),
|
|
83
|
+
]),
|
|
84
|
+
]);
|
|
85
|
+
const child = out[1];
|
|
86
|
+
assert.is(child.model, 'claude-sonnet-4-6');
|
|
87
|
+
assert.is(child.inputTokens, 100);
|
|
88
|
+
assert.is(child.outputTokens, 20);
|
|
89
|
+
assert.is(child.cost, 0.5);
|
|
90
|
+
});
|
|
91
|
+
suite('composes the breadcrumb for nested sub-agents', () => {
|
|
92
|
+
const grandchild = [msg({ agentName: 'Grounding', content: 'deep' })];
|
|
93
|
+
const childTrace = [delegating('tc2', 'Planner', grandchild)];
|
|
94
|
+
const out = flattenSubAgentMessages([delegating('tc1', 'UI Builder', childTrace)]);
|
|
95
|
+
// top: UI Builder | depth1: Planner | depth2: Grounding
|
|
96
|
+
const depth1 = out.find((e) => e.subAgentDepth === 1);
|
|
97
|
+
const depth2 = out.find((e) => e.subAgentDepth === 2);
|
|
98
|
+
assert.is(depth1.agentName, 'UI Builder › Planner');
|
|
99
|
+
assert.is(depth2.agentName, 'UI Builder › Planner › Grounding');
|
|
100
|
+
assert.is(depth2.subAgentName, 'Grounding');
|
|
101
|
+
assert.is(depth2.subAgentOf, 'tc2');
|
|
102
|
+
});
|
|
103
|
+
suite('correlates two sub-agents spawned in the same parent turn', () => {
|
|
104
|
+
const parent = msg({
|
|
105
|
+
agentName: 'UI Builder',
|
|
106
|
+
toolCalls: [
|
|
107
|
+
{
|
|
108
|
+
id: 'a',
|
|
109
|
+
name: 'requestSubAgent',
|
|
110
|
+
args: {},
|
|
111
|
+
subAgentTrace: [msg({ agentName: 'Planner', content: 'p' })],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'b',
|
|
115
|
+
name: 'requestSubAgent',
|
|
116
|
+
args: {},
|
|
117
|
+
subAgentTrace: [msg({ agentName: 'Reviewer', content: 'r' })],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
const out = flattenSubAgentMessages([parent]);
|
|
122
|
+
const planner = out.find((e) => e.subAgentName === 'Planner');
|
|
123
|
+
const reviewer = out.find((e) => e.subAgentName === 'Reviewer');
|
|
124
|
+
assert.is(planner.subAgentOf, 'a');
|
|
125
|
+
assert.is(reviewer.subAgentOf, 'b');
|
|
126
|
+
assert.is(planner.agentName, 'UI Builder › Planner');
|
|
127
|
+
assert.is(reviewer.agentName, 'UI Builder › Reviewer');
|
|
128
|
+
});
|
|
129
|
+
suite('falls back gracefully when the spawning message has no agentName', () => {
|
|
130
|
+
const out = flattenSubAgentMessages([
|
|
131
|
+
delegating('tc1', undefined, [
|
|
132
|
+
msg({ agentName: 'Planner', content: 'x' }),
|
|
133
|
+
]),
|
|
134
|
+
]);
|
|
135
|
+
// No parent name to prefix → show the sub-agent's own name unbreadcrumbed.
|
|
136
|
+
assert.is(out[1].agentName, 'Planner');
|
|
137
|
+
assert.is(out[1].subAgentName, 'Planner');
|
|
138
|
+
});
|
|
139
|
+
suite.run();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project an `AgentConfig` down to a JSON-serializable shape for the redux
|
|
3
|
+
* session store. Drops two kinds of field:
|
|
4
|
+
*
|
|
5
|
+
* 1. **Directly function-valued** — the lifecycle/dispatch hooks (`onActivate`,
|
|
6
|
+
* `onDeactivate`, `getDebugSnapshot`, `onUnresolvedTool`) and the function
|
|
7
|
+
* form of the per-turn resolvers (`systemPrompt`, `toolDefinitions`,
|
|
8
|
+
* `displayName`, `provider`, `temperature`, `toolChoice`, `toolHandlers`).
|
|
9
|
+
* 2. **Object "handler bags" whose *values* are functions** — `toolHandlers` in
|
|
10
|
+
* its object form is `{ name: handler }`, so `typeof` is `'object'`, not
|
|
11
|
+
* `'function'`. A by-value check on the field alone misses it, leaking a live
|
|
12
|
+
* handler into store state and tripping redux's serializability check (the
|
|
13
|
+
* regression this guards against — top-level agents tend to use the factory
|
|
14
|
+
* function form, but sub-agents commonly declare an object literal).
|
|
15
|
+
*
|
|
16
|
+
* `subAgents` are projected recursively. Static forms (string / number / array /
|
|
17
|
+
* plain data object) pass through unchanged.
|
|
18
|
+
*
|
|
19
|
+
* Filtering by value rather than an explicit field list means a new
|
|
20
|
+
* function-valued field on `AgentConfig` is handled automatically — no denylist
|
|
21
|
+
* to maintain. The live config on the driver stays the source of truth; the
|
|
22
|
+
* slice only holds this serializable projection and functions are never read
|
|
23
|
+
* back from it.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function stripAgentHandlers(agent) {
|
|
28
|
+
var _a;
|
|
29
|
+
const serializable = {};
|
|
30
|
+
for (const [key, value] of Object.entries(agent)) {
|
|
31
|
+
// Projected recursively below.
|
|
32
|
+
if (key === 'subAgents')
|
|
33
|
+
continue;
|
|
34
|
+
// Directly function-valued field.
|
|
35
|
+
if (typeof value === 'function')
|
|
36
|
+
continue;
|
|
37
|
+
// Object/array whose values include a function (e.g. `toolHandlers` in object
|
|
38
|
+
// form). Drop the whole field — the projection type omits it and a function
|
|
39
|
+
// must never reach the store.
|
|
40
|
+
if (value !== null &&
|
|
41
|
+
typeof value === 'object' &&
|
|
42
|
+
Object.values(value).some((v) => typeof v === 'function')) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
serializable[key] = value;
|
|
46
|
+
}
|
|
47
|
+
if ((_a = agent.subAgents) === null || _a === void 0 ? void 0 : _a.length) {
|
|
48
|
+
serializable.subAgents = agent.subAgents.map(stripAgentHandlers);
|
|
49
|
+
}
|
|
50
|
+
return serializable;
|
|
51
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
+
import { stripAgentHandlers } from './strip-agent-handlers';
|
|
4
|
+
const cfg = (overrides) => (Object.assign({ name: 'agent' }, overrides));
|
|
5
|
+
/** Mirrors what redux's serializable-state check does: find any function anywhere. */
|
|
6
|
+
const hasFunctionDeep = (value) => {
|
|
7
|
+
if (typeof value === 'function')
|
|
8
|
+
return true;
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value.some(hasFunctionDeep);
|
|
11
|
+
if (value !== null && typeof value === 'object') {
|
|
12
|
+
return Object.values(value).some(hasFunctionDeep);
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
};
|
|
16
|
+
const suite = createLogicSuite('stripAgentHandlers');
|
|
17
|
+
suite('drops object-form toolHandlers (the redux-leak regression)', () => {
|
|
18
|
+
const stripped = stripAgentHandlers(cfg({ toolHandlers: { vfs_list: () => __awaiter(void 0, void 0, void 0, function* () { return 'x'; }), vfs_read: () => __awaiter(void 0, void 0, void 0, function* () { return 'y'; }) } }));
|
|
19
|
+
// The whole handler bag is gone — not left behind with live functions inside.
|
|
20
|
+
assert.not.ok(stripped.toolHandlers);
|
|
21
|
+
assert.not.ok(hasFunctionDeep(stripped));
|
|
22
|
+
});
|
|
23
|
+
suite('drops factory-function toolHandlers and the lifecycle/dispatch hooks', () => {
|
|
24
|
+
const stripped = stripAgentHandlers(cfg({
|
|
25
|
+
toolHandlers: () => ({ t: () => __awaiter(void 0, void 0, void 0, function* () { return 'x'; }) }),
|
|
26
|
+
onActivate: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
27
|
+
onDeactivate: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
28
|
+
getDebugSnapshot: () => ({}),
|
|
29
|
+
onUnresolvedTool: () => 'redirect',
|
|
30
|
+
}));
|
|
31
|
+
assert.not.ok(stripped.toolHandlers);
|
|
32
|
+
assert.not.ok(stripped.onActivate);
|
|
33
|
+
assert.not.ok(stripped.onDeactivate);
|
|
34
|
+
assert.not.ok(stripped.getDebugSnapshot);
|
|
35
|
+
assert.not.ok(stripped.onUnresolvedTool);
|
|
36
|
+
});
|
|
37
|
+
suite('drops function-form resolvers but keeps static forms and plain data', () => {
|
|
38
|
+
const stripped = stripAgentHandlers(cfg({
|
|
39
|
+
systemPrompt: () => 'dynamic', // function → dropped
|
|
40
|
+
displayName: 'Static Name', // string → kept
|
|
41
|
+
temperature: 0.5, // number → kept
|
|
42
|
+
toolChoice: 'auto', // string → kept
|
|
43
|
+
toolDefinitions: [{ name: 'x', description: 'd', parameters: {} }], // data array → kept
|
|
44
|
+
manualSelection: { enabled: true, hint: 'pick me' }, // data object → kept
|
|
45
|
+
}));
|
|
46
|
+
assert.not.ok(stripped.systemPrompt);
|
|
47
|
+
assert.is(stripped.displayName, 'Static Name');
|
|
48
|
+
assert.is(stripped.temperature, 0.5);
|
|
49
|
+
assert.is(stripped.toolChoice, 'auto');
|
|
50
|
+
assert.equal(stripped.toolDefinitions, [{ name: 'x', description: 'd', parameters: {} }]);
|
|
51
|
+
assert.equal(stripped.manualSelection, { enabled: true, hint: 'pick me' });
|
|
52
|
+
});
|
|
53
|
+
suite('recurses into subAgents, stripping their object-form toolHandlers', () => {
|
|
54
|
+
const stripped = stripAgentHandlers(cfg({
|
|
55
|
+
name: 'boss',
|
|
56
|
+
toolHandlers: () => ({}),
|
|
57
|
+
subAgents: [
|
|
58
|
+
cfg({
|
|
59
|
+
name: 'worker',
|
|
60
|
+
toolHandlers: { vfs_list: () => __awaiter(void 0, void 0, void 0, function* () { return 'x'; }), vfs_read: () => __awaiter(void 0, void 0, void 0, function* () { return 'y'; }) },
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
63
|
+
}));
|
|
64
|
+
const subs = stripped.subAgents;
|
|
65
|
+
assert.is(subs.length, 1);
|
|
66
|
+
assert.is(subs[0].name, 'worker');
|
|
67
|
+
assert.not.ok(subs[0].toolHandlers, 'sub-agent toolHandlers must be stripped (the reported bug)');
|
|
68
|
+
// Nothing function-valued survives anywhere in the projection.
|
|
69
|
+
assert.not.ok(hasFunctionDeep(stripped));
|
|
70
|
+
});
|
|
71
|
+
suite('produces a projection with no function at any depth', () => {
|
|
72
|
+
const stripped = stripAgentHandlers(cfg({
|
|
73
|
+
name: 'boss',
|
|
74
|
+
onActivate: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
75
|
+
subAgents: [cfg({ name: 'w', toolHandlers: { t: () => __awaiter(void 0, void 0, void 0, function* () { }) } })],
|
|
76
|
+
}));
|
|
77
|
+
assert.not.ok(hasFunctionDeep(stripped));
|
|
78
|
+
// Round-trips to the expected serializable shape.
|
|
79
|
+
assert.equal(JSON.parse(JSON.stringify(stripped)), { name: 'boss', subAgents: [{ name: 'w' }] });
|
|
80
|
+
});
|
|
81
|
+
suite.run();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.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/align-event-globals.ts","../src/components/chat-driver/chat-driver.test.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.test.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/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.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"}
|
|
1
|
+
{"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.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/align-event-globals.ts","../src/components/chat-driver/chat-driver.test.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.test.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.test.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/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.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/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.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.467.
|
|
4
|
+
"version": "14.467.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.467.
|
|
68
|
-
"@genesislcap/genx": "14.467.
|
|
69
|
-
"@genesislcap/rollup-builder": "14.467.
|
|
70
|
-
"@genesislcap/ts-builder": "14.467.
|
|
71
|
-
"@genesislcap/uvu-playwright-builder": "14.467.
|
|
72
|
-
"@genesislcap/vite-builder": "14.467.
|
|
73
|
-
"@genesislcap/webpack-builder": "14.467.
|
|
67
|
+
"@genesislcap/foundation-testing": "14.467.2",
|
|
68
|
+
"@genesislcap/genx": "14.467.2",
|
|
69
|
+
"@genesislcap/rollup-builder": "14.467.2",
|
|
70
|
+
"@genesislcap/ts-builder": "14.467.2",
|
|
71
|
+
"@genesislcap/uvu-playwright-builder": "14.467.2",
|
|
72
|
+
"@genesislcap/vite-builder": "14.467.2",
|
|
73
|
+
"@genesislcap/webpack-builder": "14.467.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.467.
|
|
79
|
-
"@genesislcap/foundation-logger": "14.467.
|
|
80
|
-
"@genesislcap/foundation-redux": "14.467.
|
|
81
|
-
"@genesislcap/foundation-ui": "14.467.
|
|
82
|
-
"@genesislcap/foundation-utils": "14.467.
|
|
83
|
-
"@genesislcap/rapid-design-system": "14.467.
|
|
84
|
-
"@genesislcap/web-core": "14.467.
|
|
78
|
+
"@genesislcap/foundation-ai": "14.467.2",
|
|
79
|
+
"@genesislcap/foundation-logger": "14.467.2",
|
|
80
|
+
"@genesislcap/foundation-redux": "14.467.2",
|
|
81
|
+
"@genesislcap/foundation-ui": "14.467.2",
|
|
82
|
+
"@genesislcap/foundation-utils": "14.467.2",
|
|
83
|
+
"@genesislcap/rapid-design-system": "14.467.2",
|
|
84
|
+
"@genesislcap/web-core": "14.467.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": "2edbf1311578ebd00440aacdd362b30411428923"
|
|
97
97
|
}
|