@genesislcap/ai-assistant 15.10.6 → 15.12.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.
Files changed (70) hide show
  1. package/dist/ai-assistant.api.json +226 -0
  2. package/dist/ai-assistant.d.ts +100 -3
  3. package/dist/chat-driver.cjs +540 -117
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +524 -116
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +439 -66
  8. package/dist/dts/chat-driver-node.d.ts +5 -2
  9. package/dist/dts/chat-driver-node.d.ts.map +1 -1
  10. package/dist/dts/components/chat-driver/chat-driver.d.ts +20 -3
  11. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  12. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +2 -0
  13. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +1 -0
  14. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +2 -0
  15. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +1 -0
  16. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  17. package/dist/dts/components/settings-modal/settings-modal.template.d.ts +1 -1
  18. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  19. package/dist/dts/config/config.d.ts +39 -2
  20. package/dist/dts/config/config.d.ts.map +1 -1
  21. package/dist/dts/config/define-stateful-agent.d.ts +15 -1
  22. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  23. package/dist/dts/main/main.d.ts +25 -0
  24. package/dist/dts/main/main.d.ts.map +1 -1
  25. package/dist/dts/main/main.template.d.ts.map +1 -1
  26. package/dist/dts/react.d.ts +9 -8
  27. package/dist/dts/utils/strip-agent-handlers.d.ts +1 -1
  28. package/dist/dts/utils/sum-usage.d.ts +37 -4
  29. package/dist/dts/utils/sum-usage.d.ts.map +1 -1
  30. package/dist/dts/utils/usage-rows.d.ts +102 -0
  31. package/dist/dts/utils/usage-rows.d.ts.map +1 -0
  32. package/dist/dts/utils/usage-rows.test.d.ts +2 -0
  33. package/dist/dts/utils/usage-rows.test.d.ts.map +1 -0
  34. package/dist/esm/chat-driver-node.js +36 -1
  35. package/dist/esm/components/chat-driver/chat-driver.js +73 -10
  36. package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +137 -0
  37. package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +200 -0
  38. package/dist/esm/components/settings-modal/settings-modal.styles.js +22 -0
  39. package/dist/esm/components/settings-modal/settings-modal.template.js +25 -4
  40. package/dist/esm/components/settings-modal/settings-modal.template.test.js +1 -1
  41. package/dist/esm/config/define-stateful-agent.js +11 -0
  42. package/dist/esm/main/blocked-state.test.js +1 -0
  43. package/dist/esm/main/budget-meter.test.js +42 -0
  44. package/dist/esm/main/main.js +49 -0
  45. package/dist/esm/main/main.template.js +20 -1
  46. package/dist/esm/utils/strip-agent-handlers.js +1 -1
  47. package/dist/esm/utils/sum-usage.js +37 -4
  48. package/dist/esm/utils/usage-rows.js +90 -0
  49. package/dist/esm/utils/usage-rows.test.js +189 -0
  50. package/dist/react.cjs +11 -6
  51. package/dist/react.mjs +10 -5
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +17 -17
  54. package/src/chat-driver-node.ts +58 -0
  55. package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +185 -0
  56. package/src/components/chat-driver/chat-driver.trace-capture.test.ts +251 -0
  57. package/src/components/chat-driver/chat-driver.ts +90 -10
  58. package/src/components/settings-modal/settings-modal.styles.ts +22 -0
  59. package/src/components/settings-modal/settings-modal.template.test.ts +1 -1
  60. package/src/components/settings-modal/settings-modal.template.ts +27 -2
  61. package/src/config/config.ts +50 -1
  62. package/src/config/define-stateful-agent.ts +37 -0
  63. package/src/main/blocked-state.test.ts +1 -0
  64. package/src/main/budget-meter.test.ts +50 -0
  65. package/src/main/main.template.ts +19 -1
  66. package/src/main/main.ts +47 -0
  67. package/src/utils/strip-agent-handlers.ts +1 -1
  68. package/src/utils/sum-usage.ts +37 -4
  69. package/src/utils/usage-rows.test.ts +237 -0
  70. package/src/utils/usage-rows.ts +187 -0
@@ -127,6 +127,94 @@
127
127
  "package": "@genesislcap/foundation-ai"
128
128
  }
129
129
  },
