@genesislcap/ai-assistant 15.3.3 → 15.3.4-GENC-1475.1

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 (73) hide show
  1. package/dist/ai-assistant.api.json +215 -44
  2. package/dist/ai-assistant.d.ts +102 -18
  3. package/dist/chat-driver.cjs +94 -22
  4. package/dist/chat-driver.cjs.map +3 -3
  5. package/dist/chat-driver.mjs +94 -22
  6. package/dist/chat-driver.mjs.map +3 -3
  7. package/dist/custom-elements.json +52 -53
  8. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  9. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  10. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  11. package/dist/dts/main/main.d.ts +51 -13
  12. package/dist/dts/main/main.d.ts.map +1 -1
  13. package/dist/dts/state/ai-assistant-slice.d.ts +11 -8
  14. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  15. package/dist/dts/state/persistence/session-persistence-provider.d.ts +23 -0
  16. package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
  17. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  18. package/dist/dts/state/session-store.d.ts +1 -2
  19. package/dist/dts/state/session-store.d.ts.map +1 -1
  20. package/dist/dts/utils/cost-session-history.d.ts +39 -11
  21. package/dist/dts/utils/cost-session-history.d.ts.map +1 -1
  22. package/dist/dts/utils/resolve-cost-history-config.d.ts +6 -0
  23. package/dist/dts/utils/resolve-cost-history-config.d.ts.map +1 -1
  24. package/dist/dts/utils/sum-costs.d.ts.map +1 -1
  25. package/dist/dts/utils/sum-tokens.d.ts +8 -2
  26. package/dist/dts/utils/sum-tokens.d.ts.map +1 -1
  27. package/dist/dts/utils/sum-usage.d.ts +49 -0
  28. package/dist/dts/utils/sum-usage.d.ts.map +1 -0
  29. package/dist/dts/utils/sum-usage.test.d.ts +2 -0
  30. package/dist/dts/utils/sum-usage.test.d.ts.map +1 -0
  31. package/dist/esm/components/chat-driver/chat-driver.js +6 -0
  32. package/dist/esm/components/settings-modal/settings-modal.styles.js +47 -0
  33. package/dist/esm/components/settings-modal/settings-modal.template.js +77 -5
  34. package/dist/esm/main/main.js +152 -54
  35. package/dist/esm/state/ai-assistant-slice.js +11 -8
  36. package/dist/esm/state/ai-assistant-slice.test.js +12 -5
  37. package/dist/esm/state/debug-event-log.js +2 -2
  38. package/dist/esm/state/persistence/session-persistence.integration.test.js +5 -1
  39. package/dist/esm/state/persistence/session-persister.js +2 -2
  40. package/dist/esm/state/persistence/session-persister.test.js +10 -1
  41. package/dist/esm/state/persistence/session-snapshot.js +6 -2
  42. package/dist/esm/state/persistence/session-snapshot.test.js +4 -1
  43. package/dist/esm/state/persistence/stateful-restore.e2e.test.js +10 -1
  44. package/dist/esm/utils/cost-session-history.js +38 -14
  45. package/dist/esm/utils/cost-session-history.test.js +60 -13
  46. package/dist/esm/utils/resolve-cost-history-config.js +2 -1
  47. package/dist/esm/utils/sum-costs.js +2 -13
  48. package/dist/esm/utils/sum-tokens.js +10 -14
  49. package/dist/esm/utils/sum-usage.js +105 -0
  50. package/dist/esm/utils/sum-usage.test.js +102 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +17 -17
  53. package/src/components/chat-driver/chat-driver.ts +6 -0
  54. package/src/components/settings-modal/settings-modal.styles.ts +47 -0
  55. package/src/components/settings-modal/settings-modal.template.ts +82 -5
  56. package/src/main/main.ts +157 -53
  57. package/src/state/ai-assistant-slice.test.ts +12 -5
  58. package/src/state/ai-assistant-slice.ts +21 -13
  59. package/src/state/debug-event-log.ts +2 -2
  60. package/src/state/persistence/session-persistence-provider.ts +24 -0
  61. package/src/state/persistence/session-persistence.integration.test.ts +8 -1
  62. package/src/state/persistence/session-persister.test.ts +10 -1
  63. package/src/state/persistence/session-persister.ts +2 -2
  64. package/src/state/persistence/session-snapshot.test.ts +4 -1
  65. package/src/state/persistence/session-snapshot.ts +5 -1
  66. package/src/state/persistence/stateful-restore.e2e.test.ts +9 -1
  67. package/src/utils/cost-session-history.test.ts +66 -16
  68. package/src/utils/cost-session-history.ts +65 -23
  69. package/src/utils/resolve-cost-history-config.ts +7 -0
  70. package/src/utils/sum-costs.ts +2 -9
  71. package/src/utils/sum-tokens.ts +10 -13
  72. package/src/utils/sum-usage.test.ts +119 -0
  73. package/src/utils/sum-usage.ts +111 -0
