@genesislcap/ai-assistant 15.23.2 → 15.24.0-FUI-2603.10

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 (55) hide show
  1. package/dist/ai-assistant.api.json +421 -52
  2. package/dist/ai-assistant.d.ts +239 -694
  3. package/dist/chat-driver.cjs +138 -120
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +138 -120
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +361 -602
  8. package/dist/dts/components/chat-driver/chat-driver.d.ts +22 -11
  9. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  10. package/dist/dts/components/chat-driver/turn-failures.d.ts +96 -0
  11. package/dist/dts/components/chat-driver/turn-failures.d.ts.map +1 -0
  12. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +16 -0
  13. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  14. package/dist/dts/components/settings-modal/index.d.ts +1 -0
  15. package/dist/dts/components/settings-modal/index.d.ts.map +1 -1
  16. package/dist/dts/components/settings-modal/settings-modal.d.ts +215 -0
  17. package/dist/dts/components/settings-modal/settings-modal.d.ts.map +1 -0
  18. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  19. package/dist/dts/main/attachment-controller.d.ts +66 -0
  20. package/dist/dts/main/attachment-controller.d.ts.map +1 -0
  21. package/dist/dts/main/budget-controller.d.ts +368 -0
  22. package/dist/dts/main/budget-controller.d.ts.map +1 -0
  23. package/dist/dts/main/composer-menu-controller.d.ts +113 -0
  24. package/dist/dts/main/composer-menu-controller.d.ts.map +1 -0
  25. package/dist/dts/main/composer-resize-controller.d.ts +80 -0
  26. package/dist/dts/main/composer-resize-controller.d.ts.map +1 -0
  27. package/dist/dts/main/context-gate-controller.d.ts +197 -0
  28. package/dist/dts/main/context-gate-controller.d.ts.map +1 -0
  29. package/dist/dts/main/cost-ledger-controller.d.ts +228 -0
  30. package/dist/dts/main/cost-ledger-controller.d.ts.map +1 -0
  31. package/dist/dts/main/main.d.ts +201 -722
  32. package/dist/dts/main/main.d.ts.map +1 -1
  33. package/dist/dts/main/main.types.d.ts +25 -0
  34. package/dist/dts/main/main.types.d.ts.map +1 -1
  35. package/dist/dts/main/meta-snapshot.d.ts +50 -0
  36. package/dist/dts/main/meta-snapshot.d.ts.map +1 -0
  37. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  38. package/dist/esm/components/chat-driver/chat-driver.js +92 -142
  39. package/dist/esm/components/chat-driver/turn-failures.js +84 -0
  40. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +18 -0
  41. package/dist/esm/components/settings-modal/index.js +1 -0
  42. package/dist/esm/components/settings-modal/settings-modal.js +436 -0
  43. package/dist/esm/components/settings-modal/settings-modal.template.js +15 -4
  44. package/dist/esm/main/attachment-controller.js +254 -0
  45. package/dist/esm/main/budget-controller.js +745 -0
  46. package/dist/esm/main/composer-menu-controller.js +234 -0
  47. package/dist/esm/main/composer-resize-controller.js +119 -0
  48. package/dist/esm/main/context-gate-controller.js +289 -0
  49. package/dist/esm/main/cost-ledger-controller.js +498 -0
  50. package/dist/esm/main/main.js +501 -2268
  51. package/dist/esm/main/meta-snapshot.js +108 -0
  52. package/dist/esm/state/persistence/session-persister.js +5 -4
  53. package/docs/migration-GENC-1580.md +209 -0
  54. package/package.json +16 -16
  55. package/docs/.gitattributes +0 -2
@@ -1675,6 +1675,42 @@
1675
1675
  "declarations": [],
1676
1676
  "exports": []
1677
1677
  },
1678
+ {
1679
+ "kind": "javascript-module",
1680
+ "path": "src/main/attachment-controller.ts",
1681
+ "declarations": [],
1682
+ "exports": []
1683
+ },
1684
+ {
1685
+ "kind": "javascript-module",
1686
+ "path": "src/main/budget-controller.ts",
1687
+ "declarations": [],
1688
+ "exports": []
1689
+ },
1690
+ {
1691
+ "kind": "javascript-module",
1692
+ "path": "src/main/composer-menu-controller.ts",
1693
+ "declarations": [],
1694
+ "exports": []
1695
+ },
1696
+ {
1697
+ "kind": "javascript-module",
1698
+ "path": "src/main/composer-resize-controller.ts",
1699
+ "declarations": [],
1700
+ "exports": []
1701
+ },
1702
+ {
1703
+ "kind": "javascript-module",
1704
+ "path": "src/main/context-gate-controller.ts",
1705
+ "declarations": [],
1706
+ "exports": []
1707
+ },
1708
+ {
1709
+ "kind": "javascript-module",
1710
+ "path": "src/main/cost-ledger-controller.ts",
1711
+ "declarations": [],
1712
+ "exports": []
1713
+ },
1678
1714
  {
1679
1715
  "kind": "javascript-module",
1680
1716
  "path": "src/main/index.ts",
@@ -1787,6 +1823,59 @@
1787
1823
  "description": "Foundation AI Assistant component.",
1788
1824
  "name": "FoundationAiAssistant",
1789
1825
  "members": [
1826
+ {
1827
+ "kind": "field",
1828
+ "name": "composerResize",
1829
+ "privacy": "private",
1830
+ "readonly": true,
1831
+ "default": "new ComposerResizeController({\n enabled: () => this.chatConfig.ui?.resizableInput !== false,\n inputEl: () => this.chatInputEl,\n messagesEl: () => this.messagesEl,\n })"
1832
+ },
1833
+ {
1834
+ "kind": "field",
1835
+ "name": "composerMenus",
1836
+ "privacy": "private",
1837
+ "readonly": true,
1838
+ "default": "new ComposerMenuController(this, {\n agentPickerOpen: this.accessor('agentPickerOpen'),\n sessionMenuOpen: this.accessor('sessionMenuOpen'),\n focusInput: () => this.chatInputEl?.focus(),\n resetSessionConfirmations: () => {\n this.confirmingClear = false;\n this.confirmingCompact = false;\n },\n logMeta: (type, detail) => this.logMeta(type, detail),\n })"
1839
+ },
1840
+ {
1841
+ "kind": "field",
1842
+ "name": "attachmentCtrl",
1843
+ "privacy": "private",
1844
+ "readonly": true,
1845
+ "default": "new AttachmentController(this, {\n acceptedFiles: () => this.chatConfig.ui?.acceptedFiles,\n logMeta: (type, detail) => this.logMeta(type, detail),\n })"
1846
+ },
1847
+ {
1848
+ "kind": "field",
1849
+ "name": "contextGateCtrl",
1850
+ "privacy": "private",
1851
+ "readonly": true,
1852
+ "default": "new ContextGateController({\n chatConfig: () => this.chatConfig,\n messages: () => this.messages,\n contextTokens: () => this.contextTokens,\n contextLimit: () => this.contextLimit,\n gateContextTokens: () => this._gateContextTokens,\n contextReserve: () => this.contextReserve,\n preambleTokens: () => this.driver?.getPreambleTokens?.(),\n compactionPlanFor: (options) => this.driver?.getCompactionPlan?.(options) ?? null,\n bannerVisible: () => this.bannerVisible,\n effectiveBlockedReason: () => this.effectiveBlockedReason,\n flowOwnerAgentName: () => this.flowOwnerAgentName,\n })",
1853
+ "description": "Pure derivation — owns no state. Reaches the driver only through the two\ncallbacks below, so it stays clear of the stateful-agent surface (§11.2\nconstraint 2) and of redux entirely."
1854
+ },
1855
+ {
1856
+ "kind": "field",
1857
+ "name": "costLedger",
1858
+ "privacy": "private",
1859
+ "readonly": true,
1860
+ "default": "new CostLedgerController({\n chatConfig: () => this.chatConfig,\n messages: () => this.messages,\n sessionUsage: () => this.sessionUsage,\n providerStatuses: () => this.providerStatuses,\n persistenceActive: () => this.persistenceActive,\n stateKey: () => this.getStateKey(),\n headerTitle: () => this.headerTitle,\n provider: () => this.persistence.provider,\n elementId: () => this.id,\n })",
1861
+ "description": "Cost accounting and the persisted build-history ledger (GENC-1475). Reads the live\nsession through callbacks and owns the rows, the load/write latches and the debounce\ntimer — the element keeps only the names the settings modal binds."
1862
+ },
1863
+ {
1864
+ "kind": "field",
1865
+ "name": "budget",
1866
+ "privacy": "private",
1867
+ "readonly": true,
1868
+ "default": "new BudgetController({\n sessionRef: () => this._sessionRef,\n providerStatuses: () => this.providerStatuses,\n providerRegistry: () => this.providerRegistry,\n chatConfig: () => this.chatConfig,\n })",
1869
+ "description": "The AI-spend budget: vendor walls, the derived composer lock, the banner copy\nand the settings meter (GENC-1464).\n\nTakes the session store itself rather than a callback per field — see\n`BudgetDeps` for why this one differs from its siblings."
1870
+ },
1871
+ {
1872
+ "kind": "field",
1873
+ "name": "settings",
1874
+ "privacy": "private",
1875
+ "readonly": true,
1876
+ "default": "new SettingsModalController({\n chatConfig: () => this.chatConfig,\n contextTokens: () => this.contextTokens,\n contextLimit: () => this.contextLimit,\n budgetUsageVisible: () => this.budget.settingsUsageVisible,\n costConfig: () => this.costLedger.config,\n costHistoryLength: () => this.costLedger.history.length,\n liveCostSessionTitle: () => this.costLedger.liveTitle,\n providerStatusCount: () => this.providerStatuses.length,\n sessionRef: () => this._sessionRef,\n stateKey: () => this.getStateKey(),\n popoutMode: () => this.popoutMode,\n headerTitle: () => this.headerTitle,\n appSettingsProvider: () => this.appSettingsProvider,\n modal: () => this.settingsModal,\n tabsEl: () => this.settingsTabsEl,\n requestBudgetRefresh: () => this.requestBudgetRefresh(),\n logMeta: (type, detail) => this.logMeta(type, detail),\n })",
1877
+ "description": "The settings modal (`docs/code-health.md` §3). Reaches the two design-system\nrefs through callbacks, because the template writes them by name — see\n`SettingsModalDeps`."
1878
+ },
1790
1879
  {
1791
1880
  "kind": "field",
1792
1881
  "name": "providerRegistry",
@@ -2189,7 +2278,7 @@
2189
2278
  "type": {
2190
2279
  "text": "readonly AIProviderType[]"
2191
2280
  },
2192
- "description": "The distinct vendors the registry can currently reach, from the provider\nstatuses this element already loads on connect and refreshes on every\nobservable-registry change.\n\nThis is the element's answer to \"which vendor would the next turn use\" — a\nquestion that has **no** correct answer and deliberately gets no API. The\nprovider is resolved per turn AND per agent: `activeProviderInput` may be an\nasync function of the turn's context, an orchestrated turn picks its agent\nwith an LLM `classify()` call, and sub-agents resolve their own providers.\nAny pre-turn \"peek\" would therefore be a guess that is wrong precisely on the\nmulti-agent hosts per-vendor budgets exist for.\n\nAsking instead which vendors are REACHABLE is answerable, synchronous, and\nfree — and it is enough: the composer must stay live while any reachable\nvendor has headroom, and must lock when none does. It also makes vendor-switch\nrecovery a derivation rather than a mutation: a host that swaps its registry\nto another vendor fires the observable, the statuses reload, the walled vendor\ndrops out of this set, and `blocked` goes false with every latch left intact.",
2281
+ "description": "The distinct vendors the registry can currently reach, from the provider\nstatuses this element already loads on connect and refreshes on every\nobservable-registry change — this element's answer to \"which vendor would the\nnext turn use\", a question that has no correct answer and deliberately gets\nno API. See `BudgetController.reachableVendors` for why reachability is\nthe answerable question and what deriving from it buys.",
2193
2282
  "readonly": true
2194
2283
  },
2195
2284
  {
@@ -2198,7 +2287,7 @@
2198
2287
  "type": {
2199
2288
  "text": "readonly AIProviderType[]"
2200
2289
  },
2201
- "description": "Vendors currently walled by the AI-spend budget, in the order they were\nwalled. Empty for a host that never adopts the per-vendor API.\n\nMay include a vendor this registry cannot reach — either because the registry\nmoved on after the wall was latched, or because a 402's\n`otherVendorAvailable: false` walls every vendor the proxy meters (which is\nwhat that verdict is a statement about; see\nFoundationAiAssistant.latchBlockedFrom). Neither costs anything\ninternally — FoundationAiAssistant.blocked asks only about the\nreachable set, and the banner reads the reachability-filtered\nFoundationAiAssistant.relevantBlockedVendors — but a host rendering\nthis list itself should filter it against its own registry.\n\nRead-only on purpose: a settable array would let a host write a partial list\nand silently orphan the per-vendor banner copy.\nFoundationAiAssistant.setVendorBlocked is the write path.",
2290
+ "description": "Vendors currently walled by the AI-spend budget, in the order they were\nwalled. Empty for a host that never adopts the per-vendor API.\n\nMay include a vendor this registry cannot reach — either because the registry\nmoved on after the wall was latched, or because a 402's\n`otherVendorAvailable: false` walls every vendor the proxy meters. Neither\ncosts anything internally — FoundationAiAssistant.blocked asks only\nabout the reachable set, and the banner reads the reachability-filtered\n`BudgetController.relevantBlockedVendors` — but a host rendering this\nlist itself should filter it against its own registry.\n\nRead-only on purpose: a settable array would let a host write a partial list\nand silently orphan the per-vendor banner copy.\nFoundationAiAssistant.setVendorBlocked is the write path.",
2202
2291
  "readonly": true
2203
2292
  },
2204
2293
  {
@@ -2248,35 +2337,7 @@
2248
2337
  }
2249
2338
  }
2250
2339
  ],