130
+ {
131
+ "kind": "js",
132
+ "name": "ANTHROPIC_CACHE_READ_MULTIPLIER",
133
+ "declaration": {
134
+ "name": "ANTHROPIC_CACHE_READ_MULTIPLIER",
135
+ "package": "@genesislcap/foundation-ai"
136
+ }
137
+ },
138
+ {
139
+ "kind": "js",
140
+ "name": "ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER",
141
+ "declaration": {
142
+ "name": "ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER",
143
+ "package": "@genesislcap/foundation-ai"
144
+ }
145
+ },
146
+ {
147
+ "kind": "js",
148
+ "name": "ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER",
149
+ "declaration": {
150
+ "name": "ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER",
151
+ "package": "@genesislcap/foundation-ai"
152
+ }
153
+ },
154
+ {
155
+ "kind": "js",
156
+ "name": "anthropicRatesFor",
157
+ "declaration": {
158
+ "name": "anthropicRatesFor",
159
+ "package": "@genesislcap/foundation-ai"
160
+ }
161
+ },
162
+ {
163
+ "kind": "js",
164
+ "name": "anthropicTokenCost",
165
+ "declaration": {
166
+ "name": "anthropicTokenCost",
167
+ "package": "@genesislcap/foundation-ai"
168
+ }
169
+ },
170
+ {
171
+ "kind": "js",
172
+ "name": "GEMINI_CACHED_INPUT_MULTIPLIER",
173
+ "declaration": {
174
+ "name": "GEMINI_CACHED_INPUT_MULTIPLIER",
175
+ "package": "@genesislcap/foundation-ai"
176
+ }
177
+ },
178
+ {
179
+ "kind": "js",
180
+ "name": "GEMINI_LONG_CONTEXT_THRESHOLD",
181
+ "declaration": {
182
+ "name": "GEMINI_LONG_CONTEXT_THRESHOLD",
183
+ "package": "@genesislcap/foundation-ai"
184
+ }
185
+ },
186
+ {
187
+ "kind": "js",
188
+ "name": "geminiRatesFor",
189
+ "declaration": {
190
+ "name": "geminiRatesFor",
191
+ "package": "@genesislcap/foundation-ai"
192
+ }
193
+ },
194
+ {
195
+ "kind": "js",
196
+ "name": "geminiTokenCost",
197
+ "declaration": {
198
+ "name": "geminiTokenCost",
199
+ "package": "@genesislcap/foundation-ai"
200
+ }
201
+ },
202
+ {
203
+ "kind": "js",
204
+ "name": "vendorOfModel",
205
+ "declaration": {
206
+ "name": "vendorOfModel",
207
+ "package": "@genesislcap/foundation-ai"
208
+ }
209
+ },
210
+ {
211
+ "kind": "js",
212
+ "name": "AggregateUsage",
213
+ "declaration": {
214
+ "name": "AggregateUsage",
215
+ "package": "@genesislcap/foundation-ai"
216
+ }
217
+ },
130
218
  {
131
219
  "kind": "js",
132
220
  "name": "AIProvider",
@@ -183,6 +271,14 @@
183
271
  "package": "@genesislcap/foundation-ai"
184
272
  }
185
273
  },
274
+ {
275
+ "kind": "js",
276
+ "name": "ChatThinkingPolicy",
277
+ "declaration": {
278
+ "name": "ChatThinkingPolicy",
279
+ "package": "@genesislcap/foundation-ai"
280
+ }
281
+ },
186
282
  {
187
283
  "kind": "js",
188
284
  "name": "ChatToolChoice",
@@ -215,6 +311,94 @@
215
311
  "package": "@genesislcap/foundation-ai"
216
312
  }
217
313
  },
314
+ {
315
+ "kind": "js",
316
+ "name": "AnthropicUsageRecord",
317
+ "declaration": {
318
+ "name": "AnthropicUsageRecord",
319
+ "package": "@genesislcap/foundation-ai"
320
+ }
321
+ },
322
+ {
323
+ "kind": "js",
324
+ "name": "GeminiUsageRecord",
325
+ "declaration": {
326
+ "name": "GeminiUsageRecord",
327
+ "package": "@genesislcap/foundation-ai"
328
+ }
329
+ },
330
+ {
331
+ "kind": "js",
332
+ "name": "TokenCost",
333
+ "declaration": {
334
+ "name": "TokenCost",
335
+ "package": "@genesislcap/foundation-ai"
336
+ }
337
+ },
338
+ {
339
+ "kind": "js",
340
+ "name": "TokenCostBreakdown",
341
+ "declaration": {
342
+ "name": "TokenCostBreakdown",
343
+ "package": "@genesislcap/foundation-ai"
344
+ }
345
+ },
346
+ {
347
+ "kind": "js",
348
+ "name": "TokenRates",
349
+ "declaration": {
350
+ "name": "TokenRates",
351
+ "package": "@genesislcap/foundation-ai"
352
+ }
353
+ },
354
+ {
355
+ "kind": "js",
356
+ "name": "addUsage",
357
+ "declaration": {
358
+ "name": "addUsage",
359
+ "module": "./utils/sum-usage"
360
+ }
361
+ },
362
+ {
363
+ "kind": "js",
364
+ "name": "emptyUsage",
365
+ "declaration": {
366
+ "name": "emptyUsage",
367
+ "module": "./utils/sum-usage"
368
+ }
369
+ },
370
+ {
371
+ "kind": "js",
372
+ "name": "sumUsage",
373
+ "declaration": {
374
+ "name": "sumUsage",
375
+ "module": "./utils/sum-usage"
376
+ }
377
+ },
378
+ {
379
+ "kind": "js",
380
+ "name": "totalTokens",
381
+ "declaration": {
382
+ "name": "totalTokens",
383
+ "module": "./utils/sum-usage"
384
+ }
385
+ },
386
+ {
387
+ "kind": "js",
388
+ "name": "usageRows",
389
+ "declaration": {
390
+ "name": "usageRows",
391
+ "module": "./utils/usage-rows"
392
+ }
393
+ },
394
+ {
395
+ "kind": "js",
396
+ "name": "UsageRow",
397
+ "declaration": {
398
+ "name": "UsageRow",
399
+ "module": "./utils/usage-rows"
400
+ }
401
+ },
218
402
  {
219
403
  "kind": "js",
220
404
  "name": "clearSession",
@@ -1241,6 +1425,14 @@
1241
1425
  "module": "src/config/config.ts"
1242
1426
  }
