@genesislcap/ai-assistant 15.19.6 → 15.20.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.
- package/dist/ai-assistant.api.json +605 -72
- package/dist/ai-assistant.d.ts +404 -25
- package/dist/chat-driver.cjs +341 -28
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +341 -28
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +630 -20
- package/dist/dts/components/ai-driver/ai-driver.d.ts +33 -7
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +63 -2
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -3
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +44 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +187 -5
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/utils/condense-history.d.ts.map +1 -1
- package/dist/dts/utils/context-tokens.d.ts +156 -0
- package/dist/dts/utils/context-tokens.d.ts.map +1 -0
- package/dist/dts/utils/history-transform.d.ts +76 -14
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/resolve-context-budget.d.ts +98 -0
- package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +179 -34
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +12 -4
- package/dist/esm/main/main.js +391 -21
- package/dist/esm/main/main.styles.js +128 -0
- package/dist/esm/main/main.template.js +64 -29
- package/dist/esm/state/debug-event-log.js +1 -1
- package/dist/esm/utils/condense-history.js +1 -5
- package/dist/esm/utils/context-tokens.js +339 -0
- package/dist/esm/utils/history-transform.js +101 -19
- package/dist/esm/utils/resolve-context-budget.js +84 -0
- package/package.json +16 -16
- package/sandbox/README.md +93 -4
- package/sandbox/controls.ts +77 -10
- package/sandbox/fixtures.ts +163 -6
- package/sandbox/sandbox.css +54 -1
- package/sandbox/sandbox.ts +384 -7
package/dist/esm/main/main.js
CHANGED
|
@@ -55,11 +55,13 @@ import { AnimatedPanelToggle } from '../utils/animated-panel-toggle';
|
|
|
55
55
|
import { resolveExclusiveLoadingStyle } from '../utils/animation-exclusivity';
|
|
56
56
|
import { deleteBankedBaseline, getBankedBaseline, setBankedBaseline, } from '../utils/banked-usage-baselines';
|
|
57
57
|
import { collectSessionModels } from '../utils/collect-session-models';
|
|
58
|
+
import { estimateContextTokens, estimateSystemOverhead, isContextMeasurable, } from '../utils/context-tokens';
|
|
58
59
|
import { clearCostSessionHistory, isCostSessionRecord, loadCostSessionHistory, resolveBankedUsage, saveCostSessionHistory, sortRecordsByRecency, upsertRecord, } from '../utils/cost-session-history';
|
|
59
60
|
import { deriveCostSessionTitleFromMessages, resolveCostSessionTitle, } from '../utils/derive-cost-session-title';
|
|
60
61
|
import { formatUsd } from '../utils/format-usd';
|
|
61
62
|
import { logger } from '../utils/logger';
|
|
62
63
|
import { filterVisibleMessages, trailingInteractionRow } from '../utils/message-partition';
|
|
64
|
+
import { CONTEXT_ABORT_MARGIN_TOKENS, resolveContextBudget, } from '../utils/resolve-context-budget';
|
|
63
65
|
import { resolveCostHistoryConfig, } from '../utils/resolve-cost-history-config';
|
|
64
66
|
import { resolvePreferenceBaseline } from '../utils/resolve-preference-baseline';
|
|
65
67
|
import { stripAgentHandlers } from '../utils/strip-agent-handlers';
|
|
@@ -641,7 +643,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
641
643
|
* restored/compacted history lands (GENC-1351 §6).
|
|
642
644
|
*/
|
|
643
645
|
get sendBlocked() {
|
|
644
|
-
return this.busy ||
|
|
646
|
+
return (this.busy ||
|
|
647
|
+
this.restoring ||
|
|
648
|
+
this.compacting ||
|
|
649
|
+
this.blocked ||
|
|
650
|
+
this.contextGate === 'blocked');
|
|
645
651
|
}
|
|
646
652
|
/**
|
|
647
653
|
* Why a programmatic send was refused, for `submitMessage`'s `errors`. A
|
|
@@ -650,7 +656,14 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
650
656
|
* looping on "Assistant is busy" would spin forever.
|
|
651
657
|
*/
|
|
652
658
|
sendRefusalReason() {
|
|
653
|
-
|
|
659
|
+
if (this.blocked)
|
|
660
|
+
return this.effectiveBlockedReason;
|
|
661
|
+
// Also a standing condition rather than a transient one — a caller looping on
|
|
662
|
+
// "Assistant is busy" would spin forever — but unlike a budget wall the user
|
|
663
|
+
// CAN clear this one, so the reason says how.
|
|
664
|
+
if (this.contextGate === 'blocked')
|
|
665
|
+
return this.contextGateReason;
|
|
666
|
+
return 'Assistant is busy';
|
|
654
667
|
}
|
|
655
668
|
/**
|
|
656
669
|
* Re-runs `agentsChanged` if the live `agents` array no longer matches the
|
|
@@ -2602,6 +2615,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2602
2615
|
// reassignment preserve both.
|
|
2603
2616
|
driver.setPinnedAgent(this.pinnedAgentName);
|
|
2604
2617
|
driver.setFlowOwner(this.flowOwnerAgentName);
|
|
2618
|
+
driver.setContextGuard(this.contextGuardPolicy);
|
|
2605
2619
|
const onOrchStart = () => {
|
|
2606
2620
|
this.showHalo = 'orchestrating';
|
|
2607
2621
|
};
|
|
@@ -3009,12 +3023,45 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3009
3023
|
: yield this.providerRegistry.getStatus();
|
|
3010
3024
|
this.contextLimit = status === null || status === void 0 ? void 0 : status.contextLimit;
|
|
3011
3025
|
this.activeModel = status === null || status === void 0 ? void 0 : status.model;
|
|
3026
|
+
this.syncContextGuard();
|
|
3012
3027
|
}
|
|
3013
3028
|
catch (_b) {
|
|
3014
3029
|
// Non-fatal — context limit / model display simply won't show
|
|
3015
3030
|
}
|
|
3016
3031
|
});
|
|
3017
3032
|
}
|
|
3033
|
+
/**
|
|
3034
|
+
* Push the mid-loop context guard down to the driver (GENC-1567).
|
|
3035
|
+
*
|
|
3036
|
+
* Called wherever the inputs can change — the resolved provider status, and a
|
|
3037
|
+
* freshly built driver. A driver that never receives one simply runs without
|
|
3038
|
+
* the guard, which is the right default: it fires only when a real context
|
|
3039
|
+
* window is known.
|
|
3040
|
+
*/
|
|
3041
|
+
syncContextGuard() {
|
|
3042
|
+
var _a, _b;
|
|
3043
|
+
(_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.setContextGuard) === null || _b === void 0 ? void 0 : _b.call(_a, this.contextGuardPolicy);
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Guard policy pushed to the driver, or `undefined` when the host has switched
|
|
3047
|
+
* the gate off — so `enabled: false` really does mean no local blocking of any
|
|
3048
|
+
* kind, UI or mid-loop.
|
|
3049
|
+
*
|
|
3050
|
+
* A MARGIN, not a threshold: the driver resolves the window of the provider
|
|
3051
|
+
* each call actually goes to. The fallback window rides along because the
|
|
3052
|
+
* driver cannot learn it any other way — without it, a host that knows its
|
|
3053
|
+
* window while its provider does not report one had `fallbackContextLimit`
|
|
3054
|
+
* protecting the composer and nothing else.
|
|
3055
|
+
*/
|
|
3056
|
+
get contextGuardPolicy() {
|
|
3057
|
+
var _a;
|
|
3058
|
+
if (!this.contextBudget.enabled)
|
|
3059
|
+
return undefined;
|
|
3060
|
+
return {
|
|
3061
|
+
marginTokens: CONTEXT_ABORT_MARGIN_TOKENS,
|
|
3062
|
+
fallbackLimit: (_a = this.chatConfig.context) === null || _a === void 0 ? void 0 : _a.fallbackContextLimit,
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3018
3065
|
loadProviderStatuses() {
|
|
3019
3066
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3020
3067
|
try {
|
|
@@ -3033,6 +3080,10 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3033
3080
|
this.syncShowingSplash();
|
|
3034
3081
|
this.syncActiveCostSessionTitle();
|
|
3035
3082
|
this.reloadCostSessionHistory();
|
|
3083
|
+
// `chatConfig.context` decides whether the mid-loop guard runs at all, so a
|
|
3084
|
+
// late or changed binding has to reach the driver — otherwise switching the
|
|
3085
|
+
// gate off leaves the guard armed from whatever the config said at connect.
|
|
3086
|
+
this.syncContextGuard();
|
|
3036
3087
|
}
|
|
3037
3088
|
/**
|
|
3038
3089
|
* `persistence` (like `chatConfig`) can bind after `connectedCallback` — a host may
|
|
@@ -3128,12 +3179,31 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3128
3179
|
}
|
|
3129
3180
|
this.syncShowHalo();
|
|
3130
3181
|
this.syncShowingSplash();
|
|
3131
|
-
//
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3182
|
+
// Context size, from the estimator rather than from the last raw measurement.
|
|
3183
|
+
//
|
|
3184
|
+
// `inputTokens` on a response measured the prompt that PRODUCED it, so it is
|
|
3185
|
+
// always at least one message out of date — it cannot see the response's own
|
|
3186
|
+
// content, nor any tool results appended since, all of which ride the next
|
|
3187
|
+
// prompt. As a display figure that lag was invisible; once it gates sending,
|
|
3188
|
+
// a single large final response could carry the transcript past the block
|
|
3189
|
+
// while the composer stayed live (GENC-1567).
|
|
3190
|
+
//
|
|
3191
|
+
// `estimateContextTokens` is that measurement rebased: calibrated against
|
|
3192
|
+
// every usable anchor, with whatever followed the last one added on. It also
|
|
3193
|
+
// discards anchors invalidated by a compaction — the case where trusting the
|
|
3194
|
+
// raw figure was not a lag but a dead end, leaving the gate blocked on a
|
|
3195
|
+
// conversation that had already shrunk, with sending (the only way to produce
|
|
3196
|
+
// a fresh measurement) being the thing blocked.
|
|
3197
|
+
//
|
|
3198
|
+
// Gated on the transcript being MEASURABLE AT ALL rather than on a currently
|
|
3199
|
+
// usable anchor. Straight after a compaction every retained measurement is
|
|
3200
|
+
// invalidated, so the narrower test skipped the assignment and left a blocked
|
|
3201
|
+
// figure latched — reintroducing the dead end from the other direction. The
|
|
3202
|
+
// estimator handles the invalidated anchors; this only decides whether the
|
|
3203
|
+
// gate gets an opinion, and a provider that has never reported usage still
|
|
3204
|
+
// leaves `contextTokens` unset and the gate inert.
|
|
3205
|
+
if (isContextMeasurable(this.messages)) {
|
|
3206
|
+
this.contextTokens = estimateContextTokens(this.messages);
|
|
3137
3207
|
}
|
|
3138
3208
|
// Recompute cost and all four token buckets from the transcript in one walk.
|
|
3139
3209
|
// Recomputing (rather than incrementing on append) keeps the totals correct under
|
|
@@ -3414,8 +3484,45 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3414
3484
|
*/
|
|
3415
3485
|
get sessionMenuEnabled() {
|
|
3416
3486
|
var _a;
|
|
3487
|
+
// The context gate forces the menu open, whatever hid it (GENC-1567). Both
|
|
3488
|
+
// reasons it can be hidden produce the same dead end — a disabled composer
|
|
3489
|
+
// telling the user to compact, with no control anywhere on screen that
|
|
3490
|
+
// compacts. Note that `showSessionMenu: false` is documented for exactly the
|
|
3491
|
+
// guided-onboarding case this gate is most likely to fire in, so the override
|
|
3492
|
+
// is not an edge case; the flow-entry reserve is what should keep it rare.
|
|
3493
|
+
if (this.sessionMenuForcedByContext)
|
|
3494
|
+
return true;
|
|
3417
3495
|
return ((_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showSessionMenu) !== false && !!this.persistence.provider;
|
|
3418
3496
|
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Whether the menu is on screen ONLY because the context gate put it there.
|
|
3499
|
+
*
|
|
3500
|
+
* @internal
|
|
3501
|
+
*/
|
|
3502
|
+
get sessionMenuForcedByContext() {
|
|
3503
|
+
var _a;
|
|
3504
|
+
if (this.contextGate === 'ok')
|
|
3505
|
+
return false;
|
|
3506
|
+
return ((_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showSessionMenu) === false || !this.persistence.provider;
|
|
3507
|
+
}
|
|
3508
|
+
/**
|
|
3509
|
+
* Whether to withhold Clear from a menu the context gate forced open.
|
|
3510
|
+
*
|
|
3511
|
+
* The hosts who hid this menu did so to protect a flow from being cleared
|
|
3512
|
+
* mid-journey, and handing someone a Clear button at the moment they are stuck
|
|
3513
|
+
* hunting for a way out is how a conversation gets destroyed by accident.
|
|
3514
|
+
*
|
|
3515
|
+
* But only **while Compact is a real escape**. When the plan says compacting
|
|
3516
|
+
* would not free enough, starting a new conversation is the only way out — and
|
|
3517
|
+
* the blocked copy says exactly that, pointing the user at this menu to do it.
|
|
3518
|
+
* Withholding Clear there would send them to a menu that does not contain the
|
|
3519
|
+
* action they were just told to take.
|
|
3520
|
+
*
|
|
3521
|
+
* @internal
|
|
3522
|
+
*/
|
|
3523
|
+
get clearWithheldByContext() {
|
|
3524
|
+
return this.sessionMenuForcedByContext && this.compactable;
|
|
3525
|
+
}
|
|
3419
3526
|
/** The persistence toggle row renders only when a provider is configured. */
|
|
3420
3527
|
get persistenceToggleable() {
|
|
3421
3528
|
return !!this.persistence.provider;
|
|
@@ -3425,15 +3532,218 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3425
3532
|
return !!this.persistence.enabled && !!this.persistence.provider;
|
|
3426
3533
|
}
|
|
3427
3534
|
/**
|
|
3428
|
-
*
|
|
3429
|
-
*
|
|
3430
|
-
*
|
|
3431
|
-
*
|
|
3432
|
-
*
|
|
3535
|
+
* Context-headroom thresholds for the active model (GENC-1567) — the warning
|
|
3536
|
+
* and block lines, plus the tail budget compaction aims at.
|
|
3537
|
+
*
|
|
3538
|
+
* The agent's own `contextReserve` is deliberately NOT folded in here. This is
|
|
3539
|
+
* the AMBIENT budget, which protects any turn from any agent; a per-agent
|
|
3540
|
+
* reserve is an additional, larger requirement checked when that agent takes
|
|
3541
|
+
* ownership of a flow, where there is a turn context to resolve its function
|
|
3542
|
+
* form against.
|
|
3543
|
+
*
|
|
3544
|
+
* Overhead is read off `messages` (the Redux mirror) rather than the driver's
|
|
3545
|
+
* own history, which the compaction plan uses. That is safe rather than sloppy:
|
|
3546
|
+
* `estimateSystemOverhead` only looks at messages BEFORE the first usage-bearing
|
|
3547
|
+
* one, and the two lists agree completely that early — the drift between mirror
|
|
3548
|
+
* and driver history is only ever at the tail.
|
|
3433
3549
|
*/
|
|
3434
|
-
get
|
|
3550
|
+
get contextBudget() {
|
|
3551
|
+
return resolveContextBudget({
|
|
3552
|
+
chatConfig: this.chatConfig,
|
|
3553
|
+
contextLimit: this.contextLimit,
|
|
3554
|
+
agentReserveTokens: this.activeAgentReserveTokens,
|
|
3555
|
+
systemOverhead: estimateSystemOverhead(this.messages),
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
/**
|
|
3559
|
+
* The active agent's declared `contextReserve`, when it is a plain number.
|
|
3560
|
+
*
|
|
3561
|
+
* Read off the LIVE active agent rather than checked at flow entry, which is
|
|
3562
|
+
* where the design first put it. `flow-owner-changed` turned out to fire while
|
|
3563
|
+
* the driver is already routing the turn — after the send was allowed — so it
|
|
3564
|
+
* is not a pre-flight hook, and gating there would mean aborting a turn in
|
|
3565
|
+
* flight. Reading the active agent instead means a hungry agent's reserve
|
|
3566
|
+
* governs the gate from its next send onwards: one turn later than a true
|
|
3567
|
+
* pre-flight, but synchronous, and it never has to interrupt anything.
|
|
3568
|
+
*
|
|
3569
|
+
* `contextReserve` is a plain number by design — see `ContextReserveInput` for
|
|
3570
|
+
* why a resolver form was dropped rather than accepted and ignored.
|
|
3571
|
+
*
|
|
3572
|
+
* @internal
|
|
3573
|
+
*/
|
|
3574
|
+
get activeAgentReserveTokens() {
|
|
3435
3575
|
var _a, _b, _c;
|
|
3436
|
-
return
|
|
3576
|
+
return (_c = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getActiveAgent) === null || _b === void 0 ? void 0 : _b.call(_a)) === null || _c === void 0 ? void 0 : _c.contextReserve;
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3579
|
+
* What compacting right now would reclaim, or `null` when it would not be
|
|
3580
|
+
* worth it (GENC-1567). Sized against {@link FoundationAiAssistant.contextBudget},
|
|
3581
|
+
* so the plan aims at this model's window rather than a fixed default.
|
|
3582
|
+
*
|
|
3583
|
+
* Reads `messages` to stay reactive as the conversation grows: the driver's
|
|
3584
|
+
* history is not a tracked observable, so every consumer of this keys its
|
|
3585
|
+
* re-evaluation off the Redux mirror.
|
|
3586
|
+
*/
|
|
3587
|
+
get compactionPlan() {
|
|
3588
|
+
var _a, _b, _c;
|
|
3589
|
+
if (this.messages.length === 0)
|
|
3590
|
+
return null;
|
|
3591
|
+
const budget = this.contextBudget;
|
|
3592
|
+
return ((_c = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getCompactionPlan) === null || _b === void 0 ? void 0 : _b.call(_a, {
|
|
3593
|
+
tailTokenBudget: budget.tailTokenBudget,
|
|
3594
|
+
minReclaimTokens: budget.minReclaimTokens,
|
|
3595
|
+
})) !== null && _c !== void 0 ? _c : null);
|
|
3596
|
+
}
|
|
3597
|
+
/**
|
|
3598
|
+
* Tooltip and hint for the Compact row — why it is or is not available.
|
|
3599
|
+
*
|
|
3600
|
+
* Replaces a fixed "Not enough conversation yet to be worth compacting", which
|
|
3601
|
+
* since GENC-1567 could be flatly wrong: a long conversation whose tail is one
|
|
3602
|
+
* indivisible tool result has plenty of conversation and still cannot be
|
|
3603
|
+
* compacted. Reading the plan means the hint states the actual reason.
|
|
3604
|
+
*
|
|
3605
|
+
* @internal
|
|
3606
|
+
*/
|
|
3607
|
+
get compactHint() {
|
|
3608
|
+
const plan = this.compactionPlan;
|
|
3609
|
+
if (plan)
|
|
3610
|
+
return `Summarize older messages to free up context`;
|
|
3611
|
+
return this.messages.length === 0
|
|
3612
|
+
? 'Nothing to compact yet'
|
|
3613
|
+
: 'There is nothing left to summarize here — the recent messages are too large to shrink further';
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* How close this conversation is to the model's context window (GENC-1567).
|
|
3617
|
+
*
|
|
3618
|
+
* **Derived, never latched** — the opposite of {@link FoundationAiAssistant.blocked}.
|
|
3619
|
+
* A budget wall stays set because nothing the user does fixes it; this clears
|
|
3620
|
+
* itself the moment the conversation shrinks, which is precisely what the user
|
|
3621
|
+
* is being asked to do. The two must not be conflated: `blocked` is also
|
|
3622
|
+
* deliberately preserved across `resetSession`, and a context gate obviously
|
|
3623
|
+
* must not survive starting a new chat.
|
|
3624
|
+
*
|
|
3625
|
+
* **A flow in progress is never blocked, only warned.** Machine state lives
|
|
3626
|
+
* outside the transcript, so a stateful flow is not desynced by compaction —
|
|
3627
|
+
* but the summary can still drop detail the flow's own prompt leans on, and
|
|
3628
|
+
* blocking mid-journey would strand the user between a flow they cannot finish
|
|
3629
|
+
* and a compaction that is a poor idea right there. Letting the flow complete
|
|
3630
|
+
* and gating on the way out is the lesser harm; the headroom reserve exists so
|
|
3631
|
+
* this is rare rather than routine.
|
|
3632
|
+
*/
|
|
3633
|
+
get contextGate() {
|
|
3634
|
+
const budget = this.contextBudget;
|
|
3635
|
+
if (!budget.enabled)
|
|
3636
|
+
return 'ok';
|
|
3637
|
+
const tokens = this.contextTokens;
|
|
3638
|
+
if (tokens == null || budget.blockAt == null || budget.warnAt == null)
|
|
3639
|
+
return 'ok';
|
|
3640
|
+
if (tokens >= budget.blockAt)
|
|
3641
|
+
return this.flowOwnerAgentName !== null ? 'warn' : 'blocked';
|
|
3642
|
+
return tokens >= budget.warnAt ? 'warn' : 'ok';
|
|
3643
|
+
}
|
|
3644
|
+
/**
|
|
3645
|
+
* What the composer notice says about the context gate.
|
|
3646
|
+
*
|
|
3647
|
+
* The copy is a function of whether compaction can actually clear the gate,
|
|
3648
|
+
* which is why it reads {@link FoundationAiAssistant.compactionPlan} rather
|
|
3649
|
+
* than assuming. Telling someone to compact when the projection says it would
|
|
3650
|
+
* not free enough — a tail that is one indivisible tool result, say — sends
|
|
3651
|
+
* them into a summarizer call that leaves them exactly where they were.
|
|
3652
|
+
*
|
|
3653
|
+
* @internal
|
|
3654
|
+
*/
|
|
3655
|
+
get contextGateReason() {
|
|
3656
|
+
const level = this.contextGate;
|
|
3657
|
+
const canHelp = this.compactionPlan != null;
|
|
3658
|
+
if (level === 'blocked') {
|
|
3659
|
+
return canHelp
|
|
3660
|
+
? 'There is not enough room in the model context for more work. Click the error pill and compact the context to continue working.'
|
|
3661
|
+
: 'There is not enough room in the model context for more work. Compacting the context will not free enough room, click the error pill to clear and start a new conversation.';
|
|
3662
|
+
}
|
|
3663
|
+
if (this.flowOwnerAgentName !== null) {
|
|
3664
|
+
return 'This conversation is nearly full. The task in progress will finish, but compact before starting another.';
|
|
3665
|
+
}
|
|
3666
|
+
return canHelp
|
|
3667
|
+
? 'This conversation is getting long. Compact it to free up room.'
|
|
3668
|
+
: 'This conversation is getting long and is close to the limit.';
|
|
3669
|
+
}
|
|
3670
|
+
/**
|
|
3671
|
+
* Short label the session-menu pill wears while the gate is active — the
|
|
3672
|
+
* primary visible signal, in place of a banner.
|
|
3673
|
+
*
|
|
3674
|
+
* Borrowed from the agent pin, which is the established way this composer says
|
|
3675
|
+
* "a mode is in force": a pill that grows a label rather than a strip of prose
|
|
3676
|
+
* above the input. It states the CONDITION only; the remedy lives in the
|
|
3677
|
+
* placeholder once the composer is actually blocked, which is the moment the
|
|
3678
|
+
* user needs to be told what to do rather than merely warned.
|
|
3679
|
+
*
|
|
3680
|
+
* @internal
|
|
3681
|
+
*/
|
|
3682
|
+
get contextGateLabel() {
|
|
3683
|
+
switch (this.contextGate) {
|
|
3684
|
+
case 'blocked':
|
|
3685
|
+
return 'At context limit';
|
|
3686
|
+
case 'warn':
|
|
3687
|
+
return 'Approaching context limit';
|
|
3688
|
+
default:
|
|
3689
|
+
return '';
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
/**
|
|
3693
|
+
* Whether the composer notice has anything to show — a budget wall or the
|
|
3694
|
+
* context gate. One region for both, so the live region stays single and
|
|
3695
|
+
* registered (see the banner template's accessibility note).
|
|
3696
|
+
*
|
|
3697
|
+
* @internal
|
|
3698
|
+
*/
|
|
3699
|
+
get composerNoticeVisible() {
|
|
3700
|
+
return this.bannerVisible || this.contextGate !== 'ok';
|
|
3701
|
+
}
|
|
3702
|
+
/**
|
|
3703
|
+
* Text for that region. A budget wall outranks the context gate: it is the
|
|
3704
|
+
* condition the user cannot clear, so it must not be hidden behind advice
|
|
3705
|
+
* about compacting that would not restore sending anyway.
|
|
3706
|
+
*
|
|
3707
|
+
* For the context gate the region is present and announced but not drawn — see
|
|
3708
|
+
* {@link FoundationAiAssistant.composerNoticeSrOnly}.
|
|
3709
|
+
*
|
|
3710
|
+
* @internal
|
|
3711
|
+
*/
|
|
3712
|
+
get composerNoticeText() {
|
|
3713
|
+
if (this.bannerVisible)
|
|
3714
|
+
return this.effectiveBlockedReason;
|
|
3715
|
+
return this.contextGate === 'ok' ? '' : this.contextGateReason;
|
|
3716
|
+
}
|
|
3717
|
+
/**
|
|
3718
|
+
* Whether the notice region should be announced but not drawn.
|
|
3719
|
+
*
|
|
3720
|
+
* The context gate shows itself visually through the pill's label, the
|
|
3721
|
+
* composer outline and the placeholder — a strip of prose on top of those
|
|
3722
|
+
* would be a fourth statement of the same thing. But dropping the region
|
|
3723
|
+
* outright would be an accessibility regression, and a documented one: a
|
|
3724
|
+
* `role="status"` region announces MUTATIONS to a region already being
|
|
3725
|
+
* observed, and it is the only announcement available here, because the
|
|
3726
|
+
* composer flips to `disabled` at the same moment and a disabled control is out
|
|
3727
|
+
* of the tab order — so neither the placeholder nor an `aria-label` on it is
|
|
3728
|
+
* ever voiced.
|
|
3729
|
+
*
|
|
3730
|
+
* So the region stays mounted, keeps its text and keeps announcing; only the
|
|
3731
|
+
* visual treatment is withheld. A budget wall still draws its banner, because
|
|
3732
|
+
* that condition has no other visible expression.
|
|
3733
|
+
*
|
|
3734
|
+
* @internal
|
|
3735
|
+
*/
|
|
3736
|
+
get composerNoticeSrOnly() {
|
|
3737
|
+
return !this.bannerVisible && this.contextGate !== 'ok';
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Whether the menu's "Compact" action would do anything (GENC-1351 §5.7,
|
|
3741
|
+
* GENC-1567). Delegates to the driver's plan so the gate uses the exact same
|
|
3742
|
+
* history `compact()` acts on, judged against the same budget — one source of
|
|
3743
|
+
* truth for both whether a cut is legal and whether it reclaims enough.
|
|
3744
|
+
*/
|
|
3745
|
+
get compactable() {
|
|
3746
|
+
return this.compactionPlan != null;
|
|
3437
3747
|
}
|
|
3438
3748
|
toggleSessionMenu() {
|
|
3439
3749
|
// Mutually exclusive with the agent picker; always reopen showing the menu
|
|
@@ -3562,7 +3872,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3562
3872
|
*/
|
|
3563
3873
|
compactSession() {
|
|
3564
3874
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3565
|
-
var _a, _b, _c, _d;
|
|
3875
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3566
3876
|
const driver = this.driver;
|
|
3567
3877
|
if (!(driver === null || driver === void 0 ? void 0 : driver.compact) || driver.isBusy() || this.compacting)
|
|
3568
3878
|
return;
|
|
@@ -3579,16 +3889,32 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3579
3889
|
// false` in the `finally` would then clear the SUCCESSOR session's flag. A write
|
|
3580
3890
|
// through the captured ref is a no-op on a detached store.
|
|
3581
3891
|
const ref = this._sessionRef;
|
|
3892
|
+
// Capture the plan the affordance was gated on and hand the SAME budget to
|
|
3893
|
+
// the action, so the compaction that runs is the one the user was shown
|
|
3894
|
+
// (GENC-1567) — and so the projected reclaim can be logged next to what the
|
|
3895
|
+
// compaction actually did.
|
|
3896
|
+
const budget = this.contextBudget;
|
|
3897
|
+
const planOptions = {
|
|
3898
|
+
tailTokenBudget: budget.tailTokenBudget,
|
|
3899
|
+
minReclaimTokens: budget.minReclaimTokens,
|
|
3900
|
+
};
|
|
3901
|
+
const plan = (_b = (_a = driver.getCompactionPlan) === null || _a === void 0 ? void 0 : _a.call(driver, planOptions)) !== null && _b !== void 0 ? _b : null;
|
|
3582
3902
|
ref === null || ref === void 0 ? void 0 : ref.actions.aiAssistant.setCompacting(true);
|
|
3583
3903
|
try {
|
|
3584
|
-
const summary = yield driver.compact();
|
|
3904
|
+
const summary = yield driver.compact(planOptions);
|
|
3585
3905
|
if (summary) {
|
|
3586
3906
|
this.logMeta('context.compacted', {
|
|
3587
|
-
compactedCount: (
|
|
3588
|
-
coveredThroughTimestamp: (
|
|
3589
|
-
model: (
|
|
3907
|
+
compactedCount: (_c = summary.compaction) === null || _c === void 0 ? void 0 : _c.compactedCount,
|
|
3908
|
+
coveredThroughTimestamp: (_d = summary.compaction) === null || _d === void 0 ? void 0 : _d.coveredThroughTimestamp,
|
|
3909
|
+
model: (_e = summary.compaction) === null || _e === void 0 ? void 0 : _e.model,
|
|
3590
3910
|
trigger: 'manual',
|
|
3591
|
-
tokensBefore: (
|
|
3911
|
+
tokensBefore: (_f = summary.compaction) === null || _f === void 0 ? void 0 : _f.tokensBefore,
|
|
3912
|
+
// The projection the gate promised. Logged alongside the real figures
|
|
3913
|
+
// so a drifting estimator shows up in the diagnostics rather than as an
|
|
3914
|
+
// unexplained "I compacted and it did not help".
|
|
3915
|
+
projectedTokensAfter: plan === null || plan === void 0 ? void 0 : plan.tokensAfter,
|
|
3916
|
+
projectedReclaim: plan === null || plan === void 0 ? void 0 : plan.reclaimed,
|
|
3917
|
+
contextLimit: budget.limit,
|
|
3592
3918
|
summaryText: summary.content,
|
|
3593
3919
|
});
|
|
3594
3920
|
}
|
|
@@ -4083,6 +4409,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
4083
4409
|
// anyway, so naming the pinned agent would only imply a send is possible.
|
|
4084
4410
|
if (this.blocked)
|
|
4085
4411
|
return BLOCKED_PLACEHOLDER;
|
|
4412
|
+
// The full explanation, not a stub. With no banner above it this is the only
|
|
4413
|
+
// place the remedy is stated, and the composer it sits in is the control the
|
|
4414
|
+
// remedy is about — so the sentence is where the user is already looking.
|
|
4415
|
+
if (this.contextGate === 'blocked')
|
|
4416
|
+
return this.contextGateReason;
|
|
4086
4417
|
if (this.pinnedAgentName)
|
|
4087
4418
|
return `Message ${this.pinnedAgentName}...`;
|
|
4088
4419
|
return this.placeholder;
|
|
@@ -5162,6 +5493,9 @@ __decorate([
|
|
|
5162
5493
|
__decorate([
|
|
5163
5494
|
volatile
|
|
5164
5495
|
], FoundationAiAssistant.prototype, "visibleMessages", null);
|
|
5496
|
+
__decorate([
|
|
5497
|
+
volatile
|
|
5498
|
+
], FoundationAiAssistant.prototype, "contextGuardPolicy", null);
|
|
5165
5499
|
__decorate([
|
|
5166
5500
|
observable
|
|
5167
5501
|
], FoundationAiAssistant.prototype, "confirmingClear", void 0);
|
|
@@ -5171,12 +5505,48 @@ __decorate([
|
|
|
5171
5505
|
__decorate([
|
|
5172
5506
|
volatile
|
|
5173
5507
|
], FoundationAiAssistant.prototype, "sessionMenuEnabled", null);
|
|
5508
|
+
__decorate([
|
|
5509
|
+
volatile
|
|
5510
|
+
], FoundationAiAssistant.prototype, "sessionMenuForcedByContext", null);
|
|
5511
|
+
__decorate([
|
|
5512
|
+
volatile
|
|
5513
|
+
], FoundationAiAssistant.prototype, "clearWithheldByContext", null);
|
|
5174
5514
|
__decorate([
|
|
5175
5515
|
volatile
|
|
5176
5516
|
], FoundationAiAssistant.prototype, "persistenceToggleable", null);
|
|
5177
5517
|
__decorate([
|
|
5178
5518
|
volatile
|
|
5179
5519
|
], FoundationAiAssistant.prototype, "persistenceActive", null);
|
|
5520
|
+
__decorate([
|
|
5521
|
+
volatile
|
|
5522
|
+
], FoundationAiAssistant.prototype, "contextBudget", null);
|
|
5523
|
+
__decorate([
|
|
5524
|
+
volatile
|
|
5525
|
+
], FoundationAiAssistant.prototype, "activeAgentReserveTokens", null);
|
|
5526
|
+
__decorate([
|
|
5527
|
+
volatile
|
|
5528
|
+
], FoundationAiAssistant.prototype, "compactionPlan", null);
|
|
5529
|
+
__decorate([
|
|
5530
|
+
volatile
|
|
5531
|
+
], FoundationAiAssistant.prototype, "compactHint", null);
|
|
5532
|
+
__decorate([
|
|
5533
|
+
volatile
|
|
5534
|
+
], FoundationAiAssistant.prototype, "contextGate", null);
|
|
5535
|
+
__decorate([
|
|
5536
|
+
volatile
|
|
5537
|
+
], FoundationAiAssistant.prototype, "contextGateReason", null);
|
|
5538
|
+
__decorate([
|
|
5539
|
+
volatile
|
|
5540
|
+
], FoundationAiAssistant.prototype, "contextGateLabel", null);
|
|
5541
|
+
__decorate([
|
|
5542
|
+
volatile
|
|
5543
|
+
], FoundationAiAssistant.prototype, "composerNoticeVisible", null);
|
|
5544
|
+
__decorate([
|
|
5545
|
+
volatile
|
|
5546
|
+
], FoundationAiAssistant.prototype, "composerNoticeText", null);
|
|
5547
|
+
__decorate([
|
|
5548
|
+
volatile
|
|
5549
|
+
], FoundationAiAssistant.prototype, "composerNoticeSrOnly", null);
|
|
5180
5550
|
__decorate([
|
|
5181
5551
|
volatile
|
|
5182
5552
|
], FoundationAiAssistant.prototype, "compactable", null);
|