2251
- "description": "Wall (or release) one vendor's budget, optionally with banner copy for it.\nThe per-vendor mirror of FoundationAiAssistant.setBlocked.\n\nWalling a vendor does **not** on its own disable the composer: while another\nreachable vendor has headroom the assistant stays usable and the banner tells\nthe user to switch. Only when every reachable vendor is walled does `blocked`\nbecome true.\n\n`reason` is composed into the banner sentence for that vendor, **however many\nvendors are walled** — see FoundationAiAssistant.effectiveBlockedReason.\nIt is per-vendor copy, not a whole-banner override; FoundationAiAssistant.blockedReason\nis the override. Omitting it keeps whatever explanation that vendor already\ncarried, so a driver latch landing after a host one cannot blank it.\n\n`'none'` is rejected with a warning rather than accepted: it is the \"no\nprovider configured\" sentinel, not a vendor. Latching it would put the raw\nsentinel in the banner (\"none's AI usage limit is reached\") and could never\nbe undone by derivation, because `'none'` never appears in\nFoundationAiAssistant.reachableVendors — so `blocked` could never\nbecome derivable from it either. Use FoundationAiAssistant.setBlocked\nfor a vendor-agnostic block."
2252
- },
2253
- {
2254
- "kind": "field",
2255
- "name": "vendorBudgetWarningIssued",
2256
- "type": {
2257
- "text": "boolean"
2258
- },
2259
- "privacy": "private",
2260
- "default": "false",
2261
- "description": "One warning per element for garbage fed to\nFoundationAiAssistant.setVendorBudget. Once, not per call, because\nthe documented feed re-runs after every turn — a host bug would otherwise\nrepeat the same warning on every send for the life of the page."
2262
- },
2263
- {
2264
- "kind": "method",
2265
- "name": "warnVendorBudgetOnce",
2266
- "privacy": "private",
2267
- "return": {
2268
- "type": {
2269
- "text": "void"
2270
- }
2271
- },
2272
- "parameters": [
2273
- {
2274
- "name": "message",
2275
- "type": {
2276
- "text": "string"
2277
- }
2278
- }
2279
- ]
2340
+ "description": "Wall (or release) one vendor's budget, optionally with banner copy for it.\nThe per-vendor mirror of FoundationAiAssistant.setBlocked.\n\nWalling a vendor does **not** on its own disable the composer: while another\nreachable vendor has headroom the assistant stays usable and the banner tells\nthe user to switch. Only when every reachable vendor is walled does `blocked`\nbecome true.\n\n`reason` is composed into the banner sentence for that vendor, **however many\nvendors are walled** — see `BudgetController.effectiveBlockedReason`.\nIt is per-vendor copy, not a whole-banner override;\nFoundationAiAssistant.blockedReason is the override. Omitting it keeps\nwhatever explanation that vendor already carried, so a driver latch landing\nafter a host one cannot blank it.\n\n`'none'` is rejected with a warning rather than accepted — it is the \"no\nprovider configured\" sentinel, not a vendor, and a wall on it could never be\nundone by derivation. Use FoundationAiAssistant.setBlocked for a\nvendor-agnostic block."
2280
2341
  },
2281
2342
  {
2282
2343
  "kind": "method",
@@ -2308,8 +2369,7 @@
2308
2369
  "type": {
2309
2370
  "text": "number | null"
2310
2371
  },
2311
- "default": "null",
2312
- "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."
2372
+ "description": "When budget figures last arrived — from the host's\nFoundationAiAssistant.setVendorBudget or the wall latch's own feed —\nas 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."
2313
2373
  },
2314
2374
  {
2315
2375
  "kind": "field",
@@ -2330,25 +2390,6 @@
2330
2390
  },
2331
2391
  "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."
2332
2392
  },
2333
- {
2334
- "kind": "method",
2335
- "name": "isVendorSwept",
2336
- "privacy": "private",
2337
- "return": {
2338
- "type": {
2339
- "text": "boolean"
2340
- }
2341
- },
2342
- "parameters": [
2343
- {
2344
- "name": "vendor",
2345
- "type": {
2346
- "text": "AIProviderType"
2347
- }
2348
- }
2349
- ],
2350
- "description": "Whether this vendor's wall came from the sweep alone — see the slice's `sweptVendors`."
2351
- },
2352
2393
  {
2353
2394
  "kind": "field",
2354
2395
  "name": "blockedReason",
@@ -2389,7 +2430,7 @@
2389
2430
  "text": "string | undefined"
2390
2431
  },
2391
2432
  "privacy": "private",