1243
1427
  },
1428
+ {
1429
+ "kind": "js",
1430
+ "name": "ChatThinkingPolicy",
1431
+ "declaration": {
1432
+ "name": "ChatThinkingPolicy",
1433
+ "module": "src/config/config.ts"
1434
+ }
1435
+ },
1244
1436
  {
1245
1437
  "kind": "js",
1246
1438
  "name": "ChatToolChoice",
@@ -1983,6 +2175,34 @@
1983
2175
  ],
1984
2176
  "description": "Feed (or clear) one vendor's AI-spend figures for the settings-modal budget\nmeter — the numeric companion to\nFoundationAiAssistant.setVendorBlocked, and like it a host-driven\nwrite: the element meters nothing itself.\n\nThe meter renders a row for exactly the vendors that currently HAVE\nfigures. An **unlimited** vendor (the budget endpoint reports\n`unlimited: true` / `limitUsd: null`) is expressed by never feeding it —\nthere is deliberately no \"unlimited\" figure shape, because a bar with no\ncap has nothing truthful to fill to. `spentUsd` MAY exceed `budgetUsd`\n(the last allowed call can overshoot the cap): the row's text shows the\nreal figures and only the bar clamps.\n\nReturns `false` — and warns once, not per call — instead of throwing when\n`vendor` is not a budgeted vendor (`BUDGETED_VENDORS`, the proxy's own\nmetered list) or either figure is not a finite number `>= 0`. Invalid input\nchanges no state. Idempotent: re-feeding a vendor's current figures is a\nno-op, so hosts can re-feed unconditionally after every turn.\n\n`false` ALSO means \"not stored, because the element is not ready yet\": a\nfeed that lands before the element has its session store (a host calling\nahead of the first append, or during a pop-out remount) stores nothing and\nsays so, rather than reporting a write that never happened. That case is\ntransient and needs no host handling — the documented per-turn re-feed is\nwhat recovers it — so it is deliberately NOT warned about; the warn-once\nchannel is for garbage input only.\n\n`figures` is nullish-checked at run time, so `undefined` behaves exactly\nlike `null` and CLEARS that vendor's row. Untyped JS hosts feed this\nstraight from `GET /api/budget`, where a per-vendor lookup that misses\nyields `undefined`, not `null` — and \"no figures\" coherently means \"no\nrow\". The `| null` parameter type is unchanged for typed callers; the\nruntime is deliberately the more forgiving of the two.\n\nThe intended feed is the platform budget endpoint:\n\n```ts\n// `vendors` is an OBJECT keyed by vendor id, not an array.\nconst { vendors } = await (await fetch('/api/budget')).json();\nfor (const v of Object.values(vendors)) {\n if (v.unlimited || v.limitUsd == null) {\n assistantEl.setVendorBudget(v.vendor, null); // clear stale figures after a cap is lifted\n continue;\n }\n assistantEl.setVendorBudget(v.vendor, { budgetUsd: v.limitUsd, spentUsd: v.spentUsd });\n}\n```\n\nRe-run it on mount and after each turn. Scope and lifetime follow the rest\nof the session slice (per `stateKey`, survives pop-in/out and \"Clear\" /\n\"New chat\") — except that the figures are NOT persisted to the session\nsnapshot: the server is authoritative and the host re-feeds at boot, so a\npersisted copy could only show stale dollars after a reload."
1985
2177
  },
