@genesislcap/ai-assistant 15.19.6 → 15.20.0-alpha.3

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 (52) hide show
  1. package/dist/ai-assistant.api.json +707 -69
  2. package/dist/ai-assistant.d.ts +443 -25
  3. package/dist/chat-driver.cjs +440 -34
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +440 -34
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +709 -47
  8. package/dist/dts/components/ai-driver/ai-driver.d.ts +40 -7
  9. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  10. package/dist/dts/components/chat-driver/chat-driver.d.ts +81 -2
  11. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  12. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +23 -3
  13. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  14. package/dist/dts/config/config.d.ts +44 -0
  15. package/dist/dts/config/config.d.ts.map +1 -1
  16. package/dist/dts/main/main.d.ts +187 -5
  17. package/dist/dts/main/main.d.ts.map +1 -1
  18. package/dist/dts/main/main.styles.d.ts.map +1 -1
  19. package/dist/dts/main/main.template.d.ts.map +1 -1
  20. package/dist/dts/state/persistence/diagnostics-cursors.d.ts +7 -2
  21. package/dist/dts/state/persistence/diagnostics-cursors.d.ts.map +1 -1
  22. package/dist/dts/state/persistence/diagnostics.d.ts +30 -0
  23. package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -1
  24. package/dist/dts/state/persistence/session-persister.d.ts +18 -0
  25. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  26. package/dist/dts/utils/condense-history.d.ts +35 -0
  27. package/dist/dts/utils/condense-history.d.ts.map +1 -1
  28. package/dist/dts/utils/context-tokens.d.ts +156 -0
  29. package/dist/dts/utils/context-tokens.d.ts.map +1 -0
  30. package/dist/dts/utils/history-transform.d.ts +76 -14
  31. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  32. package/dist/dts/utils/resolve-context-budget.d.ts +98 -0
  33. package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -0
  34. package/dist/esm/components/chat-driver/chat-driver.js +200 -35
  35. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -4
  36. package/dist/esm/main/main.js +400 -24
  37. package/dist/esm/main/main.styles.js +128 -0
  38. package/dist/esm/main/main.template.js +64 -29
  39. package/dist/esm/state/debug-event-log.js +1 -1
  40. package/dist/esm/state/persistence/diagnostics-cursors.js +1 -1
  41. package/dist/esm/state/persistence/diagnostics.js +49 -0
  42. package/dist/esm/state/persistence/session-persister.js +38 -18
  43. package/dist/esm/utils/condense-history.js +125 -26
  44. package/dist/esm/utils/context-tokens.js +339 -0
  45. package/dist/esm/utils/history-transform.js +101 -19
  46. package/dist/esm/utils/resolve-context-budget.js +84 -0
  47. package/package.json +16 -16
  48. package/sandbox/README.md +93 -4
  49. package/sandbox/controls.ts +77 -10
  50. package/sandbox/fixtures.ts +163 -6
  51. package/sandbox/sandbox.css +54 -1
  52. package/sandbox/sandbox.ts +384 -7
