@genesislcap/ai-assistant 15.6.2 → 15.7.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 +422 -6
- package/dist/ai-assistant.d.ts +654 -19
- package/dist/chat-driver.cjs +285 -26
- package/dist/chat-driver.cjs.map +3 -3
- package/dist/chat-driver.mjs +285 -26
- package/dist/chat-driver.mjs.map +3 -3
- package/dist/custom-elements.json +289 -32
- package/dist/dts/channel/ai-activity-channel.d.ts +51 -1
- package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +99 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +2 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +1 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +14 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/main/blocked-state.test.d.ts +2 -0
- package/dist/dts/main/blocked-state.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +435 -19
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.styles.test.d.ts +2 -0
- package/dist/dts/main/main.styles.test.d.ts.map +1 -0
- package/dist/dts/main/main.template.d.ts +53 -0
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +162 -6
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.d.ts +6 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +11 -0
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/banked-usage-baselines.d.ts +51 -0
- package/dist/dts/utils/banked-usage-baselines.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +263 -21
- package/dist/esm/components/chat-driver/chat-driver.test.js +464 -1
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +312 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +89 -4
- package/dist/esm/components/settings-modal/settings-modal.styles.js +63 -18
- package/dist/esm/components/settings-modal/settings-modal.template.js +36 -7
- package/dist/esm/main/blocked-state.test.js +969 -0
- package/dist/esm/main/cost-session-banking.test.js +238 -8
- package/dist/esm/main/main.js +826 -48
- package/dist/esm/main/main.styles.js +72 -0
- package/dist/esm/main/main.styles.test.js +99 -0
- package/dist/esm/main/main.template.js +121 -4
- package/dist/esm/state/ai-assistant-slice.js +145 -7
- package/dist/esm/state/ai-assistant-slice.test.js +138 -1
- package/dist/esm/state/debug-event-log.js +7 -2
- package/dist/esm/state/debug-event-log.test.js +49 -1
- package/dist/esm/state/persistence/session-snapshot.test.js +18 -0
- package/dist/esm/utils/banked-usage-baselines.js +84 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/migration-GENC-1464.md +562 -0
- package/docs/sub_agent.md +20 -3
- package/package.json +17 -17
- package/src/channel/ai-activity-channel.ts +56 -2
- package/src/components/chat-driver/chat-driver.test.ts +549 -0
- package/src/components/chat-driver/chat-driver.ts +324 -14
- package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +438 -0
- package/src/components/orchestrating-driver/orchestrating-driver.ts +101 -6
- package/src/components/settings-modal/settings-modal.styles.ts +63 -18
- package/src/components/settings-modal/settings-modal.template.ts +38 -7
- package/src/main/blocked-state.test.ts +1316 -0
- package/src/main/cost-session-banking.test.ts +283 -11
- package/src/main/main.styles.test.ts +130 -0
- package/src/main/main.styles.ts +72 -0
- package/src/main/main.template.ts +131 -4
- package/src/main/main.ts +827 -42
- package/src/state/ai-assistant-slice.test.ts +215 -0
- package/src/state/ai-assistant-slice.ts +218 -8
- package/src/state/debug-event-log.test.ts +63 -0
- package/src/state/debug-event-log.ts +7 -2
- package/src/state/persistence/session-snapshot.test.ts +22 -0
- package/src/utils/banked-usage-baselines.ts +90 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
2
|
import { aiAssistantSlice, createDefaultSessionState, } from './ai-assistant-slice';
|
|
3
3
|
// GENC-1351 §5.6 / Phase 1: the new bulk loadSession + resetSession reducers.
|
|
4
|
-
const { loadSession, resetSession, setMessages, setState, setSessionMenuOpen, setShowToolCalls, setShowThinkingSteps, setShowNarration, setShowAgentSwitchIndicator, setEnabledAnimations, } = aiAssistantSlice.actions;
|
|
4
|
+
const { loadSession, resetSession, setBlocked, setBlockedReason, setVendorBlocked, setInputValue, setMessages, setState, setSessionMenuOpen, setShowToolCalls, setShowThinkingSteps, setShowNarration, setShowAgentSwitchIndicator, setEnabledAnimations, } = aiAssistantSlice.actions;
|
|
5
5
|
const reduce = aiAssistantSlice.reducer;
|
|
6
6
|
const Suite = createLogicSuite('ai-assistant-slice-persistence');
|
|
7
7
|
Suite('loadSession hydrates the restorable fields and forces idle', () => {
|
|
@@ -89,4 +89,141 @@ Suite('setSessionMenuOpen toggles the lifecycle-menu flag; resetSession clears i
|
|
|
89
89
|
const cleared = reduce(state, resetSession());
|
|
90
90
|
assert.is(cleared.sessionMenuOpen, false);
|
|
91
91
|
});
|
|
92
|
+
// GENC-1464: the backend-blocked latch (exhausted AI budget).
|
|
93
|
+
Suite('setBlocked latches the flag and its reason', () => {
|
|
94
|
+
let state = createDefaultSessionState();
|
|
95
|
+
assert.is(state.blocked, false);
|
|
96
|
+
assert.is(state.blockedReason, null);
|
|
97
|
+
state = reduce(state, setBlocked({ blocked: true }));
|
|
98
|
+
assert.is(state.blocked, true);
|
|
99
|
+
assert.is(state.blockedReason, null, 'no reason given → null, so the element uses its default');
|
|
100
|
+
state = reduce(state, setBlocked({ blocked: true, reason: 'Budget exhausted — contact billing' }));
|
|
101
|
+
assert.is(state.blockedReason, 'Budget exhausted — contact billing');
|
|
102
|
+
});
|
|
103
|
+
Suite('unblocking always clears the reason', () => {
|
|
104
|
+
// A stale explanation must never outlive the condition it described.
|
|
105
|
+
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'gone' }));
|
|
106
|
+
state = reduce(state, setBlocked({ blocked: false }));
|
|
107
|
+
assert.is(state.blocked, false);
|
|
108
|
+
assert.is(state.blockedReason, null);
|
|
109
|
+
});
|
|
110
|
+
Suite('resetSession preserves the block — "New chat" does not refill the budget', () => {
|
|
111
|
+
// Deliberately unlike sessionMenuOpen/compacting above: this is a backend
|
|
112
|
+
// condition, not conversation state, so Clear must not hand the user a
|
|
113
|
+
// working-looking composer that fails on the next send.
|
|
114
|
+
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'no funds' }));
|
|
115
|
+
state = reduce(state, setMessages([{ role: 'user', content: 'x' }]));
|
|
116
|
+
const cleared = reduce(state, resetSession());
|
|
117
|
+
assert.is(cleared.messages.length, 0, 'the transcript is still cleared');
|
|
118
|
+
assert.is(cleared.blocked, true, 'the block survives Clear');
|
|
119
|
+
assert.is(cleared.blockedReason, 'no funds', 'and so does its reason');
|
|
120
|
+
});
|
|
121
|
+
Suite('re-latching without a reason preserves the host explanation', () => {
|
|
122
|
+
// The race this fixes is deterministic, not theoretical: `tool-loop-end` is
|
|
123
|
+
// published from the driver's `finally`, i.e. BEFORE `sendMessage()` resolves.
|
|
124
|
+
// So a bus-subscribed host (what the migration guide's Option C recommends)
|
|
125
|
+
// writes its detailed copy first, and the element's own latch — which may have
|
|
126
|
+
// no figures to offer — lands a moment later. Treating an OMITTED reason as
|
|
127
|
+
// "clear it" replaced the host's copy with generic copy every single time.
|
|
128
|
+
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'contact ops@acme.com' }));
|
|
129
|
+
state = reduce(state, setBlocked({ blocked: true }));
|
|
130
|
+
assert.is(state.blocked, true);
|
|
131
|
+
assert.is(state.blockedReason, 'contact ops@acme.com', 'the host copy survives the re-latch');
|
|
132
|
+
});
|
|
133
|
+
Suite('reason:null clears the explanation while staying blocked', () => {
|
|
134
|
+
// The explicit escape hatch the omitted case gives up: `null` still clears.
|
|
135
|
+
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'stale copy' }));
|
|
136
|
+
state = reduce(state, setBlocked({ blocked: true, reason: null }));
|
|
137
|
+
assert.is(state.blocked, true);
|
|
138
|
+
assert.is(state.blockedReason, null, 'the banner falls back to the default copy');
|
|
139
|
+
});
|
|
140
|
+
Suite('resetSession still resets everything NOT on the preserved list', () => {
|
|
141
|
+
// Pins the boundary from the other side, so the preserved list cannot quietly
|
|
142
|
+
// grow: these are conversation/UI state and Clear must wipe them.
|
|
143
|
+
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'r' }));
|
|
144
|
+
state = reduce(state, setSessionMenuOpen(true));
|
|
145
|
+
state = reduce(state, setInputValue('half-typed message'));
|
|
146
|
+
const cleared = reduce(state, resetSession());
|
|
147
|
+
assert.is(cleared.inputValue, '', 'the composer draft is reset');
|
|
148
|
+
assert.is(cleared.sessionMenuOpen, false, 'the panel flag is reset');
|
|
149
|
+
assert.is(cleared.blocked, true, '…while the block is still preserved');
|
|
150
|
+
});
|
|
151
|
+
// ── Per-vendor latches (GENC-1464 per-vendor budgets) ──────────────────────────
|
|
152
|
+
Suite('setVendorBlocked walls one vendor and keeps the wall order', () => {
|
|
153
|
+
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'anthropic copy' }));
|
|
154
|
+
assert.equal(state.blockedVendors, ['anthropic']);
|
|
155
|
+
assert.equal(state.blockedVendorReasons, { anthropic: 'anthropic copy' });
|
|
156
|
+
assert.is(state.blocked, false, 'the vendor-agnostic flag is untouched');
|
|
157
|
+
state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true }));
|
|
158
|
+
assert.equal(state.blockedVendors, ['anthropic', 'gemini'], 'order is the order they were walled');
|
|
159
|
+
});
|
|
160
|
+
Suite('walling an already-walled vendor is idempotent', () => {
|
|
161
|
+
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'gemini', blocked: true, reason: 'first' }));
|
|
162
|
+
state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true, reason: 'second' }));
|
|
163
|
+
assert.equal(state.blockedVendors, ['gemini'], 'no duplicate entry');
|
|
164
|
+
assert.is(state.blockedVendorReasons.gemini, 'second');
|
|
165
|
+
});
|
|
166
|
+
Suite('the per-vendor reason honours the same three cases as setBlocked', () => {
|
|
167
|
+
// The same two-writer race (driver latch landing after a bus-subscribed host)
|
|
168
|
+
// applies per vendor, so "omitted keeps" has to hold here too.
|
|
169
|
+
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'host copy' }));
|
|
170
|
+
state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: true }));
|
|
171
|
+
assert.is(state.blockedVendorReasons.anthropic, 'host copy', 'omitted keeps');
|
|
172
|
+
state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: null }));
|
|
173
|
+
assert.is(state.blockedVendorReasons.anthropic, undefined, 'null clears');
|
|
174
|
+
});
|
|
175
|
+
Suite('releasing a vendor drops it and its copy', () => {
|
|
176
|
+
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'r' }));
|
|
177
|
+
state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true }));
|
|
178
|
+
state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: false }));
|
|
179
|
+
assert.equal(state.blockedVendors, ['gemini']);
|
|
180
|
+
assert.is(state.blockedVendorReasons.anthropic, undefined);
|
|
181
|
+
});
|
|
182
|
+
Suite('setBlocked(false) also clears every per-vendor latch', () => {
|
|
183
|
+
// THE back-compat rule. A host asserting "the wall is gone" after its own
|
|
184
|
+
// pre-flight (migration-GENC-1464 Option B) must not be overruled by a driver
|
|
185
|
+
// latch it cannot see — without this, per-vendor would silently make the
|
|
186
|
+
// documented unblock stop working.
|
|
187
|
+
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'r' }));
|
|
188
|
+
state = reduce(state, setBlocked({ blocked: true, reason: 'global' }));
|
|
189
|
+
state = reduce(state, setBlocked({ blocked: false }));
|
|
190
|
+
assert.is(state.blocked, false);
|
|
191
|
+
assert.is(state.blockedReason, null);
|
|
192
|
+
assert.equal(state.blockedVendors, []);
|
|
193
|
+
assert.equal(state.blockedVendorReasons, {});
|
|
194
|
+
});
|
|
195
|
+
Suite('setBlockedReason writes the copy without disturbing any latch', () => {
|
|
196
|
+
// It exists precisely so the element's `blockedReason` setter cannot route
|
|
197
|
+
// through `setBlocked(false, …)` — which would now clear the vendor latches.
|
|
198
|
+
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'gemini', blocked: true }));
|
|
199
|
+
state = reduce(state, setBlockedReason('host copy'));
|
|
200
|
+
assert.is(state.blockedReason, 'host copy');
|
|
201
|
+
assert.is(state.blocked, false, 'the vendor-agnostic flag is untouched');
|
|
202
|
+
assert.equal(state.blockedVendors, ['gemini'], 'and so are the per-vendor latches');
|
|
203
|
+
});
|
|
204
|
+
Suite('resetSession preserves the per-vendor latches too', () => {
|
|
205
|
+
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'no funds' }));
|
|
206
|
+
state = reduce(state, setMessages([{ role: 'user', content: 'hi' }]));
|
|
207
|
+
const cleared = reduce(state, resetSession());
|
|
208
|
+
assert.equal(cleared.messages, [], 'the conversation is wiped');
|
|
209
|
+
assert.equal(cleared.blockedVendors, ['anthropic'], 'Clear does not refill a vendor budget');
|
|
210
|
+
assert.equal(cleared.blockedVendorReasons, { anthropic: 'no funds' });
|
|
211
|
+
});
|
|
212
|
+
// `resetSession` preserves its keys through `Object.assign`, whose source parameter
|
|
213
|
+
// is structurally permissive: a typo like `blockedResaon` would compile clean,
|
|
214
|
+
// silently stop preserving the real key, AND leak a junk key into serialised state.
|
|
215
|
+
// The reducer therefore annotates its literal `satisfies
|
|
216
|
+
// Partial<AiAssistantSessionState>`, which is a COMPILE-time guarantee — so the
|
|
217
|
+
// durable proof is a type fixture, not a runtime assertion.
|
|
218
|
+
//
|
|
219
|
+
// This mirrors the reducer's clause on the same typo. If `satisfies` ever stopped
|
|
220
|
+
// rejecting excess properties (a TS behaviour change, or someone widening the
|
|
221
|
+
// annotated type), the `@ts-expect-error` below becomes unused and the BUILD fails
|
|
222
|
+
// — which is exactly the signal wanted. The runtime suites above are its
|
|
223
|
+
// behavioural companions: they pin the preserved list from both sides.
|
|
224
|
+
const _typoIsRejectedAtCompileTime = {
|
|
225
|
+
// @ts-expect-error — an excess/misspelled key must be rejected by `satisfies`
|
|
226
|
+
blockedResaon: null,
|
|
227
|
+
};
|
|
228
|
+
void _typoIsRejectedAtCompileTime;
|
|
92
229
|
Suite.run();
|
|
@@ -189,7 +189,12 @@ export function clearSession(key) {
|
|
|
189
189
|
* Human/agent-facing guide emitted as the first key of the exported debug log,
|
|
190
190
|
* so whoever opens the JSON (often an AI agent) knows how to read it without
|
|
191
191
|
* reverse-engineering the shape. Kept here next to the event catalogue it
|
|
192
|
-
* describes
|
|
192
|
+
* describes — but proximity alone demonstrably did NOT keep the two in sync
|
|
193
|
+
* (`refusal` was missing from both reason lists for a whole release), so
|
|
194
|
+
* `debug-event-log.test.ts` now asserts that every `TurnFailureReason` and every
|
|
195
|
+
* `SubAgentFailureReason` is named here, and fails the build when one is added
|
|
196
|
+
* without a matching sentence. Enumerations below read as exhaustive and WILL be
|
|
197
|
+
* trusted by an agent reading the JSON — keep them so.
|
|
193
198
|
*/
|
|
194
199
|
export const DEBUG_LOG_README = [
|
|
195
200
|
'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',
|
|
@@ -201,7 +206,7 @@ export const DEBUG_LOG_README = [
|
|
|
201
206
|
"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.",
|
|
202
207
|
"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'.",
|
|
203
208
|
"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, context.condensed). 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.",
|
|
204
|
-
'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/response-truncated, 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, for response-truncated the model + maxTokens + outputTokens + tools, 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/response_truncated), 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), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
|
|
209
|
+
'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/response-truncated/refusal/budget-exhausted, 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, for response-truncated the model + maxTokens + outputTokens + tools, 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, for budget-exhausted the budgetUsd + spentUsd figures reported by the proxy plus the resolved vendor, 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/response_truncated/refusal/budget_exhausted; budget_exhausted is terminal for the PARENT turn too — the parent stops rather than calling the model again into the same wall), 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), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
|
|
205
210
|
'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.',
|
|
206
211
|
"`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.",
|
|
207
212
|
'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.',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { clearMetaEventRegistry, clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, } from './debug-event-log';
|
|
2
|
+
import { clearMetaEventRegistry, clearSession, DEBUG_LOG_README, getMetaEvents, mergeMetaEvents, recordMetaEvent, } from './debug-event-log';
|
|
3
3
|
const event = (overrides = {}) => (Object.assign({ index: 0, timestamp: '2026-06-19T16:00:00.000Z', type: 'turn.retry', importance: 'normal' }, overrides));
|
|
4
4
|
const suite = createLogicSuite('debug-event-log merge/clear');
|
|
5
5
|
suite('mergeMetaEvents is a no-op for an empty batch', () => {
|
|
@@ -64,4 +64,52 @@ suite('clearSession drops one key without touching others', () => {
|
|
|
64
64
|
assert.is(getMetaEvents('child').length, 0);
|
|
65
65
|
assert.is(getMetaEvents('parent').length, 1);
|
|
66
66
|
});
|
|
67
|
+
// ── README ↔ taxonomy sync (GENC-1464) ─────────────────────────────────────────
|
|
68
|
+
// DEBUG_LOG_README is emitted as the FIRST key of the exported debug log
|
|
69
|
+
// specifically so an agent reading the JSON does not have to reverse-engineer it.
|
|
70
|
+
// Its reason enumerations therefore read as exhaustive and will be trusted — but
|
|
71
|
+
// they had already drifted twice (`refusal` was missing from both) before anyone
|
|
72
|
+
// noticed, because "the doc lives next to the catalogue" is not enforcement.
|
|
73
|
+
//
|
|
74
|
+
// The `Record<..., true>` below is the enforcement: adding a union member without
|
|
75
|
+
// listing it here is a COMPILE error, and listing it without documenting it is a
|
|
76
|
+
// test failure. So the next added reason fails in the one place the drift keeps
|
|
77
|
+
// happening.
|
|
78
|
+
const ALL_TURN_FAILURE_REASONS = Object.keys({
|
|
79
|
+
exception: true,
|
|
80
|
+
'malformed-function-call': true,
|
|
81
|
+
'empty-response': true,
|
|
82
|
+
'unknown-tool-limit': true,
|
|
83
|
+
'max-iterations': true,
|
|
84
|
+
'response-truncated': true,
|
|
85
|
+
refusal: true,
|
|
86
|
+
'budget-exhausted': true,
|
|
87
|
+
});
|
|
88
|
+
const ALL_SUB_AGENT_FAILURE_REASONS = Object.keys({
|
|
89
|
+
max_iterations: true,
|
|
90
|
+
malformed_tool_call: true,
|
|
91
|
+
empty_response: true,
|
|
92
|
+
unknown_tool_limit: true,
|
|
93
|
+
timeout: true,
|
|
94
|
+
response_truncated: true,
|
|
95
|
+
refusal: true,
|
|
96
|
+
budget_exhausted: true,
|
|
97
|
+
});
|
|
98
|
+
const README_TEXT = DEBUG_LOG_README.join('\n');
|
|
99
|
+
suite('DEBUG_LOG_README enumerates every TurnFailureReason', () => {
|
|
100
|
+
for (const reason of ALL_TURN_FAILURE_REASONS) {
|
|
101
|
+
assert.ok(README_TEXT.includes(reason), `turn.error reason '${reason}' is missing from DEBUG_LOG_README`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
suite('DEBUG_LOG_README enumerates every SubAgentFailureReason', () => {
|
|
105
|
+
for (const reason of ALL_SUB_AGENT_FAILURE_REASONS) {
|
|
106
|
+
assert.ok(README_TEXT.includes(reason), `subagent.failed reason '${reason}' is missing from DEBUG_LOG_README`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
suite('DEBUG_LOG_README documents the budget diagnostics that ride turn.error', () => {
|
|
110
|
+
// `recordTurnError('budget-exhausted', …)` attaches these; an agent triaging a
|
|
111
|
+
// support ticket needs to know to look for them.
|
|
112
|
+
assert.ok(README_TEXT.includes('budgetUsd'));
|
|
113
|
+
assert.ok(README_TEXT.includes('spentUsd'));
|
|
114
|
+
});
|
|
67
115
|
suite.run();
|
|
@@ -25,6 +25,24 @@ Suite('createPersistedSession stamps version + savedAt and keeps agentSnapshots'
|
|
|
25
25
|
assert.ok(s.savedAt.length > 0);
|
|
26
26
|
assert.equal(s.agentSnapshots, { A: { machine: { state: 'x', context: {} } } });
|
|
27
27
|
});
|
|
28
|
+
// GENC-1464 — the blocked latch is deliberately NOT persisted. The snapshot is
|
|
29
|
+
// per-stateKey and long-lived, so a persisted latch would survive an out-of-band
|
|
30
|
+
// budget raise with no in-element way to clear it: the user would return to a
|
|
31
|
+
// permanently dead composer against a budget that has headroom. Re-deriving the
|
|
32
|
+
// state (host pre-flight on mount, or the next 402) is strictly safer than
|
|
33
|
+
// remembering it. This test exists so a future "helpful" addition trips here and
|
|
34
|
+
// has to read that reasoning first — see `FoundationAiAssistant.blocked`'s
|
|
35
|
+
// Lifetime paragraph and docs/migration-GENC-1464.md §Scope.
|
|
36
|
+
Suite('the blocked latch is deliberately not persisted', () => {
|
|
37
|
+
const s = base();
|
|
38
|
+
assert.not.ok('blocked' in s, 'blocked must not enter the persisted snapshot');
|
|
39
|
+
assert.not.ok('blockedReason' in s, 'blockedReason must not enter the persisted snapshot');
|
|
40
|
+
// The per-vendor latches inherit the same lifetime, so they inherit the same
|
|
41
|
+
// rule — a persisted per-vendor wall is exactly as stale as a persisted global
|
|
42
|
+
// one, just harder to notice because only one vendor looks broken.
|
|
43
|
+
assert.not.ok('blockedVendors' in s, 'blockedVendors must not enter the persisted snapshot');
|
|
44
|
+
assert.not.ok('blockedVendorReasons' in s, 'blockedVendorReasons must not enter the persisted snapshot');
|
|
45
|
+
});
|
|
28
46
|
Suite('createPersistedSession defaults agentSnapshots to {}', () => {
|
|
29
47
|
const s = createPersistedSession({
|
|
30
48
|
sessionKey: 'k',
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session banked baselines for the cost ledger (GENC-1475) — the spend a project had
|
|
3
|
+
* accrued *before* the transcript currently in memory. Module-level and keyed by the session
|
|
4
|
+
* `stateKey`, exactly like the diagnostics cursors and the meta-event buffer.
|
|
5
|
+
*
|
|
6
|
+
* The lifetime is the point of this module. A baseline is only meaningful paired with the
|
|
7
|
+
* transcript it complements: `row.usage = baseline + whatever that transcript proves`. The
|
|
8
|
+
* transcript lives in the per-key session store, so the baseline must live and die with that
|
|
9
|
+
* store — which means module state keyed the same way, NOT element state.
|
|
10
|
+
*
|
|
11
|
+
* Held on the element it silently double-counted. Element instances are created and destroyed
|
|
12
|
+
* many times inside one session (the assistant is remounted as the host re-renders its layout,
|
|
13
|
+
* and a popped-out bubble and a docked panel can be wired to the same session at once), and each
|
|
14
|
+
* new instance re-derived the baseline from the ledger rows. By then those rows contain the
|
|
15
|
+
* instance-before-last's own upserts, whose `usage` already includes the live transcript — so
|
|
16
|
+
* where the transcript is NOT restored on load (`resolveBankedUsage`'s "bank the whole prior
|
|
17
|
+
* total" branch, i.e. persistence off) the live session was banked as its own baseline. Lifetime
|
|
18
|
+
* then jumped by the whole session-to-date on every remount while the live figure kept climbing
|
|
19
|
+
* normally, which is the "the two totals went up by different amounts" report.
|
|
20
|
+
*
|
|
21
|
+
* Module state dies with the JS realm, so a real page reload starts clean — exactly right, since
|
|
22
|
+
* that is also when the transcript starts over.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
const baselinesByKey = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* The baseline resolved for `key`, or `undefined` when this session has not resolved one yet.
|
|
29
|
+
*
|
|
30
|
+
* `undefined` rather than an empty usage: "nothing banked" is a legitimate resolved value (a
|
|
31
|
+
* project's first ever session), and it must not be mistaken for "not resolved", or every write
|
|
32
|
+
* would re-derive and hit the double-count above.
|
|
33
|
+
*
|
|
34
|
+
* Returns a copy — see `setBankedBaseline` for why both ends of this map are cloned.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function getBankedBaseline(key) {
|
|
39
|
+
const usage = baselinesByKey.get(key);
|
|
40
|
+
return usage ? Object.assign({}, usage) : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Record the baseline for `key`, replacing any previous one.
|
|
44
|
+
*
|
|
45
|
+
* Stores a copy, and `getBankedBaseline` hands back a copy, so nothing outside this module can
|
|
46
|
+
* reach the object the map holds. Both ends have to clone, because a baseline is aliased at both:
|
|
47
|
+
* `resolveBankedUsage` returns a *reference into a ledger row* (`row.banked` or `row.usage`),
|
|
48
|
+
* which is what gets stored here, and the caller then assigns what it reads back onto the NEXT
|
|
49
|
+
* row's `banked` field. Uncloned, one object is reachable from page-global state, two rows, and —
|
|
50
|
+
* since the row list is public and gets handed to a host's `saveCostHistory` — from host code.
|
|
51
|
+
* Whoever mutated it would silently rewrite this project's baseline for the rest of the page.
|
|
52
|
+
*
|
|
53
|
+
* A shallow spread is a complete copy: `AggregateUsage` is five flat numbers, no nested state.
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export function setBankedBaseline(key, usage) {
|
|
58
|
+
baselinesByKey.set(key, Object.assign({}, usage));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Drop the baseline for `key`, so the next write re-derives it from the ledger rows.
|
|
62
|
+
*
|
|
63
|
+
* For when the pairing is broken deliberately at the *row* end — the row this baseline came from
|
|
64
|
+
* has been deleted, or the whole ledger cleared — and for a session teardown, where the store
|
|
65
|
+
* (and with it the transcript) is disposed and a later return re-restores from scratch.
|
|
66
|
+
*
|
|
67
|
+
* NOT for a mid-session reload of the rows: those rows already include this session's own
|
|
68
|
+
* upserts, so re-deriving from them folds the live transcript into its own baseline.
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export function deleteBankedBaseline(key) {
|
|
73
|
+
baselinesByKey.delete(key);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Forget every baseline — the page-load boundary, which production gets for free from module
|
|
77
|
+
* state dying with the realm. Exists so tests can model separate page loads (and so one test's
|
|
78
|
+
* session key cannot leak a baseline into the next).
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export function resetBankedBaselines() {
|
|
83
|
+
baselinesByKey.clear();
|
|
84
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/chat-driver-node.ts","../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.compact.test.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.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.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/cost-session-banking.test.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/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/styles/settings-section.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.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/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.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/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/sum-usage.test.ts","../src/utils/sum-usage.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|
|
1
|
+
{"root":["../src/chat-driver-node.ts","../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.compact.test.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.budget.test.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.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/blocked-state.test.ts","../src/main/cost-session-banking.test.ts","../src/main/index.ts","../src/main/main.styles.test.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/styles/settings-section.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/banked-usage-baselines.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.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/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.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/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/sum-usage.test.ts","../src/utils/sum-usage.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|