@genesislcap/ai-assistant 15.23.3 → 15.24.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 (46) hide show
  1. package/dist/ai-assistant.api.json +419 -50
  2. package/dist/ai-assistant.d.ts +217 -683
  3. package/dist/chat-driver.cjs.map +1 -1
  4. package/dist/chat-driver.mjs.map +1 -1
  5. package/dist/custom-elements.json +310 -609
  6. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +16 -0
  7. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  8. package/dist/dts/components/settings-modal/index.d.ts +1 -0
  9. package/dist/dts/components/settings-modal/index.d.ts.map +1 -1
  10. package/dist/dts/components/settings-modal/settings-modal.d.ts +215 -0
  11. package/dist/dts/components/settings-modal/settings-modal.d.ts.map +1 -0
  12. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  13. package/dist/dts/main/attachment-controller.d.ts +66 -0
  14. package/dist/dts/main/attachment-controller.d.ts.map +1 -0
  15. package/dist/dts/main/budget-controller.d.ts +368 -0
  16. package/dist/dts/main/budget-controller.d.ts.map +1 -0
  17. package/dist/dts/main/composer-menu-controller.d.ts +113 -0
  18. package/dist/dts/main/composer-menu-controller.d.ts.map +1 -0
  19. package/dist/dts/main/composer-resize-controller.d.ts +80 -0
  20. package/dist/dts/main/composer-resize-controller.d.ts.map +1 -0
  21. package/dist/dts/main/context-gate-controller.d.ts +197 -0
  22. package/dist/dts/main/context-gate-controller.d.ts.map +1 -0
  23. package/dist/dts/main/cost-ledger-controller.d.ts +228 -0
  24. package/dist/dts/main/cost-ledger-controller.d.ts.map +1 -0
  25. package/dist/dts/main/main.d.ts +201 -722
  26. package/dist/dts/main/main.d.ts.map +1 -1
  27. package/dist/dts/main/main.types.d.ts +25 -0
  28. package/dist/dts/main/main.types.d.ts.map +1 -1
  29. package/dist/dts/main/meta-snapshot.d.ts +50 -0
  30. package/dist/dts/main/meta-snapshot.d.ts.map +1 -0
  31. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  32. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +18 -0
  33. package/dist/esm/components/settings-modal/index.js +1 -0
  34. package/dist/esm/components/settings-modal/settings-modal.js +436 -0
  35. package/dist/esm/components/settings-modal/settings-modal.template.js +15 -4
  36. package/dist/esm/main/attachment-controller.js +254 -0
  37. package/dist/esm/main/budget-controller.js +745 -0
  38. package/dist/esm/main/composer-menu-controller.js +234 -0
  39. package/dist/esm/main/composer-resize-controller.js +119 -0
  40. package/dist/esm/main/context-gate-controller.js +289 -0
  41. package/dist/esm/main/cost-ledger-controller.js +498 -0
  42. package/dist/esm/main/main.js +501 -2268
  43. package/dist/esm/main/meta-snapshot.js +108 -0
  44. package/dist/esm/state/persistence/session-persister.js +5 -4
  45. package/docs/migration-GENC-1580.md +209 -0
  46. package/package.json +16 -16