2178
+ {
2179
+ "kind": "field",
2180
+ "name": "budgetMeterLastFedAt",
2181
+ "type": {
2182
+ "text": "number | null"
2183
+ },
2184
+ "default": "null",
2185
+ "description": "When budget figures last arrived — from the host's FoundationAiAssistant.setVendorBudget\nor the wall latch's own feed — as epoch ms; `null` until the first feed.\n\nELEMENT state, not slice state, on purpose: the reducer is idempotent by\nvalue (an unchanged re-feed must not re-render the meter rows), but \"when\nwas this fetched\" must move on every feed, changed figures or not — a\nrefresh that confirms the same dollars is still a refresh. Only the\ntoolbar label observes this, so a stamp re-renders one span, not the rows."
2186
+ },
2187
+ {
2188
+ "kind": "field",
2189
+ "name": "settingsBudgetUpdatedLabel",
2190
+ "type": {
2191
+ "text": "string | undefined"
2192
+ },
2193
+ "description": "\"Updated HH:MM:SS\" for the meter toolbar, or `undefined` before any feed.",
2194
+ "readonly": true
2195
+ },
2196
+ {
2197
+ "kind": "method",
2198
+ "name": "requestBudgetRefresh",
2199
+ "return": {
2200
+ "type": {
2201
+ "text": "void"
2202
+ }
2203
+ },
2204
+ "description": "Ask the host to re-fetch budget figures now (GENC-1464 refresh affordance).\n\nThe element cannot fetch — the meter is host-fed by design — so this emits\n`budget-refresh-requested` and the host answers with the same fetch that\nalready feeds FoundationAiAssistant.setVendorBudget (Create's handler coalesces in-flight\nfetches, so a spammed button costs one request). Fired by the meter's\nrefresh button and on settings-modal open, so the figures are fresh exactly\nwhen someone is looking at them. Fires unconditionally: a host without a\nlistener simply keeps the per-turn cadence."
2205
+ },
1986
2206
  {
1987
2207
  "kind": "method",
1988
2208
  "name": "isVendorSwept",
@@ -4225,6 +4445,12 @@
4225
4445
  }
4226
4446
  ],
