@genesislcap/ai-assistant 15.7.0 → 15.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,6 +53,7 @@ import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } fr
53
53
  import { ChatSuggestions } from '../suggestions/chat-suggestions';
54
54
  import { AnimatedPanelToggle } from '../utils/animated-panel-toggle';
55
55
  import { resolveExclusiveLoadingStyle } from '../utils/animation-exclusivity';
56
+ import { deleteBankedBaseline, getBankedBaseline, setBankedBaseline, } from '../utils/banked-usage-baselines';
56
57
  import { collectSessionModels } from '../utils/collect-session-models';
57
58
  import { clearCostSessionHistory, isCostSessionRecord, loadCostSessionHistory, resolveBankedUsage, saveCostSessionHistory, sortRecordsByRecency, upsertRecord, } from '../utils/cost-session-history';
58
59
  import { deriveCostSessionTitleFromMessages, resolveCostSessionTitle, } from '../utils/derive-cost-session-title';
@@ -269,8 +270,9 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
269
270
  this.showingSplash = false;
270
271
  /** Title tracked for the in-flight cost session (used when `header-title` changes). */
271
272
  this._activeCostSessionTitle = '';
272
- /** Memoised pre-transcript spend for `_bankedFor` see `bankedUsageFor`. */
273
- this._bankedUsage = emptyUsage();
273
+ // NOTE: the pre-transcript ("banked") spend is deliberately NOT held here. It is keyed by
274
+ // session in `banked-usage-baselines` so the many element instances a single session sees —
275
+ // layout re-renders, popout, docking — share one baseline. See `bankedUsageFor`.
274
276
  /**
275
277
  * Whether the row list reflects storage yet. Gates cost writes so a project's banked
276
278
  * spend is never derived from a ledger that simply hasn't arrived.
@@ -851,11 +853,6 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
851
853
  *
852
854
  * @beta
853
855
  */
