@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.
- package/dist/ai-assistant.api.json +32 -2
- package/dist/ai-assistant.d.ts +42 -14
- package/dist/custom-elements.json +55 -42
- 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/main.d.ts +42 -14
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.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/settings-modal/settings-modal.styles.js +63 -18
- package/dist/esm/components/settings-modal/settings-modal.template.js +36 -7
- package/dist/esm/main/cost-session-banking.test.js +238 -8
- package/dist/esm/main/main.js +127 -37
- package/dist/esm/main/main.styles.js +26 -1
- package/dist/esm/main/main.styles.test.js +13 -0
- package/dist/esm/utils/banked-usage-baselines.js +84 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- 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/cost-session-banking.test.ts +283 -11
- package/src/main/main.styles.test.ts +27 -0
- package/src/main/main.styles.ts +26 -1
- package/src/main/main.ts +128 -37
- package/src/utils/banked-usage-baselines.ts +90 -0
package/dist/esm/main/main.js
CHANGED
|
@@ -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
|
-
|
|
273
|
-
|
|
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
|
|
1499
|
-
*
|
|
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
|
-
|
|
1513
|
-
|
|
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
|
-
|
|
1518
|
-
|
|
1519
|
-
return
|
|
1526
|
+
const banked = resolveBankedUsage(existing, this.persistenceActive);
|
|
1527
|
+
setBankedBaseline(projectKey, banked);
|
|
1528
|
+
return banked;
|
|
1520
1529
|
}
|
|
1521
1530
|
/**
|
|
1522
|
-
*
|
|
1523
|
-
*
|
|
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
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
*
|
|
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
|
-
|
|
1530
|
-
this
|
|
1531
|
-
this
|
|
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
|
|
1546
|
-
//
|
|
1547
|
-
//
|
|
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
|
|
1550
|
-
// only for rows this element wrote (`finalizeCostSession` sets `id: projectKey`); a
|
|
1551
|
-
// a host provider can carry any id, and
|
|
1552
|
-
//
|
|
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
|
|
1555
|
-
|
|
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
|
|
1561
|
-
//
|
|
1562
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
77
|
-
"@genesislcap/genx": "15.7.
|
|
78
|
-
"@genesislcap/rollup-builder": "15.7.
|
|
79
|
-
"@genesislcap/ts-builder": "15.7.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.7.
|
|
81
|
-
"@genesislcap/vite-builder": "15.7.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.7.
|
|
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.
|
|
89
|
-
"@genesislcap/foundation-logger": "15.7.
|
|
90
|
-
"@genesislcap/foundation-notifications": "15.7.
|
|
91
|
-
"@genesislcap/foundation-redux": "15.7.
|
|
92
|
-
"@genesislcap/foundation-ui": "15.7.
|
|
93
|
-
"@genesislcap/foundation-utils": "15.7.
|
|
94
|
-
"@genesislcap/rapid-design-system": "15.7.
|
|
95
|
-
"@genesislcap/web-core": "15.7.
|
|
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": "
|
|
108
|
+
"gitHead": "fff7bda0cf8c4ce2e9a7b5363d991fd63e4ecd3a"
|
|
109
109
|
}
|