@genesislcap/ai-assistant 15.4.1 → 15.5.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 +544 -82
- package/dist/ai-assistant.d.ts +324 -36
- package/dist/chat-driver.cjs +94 -22
- package/dist/chat-driver.cjs.map +3 -3
- package/dist/chat-driver.mjs +94 -22
- package/dist/chat-driver.mjs.map +3 -3
- package/dist/custom-elements.json +303 -36
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts +9 -2
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/cost-session-banking.test.d.ts +2 -0
- package/dist/dts/main/cost-session-banking.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +184 -24
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/provider/assistant-app-settings.d.ts +30 -5
- package/dist/dts/provider/assistant-app-settings.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +11 -8
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persistence-provider.d.ts +23 -0
- package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +1 -2
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/styles/settings-section.d.ts +29 -0
- package/dist/dts/styles/settings-section.d.ts.map +1 -0
- package/dist/dts/utils/cost-session-history.d.ts +103 -12
- package/dist/dts/utils/cost-session-history.d.ts.map +1 -1
- package/dist/dts/utils/resolve-cost-history-config.d.ts +9 -3
- package/dist/dts/utils/resolve-cost-history-config.d.ts.map +1 -1
- package/dist/dts/utils/sum-costs.d.ts.map +1 -1
- package/dist/dts/utils/sum-tokens.d.ts +8 -8
- package/dist/dts/utils/sum-tokens.d.ts.map +1 -1
- package/dist/dts/utils/sum-usage.d.ts +59 -0
- package/dist/dts/utils/sum-usage.d.ts.map +1 -0
- package/dist/dts/utils/sum-usage.test.d.ts +2 -0
- package/dist/dts/utils/sum-usage.test.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +6 -0
- package/dist/esm/components/settings-modal/settings-modal.styles.js +237 -18
- package/dist/esm/components/settings-modal/settings-modal.template.js +229 -73
- package/dist/esm/index.js +1 -0
- package/dist/esm/main/cost-session-banking.test.js +308 -0
- package/dist/esm/main/main.js +424 -71
- package/dist/esm/state/ai-assistant-slice.js +11 -8
- package/dist/esm/state/ai-assistant-slice.test.js +12 -5
- package/dist/esm/state/debug-event-log.js +2 -2
- package/dist/esm/state/persistence/session-persistence.integration.test.js +5 -1
- package/dist/esm/state/persistence/session-persister.js +2 -2
- package/dist/esm/state/persistence/session-persister.test.js +10 -1
- package/dist/esm/state/persistence/session-snapshot.js +6 -2
- package/dist/esm/state/persistence/session-snapshot.test.js +4 -1
- package/dist/esm/state/persistence/stateful-restore.e2e.test.js +10 -1
- package/dist/esm/styles/settings-section.js +39 -0
- package/dist/esm/utils/cost-session-history.js +92 -15
- package/dist/esm/utils/cost-session-history.test.js +155 -13
- package/dist/esm/utils/resolve-cost-history-config.js +2 -1
- package/dist/esm/utils/sum-costs.js +2 -13
- package/dist/esm/utils/sum-tokens.js +10 -27
- package/dist/esm/utils/sum-tokens.test.js +1 -5
- package/dist/esm/utils/sum-usage.js +123 -0
- package/dist/esm/utils/sum-usage.test.js +120 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/components/chat-driver/chat-driver.ts +6 -0
- package/src/components/settings-modal/settings-modal.styles.ts +237 -18
- package/src/components/settings-modal/settings-modal.template.ts +270 -81
- package/src/index.ts +1 -0
- package/src/main/cost-session-banking.test.ts +407 -0
- package/src/main/main.ts +433 -68
- package/src/provider/assistant-app-settings.ts +31 -5
- package/src/state/ai-assistant-slice.test.ts +12 -5
- package/src/state/ai-assistant-slice.ts +21 -13
- package/src/state/debug-event-log.ts +2 -2
- package/src/state/persistence/session-persistence-provider.ts +24 -0
- package/src/state/persistence/session-persistence.integration.test.ts +8 -1
- package/src/state/persistence/session-persister.test.ts +10 -1
- package/src/state/persistence/session-persister.ts +2 -2
- package/src/state/persistence/session-snapshot.test.ts +4 -1
- package/src/state/persistence/session-snapshot.ts +5 -1
- package/src/state/persistence/stateful-restore.e2e.test.ts +9 -1
- package/src/styles/settings-section.ts +40 -0
- package/src/utils/cost-session-history.test.ts +187 -16
- package/src/utils/cost-session-history.ts +142 -23
- package/src/utils/resolve-cost-history-config.ts +10 -3
- package/src/utils/sum-costs.ts +2 -9
- package/src/utils/sum-tokens.test.ts +1 -11
- package/src/utils/sum-tokens.ts +10 -26
- package/src/utils/sum-usage.test.ts +140 -0
- package/src/utils/sum-usage.ts +130 -0
|
@@ -430,6 +430,14 @@
|
|
|
430
430
|
"package": "./utils/tool-fold"
|
|
431
431
|
}
|
|
432
432
|
},
|
|
433
|
+
{
|
|
434
|
+
"kind": "js",
|
|
435
|
+
"name": "assistantSettingsSectionTitleStyles",
|
|
436
|
+
"declaration": {
|
|
437
|
+
"name": "assistantSettingsSectionTitleStyles",
|
|
438
|
+
"module": "./styles/settings-section"
|
|
439
|
+
}
|
|
440
|
+
},
|
|
433
441
|
{
|
|
434
442
|
"kind": "js",
|
|
435
443
|
"name": "TurnFailureReason",
|
|
@@ -1898,13 +1906,49 @@
|
|
|
1898
1906
|
},
|
|
1899
1907
|
"description": "Context window size for the active model, if known."
|
|
1900
1908
|
},
|
|
1909
|
+
{
|
|
1910
|
+
"kind": "field",
|
|
1911
|
+
"name": "sessionUsage",
|
|
1912
|
+
"type": {
|
|
1913
|
+
"text": "AggregateUsage"
|
|
1914
|
+
},
|
|
1915
|
+
"description": "Cost plus the four token buckets for this session — derived from the transcript\non every change, so it includes sub-agent turns and spend banked by a compaction."
|
|
1916
|
+
},
|
|
1901
1917
|
{
|
|
1902
1918
|
"kind": "field",
|
|
1903
1919
|
"name": "sessionCostUsd",
|
|
1904
1920
|
"type": {
|
|
1905
1921
|
"text": "number"
|
|
1906
1922
|
},
|
|
1907
|
-
"description": "Aggregated USD cost across every chat turn in this session."
|
|
1923
|
+
"description": "Aggregated USD cost across every chat turn in this session.\n\nRead-only (unlike previously): it is a projection of `sessionUsage`, and a\nhost assigning it would have set a figure the next transcript change overwrites.",
|
|
1924
|
+
"readonly": true
|
|
1925
|
+
},
|
|
1926
|
+
{
|
|
1927
|
+
"kind": "field",
|
|
1928
|
+
"name": "uniqueProviderModels",
|
|
1929
|
+
"type": {
|
|
1930
|
+
"text": "CostSessionModelEntry[]"
|
|
1931
|
+
},
|
|
1932
|
+
"description": "Distinct models the registry currently resolves to — the live session's model chips.\n\n`providerStatuses` carries one entry per registry SLOT, so a vendor configured across several\ntiers that happen to resolve to the same model reports it once per tier, and the chips\nrendered it once per tier too. Which tier a model came from is not what these chips are for.\n\nDeduped on the label actually displayed, not on `status.model`: a slot whose provider reports\nno model falls back to the slot's own name, and those are distinct per slot, so keying on the\nlabel keeps them from collapsing into one another.",
|
|
1933
|
+
"readonly": true
|
|
1934
|
+
},
|
|
1935
|
+
{
|
|
1936
|
+
"kind": "field",
|
|
1937
|
+
"name": "sortedCostSessionHistory",
|
|
1938
|
+
"type": {
|
|
1939
|
+
"text": "CostSessionRecord[]"
|
|
1940
|
+
},
|
|
1941
|
+
"description": "Build history for display: most recently worked on first.\n\nThe stored order is insertion order (see `upsertRecord`), which leaves a project returned to\nsitting wherever it was first seen — so ordering is applied here, on read, where it also\ncovers rows a host provider supplied.",
|
|
1942
|
+
"readonly": true
|
|
1943
|
+
},
|
|
1944
|
+
{
|
|
1945
|
+
"kind": "field",
|
|
1946
|
+
"name": "cumulativeUsage",
|
|
1947
|
+
"type": {
|
|
1948
|
+
"text": "AggregateUsage"
|
|
1949
|
+
},
|
|
1950
|
+
"description": "Lifetime usage across every project in the history scope — cost and all four token\nbuckets. What the Usage tab's lifetime section renders.\n\nRows ONLY — deliberately not rows + live session. Each row holds a project's lifetime\nusage and the active project's row is kept current on every transcript change, so\nadding the live total would count this project's spend twice. That double-count is what\nmade the figure grow on every refresh: a page-hide banked a row while the restored\nsession still carried the same spend.",
|
|
1951
|
+
"readonly": true
|
|
1908
1952
|
},
|
|
1909
1953
|
{
|
|
1910
1954
|
"kind": "field",
|
|
@@ -1912,7 +1956,16 @@
|
|
|
1912
1956
|
"type": {
|
|
1913
1957
|
"text": "number"
|
|
1914
1958
|
},
|
|
1915
|
-
"description": "
|
|
1959
|
+
"description": "Lifetime USD across every project in the history scope.",
|
|
1960
|
+
"readonly": true
|
|
1961
|
+
},
|
|
1962
|
+
{
|
|
1963
|
+
"kind": "field",
|
|
1964
|
+
"name": "liveUsageIsProjectLifetime",
|
|
1965
|
+
"type": {
|
|
1966
|
+
"text": "boolean"
|
|
1967
|
+
},
|
|
1968
|
+
"description": "Whether the live session figures amount to this project's whole history, which is true\nexactly when the transcript is restored on load. Drives the Usage tab's live labels\n(\"Project cost\" vs \"Session cost\") so they state the scope rather than imply one.\n\nNote this is NOT what makes the *rows* lifetime — those bank their pre-transcript spend\n(see `CostSessionRecord.banked`) and are lifetime either way.",
|
|
1916
1969
|
"readonly": true
|
|
1917
1970
|
},
|
|
1918
1971
|
{
|
|
@@ -1930,7 +1983,8 @@
|
|
|
1930
1983
|
"type": {
|
|
1931
1984
|
"text": "number"
|
|
1932
1985
|
},
|
|
1933
|
-
"description": "
|
|
1986
|
+
"description": "Total tokens across every chat turn in this session — all four buckets added up.\nRead-only; the per-bucket split is on `sessionUsage`.",
|
|
1987
|
+
"readonly": true
|
|
1934
1988
|
},
|
|
1935
1989
|
{
|
|
1936
1990
|
"kind": "field",
|
|
@@ -1938,7 +1992,7 @@
|
|
|
1938
1992
|
"type": {
|
|
1939
1993
|
"text": "string | undefined"
|
|
1940
1994
|
},
|
|
1941
|
-
"description": "Label shown on the
|
|
1995
|
+
"description": "Label shown on the Usage tab for the in-flight session (app title or first user phrase).",
|
|
1942
1996
|
"readonly": true
|
|
1943
1997
|
},
|
|
1944
1998
|
{
|
|
@@ -1958,7 +2012,37 @@
|
|
|
1958
2012
|
}
|
|
1959
2013
|
}
|
|
1960
2014
|
],
|
|
1961
|
-
"description": "
|
|
2015
|
+
"description": "Write this session's usage into persisted build history (`localStorage`) as a single\nrow keyed by the session key, replacing any existing row for that key.\n\n**Idempotent** — safe to call per turn, on tab close, and on a title change alike.\nThe name is historical: it does NOT end the session or reset the running totals.\nResetting them is what used to double-count spend, since the row was banked while the\npersisted transcript still carried the same usage, so a reload restored it and the\nUsage tab counted both.\n\nNo-op unless `chatConfig.costHistory.enabled` is `true`, when the live session has no\nusage yet, or when the session has no key to upsert against. Gating on the *live*\nfigures (not the banked total) means a reload that restores nothing rewrites nothing."
|
|
2016
|
+
},
|
|
2017
|
+
{
|
|
2018
|
+
"kind": "method",
|
|
2019
|
+
"name": "bankedUsageFor",
|
|
2020
|
+
"privacy": "private",
|
|
2021
|
+
"return": {
|
|
2022
|
+
"type": {
|
|
2023
|
+
"text": "AggregateUsage"
|
|
2024
|
+
}
|
|
2025
|
+
},
|
|
2026
|
+
"parameters": [
|
|
2027
|
+
{
|
|
2028
|
+
"name": "projectKey",
|
|
2029
|
+
"type": {
|
|
2030
|
+
"text": "string"
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
],
|
|
2034
|
+
"description": "Spend for `projectKey` that predates the current transcript, resolved once per project\nand memoised (`_bankedFor`) so it cannot drift mid-session as the row it came from is\nrewritten by our own upserts.\n\nThe two cases turn on whether the transcript is authoritative for this project:\n\n- **Transcript restored** (`persistenceActive`): it already accounts for everything after\n the previous row's own banked figure, so carry that figure forward unchanged. Re-banking\n `usage` here would add the restored history to itself on every reload.\n- **Transcript starts empty**: nothing on screen accounts for the previous total, so the\n whole of it becomes banked. Without this the next turn would rewrite the row downwards\n to just that turn, losing the project's earlier spend."
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
"kind": "method",
|
|
2038
|
+
"name": "resetBankedUsage",
|
|
2039
|
+
"privacy": "private",
|
|
2040
|
+
"return": {
|
|
2041
|
+
"type": {
|
|
2042
|
+
"text": "void"
|
|
2043
|
+
}
|
|
2044
|
+
},
|
|
2045
|
+
"description": "Discard the banked figure — for when the spend it stands for has been deliberately\nthrown away (the user clearing history, or deleting this project's row).\n\nNOT for reloads. A reload re-reads rows that already include our own upserts, so\nre-deriving from them would fold the live session into its own baseline; only a genuine\nproject change should re-derive, which `bankedUsageFor`'s per-project memo handles."
|
|
1962
2046
|
},
|
|
1963
2047
|
{
|
|
1964
2048
|
"kind": "method",
|
|
@@ -2044,6 +2128,42 @@
|
|
|
2044
2128
|
}
|
|
2045
2129
|
}
|
|
2046
2130
|
},
|
|
2131
|
+
{
|
|
2132
|
+
"kind": "method",
|
|
2133
|
+
"name": "costHistoryProvider",
|
|
2134
|
+
"privacy": "private",
|
|
2135
|
+
"description": "The persistence provider, but only when it implements BOTH cost-history methods.\n\nBoth or neither: a provider that can save but not load would silently accumulate a\nledger nobody ever reads back, so a partial implementation is treated as absent and\nfalls back to `localStorage`."
|
|
2136
|
+
},
|
|
2137
|
+
{
|
|
2138
|
+
"kind": "method",
|
|
2139
|
+
"name": "persistCostHistory",
|
|
2140
|
+
"privacy": "private",
|
|
2141
|
+
"return": {
|
|
2142
|
+
"type": {
|
|
2143
|
+
"text": "void"
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
"parameters": [
|
|
2147
|
+
{
|
|
2148
|
+
"name": "records",
|
|
2149
|
+
"type": {
|
|
2150
|
+
"text": "CostSessionRecord[]"
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
],
|
|
2154
|
+
"description": "Adopt `records` as the live list, and schedule a write to whichever backend owns cost\nhistory — the provider when the host implements it, else `localStorage`.\n\nThe list is adopted synchronously because that is what the Usage tab renders; only the\n*write* is deferred. This runs on every usage change (about once per LLM call, plus once\nper sub-agent turn) and each write serialises the whole ledger, so coalescing cuts a\nper-turn `localStorage.setItem` or network round-trip down to one per quiet period. Safe\nbecause the write is a full replace of idempotent state: the newest call wins and no\nintermediate state is meaningful. Flushed on `pagehide` and disconnect so nothing is lost."
|
|
2155
|
+
},
|
|
2156
|
+
{
|
|
2157
|
+
"kind": "method",
|
|
2158
|
+
"name": "flushCostHistory",
|
|
2159
|
+
"privacy": "private",
|
|
2160
|
+
"return": {
|
|
2161
|
+
"type": {
|
|
2162
|
+
"text": "void"
|
|
2163
|
+
}
|
|
2164
|
+
},
|
|
2165
|
+
"description": "Write the pending ledger now. Safe to call when nothing is pending.\n\nProvider writes are fire-and-forget and best-effort: the ledger is accounting, not\nconversation content, so a failed write is logged and never surfaced into the chat. On\n`pagehide` the `localStorage` path completes synchronously; a provider write is subject to\nthe same unload race as every other network save the assistant makes."
|
|
2166
|
+
},
|
|
2047
2167
|
{
|
|
2048
2168
|
"kind": "method",
|
|
2049
2169
|
"name": "syncActiveCostSessionTitle",
|
|
@@ -2053,7 +2173,7 @@
|
|
|
2053
2173
|
"text": "void"
|
|
2054
2174
|
}
|
|
2055
2175
|
},
|
|
2056
|
-
"description": "Finalize the previous session when the host-provided title changes."
|
|
2176
|
+
"description": "Finalize the previous session when the host-provided title changes — a rename should land on\nthe row under its old name rather than silently relabelling accrued spend.\n\nOnly when the session key still matches the one that title accrued under. Hosts change\n`costHistory.title` on project SWITCH too, and if the key has already flipped, this would\nupsert the outgoing project's live usage onto the incoming project's row as\n`banked + old live`, inflating it. Under the old append model a mis-keyed write left a stray\nrow; with the upsert it overwrites a real one."
|
|
2057
2177
|
},
|
|
2058
2178
|
{
|
|
2059
2179
|
"kind": "field",
|
|
@@ -2212,7 +2332,7 @@
|
|
|
2212
2332
|
"text": "CostSessionRecord[]"
|
|
2213
2333
|
},
|
|
2214
2334
|
"default": "[]",
|
|
2215
|
-
"description": "Persisted finalized cost sessions shown on the
|
|
2335
|
+
"description": "Persisted finalized cost sessions shown on the Usage tab build history list."
|
|
2216
2336
|
},
|
|
2217
2337
|
{
|
|
2218
2338
|
"kind": "field",
|
|
@@ -2285,13 +2405,68 @@
|
|
|
2285
2405
|
},
|
|
2286
2406
|
{
|
|
2287
2407
|
"kind": "field",
|
|
2288
|
-
"name": "
|
|
2408
|
+
"name": "_activeCostSessionKey",
|
|
2409
|
+
"type": {
|
|
2410
|
+
"text": "string | undefined"
|
|
2411
|
+
},
|
|
2412
|
+
"privacy": "private",
|
|
2413
|
+
"description": "Session key `_activeCostSessionTitle` was captured under, so a title change can tell a rename\n(same key) from a project switch (key already flipped) — see `syncActiveCostSessionTitle`."
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
"kind": "field",
|
|
2417
|
+
"name": "_bankedUsage",
|
|
2418
|
+
"type": {
|
|
2419
|
+
"text": "AggregateUsage"
|
|
2420
|
+
},
|
|
2421
|
+
"privacy": "private",
|
|
2422
|
+
"description": "Memoised pre-transcript spend for `_bankedFor` — see `bankedUsageFor`."
|
|
2423
|
+
},
|
|
2424
|
+
{
|
|
2425
|
+
"kind": "field",
|
|
2426
|
+
"name": "_bankedFor",
|
|
2427
|
+
"type": {
|
|
2428
|
+
"text": "string | undefined"
|
|
2429
|
+
},
|
|
2430
|
+
"privacy": "private",
|
|
2431
|
+
"description": "Project key `_bankedUsage` was resolved for; `undefined` means \"not yet resolved\"."
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
"kind": "field",
|
|
2435
|
+
"name": "_costHistoryLoaded",
|
|
2436
|
+
"type": {
|
|
2437
|
+
"text": "boolean"
|
|
2438
|
+
},
|
|
2439
|
+
"privacy": "private",
|
|
2440
|
+
"default": "false",
|
|
2441
|
+
"description": "Whether the row list reflects storage yet. Gates cost writes so a project's banked\nspend is never derived from a ledger that simply hasn't arrived."
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
"kind": "field",
|
|
2445
|
+
"name": "_ledgerUnreadable",
|
|
2289
2446
|
"type": {
|
|
2290
2447
|
"text": "boolean"
|
|
2291
2448
|
},
|
|
2292
2449
|
"privacy": "private",
|
|
2293
2450
|
"default": "false",
|
|
2294
|
-
"description": "
|
|
2451
|
+
"description": "Set when a provider read failed. Latches writes off for this page-load: `saveCostHistory`\nreplaces the whole stored list, so writing anything derived from the empty list we fell back\nto would destroy rows we never managed to read."
|
|
2452
|
+
},
|
|
2453
|
+
{
|
|
2454
|
+
"kind": "field",
|
|
2455
|
+
"name": "_ledgerDirty",
|
|
2456
|
+
"type": {
|
|
2457
|
+
"text": "boolean"
|
|
2458
|
+
},
|
|
2459
|
+
"privacy": "private",
|
|
2460
|
+
"default": "false",
|
|
2461
|
+
"description": "Pending (adopted but unwritten) ledger changes — see `persistCostHistory`."
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
"kind": "field",
|
|
2465
|
+
"name": "_ledgerFlushTimer",
|
|
2466
|
+
"type": {
|
|
2467
|
+
"text": "ReturnType<typeof setTimeout> | undefined"
|
|
2468
|
+
},
|
|
2469
|
+
"privacy": "private"
|
|
2295
2470
|
},
|
|
2296
2471
|
{
|
|
2297
2472
|
"kind": "field",
|
|
@@ -3146,20 +3321,20 @@
|
|
|
3146
3321
|
},
|
|
3147
3322
|
{
|
|
3148
3323
|
"kind": "field",
|
|
3149
|
-
"name": "
|
|
3324
|
+
"name": "visibleAppSettingsToggles",
|
|
3150
3325
|
"type": {
|
|
3151
|
-
"text": "
|
|
3326
|
+
"text": "AssistantAppSettingsToggle[]"
|
|
3152
3327
|
},
|
|
3153
|
-
"description": "
|
|
3328
|
+
"description": "App toggles currently visible per each entry's `visible()` predicate.",
|
|
3154
3329
|
"readonly": true
|
|
3155
3330
|
},
|
|
3156
3331
|
{
|
|
3157
3332
|
"kind": "field",
|
|
3158
|
-
"name": "
|
|
3333
|
+
"name": "appSettingsTogglesHeading",
|
|
3159
3334
|
"type": {
|
|
3160
|
-
"text": "
|
|
3335
|
+
"text": "AssistantAppSettingsHeading | undefined"
|
|
3161
3336
|
},
|
|
3162
|
-
"description": "
|
|
3337
|
+
"description": "Host-supplied heading for the toggle group, or `undefined` for none — the assistant has no\ndefault. Suppressed when no toggle is currently visible, so a `visible()` predicate hiding\nthe last one takes its heading with it rather than leaving a label over nothing.",
|
|
3163
3338
|
"readonly": true
|
|
3164
3339
|
},
|
|
3165
3340
|
{
|
|
@@ -3244,7 +3419,16 @@
|
|
|
3244
3419
|
"type": {
|
|
3245
3420
|
"text": "boolean"
|
|
3246
3421
|
},
|
|
3247
|
-
"description": "Whether the built-in
|
|
3422
|
+
"description": "Whether the built-in Usage tab summary block has anything to show.",
|
|
3423
|
+
"readonly": true
|
|
3424
|
+
},
|
|
3425
|
+
{
|
|
3426
|
+
"kind": "field",
|
|
3427
|
+
"name": "settingsCostLifetimeVisible",
|
|
3428
|
+
"type": {
|
|
3429
|
+
"text": "boolean"
|
|
3430
|
+
},
|
|
3431
|
+
"description": "Whether the lifetime-usage section has anything to say.\n\nRequires build history: with it off there are no rows, so `cumulativeUsage` falls back to\nthe live session and the section would restate the block directly below it under a\n\"lifetime\" heading it hasn't earned.",
|
|
3248
3432
|
"readonly": true
|
|
3249
3433
|
},
|
|
3250
3434
|
{
|
|
@@ -3253,7 +3437,7 @@
|
|
|
3253
3437
|
"type": {
|
|
3254
3438
|
"text": "boolean"
|
|
3255
3439
|
},
|
|
3256
|
-
"description": "Whether the
|
|
3440
|
+
"description": "Whether the Usage tab should appear in the settings modal.",
|
|
3257
3441
|
"readonly": true
|
|
3258
3442
|
},
|
|
3259
3443
|
{
|
|
@@ -3262,7 +3446,7 @@
|
|
|
3262
3446
|
"type": {
|
|
3263
3447
|
"text": "boolean"
|
|
3264
3448
|
},
|
|
3265
|
-
"description": "Whether session token usage should render on the
|
|
3449
|
+
"description": "Whether session token usage should render on the Usage tab.",
|
|
3266
3450
|
"readonly": true
|
|
3267
3451
|
},
|
|
3268
3452
|
{
|
|
@@ -4291,7 +4475,7 @@
|
|
|
4291
4475
|
"type": {
|
|
4292
4476
|
"text": "readonly string[]"
|
|
4293
4477
|
},
|
|
4294
|
-
"default": "[\n 'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',\n '`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',\n 'Timestamps are millisecond-resolution; entries that share the same millisecond are ordered by a fixed kind rank (event, then turn, then message), which is a heuristic and may not reflect exact causal order within that millisecond — e.g. a user message and the turn it triggered, or a final assistant message and its turn.end event, can appear in either order depending on whether they landed in the same millisecond. Read the logical structure of a turn rather than over-interpreting the micro-ordering of co-timestamped entries of different kinds.',\n \"kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, and `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). On model-produced assistant messages, `model` is the concrete model id that generated it (e.g. 'gemini-2.5-flash-lite') and `providerName` is the registry slot it resolved under (e.g. a tier name like 'high'/'low', or the default); together they attribute the message — and any tool calls it carries — to an exact model even across a mid-session vendor/tier switch, where one slot name can map to different models before and after the switch. Both are undefined on any entry that is NOT an LLM response: non-assistant roles (user/tool/system-event) and 'synthetic-user' echoes; assistant interaction/widget entries (empty content carrying an `interaction` — a rendered widget, not a model turn); driver-authored assistant fallbacks (the timeout, repeated-malformed-call, and empty-response apology messages); and messages restored from a session persisted before these fields existed. One partial case: on a genuine model turn whose provider exposes no `getStatus` (or reports no model), `providerName` is still set but `model` alone is undefined. A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result.\",\n \"Sub-agent messages appear inline. When a tool delegates to a sub-agent (via `requestSubAgent`), the sub-agent's whole conversation — its own assistant/tool messages, each with their own `content`/`thinking`/`toolCalls`/`toolResult` and per-message `model`/`providerName`/`inputTokens`/`outputTokens`/`cost` — is hoisted into the timeline as ordinary kind:'message' entries, interleaved by timestamp right after the tool call that spawned them (so you read the delegation top-to-bottom). A hoisted entry is marked: `subAgentDepth` is its delegation depth (1 for a sub-agent, 2 for a sub-agent's sub-agent, …), `subAgentOf` is the id of the parent tool call that spawned it (correlates it back even when two sub-agents run in one parent turn), `subAgentName` is the sub-agent's own name, and `agentName` is rewritten to a `\\\"<parent> › <sub-agent>\\\"` breadcrumb (composing when nested, e.g. `\\\"UI Builder › Planner › Grounding\\\"`). The sub-agent's per-LLM-call snapshots also surface as kind:'turn' entries with an N-M `turnIndex`, and subagent.started/completed (or subagent.failed) events bracket the run. Per-message `cost` on hoisted entries is already part of the session total (it is summed from the un-flattened history), so summing the top-level timeline does NOT double-count.\",\n \"kind:'turn' — one LLM call. `turnIndex` is a string: a top-level turn is the bare counter ('0', '1', …); a sub-agent's turns are numbered under the parent turn that activated them ('3-1', '3-2', …, and a nested sub-agent contributes '3-2-1', …), and `agentName` names the agent that ran the turn. `systemPrompt` and `toolNames` are what the model saw. A systemPrompt of '<repeated — identical to turn N>' was byte-identical to turn N and de-duplicated; the full prompt is shown whenever it changes (often because a stateful agent advanced), so prompt evolution is visible.\",\n \"kind:'turn'.`agentSnapshot` — the active agent's own view of its internal state, captured at that turn. An agent opts into this by exposing a `getDebugSnapshot()` that returns JSON-serializable per-state info; stateful/flow agents wire it automatically, so you can watch a flow advance turn-by-turn (e.g. current step, cursor, collected fields, pending changes). Absent for agents that don't expose one.\",\n \"kind:'event' — a meta/lifecycle event. `type` names it (see below); `detail` carries structured data. `detail.placement` is the emitting UI instance: 'bubble' (collapsed), 'panel' (popped-out), or 'standalone'.\",\n \"Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, subagent.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions, sub-agent start/complete), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated, context.condensed). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. A 'high' turn.error is often preceded by one or more 'normal' turn.retry events for the same reason — read them together to see how many attempts were made before bailing. 'message' and 'turn' entries carry no importance — they are the substance, always read them.\",\n 'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), provider.selected (model/provider for the upcoming turns), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',\n 'Sub-agent meta events: a sub-agent\\'s own turn.retry/turn.error/tool.failed/tool.unresolved events are merged into this same timeline, tagged with `detail.subAgent` — a `\"<parent> › <sub-agent>\"` breadcrumb that composes when nested (e.g. `\"UI Builder › Planner › Grounding\"`) — and interleaved by their original timestamps within the subagent.started→completed/failed bracket. These are the per-attempt/per-failure signals that do NOT appear among the sub-agent\\'s (hoisted) messages: a malformed/empty attempt that gets retried produces no message, and the stale-vs-hallucinated split and streak counts live only on the event. A sub-agent\\'s high-volume, message-derivable events (turn.start/turn.end, provider.selected, context.updated) are intentionally NOT merged — read its hoisted messages for model/tokens/cost and turn-by-turn activity, and the bracketing subagent.* events for the run\\'s span.',\n \"`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, token usage, session cost), activeDebugSnapshot (the active agent's `getDebugSnapshot()` taken fresh at export — reflects state NOW, which may have advanced beyond the last turn's agentSnapshot), debug (optional host-supplied debug state), host, and the export timestamp.\",\n 'To debug a failure: find the last turn.error or tool.failed, then read upward for the user message, the turn(s), and the agent/provider/state events that led into it.',\n]",
|
|
4478
|
+
"default": "[\n 'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',\n '`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',\n 'Timestamps are millisecond-resolution; entries that share the same millisecond are ordered by a fixed kind rank (event, then turn, then message), which is a heuristic and may not reflect exact causal order within that millisecond — e.g. a user message and the turn it triggered, or a final assistant message and its turn.end event, can appear in either order depending on whether they landed in the same millisecond. Read the logical structure of a turn rather than over-interpreting the micro-ordering of co-timestamped entries of different kinds.',\n \"kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, where `inputTokens` is the WHOLE prompt for that request and `cacheReadTokens`/`cacheWriteTokens` BREAK IT DOWN rather than add to it — uncached input is `inputTokens` minus those two, and adding the cache fields to `inputTokens` counts the prompt twice. The cache fields are absent on providers that report no cache split (Gemini reports reads only, since implicit caching bills no write) and on messages persisted before they existed, so read them as 0 when missing. Each bucket bills at a different rate — cache reads a fraction of uncached input, cache writes a premium, output highest — so a large token count at a small cost means the prompt was mostly cache hits. `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). On model-produced assistant messages, `model` is the concrete model id that generated it (e.g. 'gemini-2.5-flash-lite') and `providerName` is the registry slot it resolved under (e.g. a tier name like 'high'/'low', or the default); together they attribute the message — and any tool calls it carries — to an exact model even across a mid-session vendor/tier switch, where one slot name can map to different models before and after the switch. Both are undefined on any entry that is NOT an LLM response: non-assistant roles (user/tool/system-event) and 'synthetic-user' echoes; assistant interaction/widget entries (empty content carrying an `interaction` — a rendered widget, not a model turn); driver-authored assistant fallbacks (the timeout, repeated-malformed-call, and empty-response apology messages); and messages restored from a session persisted before these fields existed. One partial case: on a genuine model turn whose provider exposes no `getStatus` (or reports no model), `providerName` is still set but `model` alone is undefined. A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result.\",\n \"Sub-agent messages appear inline. When a tool delegates to a sub-agent (via `requestSubAgent`), the sub-agent's whole conversation — its own assistant/tool messages, each with their own `content`/`thinking`/`toolCalls`/`toolResult` and per-message `model`/`providerName`/`inputTokens`/`outputTokens`/`cacheReadTokens`/`cacheWriteTokens`/`cost` — is hoisted into the timeline as ordinary kind:'message' entries, interleaved by timestamp right after the tool call that spawned them (so you read the delegation top-to-bottom). A hoisted entry is marked: `subAgentDepth` is its delegation depth (1 for a sub-agent, 2 for a sub-agent's sub-agent, …), `subAgentOf` is the id of the parent tool call that spawned it (correlates it back even when two sub-agents run in one parent turn), `subAgentName` is the sub-agent's own name, and `agentName` is rewritten to a `\\\"<parent> › <sub-agent>\\\"` breadcrumb (composing when nested, e.g. `\\\"UI Builder › Planner › Grounding\\\"`). The sub-agent's per-LLM-call snapshots also surface as kind:'turn' entries with an N-M `turnIndex`, and subagent.started/completed (or subagent.failed) events bracket the run. Per-message `cost` on hoisted entries is already part of the session total (it is summed from the un-flattened history), so summing the top-level timeline does NOT double-count.\",\n \"kind:'turn' — one LLM call. `turnIndex` is a string: a top-level turn is the bare counter ('0', '1', …); a sub-agent's turns are numbered under the parent turn that activated them ('3-1', '3-2', …, and a nested sub-agent contributes '3-2-1', …), and `agentName` names the agent that ran the turn. `systemPrompt` and `toolNames` are what the model saw. A systemPrompt of '<repeated — identical to turn N>' was byte-identical to turn N and de-duplicated; the full prompt is shown whenever it changes (often because a stateful agent advanced), so prompt evolution is visible.\",\n \"kind:'turn'.`agentSnapshot` — the active agent's own view of its internal state, captured at that turn. An agent opts into this by exposing a `getDebugSnapshot()` that returns JSON-serializable per-state info; stateful/flow agents wire it automatically, so you can watch a flow advance turn-by-turn (e.g. current step, cursor, collected fields, pending changes). Absent for agents that don't expose one.\",\n \"kind:'event' — a meta/lifecycle event. `type` names it (see below); `detail` carries structured data. `detail.placement` is the emitting UI instance: 'bubble' (collapsed), 'panel' (popped-out), or 'standalone'.\",\n \"Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, subagent.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions, sub-agent start/complete), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated, context.condensed). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. A 'high' turn.error is often preceded by one or more 'normal' turn.retry events for the same reason — read them together to see how many attempts were made before bailing. 'message' and 'turn' entries carry no importance — they are the substance, always read them.\",\n 'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), provider.selected (model/provider for the upcoming turns), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',\n 'Sub-agent meta events: a sub-agent\\'s own turn.retry/turn.error/tool.failed/tool.unresolved events are merged into this same timeline, tagged with `detail.subAgent` — a `\"<parent> › <sub-agent>\"` breadcrumb that composes when nested (e.g. `\"UI Builder › Planner › Grounding\"`) — and interleaved by their original timestamps within the subagent.started→completed/failed bracket. These are the per-attempt/per-failure signals that do NOT appear among the sub-agent\\'s (hoisted) messages: a malformed/empty attempt that gets retried produces no message, and the stale-vs-hallucinated split and streak counts live only on the event. A sub-agent\\'s high-volume, message-derivable events (turn.start/turn.end, provider.selected, context.updated) are intentionally NOT merged — read its hoisted messages for model/tokens/cost and turn-by-turn activity, and the bracketing subagent.* events for the run\\'s span.',\n \"`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, token usage, session cost), activeDebugSnapshot (the active agent's `getDebugSnapshot()` taken fresh at export — reflects state NOW, which may have advanced beyond the last turn's agentSnapshot), debug (optional host-supplied debug state), host, and the export timestamp.\",\n 'To debug a failure: find the last turn.error or tool.failed, then read upward for the user message, the turn(s), and the agent/provider/state events that led into it.',\n]",
|
|
4295
4479
|
"description": "Human/agent-facing guide emitted as the first key of the exported debug log,\nso whoever opens the JSON (often an AI agent) knows how to read it without\nreverse-engineering the shape. Kept here next to the event catalogue it\ndescribes so the two stay in sync."
|
|
4296
4480
|
}
|
|
4297
4481
|
],
|
|
@@ -4682,6 +4866,31 @@
|
|
|
4682
4866
|
}
|
|
4683
4867
|
]
|
|
4684
4868
|
},
|
|
4869
|
+
{
|
|
4870
|
+
"kind": "javascript-module",
|
|
4871
|
+
"path": "src/styles/settings-section.ts",
|
|
4872
|
+
"declarations": [
|
|
4873
|
+
{
|
|
4874
|
+
"kind": "variable",
|
|
4875
|
+
"name": "assistantSettingsSectionTitleStyles",
|
|
4876
|
+
"type": {
|
|
4877
|
+
"text": "ElementStyles"
|
|
4878
|
+
},
|
|
4879
|
+
"default": "css`\n .ai-settings-section-title {\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 2px);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--neutral-foreground-hint);\n }\n`",
|
|
4880
|
+
"description": "Heading style for a settings-modal section, published so a host can title its own section.\n\nThe assistant renders no heading for the `settings-app` slot — no text and no icon — because\nonly the host knows what that section is. The consequence is that a host supplying one has to\nreproduce the styling of the built-in headings beside it, and a copied rule drifts the moment\neither side changes. So the declarations live here, are applied by the assistant's own\nheadings, and are exported for the host to apply to its.\n\nCompose it into a component's styles and put `ai-settings-section-title` on the heading:\n\n```ts\nimport { assistantSettingsSectionTitleStyles } from '@genesislcap/ai-assistant';\n\nconst styles = css`\n ${assistantSettingsSectionTitleStyles}\n :host { display: block; }\n`;\n```\n\nCarries no margin on purpose: spacing belongs to the surrounding layout, which differs\nbetween the assistant's own sections and a slotted one, and is the single declaration hosts\nhad to diverge on when they copied this rule."
|
|
4881
|
+
}
|
|
4882
|
+
],
|
|
4883
|
+
"exports": [
|
|
4884
|
+
{
|
|
4885
|
+
"kind": "js",
|
|
4886
|
+
"name": "assistantSettingsSectionTitleStyles",
|
|
4887
|
+
"declaration": {
|
|
4888
|
+
"name": "assistantSettingsSectionTitleStyles",
|
|
4889
|
+
"module": "src/styles/settings-section.ts"
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
]
|
|
4893
|
+
},
|
|
4685
4894
|
{
|
|
4686
4895
|
"kind": "javascript-module",
|
|
4687
4896
|
"path": "src/suggestions/chat-suggestions.ts",
|
|
@@ -4972,7 +5181,7 @@
|
|
|
4972
5181
|
}
|
|
4973
5182
|
}
|
|
4974
5183
|
],
|
|
4975
|
-
"description": "Load persisted cost sessions for a scope
|
|
5184
|
+
"description": "Load persisted cost sessions for a scope, in whatever order they were stored — this applies no\nordering of its own. Display order is the caller's business; see `sortRecordsByRecency`."
|
|
4976
5185
|
},
|
|
4977
5186
|
{
|
|
4978
5187
|
"kind": "function",
|
|
@@ -5000,7 +5209,7 @@
|
|
|
5000
5209
|
},
|
|
5001
5210
|
{
|
|
5002
5211
|
"kind": "function",
|
|
5003
|
-
"name": "
|
|
5212
|
+
"name": "upsertRecord",
|
|
5004
5213
|
"return": {
|
|
5005
5214
|
"type": {
|
|
5006
5215
|
"text": "CostSessionRecord[]"
|
|
@@ -5008,9 +5217,9 @@
|
|
|
5008
5217
|
},
|
|
5009
5218
|
"parameters": [
|
|
5010
5219
|
{
|
|
5011
|
-
"name": "
|
|
5220
|
+
"name": "records",
|
|
5012
5221
|
"type": {
|
|
5013
|
-
"text": "
|
|
5222
|
+
"text": "readonly CostSessionRecord[]"
|
|
5014
5223
|
}
|
|
5015
5224
|
},
|
|
5016
5225
|
{
|
|
@@ -5020,11 +5229,11 @@
|
|
|
5020
5229
|
}
|
|
5021
5230
|
}
|
|
5022
5231
|
],
|
|
5023
|
-
"description": "
|
|
5232
|
+
"description": "Pure upsert — `records` with `record` replacing any row sharing its `projectKey`, or\nprepended when there is none.\n\nAn upsert, not an append, because a row stands for a **project's lifetime usage** rather\nthan a visit to it. Appending cut a fresh row per page-hide, so one project accumulated\na stack of near-identical rows and its spend was counted once per refresh by anything\nsumming the list.\n\nThe resulting array order is incidental — it is insertion order, so a project returned to\nstays where it first landed. Do not read it as recency; `sortRecordsByRecency` is what the\nUsage tab renders through.\n\nStorage-agnostic on purpose: the same merge serves the synchronous `localStorage` path\nand the async provider-backed one, so the two can never drift on what \"upsert\" means.\nThe caller owns persistence — see the assistant's `persistCostHistory`."
|
|
5024
5233
|
},
|
|
5025
5234
|
{
|
|
5026
5235
|
"kind": "function",
|
|
5027
|
-
"name": "
|
|
5236
|
+
"name": "sortRecordsByRecency",
|
|
5028
5237
|
"return": {
|
|
5029
5238
|
"type": {
|
|
5030
5239
|
"text": "CostSessionRecord[]"
|
|
@@ -5032,19 +5241,37 @@
|
|
|
5032
5241
|
},
|
|
5033
5242
|
"parameters": [
|
|
5034
5243
|
{
|
|
5035
|
-
"name": "
|
|
5244
|
+
"name": "records",
|
|
5036
5245
|
"type": {
|
|
5037
|
-
"text": "
|
|
5246
|
+
"text": "readonly CostSessionRecord[]"
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
],
|
|
5250
|
+
"description": "Rows most-recently-worked-on first, as a new array.\n\nApplied at read time rather than baked into the stored order, so it covers rows that came from\na host provider as well as from `localStorage`, and so no write path has to maintain it.\n\n`updatedAt` is ISO-8601, which sorts lexicographically — no date parsing, and no dependence on\nthe strings being valid dates. Sorting by it rather than by insertion order is what makes the\nlist track use: the active project moves to the top on its first upsert and stays there, since\nits row is rewritten on every transcript change."
|
|
5251
|
+
},
|
|
5252
|
+
{
|
|
5253
|
+
"kind": "function",
|
|
5254
|
+
"name": "resolveBankedUsage",
|
|
5255
|
+
"return": {
|
|
5256
|
+
"type": {
|
|
5257
|
+
"text": "AggregateUsage"
|
|
5258
|
+
}
|
|
5259
|
+
},
|
|
5260
|
+
"parameters": [
|
|
5261
|
+
{
|
|
5262
|
+
"name": "existing",
|
|
5263
|
+
"type": {
|
|
5264
|
+
"text": "CostSessionRecord | undefined"
|
|
5038
5265
|
}
|
|
5039
5266
|
},
|
|
5040
5267
|
{
|
|
5041
|
-
"name": "
|
|
5268
|
+
"name": "transcriptIsAuthoritative",
|
|
5042
5269
|
"type": {
|
|
5043
|
-
"text": "
|
|
5270
|
+
"text": "boolean"
|
|
5044
5271
|
}
|
|
5045
5272
|
}
|
|
5046
5273
|
],
|
|
5047
|
-
"description": "
|
|
5274
|
+
"description": "The spend a project's next row must carry over from before its current transcript.\n\n`transcriptIsAuthoritative` means the open transcript already accounts for this project's\nhistory — true exactly when a session snapshot is restored on load. The two branches are\nnot interchangeable, and picking the wrong one is silently destructive:\n\n- Authoritative: carry the previous row's own `banked` forward. Taking `usage` instead would\n re-add the restored history on every reload, the compounding double-count this replaced.\n- Not authoritative: the whole previous total becomes banked, because nothing in the fresh\n transcript accounts for it. Taking `banked` instead would rewrite the row down to the\n current page-load and drop the project's earlier spend.\n\nNote both branches are idempotent — the result never includes the current transcript — which\nis what lets two assistant instances sharing a session write the same row."
|
|
5048
5275
|
},
|
|
5049
5276
|
{
|
|
5050
5277
|
"kind": "function",
|
|
@@ -5081,6 +5308,24 @@
|
|
|
5081
5308
|
}
|
|
5082
5309
|
],
|
|
5083
5310
|
"description": "Display date for a history row (e.g. \"Jun 14, 2026\")."
|
|
5311
|
+
},
|
|
5312
|
+
{
|
|
5313
|
+
"kind": "function",
|
|
5314
|
+
"name": "isCostSessionRecord",
|
|
5315
|
+
"return": {
|
|
5316
|
+
"type": {
|
|
5317
|
+
"text": "value is CostSessionRecord"
|
|
5318
|
+
}
|
|
5319
|
+
},
|
|
5320
|
+
"parameters": [
|
|
5321
|
+
{
|
|
5322
|
+
"name": "value",
|
|
5323
|
+
"type": {
|
|
5324
|
+
"text": "unknown"
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
],
|
|
5328
|
+
"description": "Whether a value is a usable row. Exported because rows arrive by two routes and BOTH need it:\n`loadCostSessionHistory` filters the `localStorage` path here, and the assistant filters what a\nhost provider returns. A row that skips this check is not merely ignored downstream — a\nhalf-shaped `usage` sums to `NaN` through every total, and a missing `updatedAt` throws inside\nthe sort that orders the list, during render."
|
|
5084
5329
|
}
|
|
5085
5330
|
],
|
|
5086
5331
|
"exports": [
|
|
@@ -5110,17 +5355,25 @@
|
|
|
5110
5355
|
},
|
|
5111
5356
|
{
|
|
5112
5357
|
"kind": "js",
|
|
5113
|
-
"name": "
|
|
5358
|
+
"name": "upsertRecord",
|
|
5114
5359
|
"declaration": {
|
|
5115
|
-
"name": "
|
|
5360
|
+
"name": "upsertRecord",
|
|
5116
5361
|
"module": "src/utils/cost-session-history.ts"
|
|
5117
5362
|
}
|
|
5118
5363
|
},
|
|
5119
5364
|
{
|
|
5120
5365
|
"kind": "js",
|
|
5121
|
-
"name": "
|
|
5366
|
+
"name": "sortRecordsByRecency",
|
|
5122
5367
|
"declaration": {
|
|
5123
|
-
"name": "
|
|
5368
|
+
"name": "sortRecordsByRecency",
|
|
5369
|
+
"module": "src/utils/cost-session-history.ts"
|
|
5370
|
+
}
|
|
5371
|
+
},
|
|
5372
|
+
{
|
|
5373
|
+
"kind": "js",
|
|
5374
|
+
"name": "resolveBankedUsage",
|
|
5375
|
+
"declaration": {
|
|
5376
|
+
"name": "resolveBankedUsage",
|
|
5124
5377
|
"module": "src/utils/cost-session-history.ts"
|
|
5125
5378
|
}
|
|
5126
5379
|
},
|
|
@@ -5139,6 +5392,14 @@
|
|
|
5139
5392
|
"name": "formatCostSessionDate",
|
|
5140
5393
|
"module": "src/utils/cost-session-history.ts"
|
|
5141
5394
|
}
|
|
5395
|
+
},
|
|
5396
|
+
{
|
|
5397
|
+
"kind": "js",
|
|
5398
|
+
"name": "isCostSessionRecord",
|
|
5399
|
+
"declaration": {
|
|
5400
|
+
"name": "isCostSessionRecord",
|
|
5401
|
+
"module": "src/utils/cost-session-history.ts"
|
|
5402
|
+
}
|
|
5142
5403
|
}
|
|
5143
5404
|
]
|
|
5144
5405
|
},
|
|
@@ -5733,6 +5994,12 @@
|
|
|
5733
5994
|
}
|
|
5734
5995
|
]
|
|
5735
5996
|
},
|
|
5997
|
+
{
|
|
5998
|
+
"kind": "javascript-module",
|
|
5999
|
+
"path": "src/utils/sum-usage.ts",
|
|
6000
|
+
"declarations": [],
|
|
6001
|
+
"exports": []
|
|
6002
|
+
},
|
|
5736
6003
|
{
|
|
5737
6004
|
"kind": "javascript-module",
|
|
5738
6005
|
"path": "src/utils/tool-fold.ts",
|
|
@@ -9397,7 +9664,7 @@
|
|
|
9397
9664
|
{
|
|
9398
9665
|
"kind": "variable",
|
|
9399
9666
|
"name": "settingsModalStyles",
|
|
9400
|
-
"default": "css`\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .settings-modal {\n --ai-tier-badge-bg-mix: 14%;\n --ai-tier-badge-text-lighten: 18%;\n }\n\n .settings-modal::part(dialog) {\n max-width: 620px;\n width: calc(100vw - 48px);\n max-height: calc(100vh - 48px);\n padding: 0;\n }\n\n .settings-modal::part(content) {\n display: flex;\n flex-direction: column;\n min-height: 0;\n padding: 0;\n width: 100%;\n }\n\n .settings-modal::part(top) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal::part(bottom) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-top: 1px solid var(--neutral-stroke-rest);\n border-bottom-left-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n border-bottom-right-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n background-color: var(--neutral-layer-2);\n }\n\n .settings-modal-header {\n display: flex;\n align-items: center;\n min-width: 0;\n }\n\n .settings-modal-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 2px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-content {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n }\n\n .settings-modal-tabs::part(tablist) {\n border-bottom: 0;\n padding: 0 calc(var(--design-unit) * 5px);\n column-gap: calc(var(--design-unit) * 5px);\n }\n\n .settings-modal-tabs::part(activeIndicator) {\n height: 2px;\n }\n\n .settings-modal-tab {\n padding: 0;\n font-weight: 600;\n gap: 6px;\n }\n\n .settings-modal-tab[aria-selected='true'] {\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-tabs::part(tabpanel) {\n border-top: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-tab-panel {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n padding: 0;\n border-radius: 0;\n }\n\n .settings-modal-tab-panel[hidden] {\n display: none !important;\n }\n\n .settings-modal-body {\n overflow-y: auto;\n padding: 0 calc(var(--design-unit) * 5px) calc(var(--design-unit) * 2px);\n max-height: min(60vh, 520px);\n }\n\n .settings-modal-section {\n padding: calc(var(--design-unit) * 5px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-section:last-child {\n border-bottom: none;\n }\n\n .settings-modal-section-title {\n margin: 0 0 calc(var(--design-unit) * 3px);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--neutral-foreground-hint);\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-modal-section-note {\n margin: calc(var(--design-unit) * -2px) 0 calc(var(--design-unit) * 3px);\n font-size: 12px;\n line-height: 1.5;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-modal-section-body {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-toggle-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 3px);\n padding: 0 0 calc(var(--design-unit) * 4px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-toggle-row:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-row.is-stacked {\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-list {\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.2px);\n padding: calc(var(--design-unit) * 2px);\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-list .settings-toggle-row {\n border: 0;\n }\n\n .settings-animation-category {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-animation-category-label {\n font-weight: 600;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-toggle-row + .settings-animation-category-label {\n border-top: 1px solid var(--neutral-stroke-rest);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-option {\n padding: 0;\n margin: 0;\n }\n\n .settings-animation-option:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-toggle-text {\n flex: 1;\n min-width: 0;\n }\n\n .settings-toggle-title {\n font-size: 14px;\n font-weight: 500;\n color: var(--neutral-foreground-rest);\n }\n\n .settings-toggle-desc {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.5;\n max-width: 92%;\n }\n\n .settings-toggle-control {\n flex: 0 0 auto;\n min-width: 0;\n }\n\n .settings-toggle-control-wide {\n width: 100%;\n max-width: 280px;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-desc {\n max-width: none;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-control-wide {\n width: 100%;\n max-width: none;\n }\n\n .settings-app-toggle-block {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-hint {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.4;\n padding-left: calc(var(--design-unit) * 1px);\n font-style: italic;\n }\n\n .settings-context-header {\n display: flex;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 2px);\n font-size: 12px;\n }\n\n .settings-context-label {\n color: var(--neutral-foreground-hint);\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n }\n\n .settings-context-tokens {\n color: var(--neutral-foreground-rest);\n }\n\n .settings-current-build {\n background: var(--neutral-layer-2);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n margin: calc(var(--design-unit) * 3px) 0;\n }\n\n .settings-current-build-top {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-live-badge {\n display: inline-flex;\n align-items: center;\n gap: calc(var(--design-unit) * 1px);\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--accent-fill-rest);\n }\n\n .settings-live-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--accent-fill-rest);\n animation: settings-live-pulse 1.6s ease-out infinite;\n }\n\n @keyframes settings-live-pulse {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-fill-rest) 55%, transparent);\n }\n\n 70% {\n box-shadow: 0 0 0 7px transparent;\n }\n\n 100% {\n box-shadow: 0 0 0 0 transparent;\n }\n }\n\n .settings-current-build-name {\n font-size: 14px;\n font-weight: 600;\n }\n\n .settings-current-build-stats {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 3px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-build-stat {\n flex: 1;\n min-width: 0;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1px);\n padding: calc(var(--design-unit) * 3px);\n }\n\n .settings-build-stat-label {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-build-stat-value {\n font-size: 22px;\n font-weight: 700;\n margin-top: calc(var(--design-unit) * 1px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-build-stat-value .prefix,\n .settings-build-stat-value .suffix {\n color: var(--accent-fill-rest);\n }\n\n .settings-model-tags {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-model-tag {\n font-size: 11px;\n font-weight: 600;\n padding: 3px 9px;\n border-radius: 20px;\n font-family: ui-monospace, monospace;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-model-tag,\n .settings-cost-history-icon {\n --model-tag-color: var(--neutral-foreground-rest);\n\n background: color-mix(\n in srgb,\n var(--model-tag-color) var(--ai-tier-badge-bg-mix),\n var(--neutral-layer-1)\n );\n border-color: color-mix(in srgb, var(--model-tag-color) 20%, var(--neutral-layer-1));\n color: color-mix(in srgb, var(--model-tag-color), white var(--ai-tier-badge-text-lighten));\n }\n\n .settings-cost-history-title {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--neutral-foreground-hint);\n font-weight: 600;\n margin: calc(var(--design-unit) * 4px) 0 calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n border-radius: calc(var(--control-corner-radius) * 1px);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 700;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-cost-history-row {\n display: flex;\n flex-direction: column;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n }\n\n .settings-cost-history-row-content {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-row-content + .settings-cost-history-row-content {\n border-top: 1px solid var(--neutral-stroke-rest);\n margin-top: calc(var(--design-unit) * 2px);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-row-body {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n gap: calc(var(--design-unit) * 2px);\n justify-content: space-between;\n }\n\n .settings-cost-history-row-right {\n align-items: flex-end;\n }\n\n .settings-cost-history-row-cost,\n .settings-cost-history-row-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--neutral-foreground-rest);\n min-width: 0;\n }\n\n .settings-cost-history-row-metrics {\n display: flex;\n flex-direction: column;\n flex: 0 0 auto;\n font-size: 13px;\n font-family: ui-monospace, monospace;\n color: var(--neutral-foreground-rest);\n white-space: nowrap;\n }\n\n .settings-cost-history-row-tokens,\n .settings-cost-history-row-date {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete {\n flex: 0 0 auto;\n margin-top: -2px;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete:hover {\n color: var(--error-color);\n }\n\n .settings-cost-history ::slotted(*) {\n display: block;\n }\n\n .settings-modal-footer {\n width: 100%;\n }\n\n .settings-done-button {\n margin-left: auto;\n }\n\n .settings-modal-footer-actions {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n width: 100%;\n }\n\n .settings-disclaimer {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n max-width: 60%;\n line-height: 1.4;\n }\n\n /*\n * Inline spinner shown in the \"Download agent log\" button while the log builds\n * (GENC-1351 §5.8) — the lifetime-stitch path is an async provider round-trip.\n * Sized to the button label and tinted with currentColor so it tracks the\n * button's text colour in every appearance / theme.\n */\n [part='download-button'] .download-spinner {\n display: inline-block;\n width: 12px;\n height: 12px;\n margin-right: calc(var(--design-unit) * 1px);\n vertical-align: -1px;\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: download-spin 0.6s linear infinite;\n }\n\n @keyframes download-spin {\n to {\n transform: rotate(360deg);\n }\n }\n`",
|
|
9667
|
+
"default": "css`\n /*\n * Section headings come from the shared fragment the package also exports, so a host titling\n * its own slotted section applies the same rule rather than a copy of it.\n */\n ${assistantSettingsSectionTitleStyles}\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .settings-modal {\n --ai-tier-badge-bg-mix: 14%;\n --ai-tier-badge-text-lighten: 18%;\n }\n\n .settings-modal::part(dialog) {\n max-width: 620px;\n width: calc(100vw - 48px);\n max-height: calc(100vh - 48px);\n padding: 0;\n }\n\n .settings-modal::part(content) {\n display: flex;\n flex-direction: column;\n min-height: 0;\n padding: 0;\n width: 100%;\n }\n\n .settings-modal::part(top) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal::part(bottom) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-top: 1px solid var(--neutral-stroke-rest);\n border-bottom-left-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n border-bottom-right-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n background-color: var(--neutral-layer-2);\n }\n\n .settings-modal-header {\n display: flex;\n align-items: center;\n min-width: 0;\n }\n\n .settings-modal-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 2px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-content {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n }\n\n .settings-modal-tabs::part(tablist) {\n border-bottom: 0;\n padding: 0 calc(var(--design-unit) * 5px);\n column-gap: calc(var(--design-unit) * 5px);\n }\n\n .settings-modal-tabs::part(activeIndicator) {\n height: 2px;\n }\n\n .settings-modal-tab {\n padding: 0;\n font-weight: 600;\n gap: 6px;\n }\n\n .settings-modal-tab[aria-selected='true'] {\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-tabs::part(tabpanel) {\n border-top: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-tab-panel {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n padding: 0;\n border-radius: 0;\n }\n\n .settings-modal-tab-panel[hidden] {\n display: none !important;\n }\n\n .settings-modal-body {\n overflow-y: auto;\n padding: 0 calc(var(--design-unit) * 5px) calc(var(--design-unit) * 2px);\n max-height: min(60vh, 520px);\n }\n\n .settings-modal-section {\n padding: calc(var(--design-unit) * 5px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-section:last-child {\n border-bottom: none;\n }\n\n /*\n * Only the spacing. Everything else about this heading comes from the exported fragment\n * above, which the assistant and its hosts share; margin is deliberately not in there,\n * since the gap below a heading depends on the layout around it.\n */\n .ai-settings-section-title {\n margin: 0 0 calc(var(--design-unit) * 3px);\n }\n\n .settings-modal-section-note {\n margin: calc(var(--design-unit) * -2px) 0 calc(var(--design-unit) * 3px);\n font-size: 12px;\n line-height: 1.5;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-modal-section-body {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-toggle-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 3px);\n padding: 0 0 calc(var(--design-unit) * 4px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-toggle-row:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-row.is-stacked {\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n }\n\n /*\n * For rows whose control is a button rather than a switch. The base row top-aligns, which suits\n * a switch (it lands level with the title), but a button is tall enough that top-aligning leaves\n * it above the title-plus-description block it acts on.\n */\n .settings-toggle-row.is-centered {\n align-items: center;\n }\n\n .settings-animation-list {\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.2px);\n padding: calc(var(--design-unit) * 2px);\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-list .settings-toggle-row {\n border: 0;\n }\n\n .settings-animation-category {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-animation-category-label {\n font-weight: 600;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-toggle-row + .settings-animation-category-label {\n border-top: 1px solid var(--neutral-stroke-rest);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-option {\n padding: 0;\n margin: 0;\n }\n\n .settings-animation-option:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-toggle-text {\n flex: 1;\n min-width: 0;\n }\n\n .settings-toggle-title {\n font-size: 14px;\n font-weight: 500;\n color: var(--neutral-foreground-rest);\n }\n\n .settings-toggle-desc {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.5;\n max-width: 92%;\n }\n\n .settings-toggle-control {\n flex: 0 0 auto;\n min-width: 0;\n }\n\n .settings-toggle-control-wide {\n width: 100%;\n max-width: 280px;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-desc {\n max-width: none;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-control-wide {\n width: 100%;\n max-width: none;\n }\n\n /*\n * Sub-heading over the provider toggle group. Unlike the section headings it sits INSIDE\n * .settings-modal-section-body, so the parent's 4-unit flex gap already separates it from the\n * first toggle — and flex does not collapse margins, so the shared rule's 3-unit margin-bottom\n * would add to that gap rather than replace it, leaving the label adrift from the rows it\n * names. Zero the margin and pull back 2 of the 4 gap units, expressed in the same token so it\n * tracks if that gap changes.\n */\n .settings-app-toggles-title {\n margin: 0 0 calc(var(--design-unit) * -2px);\n }\n\n .settings-app-toggle-block {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-hint {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.4;\n padding-left: calc(var(--design-unit) * 1px);\n font-style: italic;\n }\n\n .settings-context-header {\n display: flex;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 2px);\n font-size: 12px;\n }\n\n .settings-context-label {\n color: var(--neutral-foreground-hint);\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n }\n\n .settings-context-tokens {\n color: var(--neutral-foreground-rest);\n }\n\n .settings-current-build,\n .settings-lifetime-usage {\n background: var(--neutral-layer-2);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n margin: calc(var(--design-unit) * 3px) 0;\n }\n\n /*\n * Deliberately quieter than the live block: no pulsing badge and no accent, so the\n * eye still lands on what is being built now. It leads only because it is the wider\n * figure, not because it is the more urgent one.\n */\n .settings-lifetime-usage-top {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-lifetime-label {\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-current-build-top {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n /*\n * Optically centring these 10px caps against the 14px project name beside them. Flex offers\n * only two alignments and neither lands it, because a box centre and a text baseline are\n * different lines:\n * - align-items: center sits the badge baseline about 1.2px high\n * - align-items: baseline matches baselines, but the caps then read as bottom-aligned\n * against the name's ascenders, so the badge looks low\n * The eye wants the midpoint, so: centre the boxes, then translate down by the ~1px of\n * overshoot. This is a nudge, but it corrects a fixed relationship between two font sizes\n * rather than some neighbouring element's layout, so unlike a margin tuned to a sibling it\n * cannot rot when that sibling changes. In em, so it tracks the badge text.\n *\n * display: block, not inline-flex: a flex container has no text baseline of its own and\n * synthesizes one from its first child, which here is the dot, an empty span. As a block box\n * the badge takes the baseline of its first line, i.e. the text.\n */\n .settings-live-badge {\n display: block;\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--accent-fill-rest);\n white-space: nowrap;\n transform: translateY(0.1em);\n }\n\n .settings-live-dot {\n display: inline-block;\n width: 7px;\n height: 7px;\n\n /* Replaces the flex gap the badge no longer has. */\n margin-right: calc(var(--design-unit) * 1px);\n\n /* Centres the dot on the text beside it without disturbing that text's baseline. */\n vertical-align: middle;\n border-radius: 50%;\n background: var(--accent-fill-rest);\n animation: settings-live-pulse 1.6s ease-out infinite;\n }\n\n @keyframes settings-live-pulse {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-fill-rest) 55%, transparent);\n }\n\n 70% {\n box-shadow: 0 0 0 7px transparent;\n }\n\n 100% {\n box-shadow: 0 0 0 0 transparent;\n }\n }\n\n .settings-current-build-name {\n font-size: 14px;\n line-height: 1;\n font-weight: 600;\n }\n\n /*\n * Cost and the token buckets side by side: one number in its own box, the four buckets\n * sharing the box next to it. Stretch, so the two are the same height however tall the 22px\n * cost value makes the left one.\n */\n .settings-usage-row {\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n gap: calc(var(--design-unit) * 3px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-usage-row-tokens {\n display: flex;\n flex: 1 1 auto;\n min-width: 0;\n }\n\n .settings-build-stat {\n min-width: 0;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1px);\n padding: calc(var(--design-unit) * 3px);\n }\n\n .settings-build-stat-label {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-build-stat-value {\n font-size: 22px;\n font-weight: 700;\n margin-top: calc(var(--design-unit) * 1px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-build-stat-value .prefix,\n .settings-build-stat-value .suffix {\n color: var(--accent-fill-rest);\n }\n\n /*\n * Per-bucket token split, beneath the headline cost. Small paired label/value cells\n * rather than four more stat cards: they are the detail behind the cost above, so they\n * have to read as subordinate to it rather than compete with it. The four buckets ARE\n * the token figure now — there is no merged total anywhere for them to break down.\n */\n .settings-token-breakdown {\n display: flex;\n flex-direction: column;\n\n /*\n * Tight against the buckets below. One design unit rather than two, because each bucket\n * separates its own label from its value by only 2px — at two units the title sat four\n * times further from the bucket labels than they sit from their own numbers, which read\n * as a gap between unrelated blocks instead of a heading over its content.\n */\n gap: calc(var(--design-unit) * 1px);\n margin-bottom: calc(var(--design-unit) * 3px);\n padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1px);\n }\n\n /*\n * Titles the box, because nothing else in it named the unit. Same styling and position as\n * the cost box's label, so two boxes sitting side by side are titled the same way; the\n * hierarchy against the bucket labels comes from position, not a second type size.\n */\n .settings-token-breakdown-label {\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-token-buckets {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-token-bucket {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 2px;\n }\n\n .settings-token-bucket-label {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-token-bucket-value {\n font-size: 13px;\n font-weight: 600;\n color: var(--neutral-foreground-rest);\n }\n\n /*\n * Cache reads bill at a fraction of the uncached input rate, and in an agentic\n * session they are usually the largest bucket by a wide margin. Muted so the biggest\n * number on the row doesn't read as the dominant cost driver when it is the cheapest.\n */\n .settings-token-bucket.is-discounted .settings-token-bucket-value {\n font-weight: 500;\n color: var(--neutral-foreground-hint);\n }\n\n /*\n * Usage-row overrides. Placed after the base rules they refine rather than up beside\n * .settings-usage-row itself, because stylelint's no-descending-specificity rejects a\n * compound selector appearing before the plain one it overrides.\n */\n\n /* Sized by its content; the buckets take the rest, since four cells need the width. */\n .settings-usage-row > .settings-build-stat {\n flex: 0 1 auto;\n }\n\n .settings-usage-row .settings-token-breakdown {\n flex: 1 1 auto;\n\n /* The row owns the spacing now; this centres the column against the taller cost box. */\n justify-content: center;\n margin-bottom: 0;\n }\n\n /* Equal columns, so the four buckets line up as a grid rather than bunching left. */\n .settings-usage-row .settings-token-bucket {\n flex: 1 1 0;\n }\n\n .settings-model-tags {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-model-tag {\n font-size: 11px;\n font-weight: 600;\n padding: 3px 9px;\n border-radius: 20px;\n font-family: ui-monospace, monospace;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-model-tag,\n .settings-cost-history-icon {\n --model-tag-color: var(--neutral-foreground-rest);\n\n background: color-mix(\n in srgb,\n var(--model-tag-color) var(--ai-tier-badge-bg-mix),\n var(--neutral-layer-1)\n );\n border-color: color-mix(in srgb, var(--model-tag-color) 20%, var(--neutral-layer-1));\n color: color-mix(in srgb, var(--model-tag-color), white var(--ai-tier-badge-text-lighten));\n }\n\n .settings-cost-history-title {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--neutral-foreground-hint);\n font-weight: 600;\n margin: calc(var(--design-unit) * 4px) 0 calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n border-radius: calc(var(--control-corner-radius) * 1px);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 700;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-cost-history-row {\n display: flex;\n flex-direction: column;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n }\n\n .settings-cost-history-row-content {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 2px);\n }\n\n /*\n * Both of these carry a ?hidden binding (showTokens), and both declare display: flex — which\n * silently beat the UA [hidden] { display: none } rule, because author-origin declarations\n * outrank user-agent origin whatever the specificity. Without this, showTokens: false still\n * rendered the full breakdown in the lifetime block, the live block and every history row.\n * Sibling elements that hide correctly do so only by declaring no display at all. Same\n * treatment as .settings-modal-tab-panel[hidden] near the top of this file.\n */\n .settings-usage-row-tokens[hidden],\n .settings-cost-history-row-content[hidden] {\n display: none !important;\n }\n\n /*\n * The buckets inside a history row drop the panel treatment they carry in the summary\n * blocks — the row is already a bordered card, and a second box inside it reads as a\n * nested panel. The adjacent-sibling rule above supplies the separator instead.\n */\n .settings-cost-history-row-breakdown .settings-token-breakdown {\n flex: 1 1 auto;\n margin-bottom: 0;\n padding: 0;\n background: none;\n border: none;\n }\n\n .settings-cost-history-row-content + .settings-cost-history-row-content {\n border-top: 1px solid var(--neutral-stroke-rest);\n margin-top: calc(var(--design-unit) * 2px);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-row-body {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n gap: calc(var(--design-unit) * 2px);\n justify-content: space-between;\n }\n\n /*\n * Holds the cost and the delete button as one centred pair. The button used to be a sibling\n * of the whole row body, kept in place by a -2px nudge tuned for when the metrics column had\n * two lines (cost plus a merged token total). Dropping that second line left the nudge\n * compensating for nothing. Centring the two against each other needs no magic number and\n * cannot drift again if the metrics content changes.\n *\n * NB this rule previously set align-items with no display:flex, so it had no effect at all.\n */\n .settings-cost-history-row-right {\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 1px);\n flex: 0 0 auto;\n }\n\n .settings-cost-history-row-cost,\n .settings-cost-history-row-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--neutral-foreground-rest);\n min-width: 0;\n }\n\n .settings-cost-history-row-metrics {\n display: flex;\n flex-direction: column;\n flex: 0 0 auto;\n font-size: 13px;\n font-family: ui-monospace, monospace;\n color: var(--neutral-foreground-rest);\n white-space: nowrap;\n }\n\n .settings-cost-history-row-date {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete {\n flex: 0 0 auto;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete:hover {\n color: var(--error-color);\n }\n\n .settings-cost-history ::slotted(*) {\n display: block;\n }\n\n .settings-modal-footer {\n width: 100%;\n }\n\n .settings-done-button {\n margin-left: auto;\n }\n\n .settings-modal-footer-actions {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n width: 100%;\n }\n\n .settings-disclaimer {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n max-width: 60%;\n line-height: 1.4;\n }\n\n /*\n * Inline spinner shown in the \"Download agent log\" button while the log builds\n * (GENC-1351 §5.8) — the lifetime-stitch path is an async provider round-trip.\n * Sized to the button label and tinted with currentColor so it tracks the\n * button's text colour in every appearance / theme.\n */\n [part='download-button'] .download-spinner {\n display: inline-block;\n width: 12px;\n height: 12px;\n margin-right: calc(var(--design-unit) * 1px);\n vertical-align: -1px;\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: download-spin 0.6s linear infinite;\n }\n\n @keyframes download-spin {\n to {\n transform: rotate(360deg);\n }\n }\n`",
|
|
9401
9668
|
"description": "Styles for the assistant settings modal (tabs, sections, toggles, cost summary)."
|
|
9402
9669
|
}
|
|
9403
9670
|
],
|
|
@@ -9432,7 +9699,7 @@
|
|
|
9432
9699
|
}
|
|
9433
9700
|
}
|
|
9434
9701
|
],
|
|
9435
|
-
"description": "Modal shell for assistant settings —
|
|
9702
|
+
"description": "Modal shell for assistant settings — Configuration and Usage tabs, section slots for\napp-specific UI, and built-in chat-bot toggles."
|
|
9436
9703
|
}
|
|
9437
9704
|
],
|
|
9438
9705
|
"exports": [
|