4227
4447
  "events": [
4448
+ {
4449
+ "name": "budget-refresh-requested",
4450
+ "type": {
4451
+ "text": "CustomEvent"
4452
+ }
4453
+ },
4228
4454
  {
4229
4455
  "type": {
4230
4456
  "text": "CustomEvent"
@@ -6278,8 +6504,112 @@
6278
6504
  {
6279
6505
  "kind": "javascript-module",
6280
6506
  "path": "src/utils/sum-usage.ts",
6281
- "declarations": [],
6282
- "exports": []
6507
+ "declarations": [
6508
+ {
6509
+ "kind": "function",
6510
+ "name": "emptyUsage",
6511
+ "return": {
6512
+ "type": {
6513
+ "text": "AggregateUsage"
6514
+ }
6515
+ },
6516
+ "description": "Zeroed AggregateUsage. A factory, not a shared const, because callers\naccumulate into the returned object."
6517
+ },
6518
+ {
6519
+ "kind": "function",
6520
+ "name": "totalTokens",
6521
+ "return": {
6522
+ "type": {
6523
+ "text": "number"
6524
+ }
6525
+ },
6526
+ "parameters": [
6527
+ {
6528
+ "name": "usage",
6529
+ "type": {
6530
+ "text": "AggregateUsage"
6531
+ }
6532
+ }
6533
+ ],
6534
+ "description": "Total tokens processed — the four buckets added up. Safe to sum because the\nbuckets are disjoint (see AggregateUsage); the equivalent sum over\nper-message fields would double-count the prompt.\n\nBear in mind what this number is: because every turn resends the conversation,\na turn's prompt is counted again in the next turn's. It is billed throughput,\nNOT the count of distinct tokens in the conversation, so it grows\nsuper-linearly with turn count and is expected to dwarf the context size."
6535
+ },
6536
+ {
6537
+ "kind": "function",
6538
+ "name": "addUsage",
6539
+ "return": {
6540
+ "type": {
6541
+ "text": "AggregateUsage"
6542
+ }
6543
+ },
6544
+ "parameters": [
6545
+ {
6546
+ "name": "a",
6547
+ "type": {
6548
+ "text": "AggregateUsage"
6549
+ }
6550
+ },
6551
+ {
6552
+ "name": "b",
6553
+ "type": {
6554
+ "text": "AggregateUsage"
6555
+ }
6556
+ }
6557
+ ],
6558
+ "description": "`a + b`, per bucket, as a new object.\n\nUsed to lay a row's banked (pre-transcript) spend over what the live transcript\nproves. Pure rather than in-place because both operands are live state a caller\nmust not mutate."
6559
+ },
6560
+ {
6561
+ "kind": "function",
6562
+ "name": "sumUsage",
6563
+ "return": {
6564
+ "type": {
6565
+ "text": "AggregateUsage"
6566
+ }
6567
+ },
6568
+ "parameters": [
6569
+ {
6570
+ "name": "messages",
6571
+ "type": {
6572
+ "text": "readonly ChatMessage[]"
6573
+ }
6574
+ }
6575
+ ],
6576
+ "description": "Sum cost and per-bucket token usage across a message list.\n\nOne walk produces everything a usage display needs, replacing separate cost and\ntoken passes over the same (potentially deep) transcript. Recurses into each\ntool call's `subAgentTrace`, so LLM work a sub-agent did — possibly on another\nprovider at other rates — lands in the total; the driver embeds a whole\nsub-agent conversation in the parent's tool result, so this is the only place\nthat spend is visible.\n\nSplitting the prompt into buckets is the point: `cost` already has the cache\ndiscount applied, but a single token total cannot show that a session was cheap\nbecause most of its prompt was a cache hit. The three input buckets are derived\nfrom the per-message fields, where `inputTokens` is the whole prompt and the\ncache fields break it down — so uncached input is the remainder, never an\naddition.\n\nAlso counts `compaction.rolledUpUsage`: compaction destroys the messages it\nsummarises, and without their banked usage every total re-derived from the\ntranscript would drop by whatever those turns had already spent.\n\nReturns a zeroed total when no message carries usage — providers that report\nnone (e.g. Chrome built-in) are skipped silently rather than guessed at."
6577
+ }
6578
+ ],
6579
+ "exports": [
6580
+ {
6581
+ "kind": "js",
6582
+ "name": "emptyUsage",
6583
+ "declaration": {
6584
+ "name": "emptyUsage",
6585
+ "module": "src/utils/sum-usage.ts"
6586
+ }
6587
+ },
6588
+ {
6589
+ "kind": "js",
6590
+ "name": "totalTokens",
6591
+ "declaration": {
6592
+ "name": "totalTokens",
6593
+ "module": "src/utils/sum-usage.ts"
6594
+ }
6595
+ },
6596
+ {
6597
+ "kind": "js",
6598
+ "name": "addUsage",
6599
+ "declaration": {
6600
+ "name": "addUsage",
6601
+ "module": "src/utils/sum-usage.ts"
6602
+ }
6603
+ },
6604
+ {
6605
+ "kind": "js",
6606
+ "name": "sumUsage",
6607
+ "declaration": {
6608
+ "name": "sumUsage",
6609
+ "module": "src/utils/sum-usage.ts"
6610
+ }
6611
+ }
6612
+ ]
6283
6613
  },
6284
6614
  {
6285
6615
  "kind": "javascript-module",
@@ -6347,6 +6677,40 @@
6347
6677
  }
6348
6678
  ]
6349
6679
  },
6680
+ {
6681
+ "kind": "javascript-module",
6682
+ "path": "src/utils/usage-rows.ts",
6683
+ "declarations": [
6684
+ {
6685
+ "kind": "function",
6686
+ "name": "usageRows",
6687
+ "return": {
6688
+ "type": {
6689
+ "text": "UsageRow[]"
6690
+ }
6691
+ },
6692
+ "parameters": [
6693
+ {
6694
+ "name": "messages",
6695
+ "type": {
6696
+ "text": "readonly ChatMessage[]"
6697
+ }
6698
+ }
6699
+ ],
6700
+ "description": "Project a transcript into one row per billable unit of spend."
6701
+ }
6702
+ ],
6703
+ "exports": [
6704
+ {
6705
+ "kind": "js",
6706
+ "name": "usageRows",
6707
+ "declaration": {
6708
+ "name": "usageRows",
6709
+ "module": "src/utils/usage-rows.ts"
6710
+ }
6711
+ }
6712
+ ]
6713
+ },
6350
6714
  {
6351
6715
  "kind": "javascript-module",
6352
6716
  "path": "src/utils/with-timeout.ts",
@@ -7680,6 +8044,15 @@
7680
8044
  "privacy": "private",
7681
8045
  "description": "Active agent's prompt-cache policy selector (static value or per-turn resolver).\n`undefined` requests no caching (equivalent to `{ scope: 'default' }`)."
7682
8046
  },
8047
+ {
8048
+ "kind": "field",
8049
+ "name": "activeThinkingPolicyInput",
8050
+ "type": {
8051
+ "text": "ThinkingPolicyInput | undefined"
8052
+ },
8053
+ "privacy": "private",
8054
+ "description": "Active agent's extended-thinking selector (static value or per-turn resolver). `undefined` —\nunset, or returned by the resolver — leaves the model on its own default posture, which is\nneither uniformly on nor off, so an agent that never sets this is unaffected by the option."
8055
+ },
7683
8056
  {
7684
8057
  "kind": "field",
7685
8058
  "name": "activeTailContextInput",
@@ -8586,9 +8959,9 @@
8586
8959
  "name": "traceCapture",
8587
8960
  "optional": true,
8588
8961
  "type": {
8589
- "text": "{ trace?: ChatMessage[] }"
8962
+ "text": "{ traces: ChatMessage[][] }"
8590
8963
  },
8591
- "description": "Optional per-invocation slot. When provided, the trace\nfrom any sub-agent call is written here rather than to shared instance state,\nso parallel tool calls each capture their own trace independently."
8964
+ "description": "Optional per-tool-call accumulator. When provided, every\nsub-agent call's trace is **appended** here rather than written to shared\ninstance state, so parallel tool calls each capture their own traces\nindependently.\n\nDeliberately a list of traces, not one slot. A single handler may call\n`requestSubAgent` more than once — a code-driven scheduler dispatching a\ndependency graph, a retry of a timed-out child, any fan-out helper — and a\nsingle slot kept only the last, so every other child ran, was billed by the\nprovider, and then vanished from history. The loss was silent in the worst\nway: `sumUsage` and `usageRows` both recurse into the trace, so they summed a\ntruncated input and still agreed with each other. Measured at 1 of 7 traces\nkept on a seven-way fan-out, reporting ~2.3x under the real cost, with the\nerror growing as the fan-out widens."
8592
8965
  }
8593
8966
  ],