2392
- "description": "Copy the driver writes into the TRANSCRIPT when a turn hits the budget wall,\nor `undefined` to let the driver compose it per wall.\n\nA set `blockedReason` still wins — a white-labelled host must not read its\nown explanation in the banner and shipped copy directly below it, and the\nvendor-agnostic latch's `formatBlockedReason(budget)` form stays advice-free\nexactly as before.\n\n`undefined` (the usual case) is NOT the old `DEFAULT_BUDGET_EXHAUSTED_MESSAGE`\nfallback: it hands composition to the driver, which builds the bubble from\nEACH wall's own 402 — naming the exhausted vendor and advising a switch only\nwhen that refusal's `otherVendorAvailable` positively vouched for headroom\nelsewhere. That is immune to the staleness this getter used to guard against\nby freezing the copy: construction-time advice could outlive its truth\n(`getOrCreateDriver` re-runs on an agents swap and would have baked the\nsentence into every later bubble), but per-wall advice is derived from the\nrefusal it annotates and dies with the turn. Forcing the default here was\nalso what made the driver's composed bubble unreachable in every real\nmount — the banner advised switching while the bubble under it never did\n(the GENC-1464 tester finding, root cause).",
2433
+ "description": "Copy the driver writes into the TRANSCRIPT when a turn hits the budget wall,\nor `undefined` to let the driver compose it per wall — see\n`BudgetController.transcriptMessage`.",
2393
2434
  "readonly": true
2394
2435
  },
2395
2436
  {
@@ -2476,7 +2517,7 @@
2476
2517
  "type": {
2477
2518
  "text": "CostSessionModelEntry[]"
2478
2519
  },
2479
- "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.",
2520
+ "description": "The cost ledger's derived surface (GENC-1475). Every member below delegates to\n`CostLedgerController` — the names are this element's public API and the\nsettings modal's bindings, so they stay put while the accounting moves.\n\nNone carries `@volatile`: the decorators live on the controller's getters, and they\nreach the binding observer from there — see `docs/code-health.md` §11.4.",
2480
2521
  "readonly": true
2481
2522
  },
2482
2523
  {
@@ -2485,7 +2526,7 @@
2485
2526
  "type": {
2486
2527
  "text": "CostSessionRecord[]"
2487
2528
  },
2488
- "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.",
2529
+ "description": "Build history for display: most recently worked on first.",
2489
2530
  "readonly": true
2490
2531
  },
2491
2532
  {
@@ -2494,7 +2535,7 @@
2494
2535
  "type": {
2495
2536
  "text": "AggregateUsage"
2496
2537
  },
2497
- "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.",
2538
+ "description": "Lifetime usage across every project in the history scope — rows only, never plus live.",
2498
2539
  "readonly": true
2499
2540
  },
2500
2541
  {
@@ -2512,7 +2553,7 @@
2512
2553
  "type": {
2513
2554
  "text": "boolean"
2514
2555
  },
2515
- "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.",
2556
+ "description": "Whether the live session figures amount to this project's whole history.",
2516
2557
  "readonly": true
2517
2558
  },
2518
2559
  {
@@ -2524,6 +2565,14 @@
2524
2565
  "description": "Resolved cost tab and build-history settings from `chatConfig.costHistory`.",
2525
2566
  "readonly": true
2526
2567
  },
2568
+ {
2569
+ "kind": "field",
2570
+ "name": "costSessionHistory",
2571
+ "type": {
2572
+ "text": "CostSessionRecord[]"
2573
+ },
2574
+ "description": "Persisted finalized cost sessions shown on the Usage tab build history list.\n\nA get/set pair rather than a read-only projection: the banking tests seed rows to\nmodel a page load that found some, and a host provider's rows arrive the same way."
2575
+ },
2527
2576
  {
2528
2577
  "kind": "field",
2529
2578
  "name": "sessionTokensConsumed",
@@ -2559,26 +2608,7 @@
2559
2608
  }
2560
2609
  }
2561
2610
  ],
2562
- "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."
2563
- },
2564
- {
2565
- "kind": "method",
2566
- "name": "bankedUsageFor",
2567
- "privacy": "private",
2568
- "return": {
2569
- "type": {
2570
- "text": "AggregateUsage"
2571
- }
2572
- },
2573
- "parameters": [
2574
- {
2575
- "name": "projectKey",
2576
- "type": {
2577
- "text": "string"
2578
- }
2579
- }
2580
- ],
2581
- "description": "Spend for `projectKey` that predates the current transcript, resolved once per SESSION and\nshared by every element instance wired to it (see `banked-usage-baselines`), so it cannot\ndrift as the row it came from is rewritten 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.\n\nThe baseline is deliberately NOT element state. This element is remounted repeatedly inside\na single session (the host re-rendering its layout, popping out, docking) and a fresh\ninstance re-deriving would take the second branch against rows that already hold the live\ntranscript — banking the session as its own baseline. Keying on the session instead means a\nremount inherits the baseline that transcript was paired with."
2611
+ "description": "Write this session's usage into persisted build history as a single row keyed by the\nsession 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. The\nname is historical: it does NOT end the session or reset the running totals. See\n`CostLedgerController.finalize` for the full contract and the no-op cases."
2582
2612
  },
2583
2613
  {
2584
2614
  "kind": "method",
@@ -2603,7 +2633,7 @@
2603
2633
  }
2604
2634
  }
2605
2635
  ],
2606
- "description": "Re-baseline this session against an emptied transcript: what came before, plus what the\ntranscript being discarded proved. `discarded` must be read BEFORE the live figures are\nreset, since that is the only record of the latter.\n\nNot the same as dropping the baseline. Dropping it re-derives through `persistenceActive`,\nand with persistence ON that carries the row's *old* banked figure forward — discarding\nevery turn between the last baseline and the clear. The transcript is gone either way, so\nthe branch that reads it as authoritative no longer applies.\n\nDerived from the memoised baseline and the live figure rather than from the project's row,\nbecause a row is only as current as the last `finalizeCostSession` — which skips its write\nwhile the ledger is unloaded, and while the session has no usage yet."
2636
+ "description": "Re-baseline this session against an emptied transcript — see\n`CostLedgerController.rebaselineOnClearedTranscript`. `discarded` must be read\nBEFORE the live figures are reset, since that is the only record of the latter."
2607
2637
  },
2608
2638
  {
2609
2639
  "kind": "method",
@@ -2669,73 +2699,6 @@
2669
2699
  },
2670
2700
  "description": "Clear all persisted build history for this assistant scope."
2671
2701
  },
2672
- {
2673
- "kind": "method",
2674
- "name": "getCostHistoryScope",
2675
- "privacy": "private",
2676
- "return": {
2677
- "type": {
2678
- "text": "string"
2679
- }
2680
- }
2681
- },
2682
- {
2683
- "kind": "method",
2684
- "name": "reloadCostSessionHistory",
2685
- "privacy": "private",
2686
- "return": {
2687
- "type": {
2688
- "text": "void"
2689
- }
2690
- }
2691
- },
2692
- {
2693
- "kind": "method",
2694
- "name": "costHistoryProvider",
2695
- "privacy": "private",
2696
- "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`."
2697
- },
2698
- {
2699
- "kind": "method",
2700
- "name": "persistCostHistory",
2701
- "privacy": "private",
2702
- "return": {
2703
- "type": {
2704
- "text": "void"
2705
- }
2706
- },
2707
- "parameters": [
2708
- {
2709
- "name": "records",
2710
- "type": {
2711
- "text": "CostSessionRecord[]"
2712
- }
2713
- }
2714
- ],
2715
- "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."
2716
- },
2717
- {
2718
- "kind": "method",
2719
- "name": "flushCostHistory",
2720
- "privacy": "private",
2721
- "return": {
2722
- "type": {
2723
- "text": "void"
2724
- }
2725
- },
2726
- "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."
2727
- },
2728
- {
2729
- "kind": "method",
2730
- "name": "syncActiveCostSessionTitle",
2731
- "privacy": "private",
2732
- "return": {
2733
- "type": {
2734
- "text": "void"
2735
- }
2736
- },
2737
- "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."
2738
- },
2739
2702
  {
2740
2703
  "kind": "field",
2741
2704
  "name": "activeModel",
@@ -2782,7 +2745,7 @@
2782
2745
  "type": {
2783
2746
  "text": "ChatAttachment[]"
2784
2747
  },
2785
- "default": "[]"
2748
+ "description": "Attachments pending on the next message."
2786
2749
  },
2787
2750
  {
2788
2751
  "kind": "field",
@@ -2790,7 +2753,7 @@
2790
2753
  "type": {
2791
2754
  "text": "string[]"
2792
2755
  },
2793
- "default": "[]"
2756
+ "description": "Rejection messages for files the last pick refused."
2794
2757
  },
2795
2758
  {
2796
2759
  "kind": "field",
@@ -2807,7 +2770,6 @@
2807
2770
  "type": {
2808
2771
  "text": "number"
2809
2772
  },
2810
- "default": "0",
2811
2773
  "description": "Bump to re-render app settings toggles when the host provider notifies a change."
2812
2774
  },
2813
2775
  {
@@ -2816,7 +2778,6 @@
2816
2778
  "type": {
2817
2779
  "text": "boolean"
2818
2780
  },
2819
- "default": "false",
2820
2781
  "description": "Whether the settings modal is open."
2821
2782
  },
2822
2783
  {
@@ -2834,7 +2795,6 @@
2834
2795
  "type": {
2835
2796
  "text": "SettingsModalTab"
2836
2797
  },
2837
- "default": "'settings'",
2838
2798
  "description": "Active tab inside the settings modal."
2839
2799
  },
