@genesislcap/ai-assistant 15.23.0 → 15.23.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.
- package/dist/ai-assistant.api.json +269 -5
- package/dist/ai-assistant.d.ts +256 -31
- package/dist/chat-driver.cjs +125 -58
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +125 -58
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +125 -6
- package/dist/dts/components/ai-driver/ai-driver.d.ts +63 -0
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +7 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +53 -17
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +16 -1
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +102 -14
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/utils/context-tokens.d.ts +100 -43
- package/dist/dts/utils/context-tokens.d.ts.map +1 -1
- package/dist/dts/utils/history-transform.d.ts +7 -1
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/resolve-context-budget.d.ts +8 -5
- package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +22 -2
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +92 -0
- package/dist/esm/config/define-stateful-agent.js +1 -0
- package/dist/esm/main/main.js +170 -27
- package/dist/esm/utils/context-tokens.js +331 -106
- package/dist/esm/utils/history-transform.js +5 -5
- package/dist/esm/utils/resolve-context-budget.js +37 -7
- package/package.json +16 -16
|
@@ -46,6 +46,40 @@ function isSpecialist(agent) {
|
|
|
46
46
|
function isFallback(agent) {
|
|
47
47
|
return agent.fallback === true;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Flatten a `contextReserve` declaration to the two numbers the gate reads
|
|
51
|
+
* (GENC-1582), or `undefined` when the agent declared nothing.
|
|
52
|
+
*
|
|
53
|
+
* A bare number is the peak-only form and leaves the warning to the host's
|
|
54
|
+
* `warnMultiplier`. Non-positive `tokens` is treated as no declaration rather
|
|
55
|
+
* than as "needs no headroom": zero would resolve to a reserve of 0, which sets
|
|
56
|
+
* `blockAt` to the whole window and switches the gate off for every agent the
|
|
57
|
+
* turn could reach — the opposite of what declaring a reserve is for.
|
|
58
|
+
*
|
|
59
|
+
* **Validated at runtime, not just by the type.** `contextReserve` is host-supplied
|
|
60
|
+
* `@beta` config and can arrive from JS, from JSON, or from a dynamic source, so
|
|
61
|
+
* `tokens` may be absent or not a number whatever the signature says. That matters
|
|
62
|
+
* more than a violated return type: `undefined <= 0` is `false`, so a bare
|
|
63
|
+
* comparison passed a malformed reserve straight through as `{ tokens: undefined }`,
|
|
64
|
+
* and the caller's `reserve.tokens > winner.tokens` then compared against
|
|
65
|
+
* `undefined` — always false. One malformed entry on an earlier-listed agent
|
|
66
|
+
* therefore SUPPRESSED every valid reserve behind it, and the gate fell back to the
|
|
67
|
+
* ambient default while still naming the malformed agent. Silent, because
|
|
68
|
+
* `resolveContextBudget` coalesces an undefined reserve to that same default
|
|
69
|
+
* (reviewer catch on #2504).
|
|
70
|
+
*/
|
|
71
|
+
function normalizeContextReserve(input) {
|
|
72
|
+
if (input == null)
|
|
73
|
+
return undefined;
|
|
74
|
+
const positive = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
|
|
75
|
+
if (typeof input === 'number')
|
|
76
|
+
return positive(input) ? { tokens: input } : undefined;
|
|
77
|
+
if (!positive(input.tokens))
|
|
78
|
+
return undefined;
|
|
79
|
+
return positive(input.warnAheadTokens)
|
|
80
|
+
? { tokens: input.tokens, warnAheadTokens: input.warnAheadTokens }
|
|
81
|
+
: { tokens: input.tokens };
|
|
82
|
+
}
|
|
49
83
|
/**
|
|
50
84
|
* Placeholder a consumer can put in its fallback prompt to position
|
|
51
85
|
* {@link UNROUTABLE_INSTRUCTION} itself, instead of having it appended.
|
|
@@ -293,6 +327,10 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
293
327
|
canCompact(options) {
|
|
294
328
|
return this.chatDriver.canCompact(options);
|
|
295
329
|
}
|
|
330
|
+
/** {@inheritDoc AiDriver.getPreambleTokens} */
|
|
331
|
+
getPreambleTokens() {
|
|
332
|
+
return this.chatDriver.getPreambleTokens();
|
|
333
|
+
}
|
|
296
334
|
/** {@inheritDoc AiDriver.getCompactionPlan} */
|
|
297
335
|
getCompactionPlan(options) {
|
|
298
336
|
return this.chatDriver.getCompactionPlan(options);
|
|
@@ -499,6 +537,60 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
499
537
|
primeRestoredAgentState(snapshots) {
|
|
500
538
|
this.restoredAgentStates = Object.assign({}, snapshots);
|
|
501
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* {@inheritDoc AiDriver.getContextReserve}
|
|
542
|
+
*
|
|
543
|
+
* Two cases, because routing has two modes and they need opposite answers.
|
|
544
|
+
*/
|
|
545
|
+
getContextReserve() {
|
|
546
|
+
// LOCKED — a user pin or a flow-owner lock. `handleTurn` routes straight to
|
|
547
|
+
// this agent and the handoff branch at the bottom of the routing loop refuses
|
|
548
|
+
// to route away while a lock is held, so no other agent's headroom can come
|
|
549
|
+
// into play until it clears. Its reserve is the whole requirement.
|
|
550
|
+
const locked = this.resolvePinnedAgent();
|
|
551
|
+
if (locked) {
|
|
552
|
+
const reserve = normalizeContextReserve(locked.contextReserve);
|
|
553
|
+
return reserve == null ? undefined : Object.assign(Object.assign({}, reserve), { agentName: locked.name, basis: 'locked' });
|
|
554
|
+
}
|
|
555
|
+
// AUTOMATIC — the classifier picks a specialist, may fall through to the
|
|
556
|
+
// fallback, and may hand off and re-classify more than once inside the one
|
|
557
|
+
// turn. Any of them could be the agent that runs, and the send is allowed or
|
|
558
|
+
// refused before we know which, so the requirement is the largest reserve
|
|
559
|
+
// among them: the turn has to fit whichever way it routes.
|
|
560
|
+
//
|
|
561
|
+
// The MAX, not the sum, even though a handoff chain really does add each leg's
|
|
562
|
+
// growth on top of the last. Summing every reachable agent would reserve room
|
|
563
|
+
// for a journey nobody is taking and strand most of the window on a two-line
|
|
564
|
+
// question; the mid-loop context guard is what bounds a chain that does run
|
|
565
|
+
// long. The max is the smallest reserve that makes the FIRST leg safe, which
|
|
566
|
+
// is the only leg this gate can speak for.
|
|
567
|
+
//
|
|
568
|
+
// `excludeFromClassifier` agents are absent by construction — they are
|
|
569
|
+
// reachable only by pin or by a restored flow-owner lock, and both of those
|
|
570
|
+
// are the locked arm above. The active agent is folded in as a floor: on the
|
|
571
|
+
// turn after a lock clears it is still the agent holding the context, and it
|
|
572
|
+
// need not be one the classifier could have picked.
|
|
573
|
+
const candidates = [...this.specialists];
|
|
574
|
+
if (this.fallback)
|
|
575
|
+
candidates.push(this.fallback);
|
|
576
|
+
if (this.activeAgent)
|
|
577
|
+
candidates.push(this.activeAgent);
|
|
578
|
+
// Compared on `tokens` — the figure that decides whether a turn may start.
|
|
579
|
+
// `warnAheadTokens` is not a candidate for the max in its own right; it rides
|
|
580
|
+
// along with whichever agent wins.
|
|
581
|
+
let winner;
|
|
582
|
+
let agentName = '';
|
|
583
|
+
for (const agent of candidates) {
|
|
584
|
+
const reserve = normalizeContextReserve(agent.contextReserve);
|
|
585
|
+
if (!reserve)
|
|
586
|
+
continue;
|
|
587
|
+
if (!winner || reserve.tokens > winner.tokens) {
|
|
588
|
+
winner = reserve;
|
|
589
|
+
agentName = agent.name;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return winner ? Object.assign(Object.assign({}, winner), { agentName, basis: 'reachable-max' }) : undefined;
|
|
593
|
+
}
|
|
502
594
|
/** {@inheritDoc AiDriver.getActiveAgent} */
|
|
503
595
|
getActiveAgent() {
|
|
504
596
|
return this.activeAgent;
|
|
@@ -264,6 +264,7 @@ export function defineStatefulAgent(opts) {
|
|
|
264
264
|
onUnresolvedTool: wrappedOnUnresolvedTool,
|
|
265
265
|
resumable: wrappedResumable,
|
|
266
266
|
notResumableMessage: opts.notResumableMessage,
|
|
267
|
+
contextReserve: opts.contextReserve,
|
|
267
268
|
onActivate: (ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
268
269
|
state = yield opts.init(ctx);
|
|
269
270
|
// Sample once with the init state to fail loud on fold misuse at the
|
package/dist/esm/main/main.js
CHANGED
|
@@ -55,7 +55,7 @@ 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,
|
|
58
|
+
import { estimateContextTokens, isContextMeasurable, } from '../utils/context-tokens';
|
|
59
59
|
import { clearCostSessionHistory, isCostSessionRecord, loadCostSessionHistory, resolveBankedUsage, saveCostSessionHistory, sortRecordsByRecency, upsertRecord, } from '../utils/cost-session-history';
|
|
60
60
|
import { deriveCostSessionTitleFromMessages, resolveCostSessionTitle, } from '../utils/derive-cost-session-title';
|
|
61
61
|
import { formatUsd } from '../utils/format-usd';
|
|
@@ -607,6 +607,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
607
607
|
// immediately, but only if they haven't moved on to something else.
|
|
608
608
|
// `cancelling` is busy too, so loading → cancelling must NOT settle yet.
|
|
609
609
|
if (this.isBusyState(prev) && !this.isBusyState(value)) {
|
|
610
|
+
this.rearmContextGate();
|
|
610
611
|
this.maybeAutoFocusChatInput();
|
|
611
612
|
// Apply a session re-key deferred by the busy guard first (it rebuilds the
|
|
612
613
|
// driver against current agents, so the agents-swap below then no-ops).
|
|
@@ -622,6 +623,31 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
622
623
|
isBusyState(s) {
|
|
623
624
|
return s === 'loading' || s === 'cancelling';
|
|
624
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Re-arm {@link FoundationAiAssistant.contextGate} against the size the turn
|
|
628
|
+
* just left behind (GENC-1582).
|
|
629
|
+
*
|
|
630
|
+
* The final call of a turn is made AFTER `agentEnd` condensation fires, so by
|
|
631
|
+
* the time a turn settles `contextTokens` is the resting figure rather than the
|
|
632
|
+
* mid-turn peak — which is the number the reserve's thresholds are drawn
|
|
633
|
+
* against.
|
|
634
|
+
*
|
|
635
|
+
* **Called from BOTH settle paths, and it has to be.** A turn does not end
|
|
636
|
+
* through the `state` setter: `send()`'s `finally` writes `setState('idle')`
|
|
637
|
+
* straight to the captured session ref, because the element may have
|
|
638
|
+
* disconnected mid-turn, and that block then replicates the parts of the
|
|
639
|
+
* setter's settle that matter. Arming the gate inline in the setter therefore
|
|
640
|
+
* armed it on the one path a real turn never takes — the gate silently kept
|
|
641
|
+
* falling back to the live figure and the hold did nothing at all. Found by
|
|
642
|
+
* driving a real UI Builder turn and reading `_gateContextTokens`, which was
|
|
643
|
+
* `undefined` from start to finish; no unit test noticed, because the test
|
|
644
|
+
* harness sets `state` through the property.
|
|
645
|
+
*
|
|
646
|
+
* One method rather than two assignments so the two paths cannot drift again.
|
|
647
|
+
*/
|
|
648
|
+
rearmContextGate() {
|
|
649
|
+
this._gateContextTokens = this.contextTokens;
|
|
650
|
+
}
|
|
625
651
|
/**
|
|
626
652
|
* True while a turn is active — `loading` or `cancelling` (a stop was
|
|
627
653
|
* requested but the turn is still winding down). The single "is a turn
|
|
@@ -3205,6 +3231,12 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3205
3231
|
if (isContextMeasurable(this.messages)) {
|
|
3206
3232
|
this.contextTokens = estimateContextTokens(this.messages);
|
|
3207
3233
|
}
|
|
3234
|
+
// Keep the gate's figure in step while at rest. Deliberately skipped mid-turn:
|
|
3235
|
+
// that is the spike the gate must not react to (see `_gateContextTokens`). A
|
|
3236
|
+
// compaction reaches this branch — it runs under `compacting`, not `busy` — so
|
|
3237
|
+
// the shrink it produces is picked up immediately rather than waiting for a turn.
|
|
3238
|
+
if (!this.busy)
|
|
3239
|
+
this._gateContextTokens = this.contextTokens;
|
|
3208
3240
|
// Recompute cost and all four token buckets from the transcript in one walk.
|
|
3209
3241
|
// Recomputing (rather than incrementing on append) keeps the totals correct under
|
|
3210
3242
|
// any mutation of the message list — clear-chat, re-render, restore. `sumUsage`
|
|
@@ -3503,6 +3535,13 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3503
3535
|
var _a;
|
|
3504
3536
|
if (this.contextGate === 'ok')
|
|
3505
3537
|
return false;
|
|
3538
|
+
// The `'model'` cause is the exception (GENC-1582). This override exists so a
|
|
3539
|
+
// user told to compact can reach Compact — but when the window cannot host the
|
|
3540
|
+
// agent, Compact and Clear are both futile, and forcing a menu of dead actions
|
|
3541
|
+
// in front of someone is worse than not opening it. The copy points at the
|
|
3542
|
+
// model picker instead.
|
|
3543
|
+
if (this.contextBlockCause === 'model')
|
|
3544
|
+
return false;
|
|
3506
3545
|
return ((_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showSessionMenu) === false || !this.persistence.provider;
|
|
3507
3546
|
}
|
|
3508
3547
|
/**
|
|
@@ -3535,11 +3574,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3535
3574
|
* Context-headroom thresholds for the active model (GENC-1567) — the warning
|
|
3536
3575
|
* and block lines, plus the tail budget compaction aims at.
|
|
3537
3576
|
*
|
|
3538
|
-
* The agent
|
|
3539
|
-
*
|
|
3540
|
-
*
|
|
3541
|
-
*
|
|
3542
|
-
*
|
|
3577
|
+
* The per-agent `contextReserve` IS folded in, via the driver (GENC-1582). It
|
|
3578
|
+
* used to be described here as a separate, later check made when an agent took
|
|
3579
|
+
* ownership of a flow — but there is no such check, and by flow entry the send
|
|
3580
|
+
* has already been allowed. Resolving it here, before the send, is what makes it
|
|
3581
|
+
* a gate rather than a label.
|
|
3543
3582
|
*
|
|
3544
3583
|
* Overhead is read off `messages` (the Redux mirror) rather than the driver's
|
|
3545
3584
|
* own history, which the compaction plan uses. That is safe rather than sloppy:
|
|
@@ -3548,32 +3587,63 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3548
3587
|
* and driver history is only ever at the tail.
|
|
3549
3588
|
*/
|
|
3550
3589
|
get contextBudget() {
|
|
3590
|
+
// Resolved ONCE. `contextReserve` re-reads live routing state on every access,
|
|
3591
|
+
// so two reads can land on different winners and pair one agent's
|
|
3592
|
+
// `warnAheadTokens` with another's `tokens` — the exact pairing
|
|
3593
|
+
// `ContextReserveResolution` warns against, and the reason this is a local
|
|
3594
|
+
// rather than two property reads (reviewer catch on #2504). It also stops the
|
|
3595
|
+
// orchestrator walking every specialist plus the fallback twice per evaluation.
|
|
3596
|
+
const reserve = this.contextReserve;
|
|
3551
3597
|
return resolveContextBudget({
|
|
3552
3598
|
chatConfig: this.chatConfig,
|
|
3553
3599
|
contextLimit: this.contextLimit,
|
|
3554
|
-
agentReserveTokens:
|
|
3555
|
-
|
|
3600
|
+
agentReserveTokens: reserve === null || reserve === void 0 ? void 0 : reserve.tokens,
|
|
3601
|
+
agentWarnAheadTokens: reserve === null || reserve === void 0 ? void 0 : reserve.warnAheadTokens,
|
|
3556
3602
|
});
|
|
3557
3603
|
}
|
|
3558
3604
|
/**
|
|
3559
|
-
*
|
|
3605
|
+
* Headroom the next turn needs, resolved against how routing is currently
|
|
3606
|
+
* constrained (GENC-1582), and which agent set it.
|
|
3607
|
+
*
|
|
3608
|
+
* The driver owns the answer because the driver owns routing: whether a pin or a
|
|
3609
|
+
* flow-owner lock is in force, which agents the classifier can reach, and which
|
|
3610
|
+
* one is active. This used to read `getActiveAgent()?.contextReserve` — the agent
|
|
3611
|
+
* that LAST ran — which is a different agent from the one about to run whenever
|
|
3612
|
+
* the orchestrator is doing its job, and the handover turn is precisely the one
|
|
3613
|
+
* that needs the room.
|
|
3560
3614
|
*
|
|
3561
|
-
*
|
|
3562
|
-
*
|
|
3563
|
-
*
|
|
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.
|
|
3615
|
+
* Falls back to the active agent for a host-supplied driver that does not
|
|
3616
|
+
* implement the resolution, so an existing single-agent driver keeps the old
|
|
3617
|
+
* behaviour rather than silently losing its reserve.
|
|
3568
3618
|
*
|
|
3569
3619
|
* `contextReserve` is a plain number by design — see `ContextReserveInput` for
|
|
3570
3620
|
* why a resolver form was dropped rather than accepted and ignored.
|
|
3571
3621
|
*
|
|
3572
3622
|
* @internal
|
|
3573
3623
|
*/
|
|
3574
|
-
get
|
|
3575
|
-
var _a, _b, _c;
|
|
3576
|
-
|
|
3624
|
+
get contextReserve() {
|
|
3625
|
+
var _a, _b, _c, _d;
|
|
3626
|
+
const resolved = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getContextReserve) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
3627
|
+
if (resolved)
|
|
3628
|
+
return resolved;
|
|
3629
|
+
const active = (_d = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.getActiveAgent) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
3630
|
+
const declared = active === null || active === void 0 ? void 0 : active.contextReserve;
|
|
3631
|
+
if (declared == null)
|
|
3632
|
+
return undefined;
|
|
3633
|
+
// Runtime-validated for the same reason `normalizeContextReserve` is: this is
|
|
3634
|
+
// host-supplied config, and `undefined <= 0` is `false`, so a bare comparison
|
|
3635
|
+
// lets a malformed reserve through as `tokens: undefined` (reviewer catch on #2504).
|
|
3636
|
+
const positive = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
|
|
3637
|
+
const tokens = typeof declared === 'number' ? declared : declared.tokens;
|
|
3638
|
+
if (!positive(tokens))
|
|
3639
|
+
return undefined;
|
|
3640
|
+
const warnAhead = typeof declared === 'number' ? undefined : declared.warnAheadTokens;
|
|
3641
|
+
return {
|
|
3642
|
+
tokens,
|
|
3643
|
+
warnAheadTokens: positive(warnAhead) ? warnAhead : undefined,
|
|
3644
|
+
agentName: active.name,
|
|
3645
|
+
basis: 'locked',
|
|
3646
|
+
};
|
|
3577
3647
|
}
|
|
3578
3648
|
/**
|
|
3579
3649
|
* What compacting right now would reclaim, or `null` when it would not be
|
|
@@ -3585,14 +3655,15 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3585
3655
|
* re-evaluation off the Redux mirror.
|
|
3586
3656
|
*/
|
|
3587
3657
|
get compactionPlan() {
|
|
3588
|
-
var _a, _b, _c;
|
|
3658
|
+
var _a, _b, _c, _d, _e;
|
|
3589
3659
|
if (this.messages.length === 0)
|
|
3590
3660
|
return null;
|
|
3591
3661
|
const budget = this.contextBudget;
|
|
3592
|
-
return ((
|
|
3662
|
+
return ((_e = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getCompactionPlan) === null || _b === void 0 ? void 0 : _b.call(_a, {
|
|
3663
|
+
preambleTokens: (_d = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.getPreambleTokens) === null || _d === void 0 ? void 0 : _d.call(_c),
|
|
3593
3664
|
tailTokenBudget: budget.tailTokenBudget,
|
|
3594
3665
|
minReclaimTokens: budget.minReclaimTokens,
|
|
3595
|
-
})) !== null &&
|
|
3666
|
+
})) !== null && _e !== void 0 ? _e : null);
|
|
3596
3667
|
}
|
|
3597
3668
|
/**
|
|
3598
3669
|
* Tooltip and hint for the Compact row — why it is or is not available.
|
|
@@ -3630,11 +3701,48 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3630
3701
|
* and gating on the way out is the lesser harm; the headroom reserve exists so
|
|
3631
3702
|
* this is rare rather than routine.
|
|
3632
3703
|
*/
|
|
3704
|
+
/**
|
|
3705
|
+
* WHY the composer is blocked, when it is (GENC-1582). `undefined` unless
|
|
3706
|
+
* {@link FoundationAiAssistant.contextGate} is `'blocked'`.
|
|
3707
|
+
*
|
|
3708
|
+
* A cause rather than a fourth gate level: the severity is identical — sends are
|
|
3709
|
+
* refused either way — and every consumer switches on the three levels. What
|
|
3710
|
+
* differs is what the user can do about it, which is worth splitting because the
|
|
3711
|
+
* two answers have no remedy in common.
|
|
3712
|
+
*
|
|
3713
|
+
* - `'transcript'` — the conversation has grown into the reserve. Compacting or
|
|
3714
|
+
* clearing helps, and the copy says so.
|
|
3715
|
+
* - `'model'` — the active agent's reserve meets or exceeds the whole window, so
|
|
3716
|
+
* `blockAt` is 0 and EVERY message is refused, including the first of an empty
|
|
3717
|
+
* chat. Neither compacting nor clearing can help: the constraint is the model,
|
|
3718
|
+
* not the transcript. Before this existed the user was told to clear the chat,
|
|
3719
|
+
* did, and hit the identical block with no way to understand why.
|
|
3720
|
+
*
|
|
3721
|
+
* Keyed off `blockAt === 0`, deliberately NOT off whether a compaction plan
|
|
3722
|
+
* exists. A null plan has two unrelated causes — nothing left worth summarizing,
|
|
3723
|
+
* versus a window that cannot host the agent at all — and conflating them is what
|
|
3724
|
+
* produced the misleading copy.
|
|
3725
|
+
*
|
|
3726
|
+
* Note the block itself is correct in both cases and must stay: an agent that
|
|
3727
|
+
* measurably cannot complete a turn in this window would otherwise start one and
|
|
3728
|
+
* die mid-loop on a provider context error, with partial writes and no clean
|
|
3729
|
+
* exit. Refusing up front is the better failure.
|
|
3730
|
+
*
|
|
3731
|
+
* @internal
|
|
3732
|
+
*/
|
|
3733
|
+
get contextBlockCause() {
|
|
3734
|
+
if (this.contextGate !== 'blocked')
|
|
3735
|
+
return undefined;
|
|
3736
|
+
return this.contextBudget.blockAt === 0 ? 'model' : 'transcript';
|
|
3737
|
+
}
|
|
3633
3738
|
get contextGate() {
|
|
3739
|
+
var _a;
|
|
3634
3740
|
const budget = this.contextBudget;
|
|
3635
3741
|
if (!budget.enabled)
|
|
3636
3742
|
return 'ok';
|
|
3637
|
-
|
|
3743
|
+
// The figure from the last moment nothing was running, not the live one — see
|
|
3744
|
+
// `_gateContextTokens` for why the gauge and the gate differ here.
|
|
3745
|
+
const tokens = (_a = this._gateContextTokens) !== null && _a !== void 0 ? _a : this.contextTokens;
|
|
3638
3746
|
if (tokens == null || budget.blockAt == null || budget.warnAt == null)
|
|
3639
3747
|
return 'ok';
|
|
3640
3748
|
if (tokens >= budget.blockAt)
|
|
@@ -3653,12 +3761,36 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3653
3761
|
* @internal
|
|
3654
3762
|
*/
|
|
3655
3763
|
get contextGateReason() {
|
|
3764
|
+
var _a, _b;
|
|
3656
3765
|
const level = this.contextGate;
|
|
3657
3766
|
const canHelp = this.compactionPlan != null;
|
|
3767
|
+
if (level === 'blocked' && this.contextBlockCause === 'model') {
|
|
3768
|
+
// The window cannot host this agent, so neither remedy the other branches
|
|
3769
|
+
// offer can help. Name the agent and both figures instead: the numbers are
|
|
3770
|
+
// what make "switch model" an instruction rather than a shrug, and they are
|
|
3771
|
+
// already to hand.
|
|
3772
|
+
const reserve = this.contextReserve;
|
|
3773
|
+
const need = reserve ? `${reserve.tokens.toLocaleString()} tokens` : 'more headroom';
|
|
3774
|
+
const have = (_a = this.contextLimit) === null || _a === void 0 ? void 0 : _a.toLocaleString();
|
|
3775
|
+
const who = (_b = reserve === null || reserve === void 0 ? void 0 : reserve.agentName) !== null && _b !== void 0 ? _b : 'This task';
|
|
3776
|
+
return (`${who} needs about ${need} of context headroom to complete a turn, and this model has ` +
|
|
3777
|
+
`${have !== null && have !== void 0 ? have : 'less than that'}. Switch to a model with a larger context window, or pick a ` +
|
|
3778
|
+
`different agent — compacting or clearing this conversation will not help.`);
|
|
3779
|
+
}
|
|
3658
3780
|
if (level === 'blocked') {
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3781
|
+
// Name the agent whose headroom is binding when routing is automatic
|
|
3782
|
+
// (GENC-1582). The reserve is then the hungriest agent the turn could land
|
|
3783
|
+
// on, so the composer can close while there is plenty of room for the short
|
|
3784
|
+
// question the user was about to type — and a message that only said "not
|
|
3785
|
+
// enough room for more work" would read as a bug rather than a reason.
|
|
3786
|
+
const reserve = this.contextReserve;
|
|
3787
|
+
const cause = (reserve === null || reserve === void 0 ? void 0 : reserve.basis) === 'reachable-max'
|
|
3788
|
+
? `There is not enough room in the model context to start another task — ${reserve.agentName} needs the most of any I could pick up next.`
|
|
3789
|
+
: 'There is not enough room in the model context for more work.';
|
|
3790
|
+
const remedy = canHelp
|
|
3791
|
+
? 'Click the error pill and compact the context to continue working.'
|
|
3792
|
+
: 'Compacting the context will not free enough room, click the error pill to clear and start a new conversation.';
|
|
3793
|
+
return `${cause} ${remedy}`;
|
|
3662
3794
|
}
|
|
3663
3795
|
if (this.flowOwnerAgentName !== null) {
|
|
3664
3796
|
return 'This conversation is nearly full. The task in progress will finish, but compact before starting another.';
|
|
@@ -3963,6 +4095,10 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3963
4095
|
if (this.driver)
|
|
3964
4096
|
this.teardownDriver(key);
|
|
3965
4097
|
(_g = this._sessionRef) === null || _g === void 0 ? void 0 : _g.actions.aiAssistant.resetSession();
|
|
4098
|
+
// `resetSession` zeroes the store-backed `contextTokens`; the gate's held copy
|
|
4099
|
+
// is ours to drop. Leaving it would carry a block into a conversation that no
|
|
4100
|
+
// longer exists — the one thing `contextGate` must never do (GENC-1582).
|
|
4101
|
+
this._gateContextTokens = undefined;
|
|
3966
4102
|
if (key) {
|
|
3967
4103
|
this.rebaselineOnClearedTranscript(key, discarded);
|
|
3968
4104
|
this.rebuildDriver(key);
|
|
@@ -5172,6 +5308,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
5172
5308
|
// setState was dispatched directly via the captured ref (not via the
|
|
5173
5309
|
// state setter) so the setter's loading→idle cleanup path doesn't fire
|
|
5174
5310
|
// here. Replicate the parts of it that matter for the post-turn settle.
|
|
5311
|
+
this.rearmContextGate();
|
|
5175
5312
|
this.maybeAutoFocusChatInput();
|
|
5176
5313
|
this.applyDeferredSessionSwitch();
|
|
5177
5314
|
this.applyDeferredAgentsSwap();
|
|
@@ -5385,6 +5522,9 @@ __decorate([
|
|
|
5385
5522
|
__decorate([
|
|
5386
5523
|
observable
|
|
5387
5524
|
], FoundationAiAssistant.prototype, "_sessionRef", void 0);
|
|
5525
|
+
__decorate([
|
|
5526
|
+
observable
|
|
5527
|
+
], FoundationAiAssistant.prototype, "_gateContextTokens", void 0);
|
|
5388
5528
|
__decorate([
|
|
5389
5529
|
volatile
|
|
5390
5530
|
], FoundationAiAssistant.prototype, "busy", null);
|
|
@@ -5528,13 +5668,16 @@ __decorate([
|
|
|
5528
5668
|
], FoundationAiAssistant.prototype, "contextBudget", null);
|
|
5529
5669
|
__decorate([
|
|
5530
5670
|
volatile
|
|
5531
|
-
], FoundationAiAssistant.prototype, "
|
|
5671
|
+
], FoundationAiAssistant.prototype, "contextReserve", null);
|
|
5532
5672
|
__decorate([
|
|
5533
5673
|
volatile
|
|
5534
5674
|
], FoundationAiAssistant.prototype, "compactionPlan", null);
|
|
5535
5675
|
__decorate([
|
|
5536
5676
|
volatile
|
|
5537
5677
|
], FoundationAiAssistant.prototype, "compactHint", null);
|
|
5678
|
+
__decorate([
|
|
5679
|
+
volatile
|
|
5680
|
+
], FoundationAiAssistant.prototype, "contextBlockCause", null);
|
|
5538
5681
|
__decorate([
|
|
5539
5682
|
volatile
|
|
5540
5683
|
], FoundationAiAssistant.prototype, "contextGate", null);
|