@genesislcap/ai-assistant 15.12.0 → 15.13.0
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 +245 -3
- package/dist/ai-assistant.d.ts +127 -7
- package/dist/chat-driver.cjs +79 -13
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +76 -12
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +452 -359
- package/dist/dts/chat-driver-node.d.ts +2 -2
- package/dist/dts/chat-driver-node.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +45 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +2 -0
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +20 -1
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/persistence/diagnostics.d.ts +65 -8
- package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -1
- package/dist/dts/state/persistence/index.d.ts +1 -1
- package/dist/dts/state/persistence/index.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persister.d.ts +4 -3
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/utils/sum-usage.d.ts +20 -0
- package/dist/dts/utils/sum-usage.d.ts.map +1 -1
- package/dist/esm/chat-driver-node.js +12 -2
- package/dist/esm/components/chat-driver/chat-driver.js +60 -5
- package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +268 -0
- package/dist/esm/main/main.js +53 -28
- package/dist/esm/state/debug-event-log.js +7 -2
- package/dist/esm/state/persistence/diagnostics.js +79 -16
- package/dist/esm/state/persistence/diagnostics.test.js +174 -1
- package/dist/esm/state/persistence/index.js +1 -1
- package/dist/esm/state/persistence/session-persister.js +13 -5
- package/dist/esm/state/persistence/session-persister.test.js +31 -0
- package/dist/esm/utils/sum-usage.js +43 -0
- package/dist/esm/utils/sum-usage.test.js +45 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/chat-driver-node.ts +12 -2
- package/src/components/chat-driver/chat-driver.ts +107 -6
- package/src/components/chat-driver/chat-driver.turn-usage.test.ts +362 -0
- package/src/main/main.ts +52 -23
- package/src/state/debug-event-log.ts +7 -2
- package/src/state/persistence/diagnostics.test.ts +208 -1
- package/src/state/persistence/diagnostics.ts +117 -15
- package/src/state/persistence/index.ts +1 -1
- package/src/state/persistence/session-persister.test.ts +37 -0
- package/src/state/persistence/session-persister.ts +13 -5
- package/src/utils/sum-usage.test.ts +52 -1
- package/src/utils/sum-usage.ts +45 -0
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Server-saved diagnostics (GENC-1351 §5.8) — the forward-only stream that lets
|
|
3
3
|
* the debug log persist across a whole session lifetime, not just the current
|
|
4
4
|
* page load. This module owns the *pure* pieces: the entry type and the
|
|
5
|
-
* reassembly back into the `{ readme, timeline, meta }` debug-log
|
|
6
|
-
* element owns capture/cadence; the provider owns storage layout.
|
|
5
|
+
* reassembly back into the `{ readme, sessionUsage, timeline, meta }` debug-log
|
|
6
|
+
* shape. The element owns capture/cadence; the provider owns storage layout.
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
@@ -31,6 +31,28 @@ export function diagnosticsMessageKey(entry) {
|
|
|
31
31
|
? `${entry.timestamp}::${(_a = entry.role) !== null && _a !== void 0 ? _a : ''}::${(_b = entry.subAgentOf) !== null && _b !== void 0 ? _b : ''}::${(_c = entry.category) !== null && _c !== void 0 ? _c : ''}`
|
|
32
32
|
: null;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Identity of a `turn` entry — `turnIndex::timestamp::agentName`.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately NOT the serialized entry, which two copies of the same call do not always
|
|
38
|
+
* share. `buildTimelineEntries` renders `systemPrompt` *relative to the preceding
|
|
39
|
+
* snapshots* (collapsing it to `<repeated — identical to turn N>` when it matches the
|
|
40
|
+
* previous full prompt), so if the snapshot holding that full prompt is evicted from the
|
|
41
|
+
* ring buffer between the unpriced flush and the priced one, the surviving turn renders
|
|
42
|
+
* its prompt differently and the two serializations diverge. Both copies are in the
|
|
43
|
+
* stream by then, so nothing would collapse them — and the log whose purpose is per-call
|
|
44
|
+
* cost would show one model call as two turns, one of them unpriced.
|
|
45
|
+
*
|
|
46
|
+
* All three components are fixed when the snapshot is created and never re-derived.
|
|
47
|
+
* `turnIndex` alone is not enough: it restarts at `'0'` on each page load, and a lifetime
|
|
48
|
+
* log legitimately holds one turn `'0'` per load. `agentName` separates two sub-agents
|
|
49
|
+
* invoked in the same parent turn, which share a `turnIndex` prefix by construction (see
|
|
50
|
+
* `forwardSubAgentSnapshots`).
|
|
51
|
+
*/
|
|
52
|
+
function turnIdentity(entry) {
|
|
53
|
+
var _a, _b, _c;
|
|
54
|
+
return `turn::${String((_a = entry.turnIndex) !== null && _a !== void 0 ? _a : '')}::${(_b = entry.timestamp) !== null && _b !== void 0 ? _b : ''}::${String((_c = entry.agentName) !== null && _c !== void 0 ? _c : '')}`;
|
|
55
|
+
}
|
|
34
56
|
// Tie-break co-timestamped entries by cause → call → output (event, turn, message),
|
|
35
57
|
// matching the original in-place `getDebugLog` sort.
|
|
36
58
|
const KIND_RANK = { event: 0, turn: 1, message: 2 };
|
|
@@ -38,19 +60,26 @@ const KIND_RANK = { event: 0, turn: 1, message: 2 };
|
|
|
38
60
|
// are filtered out first) sorts last.
|
|
39
61
|
const UNKNOWN_KIND_RANK = 99;
|
|
40
62
|
/**
|
|
41
|
-
* Reassemble diagnostic entries into the `{ readme, timeline, meta }`
|
|
42
|
-
* shape (GENC-1351 §5.8). `timeline` = the `message`/`turn`/`event` entries sorted
|
|
63
|
+
* Reassemble diagnostic entries into the `{ readme, sessionUsage, timeline, meta }`
|
|
64
|
+
* debug-log shape (GENC-1351 §5.8). `timeline` = the `message`/`turn`/`event` entries sorted
|
|
43
65
|
* by ISO `timestamp` (kind-rank tie-break). `meta` = the **newest** `meta-snapshot`'s
|
|
44
66
|
* block only — a "state at export" photo, matching the single-page log. The older
|
|
45
67
|
* snapshots are intentionally dropped: the per-turn evolution they would show is
|
|
46
68
|
* already in the timeline (`turn.agentSnapshot` + `context.updated` events), so
|
|
47
69
|
* keeping them would just repeat the bulky, near-static `agentSummary`. `readme` =
|
|
48
|
-
* the passed (current) constant.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
70
|
+
* the passed (current) constant. `sessionUsage` = that same newest snapshot's
|
|
71
|
+
* `meta.context.sessionUsage`, lifted to the top level. Pure — reused for both the
|
|
72
|
+
* live current-page log and the reassembled lifetime log stitched from the persisted
|
|
73
|
+
* stream, so both come out shape-identical.
|
|
74
|
+
*
|
|
75
|
+
* NOTE for a caller stitching a STORED stream (a headless consumer harvesting its own
|
|
76
|
+
* lifetime log, say): the newest stored `meta-snapshot` is frozen at the last
|
|
77
|
+
* config-signature change, so `meta` and `sessionUsage` are as old as that unless you
|
|
78
|
+
* pass a fresh snapshot of your own — {@link withFreshMetaSnapshot} does exactly that,
|
|
79
|
+
* and the element's download path goes through it.
|
|
51
80
|
*/
|
|
52
81
|
export function assembleDebugLog(entries, readme) {
|
|
53
|
-
var _a, _b;
|
|
82
|
+
var _a, _b, _c;
|
|
54
83
|
// Safety net for exact-duplicate entries: two element instances sharing one
|
|
55
84
|
// session (bubble + popped-out panel) can each append the same entry, and a
|
|
56
85
|
// reload's second instance can re-append restored history. Such copies are
|
|
@@ -58,22 +87,32 @@ export function assembleDebugLog(entries, readme) {
|
|
|
58
87
|
// (e.g. two real `assistant.connected` from two instances) differ in index /
|
|
59
88
|
// placement and are kept. (The shared cursor store prevents most of this at
|
|
60
89
|
// write time; this guards the read path regardless of what's in the stream.)
|
|
61
|
-
|
|
90
|
+
//
|
|
91
|
+
// A `turn` is identified by `turnIndex` + `timestamp` + `agentName` instead (see
|
|
92
|
+
// `turnIdentity`), so the unpriced copy a mid-call flush persisted and the priced copy
|
|
93
|
+
// that follows it collapse into ONE turn — the priced one — rather than reading as two
|
|
94
|
+
// model calls.
|
|
95
|
+
const seenAt = new Map();
|
|
62
96
|
let latestMeta;
|
|
63
97
|
const timeline = [];
|
|
64
98
|
for (const entry of entries) {
|
|
65
|
-
const id = JSON.stringify(entry);
|
|
66
|
-
if (seen.has(id))
|
|
67
|
-
continue;
|
|
68
|
-
seen.add(id);
|
|
69
99
|
if (entry.kind === 'meta-snapshot') {
|
|
70
100
|
if (!latestMeta || ((_a = entry.timestamp) !== null && _a !== void 0 ? _a : '') >= ((_b = latestMeta.timestamp) !== null && _b !== void 0 ? _b : '')) {
|
|
71
101
|
latestMeta = entry;
|
|
72
102
|
}
|
|
103
|
+
continue;
|
|
73
104
|
}
|
|
74
|
-
|
|
75
|
-
|
|
105
|
+
const id = entry.kind === 'turn' ? turnIdentity(entry) : JSON.stringify(entry);
|
|
106
|
+
const at = seenAt.get(id);
|
|
107
|
+
if (at !== undefined) {
|
|
108
|
+
// Same entry seen twice — keep the richer copy. `usage` on a turn is the only
|
|
109
|
+
// field that can arrive late, so it is the only upgrade there is to make.
|
|
110
|
+
if (entry.usage && !timeline[at].usage)
|
|
111
|
+
timeline[at] = entry;
|
|
112
|
+
continue;
|
|
76
113
|
}
|
|
114
|
+
seenAt.set(id, timeline.length);
|
|
115
|
+
timeline.push(entry);
|
|
77
116
|
}
|
|
78
117
|
timeline.sort((a, b) => {
|
|
79
118
|
var _a, _b, _c, _d;
|
|
@@ -85,5 +124,29 @@ export function assembleDebugLog(entries, readme) {
|
|
|
85
124
|
return 1;
|
|
86
125
|
return ((_c = KIND_RANK[a.kind]) !== null && _c !== void 0 ? _c : UNKNOWN_KIND_RANK) - ((_d = KIND_RANK[b.kind]) !== null && _d !== void 0 ? _d : UNKNOWN_KIND_RANK);
|
|
87
126
|
});
|
|
88
|
-
|
|
127
|
+
// The session totals live inside the newest snapshot's `meta`, which is `unknown` by
|
|
128
|
+
// design (a provider stores these opaquely) — so this narrows just the one path it
|
|
129
|
+
// reads rather than typing the whole block.
|
|
130
|
+
const context = (_c = latestMeta === null || latestMeta === void 0 ? void 0 : latestMeta.meta) === null || _c === void 0 ? void 0 : _c.context;
|
|
131
|
+
return { readme, sessionUsage: context === null || context === void 0 ? void 0 : context.sessionUsage, timeline, meta: latestMeta === null || latestMeta === void 0 ? void 0 : latestMeta.meta };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A stored diagnostics stream with its `meta-snapshot`s replaced by `fresh` — what to
|
|
135
|
+
* pass {@link assembleDebugLog} when reassembling a persisted stream for export.
|
|
136
|
+
*
|
|
137
|
+
* The stored snapshots are dropped rather than out-timestamped. `assembleDebugLog` keeps
|
|
138
|
+
* whichever claims the later time, and a stream written on another machine can carry a
|
|
139
|
+
* skewed clock; `fresh` is built from live state and so is the more current by
|
|
140
|
+
* construction. Only one snapshot ever survives reassembly, so the count is unchanged —
|
|
141
|
+
* though a snapshot taken while the driver is unwired (mid-popout) can leave
|
|
142
|
+
* `activeFoldStack`/`activeDebugSnapshot` unset where a stored one had them. The figures
|
|
143
|
+
* this exists for — the context block and `sessionUsage` — read the session store and
|
|
144
|
+
* element props, which survive an unwire.
|
|
145
|
+
*
|
|
146
|
+
* Pure, and separate from the element for that reason: the swap it performs is the fix
|
|
147
|
+
* for a silent bug (a lifetime log reporting near-zero spend against a transcript full of
|
|
148
|
+
* priced messages), so it needs to be testable without mounting anything.
|
|
149
|
+
*/
|
|
150
|
+
export function withFreshMetaSnapshot(stored, fresh) {
|
|
151
|
+
return [...stored.filter((e) => e.kind !== 'meta-snapshot'), fresh];
|
|
89
152
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { assembleDebugLog, diagnosticsMessageKey } from './diagnostics';
|
|
2
|
+
import { assembleDebugLog, diagnosticsMessageKey, withFreshMetaSnapshot, } from './diagnostics';
|
|
3
3
|
// GENC-1351 §5.8: `assembleDebugLog` reassembles the forward-only diagnostics
|
|
4
4
|
// stream back into the `{ readme, timeline, meta }` debug-log shape — timeline
|
|
5
5
|
// sorted by timestamp, meta-snapshots pulled out with the latest winning.
|
|
@@ -69,7 +69,180 @@ Suite('collapses exact-duplicate entries but keeps genuinely-distinct ones', ()
|
|
|
69
69
|
assert.is(timeline.filter((e) => e.kind === 'message').length, 1, 'identical message collapsed');
|
|
70
70
|
assert.is(timeline.filter((e) => e.type === 'assistant.connected').length, 2, 'two distinct connects (different index) kept; the repeated connA collapsed');
|
|
71
71
|
});
|
|
72
|
+
Suite('lifts the newest snapshot’s session usage to the top of the log', () => {
|
|
73
|
+
// The four buckets + USD are the headline of a cost investigation, and `meta` is a
|
|
74
|
+
// large block to go digging in — so they surface at the top level. Same object, not a
|
|
75
|
+
// second derivation, and taken from the SAME snapshot that becomes `meta`.
|
|
76
|
+
const usage = {
|
|
77
|
+
costUsd: 1.25,
|
|
78
|
+
uncachedInputTokens: 1000,
|
|
79
|
+
cacheReadTokens: 9000,
|
|
80
|
+
cacheWriteTokens: 500,
|
|
81
|
+
outputTokens: 300,
|
|
82
|
+
};
|
|
83
|
+
const entries = [
|
|
84
|
+
{
|
|
85
|
+
kind: 'meta-snapshot',
|
|
86
|
+
timestamp: '2026-01-01T00:00:01.000Z',
|
|
87
|
+
meta: { context: { sessionUsage: Object.assign(Object.assign({}, usage), { costUsd: 0.01 }) } },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
kind: 'meta-snapshot',
|
|
91
|
+
timestamp: '2026-01-01T00:00:09.000Z',
|
|
92
|
+
meta: { context: { sessionUsage: usage } },
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
assert.equal(assembleDebugLog(entries, README).sessionUsage, usage);
|
|
96
|
+
});
|
|
97
|
+
Suite('leaves session usage undefined when no snapshot carries it', () => {
|
|
98
|
+
// A stream with no meta-snapshot, or one written before the field existed. Absent
|
|
99
|
+
// rather than zeroed: "not recorded" must not read as "this session spent nothing".
|
|
100
|
+
assert.is(assembleDebugLog([], README).sessionUsage, undefined);
|
|
101
|
+
const noContext = [
|
|
102
|
+
{ kind: 'meta-snapshot', timestamp: '2026-01-01T00:00:01.000Z', meta: { host: 'localhost' } },
|
|
103
|
+
];
|
|
104
|
+
assert.is(assembleDebugLog(noContext, README).sessionUsage, undefined);
|
|
105
|
+
});
|
|
106
|
+
Suite('collapses the turn pair even when its rendered systemPrompt differs', () => {
|
|
107
|
+
// The pair is produced by two separate flushes, and `buildTimelineEntries` renders
|
|
108
|
+
// `systemPrompt` relative to the preceding snapshots — so if the snapshot holding the
|
|
109
|
+
// full prompt is evicted from the ring buffer between the two, the surviving turn
|
|
110
|
+
// renders its prompt in full where the earlier copy had the '<repeated>' marker. Any
|
|
111
|
+
// identity derived from the serialized entry breaks here and the log shows one model
|
|
112
|
+
// call as two turns, one unpriced. Identity is turnIndex + timestamp + agentName.
|
|
113
|
+
const unpriced = {
|
|
114
|
+
kind: 'turn',
|
|
115
|
+
turnIndex: '7',
|
|
116
|
+
timestamp: '2026-01-01T00:00:01.000Z',
|
|
117
|
+
agentName: 'Trade Operations',
|
|
118
|
+
systemPrompt: '<repeated — identical to turn 0>',
|
|
119
|
+
toolNames: [],
|
|
120
|
+
};
|
|
121
|
+
const priced = Object.assign(Object.assign({}, unpriced), { systemPrompt: 'the full prompt, now that turn 0 has been evicted', usage: {
|
|
122
|
+
costUsd: 0.5,
|
|
123
|
+
uncachedInputTokens: 100,
|
|
124
|
+
cacheReadTokens: 0,
|
|
125
|
+
cacheWriteTokens: 0,
|
|
126
|
+
outputTokens: 20,
|
|
127
|
+
} });
|
|
128
|
+
const turns = assembleDebugLog([unpriced, priced], README).timeline.filter((e) => e.kind === 'turn');
|
|
129
|
+
assert.is(turns.length, 1, 'still one model call');
|
|
130
|
+
assert.equal(turns[0].usage, priced.usage, 'and it is the priced copy');
|
|
131
|
+
});
|
|
132
|
+
Suite('keeps same-index turns from different loads and different sub-agents apart', () => {
|
|
133
|
+
// `turnIndex` restarts at '0' on every page load, so a lifetime log holds one turn '0'
|
|
134
|
+
// per load; two sub-agents invoked in one parent turn share the index prefix and are
|
|
135
|
+
// separated by `agentName` (see `forwardSubAgentSnapshots`).
|
|
136
|
+
const load1 = {
|
|
137
|
+
kind: 'turn',
|
|
138
|
+
turnIndex: '0',
|
|
139
|
+
timestamp: '2026-01-01T00:00:01.000Z',
|
|
140
|
+
agentName: 'Booker',
|
|
141
|
+
toolNames: [],
|
|
142
|
+
};
|
|
143
|
+
const load2 = Object.assign(Object.assign({}, load1), { timestamp: '2026-01-02T09:00:00.000Z' });
|
|
144
|
+
const sibling = Object.assign(Object.assign({}, load1), { turnIndex: '3-1', agentName: 'Planner' });
|
|
145
|
+
const cousin = Object.assign(Object.assign({}, sibling), { agentName: 'Grounding' });
|
|
146
|
+
const turns = assembleDebugLog([load1, load2, sibling, cousin], README).timeline.filter((e) => e.kind === 'turn');
|
|
147
|
+
assert.is(turns.length, 4, 'four distinct calls, none collapsed');
|
|
148
|
+
});
|
|
149
|
+
Suite('collapses the unpriced and priced copies of one turn onto the priced one', () => {
|
|
150
|
+
// A turn entry is persisted when it is created (before its model call) and again once
|
|
151
|
+
// `usage` is back-filled, so a stitched stream carries both. They are ONE model call
|
|
152
|
+
// and must read as one — with the cost, or the re-emit gained nothing.
|
|
153
|
+
const unpriced = {
|
|
154
|
+
kind: 'turn',
|
|
155
|
+
turnIndex: '0',
|
|
156
|
+
timestamp: '2026-01-01T00:00:01.000Z',
|
|
157
|
+
toolNames: [],
|
|
158
|
+
};
|
|
159
|
+
const priced = Object.assign(Object.assign({}, unpriced), { usage: {
|
|
160
|
+
costUsd: 0.5,
|
|
161
|
+
uncachedInputTokens: 100,
|
|
162
|
+
cacheReadTokens: 0,
|
|
163
|
+
cacheWriteTokens: 0,
|
|
164
|
+
outputTokens: 20,
|
|
165
|
+
} });
|
|
166
|
+
for (const order of [
|
|
167
|
+
[unpriced, priced],
|
|
168
|
+
[priced, unpriced],
|
|
169
|
+
]) {
|
|
170
|
+
const turns = assembleDebugLog(order, README).timeline.filter((e) => e.kind === 'turn');
|
|
171
|
+
assert.is(turns.length, 1, 'one turn, not two');
|
|
172
|
+
assert.equal(turns[0].usage, priced.usage, 'the priced copy wins regardless of stream order');
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
Suite('keeps two genuinely-different turns that differ only in usage', () => {
|
|
176
|
+
// The collapse keys on everything EXCEPT usage, so distinct calls stay distinct — they
|
|
177
|
+
// differ in timestamp (and turnIndex) whatever else they share.
|
|
178
|
+
const a = {
|
|
179
|
+
kind: 'turn',
|
|
180
|
+
turnIndex: '0',
|
|
181
|
+
timestamp: '2026-01-01T00:00:01.000Z',
|
|
182
|
+
toolNames: [],
|
|
183
|
+
};
|
|
184
|
+
const b = Object.assign(Object.assign({}, a), { turnIndex: '1', timestamp: '2026-01-01T00:00:02.000Z' });
|
|
185
|
+
const turns = assembleDebugLog([a, b], README).timeline.filter((e) => e.kind === 'turn');
|
|
186
|
+
assert.is(turns.length, 2);
|
|
187
|
+
});
|
|
72
188
|
Suite.run();
|
|
189
|
+
// The download path's swap: a stored stream's `meta-snapshot`s replaced by an export-time
|
|
190
|
+
// one. This is the fix for a SILENT bug — the persisted stream only re-appends that block
|
|
191
|
+
// when the config signature changes, so a stitched lifetime log reported the totals frozen
|
|
192
|
+
// at the session's first flush (near-zero spend against a transcript full of priced
|
|
193
|
+
// messages) and nothing about it looked wrong.
|
|
194
|
+
const FreshSuite = createLogicSuite('withFreshMetaSnapshot');
|
|
195
|
+
const storedStream = [
|
|
196
|
+
{
|
|
197
|
+
kind: 'meta-snapshot',
|
|
198
|
+
timestamp: '2026-01-01T00:00:00.000Z',
|
|
199
|
+
meta: { context: { sessionUsage: { costUsd: 0.01 } } },
|
|
200
|
+
dedupSignature: 'cfg-1',
|
|
201
|
+
},
|
|
202
|
+
{ kind: 'message', timestamp: '2026-01-01T00:00:01.000Z', role: 'user', content: 'hi' },
|
|
203
|
+
{ kind: 'turn', turnIndex: '0', timestamp: '2026-01-01T00:00:02.000Z', toolNames: [] },
|
|
204
|
+
{ kind: 'event', index: 0, timestamp: '2026-01-01T00:00:03.000Z', type: 'turn.end' },
|
|
205
|
+
];
|
|
206
|
+
const freshSnapshot = {
|
|
207
|
+
kind: 'meta-snapshot',
|
|
208
|
+
timestamp: '2026-01-01T02:00:00.000Z',
|
|
209
|
+
meta: {
|
|
210
|
+
context: {
|
|
211
|
+
sessionUsage: {
|
|
212
|
+
costUsd: 1.25,
|
|
213
|
+
uncachedInputTokens: 1000,
|
|
214
|
+
cacheReadTokens: 9000,
|
|
215
|
+
cacheWriteTokens: 500,
|
|
216
|
+
outputTokens: 300,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
dedupSignature: 'cfg-1',
|
|
221
|
+
};
|
|
222
|
+
FreshSuite('reports the export-time totals, not the frozen stored ones', () => {
|
|
223
|
+
var _a, _b;
|
|
224
|
+
const log = assembleDebugLog(withFreshMetaSnapshot(storedStream, freshSnapshot), README);
|
|
225
|
+
assert.is((_a = log.sessionUsage) === null || _a === void 0 ? void 0 : _a.costUsd, 1.25, 'the fresh snapshot supplies the totals');
|
|
226
|
+
assert.is((_b = log.sessionUsage) === null || _b === void 0 ? void 0 : _b.cacheReadTokens, 9000);
|
|
227
|
+
assert.equal(log.meta, freshSnapshot.meta, 'and the whole meta block is the fresh one');
|
|
228
|
+
});
|
|
229
|
+
FreshSuite('keeps every non-snapshot entry', () => {
|
|
230
|
+
const timeline = assembleDebugLog(withFreshMetaSnapshot(storedStream, freshSnapshot), README).timeline;
|
|
231
|
+
assert.equal(timeline.map((e) => e.kind), ['message', 'turn', 'event'], 'the conversation is untouched — only the snapshots are swapped');
|
|
232
|
+
});
|
|
233
|
+
FreshSuite('wins even when the stored snapshot claims a later time', () => {
|
|
234
|
+
var _a;
|
|
235
|
+
// A stream written on another machine can carry a skewed clock, and reassembly keeps
|
|
236
|
+
// whichever snapshot claims the later timestamp — so the stale one must be REMOVED
|
|
237
|
+
// rather than out-timestamped.
|
|
238
|
+
const skewed = [
|
|
239
|
+
Object.assign(Object.assign({}, storedStream[0]), { timestamp: '2099-01-01T00:00:00.000Z' }),
|
|
240
|
+
...storedStream.slice(1),
|
|
241
|
+
];
|
|
242
|
+
const log = assembleDebugLog(withFreshMetaSnapshot(skewed, freshSnapshot), README);
|
|
243
|
+
assert.is((_a = log.sessionUsage) === null || _a === void 0 ? void 0 : _a.costUsd, 1.25);
|
|
244
|
+
});
|
|
245
|
+
FreshSuite.run();
|
|
73
246
|
// GENC-1461: the reasoning/narration/answer split (GENC-1411) emits several messages from ONE model
|
|
74
247
|
// response, so they share `timestamp` + `role` + `subAgentOf`. The message identity key must still
|
|
75
248
|
// tell them apart — otherwise the forward-capture dedup treats them as one and drops all but the
|
|
@@ -582,9 +582,10 @@ export class SessionPersister {
|
|
|
582
582
|
}
|
|
583
583
|
/**
|
|
584
584
|
* The diagnostic entries not yet appended this load — the forward-capture delta
|
|
585
|
-
* (§5.8). Skips messages present at restore or already appended; emits each
|
|
586
|
-
* (by
|
|
587
|
-
*
|
|
585
|
+
* (§5.8). Skips messages present at restore or already appended; emits each meta-event
|
|
586
|
+
* (by index) once and each turn (by turn-index) once unpriced plus once more when its
|
|
587
|
+
* per-call `usage` lands; appends a `meta-snapshot` only when the block changed.
|
|
588
|
+
* Advances the shared per-session cursors as it goes.
|
|
588
589
|
*
|
|
589
590
|
* TODO(GENC-1461, longer-term): this whole delta step exists only because the element *pulls* the
|
|
590
591
|
* ENTIRE debug log on every throttled flush (`getDiagnosticEntries()` returns the full timeline),
|
|
@@ -613,10 +614,17 @@ export class SessionPersister {
|
|
|
613
614
|
delta.push(entry);
|
|
614
615
|
}
|
|
615
616
|
else if (entry.kind === 'turn') {
|
|
617
|
+
// A turn entry is created BEFORE its model call and priced when the response
|
|
618
|
+
// lands, so a flush that falls in between persists it unpriced. Emit it a second
|
|
619
|
+
// time once `usage` is there — keyed separately so it happens at most once per
|
|
620
|
+
// turn — and let `assembleDebugLog` collapse the pair onto the priced copy.
|
|
621
|
+
// Without this the stitched lifetime log silently loses per-call cost for any
|
|
622
|
+
// turn that outlived a flush, which is most of the slow ones.
|
|
616
623
|
const key = String((_a = entry.turnIndex) !== null && _a !== void 0 ? _a : '');
|
|
617
|
-
|
|
624
|
+
const emitKey = entry.usage ? `${key}::usage` : key;
|
|
625
|
+
if (cursors.emittedTurnKeys.has(emitKey))
|
|
618
626
|
continue;
|
|
619
|
-
cursors.emittedTurnKeys.add(
|
|
627
|
+
cursors.emittedTurnKeys.add(emitKey);
|
|
620
628
|
delta.push(entry);
|
|
621
629
|
}
|
|
622
630
|
else if (entry.kind === 'event') {
|
|
@@ -336,6 +336,37 @@ Suite('flushDiagnostics() keeps co-timestamped split messages (reasoning + answe
|
|
|
336
336
|
assert.is(f.appended.length, 1, 'one flush');
|
|
337
337
|
assert.is(f.appended[0].length, 2, 'both the reasoning and the answer message are captured, not collapsed to one');
|
|
338
338
|
}));
|
|
339
|
+
Suite('flushDiagnostics() re-emits a turn once its per-call usage lands', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
340
|
+
// A turn entry is created BEFORE its model call and priced when the response returns,
|
|
341
|
+
// so a flush landing in between persists it unpriced. Emitting it exactly once more —
|
|
342
|
+
// when `usage` is there — is what keeps per-call cost in a stitched lifetime log;
|
|
343
|
+
// `assembleDebugLog` collapses the pair back onto the priced copy.
|
|
344
|
+
const inFlight = {
|
|
345
|
+
kind: 'turn',
|
|
346
|
+
turnIndex: '0',
|
|
347
|
+
timestamp: '2026-01-01T00:00:01.000Z',
|
|
348
|
+
toolNames: [],
|
|
349
|
+
};
|
|
350
|
+
const { p, f } = makePersister({ diagnosticEntries: [inFlight] });
|
|
351
|
+
yield p.flushDiagnostics();
|
|
352
|
+
assert.is(f.appended.length, 1, 'the unpriced turn is captured');
|
|
353
|
+
assert.is(f.appended[0][0].usage, undefined);
|
|
354
|
+
const priced = Object.assign(Object.assign({}, inFlight), { usage: {
|
|
355
|
+
costUsd: 0.25,
|
|
356
|
+
uncachedInputTokens: 100,
|
|
357
|
+
cacheReadTokens: 900,
|
|
358
|
+
cacheWriteTokens: 0,
|
|
359
|
+
outputTokens: 40,
|
|
360
|
+
} });
|
|
361
|
+
f.diagnosticEntries = [priced];
|
|
362
|
+
yield p.flushDiagnostics();
|
|
363
|
+
assert.is(f.appended.length, 2, 'the priced copy is appended');
|
|
364
|
+
assert.equal(f.appended[1][0].usage, priced.usage);
|
|
365
|
+
// ...and only once. A throttled flush runs on every transcript change, so a turn that
|
|
366
|
+
// re-emitted per flush would multiply the biggest entries in the stream.
|
|
367
|
+
yield p.flushDiagnostics();
|
|
368
|
+
assert.is(f.appended.length, 2, 'no further re-append of the priced turn');
|
|
369
|
+
}));
|
|
339
370
|
Suite('flushDiagnostics() still dedups a genuine re-append of the same message across flushes', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
340
371
|
// The dedup must still stop the SAME message being re-appended on every throttled flush.
|
|
341
372
|
const m = diagMsg({ category: 'reasoning', content: 'thinking…' });
|
|
@@ -46,6 +46,49 @@ export function addUsage(a, b) {
|
|
|
46
46
|
outputTokens: a.outputTokens + b.outputTokens,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The four buckets plus USD for ONE message — the per-message fields projected into
|
|
51
|
+
* the disjoint, addable shape, or `undefined` when the message reports no usage at
|
|
52
|
+
* all (a user turn, a display-only reasoning/narration split, a provider that
|
|
53
|
+
* reports none).
|
|
54
|
+
*
|
|
55
|
+
* Shallow by design: it does NOT recurse into `toolCalls[].subAgentTrace` and does
|
|
56
|
+
* NOT count `compaction.rolledUpUsage`, so it answers "what did this one request
|
|
57
|
+
* cost" rather than "what did this message and everything under it cost". Use
|
|
58
|
+
* {@link sumUsage} for the latter — passing a whole transcript through this one
|
|
59
|
+
* message at a time would silently drop both.
|
|
60
|
+
*
|
|
61
|
+
* `costUsd` folds in `externalCostUsd` (non-LLM spend a widget reported), matching
|
|
62
|
+
* `sumUsage`. It is `0` when the provider priced nothing, which is NOT a claim that
|
|
63
|
+
* the call was free — see {@link AggregateUsage}; `UsageRow.costUsd` is the
|
|
64
|
+
* shape that keeps unpriced distinguishable, and is the right tool for a ledger.
|
|
65
|
+
*
|
|
66
|
+
* @beta
|
|
67
|
+
*/
|
|
68
|
+
export function messageUsage(m) {
|
|
69
|
+
var _a, _b, _c, _d, _e, _f;
|
|
70
|
+
if (m.cost == null &&
|
|
71
|
+
m.externalCostUsd == null &&
|
|
72
|
+
m.inputTokens == null &&
|
|
73
|
+
m.outputTokens == null &&
|
|
74
|
+
m.cacheReadTokens == null &&
|
|
75
|
+
m.cacheWriteTokens == null) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const cacheReadTokens = (_a = m.cacheReadTokens) !== null && _a !== void 0 ? _a : 0;
|
|
79
|
+
const cacheWriteTokens = (_b = m.cacheWriteTokens) !== null && _b !== void 0 ? _b : 0;
|
|
80
|
+
return {
|
|
81
|
+
costUsd: ((_c = m.cost) !== null && _c !== void 0 ? _c : 0) + ((_d = m.externalCostUsd) !== null && _d !== void 0 ? _d : 0),
|
|
82
|
+
// The uncached REMAINDER, clamped — the same arithmetic `accumulate` below applies,
|
|
83
|
+
// for the same reasons. The two are deliberately separate implementations (that walk
|
|
84
|
+
// mutates one accumulator rather than allocating per message) and must agree;
|
|
85
|
+
// `sum-usage.test.ts` pins them together.
|
|
86
|
+
uncachedInputTokens: Math.max(0, ((_e = m.inputTokens) !== null && _e !== void 0 ? _e : 0) - cacheReadTokens - cacheWriteTokens),
|
|
87
|
+
cacheReadTokens,
|
|
88
|
+
cacheWriteTokens,
|
|
89
|
+
outputTokens: (_f = m.outputTokens) !== null && _f !== void 0 ? _f : 0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
49
92
|
/**
|
|
50
93
|
* Sum cost and per-bucket token usage across a message list.
|
|
51
94
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { addUsage, emptyUsage, sumUsage, totalTokens } from './sum-usage';
|
|
2
|
+
import { addUsage, emptyUsage, messageUsage, sumUsage, totalTokens } from './sum-usage';
|
|
3
3
|
const suite = createLogicSuite('sumUsage');
|
|
4
4
|
const msg = (over = {}) => (Object.assign({ role: 'assistant', content: '' }, over));
|
|
5
5
|
suite('returns zeroes when no message carries usage', () => {
|
|
@@ -117,4 +117,48 @@ suite('addUsage with an empty operand is the identity', () => {
|
|
|
117
117
|
assert.equal(addUsage(a, emptyUsage()), a);
|
|
118
118
|
assert.equal(addUsage(emptyUsage(), a), a);
|
|
119
119
|
});
|
|
120
|
+
suite('messageUsage is undefined for a message reporting nothing', () => {
|
|
121
|
+
// A user turn, or a display-only reasoning/narration split. Distinct from a zeroed
|
|
122
|
+
// usage: "this call reported nothing" is not "this call cost nothing".
|
|
123
|
+
assert.is(messageUsage(msg({ role: 'user', content: 'hi' })), undefined);
|
|
124
|
+
assert.is(messageUsage(msg({ content: 'thinking…', category: 'reasoning' })), undefined);
|
|
125
|
+
});
|
|
126
|
+
suite('messageUsage agrees with sumUsage over the same single message', () => {
|
|
127
|
+
// The two derivations are deliberately separate — `accumulate` mutates one
|
|
128
|
+
// accumulator rather than allocating per message — so pin them together. A drift
|
|
129
|
+
// here would put one figure on a turn entry and a different one in the session
|
|
130
|
+
// total, both looking plausible.
|
|
131
|
+
const cases = [
|
|
132
|
+
{ inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50, cost: 0.4 },
|
|
133
|
+
{ inputTokens: 400, outputTokens: 60 },
|
|
134
|
+
{ inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90 },
|
|
135
|
+
{ cost: 0.01, externalCostUsd: 0.02 },
|
|
136
|
+
];
|
|
137
|
+
for (const over of cases) {
|
|
138
|
+
const m = msg(over);
|
|
139
|
+
assert.equal(messageUsage(m), sumUsage([m]), `messageUsage must match sumUsage for ${JSON.stringify(over)}`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
suite('messageUsage is shallow — it prices the one request, not the tree', () => {
|
|
143
|
+
// Deliberately blind to sub-agent traces and banked compaction usage: it answers
|
|
144
|
+
// "what did this call cost", which is what a turn snapshot needs. `sumUsage` is the
|
|
145
|
+
// function that walks everything underneath.
|
|
146
|
+
const m = msg({
|
|
147
|
+
inputTokens: 100,
|
|
148
|
+
outputTokens: 10,
|
|
149
|
+
cost: 0.05,
|
|
150
|
+
toolCalls: [
|
|
151
|
+
{
|
|
152
|
+
id: 't1',
|
|
153
|
+
name: 'delegate',
|
|
154
|
+
args: {},
|
|
155
|
+
subAgentTrace: [msg({ inputTokens: 900, outputTokens: 90, cost: 0.5 })],
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
const usage = messageUsage(m);
|
|
160
|
+
assert.is(usage.costUsd, 0.05, 'the child conversation is not folded in');
|
|
161
|
+
assert.is(usage.uncachedInputTokens, 100);
|
|
162
|
+
assert.ok(sumUsage([m]).costUsd > usage.costUsd, 'sumUsage does fold it in');
|
|
163
|
+
});
|
|
120
164
|
suite.run();
|
|
@@ -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.invocation-scope.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.thinking-policy.test.ts","../src/components/chat-driver/chat-driver.trace-capture.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.test.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.test.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/budget-meter.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/format-usd.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/usage-rows.test.ts","../src/utils/usage-rows.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.invocation-scope.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.thinking-policy.test.ts","../src/components/chat-driver/chat-driver.trace-capture.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/chat-driver.turn-usage.test.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.test.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.test.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/budget-meter.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/format-usd.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/usage-rows.test.ts","../src/utils/usage-rows.ts","../src/utils/with-timeout.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": "15.
|
|
4
|
+
"version": "15.13.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -73,26 +73,26 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@genesislcap/foundation-testing": "15.
|
|
77
|
-
"@genesislcap/genx": "15.
|
|
78
|
-
"@genesislcap/rollup-builder": "15.
|
|
79
|
-
"@genesislcap/ts-builder": "15.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.
|
|
81
|
-
"@genesislcap/vite-builder": "15.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.
|
|
76
|
+
"@genesislcap/foundation-testing": "15.13.0",
|
|
77
|
+
"@genesislcap/genx": "15.13.0",
|
|
78
|
+
"@genesislcap/rollup-builder": "15.13.0",
|
|
79
|
+
"@genesislcap/ts-builder": "15.13.0",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "15.13.0",
|
|
81
|
+
"@genesislcap/vite-builder": "15.13.0",
|
|
82
|
+
"@genesislcap/webpack-builder": "15.13.0",
|
|
83
83
|
"@types/dompurify": "^3.0.5",
|
|
84
84
|
"@types/marked": "^5.0.2",
|
|
85
85
|
"esbuild": "0.25.12"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@genesislcap/foundation-ai": "15.
|
|
89
|
-
"@genesislcap/foundation-logger": "15.
|
|
90
|
-
"@genesislcap/foundation-notifications": "15.
|
|
91
|
-
"@genesislcap/foundation-redux": "15.
|
|
92
|
-
"@genesislcap/foundation-ui": "15.
|
|
93
|
-
"@genesislcap/foundation-utils": "15.
|
|
94
|
-
"@genesislcap/rapid-design-system": "15.
|
|
95
|
-
"@genesislcap/web-core": "15.
|
|
88
|
+
"@genesislcap/foundation-ai": "15.13.0",
|
|
89
|
+
"@genesislcap/foundation-logger": "15.13.0",
|
|
90
|
+
"@genesislcap/foundation-notifications": "15.13.0",
|
|
91
|
+
"@genesislcap/foundation-redux": "15.13.0",
|
|
92
|
+
"@genesislcap/foundation-ui": "15.13.0",
|
|
93
|
+
"@genesislcap/foundation-utils": "15.13.0",
|
|
94
|
+
"@genesislcap/rapid-design-system": "15.13.0",
|
|
95
|
+
"@genesislcap/web-core": "15.13.0",
|
|
96
96
|
"dompurify": "^3.3.1",
|
|
97
97
|
"marked": "^17.0.3"
|
|
98
98
|
},
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"access": "public"
|
|
106
106
|
},
|
|
107
107
|
"customElements": "dist/custom-elements.json",
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "719ab9df50445af3dabf8f6fb9966d004e5ae524"
|
|
109
109
|
}
|