2840
2800
  {
@@ -2970,41 +2930,28 @@
2970
2930
  "type": {
2971
2931
  "text": "HTMLElement[]"
2972
2932
  },
2973
- "default": "[]",
2974
- "description": "Light-DOM nodes assigned to settings modal slots (via `slotted()` bindings)."
2933
+ "description": "Light-DOM nodes assigned to settings modal slots. The template's `slotted()`\nbindings write these by property NAME, so each needs a real setter here even\nthough the controller owns the arrays."
2975
2934
  },
2976
2935
  {
2977
2936
  "kind": "field",
2978
2937
  "name": "settingsAppSlotted",
2979
2938
  "type": {
2980
2939
  "text": "HTMLElement[]"
2981
- },
2982
- "default": "[]"
2940
+ }
2983
2941
  },
2984
2942
  {
2985
2943
  "kind": "field",
2986
2944
  "name": "settingsCostCurrentSlotted",
2987
2945
  "type": {
2988
2946
  "text": "HTMLElement[]"
2989
- },
2990
- "default": "[]"
2947
+ }
2991
2948
  },
2992
2949
  {
2993
2950
  "kind": "field",
2994
2951
  "name": "settingsCostHistorySlotted",
2995
2952
  "type": {
2996
2953
  "text": "HTMLElement[]"
2997
- },
2998
- "default": "[]"
2999
- },
3000
- {
3001
- "kind": "field",
3002
- "name": "costSessionHistory",
3003
- "type": {
3004
- "text": "CostSessionRecord[]"
3005
- },
3006
- "default": "[]",
3007
- "description": "Persisted finalized cost sessions shown on the Usage tab build history list."
2954
+ }
3008
2955
  },
3009
2956
  {
3010
2957
  "kind": "field",
@@ -3023,6 +2970,16 @@
3023
2970
  },
3024
2971
  "privacy": "private"
3025
2972
  },
2973
+ {
2974
+ "kind": "field",
2975
+ "name": "orchestrator",
2976
+ "type": {
2977
+ "text": "OrchestratingDriver | undefined"
2978
+ },
2979
+ "privacy": "private",
2980
+ "description": "This element's driver as an orchestrator, or `undefined` when the session is\nrunning a bare `ChatDriver`.\n\nThe five call sites that used to test the type themselves now read this\n(§11.2 constraint 3 of `docs/code-health.md`): the agent pin, the flow-owner\nlock, the wire-up in `wireDriver`, and the two `driverKind` diagnostics. The\ntype test itself lives in `asOrchestrator`, shared with the session\npersister.",
2981
+ "readonly": true
2982
+ },
3026
2983
  {
3027
2984
  "kind": "field",
3028
2985
  "name": "driverCleanup",
@@ -3065,72 +3022,6 @@
3065
3022
  "privacy": "private",
3066
3023
  "description": "Unsubscribe handle for the provider-registry change listener (observable registries only)."
3067
3024
  },
3068
- {
3069
- "kind": "field",
3070
- "name": "unsubAppSettingsProvider",
3071
- "type": {
3072
- "text": "() => void | undefined"
3073
- },
3074
- "privacy": "private",
3075
- "description": "Unsubscribe handle for (AssistantAppSettingsProvider:interface).subscribe."
3076
- },
3077
- {
3078
- "kind": "field",
3079
- "name": "_activeCostSessionTitle",
3080
- "type": {
3081
- "text": "string"
3082
- },
3083
- "privacy": "private",
3084
- "default": "''",
3085
- "description": "Title tracked for the in-flight cost session (used when `header-title` changes)."
3086
- },
3087
- {
3088
- "kind": "field",
3089
- "name": "_activeCostSessionKey",
3090
- "type": {
3091
- "text": "string | undefined"
3092
- },
3093
- "privacy": "private",
3094
- "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`."
3095
- },
3096
- {
3097
- "kind": "field",
3098
- "name": "_costHistoryLoaded",
3099
- "type": {
3100
- "text": "boolean"
3101
- },
3102
- "privacy": "private",
3103
- "default": "false",
3104
- "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."
3105
- },
3106
- {
3107
- "kind": "field",
3108
- "name": "_ledgerUnreadable",
3109
- "type": {
3110
- "text": "boolean"
3111
- },
3112
- "privacy": "private",
3113
- "default": "false",
3114
- "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."
3115
- },
3116
- {
3117
- "kind": "field",
3118
- "name": "_ledgerDirty",
3119
- "type": {
3120
- "text": "boolean"
3121
- },
3122
- "privacy": "private",
3123
- "default": "false",
3124
- "description": "Pending (adopted but unwritten) ledger changes — see `persistCostHistory`."
3125
- },
3126
- {
3127
- "kind": "field",
3128
- "name": "_ledgerFlushTimer",
3129
- "type": {
3130
- "text": "ReturnType<typeof setTimeout> | undefined"
3131
- },
3132
- "privacy": "private"
3133
- },
3134
3025
  {
3135
3026
  "kind": "field",
3136
3027
  "name": "_handlePageHide",
@@ -3171,52 +3062,31 @@
3171
3062
  "description": "Bound to the rapid-text-area chat input via the template ref."
3172
3063
  },
3173
3064
  {
3174
- "kind": "field",
3175
- "name": "composerHeightPx",
3176
- "type": {
3177
- "text": "number | null"
3178
- },
3179
- "default": "null",
3180
- "description": "Explicit composer height (px) once the user has dragged the resize handle;\n`null` keeps the default auto-grow behaviour. Written into the\n`--composer-height` custom property via the `composerHeightStyle` getter."
3181
- },
3182
- {
3183
- "kind": "field",
3184
- "name": "_resizeStartY",
3185
- "type": {
3186
- "text": "number"
3187
- },
3065
+ "kind": "method",
3066
+ "name": "accessor",
3188
3067
  "privacy": "private",
3189
- "default": "0",
3190
- "description": "Pointer Y and composer height captured at the start of a resize drag."
3191
- },
3192
- {
3193
- "kind": "field",
3194
- "name": "_resizeStartHeight",
3195
- "type": {
3196
- "text": "number"
3068
+ "return": {
3069
+ "type": {
3070
+ "text": "Accessor<this[K]>"
3071
+ }
3197
3072
  },
3198
- "privacy": "private",
3199
- "default": "0"
3073
+ "parameters": [
3074
+ {
3075
+ "name": "key",
3076
+ "type": {
3077
+ "text": "K"
3078
+ }
3079
+ }
3080
+ ],
3081
+ "description": "A get/set pair over one of this element's own properties, for a controller\nthat drives state it does not own. Written as a helper rather than two inline\nclosures per call: a pair of same-typed closures is easy to transpose by\naccident, and a single object cannot be."
3200
3082
  },
3201
3083
  {
3202
3084
  "kind": "field",
3203
- "name": "_composerNaturalHeight",
3085
+ "name": "composerHeightPx",
3204
3086
  "type": {
3205
- "text": "number"
3087
+ "text": "number | null"
3206
3088
  },
3207
- "privacy": "private",
3208
- "default": "0",
3209
- "description": "The composer's natural (un-resized) height, measured on first drag — used\nas the lower bound so the box can't be dragged smaller than it started."
3210
- },
3211
- {
3212
- "kind": "field",
3213
- "name": "_resizeMaxHeight",
3214
- "privacy": "private",
3215
- "default": "400",
3216
- "description": "Upper bound for the current drag — capped to the headroom above so growing\nthe composer never pushes the input row's buttons off the bottom.",
3217
- "type": {
3218
- "text": "number"
3219
- }
3089
+ "description": "Explicit composer height once dragged; `null` keeps the default auto-grow."
3220
3090
  },
3221
3091
  {
3222
3092
  "kind": "field",
@@ -3617,16 +3487,6 @@
3617
3487
  },
3618
3488
  "description": "Push the mid-loop context guard down to the driver (GENC-1567).\n\nCalled wherever the inputs can change — the resolved provider status, and a\nfreshly built driver. A driver that never receives one simply runs without\nthe guard, which is the right default: it fires only when a real context\nwindow is known."
3619
3489
  },
3620
- {
3621
- "kind": "field",
3622
- "name": "contextGuardPolicy",
3623
- "type": {
3624
- "text": "ContextGuardPolicy | undefined"
3625
- },
3626
- "privacy": "private",
3627
- "description": "Guard policy pushed to the driver, or `undefined` when the host has switched\nthe gate off — so `enabled: false` really does mean no local blocking of any\nkind, UI or mid-loop.\n\nA MARGIN, not a threshold: the driver resolves the window of the provider\neach call actually goes to. The fallback window rides along because the\ndriver cannot learn it any other way — without it, a host that knows its\nwindow while its provider does not report one had `fallbackContextLimit`\nprotecting the composer and nothing else.",
3628
- "readonly": true
3629
- },
3630
3490
  {
3631
3491
  "kind": "method",
3632
3492
  "name": "loadProviderStatuses",
@@ -3815,203 +3675,133 @@
3815
3675
  "description": "Record a meta/lifecycle event onto this session's debug-log timeline\n(exported via FoundationAiAssistant.getDebugLog). Every event\nauto-carries `placement` so a session emitted from both the bubble and the\nlayout panel stays disambiguated. Keyed by `stateKey` so the timeline is\nshared across pop-in/pop-out and survives driver rebuilds."
3816
3676
  },