8594
8967
  "description": "Builds the context object passed to every tool handler call.\nCentralised here so fold shortcut dispatch and the main tool loop use the\nsame context without duplication."
@@ -9132,6 +9505,67 @@
9132
9505
  }
9133
9506
  ]
9134
9507
  },
9508
+ {
9509
+ "kind": "javascript-module",
9510
+ "path": "src/components/chat-markdown/chat-markdown.ts",
9511
+ "declarations": [
9512
+ {
9513
+ "kind": "class",
9514
+ "description": "",
9515
+ "name": "AiChatMarkdown",
9516
+ "members": [
9517
+ {
9518
+ "kind": "field",
9519
+ "name": "content",
9520
+ "type": {
9521
+ "text": "string"
9522
+ },
9523
+ "default": "''",
9524
+ "description": "The markdown string to render."
9525
+ }
9526
+ ],
9527
+ "superclass": {
9528
+ "name": "GenesisElement",
9529
+ "package": "@genesislcap/web-core"
9530
+ },
9531
+ "tagName": "ai-chat-markdown",
9532
+ "customElement": true
9533
+ }
9534
+ ],
9535
+ "exports": [
9536
+ {
9537
+ "kind": "js",
9538
+ "name": "AiChatMarkdown",
9539
+ "declaration": {
9540
+ "name": "AiChatMarkdown",
9541
+ "module": "src/components/chat-markdown/chat-markdown.ts"
9542
+ }
9543
+ },
9544
+ {
9545
+ "kind": "custom-element-definition",
9546
+ "name": "ai-chat-markdown",
9547
+ "declaration": {
9548
+ "name": "AiChatMarkdown",
9549
+ "module": "src/components/chat-markdown/chat-markdown.ts"
9550
+ }
9551
+ }
9552
+ ]
9553
+ },
9554
+ {
9555
+ "kind": "javascript-module",
9556
+ "path": "src/components/chat-markdown/index.ts",
9557
+ "declarations": [],
9558
+ "exports": [
9559
+ {
9560
+ "kind": "js",
9561
+ "name": "*",
9562
+ "declaration": {
9563
+ "name": "*",
9564
+ "package": "./chat-markdown"
9565
+ }
9566
+ }
9567
+ ]
9568
+ },
9135
9569
  {
9136
9570
  "kind": "javascript-module",
9137
9571
  "path": "src/components/orchestrating-driver/index.ts",
@@ -10007,7 +10441,7 @@
10007
10441
  {
10008
10442
  "kind": "variable",
10009
10443
  "name": "settingsModalStyles",
10010
- "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 /*\n * The budget meter (GENC-1464) is the context indicator's money sibling and\n * shares its row anatomy 1:1, so the budget classes JOIN these rules rather\n * than restating them — one place to restyle the header/label/figures shape,\n * and the two indicators cannot drift apart. Design-system tokens only, and\n * unfallbacked: every token here ships a DS default, so a var(--x, y)\n * fallback would be dead code that hides a broken token name (#2411/#2436\n * review rule, pinned by settings-modal.styles.test.ts).\n */\n .settings-context-header,\n .settings-budget-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 .settings-budget-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 .settings-budget-figures {\n color: var(--neutral-foreground-rest);\n }\n\n /*\n * ONE rhythm for every meter row. The outer wrapper holds the context\n * indicator and the budget meter; the inner one holds the budget meter's\n * vendor rows — and both gap by the same 2 units (half the section body's 4)\n * so that visually identical meter rows are evenly spaced whatever mix of\n * them is on screen. Without the wrapper the context row inherits the body's\n * 4-unit gap while the vendor rows keep 2, and three identical-looking rows\n * read as an uneven 4/2/2. The wrapper renders only when at least one meter\n * does — see settingsUsageMetersTemplate for why an always-on one would cost\n * a stray gap.\n */\n .settings-usage-meters,\n .settings-budget-usage {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\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 eye lands\n * on what is being built now — which is why that block leads the tab and this one follows,\n * under the history heading whose rows it totals.\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\n /*\n * min-content (not 0) so a bucket cannot be shrunk narrower than its own unbreakable label.\n * With the label set to nowrap, a zero minimum would overflow it into the next column instead\n * of wrapping — so the container's flex-wrap takes over and moves a bucket to a second row,\n * which is the degradation we want when the modal is dragged narrow.\n */\n min-width: min-content;\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 * One line, always. The rate marker is a separate inline box after collapsible whitespace, so\n * a wrappable label gives it a break opportunity of its own — in a narrow column it orphaned\n * onto line 2, which also pushed that bucket's value a line below the other three and broke\n * the row of figures. nowrap removes every break opportunity in the label, the space before\n * the marker included, so no glue character is needed.\n */\n white-space: nowrap;\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 * The price-direction marker on a cache bucket's label — down for reads (a fraction of the\n * uncached input rate), up for writes (a premium over it). Three tiers, so a bucket with no\n * marker is the reference rate rather than an unlabelled fourth case.\n *\n * Carried by the label rather than the value, which is what this used to do: cache reads are\n * usually the largest bucket by a wide margin in an agentic session, so the value was muted to\n * stop the biggest number reading as the dominant cost driver. That priced the treatment to the\n * wrong thing — a dimmed 0 (every Gemini cache write) looked disabled next to a normal 0, and a\n * rate is a property of the bucket, not of this turn's figure.\n *\n * Sized down and nudged up rather than tinted: the label is already the hint colour, so a\n * dimmer marker would vanish and a brighter one would out-shout the word it qualifies.\n */\n .settings-token-bucket-rate {\n font-size: 8px;\n line-height: 1;\n vertical-align: 0.15em;\n letter-spacing: 0;\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 /*\n * Basis auto, not 0: a zero basis splits the row into four equal columns, so CACHE WRITE is\n * squeezed exactly as hard as INPUT despite being twice the label. Sizing from content gives the\n * long labels the room their text needs and lets the short ones concede it, which is what makes\n * white-space: nowrap above safe — every column starts at least as wide as its own content,\n * then shares the slack. Still grows and shrinks, so the four stay flush edge to edge.\n */\n .settings-usage-row .settings-token-bucket {\n flex: 1 1 auto;\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 /*\n * The heading labels the lifetime card as well as the rows, so the card gives up its own top\n * margin and the gap becomes the heading's alone. Adjacent siblings collapse to the LARGER of\n * the two margins, so leaving both would space the caps 3 units off the block they label while\n * the rows below sit at 2 — reading as though the heading belonged to neither.\n */\n .settings-cost-history-title + .settings-lifetime-usage {\n margin-top: 0;\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 * All of these carry a ?hidden binding, and all declare display: flex — which silently beat the\n * UA [hidden] { display: none } rule, because author-origin declarations outrank user-agent\n * origin whatever the specificity. Without this, showTokens: false still rendered the full\n * breakdown in the lifetime block, the live block and every history row, and an empty row list\n * still occupied its padding under the lifetime total. Sibling elements that hide correctly do\n * so only by declaring no display at all. Same treatment as .settings-modal-tab-panel[hidden]\n * near the top of this file.\n */\n .settings-usage-row-tokens[hidden],\n .settings-cost-history[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`",
10444
+ "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 /*\n * The budget meter (GENC-1464) is the context indicator's money sibling and\n * shares its row anatomy 1:1, so the budget classes JOIN these rules rather\n * than restating them — one place to restyle the header/label/figures shape,\n * and the two indicators cannot drift apart. Design-system tokens only, and\n * unfallbacked: every token here ships a DS default, so a var(--x, y)\n * fallback would be dead code that hides a broken token name (#2411/#2436\n * review rule, pinned by settings-modal.styles.test.ts).\n */\n .settings-context-header,\n .settings-budget-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 .settings-budget-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 .settings-budget-figures {\n color: var(--neutral-foreground-rest);\n }\n\n /* Freshness footer (GENC-1464): refresh button + label under the vendor\n rows, left-aligned — a footnote to the figures above it. The button's\n inline padding is pinned and cancelled by the container margin so the\n ICON GLYPH (not the button box) sits flush with the budget labels'\n left edge, whatever the density tokens say. */\n .settings-budget-toolbar {\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 4px;\n margin-inline-start: -4px;\n }\n\n .settings-budget-refresh::part(control) {\n padding-inline: 4px;\n }\n\n .settings-budget-updated {\n color: var(--neutral-foreground-hint);\n font-size: 11px;\n }\n\n /*\n * ONE rhythm for every meter row. The outer wrapper holds the context\n * indicator and the budget meter; the inner one holds the budget meter's\n * vendor rows — and both gap by the same 2 units (half the section body's 4)\n * so that visually identical meter rows are evenly spaced whatever mix of\n * them is on screen. Without the wrapper the context row inherits the body's\n * 4-unit gap while the vendor rows keep 2, and three identical-looking rows\n * read as an uneven 4/2/2. The wrapper renders only when at least one meter\n * does — see settingsUsageMetersTemplate for why an always-on one would cost\n * a stray gap.\n */\n .settings-usage-meters,\n .settings-budget-usage {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\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 eye lands\n * on what is being built now — which is why that block leads the tab and this one follows,\n * under the history heading whose rows it totals.\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\n /*\n * min-content (not 0) so a bucket cannot be shrunk narrower than its own unbreakable label.\n * With the label set to nowrap, a zero minimum would overflow it into the next column instead\n * of wrapping — so the container's flex-wrap takes over and moves a bucket to a second row,\n * which is the degradation we want when the modal is dragged narrow.\n */\n min-width: min-content;\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 * One line, always. The rate marker is a separate inline box after collapsible whitespace, so\n * a wrappable label gives it a break opportunity of its own — in a narrow column it orphaned\n * onto line 2, which also pushed that bucket's value a line below the other three and broke\n * the row of figures. nowrap removes every break opportunity in the label, the space before\n * the marker included, so no glue character is needed.\n */\n white-space: nowrap;\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 * The price-direction marker on a cache bucket's label — down for reads (a fraction of the\n * uncached input rate), up for writes (a premium over it). Three tiers, so a bucket with no\n * marker is the reference rate rather than an unlabelled fourth case.\n *\n * Carried by the label rather than the value, which is what this used to do: cache reads are\n * usually the largest bucket by a wide margin in an agentic session, so the value was muted to\n * stop the biggest number reading as the dominant cost driver. That priced the treatment to the\n * wrong thing — a dimmed 0 (every Gemini cache write) looked disabled next to a normal 0, and a\n * rate is a property of the bucket, not of this turn's figure.\n *\n * Sized down and nudged up rather than tinted: the label is already the hint colour, so a\n * dimmer marker would vanish and a brighter one would out-shout the word it qualifies.\n */\n .settings-token-bucket-rate {\n font-size: 8px;\n line-height: 1;\n vertical-align: 0.15em;\n letter-spacing: 0;\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 /*\n * Basis auto, not 0: a zero basis splits the row into four equal columns, so CACHE WRITE is\n * squeezed exactly as hard as INPUT despite being twice the label. Sizing from content gives the\n * long labels the room their text needs and lets the short ones concede it, which is what makes\n * white-space: nowrap above safe — every column starts at least as wide as its own content,\n * then shares the slack. Still grows and shrinks, so the four stay flush edge to edge.\n */\n .settings-usage-row .settings-token-bucket {\n flex: 1 1 auto;\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 /*\n * The heading labels the lifetime card as well as the rows, so the card gives up its own top\n * margin and the gap becomes the heading's alone. Adjacent siblings collapse to the LARGER of\n * the two margins, so leaving both would space the caps 3 units off the block they label while\n * the rows below sit at 2 — reading as though the heading belonged to neither.\n */\n .settings-cost-history-title + .settings-lifetime-usage {\n margin-top: 0;\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 * All of these carry a ?hidden binding, and all declare display: flex — which silently beat the\n * UA [hidden] { display: none } rule, because author-origin declarations outrank user-agent\n * origin whatever the specificity. Without this, showTokens: false still rendered the full\n * breakdown in the lifetime block, the live block and every history row, and an empty row list\n * still occupied its padding under the lifetime total. Sibling elements that hide correctly do\n * so only by declaring no display at all. Same treatment as .settings-modal-tab-panel[hidden]\n * near the top of this file.\n */\n .settings-usage-row-tokens[hidden],\n .settings-cost-history[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`",
10011
10445
  "description": "Styles for the assistant settings modal (tabs, sections, toggles, cost summary)."
10012
10446
  }
10013
10447
  ],