@@ -0,0 +1,197 @@
1
+ import type { ChatConfig, ChatMessage } from '@genesislcap/foundation-ai';
2
+ import type { ContextReserveResolution } from '../components/ai-driver/ai-driver';
3
+ import type { ContextGuardPolicy } from '../components/chat-driver/chat-driver';
4
+ import type { CompactionProjection } from '../utils/context-tokens';
5
+ import type { CompactionPlanOptions } from '../utils/history-transform';
6
+ import { type ResolvedContextBudget } from '../utils/resolve-context-budget';
7
+ /** What the controller needs from its host, as callbacks so it holds no element reference. */
8
+ export interface ContextGateDeps {
9
+ chatConfig: () => ChatConfig;
10
+ /**
11
+ * The Redux mirror, not the driver's history. Safe rather than sloppy: the two
12
+ * lists only ever drift at the tail, and nothing here inspects the tail.
13
+ */
14
+ messages: () => readonly ChatMessage[];
15
+ /** The live gauge figure — what the context indicator shows. */
16
+ contextTokens: () => number | undefined;
17
+ /**
18
+ * The figure the GATE is judged against: `contextTokens` as of the last moment
19
+ * no turn was running (GENC-1582). Distinct from the live gauge on purpose — see
20
+ * the element's `_gateContextTokens`. Falls back to the live figure when unset.
21
+ */
22
+ gateContextTokens: () => number | undefined;
23
+ contextLimit: () => number | undefined;
24
+ /**
25
+ * Headroom the next turn needs and which agent set it, resolved by the driver
26
+ * against live routing state (GENC-1582). A callback because reaching it means
27
+ * going through the driver, which the element owns — this controller never
28
+ * touches a driver (§11.2 constraint 2).
29
+ */
30
+ contextReserve: () => ContextReserveResolution | undefined;
31
+ /**
32
+ * The preamble the driver actually measured on the last request, when it builds
33
+ * requests at all. Replaces the estimate this controller used to compute (and
34
+ * memoize) from the transcript: a measurement beats a walk, and it is why the
35
+ * `systemOverhead` input to `resolveContextBudget` is gone.
36
+ */
37
+ preambleTokens: () => number | undefined;
38
+ /** The driver's plan for the exact history `compact()` acts on. */
39
+ compactionPlanFor: (options: CompactionPlanOptions) => CompactionProjection | null;
40
+ /** A budget wall outranks the gate, so the notice getters have to see it. */
41
+ bannerVisible: () => boolean;
42
+ effectiveBlockedReason: () => string;
43
+ /** Non-null while a guided flow owns the turn — a flow is warned, never blocked. */
44
+ flowOwnerAgentName: () => string | null;
45
+ }
46
+ /**
47
+ * The context gate: how close the conversation is to the model's window, what
48
+ * compacting would reclaim, and the copy that states both (GENC-1567).
49
+ *
50
+ * Owns **no state at all** — every member derives from the callbacks above. That
51
+ * is what makes it the safest of these extractions: there is no field to forget
52
+ * to mark `@observable`, so the observability trap in `docs/code-health.md` §11.4
53
+ * cannot bite here.
54
+ *
55
+ * @internal
56
+ */
57
+ export declare class ContextGateController {
58
+ private readonly deps;
59
+ constructor(deps: ContextGateDeps);
60
+ /** Window, warn/block thresholds and compaction sizing for the active model. */
61
+ get budget(): ResolvedContextBudget;
62
+ /**
63
+ * What compacting right now would reclaim, or `null` when it would not be worth
64
+ * it. Sized against {@link ContextGateController.budget}, so the plan aims at
65
+ * this model's window rather than a fixed default.
66
+ *
67
+ * Reads `messages` to stay reactive as the conversation grows: the driver's
68
+ * history is not a tracked observable, so every consumer keys its re-evaluation
69
+ * off the Redux mirror.
70
+ *
71
+ * Deliberately NOT memoized on `messages` identity: it runs against the driver's
72
+ * history, which can move without the mirror moving (a mid-turn `condenseWhen`
73
+ * rewrite), so an identity key would risk serving a plan for a transcript the
74
+ * driver no longer has.
75
+ */
76
+ get plan(): CompactionProjection | null;
77
+ /**
78
+ * Guard policy pushed to the driver, or `undefined` when the host has switched
79
+ * the gate off — so `enabled: false` really does mean no local blocking of any
80
+ * kind, UI or mid-loop.
81
+ *
82
+ * A MARGIN, not a threshold: the driver resolves the window of the provider each
83
+ * call actually goes to. The fallback window rides along because the driver
84
+ * cannot learn it any other way — without it, a host that knows its window while
85
+ * its provider does not report one had `fallbackContextLimit` protecting the
86
+ * composer and nothing else.
87
+ */
88
+ get guardPolicy(): ContextGuardPolicy | undefined;
89
+ /**
90
+ * How close this conversation is to the model's context window.
91
+ *
92
+ * **Derived, never latched** — the opposite of the budget wall. A wall stays set
93
+ * because nothing the user does fixes it; this clears itself the moment the
94
+ * conversation shrinks, which is precisely what the user is being asked to do.
95
+ *
96
+ * **A flow in progress is never blocked, only warned.** Machine state lives
97
+ * outside the transcript, so a stateful flow is not desynced by compaction — but
98
+ * the summary can still drop detail the flow's own prompt leans on, and blocking
99
+ * mid-journey would strand the user between a flow they cannot finish and a
100
+ * compaction that is a poor idea right there.
101
+ */
102
+ get gate(): 'ok' | 'warn' | 'blocked';
103
+ /**
104
+ * WHY the composer is blocked, when it is (GENC-1582). `undefined` unless
105
+ * {@link ContextGateController.gate} is `'blocked'`.
106
+ *
107
+ * - `'transcript'` — the conversation has grown into the reserve.
108
+ * - `'model'` — the reserve meets or exceeds the whole window, so `blockAt` is 0
109
+ * and EVERY message is refused, including the first of an empty chat.
110
+ *
111
+ * Keyed off `blockAt === 0`, deliberately NOT off whether a compaction plan
112
+ * exists. A null plan has two unrelated causes — nothing left worth summarizing,
113
+ * versus a window that cannot host the agent at all — and only the second is a
114
+ * statement about the configuration rather than the transcript.
115
+ *
116
+ * **This drives behaviour, not copy.** It reads as though it should also pick the
117
+ * wording, and GENC-1582 had it do so: the `'model'` branch told the user to
118
+ * switch to a model with a larger context window, or pick a different agent.
119
+ * Removed, because the assistant offers no way to do either — the model follows
120
+ * from the host's provider registry and each agent's `provider` field, and the
121
+ * only user-facing lever is the agent picker, which is triple-gated
122
+ * (`agentPicker` enabled, two or more agents, at least one with
123
+ * `manualSelection.enabled`) and commonly absent. Copy that names a control the
124
+ * user does not have is worse than copy that names a remedy which merely turns
125
+ * out to be insufficient, and `contextGateReason` is not host-overridable, so
126
+ * nobody downstream could correct it either. Both causes now read the GENC-1567
127
+ * sentence.
128
+ *
129
+ * What it still decides is `sessionMenuForcedByContext`: the transcript cause
130
+ * forces the session menu open so a user told to compact can reach Compact, and
131
+ * the model cause deliberately does not, because there Compact and Clear are
132
+ * both futile and a menu of dead actions helps nobody.
133
+ *
134
+ * The block itself is correct in both cases and must stay: an agent that
135
+ * measurably cannot complete a turn in this window would otherwise start one and
136
+ * die mid-loop on a provider context error, with partial writes and no clean
137
+ * exit. Refusing up front is the better failure.
138
+ */
139
+ get blockCause(): 'transcript' | 'model' | undefined;
140
+ /**
141
+ * What the composer notice says about the gate.
142
+ *
143
+ * The copy is a function of whether compaction can actually clear it, which is
144
+ * why it reads {@link ContextGateController.plan} rather than assuming. Telling
145
+ * someone to compact when the projection says it would not free enough — a tail
146
+ * that is one indivisible tool result, say — sends them into a summarizer call
147
+ * that leaves them exactly where they were.
148
+ */
149
+ get gateReason(): string;
150
+ /**
151
+ * Short label the session-menu pill wears while the gate is active — the primary
152
+ * visible signal, in place of a banner.
153
+ *
154
+ * It states the CONDITION only; the remedy lives in the placeholder once the
155
+ * composer is actually blocked, which is the moment the user needs to be told
156
+ * what to do rather than merely warned.
157
+ */
158
+ get gateLabel(): string;
159
+ /** Tooltip and hint for the Compact row — why it is or is not available. */
160
+ get compactHint(): string;
161
+ /**
162
+ * Whether the menu's "Compact" action would do anything. Delegates to the
163
+ * driver's plan so the gate uses the exact same history `compact()` acts on,
164
+ * judged against the same budget — one source of truth for both whether a cut is
165
+ * legal and whether it reclaims enough.
166
+ */
167
+ get compactable(): boolean;
168
+ /**
169
+ * Whether the composer notice has anything to show — a budget wall or the gate.
170
+ * One region for both, so the live region stays single and registered.
171
+ */
172
+ get noticeVisible(): boolean;
173
+ /**
174
+ * Text for that region. A budget wall outranks the gate: it is the condition the
175
+ * user cannot clear, so it must not be hidden behind advice about compacting that
176
+ * would not restore sending anyway.
177
+ */
178
+ get noticeText(): string;
179
+ /**
180
+ * Whether the notice region should be announced but not drawn.
181
+ *
182
+ * The gate shows itself visually through the pill's label, the composer outline
183
+ * and the placeholder — a strip of prose on top would be a fourth statement of
184
+ * the same thing. But dropping the region outright would be an accessibility
185
+ * regression: a `role="status"` region announces MUTATIONS to a region already
186
+ * being observed, and it is the only announcement available here, because the
187
+ * composer flips to `disabled` at the same moment and a disabled control is out
188
+ * of the tab order — so neither the placeholder nor an `aria-label` on it is ever
189
+ * voiced.
190
+ *
191
+ * So the region stays mounted, keeps its text and keeps announcing; only the
192
+ * visual treatment is withheld. A budget wall still draws its banner, because
193
+ * that condition has no other visible expression.
194
+ */
195
+ get noticeSrOnly(): boolean;
196
+ }
197
+ //# sourceMappingURL=context-gate-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-gate-controller.d.ts","sourceRoot":"","sources":["../../../src/main/context-gate-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE1E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,iCAAiC,CAAC;AAEzC,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,UAAU,CAAC;IAC7B;;;OAGG;IACH,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;IACvC,gEAAgE;IAChE,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC5C,YAAY,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;OAKG;IACH,cAAc,EAAE,MAAM,wBAAwB,GAAG,SAAS,CAAC;IAC3D;;;;;OAKG;IACH,cAAc,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACzC,mEAAmE;IACnE,iBAAiB,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,oBAAoB,GAAG,IAAI,CAAC;IACnF,6EAA6E;IAC7E,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,sBAAsB,EAAE,MAAM,MAAM,CAAC;IACrC,oFAAoF;IACpF,kBAAkB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,eAAe;IAElD,gFAAgF;IAChF,IACI,MAAM,IAAI,qBAAqB,CAclC;IAED;;;;;;;;;;;;;OAaG;IACH,IACI,IAAI,IAAI,oBAAoB,GAAG,IAAI,CAQtC;IAED;;;;;;;;;;OAUG;IACH,IACI,WAAW,IAAI,kBAAkB,GAAG,SAAS,CAMhD;IAED;;;;;;;;;;;;OAYG;IACH,IACI,IAAI,IAAI,IAAI,GAAG,MAAM,GAAG,SAAS,CAWpC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,IACI,UAAU,IAAI,YAAY,GAAG,OAAO,GAAG,SAAS,CAGnD;IAED;;;;;;;;OAQG;IACH,IACI,UAAU,IAAI,MAAM,CAyBvB;IAED;;;;;;;OAOG;IACH,IACI,SAAS,IAAI,MAAM,CAStB;IAED,4EAA4E;IAC5E,IACI,WAAW,IAAI,MAAM,CAKxB;IAED;;;;;OAKG;IACH,IACI,WAAW,IAAI,OAAO,CAEzB;IAED;;;OAGG;IACH,IACI,aAAa,IAAI,OAAO,CAE3B;IAED;;;;OAIG;IACH,IACI,UAAU,IAAI,MAAM,CAGvB;IAED;;;;;;;;;;;;;;;OAeG;IACH,IACI,YAAY,IAAI,OAAO,CAE1B;CACF"}
@@ -0,0 +1,228 @@
1
+ import type { AggregateUsage, AIProviderRegistryStatusEntry, ChatConfig, ChatMessage } from '@genesislcap/foundation-ai';
2
+ import type { SessionPersistenceProvider } from '../state/persistence/session-persistence-provider';
3
+ import { type CostSessionModelEntry, type CostSessionRecord } from '../utils/cost-session-history';
4
+ import { type ResolvedCostHistoryConfig } from '../utils/resolve-cost-history-config';
5
+ /** What the controller needs from its host, as callbacks so it holds no element reference. */
6
+ export interface CostLedgerDeps {
7
+ chatConfig: () => ChatConfig;
8
+ /** The transcript, for title derivation and the per-row model list. */
9
+ messages: () => readonly ChatMessage[];
10
+ /** Live session usage — the figure every row is a projection of. */
11
+ sessionUsage: () => AggregateUsage;
12
+ providerStatuses: () => AIProviderRegistryStatusEntry[];
13
+ /**
14
+ * `persistence.enabled && persistence.provider` — whether the transcript on screen
15
+ * speaks for this project's history. The whole banking split turns on it; see
16
+ * {@link CostLedgerController.bankedUsageFor}.
17
+ */
18
+ persistenceActive: () => boolean;
19
+ /** The *wired* session key, which can lag the attributes during a deferred switch. */
20
+ stateKey: () => string | undefined;
21
+ headerTitle: () => string | undefined;
22
+ /** The host's persistence provider, whose cost-history methods are optional. */
23
+ provider: () => SessionPersistenceProvider | undefined;
24
+ /** The element `id`, the middle fallback for the history scope. */
25
+ elementId: () => string;
26
+ }
27
+ /**
28
+ * The cost ledger: this session's usage, the persisted per-project build history it
29
+ * upserts into, and the pre-transcript ("banked") spend that makes a row report a
30
+ * project's LIFETIME total rather than whatever is currently on screen (GENC-1475).
31
+ *
32
+ * The invariant the whole file protects: **a row moves forward, never backwards.**
33
+ * Every figure is derived from state rather than incremented, so the several element
34
+ * instances one session sees — layout re-renders, popout, docking — write the same
35
+ * row instead of each adding a contribution. `cost-session-banking.test.ts` pins the
36
+ * cases where earlier attempts got this wrong in both directions.
37
+ *
38
+ * @internal
39
+ */
40
+ export declare class CostLedgerController {
41
+ private readonly deps;
42
+ /**
43
+ * Persisted rows, newest write wins. Observable because the Usage tab renders them
44
+ * (through the host's `costSessionHistory` and `sortedCostSessionHistory`), and
45
+ * because the derived lifetime totals below have to re-run when a row lands.
46
+ */
47
+ history: CostSessionRecord[];
48
+ /**
49
+ * Title tracked for the in-flight session, and the session key it accrued under so
50
+ * a title change can tell a rename (same key) from a project switch (key already
51
+ * flipped) — see {@link CostLedgerController.syncActiveTitle}.
52
+ *
53
+ * Not observable: read only by the title getter's own fallback chain, which already
54
+ * depends on `chatConfig` and `messages`, and written only from lifecycle hooks.
55
+ */
56
+ private activeTitle;
57
+ private activeKey?;
58
+ /**
59
+ * What the last read of the backing store told us, and the two gates every write
60
+ * consults. Neither is observable — nothing renders them.
61
+ *
62
+ * `loaded`: whether the row list reflects storage yet. Gates writes so a project's
63
+ * banked spend is never derived from a ledger that simply hasn't arrived.
64
+ *
65
+ * `unreadable`: set when a provider read failed, latching writes off for this
66
+ * page-load. `saveCostHistory` replaces the whole stored list, so writing anything
67
+ * derived from the empty list we fell back to would destroy rows we never read.
68
+ *
69
+ * Private, though `cost-session-banking.test.ts` drives both to model a provider
70
+ * read that is in flight or has failed. That works because the test reaches them
71
+ * through an `as unknown as { costLedger: … }` structural cast, which erases the
72
+ * class type — so exposing them bought nothing and only put a mutable latch on the
73
+ * surface of an `@internal` controller (PR #2503 review).
74
+ */
75
+ private loaded;
76
+ private unreadable;
77
+ /** Pending (adopted but unwritten) ledger changes — see {@link CostLedgerController.persist}. */
78
+ private dirty;
79
+ private flushTimer?;
80
+ constructor(deps: CostLedgerDeps);
81
+ /** Resolved cost tab and build-history settings from `chatConfig.costHistory`. */
82
+ get config(): ResolvedCostHistoryConfig;
83
+ /**
84
+ * Distinct models the registry currently resolves to — the live session's model chips.
85
+ *
86
+ * `providerStatuses` carries one entry per registry SLOT, so a vendor configured across several
87
+ * tiers that happen to resolve to the same model reports it once per tier, and the chips
88
+ * rendered it once per tier too. Which tier a model came from is not what these chips are for.
89
+ *
90
+ * Deduped on the label actually displayed, not on `status.model`: a slot whose provider reports
91
+ * no model falls back to the slot's own name, and those are distinct per slot, so keying on the
92
+ * label keeps them from collapsing into one another.
93
+ */
94
+ get uniqueProviderModels(): CostSessionModelEntry[];
95
+ /**
96
+ * Build history for display: most recently worked on first.
97
+ *
98
+ * The stored order is insertion order (see `upsertRecord`), which leaves a project returned to
99
+ * sitting wherever it was first seen — so ordering is applied here, on read, where it also
100
+ * covers rows a host provider supplied.
101
+ */
102
+ get sortedHistory(): CostSessionRecord[];
103
+ /**
104
+ * Lifetime usage across every project in the history scope — cost and all four token
105
+ * buckets. What the Usage tab's lifetime section renders.
106
+ *
107
+ * Rows ONLY — deliberately not rows + live session. Each row holds a project's lifetime
108
+ * usage and the active project's row is kept current on every transcript change, so
109
+ * adding the live total would count this project's spend twice. That double-count is what
110
+ * made the figure grow on every refresh: a page-hide banked a row while the restored
111
+ * session still carried the same spend.
112
+ */
113
+ get cumulativeUsage(): AggregateUsage;
114
+ /** Lifetime USD across every project in the history scope. */
115
+ get cumulativeCostUsd(): number;
116
+ /**
117
+ * Whether the live session figures amount to this project's whole history, which is true
118
+ * exactly when the transcript is restored on load. Drives the Usage tab's live labels
119
+ * ("Project cost" vs "Session cost") so they state the scope rather than imply one.
120
+ *
121
+ * Note this is NOT what makes the *rows* lifetime — those bank their pre-transcript spend
122
+ * (see `CostSessionRecord.banked`) and are lifetime either way.
123
+ */
124
+ get liveUsageIsProjectLifetime(): boolean;
125
+ /** Label shown on the Usage tab for the in-flight session (app title or first user phrase). */
126
+ get liveTitle(): string | undefined;
127
+ /**
128
+ * Write this session's usage into persisted build history as a single row keyed by the
129
+ * session key, replacing any existing row for that key.
130
+ *
131
+ * **Idempotent** — safe to call per turn, on tab close, and on a title change alike.
132
+ * The name is historical: it does NOT end the session or reset the running totals.
133
+ * Resetting them is what used to double-count spend, since the row was banked while the
134
+ * persisted transcript still carried the same usage, so a reload restored it and the
135
+ * Usage tab counted both.
136
+ *
137
+ * No-op unless `chatConfig.costHistory.enabled` is `true`, when the live session has no
138
+ * usage yet, or when the session has no key to upsert against. Gating on the *live*
139
+ * figures (not the banked total) means a reload that restores nothing rewrites nothing.
140
+ */
141
+ finalize(options?: {
142
+ title?: string;
143
+ }): void;
144
+ /**
145
+ * Spend for `projectKey` that predates the current transcript, resolved once per SESSION and
146
+ * shared by every element instance wired to it (see `banked-usage-baselines`), so it cannot
147
+ * drift as the row it came from is rewritten by our own upserts.
148
+ *
149
+ * The two cases turn on whether the transcript is authoritative for this project:
150
+ *
151
+ * - **Transcript restored** (`persistenceActive`): it already accounts for everything after
152
+ * the previous row's own banked figure, so carry that figure forward unchanged. Re-banking
153
+ * `usage` here would add the restored history to itself on every reload.
154
+ * - **Transcript starts empty**: nothing on screen accounts for the previous total, so the
155
+ * whole of it becomes banked. Without this the next turn would rewrite the row downwards
156
+ * to just that turn, losing the project's earlier spend.
157
+ *
158
+ * The baseline is deliberately NOT element or controller state. This element is remounted
159
+ * repeatedly inside a single session (the host re-rendering its layout, popping out, docking)
160
+ * and a fresh instance re-deriving would take the second branch against rows that already hold
161
+ * the live transcript — banking the session as its own baseline. Keying on the session instead
162
+ * means a remount inherits the baseline that transcript was paired with.
163
+ */
164
+ private bankedUsageFor;
165
+ /**
166
+ * Re-baseline this session against an emptied transcript: what came before, plus what the
167
+ * transcript being discarded proved. `discarded` must be read BEFORE the live figures are
168
+ * reset, since that is the only record of the latter.
169
+ *
170
+ * Not the same as dropping the baseline. Dropping it re-derives through `persistenceActive`,
171
+ * and with persistence ON that carries the row's *old* banked figure forward — discarding
172
+ * every turn between the last baseline and the clear. The transcript is gone either way, so
173
+ * the branch that reads it as authoritative no longer applies.
174
+ *
175
+ * Derived from the memoised baseline and the live figure rather than from the project's row,
176
+ * because a row is only as current as the last {@link CostLedgerController.finalize} — which
177
+ * skips its write while the ledger is unloaded, and while the session has no usage yet.
178
+ */
179
+ rebaselineOnClearedTranscript(key: string, discarded: AggregateUsage): void;
180
+ /** Remove one entry from persisted build history. */
181
+ remove(id: string): void;
182
+ /** Clear all persisted build history for this assistant scope. */
183
+ clear(): void;
184
+ private scope;
185
+ /** Re-read the ledger for the current scope. Runs on connect and on every `chatConfig` rebind. */
186
+ reload(): void;
187
+ /**
188
+ * The persistence provider, but only when it implements BOTH cost-history methods.
189
+ *
190
+ * Both or neither: a provider that can save but not load would silently accumulate a
191
+ * ledger nobody ever reads back, so a partial implementation is treated as absent and
192
+ * falls back to `localStorage`.
193
+ */
194
+ private costHistoryProvider;
195
+ /**
196
+ * Adopt `records` as the live list, and schedule a write to whichever backend owns cost
197
+ * history — the provider when the host implements it, else `localStorage`.
198
+ *
199
+ * The list is adopted synchronously because that is what the Usage tab renders; only the
200
+ * *write* is deferred. This runs on every usage change (about once per LLM call, plus once
201
+ * per sub-agent turn) and each write serialises the whole ledger, so coalescing cuts a
202
+ * per-turn `localStorage.setItem` or network round-trip down to one per quiet period. Safe
203
+ * because the write is a full replace of idempotent state: the newest call wins and no
204
+ * intermediate state is meaningful. Flushed on `pagehide` and disconnect so nothing is lost.
205
+ */
206
+ private persist;
207
+ /**
208
+ * Write the pending ledger now. Safe to call when nothing is pending.
209
+ *
210
+ * Provider writes are fire-and-forget and best-effort: the ledger is accounting, not
211
+ * conversation content, so a failed write is logged and never surfaced into the chat. On
212
+ * `pagehide` the `localStorage` path completes synchronously; a provider write is subject to
213
+ * the same unload race as every other network save the assistant makes.
214
+ */
215
+ flush(): void;
216
+ /**
217
+ * Finalize the previous session when the host-provided title changes — a rename should land on
218
+ * the row under its old name rather than silently relabelling accrued spend.
219
+ *
220
+ * Only when the session key still matches the one that title accrued under. Hosts change
221
+ * `costHistory.title` on project SWITCH too, and if the key has already flipped, this would
222
+ * upsert the outgoing project's live usage onto the incoming project's row as
223
+ * `banked + old live`, inflating it. Under the old append model a mis-keyed write left a stray
224
+ * row; with the upsert it overwrites a real one.
225
+ */
226
+ syncActiveTitle(): void;
227
+ }
228
+ //# sourceMappingURL=cost-ledger-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-ledger-controller.d.ts","sourceRoot":"","sources":["../../../src/main/cost-ledger-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,UAAU,EACV,WAAW,EACZ,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mDAAmD,CAAC;AAOpG,OAAO,EAQL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAMvC,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,sCAAsC,CAAC;AAW9C,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,UAAU,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;IACvC,oEAAoE;IACpE,YAAY,EAAE,MAAM,cAAc,CAAC;IACnC,gBAAgB,EAAE,MAAM,6BAA6B,EAAE,CAAC;IACxD;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC;IACjC,sFAAsF;IACtF,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACtC,gFAAgF;IAChF,QAAQ,EAAE,MAAM,0BAA0B,GAAG,SAAS,CAAC;IACvD,mEAAmE;IACnE,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAoB;IA8CnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IA7CjC;;;;OAIG;IACS,OAAO,EAAE,iBAAiB,EAAE,CAAM;IAE9C;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,SAAS,CAAC,CAAS;IAM3B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,iGAAiG;IACjG,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAC,CAAgC;gBAEtB,IAAI,EAAE,cAAc;IAEjD,kFAAkF;IAClF,IACI,MAAM,IAAI,yBAAyB,CAEtC;IAED;;;;;;;;;;OAUG;IACH,IACI,oBAAoB,IAAI,qBAAqB,EAAE,CAiBlD;IAED;;;;;;OAMG;IACH,IACI,aAAa,IAAI,iBAAiB,EAAE,CAEvC;IAED;;;;;;;;;OASG;IACH,IACI,eAAe,IAAI,cAAc,CAKpC;IAED,8DAA8D;IAC9D,IACI,iBAAiB,IAAI,MAAM,CAE9B;IAED;;;;;;;OAOG;IACH,IACI,0BAA0B,IAAI,OAAO,CAExC;IAED,+FAA+F;IAC/F,IACI,SAAS,IAAI,MAAM,GAAG,SAAS,CAKlC;IAED;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA6C5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,cAAc;IAWtB;;;;;;;;;;;;;OAaG;IACH,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,IAAI;IAyC3E,qDAAqD;IACrD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAcxB,kEAAkE;IAClE,KAAK,IAAI,IAAI;IAgBb,OAAO,CAAC,KAAK;IAMb,kGAAkG;IAClG,MAAM,IAAI,IAAI;IAgDd;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,OAAO;IAWf;;;;;;;OAOG;IACH,KAAK,IAAI,IAAI;IAmBb;;;;;;;;;OASG;IACH,eAAe,IAAI,IAAI;CAYxB"}