3817
3677
  {
3818
- "kind": "field",
3819
- "name": "_dismissPointerHandler",
3820
- "type": {
3821
- "text": "(e: Event) => void | undefined"
3822
- },
3823
- "privacy": "private",
3678
+ "kind": "method",
3679
+ "name": "toggleAgentPicker",
3824
3680
  "description": "Document-level dismissal for whichever composer menu is open. Bound only\nwhile one IS open — a permanently-attached listener on a component that can\nbe mounted twice (bubble + popout panel) is a cost every click on the page\npays for a menu that is usually closed.\n\nUses `composedPath()` rather than `event.target`: the click lands on a node\ninside this element's shadow root, which retargets to the host, so a plain\n`contains()` check cannot tell \"clicked the menu\" from \"clicked the\ntranscript\". The path crosses the boundary and carries both."
3825
3681
  },
3826
3682
  {
3827
3683
  "kind": "field",
3828
- "name": "_dismissKeyHandler",
3684
+ "name": "confirmingClear",
3829
3685
  "type": {
3830
- "text": "(e: KeyboardEvent) => void | undefined"
3686
+ "text": "boolean"
3831
3687
  },
3832
- "privacy": "private"
3688
+ "default": "false",
3689
+ "description": "Two-step confirm for the destructive Clear action; reset whenever the menu toggles."
3833
3690
  },
3834
3691
  {
3835
3692
  "kind": "field",
3836
- "name": "_dismissResizeHandler",
3693
+ "name": "confirmingCompact",
3837
3694
  "type": {
3838
- "text": "() => void | undefined"
3839
- },
3840
- "privacy": "private"
3841
- },
3842
- {
3843
- "kind": "method",
3844
- "name": "closeOpenComposerMenus",
3845
- "privacy": "private",
3846
- "return": {
3847
- "type": {
3848
- "text": "void"
3849
- }
3850
- }
3851
- },
3852
- {
3853
- "kind": "method",
3854
- "name": "positionComposerMenu",
3855
- "privacy": "private",
3856
- "return": {
3857
- "type": {
3858
- "text": "void"
3859
- }
3695
+ "text": "boolean"
3860
3696
  },
3861
- "parameters": [
3862
- {
3863
- "name": "panelSelector",
3864
- "type": {
3865
- "text": "string"
3866
- }
3867
- },
3868
- {
3869
- "name": "triggerSelector",
3870
- "type": {
3871
- "text": "string"
3872
- }
3873
- }
3874
- ],
3875
- "description": "Places an open composer menu directly above the control that opened it.\n\nCSS alone cannot: the menu is a child of `.composer` — which is what lets its\nwidth be capped against the composer rather than against a button — while its\nanchor is a button several levels down the bar, and no selector can read a\nsibling's offset. So the left edge is measured on open and written as a\ncustom property.\n\nClamped to the composer, so a menu opened from a control near the right edge\nof a narrow panel slides back inside instead of overhanging. The animation\norigin is written separately and keeps pointing at the button's centre even\nafter such a shift — the menu still grows out of the thing that opened it,\nwhich is the whole point of anchoring it there."
3697
+ "default": "false"
3876
3698
  },
3877
3699
  {
3878
- "kind": "method",
3879
- "name": "repositionOpenComposerMenu",
3880
- "privacy": "private",
3881
- "return": {
3882
- "type": {
3883
- "text": "void"
3884
- }
3700
+ "kind": "field",
3701
+ "name": "sessionMenuEnabled",
3702
+ "type": {
3703
+ "text": "boolean"
3885
3704
  },
3886
- "description": "Re-place whichever menu is open. Cheap enough to run from a resize."
3887
- },
3888
- {
3889
- "kind": "method",
3890
- "name": "syncComposerMenuDismissal",
3891
- "privacy": "private",
3892
- "return": {
3893
- "type": {
3894
- "text": "void"
3895
- }
3896
- }
3897
- },
3898
- {
3899
- "kind": "method",
3900
- "name": "teardownComposerMenuDismissal",
3901
- "privacy": "private",
3902
- "return": {
3903
- "type": {
3904
- "text": "void"
3905
- }
3906
- }
3705
+ "description": "Whether the lifecycle-menu button shows. It occupies the stop-button slot,\nso it only appears when idle (mutually exclusive with Stop). Gated on\npersistence — the menu's actions (Compact, Clear, the \"remember chat\" toggle)\nonly make sense with a provider configured, so it's hidden entirely without\none. A host can additionally force it off via `chatConfig.ui.showSessionMenu:\nfalse` even when a provider is present (e.g. a guided onboarding flow where\nclearing/compacting mid-journey would be disruptive).",
3706
+ "readonly": true
3907
3707
  },
3908
3708
  {
3909
3709
  "kind": "field",
3910
- "name": "MENU_HEADROOM_RESERVE",
3710
+ "name": "persistenceToggleable",
3911
3711
  "type": {
3912
- "text": "number"
3712
+ "text": "boolean"
3913
3713
  },
3914
- "privacy": "private",
3915
- "static": true,
3916
- "readonly": true,
3917
- "default": "20",
3918
- "description": "Vertical space an open menu must leave free above the button, covering BOTH\nthe gap the menu sits off the button by (8px, declared in design units in\nmain.styles.ts) and the breathing room wanted above it. The two are lumped\ntogether because only the second half is a free choice: the first is fixed by\nthe stylesheet and simply has to be subtracted from the headroom, and a\nconstant named for only one of them invites someone to \"fix\" a gap that is\ndoing exactly what it should."
3714
+ "description": "The persistence toggle row renders only when a provider is configured.",
3715
+ "readonly": true
3919
3716
  },
3920
3717
  {
3921
3718
  "kind": "field",
3922
- "name": "_agentPickerToggle",
3923
- "privacy": "private",
3924
- "readonly": true,
3925
- "default": "new AnimatedPanelToggle(\n this,\n '.agent-picker-panel',\n AGENT_PICKER_CLOSE_MS,\n () => this.agentPickerOpen,\n (v) => {\n this.agentPickerOpen = v;\n },\n )"
3926
- },
3927
- {
3928
- "kind": "method",
3929
- "name": "toggleAgentPicker"
3719
+ "name": "persistenceActive",
3720
+ "type": {
3721
+ "text": "boolean"
3722
+ },
3723
+ "description": "Whether persistence is currently active (on + a provider) for this session.",
3724
+ "readonly": true
3930
3725
  },
3931
3726
  {
3932
3727
  "kind": "field",
3933
- "name": "confirmingClear",
3728
+ "name": "contextBudget",
3934
3729
  "type": {
3935
- "text": "boolean"
3730
+ "text": "ResolvedContextBudget"
3936
3731
  },
3937
- "default": "false",
3938
- "description": "Two-step confirm for the destructive Clear action; reset whenever the menu toggles."
3732
+ "description": "The context gate's derived surface (GENC-1567). Every member below delegates to\n`ContextGateController` — the names are this element's public API and the\ntemplate's bindings, so they stay put while the derivation moves.\n\nThe per-agent `contextReserve` IS folded in, via the driver (GENC-1582). It\nused to be described here as a separate, later check made when an agent took\nownership of a flow — but there is no such check, and by flow entry the send\nhas already been allowed. Resolving it before the send is what makes it a gate\nrather than a label.\n\nNone carries `@volatile`: the decorators live on the controller's getters, and\nthey reach the binding observer from there — see `docs/code-health.md` §11.4.",
3733
+ "readonly": true
3939
3734
  },
3940
3735
  {
3941
3736
  "kind": "field",
3942
- "name": "confirmingCompact",
3737
+ "name": "compactionPlan",
3943
3738
  "type": {
3944
- "text": "boolean"
3739
+ "text": "CompactionProjection | null"
3945
3740
  },
3946
- "default": "false"
3741
+ "readonly": true
3947
3742
  },
3948
3743
  {
3949
3744
  "kind": "field",
3950
- "name": "_sessionMenuToggle",
3951
- "privacy": "private",
3952
- "readonly": true,
3953
- "default": "new AnimatedPanelToggle(\n this,\n '.session-menu-panel',\n SESSION_MENU_CLOSE_MS,\n () => this.sessionMenuOpen,\n (v) => {\n this.sessionMenuOpen = v;\n },\n )"
3745
+ "name": "contextGate",
3746
+ "type": {
3747
+ "text": "'ok' | 'warn' | 'blocked'"
3748
+ },
3749
+ "readonly": true
3954
3750
  },
3955
3751
  {
3956
3752
  "kind": "field",
3957
- "name": "sessionMenuEnabled",
3753
+ "name": "contextGateReason",
3958
3754
  "type": {
3959
- "text": "boolean"
3755
+ "text": "string"
3960
3756
  },
3961
- "description": "Whether the lifecycle-menu button shows. It occupies the stop-button slot,\nso it only appears when idle (mutually exclusive with Stop). Gated on\npersistence — the menu's actions (Compact, Clear, the \"remember chat\" toggle)\nonly make sense with a provider configured, so it's hidden entirely without\none. A host can additionally force it off via `chatConfig.ui.showSessionMenu:\nfalse` even when a provider is present (e.g. a guided onboarding flow where\nclearing/compacting mid-journey would be disruptive).",
3962
3757
  "readonly": true
3963
3758
  },
3964
3759
  {
3965
3760
  "kind": "field",
3966
- "name": "persistenceToggleable",
3761
+ "name": "contextGateLabel",
3967
3762
  "type": {
3968
- "text": "boolean"
3763
+ "text": "string"
3969
3764
  },
3970
- "description": "The persistence toggle row renders only when a provider is configured.",
3971
3765
  "readonly": true
3972
3766
  },
3973
3767
  {
3974
3768
  "kind": "field",
3975
- "name": "persistenceActive",
3769
+ "name": "compactHint",
3976
3770
  "type": {
3977
- "text": "boolean"
3771
+ "text": "string"
3978
3772
  },
3979
- "description": "Whether persistence is currently active (on + a provider) for this session.",
3980
3773
  "readonly": true
3981
3774
  },
3982
3775
  {
3983
3776
  "kind": "field",
3984
- "name": "contextBudget",
3777
+ "name": "compactable",
3985
3778
  "type": {
3986
- "text": "ResolvedContextBudget"
3779
+ "text": "boolean"
3987
3780
  },
3988
- "description": "Context-headroom thresholds for the active model (GENC-1567) — the warning\nand block lines, plus the tail budget compaction aims at.\n\nThe per-agent `contextReserve` IS folded in, via the driver (GENC-1582). It\nused to be described here as a separate, later check made when an agent took\nownership of a flow — but there is no such check, and by flow entry the send\nhas already been allowed. Resolving it here, before the send, is what makes it\na gate rather than a label.\n\nOverhead is read off `messages` (the Redux mirror) rather than the driver's\nown history, which the compaction plan uses. That is safe rather than sloppy:\n`estimateSystemOverhead` only looks at messages BEFORE the first usage-bearing\none, and the two lists agree completely that early — the drift between mirror\nand driver history is only ever at the tail.",
3989
3781
  "readonly": true
3990
3782
  },
3991
3783
  {
3992
3784
  "kind": "field",
3993
- "name": "compactionPlan",
3785
+ "name": "composerNoticeVisible",
3994
3786
  "type": {
3995
- "text": "CompactionProjection | null"
3787
+ "text": "boolean"
3996
3788
  },
3997
- "description": "What compacting right now would reclaim, or `null` when it would not be\nworth it (GENC-1567). Sized against FoundationAiAssistant.contextBudget,\nso the plan aims at this model's window rather than a fixed default.\n\nReads `messages` to stay reactive as the conversation grows: the driver's\nhistory is not a tracked observable, so every consumer of this keys its\nre-evaluation off the Redux mirror.",
3998
3789
  "readonly": true
3999
3790
  },
4000
3791
  {
4001
3792
  "kind": "field",
4002
- "name": "contextGate",
3793
+ "name": "composerNoticeText",
4003
3794
  "type": {
4004
- "text": "'ok' | 'warn' | 'blocked'"
3795
+ "text": "string"
4005
3796
  },
4006
3797
  "readonly": true
4007
3798
  },
4008
3799
  {
4009
3800
  "kind": "field",
4010
- "name": "compactable",
3801
+ "name": "composerNoticeSrOnly",
4011
3802
  "type": {
4012
3803
  "text": "boolean"
4013
3804
  },
4014
- "description": "Whether the menu's \"Compact\" action would do anything (GENC-1351 §5.7,\nGENC-1567). Delegates to the driver's plan so the gate uses the exact same\nhistory `compact()` acts on, judged against the same budget — one source of\ntruth for both whether a cut is legal and whether it reclaims enough.",
4015
3805
  "readonly": true
4016
3806
  },
4017
3807
  {
@@ -4130,7 +3920,7 @@
4130
3920
  "type": {
4131
3921
  "text": "boolean"
4132
3922
  },
4133
- "description": "Whether the settings modal AI Model section has slotted app content, context usage or budget figures.",
3923
+ "description": "The settings modal's derived surface. Every member below delegates to\n`SettingsModalController` — the names are this element's public API and the\n943-line template's bindings, so they stay put while the logic moves into the\n`settings-modal.ts` that folder never had (`docs/code-health.md` §3).\n\nNone carries `@volatile`: the decorators live on the controller's getters, and\nthey reach the binding observer from there — see `docs/code-health.md` §11.4.",
4134
3924
  "readonly": true
4135
3925
  },
4136
3926
  {
@@ -4193,41 +3983,21 @@
4193
3983
  "type": {
4194
3984
  "text": "void"
4195
3985
  }
4196
- },
4197
- "parameters": [
4198
- {
4199
- "name": "id",
4200
- "type": {
4201
- "text": "string"
4202
- }
4203
- },
4204
- {
4205
- "name": "value",
4206
- "type": {
4207
- "text": "boolean"
4208
- }
4209
- }
4210
- ]
4211
- },
4212
- {
4213
- "kind": "method",
4214
- "name": "wireAppSettingsProvider",
4215
- "privacy": "private",
4216
- "return": {
4217
- "type": {
4218
- "text": "void"
4219
- }
4220
- }
4221
- },
4222
- {
4223
- "kind": "method",
4224
- "name": "unwireAppSettingsProvider",
4225
- "privacy": "private",
4226
- "return": {
4227
- "type": {
4228
- "text": "void"
3986
+ },
3987
+ "parameters": [
3988
+ {
3989
+ "name": "id",
3990
+ "type": {
3991
+ "text": "string"
3992
+ }
3993
+ },
3994
+ {
3995
+ "name": "value",
3996
+ "type": {
3997
+ "text": "boolean"
3998
+ }
4229
3999
  }
4230
- }
4000
+ ]
4231
4001
  },