@@ -5916,7 +5916,7 @@
5916
5916
  {
5917
5917
  "kind": "Interface",
5918
5918
  "canonicalReference": "@genesislcap/ai-assistant!CostSessionModelEntry:interface",
5919
- "docComment": "/**\n * A model used during a finalized cost session.\n */\n",
5919
+ "docComment": "/**\n * A model used during a recorded cost session.\n */\n",
5920
5920
  "excerptTokens": [
5921
5921
  {
5922
5922
  "kind": "Content",
@@ -5989,7 +5989,7 @@
5989
5989
  {
5990
5990
  "kind": "Interface",
5991
5991
  "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord:interface",
5992
- "docComment": "/**\n * Persisted snapshot of a completed assistant cost session.\n */\n",
5992
+ "docComment": "/**\n * Persisted usage record for one project's assistant work.\n */\n",
5993
5993
  "excerptTokens": [
5994
5994
  {
5995
5995
  "kind": "Content",
@@ -6003,16 +6003,16 @@
6003
6003
  "members": [
6004
6004
  {
6005
6005
  "kind": "PropertySignature",
6006
- "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#costUsd:member",
6006
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#id:member",
6007
6007
  "docComment": "",
6008
6008
  "excerptTokens": [
6009
6009
  {
6010
6010
  "kind": "Content",
6011
- "text": "costUsd: "
6011
+ "text": "id: "
6012
6012
  },
6013
6013
  {
6014
6014
  "kind": "Content",
6015
- "text": "number"
6015
+ "text": "string"
6016
6016
  },
6017
6017
  {
6018
6018
  "kind": "Content",
@@ -6022,7 +6022,7 @@
6022
6022
  "isReadonly": false,
6023
6023
  "isOptional": false,
6024
6024
  "releaseTag": "Public",
6025
- "name": "costUsd",
6025
+ "name": "id",
6026
6026
  "propertyTypeTokenRange": {
6027
6027
  "startIndex": 1,
6028
6028
  "endIndex": 2
@@ -6030,16 +6030,21 @@
6030
6030
  },
6031
6031
  {
6032
6032
  "kind": "PropertySignature",
6033
- "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#endedAt:member",
6034
- "docComment": "/**\n * ISO-8601 timestamp when the session was finalized.\n */\n",
6033
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#models:member",
6034
+ "docComment": "",
6035
6035
  "excerptTokens": [
6036
6036
  {
6037
6037
  "kind": "Content",
6038
- "text": "endedAt: "
6038
+ "text": "models: "
6039
+ },
6040
+ {
6041
+ "kind": "Reference",
6042
+ "text": "CostSessionModelEntry",
6043
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionModelEntry:interface"
6039
6044
  },
6040
6045
  {
6041
6046
  "kind": "Content",
6042
- "text": "string"
6047
+ "text": "[]"
6043
6048
  },
6044
6049
  {
6045
6050
  "kind": "Content",
@@ -6049,20 +6054,20 @@
6049
6054
  "isReadonly": false,
6050
6055
  "isOptional": false,
6051
6056
  "releaseTag": "Public",
6052
- "name": "endedAt",
6057
+ "name": "models",
6053
6058
  "propertyTypeTokenRange": {
6054
6059
  "startIndex": 1,
6055
- "endIndex": 2
6060
+ "endIndex": 3
6056
6061
  }
6057
6062
  },
6058
6063
  {
6059
6064
  "kind": "PropertySignature",
6060
- "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#id:member",
6061
- "docComment": "",
6065
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#projectKey:member",
6066
+ "docComment": "/**\n * Stable identity of the work this row stands for — the assistant's session key (a project id, for hosts that key sessions per project). The upsert key: a row is one project's lifetime usage, so re-reporting the same key REPLACES the row rather than adding another.\n */\n",
6062
6067
  "excerptTokens": [
6063
6068
  {
6064
6069
  "kind": "Content",
6065
- "text": "id: "
6070
+ "text": "projectKey: "
6066
6071
  },
6067
6072
  {
6068
6073
  "kind": "Content",
@@ -6076,7 +6081,7 @@
6076
6081
  "isReadonly": false,
6077
6082
  "isOptional": false,
6078
6083
  "releaseTag": "Public",
6079
- "name": "id",
6084
+ "name": "projectKey",
6080
6085
  "propertyTypeTokenRange": {
6081
6086
  "startIndex": 1,
6082
6087
  "endIndex": 2
@@ -6084,21 +6089,16 @@
6084
6089
  },
6085
6090
  {
6086
6091
  "kind": "PropertySignature",
6087
- "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#models:member",
6092
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#title:member",
6088
6093
  "docComment": "",
6089
6094
  "excerptTokens": [
6090
6095
  {
6091
6096
  "kind": "Content",
6092
- "text": "models: "
6093
- },
6094
- {
6095
- "kind": "Reference",
6096
- "text": "CostSessionModelEntry",
6097
- "canonicalReference": "@genesislcap/ai-assistant!CostSessionModelEntry:interface"
6097
+ "text": "title: "
6098
6098
  },
6099
6099
  {
6100
6100
  "kind": "Content",
6101
- "text": "[]"
6101
+ "text": "string"
6102
6102
  },
6103
6103
  {
6104
6104
  "kind": "Content",
@@ -6108,20 +6108,20 @@
6108
6108
  "isReadonly": false,
6109
6109
  "isOptional": false,
6110
6110
  "releaseTag": "Public",
6111
- "name": "models",
6111
+ "name": "title",
6112
6112
  "propertyTypeTokenRange": {
6113
6113
  "startIndex": 1,
6114
- "endIndex": 3
6114
+ "endIndex": 2
6115
6115
  }
6116
6116
  },
6117
6117
  {
6118
6118
  "kind": "PropertySignature",
6119
- "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#title:member",
6120
- "docComment": "",
6119
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#updatedAt:member",
6120
+ "docComment": "/**\n * ISO-8601 timestamp of the most recent usage recorded for this row.\n *\n * Named for what it is: the row is upserted as spend accrues, so this moves. It is NOT an end time — a row has no terminal state, because a project can always be worked on again.\n */\n",
6121
6121
  "excerptTokens": [
6122
6122
  {
6123
6123
  "kind": "Content",
6124
- "text": "title: "
6124
+ "text": "updatedAt: "
6125
6125
  },
6126
6126
  {
6127
6127
  "kind": "Content",
@@ -6135,7 +6135,7 @@
6135
6135
  "isReadonly": false,
6136
6136
  "isOptional": false,
6137
6137
  "releaseTag": "Public",
6138
- "name": "title",
6138
+ "name": "updatedAt",
6139
6139
  "propertyTypeTokenRange": {
6140
6140
  "startIndex": 1,
6141
6141
  "endIndex": 2
@@ -6143,16 +6143,17 @@
6143
6143
  },
6144
6144
  {
6145
6145
  "kind": "PropertySignature",
6146
- "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#tokensConsumed:member",
6147
- "docComment": "",
6146
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord#usage:member",
6147
+ "docComment": "/**\n * Cost plus the four token buckets — the same shape the live session tile renders, so a history row can show the identical breakdown instead of a lone merged total that cannot explain a large token count at a small cost.\n */\n",
6148
6148
  "excerptTokens": [
6149
6149
  {
6150
6150
  "kind": "Content",
6151
- "text": "tokensConsumed: "
6151
+ "text": "usage: "
6152
6152
  },
6153
6153
  {
6154
- "kind": "Content",
6155
- "text": "number"
6154
+ "kind": "Reference",
6155
+ "text": "AggregateUsage",
6156
+ "canonicalReference": "@genesislcap/foundation-ai!AggregateUsage:interface"
6156
6157
  },
6157
6158
  {
6158
6159
  "kind": "Content",
@@ -6162,7 +6163,7 @@
6162
6163
  "isReadonly": false,
6163
6164
  "isOptional": false,
6164
6165
  "releaseTag": "Public",
6165
- "name": "tokensConsumed",
6166
+ "name": "usage",
6166
6167
  "propertyTypeTokenRange": {
6167
6168
  "startIndex": 1,
6168
6169
  "endIndex": 2
@@ -6801,7 +6802,7 @@
6801
6802
  {
6802
6803
  "kind": "Class",
6803
6804
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant:class",
6804
- "docComment": "/**\n * Foundation AI Assistant component.\n *\n * @remarks\n *\n * Register one or more AI providers via `registerAIProviders` from `@genesislcap/foundation-ai`; this element resolves the `AIProviderRegistry` through the DI container. Pass agent configuration via the `agents` property — each agent can override which registered provider it uses via `provider:`. The component creates a `ChatDriver` (single agent) or `OrchestratingDriver` (multiple agents) to manage the conversation loop.\n *\n * Popout/collapse coordination uses `agenticActivityBus` topics `chat-popout` and `chat-popin` — not DOM `CustomEvent`s on this element.\n *\n * **Cost build history:** opt-in via `chatConfig.costHistory.enabled`. When enabled, finalized sessions persist in `localStorage` (scoped by `costHistory.scope`, the element `id`, or `'default'`). Snapshots run on tab close and when the session title changes; hosts may call {@link FoundationAiAssistant.finalizeCostSession}.\n *\n * @fires\n *\n * chat-header-mousedown - Fired when the user presses the chat header in `popout-mode=\"expand\"` (for drag-to-popout hosts). Bubbles and composed. detail: `ChatHeaderMouseDownDetail`\n *\n * @fires\n *\n * session-cleared - Fired after the session is wiped (the lifecycle menu's Clear). The payload is captured before teardown so the host can clean up any external state the active agent was working on. Bubbles and composed. detail: `SessionClearedDetail`\n *\n * @beta\n */\n",
6805
+ "docComment": "/**\n * Foundation AI Assistant component.\n *\n * @remarks\n *\n * Register one or more AI providers via `registerAIProviders` from `@genesislcap/foundation-ai`; this element resolves the `AIProviderRegistry` through the DI container. Pass agent configuration via the `agents` property — each agent can override which registered provider it uses via `provider:`. The component creates a `ChatDriver` (single agent) or `OrchestratingDriver` (multiple agents) to manage the conversation loop.\n *\n * Popout/collapse coordination uses `agenticActivityBus` topics `chat-popout` and `chat-popin` — not DOM `CustomEvent`s on this element.\n *\n * **Cost build history:** opt-in via `chatConfig.costHistory.enabled`. When enabled, each session's usage is kept as exactly ONE row per session key in `localStorage` (scoped by `costHistory.scope`, the element `id`, or `'default'`), upserted as spend accrues. So a row is a project's lifetime usage rather than a visit to it, and the Cost tab's total is the sum of the rows alone — the live session is never added on top, because the active project's own row already reflects it. Hosts may call {@link FoundationAiAssistant.finalizeCostSession} to flush early; it is idempotent.\n *\n * @fires\n *\n * chat-header-mousedown - Fired when the user presses the chat header in `popout-mode=\"expand\"` (for drag-to-popout hosts). Bubbles and composed. detail: `ChatHeaderMouseDownDetail`\n *\n * @fires\n *\n * session-cleared - Fired after the session is wiped (the lifecycle menu's Clear). The payload is captured before teardown so the host can clean up any external state the active agent was working on. Bubbles and composed. detail: `SessionClearedDetail`\n *\n * @beta\n */\n",
6805
6806
  "excerptTokens": [
6806
6807
  {
6807
6808
  "kind": "Content",
@@ -8293,7 +8294,7 @@
8293
8294
  {
8294
8295
  "kind": "Method",
8295
8296
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#finalizeCostSession:member(1)",
8296
- "docComment": "/**\n * Snapshot the running session into persisted build history (`localStorage`). No-op unless `chatConfig.costHistory.enabled` is `true`.\n */\n",
8297
+ "docComment": "/**\n * Write this session's usage into persisted build history (`localStorage`) as a single row 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. The name is historical: it does NOT end the session or reset the running totals. Resetting them is what used to double-count spend, since the row was banked while the persisted transcript still carried the same usage, so a reload restored it and the Cost tab counted both.\n *\n * No-op unless `chatConfig.costHistory.enabled` is `true`, when there is no usage yet, or when the session has no key to upsert against.\n */\n",
8297
8298
  "excerptTokens": [
8298
8299
  {
8299
8300
  "kind": "Content",
@@ -10022,7 +10023,7 @@
10022
10023
  {
10023
10024
  "kind": "Property",
10024
10025
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#sessionCostUsd:member",
10025
- "docComment": "/**\n * Aggregated USD cost across every chat turn in this session.\n */\n",
10026
+ "docComment": "/**\n * Aggregated USD cost across every chat turn in this session.\n *\n * Read-only (unlike previously): it is a projection of `sessionUsage`, and a host assigning it would have set a figure the next transcript change overwrites.\n */\n",
10026
10027
  "excerptTokens": [
10027
10028
  {
10028
10029
  "kind": "Content",
@@ -10034,10 +10035,10 @@
10034
10035
  },
10035
10036
  {
10036
10037
  "kind": "Content",
10037
- "text": ";\n\nset sessionCostUsd(value: number);"
10038
+ "text": ";"
10038
10039
  }
10039
10040
  ],
10040
- "isReadonly": false,
10041
+ "isReadonly": true,
10041
10042
  "isOptional": false,
10042
10043
  "releaseTag": "Beta",
10043
10044
  "name": "sessionCostUsd",
@@ -10206,7 +10207,7 @@
10206
10207
  {
10207
10208
  "kind": "Property",
10208
10209
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#sessionTokensConsumed:member",
10209
- "docComment": "/**\n * Cumulative input + output tokens across every chat turn in this session.\n */\n",
10210
+ "docComment": "/**\n * Total tokens across every chat turn in this session — all four buckets added up. Read-only; the per-bucket split is on `sessionUsage`.\n */\n",
10210
10211
  "excerptTokens": [
10211
10212
  {
10212
10213
  "kind": "Content",
@@ -10218,10 +10219,10 @@
10218
10219
  },
10219
10220
  {
10220
10221
  "kind": "Content",
10221
- "text": ";\n\nset sessionTokensConsumed(value: number);"
10222
+ "text": ";"
10222
10223
  }
10223
10224
  ],
10224
- "isReadonly": false,
10225
+ "isReadonly": true,
10225
10226
  "isOptional": false,
10226
10227
  "releaseTag": "Beta",
10227
10228
  "name": "sessionTokensConsumed",
@@ -10233,6 +10234,46 @@
10233
10234
  "isProtected": false,
10234
10235
  "isAbstract": false
10235
10236
  },
10237
+ {
10238
+ "kind": "Property",
10239
+ "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#sessionUsage:member",
10240
+ "docComment": "/**\n * Cost plus the four token buckets for this session — derived from the transcript on every change, so it includes sub-agent turns and spend banked by a compaction.\n */\n",
10241
+ "excerptTokens": [
10242
+ {
10243
+ "kind": "Content",
10244
+ "text": "get sessionUsage(): "
10245
+ },
10246
+ {
10247
+ "kind": "Reference",
10248
+ "text": "AggregateUsage",
10249
+ "canonicalReference": "@genesislcap/foundation-ai!AggregateUsage:interface"
10250
+ },
10251
+ {
10252
+ "kind": "Content",
10253
+ "text": ";\n\nset sessionUsage(value: "
10254
+ },
10255
+ {
10256
+ "kind": "Reference",
10257
+ "text": "AggregateUsage",
10258
+ "canonicalReference": "@genesislcap/foundation-ai!AggregateUsage:interface"
10259
+ },
10260
+ {
10261
+ "kind": "Content",
10262
+ "text": ");"
10263
+ }
10264
+ ],
10265
+ "isReadonly": false,
10266
+ "isOptional": false,
10267
+ "releaseTag": "Beta",
10268
+ "name": "sessionUsage",
10269
+ "propertyTypeTokenRange": {
10270
+ "startIndex": 1,
10271
+ "endIndex": 2
10272
+ },
10273
+ "isStatic": false,
10274
+ "isProtected": false,
10275
+ "isAbstract": false
10276
+ },
10236
10277
  {
10237
10278
  "kind": "Method",
10238
10279
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#setAgent:member(1)",
@@ -15177,6 +15218,65 @@
15177
15218
  ],
15178
15219
  "name": "load"
15179
15220
  },
15221
+ {
15222
+ "kind": "MethodSignature",
15223
+ "canonicalReference": "@genesislcap/ai-assistant!SessionPersistenceProvider#loadCostHistory:member(1)",
15224
+ "docComment": "/**\n * Scoped cost/build history — the per-project usage rows behind the Cost tab.\n *\n * ‼️ **Keyed by SCOPE, not `sessionKey`**, unlike every other method on this interface. A row holds one project's lifetime usage and the list spans projects, so keying it per session would defeat the model. The scope comes from `chatConfig.costHistory.scope`, falling back to the element `id`, then `'default'`.\n *\n * Optional: when a provider omits these, the assistant falls back to `localStorage`, so hosts that don't implement them are unaffected. Implementing them moves the ledger to the host's own backend, which is what stops it being stranded in one browser.\n *\n * Read/written **independently of the `enabled` toggle** (as with preferences and diagnostics): usage accounting is not conversation content, so a user turning off chat retention should not lose their cost ledger. Best-effort — a failed write must never affect the chat.\n *\n * `undefined` from `loadCostHistory` means \"nothing stored\", the same as `[]`.\n */\n",
15225
+ "excerptTokens": [
15226
+ {
15227
+ "kind": "Content",
15228
+ "text": "loadCostHistory?(scope: "
15229
+ },
15230
+ {
15231
+ "kind": "Content",
15232
+ "text": "string"
15233
+ },
15234
+ {
15235
+ "kind": "Content",
15236
+ "text": "): "
15237
+ },
15238
+ {
15239
+ "kind": "Reference",
15240
+ "text": "Promise",
15241
+ "canonicalReference": "!Promise:interface"
15242
+ },
15243
+ {
15244
+ "kind": "Content",
15245
+ "text": "<"
15246
+ },
15247
+ {
15248
+ "kind": "Reference",
15249
+ "text": "CostSessionRecord",
15250
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord:interface"
15251
+ },
15252
+ {
15253
+ "kind": "Content",
15254
+ "text": "[] | undefined>"
15255
+ },
15256
+ {
15257
+ "kind": "Content",
15258
+ "text": ";"
15259
+ }
15260
+ ],
15261
+ "isOptional": true,
15262
+ "returnTypeTokenRange": {
15263
+ "startIndex": 3,
15264
+ "endIndex": 7
15265
+ },
15266
+ "releaseTag": "Public",
15267
+ "overloadIndex": 1,
15268
+ "parameters": [
15269
+ {
15270
+ "parameterName": "scope",
15271
+ "parameterTypeTokenRange": {
15272
+ "startIndex": 1,
15273
+ "endIndex": 2
15274
+ },
15275
+ "isOptional": false
15276
+ }
15277
+ ],
15278
+ "name": "loadCostHistory"
15279
+ },
15180
15280
  {
15181
15281
  "kind": "MethodSignature",
15182
15282
  "canonicalReference": "@genesislcap/ai-assistant!SessionPersistenceProvider#loadDiagnostics:member(1)",
@@ -15362,6 +15462,77 @@
15362
15462
  ],
15363
15463
  "name": "save"
15364
15464
  },
15465
+ {
15466
+ "kind": "MethodSignature",
15467
+ "canonicalReference": "@genesislcap/ai-assistant!SessionPersistenceProvider#saveCostHistory:member(1)",
15468
+ "docComment": "/**\n * Replace the stored row list for `scope`. See {@link SessionPersistenceProvider.loadCostHistory}.\n */\n",
15469
+ "excerptTokens": [
15470
+ {
15471
+ "kind": "Content",
15472
+ "text": "saveCostHistory?(scope: "
15473
+ },
15474
+ {
15475
+ "kind": "Content",
15476
+ "text": "string"
15477
+ },
15478
+ {
15479
+ "kind": "Content",
15480
+ "text": ", records: "
15481
+ },
15482
+ {
15483
+ "kind": "Reference",
15484
+ "text": "CostSessionRecord",
15485
+ "canonicalReference": "@genesislcap/ai-assistant!CostSessionRecord:interface"
15486
+ },
15487
+ {
15488
+ "kind": "Content",
15489
+ "text": "[]"
15490
+ },
15491
+ {
15492
+ "kind": "Content",
15493
+ "text": "): "
15494
+ },
15495
+ {
15496
+ "kind": "Reference",
15497
+ "text": "Promise",
15498
+ "canonicalReference": "!Promise:interface"
15499
+ },
15500
+ {
15501
+ "kind": "Content",
15502
+ "text": "<void>"
15503
+ },
15504
+ {
15505
+ "kind": "Content",
15506
+ "text": ";"
15507
+ }
15508
+ ],
15509
+ "isOptional": true,
15510
+ "returnTypeTokenRange": {
15511
+ "startIndex": 6,
15512
+ "endIndex": 8
15513
+ },
15514
+ "releaseTag": "Public",
15515
+ "overloadIndex": 1,
15516
+ "parameters": [
15517
+ {
15518
+ "parameterName": "scope",
15519
+ "parameterTypeTokenRange": {
15520
+ "startIndex": 1,
15521
+ "endIndex": 2
15522
+ },
15523
+ "isOptional": false
15524
+ },
15525
+ {
15526
+ "parameterName": "records",
15527
+ "parameterTypeTokenRange": {
15528
+ "startIndex": 3,
15529
+ "endIndex": 5
15530
+ },
15531
+ "isOptional": false
15532
+ }
15533
+ ],
15534
+ "name": "saveCostHistory"
15535
+ },
15365
15536
  {
15366
15537
  "kind": "PropertySignature",
15367
15538
  "canonicalReference": "@genesislcap/ai-assistant!SessionPersistenceProvider#saveDebounceMs:member",
@@ -1,4 +1,5 @@
1
1
  import { AgentPickerMode } from '@genesislcap/foundation-ai';
2
+ import type { AggregateUsage } from '@genesislcap/foundation-ai';
2
3
  import { AIProviderRegistry } from '@genesislcap/foundation-ai';
3
4
  import type { AIProviderRegistryStatusEntry } from '@genesislcap/foundation-ai';
4
5
  import type { AIProviderType } from '@genesislcap/foundation-ai';
@@ -1698,21 +1699,38 @@ declare type ChatInteractionEventsMap = {
1698
1699
 
1699
1700
  export { ChatToolChoice }
1700
1701
 
1701
- /** A model used during a finalized cost session. */
1702
+ /** A model used during a recorded cost session. */
1702
1703
  export declare interface CostSessionModelEntry {
1703
1704
  model: string;
1704
1705
  /** Vendor id from `getStatus()` (e.g. `'anthropic'`, `'gemini'`). */
1705
1706
  provider?: AIProviderType;
1706
1707
  }
1707
1708
 
1708
- /** Persisted snapshot of a completed assistant cost session. */
1709
+ /** Persisted usage record for one project's assistant work. */
1709
1710
  export declare interface CostSessionRecord {
1710
1711
  id: string;
1712
+ /**
1713
+ * Stable identity of the work this row stands for — the assistant's session key
1714
+ * (a project id, for hosts that key sessions per project). The upsert key: a row
1715
+ * is one project's lifetime usage, so re-reporting the same key REPLACES the row
1716
+ * rather than adding another.
1717
+ */
1718
+ projectKey: string;
1711
1719
  title: string;
1712
- /** ISO-8601 timestamp when the session was finalized. */
1713
- endedAt: string;
1714
- costUsd: number;
1715
- tokensConsumed: number;
1720
+ /**
1721
+ * ISO-8601 timestamp of the most recent usage recorded for this row.
1722
+ *
1723
+ * Named for what it is: the row is upserted as spend accrues, so this moves. It is
1724
+ * NOT an end time — a row has no terminal state, because a project can always be
1725
+ * worked on again.
1726
+ */
1727
+ updatedAt: string;
1728
+ /**
1729
+ * Cost plus the four token buckets — the same shape the live session tile renders, so
1730
+ * a history row can show the identical breakdown instead of a lone merged total that
1731
+ * cannot explain a large token count at a small cost.
1732
+ */
1733
+ usage: AggregateUsage;
1716
1734
  models: CostSessionModelEntry[];
1717
1735
  }
1718
1736
 
@@ -1905,10 +1923,13 @@ export declare interface FallbackAgentConfig extends BaseAgentConfig {
1905
1923
  *
1906
1924
  * Popout/collapse coordination uses `agenticActivityBus` topics `chat-popout` and `chat-popin` — not DOM `CustomEvent`s on this element.
1907
1925
  *
1908
- * **Cost build history:** opt-in via `chatConfig.costHistory.enabled`. When enabled,
1909
- * finalized sessions persist in `localStorage` (scoped by `costHistory.scope`, the
1910
- * element `id`, or `'default'`). Snapshots run on tab close and when the session
1911
- * title changes; hosts may call {@link FoundationAiAssistant.finalizeCostSession}.
1926
+ * **Cost build history:** opt-in via `chatConfig.costHistory.enabled`. When enabled, each
1927
+ * session's usage is kept as exactly ONE row per session key in `localStorage` (scoped by
1928
+ * `costHistory.scope`, the element `id`, or `'default'`), upserted as spend accrues. So a
1929
+ * row is a project's lifetime usage rather than a visit to it, and the Cost tab's total is
1930
+ * the sum of the rows alone — the live session is never added on top, because the active
1931
+ * project's own row already reflects it. Hosts may call
1932
+ * {@link FoundationAiAssistant.finalizeCostSession} to flush early; it is idempotent.
1912
1933
  *
1913
1934
  * @fires chat-header-mousedown - Fired when the user presses the chat header in `popout-mode="expand"` (for drag-to-popout hosts). Bubbles and composed. detail: `ChatHeaderMouseDownDetail`
1914
1935
  * @fires session-cleared - Fired after the session is wiped (the lifecycle menu's Clear). The payload is captured before teardown so the host can clean up any external state the active agent was working on. Bubbles and composed. detail: `SessionClearedDetail`
@@ -2141,21 +2162,42 @@ export declare class FoundationAiAssistant extends GenesisElement {
2141
2162
  /** Context window size for the active model, if known. */
2142
2163
  get contextLimit(): number | undefined;
2143
2164
  set contextLimit(value: number | undefined);
2144
- /** Aggregated USD cost across every chat turn in this session. */
2165
+ /**
2166
+ * Cost plus the four token buckets for this session — derived from the transcript
2167
+ * on every change, so it includes sub-agent turns and spend banked by a compaction.
2168
+ */
2169
+ get sessionUsage(): AggregateUsage;
2170
+ set sessionUsage(value: AggregateUsage);
2171
+ /**
2172
+ * Aggregated USD cost across every chat turn in this session.
2173
+ *
2174
+ * Read-only (unlike previously): it is a projection of `sessionUsage`, and a
2175
+ * host assigning it would have set a figure the next transcript change overwrites.
2176
+ */
2145
2177
  get sessionCostUsd(): number;
2146
- set sessionCostUsd(value: number);
2147
2178
  /** Saved build history plus the in-flight session — shown on the Cost tab summary. */
2148
2179
  get cumulativeCostUsd(): number;
2149
2180
  /** Resolved cost tab and build-history settings from `chatConfig.costHistory`. */
2150
2181
  get resolvedCostHistory(): ResolvedCostHistoryConfig;
2151
- /** Cumulative input + output tokens across every chat turn in this session. */
2182
+ /**
2183
+ * Total tokens across every chat turn in this session — all four buckets added up.
2184
+ * Read-only; the per-bucket split is on `sessionUsage`.
2185
+ */
2152
2186
  get sessionTokensConsumed(): number;
2153
- set sessionTokensConsumed(value: number);
2154
2187
  /** Label shown on the Cost tab for the in-flight session (app title or first user phrase). */
2155
2188
  get liveCostSessionTitle(): string | undefined;
2156
2189
  /**
2157
- * Snapshot the running session into persisted build history (`localStorage`).
2158
- * No-op unless `chatConfig.costHistory.enabled` is `true`.
2190
+ * Write this session's usage into persisted build history (`localStorage`) as a single
2191
+ * row keyed by the session key, replacing any existing row for that key.
2192
+ *
2193
+ * **Idempotent** — safe to call per turn, on tab close, and on a title change alike.
2194
+ * The name is historical: it does NOT end the session or reset the running totals.
2195
+ * Resetting them is what used to double-count spend, since the row was banked while the
2196
+ * persisted transcript still carried the same usage, so a reload restored it and the
2197
+ * Cost tab counted both.
2198
+ *
2199
+ * No-op unless `chatConfig.costHistory.enabled` is `true`, when there is no usage yet,
2200
+ * or when the session has no key to upsert against.
2159
2201
  */
2160
2202
  finalizeCostSession(options?: {
2161
2203
  title?: string;
@@ -2167,6 +2209,22 @@ export declare class FoundationAiAssistant extends GenesisElement {
2167
2209
  clearCostSessionHistory(): void;
2168
2210
  private getCostHistoryScope;
2169
2211
  private reloadCostSessionHistory;
2212
+ /**
2213
+ * The persistence provider, but only when it implements BOTH cost-history methods.
2214
+ *
2215
+ * Both or neither: a provider that can save but not load would silently accumulate a
2216
+ * ledger nobody ever reads back, so a partial implementation is treated as absent and
2217
+ * falls back to `localStorage`.
2218
+ */
2219
+ private costHistoryProvider;
2220
+ /**
2221
+ * Adopt `records` as the live list and write them through to whichever backend owns
2222
+ * cost history — the provider when the host implements it, else `localStorage`.
2223
+ *
2224
+ * Provider writes are fire-and-forget and best-effort: the ledger is accounting, not
2225
+ * conversation content, so a failed write is logged and never surfaced into the chat.
2226
+ */
2227
+ private persistCostHistory;
2170
2228
  /** Finalize the previous session when the host-provided title changes. */
2171
2229
  private syncActiveCostSessionTitle;
2172
2230
  /** Active model id resolved from the AIProvider, if exposed. */
@@ -2222,8 +2280,6 @@ export declare class FoundationAiAssistant extends GenesisElement {
2222
2280
  private unsubAppSettingsProvider?;
2223
2281
  /** Title tracked for the in-flight cost session (used when `header-title` changes). */
2224
2282
  private _activeCostSessionTitle;
2225
- /** Prevents double-finalize for the same session instance (pagehide + title change). */
2226
- private _costSessionFinalizedForInstance;
2227
2283
  private _handlePageHide;
2228
2284
  private haloStartPublished;
2229
2285
  /** Fingerprint of the agents array used to build the current driver. Used by agentsChanged to skip spurious rebuilds. */
@@ -3268,6 +3324,12 @@ declare interface ResolvedCostHistoryConfig {
3268
3324
  showTokens: boolean;
3269
3325
  /** Live-session badge label on the Cost tab summary. */
3270
3326
  badgeLabel: string;
3327
+ /**
3328
+ * Host-supplied disclosure line for the Cost tab. Stays optional — unlike
3329
+ * `badgeLabel` there is no default, because only the host knows what its `scope`
3330
+ * spans, and the library must not assert it.
3331
+ */
3332
+ summaryHint?: string;
3271
3333
  }
3272
3334
 
3273
3335
  /**
@@ -3385,6 +3447,28 @@ export declare interface SessionPersistenceProvider {
3385
3447
  loadPreferences?(sessionKey: string): Promise<SessionPreferences | undefined>;
3386
3448
  /** Persist the user's UI preferences for `sessionKey` (kept across `clear`). */
3387
3449
  savePreferences?(sessionKey: string, preferences: SessionPreferences): Promise<void>;
3450
+ /**
3451
+ * Scoped cost/build history — the per-project usage rows behind the Cost tab.
3452
+ *
3453
+ * ‼️ **Keyed by SCOPE, not `sessionKey`**, unlike every other method on this interface.
3454
+ * A row holds one project's lifetime usage and the list spans projects, so keying it
3455
+ * per session would defeat the model. The scope comes from
3456
+ * `chatConfig.costHistory.scope`, falling back to the element `id`, then `'default'`.
3457
+ *
3458
+ * Optional: when a provider omits these, the assistant falls back to `localStorage`, so
3459
+ * hosts that don't implement them are unaffected. Implementing them moves the ledger to
3460
+ * the host's own backend, which is what stops it being stranded in one browser.
3461
+ *
3462
+ * Read/written **independently of the `enabled` toggle** (as with preferences and
3463
+ * diagnostics): usage accounting is not conversation content, so a user turning off
3464
+ * chat retention should not lose their cost ledger. Best-effort — a failed write must
3465
+ * never affect the chat.
3466
+ *
3467
+ * `undefined` from `loadCostHistory` means "nothing stored", the same as `[]`.
3468
+ */
3469
+ loadCostHistory?(scope: string): Promise<CostSessionRecord[] | undefined>;
3470
+ /** Replace the stored row list for `scope`. See {@link SessionPersistenceProvider.loadCostHistory}. */
3471
+ saveCostHistory?(scope: string, records: CostSessionRecord[]): Promise<void>;
3388
3472
  /**
3389
3473
  * Server-saved diagnostics (GENC-1351 §5.8) — a **forward-only append** stream
3390
3474
  * that lets the downloadable debug log span the whole session lifetime, not just