854
- /** Whether this vendor's wall came from the sweep alone — see the slice's `sweptVendors`. */
855
- isVendorSwept(vendor) {
856
- var _a, _b;
857
- return ((_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.sweptVendors) !== null && _b !== void 0 ? _b : []).includes(vendor);
858
- }
859
856
  setVendorBlocked(vendor, blocked, reason) {
860
857
  var _a;
861
858
  if (vendor === 'none') {
@@ -865,6 +862,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
865
862
  }
866
863
  (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setVendorBlocked({ vendor, blocked, reason });
867
864
  }
865
+ /** Whether this vendor's wall came from the sweep alone — see the slice's `sweptVendors`. */
866
+ isVendorSwept(vendor) {
867
+ var _a, _b;
868
+ return ((_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.sweptVendors) !== null && _b !== void 0 ? _b : []).includes(vendor);
869
+ }
868
870
  /**
869
871
  * The walled vendors the user can still be routed to — i.e.
870
872
  * {@link FoundationAiAssistant.blockedVendors} narrowed to the reachable set,
@@ -1495,9 +1497,9 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1495
1497
  // The row is a projection of the session totals, not a transfer out of them.
1496
1498
  }
1497
1499
  /**
1498
- * Spend for `projectKey` that predates the current transcript, resolved once per project
1499
- * and memoised (`_bankedFor`) so it cannot drift mid-session as the row it came from is
1500
- * rewritten by our own upserts.
1500
+ * Spend for `projectKey` that predates the current transcript, resolved once per SESSION and
1501
+ * shared by every element instance wired to it (see `banked-usage-baselines`), so it cannot
1502
+ * drift as the row it came from is rewritten by our own upserts.
1501
1503
  *
1502
1504
  * The two cases turn on whether the transcript is authoritative for this project:
1503
1505
  *
@@ -1507,28 +1509,76 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1507
1509
  * - **Transcript starts empty**: nothing on screen accounts for the previous total, so the
1508
1510
  * whole of it becomes banked. Without this the next turn would rewrite the row downwards
1509
1511
  * to just that turn, losing the project's earlier spend.
1512
+ *
1513
+ * The baseline is deliberately NOT element state. This element is remounted repeatedly inside
1514
+ * a single session (the host re-rendering its layout, popping out, docking) and a fresh
1515
+ * instance re-deriving would take the second branch against rows that already hold the live
1516
+ * transcript — banking the session as its own baseline. Keying on the session instead means a
1517
+ * remount inherits the baseline that transcript was paired with.
1510
1518
  */
1511
1519
  bankedUsageFor(projectKey) {
1512
- if (this._bankedFor === projectKey)
1513
- return this._bankedUsage;
1520
+ const resolved = getBankedBaseline(projectKey);
1521
+ if (resolved)
1522
+ return resolved;
1514
1523
  const existing = this.costSessionHistory.find((r) => r.projectKey === projectKey);
1515
1524
  // `persistenceActive` is what decides whether the transcript speaks for the project's
1516
1525
  // history: it is on only when a provider is wired AND the user is remembering the chat.
1517
- this._bankedUsage = resolveBankedUsage(existing, this.persistenceActive);
1518
- this._bankedFor = projectKey;
1519
- return this._bankedUsage;
1526
+ const banked = resolveBankedUsage(existing, this.persistenceActive);
1527
+ setBankedBaseline(projectKey, banked);
1528
+ return banked;
1520
1529
  }
1521
1530
  /**
1522
- * Discard the banked figure for when the spend it stands for has been deliberately
1523
- * thrown away (the user clearing history, or deleting this project's row).
1531
+ * Re-baseline this session against an emptied transcript: what came before, plus what the
1532
+ * transcript being discarded proved. `discarded` must be read BEFORE the live figures are
1533
+ * reset, since that is the only record of the latter.
1534
+ *
1535
+ * Not the same as dropping the baseline. Dropping it re-derives through `persistenceActive`,
1536
+ * and with persistence ON that carries the row's *old* banked figure forward — discarding
1537
+ * every turn between the last baseline and the clear. The transcript is gone either way, so
1538
+ * the branch that reads it as authoritative no longer applies.
1524
1539
  *
1525
- * NOT for reloads. A reload re-reads rows that already include our own upserts, so
1526
- * re-deriving from them would fold the live session into its own baseline; only a genuine
1527
- * project change should re-derive, which `bankedUsageFor`'s per-project memo handles.
1540
+ * Derived from the memoised baseline and the live figure rather than from the project's row,
1541
+ * because a row is only as current as the last `finalizeCostSession` which skips its write
1542
+ * while the ledger is unloaded, and while the session has no usage yet.
1528
1543
  */
1529
- resetBankedUsage() {
1530
- this._bankedFor = undefined;
1531
- this._bankedUsage = emptyUsage();
1544
+ rebaselineOnClearedTranscript(key, discarded) {
1545
+ // Already resolved for this session? Then the new baseline is fully determined without
1546
+ // consulting the ledger at all — `bankedUsageFor` would short-circuit on this same value — so
1547
+ // answer before the guard below, which would otherwise discard a figure that is both correct
1548
+ // and independent of the rows. Losing it costs real spend: with a row at `usage: 120,
1549
+ // banked: 90` and 30 on screen, a clear during a reload would drop the memoised 90, and the
1550
+ // next turn would re-derive `row.banked` and rewrite the row BACKWARDS from 120 to 95.
1551
+ const resolved = getBankedBaseline(key);
1552
+ if (resolved) {
1553
+ setBankedBaseline(key, addUsage(resolved, discarded));
1554
+ return;
1555
+ }
1556
+ // Nothing memoised, so a baseline can only come from the rows — and a stamped one is STICKY
1557
+ // (`bankedUsageFor` short-circuits on any stored entry, and a zero usage is a *resolved* value
1558
+ // there, not "unresolved"). So unless the rows are genuinely known, do not guess:
1559
+ //
1560
+ // - `enabled` false: `reloadCostSessionHistory` sets `_costHistoryLoaded` TRUE over a
1561
+ // deliberately empty list, meaning "nothing will be loaded" rather than "rows are known".
1562
+ // The flag alone therefore is not enough. Reachable because `enabled` is
1563
+ // `cost?.enabled === true` — false for an unbound `chatConfig`, and for any host resolving
1564
+ // it from an async setting, exactly as `chatConfigChanged` already expects.
1565
+ // - Provider read in flight: the list is emptied and the flag cleared synchronously, on
1566
+ // connect and again on every `chatConfig` rebind.
1567
+ // - Unreadable: the rows we hold are a fallback, not the ledger.
1568
+ //
1569
+ // In all three, pinning would bury this project's real total the moment it arrived — a later
1570
+ // load does NOT invalidate baselines (deliberately, see `reloadCostSessionHistory`), so the
1571
+ // next write would replace the row off the pinned figure and lose everything before it.
1572
+ //
1573
+ // Dropping instead leaves the next write to re-derive from rows that actually arrived. It
1574
+ // under-banks `discarded` in that window — a bounded loss, and what every clear did before
1575
+ // this method existed. Nothing is written while disabled, so there is no banked value to
1576
+ // preserve in that case either.
1577
+ if (!this.resolvedCostHistory.enabled || !this._costHistoryLoaded || this._ledgerUnreadable) {
1578
+ deleteBankedBaseline(key);
1579
+ return;
1580
+ }
1581
+ setBankedBaseline(key, addUsage(this.bankedUsageFor(key), discarded));
1532
1582
  }
1533
1583
  showConfirmationDialog(confirmationMessage, modalParent, onConfirm, onCancel) {
1534
1584
  showNotificationDialog({
@@ -1542,24 +1592,27 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1542
1592
  }
1543
1593
  /** Remove one entry from persisted build history. */
1544
1594
  removeCostSessionFromHistory(id) {
1545
- // Deleting the active project's row discards its banked spend by intent, so drop the
1546
- // memo too — otherwise the next turn would rewrite the row we just removed, restoring
1547
- // the total the user asked to be rid of.
1595
+ // Deleting a row discards its banked spend by intent, so drop the baseline too —
1596
+ // otherwise the next turn would rewrite the row we just removed, restoring the total the
1597
+ // user asked to be rid of.
1548
1598
  //
1549
- // Resolve the row first: `id` is a row id while `_bankedFor` is a projectKey. They match
1550
- // only for rows this element wrote (`finalizeCostSession` sets `id: projectKey`); a row from
1551
- // a host provider can carry any id, and comparing across the two namespaces would silently
1552
- // skip the reset.
1599
+ // Resolve the row first: `id` is a row id while baselines are keyed by projectKey. They
1600
+ // match only for rows this element wrote (`finalizeCostSession` sets `id: projectKey`); a
1601
+ // row from a host provider can carry any id, and deleting by the wrong namespace would
1602
+ // silently leave the baseline in place.
1553
1603
  const removed = this.costSessionHistory.find((record) => record.id === id);
1554
- if (removed && this._bankedFor === removed.projectKey)
1555
- this.resetBankedUsage();
1604
+ if (removed)
1605
+ deleteBankedBaseline(removed.projectKey);
1556
1606
  this.persistCostHistory(this.costSessionHistory.filter((record) => record.id !== id));
1557
1607
  }
1558
1608
  /** Clear all persisted build history for this assistant scope. */
1559
1609
  clearCostSessionHistory() {
1560
- // Every row is gone, so nothing is banked any more — without this the active project's
1561
- // next turn would resurrect its pre-clear total.
1562
- this.resetBankedUsage();
1610
+ // Every row is gone, so nothing is banked any more — without this the next turn would
1611
+ // resurrect the pre-clear total. Dropped per row rather than for the active session alone,
1612
+ // so a project whose row was cleared while another session was displaying it does not keep
1613
+ // a baseline pointing at spend that no longer exists anywhere.
1614
+ for (const record of this.costSessionHistory)
1615
+ deleteBankedBaseline(record.projectKey);
1563
1616
  // Provider-backed: write an empty ledger through. The `localStorage` path instead
1564
1617
  // removes its key outright rather than leaving an empty array behind.
1565
1618
  if (this.costHistoryProvider()) {
@@ -2403,6 +2456,10 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2403
2456
  // keeping the high-water `lastEventIndex` would then skip every event of the
2404
2457
  // resumed session (GENC-1351 §5.12 / #9). Clear (same-session) still uses reset.
2405
2458
  deleteDiagnosticsCursorsFor(oldKey);
2459
+ // The cost baseline is paired with the transcript in the store just disposed, so it goes
2460
+ // with it (GENC-1475). A later switch back re-restores from the provider — or starts empty
2461
+ // where it does not — and re-derives from the ledger rows accordingly.
2462
+ deleteBankedBaseline(oldKey);
2406
2463
  clearMetaSession(oldKey);
2407
2464
  // Activate the incoming session. Swapping the observable `_sessionRef` re-points
2408
2465
  // every store-backed getter binding at the new store; `activateSession` rebuilds
@@ -2984,11 +3041,17 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2984
3041
  catch (e) {
2985
3042
  logger.error('Session clear: agent serialize() failed:', e);
2986
3043
  }
3044
+ // Read BEFORE `resetSession()` zeroes it: this is what the transcript about to be discarded
3045
+ // proved, and nothing else records it. Banking it is what stops the next turn rewriting the
3046
+ // project's row down to itself and losing the spend up to the clear (GENC-1475).
3047
+ const discarded = this.sessionUsage;
2987
3048
  if (this.driver)
2988
3049
  this.teardownDriver(key);
2989
3050
  (_g = this._sessionRef) === null || _g === void 0 ? void 0 : _g.actions.aiAssistant.resetSession();
2990
- if (key)
3051
+ if (key) {
3052
+ this.rebaselineOnClearedTranscript(key, discarded);
2991
3053
  this.rebuildDriver(key);
3054
+ }
2992
3055
  // Restore-core is owned by the shared per-session persister (GENC-1351 §5.10):
2993
3056
  // it drops the snapshot + resets the carry-forward cache. Diagnostics is
2994
3057
  // append-only and handled separately below.
@@ -3096,10 +3159,34 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
3096
3159
  get settingsCostCurrentSectionVisible() {
3097
3160
  return this.settingsCostCurrentSlotted.length > 0;
3098
3161
  }
3162
+ /**
3163
+ * Whether the build-history section has anything in it — the built-in row list, or a host's own
3164
+ * markup in the `settings-cost-history` slot.
3165
+ *
3166
+ * Slotted content is checked FIRST, ahead of `enabled`. That flag governs the *built-in* ledger:
3167
+ * it gates the row writes, the reload, and the lifetime sum — and it defaults to `false`
3168
+ * (`cost?.enabled === true`), while the Usage tab itself shows for `showCost`/`showTokens`, which
3169
+ * default true. So a host can legitimately render its own build history into the slot and never
3170
+ * opt into our `localStorage` ledger. Short-circuiting on `enabled` above the slot check hid that
3171
+ * host's markup outright, since the slot is the only thing in the container.
3172
+ */
3099
3173
  get settingsCostHistorySectionVisible() {
3174
+ if (this.settingsCostHistorySlotted.length > 0)
3175
+ return true;
3100
3176
  if (!this.resolvedCostHistory.enabled)
3101
3177
  return false;
3102
- return this.costSessionHistory.length > 0 || this.settingsCostHistorySlotted.length > 0;
3178
+ return this.costSessionHistory.length > 0;
3179
+ }
3180
+ /**
3181
+ * Whether the "Build history" heading has anything under it.
3182
+ *
3183
+ * Broader than {@link FoundationAiAssistant.settingsCostHistorySectionVisible} because the
3184
+ * heading also covers the lifetime total, which renders before the first row exists (rows are
3185
+ * written as spend accrues, the lifetime section only needs history switched on). Gating the
3186
+ * heading on the rows alone left that total sitting under nothing.
3187
+ */
3188
+ get settingsCostHistoryHeadingVisible() {
3189
+ return this.settingsCostHistorySectionVisible || this.settingsCostLifetimeVisible;
3103
3190
  }
3104
3191
  /** Whether the built-in Usage tab summary block has anything to show. */
3105
3192
  get settingsCostSummaryVisible() {
@@ -4218,6 +4305,9 @@ __decorate([
4218
4305
  __decorate([
4219
4306
  volatile
4220
4307
  ], FoundationAiAssistant.prototype, "settingsCostHistorySectionVisible", null);
4308
+ __decorate([
4309
+ volatile
4310
+ ], FoundationAiAssistant.prototype, "settingsCostHistoryHeadingVisible", null);
4221
4311
  __decorate([
4222
4312
  volatile
4223
4313
  ], FoundationAiAssistant.prototype, "settingsCostSummaryVisible", null);
@@ -10,6 +10,19 @@ const baseStyles = css `
10
10
  float still triggers at 1500px. */
11
11
  --ai-conversation-max-width: 1500px;
12
12
 
13
+ /* INTERNAL — not a supported override point, despite sitting beside one.
14
+ Deliberately NOT --ai-*-namespaced and absent from docs/styling.md: the
15
+ documented banner surface is ::part(blocked-banner), and this token is
16
+ plumbing, not a knob. It is the banner's block margin AND the exact
17
+ amount the resize handle pulls itself up by when it follows the banner —
18
+ one name, on the host only because a sibling cannot read a custom
19
+ property declared on the banner (they inherit downward), so the
20
+ cancellation is structural: change the gap here and both rules move
21
+ together, instead of two magnitudes ~80 lines apart that only prose kept
22
+ in sync. A host that overrides it anyway gets a consistent gap, but that
23
+ is a side effect, not a promise. */
24
+ --blocked-banner-gap: calc(var(--design-unit) * 1px);
25
+
13
26
  display: flex;
14
27
  flex-direction: column;
15
28
  height: 100%;
@@ -638,7 +651,7 @@ const baseStyles = css `
638
651
  /* Block axis only. The inline-axis margin is owned by the composer width group
639
652
  above (margin-inline: auto), which centres the banner on the same column as
640
653
  the input row — an inline margin here would fight it. */
641
- margin-block: calc(var(--design-unit) * 1px);
654
+ margin-block: var(--blocked-banner-gap);
642
655
  padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
643
656
  border: calc(var(--stroke-width) * 1px) solid var(--warning-color);
644
657
  border-radius: calc(var(--control-corner-radius) * 1px);
@@ -704,6 +717,18 @@ const baseStyles = css `
704
717
  opacity: 70%;
705
718
  }
706
719
 
720
+ /* With the banner visible, its block-end margin and the handle's height used
721
+ to STACK into an ~11px void between the exhausted message and the composer
722
+ — and the hover pill centres in the handle's own 7px slice, i.e. below the
723
+ middle of the gap the user actually sees. Pull the handle up over the
724
+ banner's end margin instead: the handle's height becomes the whole gap and
725
+ the pill lands dead-centre of it. FAST's when-anchors are comment nodes,
726
+ so the adjacency holds whenever the handle renders; without a handle
727
+ (resize disabled) the banner keeps its normal margin before the input row. */
728
+ .blocked-banner.is-visible + .composer-resize-handle {
729
+ margin-block-start: calc(var(--blocked-banner-gap) * -1);
730
+ }
731
+
707
732
  .file-input {
708
733
  display: none;
709
734
  }
@@ -77,6 +77,19 @@ suite('the blocked banner keeps its unblocked container visually inert', () => {
77
77
  assert.not.ok(emptyStateRule.includes(property), `the always-present container must not set ${property} — that is .is-visible's job`);
78
78
  }
79
79
  });
80
+ suite('the visible banner hands its end margin to the resize handle', () => {
81
+ // Reviewer's manual-testing note: the banner's block-end margin plus the 7px
82
+ // handle stacked into a gap the hover pill was NOT centred in (it centres in
83
+ // the handle alone). The adjacency rule collapses the two so the handle IS
84
+ // the gap. What gets pinned is the INVARIANT, not the selector's existence:
85
+ // both rules must read the ONE shared token, because the cancellation only
86
+ // works while the banner's margin and the handle's pull are equal-and-
87
+ // opposite — two hand-kept magnitudes ~80 lines apart regress silently, and
88
+ // an existence-only assertion stayed green while they did.
89
+ assert.ok(/:host\s*\{[^}]*--blocked-banner-gap:/.test(cssText), 'the shared gap token is declared on the host (a sibling cannot read one on the banner)');
90
+ assert.ok(/\.blocked-banner\.is-visible\s*\{[^}]*margin-block:\s*var\(--blocked-banner-gap\)/.test(cssText), "the banner's block margin IS the token");
91
+ assert.ok(/\.blocked-banner\.is-visible \+ \.composer-resize-handle\s*\{[^}]*margin-block-start:\s*calc\(var\(--blocked-banner-gap\) \* -1\)/.test(cssText), 'and the handle pulls up by exactly that token, negated — equal and opposite by construction');
92
+ });
80
93
  suite('partial exhaustion has its own modifier, layered on the visible state', () => {
81
94
  // `.is-visible` carries the layout and surface for BOTH states; `.is-partial`
82
95
  // only softens it. Additive, never a replacement — so host CSS keyed on the
@@ -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.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/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"}
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.7.0",
4
+ "version": "15.7.2",
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.7.0",
77
- "@genesislcap/genx": "15.7.0",
78
- "@genesislcap/rollup-builder": "15.7.0",
79
- "@genesislcap/ts-builder": "15.7.0",
80
- "@genesislcap/uvu-playwright-builder": "15.7.0",
81
- "@genesislcap/vite-builder": "15.7.0",
82
- "@genesislcap/webpack-builder": "15.7.0",
76
+ "@genesislcap/foundation-testing": "15.7.2",
77
+ "@genesislcap/genx": "15.7.2",
78
+ "@genesislcap/rollup-builder": "15.7.2",
79
+ "@genesislcap/ts-builder": "15.7.2",
80
+ "@genesislcap/uvu-playwright-builder": "15.7.2",
81
+ "@genesislcap/vite-builder": "15.7.2",
82
+ "@genesislcap/webpack-builder": "15.7.2",
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.7.0",
89
- "@genesislcap/foundation-logger": "15.7.0",
90
- "@genesislcap/foundation-notifications": "15.7.0",
91
- "@genesislcap/foundation-redux": "15.7.0",
92
- "@genesislcap/foundation-ui": "15.7.0",
93
- "@genesislcap/foundation-utils": "15.7.0",
94
- "@genesislcap/rapid-design-system": "15.7.0",
95
- "@genesislcap/web-core": "15.7.0",
88
+ "@genesislcap/foundation-ai": "15.7.2",
89
+ "@genesislcap/foundation-logger": "15.7.2",
90
+ "@genesislcap/foundation-notifications": "15.7.2",
91
+ "@genesislcap/foundation-redux": "15.7.2",
92
+ "@genesislcap/foundation-ui": "15.7.2",
93
+ "@genesislcap/foundation-utils": "15.7.2",
94
+ "@genesislcap/rapid-design-system": "15.7.2",
95
+ "@genesislcap/web-core": "15.7.2",
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": "485a2cc9d4b8695ea2e57a9cc66cad55effb2184"
108
+ "gitHead": "fff7bda0cf8c4ce2e9a7b5363d991fd63e4ecd3a"
109
109
  }