4232
4002
  {
4233
4003
  "kind": "field",
@@ -4243,7 +4013,7 @@
4243
4013
  "type": {
4244
4014
  "text": "boolean"
4245
4015
  },
4246
- "description": "Whether the build-history section has anything in it — the built-in row list, or a host's own\nmarkup in the `settings-cost-history` slot.\n\nSlotted content is checked FIRST, ahead of `enabled`. That flag governs the *built-in* ledger:\nit gates the row writes, the reload, and the lifetime sum — and it defaults to `false`\n(`cost?.enabled === true`), while the Usage tab itself shows for `showCost`/`showTokens`, which\ndefault true. So a host can legitimately render its own build history into the slot and never\nopt into our `localStorage` ledger. Short-circuiting on `enabled` above the slot check hid that\nhost's markup outright, since the slot is the only thing in the container.",
4016
+ "description": "Whether the build-history section has anything in it — the built-in row list, or a host's own\nmarkup in the `settings-cost-history` slot. See\n`SettingsModalController.costHistorySectionVisible` for why slotted content is\nchecked ahead of `enabled`.",
4247
4017
  "readonly": true
4248
4018
  },
4249
4019
  {
@@ -4252,7 +4022,7 @@
4252
4022
  "type": {
4253
4023
  "text": "boolean"
4254
4024
  },
4255
- "description": "Whether the \"Build history\" heading has anything under it.\n\nBroader than FoundationAiAssistant.settingsCostHistorySectionVisible because the\nheading also covers the lifetime total, which renders before the first row exists (rows are\nwritten as spend accrues, the lifetime section only needs history switched on). Gating the\nheading on the rows alone left that total sitting under nothing.",
4025
+ "description": "Whether the \"Build history\" heading has anything under it — broader than\nFoundationAiAssistant.settingsCostHistorySectionVisible, because the\nheading also covers the lifetime total, which renders before the first row exists.",
4256
4026
  "readonly": true
4257
4027
  },
4258
4028
  {
@@ -4270,7 +4040,7 @@
4270
4040
  "type": {
4271
4041
  "text": "boolean"
4272
4042
  },
4273
- "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.",
4043
+ "description": "Whether the lifetime-usage section has anything to say. Requires build history\nAND a resolvable state key — see `SettingsModalController.costLifetimeVisible`.",
4274
4044
  "readonly": true
4275
4045
  },
4276
4046
  {
@@ -4362,45 +4132,6 @@
4362
4132
  ],
4363
4133
  "description": "Sync the active settings modal tab when the design-system tab bar changes."
4364
4134
  },
4365
- {
4366
- "kind": "method",
4367
- "name": "resetSettingsModalTabsOnOpen",
4368
- "privacy": "private",
4369
- "return": {
4370
- "type": {
4371
- "text": "void"
4372
- }
4373
- },
4374
- "description": "Ensure the settings tab is selected when the modal opens."
4375
- },
4376
- {
4377
- "kind": "method",
4378
- "name": "resetSettingsModalTabsOnClose",
4379
- "privacy": "private",
4380
- "return": {
4381
- "type": {
4382
- "text": "void"
4383
- }
4384
- },
4385
- "description": "Return to the settings tab when the modal closes on Cost."
4386
- },
4387
- {
4388
- "kind": "method",
4389
- "name": "snapSettingsModalTabIndicator",
4390
- "privacy": "private",
4391
- "return": {
4392
- "type": {
4393
- "text": "void"
4394
- }
4395
- },
4396
- "parameters": [
4397
- {
4398
- "name": "retry",
4399
- "default": "true"
4400
- }
4401
- ],
4402
- "description": "Snap the tab underline to the settings tab without animating from Cost.\nUsed after hide/show when aria state is correct but the indicator drifted."
4403
- },
4404
4135
  {
4405
4136
  "kind": "field",
4406
4137
  "name": "pinnedAgentHint",
@@ -4513,7 +4244,7 @@
4513
4244
  "text": "DiagnosticEntry"
4514
4245
  }
4515
4246
  },