@@ -630,6 +630,13 @@ export declare interface AiDriver extends EventTarget {
630
630
  * Return the full, unredacted conversation history.
631
631
  */
632
632
  getRawHistory?(): readonly ChatMessage[];
633
+ /**
634
+ * The history a host should PERSIST — {@link AiDriver.getRawHistory} with tool payloads the
635
+ * driver has already condensed out of the model's context replaced by the stubs it sent
636
+ * (GENC-1566). Optional: a driver that does not implement tool-context condensation has nothing
637
+ * to project, and a caller falls back to {@link AiDriver.getRawHistory}.
638
+ */
639
+ getHistoryForPersistence?(): ChatMessage[];
633
640
  /**
634
641
  * The live `AgentConfig` currently active in the driver — the instance whose
635
642
  * `onActivate` ran, so its `serialize`/`getDebugSnapshot` closures hold live
@@ -638,6 +645,16 @@ export declare interface AiDriver extends EventTarget {
638
645
  * retain the active config implement it.
639
646
  */
640
647
  getActiveAgent?(): AgentConfig | undefined;
648
+ /**
649
+ * Set the mid-loop context guard (GENC-1567). `undefined` disables it.
650
+ *
651
+ * Carries a margin rather than an absolute threshold: the window belongs to
652
+ * whichever provider the next call resolves to, which the driver learns during
653
+ * the call and the host cannot know until afterwards. The host owns the policy;
654
+ * the driver applies it against the model actually being addressed. Optional —
655
+ * only drivers that own a tool loop implement it.
656
+ */
657
+ setContextGuard?(policy?: ContextGuardPolicy): void;
641
658
  /**
642
659
  * Destructively compact older conversation turns into a single
643
660
  * `compacted-summary` message (GENC-1351 §5.7), summarizing them via the
@@ -645,15 +662,29 @@ export declare interface AiDriver extends EventTarget {
645
662
  * success. Resolves to the created summary message, or `null` when there is
646
663
  * nothing worth compacting or the provider cannot summarize. Optional — only
647
664
  * drivers that own history implement it.
665
+ *
666
+ * Pass the same `options` used for {@link AiDriver.getCompactionPlan} so the
667
+ * action does exactly what the affordance advertised.
668
+ */
669
+ compact?(options?: CompactionPlanOptions): Promise<ChatMessage | null>;
670
+ /**
671
+ * What compacting right now would cost and reclaim, without running the
672
+ * summarizer (GENC-1567) — or `null` when there is no safe cut, or the
673
+ * projected reclaim is not worth the call.
674
+ *
675
+ * The single source of truth for every compaction affordance: the menu's
676
+ * enabled state, its tooltip, the context gate's banner copy, and the action
677
+ * itself all read this, so none of them can promise a reclaim the action does
678
+ * not deliver. Optional — only history-owning drivers implement it.
648
679
  */
649
- compact?(): Promise<ChatMessage | null>;
680
+ getCompactionPlan?(options?: CompactionPlanOptions): CompactionProjection | null;
650
681
  /**
651
- * Whether `compact()` would compact something right now — a summarizable run of
652
- * older turns exists behind a clean boundary. The UI gates its "Compact"
653
- * affordance on this, so the button reflects the exact history `compact()` acts
654
- * on (GENC-1351). Optional — only history-owning drivers implement it.
682
+ * Whether `compact()` would compact something right now. Convenience over
683
+ * {@link AiDriver.getCompactionPlan} — prefer the plan where the caller needs
684
+ * to say how much would be reclaimed. Optional — only history-owning drivers
685
+ * implement it.
655
686
  */
656
- canCompact?(): boolean;
687
+ canCompact?(options?: CompactionPlanOptions): boolean;
657
688
  /**
658
689
  * Returns true if the driver is currently processing a request.
659
690
  */
@@ -1133,6 +1164,34 @@ declare interface BaseAgentConfig {
1133
1164
  * @beta
1134
1165
  */
1135
1166
  notResumableMessage?: string;
1167
+ /**
1168
+ * Tokens this agent wants kept free for one of its turns (GENC-1567).
1169
+ * Overrides `chatConfig.context.reserveTokens` while this agent is active;
1170
+ * leave unset to inherit it.
1171
+ *
1172
+ * The third member of the flow-safety group, alongside
1173
+ * {@link BaseAgentConfig.resumable} and {@link BaseAgentConfig.notResumableMessage}:
1174
+ * `resumable` says whether the agent may be interrupted, and this says how much
1175
+ * room it needs so that it does not have to be. An agent whose flow runs a long
1176
+ * tool loop, or returns large payloads, needs more than the global default.
1177
+ *
1178
+ * **When it takes effect.** The reserve is read from the agent that is already
1179
+ * ACTIVE, so it governs the gate from that agent's next send onwards — one turn
1180
+ * later than a true pre-flight. It is deliberately not enforced at flow entry:
1181
+ * `flow-owner-changed` fires while the driver is already routing the turn, so
1182
+ * gating there would mean aborting a turn in flight. An agent that activates
1183
+ * mid-turn therefore gets its first turn under the global reserve; the mid-loop
1184
+ * context guard is what stops that turn running away.
1185
+ *
1186
+ * **Set this from measurement, not from a guess.** Per-turn `inputTokens` is
1187
+ * recorded for every call, so a single real run of the flow tells you its peak
1188
+ * growth; a number invented up front will be wrong in whichever direction is
1189
+ * least convenient. Leaving it unset and inheriting the global default is the
1190
+ * right starting position.
1191
+ *
1192
+ * @beta
1193
+ */
1194
+ contextReserve?: ContextReserveInput;
1136
1195
  }
1137
1196
 
1138
1197
  /**
@@ -1201,16 +1260,6 @@ export { CachePolicy }
1201
1260
  */
1202
1261
  export declare type CachePolicyInput = CachePolicy | ((ctx: SystemPromptContext) => CachePolicy | Promise<CachePolicy>);
1203
1262
 
1204
- /**
1205
- * Plain TS class that drives a multi-turn chat conversation, including the tool-call loop.
1206
- * Owned by `FoundationAiAssistant` — created in `connectedCallback`, torn down in `disconnectedCallback`.
1207
- *
1208
- * Dispatches `'history-updated'` events on itself so the owning element can observe changes.
1209
- *
1210
- * @fires history-updated - Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution, post-resolve external cost) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`
1211
- *
1212
- * @beta
1213
- */
1214
1263
  export declare class ChatDriver extends EventTarget implements AiDriver {
1215
1264
  private readonly providerRegistry;
1216
1265
  private history;
@@ -1819,18 +1868,78 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
1819
1868
  * Optional transform applied to conversation history immediately before each LLM request.
1820
1869
  * Cleared when `undefined`. Does not alter stored history.
1821
1870
  */
1871
+ /**
1872
+ * Mid-loop context guard policy (GENC-1567). `undefined` disables the guard.
1873
+ *
1874
+ * Carries a MARGIN rather than an absolute threshold, and the difference is the
1875
+ * whole point: the threshold depends on the window of whichever provider the
1876
+ * next call resolves to, which the driver learns during the call and the host
1877
+ * cannot know until afterwards. The host owns the policy; the driver applies it
1878
+ * against the model actually being addressed.
1879
+ */
1880
+ private contextGuard?;
1881
+ /** Window of the provider the current call resolved to, if it reports one. */
1882
+ private lastResolvedContextLimit?;
1883
+ /**
1884
+ * Set the mid-loop context guard. Its margin is deliberately far smaller than
1885
+ * the reserve that blocks NEW turns: that reserve exists so an accepted turn can
1886
+ * spend it, and a guard set at the same line would kill every turn that used
1887
+ * the headroom it was given.
1888
+ */
1889
+ setContextGuard(policy?: ContextGuardPolicy): void;
1890
+ /**
1891
+ * Whether issuing `requestHistory` would run the context window out.
1892
+ *
1893
+ * Inert unless a margin has been set AND the resolved provider reports a
1894
+ * window — with no window there is nothing to measure against, and inventing
1895
+ * one to refuse a request is worse than letting the provider answer.
1896
+ *
1897
+ * Stopping here is strictly better than letting the request go: the provider
1898
+ * would reject an oversized prompt outright, leaving a transcript still too
1899
+ * large to retry and no explanation the user can act on, whereas ending the
1900
+ * turn keeps history intact so compaction is still available and the work so
1901
+ * far is not lost.
1902
+ */
1903
+ private contextExhausted;
1822
1904
  setProviderHistoryTransform(transform?: (history: ChatMessage[]) => ChatMessage[]): void;
1823
1905
  getHistory(): ReadonlyArray<ChatMessage>;
1824
1906
  getRawHistory(): readonly ChatMessage[];
1825
1907
  /** Returns the current fold stack names for debugging. */
1826
1908
  getActiveFoldNames(): string[];
1909
+ /**
1910
+ * The transcript as it should be **stored**: every tool payload the model has already stopped
1911
+ * seeing replaced by the same stub it was sent (GENC-1566). Live history is untouched — this
1912
+ * returns a copy.
1913
+ *
1914
+ * Condensation deliberately spares stored history, because the live transcript and the debug
1915
+ * export both read it. A host writing that transcript to durable storage inherits the other
1916
+ * half of that decision: it stores payloads the driver has already retired, on every autosave,
1917
+ * for the life of the session. Measured on one small Create session — 58,187 chars, 7.4% of a
1918
+ * 790 KB session object, across five payloads worth 14,434 tokens of context, written 86 times.
1919
+ *
1920
+ * Only already-collapsed payloads are touched, so this can never store a stub for something the
1921
+ * model can still see, and it has no side effects (see `condenseForPersistence`, which is
1922
+ * internal). A host that wants the unredacted transcript still calls
1923
+ * {@link ChatDriver.getRawHistory}, and a diagnostics stream that keeps full payloads is
1924
+ * unaffected — which is what makes the elision recoverable.
1925
+ */
1926
+ getHistoryForPersistence(): ChatMessage[];
1827
1927
  getSuggestions(history: ChatMessage[], prompt: string, count: number, allAgentInfo?: AllAgentSummary[]): Promise<string[]>;
1828
1928
  /**
1829
1929
  * Whether `compact()` would do anything right now — a summarizable run of older
1830
1930
  * turns exists behind a clean boundary. Uses the same `history` `compact()`
1831
1931
  * acts on, so the UI's gate can't disagree with the action (GENC-1351 follow-up).
1832
1932
  */
1833
- canCompact(): boolean;
1933
+ canCompact(options?: CompactionPlanOptions): boolean;
1934
+ /**
1935
+ * {@inheritDoc AiDriver.getCompactionPlan}
1936
+ *
1937
+ * Runs against the driver's own `history` — the exact list `compact()` acts on
1938
+ * — so a projection and the compaction it describes can never be computed from
1939
+ * different transcripts (the GENC-1351 follow-up that first made `canCompact`
1940
+ * read `history` rather than a mirrored copy).
1941
+ */
1942
+ getCompactionPlan(options?: CompactionPlanOptions): CompactionProjection | null;
1834
1943
  /**
1835
1944
  * Destructively compact older turns into a single `compacted-summary` message
1836
1945
  * (GENC-1351 §5.7). Summarizes everything before a clean recent-tail boundary
@@ -1840,7 +1949,7 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
1840
1949
  * the new one. Returns the created summary message, or `null` when there is
1841
1950
  * nothing worth compacting or the default provider cannot summarize.
1842
1951
  */
1843
- compact(): Promise<ChatMessage | null>;
1952
+ compact(options?: CompactionPlanOptions): Promise<ChatMessage | null>;
1844
1953
  isBusy(): boolean;
1845
1954
  /**
1846
1955
  * Wire a parent driver as the host for this driver's interactions. When set,
@@ -2108,6 +2217,28 @@ export { ChatThinkingPolicy }
2108
2217
 
2109
2218
  export { ChatToolChoice }
2110
2219
 
2220
+ /** Options for {@link planCompaction}. */
2221
+ declare interface CompactionPlanOptions {
2222
+ /** Token budget for the verbatim tail. */
2223
+ tailTokenBudget?: number;
2224
+ /** Minimum reclaim for the plan to be considered worth running. */
2225
+ minReclaimTokens?: number;
2226
+ }
2227
+
2228
+ /** What a compaction at a given cut would cost and reclaim. */
2229
+ declare interface CompactionProjection {
2230
+ /** Index the verbatim tail starts at — everything before it is summarized. */
2231
+ cut: number;
2232
+ /** How many messages the summary would replace. */
2233
+ compactedCount: number;
2234
+ /** Estimated context now. */
2235
+ tokensBefore: number;
2236
+ /** Estimated context afterwards — preamble + summary allowance + the tail. */
2237
+ tokensAfter: number;
2238
+ /** `tokensBefore − tokensAfter`, floored at zero. */
2239
+ reclaimed: number;
2240
+ }
2241
+
2111
2242
  /**
2112
2243
  * `aria-describedby` for the composer's textarea, send button and attach button:
2113
2244
  * the banner's id whenever the banner has something to say, otherwise `null` (so
@@ -2133,6 +2264,51 @@ export { ChatToolChoice }
2133
2264
  */
2134
2265
  export declare const composerDescribedBy: (x: FoundationAiAssistant) => string | null;
2135
2266
 
2267
+ /**
2268
+ * Plain TS class that drives a multi-turn chat conversation, including the tool-call loop.
2269
+ * Owned by `FoundationAiAssistant` — created in `connectedCallback`, torn down in `disconnectedCallback`.
2270
+ *
2271
+ * Dispatches `'history-updated'` events on itself so the owning element can observe changes.
2272
+ *
2273
+ * @fires history-updated - Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution, post-resolve external cost) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`
2274
+ *
2275
+ * @beta
2276
+ */
2277
+ /**
2278
+ * Mid-loop context guard policy, pushed down by the host (GENC-1567).
2279
+ *
2280
+ * @internal
2281
+ */
2282
+ export declare interface ContextGuardPolicy {
2283
+ /**
2284
+ * How close to the model's context window a running tool loop may get before
2285
+ * it is stopped.
2286
+ */
2287
+ marginTokens: number;
2288
+ /**
2289
+ * Window to assume when the resolved provider reports none. Consulted only in
2290
+ * that case — a provider that states its own window always wins.
2291
+ */
2292
+ fallbackLimit?: number;
2293
+ }
2294
+
2295
+ /**
2296
+ * Context headroom an agent reserves for itself, in tokens.
2297
+ *
2298
+ * A plain number, unlike the value-or-resolver shape `provider` / `temperature` /
2299
+ * `resumable` use. The resolver form was drafted for it and removed before
2300
+ * release: the gate is recomputed from a synchronous getter with no turn context
2301
+ * to resolve against, so no path could ever have invoked one. Accepting a
2302
+ * type-correct option that is guaranteed to do nothing is worse than not
2303
+ * offering it — a warning after the fact still leaves the configuration inert.
2304
+ *
2305
+ * It can widen to a resolver later without breaking a caller, since every
2306
+ * existing `number` stays valid.
2307
+ *
2308
+ * @beta
2309
+ */
2310
+ export declare type ContextReserveInput = number;
2311
+
2136
2312
  /** A model used during a recorded cost session. */
2137
2313
  export declare interface CostSessionModelEntry {
2138
2314
  model: string;
@@ -3664,6 +3840,27 @@ export declare class FoundationAiAssistant extends GenesisElement {
3664
3840
  sessionKeyChanged(_oldValue: string | undefined, _newValue: string | undefined): void;
3665
3841
  disconnectedCallback(): void;
3666
3842
  private resolveContextLimit;
3843
+ /**
3844
+ * Push the mid-loop context guard down to the driver (GENC-1567).
3845
+ *
3846
+ * Called wherever the inputs can change — the resolved provider status, and a
3847
+ * freshly built driver. A driver that never receives one simply runs without
3848
+ * the guard, which is the right default: it fires only when a real context
3849
+ * window is known.
3850
+ */
3851
+ private syncContextGuard;
3852
+ /**
3853
+ * Guard policy pushed to the driver, or `undefined` when the host has switched
3854
+ * the gate off — so `enabled: false` really does mean no local blocking of any
3855
+ * kind, UI or mid-loop.
3856
+ *
3857
+ * A MARGIN, not a threshold: the driver resolves the window of the provider
3858
+ * each call actually goes to. The fallback window rides along because the
3859
+ * driver cannot learn it any other way — without it, a host that knows its
3860
+ * window while its provider does not report one had `fallbackContextLimit`
3861
+ * protecting the composer and nothing else.
3862
+ */
3863
+ private get contextGuardPolicy();
3667
3864
  private loadProviderStatuses;
3668
3865
  chatConfigChanged(): void;
3669
3866
  /**
@@ -3795,16 +3992,175 @@ export declare class FoundationAiAssistant extends GenesisElement {
3795
3992
  * clearing/compacting mid-journey would be disruptive).
3796
3993
  */
3797
3994
  get sessionMenuEnabled(): boolean;
3995
+ /**
3996
+ * Whether the menu is on screen ONLY because the context gate put it there.
3997
+ *
3998
+ * @internal
3999
+ */
4000
+ get sessionMenuForcedByContext(): boolean;
4001
+ /**
4002
+ * Whether to withhold Clear from a menu the context gate forced open.
4003
+ *
4004
+ * The hosts who hid this menu did so to protect a flow from being cleared
4005
+ * mid-journey, and handing someone a Clear button at the moment they are stuck
4006
+ * hunting for a way out is how a conversation gets destroyed by accident.
4007
+ *
4008
+ * But only **while Compact is a real escape**. When the plan says compacting
4009
+ * would not free enough, starting a new conversation is the only way out — and
4010
+ * the blocked copy says exactly that, pointing the user at this menu to do it.
4011
+ * Withholding Clear there would send them to a menu that does not contain the
4012
+ * action they were just told to take.
4013
+ *
4014
+ * @internal
4015
+ */
4016
+ get clearWithheldByContext(): boolean;
3798
4017
  /** The persistence toggle row renders only when a provider is configured. */
3799
4018
  get persistenceToggleable(): boolean;
3800
4019
  /** Whether persistence is currently active (on + a provider) for this session. */
3801
4020
  get persistenceActive(): boolean;
3802
4021
  /**
3803
- * Whether the menu's "Compact" action would do anything (GENC-1351 §5.7).
3804
- * Delegates to `driver.canCompact()` so the gate uses the exact same history
3805
- * `compact()` acts on — one source of truth. Also reads `messages`, purely to
3806
- * stay reactive as the conversation grows: the driver's history isn't a tracked
3807
- * observable, so the button keys its re-evaluation off the Redux mirror.
4022
+ * Context-headroom thresholds for the active model (GENC-1567) — the warning
4023
+ * and block lines, plus the tail budget compaction aims at.
4024
+ *
4025
+ * The agent's own `contextReserve` is deliberately NOT folded in here. This is
4026
+ * the AMBIENT budget, which protects any turn from any agent; a per-agent
4027
+ * reserve is an additional, larger requirement checked when that agent takes
4028
+ * ownership of a flow, where there is a turn context to resolve its function
4029
+ * form against.
4030
+ *
4031
+ * Overhead is read off `messages` (the Redux mirror) rather than the driver's
4032
+ * own history, which the compaction plan uses. That is safe rather than sloppy:
4033
+ * `estimateSystemOverhead` only looks at messages BEFORE the first usage-bearing
4034
+ * one, and the two lists agree completely that early — the drift between mirror
4035
+ * and driver history is only ever at the tail.
4036
+ */
4037
+ get contextBudget(): ResolvedContextBudget;
4038
+ /**
4039
+ * The active agent's declared `contextReserve`, when it is a plain number.
4040
+ *
4041
+ * Read off the LIVE active agent rather than checked at flow entry, which is
4042
+ * where the design first put it. `flow-owner-changed` turned out to fire while
4043
+ * the driver is already routing the turn — after the send was allowed — so it
4044
+ * is not a pre-flight hook, and gating there would mean aborting a turn in
4045
+ * flight. Reading the active agent instead means a hungry agent's reserve
4046
+ * governs the gate from its next send onwards: one turn later than a true
4047
+ * pre-flight, but synchronous, and it never has to interrupt anything.
4048
+ *
4049
+ * `contextReserve` is a plain number by design — see `ContextReserveInput` for
4050
+ * why a resolver form was dropped rather than accepted and ignored.
4051
+ *
4052
+ * @internal
4053
+ */
4054
+ private get activeAgentReserveTokens();
4055
+ /**
4056
+ * What compacting right now would reclaim, or `null` when it would not be
4057
+ * worth it (GENC-1567). Sized against {@link FoundationAiAssistant.contextBudget},
4058
+ * so the plan aims at this model's window rather than a fixed default.
4059
+ *
4060
+ * Reads `messages` to stay reactive as the conversation grows: the driver's
4061
+ * history is not a tracked observable, so every consumer of this keys its
4062
+ * re-evaluation off the Redux mirror.
4063
+ */
4064
+ get compactionPlan(): CompactionProjection | null;
4065
+ /**
4066
+ * Tooltip and hint for the Compact row — why it is or is not available.
4067
+ *
4068
+ * Replaces a fixed "Not enough conversation yet to be worth compacting", which
4069
+ * since GENC-1567 could be flatly wrong: a long conversation whose tail is one
4070
+ * indivisible tool result has plenty of conversation and still cannot be
4071
+ * compacted. Reading the plan means the hint states the actual reason.
4072
+ *
4073
+ * @internal
4074
+ */
4075
+ get compactHint(): string;
4076
+ /**
4077
+ * How close this conversation is to the model's context window (GENC-1567).
4078
+ *
4079
+ * **Derived, never latched** — the opposite of {@link FoundationAiAssistant.blocked}.
4080
+ * A budget wall stays set because nothing the user does fixes it; this clears
4081
+ * itself the moment the conversation shrinks, which is precisely what the user
4082
+ * is being asked to do. The two must not be conflated: `blocked` is also
4083
+ * deliberately preserved across `resetSession`, and a context gate obviously
4084
+ * must not survive starting a new chat.
4085
+ *
4086
+ * **A flow in progress is never blocked, only warned.** Machine state lives
4087
+ * outside the transcript, so a stateful flow is not desynced by compaction —
4088
+ * but the summary can still drop detail the flow's own prompt leans on, and
4089
+ * blocking mid-journey would strand the user between a flow they cannot finish
4090
+ * and a compaction that is a poor idea right there. Letting the flow complete
4091
+ * and gating on the way out is the lesser harm; the headroom reserve exists so
4092
+ * this is rare rather than routine.
4093
+ */
4094
+ get contextGate(): 'ok' | 'warn' | 'blocked';
4095
+ /**
4096
+ * What the composer notice says about the context gate.
4097
+ *
4098
+ * The copy is a function of whether compaction can actually clear the gate,
4099
+ * which is why it reads {@link FoundationAiAssistant.compactionPlan} rather
4100
+ * than assuming. Telling someone to compact when the projection says it would
4101
+ * not free enough — a tail that is one indivisible tool result, say — sends
4102
+ * them into a summarizer call that leaves them exactly where they were.
4103
+ *
4104
+ * @internal
4105
+ */
4106
+ get contextGateReason(): string;
4107
+ /**
4108
+ * Short label the session-menu pill wears while the gate is active — the
4109
+ * primary visible signal, in place of a banner.
4110
+ *
4111
+ * Borrowed from the agent pin, which is the established way this composer says
4112
+ * "a mode is in force": a pill that grows a label rather than a strip of prose
4113
+ * above the input. It states the CONDITION only; the remedy lives in the
4114
+ * placeholder once the composer is actually blocked, which is the moment the
4115
+ * user needs to be told what to do rather than merely warned.
4116
+ *
4117
+ * @internal
4118
+ */
4119
+ get contextGateLabel(): string;
4120
+ /**
4121
+ * Whether the composer notice has anything to show — a budget wall or the
4122
+ * context gate. One region for both, so the live region stays single and
4123
+ * registered (see the banner template's accessibility note).
4124
+ *
4125
+ * @internal
4126
+ */
4127
+ get composerNoticeVisible(): boolean;
4128
+ /**
4129
+ * Text for that region. A budget wall outranks the context gate: it is the
4130
+ * condition the user cannot clear, so it must not be hidden behind advice
4131
+ * about compacting that would not restore sending anyway.
4132
+ *
4133
+ * For the context gate the region is present and announced but not drawn — see
4134
+ * {@link FoundationAiAssistant.composerNoticeSrOnly}.
4135
+ *
4136
+ * @internal
4137
+ */
4138
+ get composerNoticeText(): string;
4139
+ /**
4140
+ * Whether the notice region should be announced but not drawn.
4141
+ *
4142
+ * The context gate shows itself visually through the pill's label, the
4143
+ * composer outline and the placeholder — a strip of prose on top of those
4144
+ * would be a fourth statement of the same thing. But dropping the region
4145
+ * outright would be an accessibility regression, and a documented one: a
4146
+ * `role="status"` region announces MUTATIONS to a region already being
4147
+ * observed, and it is the only announcement available here, because the
4148
+ * composer flips to `disabled` at the same moment and a disabled control is out
4149
+ * of the tab order — so neither the placeholder nor an `aria-label` on it is
4150
+ * ever voiced.
4151
+ *
4152
+ * So the region stays mounted, keeps its text and keeps announcing; only the
4153
+ * visual treatment is withheld. A budget wall still draws its banner, because
4154
+ * that condition has no other visible expression.
4155
+ *
4156
+ * @internal
4157
+ */
4158
+ get composerNoticeSrOnly(): boolean;
4159
+ /**
4160
+ * Whether the menu's "Compact" action would do anything (GENC-1351 §5.7,
4161
+ * GENC-1567). Delegates to the driver's plan so the gate uses the exact same
4162
+ * history `compact()` acts on, judged against the same budget — one source of
4163
+ * truth for both whether a cut is legal and whether it reclaims enough.
3808
4164
  */
3809
4165
  get compactable(): boolean;
3810
4166
  toggleSessionMenu(): void;
@@ -4591,10 +4947,28 @@ export declare class OrchestratingDriver extends EventTarget implements AiDriver
4591
4947
  setFlowOwner(name: string | null): void;
4592
4948
  loadHistory(messages: ChatMessage[]): void;
4593
4949
  getRawHistory(): readonly ChatMessage[];
4950
+ /**
4951
+ * Delegates to the inner {@link ChatDriver} — condensation policies are registered there, so it
4952
+ * is the only object that knows which payloads the model has stopped seeing (GENC-1566).
4953
+ *
4954
+ * **This forward is the whole feature, for any multi-agent host.** `AiDriver` declares
4955
+ * `getHistoryForPersistence` optional, and `SessionPersister` calls it through `?.`, falling back
4956
+ * to {@link OrchestratingDriver.getRawHistory}. This class does NOT extend `ChatDriver` — it
4957
+ * wraps one — so without an explicit forward the optional call resolves to `undefined` and every
4958
+ * host on an orchestrating driver silently keeps persisting the full payloads. Measured exactly
4959
+ * that way on a Create run: 10 `context.condensed` events fired, and the session still stored
4960
+ * the two largest `read_context` results at 26,907 and 23,060 chars. The method existed, the
4961
+ * tests passed, and the feature did nothing.
4962
+ */
4963
+ getHistoryForPersistence(): ChatMessage[];
4964
+ /** {@inheritDoc AiDriver.setContextGuard} */
4965
+ setContextGuard(policy?: ContextGuardPolicy): void;
4594
4966
  /** {@inheritDoc AiDriver.compact} */
4595
- compact(): Promise<ChatMessage | null>;
4967
+ compact(options?: CompactionPlanOptions): Promise<ChatMessage | null>;
4596
4968
  /** {@inheritDoc AiDriver.canCompact} */
4597
- canCompact(): boolean;
4969
+ canCompact(options?: CompactionPlanOptions): boolean;
4970
+ /** {@inheritDoc AiDriver.getCompactionPlan} */
4971
+ getCompactionPlan(options?: CompactionPlanOptions): CompactionProjection | null;
4598
4972
  /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
4599
4973
  getTurnSnapshots(): ReadonlyArray<TurnSnapshot>;
4600
4974
  /** Delegates to the inner {@link ChatDriver} — interactions resolve there, so it holds the buffer. */
@@ -4745,6 +5119,50 @@ declare type ProviderRefusedDetail = NonNullable<Extract<ChatDriverResult, {
4745
5119
  /** Name reserved for the cross-agent handoff tool — injected by OrchestratingDriver. */
4746
5120
  export declare const REQUEST_CONTINUATION_TOOL = "request_continuation";
4747
5121
 
5122
+ /** Resolved context-headroom thresholds for the active model. */
5123
+ declare interface ResolvedContextBudget {
5124
+ /**
5125
+ * Whether the gate can act. False when the host disabled it, or when no
5126
+ * context limit is known — with no limit there is no percentage, and blocking
5127
+ * against a guessed window would be worse than not blocking at all.
5128
+ */
5129
+ enabled: boolean;
5130
+ /** The context window in play, from the provider or the configured fallback. */
5131
+ limit?: number;
5132
+ /** Headroom reserved for one turn. */
5133
+ reserveTokens: number;
5134
+ /** Context size at which the warning appears. */
5135
+ warnAt?: number;
5136
+ /** Context size at which new sends are blocked. */
5137
+ blockAt?: number;
5138
+ /**
5139
+ * Token budget for the verbatim tail a compaction leaves behind.
5140
+ *
5141
+ * Derived so a compaction lands back below the WARNING line, not merely below
5142
+ * the block: clearing the block by a hair would re-warn on the next turn and
5143
+ * make the user compact again almost immediately. The summary's own allowance
5144
+ * is subtracted because it occupies the space it is meant to free.
5145
+ */
5146
+ tailTokenBudget: number;
5147
+ /** Smallest reclaim for compaction to be worth running. */
5148
+ minReclaimTokens: number;
5149
+ /**
5150
+ * Context size at which a RUNNING turn is stopped mid-loop (GENC-1567).
5151
+ *
5152
+ * Deliberately far higher than {@link ResolvedContextBudget.blockAt}, and the
5153
+ * gap is the whole point: `blockAt` is where new turns stop being ACCEPTED, and
5154
+ * the reserve below it exists precisely so an accepted turn can spend it. A
5155
+ * mid-loop guard set at `blockAt` would kill every turn that used the headroom
5156
+ * it was given, which is the opposite of the intent.
5157
+ *
5158
+ * This is the last-resort edge — close enough to the window that one more
5159
+ * request would likely be rejected outright. Stopping here keeps the
5160
+ * transcript intact and compaction possible, where letting the request go
5161
+ * gives a provider error and a conversation still too big to retry.
5162
+ */
5163
+ abortAt?: number;
5164
+ }
5165
+
4748
5166
  /** Resolved cost-history settings from `chatConfig.costHistory`. */
4749
5167
  declare interface ResolvedCostHistoryConfig {
4750
5168
  /** When `true`, sessions are persisted automatically and history is shown. */