@@ -10056,67 +10490,6 @@
10056
10490
  }
10057
10491
  ]
10058
10492
  },
10059
- {
10060
- "kind": "javascript-module",
10061
- "path": "src/components/chat-markdown/chat-markdown.ts",
10062
- "declarations": [
10063
- {
10064
- "kind": "class",
10065
- "description": "",
10066
- "name": "AiChatMarkdown",
10067
- "members": [
10068
- {
10069
- "kind": "field",
10070
- "name": "content",
10071
- "type": {
10072
- "text": "string"
10073
- },
10074
- "default": "''",
10075
- "description": "The markdown string to render."
10076
- }
10077
- ],
10078
- "superclass": {
10079
- "name": "GenesisElement",
10080
- "package": "@genesislcap/web-core"
10081
- },
10082
- "tagName": "ai-chat-markdown",
10083
- "customElement": true
10084
- }
10085
- ],
10086
- "exports": [
10087
- {
10088
- "kind": "js",
10089
- "name": "AiChatMarkdown",
10090
- "declaration": {
10091
- "name": "AiChatMarkdown",
10092
- "module": "src/components/chat-markdown/chat-markdown.ts"
10093
- }
10094
- },
10095
- {
10096
- "kind": "custom-element-definition",
10097
- "name": "ai-chat-markdown",
10098
- "declaration": {
10099
- "name": "AiChatMarkdown",
10100
- "module": "src/components/chat-markdown/chat-markdown.ts"
10101
- }
10102
- }
10103
- ]
10104
- },
10105
- {
10106
- "kind": "javascript-module",
10107
- "path": "src/components/chat-markdown/index.ts",
10108
- "declarations": [],
10109
- "exports": [
10110
- {
10111
- "kind": "js",
10112
- "name": "*",
10113
- "declaration": {
10114
- "name": "*",
10115
- "package": "./chat-markdown"
10116
- }
10117
- }
10118
- ]
10119
- },
10120
10493
  {
10121
10494
  "kind": "javascript-module",
10122
10495
  "path": "src/state/persistence/build-timeline-entries.ts",