4516
- "description": "The single `meta-snapshot` entry for right now — the export-time `meta` block\n(agent summary, active prompt/state, context + cost) plus the `dedupSignature`\nthe forward-capture delta keys on.\n\nSeparate from `buildDiagnosticEntries` because the download path needs a\nFRESH one on its own: the persisted stream only re-appends this block when the\nnear-static config changes, so the newest stored snapshot's volatile half — the\n`context` figures especially — is typically frozen at the session's first flush."
4247
+ "description": "The `meta-snapshot` entry for right now. The assembly is a pure transformation\nin `buildMetaSnapshot`; this gathers what it reports.\n\nThe live agent comes from the DRIVER, not `this.activeAgent` (the redux-stripped\nprojection, no functions) and not a lookup in `this.agents` (which may mint\nfresh, never-activated configs) — only the driver holds the instance whose\n`onActivate` ran, so only its `getDebugSnapshot` closure holds state."
4517
4248
  },
4518
4249
  {
4519
4250
  "kind": "method",
@@ -4735,101 +4466,6 @@
4735
4466
  }
4736
4467
  ]
4737
4468
  },
4738
- {
4739
- "kind": "method",
4740
- "name": "isAcceptedFile",
4741
- "privacy": "private",
4742
- "return": {
4743
- "type": {
4744
- "text": "boolean"
4745
- }
4746
- },
4747
- "parameters": [
4748
- {
4749
- "name": "file",
4750
- "type": {
4751
- "text": "File"
4752
- }
4753
- }
4754
- ]
4755
- },
4756
- {
4757
- "kind": "method",
4758
- "name": "readFileAsText",
4759
- "privacy": "private",
4760
- "return": {
4761
- "type": {
4762
- "text": "Promise<string>"
4763
- }
4764
- },
4765
- "parameters": [
4766
- {
4767
- "name": "file",
4768
- "type": {
4769
- "text": "File"
4770
- }
4771
- }
4772
- ]
4773
- },
4774
- {
4775
- "kind": "method",
4776
- "name": "readFileAsBase64",
4777
- "privacy": "private",
4778
- "return": {
4779
- "type": {
4780
- "text": "Promise<string>"
4781
- }
4782
- },
4783
- "parameters": [
4784
- {
4785
- "name": "file",
4786
- "type": {
4787
- "text": "File"
4788
- }
4789
- }
4790
- ],
4791
- "description": "The base64 payload of a file, without any `data:` prefix — the raw form\n`ChatImageAttachment.data` specifies (transports add their own framing)."
4792
- },
4793
- {
4794
- "kind": "method",
4795
- "name": "processFiles",
4796
- "privacy": "private",
4797
- "return": {
4798
- "type": {
4799
- "text": "Promise<{ attachments: ChatAttachment[]; errors: string[] }>"
4800
- }
4801
- },
4802
- "parameters": [
4803
- {
4804
- "name": "files",
4805
- "type": {
4806
- "text": "File[]"
4807
- }
4808
- },
4809
- {
4810
- "name": "existingBytes",
4811
- "default": "0"
4812
- }
4813
- ]
4814
- },
4815
- {
4816
- "kind": "method",
4817
- "name": "loadSelectedFiles",
4818
- "privacy": "private",
4819
- "return": {
4820
- "type": {
4821
- "text": "Promise<void>"
4822
- }
4823
- },
4824
- "parameters": [
4825
- {
4826
- "name": "e",
4827
- "type": {
4828
- "text": "Event"
4829
- }
4830
- }
4831
- ]
4832
- },
4833
4469
  {
4834
4470
  "kind": "method",
4835
4471
  "name": "handleSendClick"
@@ -4949,7 +4585,7 @@
4949
4585
  "type": {
4950
4586
  "text": "boolean"
4951
4587
  },
4952
- "description": "Whether the message input shows a drag handle to resize its height.\nOn by default; hosts opt out via `chatConfig.ui.resizableInput: false`.",
4588
+ "description": "Whether the message input shows a drag handle to resize its height.",
4953
4589
  "readonly": true
4954
4590
  },
4955
4591
  {
@@ -4958,7 +4594,7 @@
4958
4594
  "type": {
4959
4595
  "text": "string"
4960
4596
  },
4961
- "description": "Inline style for the chat input: writes the chosen height into the\n`--composer-height` custom property (read by `.chat-input` + its control in\n`main.styles.ts`). Empty until the user drags (or when resizing is disabled),\nso the default applies.",
4597
+ "description": "Inline style carrying the dragged height into `--composer-height`.",
4962
4598
  "readonly": true
4963
4599
  },
4964
4600
  {
@@ -4976,8 +4612,7 @@
4976
4612
  "text": "PointerEvent"
4977
4613
  }
4978
4614
  }
4979
- ],
4980
- "description": "Begin a composer resize drag. Captures the pointer so move/up keep firing\non the handle even when the cursor leaves its 7px band."
4615
+ ]
4981
4616
  },
4982
4617
  {
4983
4618
  "kind": "method",
@@ -4994,8 +4629,7 @@
4994
4629
  "text": "PointerEvent"
4995
4630
  }
4996
4631
  }
4997
- ],
4998
- "description": "Resize while dragging. Dragging up (clientY decreases) grows the composer;\nthe result is clamped between the natural height (lower) and the available\nheadroom/max (upper). No-op unless a drag is active (the handle holds pointer\ncapture), so plain hover-moves are ignored."
4632
+ ]
4999
4633
  },
5000
4634
  {
5001
4635
  "kind": "method",
@@ -5012,8 +4646,7 @@
5012
4646
  "text": "PointerEvent"
5013
4647
  }
5014
4648
  }
5015
- ],
5016
- "description": "End a resize drag and release the pointer capture."
4649
+ ]
5017
4650
  }
5018
4651
  ],
5019
4652
  "events": [
@@ -5127,6 +4760,22 @@
5127
4760
  "module": "src/main/main.ts"
5128
4761
  }
5129
4762
  },
4763
+ {
4764
+ "kind": "js",
4765
+ "name": "budgetPercentOf",
4766
+ "declaration": {
4767
+ "name": "budgetPercentOf",
4768
+ "module": "./budget-controller"
4769
+ }
4770
+ },
4771
+ {
4772
+ "kind": "js",
4773
+ "name": "formatBlockedReason",
4774
+ "declaration": {
4775
+ "name": "formatBlockedReason",
4776
+ "module": "./budget-controller"
4777
+ }
4778
+ },
5130
4779
  {
5131
4780
  "kind": "js",
5132
4781
  "name": "FoundationAiAssistant",
@@ -5221,6 +4870,12 @@
5221
4870
  }
5222
4871
  ]
5223
4872
  },
4873
+ {
4874
+ "kind": "javascript-module",
4875
+ "path": "src/main/meta-snapshot.ts",
4876
+ "declarations": [],
4877
+ "exports": []
4878
+ },
5224
4879
  {
5225
4880
  "kind": "javascript-module",
5226
4881
  "path": "src/provider/ai-provider-switcher.ts",
@@ -9484,6 +9139,32 @@
9484
9139
  ],
9485
9140
  "description": "Build the `done` loop result, carrying the typed failure reason when the turn\nbailed (PTC-0). The discriminant stays `'done'` either way — the same value a\nclean turn returns — so consumers matching on `reason === 'done'` are unchanged;\n`failureReason` is simply present on a failure and absent on success. Omitted\n(rather than set to `undefined`) so a happy-path result stays byte-identical to\nthe historical `{ reason: 'done' }`."
9486
9141
  },
9142
+ {
9143
+ "kind": "method",
9144
+ "name": "endTurnWithFailure",
9145
+ "privacy": "private",
9146
+ "return": {
9147
+ "type": {
9148
+ "text": "ChatDriverResult"
9149
+ }
9150
+ },
9151
+ "parameters": [
9152
+ {
9153
+ "name": "exit",
9154
+ "type": {
9155
+ "text": "TurnFailureExit"
9156
+ }
9157
+ },
9158
+ {
9159
+ "name": "overrides",
9160
+ "optional": true,
9161
+ "type": {
9162
+ "text": "{\n copy?: string;\n budget?: BudgetDetail;\n providerRefused?: ProviderRefusedDetail;\n }"
9163
+ }
9164
+ }
9165
+ ],
9166
+ "description": "End the turn on a terminal failure: fail upward when this driver is a sub-agent, speak\nto the user when it is not, and hand the reason to the caller either way.\n\nOnly that fork lives here. Each exit keeps its own `recordTurnError` call, because the\ntelemetry payload is what legitimately differs between them — attempt counts, token\nfigures, tool names — and folding it in would mean one optional field per condition.\n\n`overrides.copy` serves the two walls whose prose is composed per occurrence:\n`budgetExhaustedBubble` names the vendor and can offer another, `providerRefusedBubble`\nprefers a host-supplied message. Every other exit reads its copy from the table.\n\nOne exit stays out: the `BudgetExhaustedError` catch. Not because its shape differs —\nits two telemetry payloads are field-for-field identical — but because its main-turn\nbranch calls `reportBudgetExhausted`, which is public and also serves\n`OrchestratingDriver`'s classification phase, where no tool loop is running. That\nmethod has to survive either way, so routing the catch through here would relocate the\nduplication rather than remove it."
9167
+ },
9487
9168
  {
9488
9169
  "kind": "method",
9489
9170
  "name": "reportBudgetExhausted",
@@ -9519,25 +9200,6 @@
9519
9200
  },
9520
9201
  "description": "The sentence shown when the upstream provider refuses (GENC-1506).\n\nDeliberately far simpler than `budgetExhaustedBubble`: no vendor name, no figures, no\nswitch-provider advice, and no branching on `kind`. The copy is cause-free by design — a user who\ncan see their own remaining spend must not be told about a limit, and we must not imply the bill\nhas gone unpaid — so there is nothing here to compose. A host override wins verbatim."
9521
9202
  },
9522
- {
9523
- "kind": "method",
9524
- "name": "reportProviderRefused",
9525
- "privacy": "private",
9526
- "return": {
9527
- "type": {
9528
- "text": "ChatDriverResult"
9529
- }
9530
- },
9531
- "parameters": [
9532
- {
9533
- "name": "e",
9534
- "type": {
9535
- "text": "ProviderRefusedError"
9536
- }
9537
- }
9538
- ],
9539
- "description": "Terminal provider-refusal outcome. Mirrors `reportBudgetExhausted` so the two walls behave\nidentically from the caller's side, while keeping their diagnostics distinct.\n\n`kind` reaches the debug log and the result but never the transcript: it is what lets an operator\ntell \"top up the account\" from \"rotate the key\", and with cause-free user copy this is the only\nplace that distinction survives."
9540
- },
9541
9203
  {
9542
9204
  "kind": "method",
9543
9205
  "name": "failureReasonOf",
@@ -10709,6 +10371,57 @@
10709
10371
  }
10710
10372
  ]
10711
10373
  },
10374
+ {
10375
+ "kind": "javascript-module",
10376
+ "path": "src/components/chat-driver/turn-failures.ts",
10377
+ "declarations": [
10378
+ {
10379
+ "kind": "variable",
10380
+ "name": "TURN_FAILURE_EXITS",
10381
+ "type": {
10382
+ "text": "object"
10383
+ },
10384
+ "default": "{\n contextGuard: {\n subReason: 'context_exhausted',\n resultReason: 'context-exhausted',\n copy:\n 'I had to stop here — this conversation has filled the available context. ' +\n 'Compact it to free up room, then ask me to continue.',\n },\n contextOverflow: {\n subReason: 'context_exhausted',\n resultReason: 'context-exhausted',\n copy: DEFAULT_CONTEXT_OVERFLOW_MESSAGE,\n },\n malformedToolCall: {\n subReason: 'malformed_tool_call',\n // Not `malformed-tool-call`: the main-turn reason is named after the provider finish\n // reason (`MALFORMED_FUNCTION_CALL`) that produces it, and is part of the published\n // TurnFailureReason union, so the two names cannot be brought into line here.\n resultReason: 'malformed-function-call',\n copy: 'While working on your request, I repeatedly called my tools incorrectly. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',\n },\n responseTruncated: {\n subReason: 'response_truncated',\n resultReason: 'response-truncated',\n copy: 'My response was cut off because a single step reached the model output limit. This usually means one step tried to produce too much at once — try breaking your request into smaller steps.',\n },\n timeout: {\n subReason: 'timeout',\n // There is no `timeout` member of TurnFailureReason, so the main turn reports the\n // generic `exception`. A sub-agent gets the specific reason because its parent\n // branches on it to decide whether a retry is worth attempting.\n resultReason: 'exception',\n copy: 'The request timed out. You can ask me to try again, or break this into a smaller step.',\n },\n unknownToolLimit: {\n subReason: 'unknown_tool_limit',\n resultReason: 'unknown-tool-limit',\n copy: \"I'm sorry, I repeatedly tried to use tools that aren't available to me, so I couldn't complete that. If a 'Download agent log' option appears in the Settings (cog) menu, you can download the log and share it with whoever set up this assistant to help fix the issue.\",\n },\n maxIterations: {\n subReason: 'max_iterations',\n resultReason: 'max-iterations',\n copy: \"I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.\",\n },\n refusal: {\n subReason: 'refusal',\n resultReason: 'refusal',\n copy: 'This request was declined. Try rephrasing it, or breaking it into smaller steps.',\n },\n emptyResponse: {\n subReason: 'empty_response',\n resultReason: 'empty-response',\n copy: 'While working on your request, I repeatedly generated a blank response. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',\n },\n providerRefused: {\n subReason: 'provider_refused',\n resultReason: 'provider-refused',\n // Overridden per wall by `ChatDriver.providerRefusedBubble`, which prefers a\n // host-supplied message; this is the fallback that method returns.\n copy: DEFAULT_PROVIDER_REFUSED_MESSAGE,\n },\n budgetExhausted: {\n subReason: 'budget_exhausted',\n resultReason: 'budget-exhausted',\n // Overridden per wall by `ChatDriver.budgetExhaustedBubble`, which upgrades this with\n // \"switch to another AI\" when a second vendor is reachable.\n copy: DEFAULT_BUDGET_EXHAUSTED_MESSAGE,\n },\n // `satisfies`, not an annotation: an annotation would widen `resultReason` to\n // `TurnFailureReason` and cost the literal types that let `turn-failures.test.ts` pin the\n // two deliberate divergences at compile time instead of at runtime.\n}",
10385
+ "description": "The terminal outcomes of a tool loop.\n\n`contextGuard` and `contextOverflow` share a `subReason` because one failure is reached\ntwo ways the user experiences differently: the guard stops at the edge of the window with\nthe transcript intact, so its copy can point at compaction, while an overflow means the\nprovider has already rejected the request.\n\nThree entries take their copy from `@genesislcap/foundation-ai`, which owns the default\nmessage for every failure it also exports an error class for. The rest is for conditions\nthis driver detects itself, which have no upstream home."
10386
+ },
10387
+ {
10388
+ "kind": "variable",
10389
+ "name": "satisfies",
10390
+ "description": "The terminal outcomes of a tool loop.\n\n`contextGuard` and `contextOverflow` share a `subReason` because one failure is reached\ntwo ways the user experiences differently: the guard stops at the edge of the window with\nthe transcript intact, so its copy can point at compaction, while an overflow means the\nprovider has already rejected the request.\n\nThree entries take their copy from `@genesislcap/foundation-ai`, which owns the default\nmessage for every failure it also exports an error class for. The rest is for conditions\nthis driver detects itself, which have no upstream home."
10391
+ },
10392
+ {
10393
+ "kind": "variable",
10394
+ "name": "Record",
10395
+ "description": "The terminal outcomes of a tool loop.\n\n`contextGuard` and `contextOverflow` share a `subReason` because one failure is reached\ntwo ways the user experiences differently: the guard stops at the edge of the window with\nthe transcript intact, so its copy can point at compaction, while an overflow means the\nprovider has already rejected the request.\n\nThree entries take their copy from `@genesislcap/foundation-ai`, which owns the default\nmessage for every failure it also exports an error class for. The rest is for conditions\nthis driver detects itself, which have no upstream home."
10396
+ }
10397
+ ],
10398
+ "exports": [
10399
+ {
10400
+ "kind": "js",
10401
+ "name": "TURN_FAILURE_EXITS",
10402
+ "declaration": {
10403
+ "name": "TURN_FAILURE_EXITS",
10404
+ "module": "src/components/chat-driver/turn-failures.ts"
10405
+ }
10406
+ },
10407
+ {
10408
+ "kind": "js",
10409
+ "name": "satisfies",
10410
+ "declaration": {
10411
+ "name": "satisfies",
10412
+ "module": "src/components/chat-driver/turn-failures.ts"
10413
+ }
10414
+ },
10415
+ {
10416
+ "kind": "js",
10417
+ "name": "Record",
10418
+ "declaration": {
10419
+ "name": "Record",
10420
+ "module": "src/components/chat-driver/turn-failures.ts"
10421
+ }
10422
+ }
10423
+ ]
10424
+ },
10712
10425
  {
10713
10426
  "kind": "javascript-module",
10714
10427
  "path": "src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts",
@@ -12023,6 +11736,46 @@
12023
11736
  "path": "src/components/settings-modal/index.ts",
12024
11737
  "declarations": [],
12025
11738
  "exports": [
11739
+ {
11740
+ "kind": "js",
11741
+ "name": "SettingsModalController",
11742
+ "declaration": {
11743
+ "name": "SettingsModalController",
11744
+ "module": "./settings-modal"
11745
+ }
11746
+ },
11747
+ {
11748
+ "kind": "js",
11749
+ "name": "type",
11750
+ "declaration": {
11751
+ "name": "type",
11752
+ "module": "./settings-modal"
11753
+ }
11754
+ },
11755
+ {
11756
+ "kind": "js",
11757
+ "name": "SettingsModalDeps",
11758
+ "declaration": {
11759
+ "name": "SettingsModalDeps",
11760
+ "module": "./settings-modal"
11761
+ }
11762
+ },
11763
+ {
11764
+ "kind": "js",
11765
+ "name": "type",
11766
+ "declaration": {
11767
+ "name": "type",
11768
+ "module": "./settings-modal"
11769
+ }
11770
+ },
11771
+ {
11772
+ "kind": "js",
11773
+ "name": "SettingsModalTabsElement",
11774
+ "declaration": {
11775
+ "name": "SettingsModalTabsElement",
11776
+ "module": "./settings-modal"
11777
+ }
11778
+ },
12026
11779
  {
12027
11780
  "kind": "js",
12028
11781
  "name": "SettingsModalTemplate",
@@ -12113,6 +11866,12 @@
12113
11866
  }
12114
11867
  ]
12115
11868
  },
11869
+ {
11870
+ "kind": "javascript-module",
11871
+ "path": "src/components/settings-modal/settings-modal.ts",
11872
+ "declarations": [],
11873
+ "exports": []
11874
+ },
12116
11875
  {
12117
11876
  "kind": "javascript-module",
12118
11877
  "path": "src/state/persistence/build-timeline-entries.ts",