@genesislcap/ai-assistant 15.5.0 → 15.6.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 +27 -0
- package/dist/ai-assistant.d.ts +12 -0
- package/dist/chat-driver.cjs +15 -4
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +15 -4
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +11 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts +12 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/utils/condense-history.d.ts +18 -0
- package/dist/dts/utils/condense-history.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +3 -1
- package/dist/esm/utils/condense-history.js +33 -5
- package/dist/esm/utils/condense-history.test.js +97 -0
- package/package.json +17 -17
- package/src/components/chat-driver/chat-driver.ts +15 -0
- package/src/utils/condense-history.test.ts +166 -0
- package/src/utils/condense-history.ts +56 -3
|
@@ -7505,6 +7505,17 @@
|
|
|
7505
7505
|
"description": "Hard cap on tool-loop iterations.",
|
|
7506
7506
|
"default": "maxToolIterations"
|
|
7507
7507
|
},
|
|
7508
|
+
{
|
|
7509
|
+
"kind": "field",
|
|
7510
|
+
"name": "condenseBatchCalls",
|
|
7511
|
+
"type": {
|
|
7512
|
+
"text": "number"
|
|
7513
|
+
},
|
|
7514
|
+
"privacy": "private",
|
|
7515
|
+
"readonly": true,
|
|
7516
|
+
"description": "Model-calls per condensation batch; `1` collapses as soon as a trigger fires.",
|
|
7517
|
+
"default": "condenseBatchCalls"
|
|
7518
|
+
},
|
|
7508
7519
|
{
|
|
7509
7520
|
"kind": "field",
|
|
7510
7521
|
"name": "sessionKey",
|
|
@@ -96,6 +96,16 @@ export interface ChatDriverConfig {
|
|
|
96
96
|
maxToolIterations?: number;
|
|
97
97
|
/** Hard cap on fold operations. Default `5`. */
|
|
98
98
|
maxFoldOperations?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Collapse `condenseWhen` payloads in batches of this many model-calls rather than as
|
|
101
|
+
* soon as each trigger fires. A positive integer; decimals are floored and anything
|
|
102
|
+
* below `1` becomes `1`. Default `1` — collapse immediately, the historical behaviour.
|
|
103
|
+
*
|
|
104
|
+
* Condensation rewrites history in place, so collapsing continuously breaks the
|
|
105
|
+
* provider's prompt cache on nearly every call of a re-read loop. Raising this trades
|
|
106
|
+
* up to that many calls' worth of extra context for one cache break per batch.
|
|
107
|
+
*/
|
|
108
|
+
condenseBatchCalls?: number;
|
|
99
109
|
/** Ring-buffer size for per-turn snapshots. Default `400`. */
|
|
100
110
|
maxTurnSnapshots?: number;
|
|
101
111
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
@@ -419,6 +429,8 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
419
429
|
private unsubscribeRegistry?;
|
|
420
430
|
/** Hard cap on tool-loop iterations. */
|
|
421
431
|
private readonly maxToolIterations;
|
|
432
|
+
/** Model-calls per condensation batch; `1` collapses as soon as a trigger fires. */
|
|
433
|
+
private readonly condenseBatchCalls;
|
|
422
434
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
423
435
|
private readonly sessionKey;
|
|
424
436
|
/** Injected activity bus; defaults to a no-op off-browser (Node/tests/headless). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAGlB,cAAc,EACd,gBAAgB,EAEhB,WAAW,EAGX,cAAc,EAKd,yBAAyB,EAEzB,qBAAqB,EAGtB,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EACV,WAAW,EAKX,iBAAiB,EAIjB,oBAAoB,EACpB,iBAAiB,EAElB,MAAM,qBAAqB,CAAC;AAgB7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAW1E,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA8CxE,wFAAwF;AACxF,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAMhE;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,WAAW,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;AAE9E;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAQD;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,iFAAiF;IACjF,eAAe,CAAC,EAAE,oBAAoB,CAAC;IACvC,2DAA2D;IAC3D,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,oDAAoD;IACpD,aAAa,CAAC,EAAE,WAAW,EAAE,CAAC;IAC9B,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,qBAAa,UAAW,SAAQ,WAAY,YAAW,QAAQ;
|
|
1
|
+
{"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,6 +64,24 @@ export interface CondenseContext {
|
|
|
64
64
|
* `phaseEnd` clock. True once `endPhase()` has been called since the call.
|
|
65
65
|
*/
|
|
66
66
|
phaseEnded: (phaseEpoch: number) => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Collapse in batches of this many model-calls instead of continuously. A positive
|
|
69
|
+
* integer; decimals are floored and anything below `1` becomes `1`. Omit (or `1`) to
|
|
70
|
+
* collapse as soon as a trigger fires, which is the historical behaviour.
|
|
71
|
+
*
|
|
72
|
+
* Condensation rewrites a payload *in place*, so the model-bound history is not
|
|
73
|
+
* append-only and every prompt-cache entry written before the rewrite is invalidated
|
|
74
|
+
* from that position on. Collapsing the moment each trigger fires therefore breaks the
|
|
75
|
+
* cache on nearly every call of a re-read loop — the case condensation exists to
|
|
76
|
+
* contain. Batching holds the collapse set still between boundaries so the prefix is
|
|
77
|
+
* append-only in between, trading up to `batchCalls` calls' worth of extra context for
|
|
78
|
+
* one cache break per batch instead of one per call.
|
|
79
|
+
*
|
|
80
|
+
* `agentEnd` and `phaseEnd` are exempt and still collapse as they fire: they mark a
|
|
81
|
+
* discontinuity that invalidates the cached prefix anyway, so deferring them would hold
|
|
82
|
+
* stale payloads longer for nothing.
|
|
83
|
+
*/
|
|
84
|
+
batchCalls?: number;
|
|
67
85
|
}
|
|
68
86
|
/**
|
|
69
87
|
* Structured detail for a `context.condensed` meta-event. The index signature
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condense-history.d.ts","sourceRoot":"","sources":["../../../src/utils/condense-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAmB,MAAM,4BAA4B,CAAC;AAE/F;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAQ9C;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD;;;OAGG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"condense-history.d.ts","sourceRoot":"","sources":["../../../src/utils/condense-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAmB,MAAM,4BAA4B,CAAC;AAE/F;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAQ9C;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD;;;OAGG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;CACrB;AAgED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC/C,GAAG,EAAE,eAAe,EACpB,WAAW,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,GAClD,WAAW,EAAE,CA0Jf"}
|
|
@@ -230,8 +230,9 @@ export class ChatDriver extends EventTarget {
|
|
|
230
230
|
* picked up on the next turn.
|
|
231
231
|
*/
|
|
232
232
|
this.resolvedStatusCache = new Map();
|
|
233
|
-
const { toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS, sessionKey = '', activityBus = NOOP_ACTIVITY_BUS, } = config;
|
|
233
|
+
const { toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, condenseBatchCalls = 1, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS, sessionKey = '', activityBus = NOOP_ACTIVITY_BUS, } = config;
|
|
234
234
|
this.maxToolIterations = maxToolIterations;
|
|
235
|
+
this.condenseBatchCalls = condenseBatchCalls;
|
|
235
236
|
this.sessionKey = sessionKey;
|
|
236
237
|
this.activityBus = activityBus;
|
|
237
238
|
if (typeof toolHandlers === 'function') {
|
|
@@ -1824,6 +1825,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1824
1825
|
// A call's phase has ended once the app declared a later phase epoch
|
|
1825
1826
|
// current via `endPhase()`.
|
|
1826
1827
|
phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
|
|
1828
|
+
batchCalls: this.condenseBatchCalls,
|
|
1827
1829
|
}, (detail) => recordMetaEvent(this.sessionKey, 'context.condensed', detail));
|
|
1828
1830
|
const normalizedHistory = normalizeForProvider(condensedHistory);
|
|
1829
1831
|
const historyForProvider = this.providerHistoryTransform
|
|
@@ -60,6 +60,23 @@ function triggerLabel(trigger) {
|
|
|
60
60
|
function estimateTokensSaved(origLen, stubLen) {
|
|
61
61
|
return Math.max(0, Math.ceil((origLen - stubLen) / APPROX_CHARS_PER_TOKEN));
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether a trigger marks a genuine discontinuity rather than firing continuously, and so is
|
|
65
|
+
* exempt from batching.
|
|
66
|
+
*
|
|
67
|
+
* `agentEnd` advances only when the active agent's name changes (or a sub-agent completes /
|
|
68
|
+
* the agent is released), which swaps the system prompt and tool definitions — those render
|
|
69
|
+
* before the messages, so the cached prefix is invalidated at that point regardless. `phaseEnd`
|
|
70
|
+
* is the app declaring the same kind of boundary explicitly, and falls back to `agentEnd`.
|
|
71
|
+
* Deferring either to the next batch would buy no cache and only hold stale payloads longer.
|
|
72
|
+
*
|
|
73
|
+
* The rest fire continuously (`superseded`, `age`) or at a point where the history is otherwise
|
|
74
|
+
* append-only (`turnEnd` — a new turn appends a user message, leaving the previous entry live),
|
|
75
|
+
* so batching them is exactly where the cache is won.
|
|
76
|
+
*/
|
|
77
|
+
function marksDiscontinuity(kind) {
|
|
78
|
+
return kind === 'agentEnd' || kind === 'phaseEnd';
|
|
79
|
+
}
|
|
63
80
|
/**
|
|
64
81
|
* Collapse stale tool payloads (declared via `condenseWhen`) out of the
|
|
65
82
|
* model-bound history. Pure over the `history` array — it emits new message
|
|
@@ -98,7 +115,7 @@ function estimateTokensSaved(origLen, stubLen) {
|
|
|
98
115
|
* @param onCondensed - Invoked once per payload at its full→stub transition.
|
|
99
116
|
*/
|
|
100
117
|
export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
101
|
-
var _a;
|
|
118
|
+
var _a, _b;
|
|
102
119
|
if (policies.size === 0)
|
|
103
120
|
return history;
|
|
104
121
|
const triggersOf = (entry) => Array.isArray(entry.policy.on) ? entry.policy.on : [entry.policy.on];
|
|
@@ -106,15 +123,26 @@ export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
106
123
|
// lookup. For each `superseded` key the LAST call in history order survives.
|
|
107
124
|
// The name lookup lets a tool message (which carries only an id) name its tool
|
|
108
125
|
// in stubs and events.
|
|
126
|
+
// Batch boundary: the collapse set is resolved as of this model-call and stays put
|
|
127
|
+
// until the next boundary, so the prefix is append-only in between. `batchCalls` of 1
|
|
128
|
+
// (the default) makes this `ctx.modelCall` — i.e. no batching.
|
|
129
|
+
const batchCalls = Math.max(1, Math.floor((_a = ctx.batchCalls) !== null && _a !== void 0 ? _a : 1));
|
|
130
|
+
const asOf = batchCalls === 1 ? ctx.modelCall : Math.floor(ctx.modelCall / batchCalls) * batchCalls;
|
|
131
|
+
// A call made after the boundary is invisible to this pass in BOTH directions: it
|
|
132
|
+
// cannot supersede an older call (which would collapse the older payload early), and
|
|
133
|
+
// it cannot itself collapse (which would elide the very result just fetched). Unbatched,
|
|
134
|
+
// the boundary is the live clock and this gate is off entirely — a call registered on
|
|
135
|
+
// the current model-call is still eligible, as it always was.
|
|
136
|
+
const withinBatch = (entry) => batchCalls === 1 || entry.iteration <= asOf;
|
|
109
137
|
const latestByKey = new Map();
|
|
110
138
|
const nameById = new Map();
|
|
111
139
|
for (const msg of history) {
|
|
112
|
-
if (!((
|
|
140
|
+
if (!((_b = msg.toolCalls) === null || _b === void 0 ? void 0 : _b.length))
|
|
113
141
|
continue;
|
|
114
142
|
for (const tc of msg.toolCalls) {
|
|
115
143
|
nameById.set(tc.id, tc.name);
|
|
116
144
|
const entry = policies.get(tc.id);
|
|
117
|
-
if (!entry)
|
|
145
|
+
if (!entry || !withinBatch(entry))
|
|
118
146
|
continue;
|
|
119
147
|
for (const t of triggersOf(entry)) {
|
|
120
148
|
if (t.kind === 'superseded')
|
|
@@ -128,7 +156,7 @@ export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
128
156
|
// past the last allowed view: turns:1 → seen once, then gone. Monotonic
|
|
129
157
|
// clock, so this holds across turn boundaries too.
|
|
130
158
|
case 'age':
|
|
131
|
-
return
|
|
159
|
+
return asOf - entry.iteration > trig.turns;
|
|
132
160
|
case 'turnEnd':
|
|
133
161
|
return ctx.turn > entry.turn;
|
|
134
162
|
case 'agentEnd':
|
|
@@ -144,7 +172,7 @@ export function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
144
172
|
};
|
|
145
173
|
// The first listed trigger that fires — drives the collapse, the stub reason,
|
|
146
174
|
// and the event label. `undefined` means the payload stays full.
|
|
147
|
-
const firstFired = (toolCallId, entry) => triggersOf(entry).find((t) => triggerFires(toolCallId, entry, t));
|
|
175
|
+
const firstFired = (toolCallId, entry) => triggersOf(entry).find((t) => (marksDiscontinuity(t.kind) || withinBatch(entry)) && triggerFires(toolCallId, entry, t));
|
|
148
176
|
return history.map((msg) => {
|
|
149
177
|
var _a, _b, _c, _d;
|
|
150
178
|
// Tool-call ARGS live on the assistant message.
|
|
@@ -27,6 +27,7 @@ const ctx = (o = {}) => {
|
|
|
27
27
|
turn: (_b = o.turn) !== null && _b !== void 0 ? _b : 0,
|
|
28
28
|
activationEnded: (_c = o.activationEnded) !== null && _c !== void 0 ? _c : (() => false),
|
|
29
29
|
phaseEnded: (_d = o.phaseEnded) !== null && _d !== void 0 ? _d : (() => false),
|
|
30
|
+
batchCalls: o.batchCalls,
|
|
30
31
|
});
|
|
31
32
|
};
|
|
32
33
|
const sink = () => {
|
|
@@ -336,3 +337,99 @@ suite('multi-trigger first-wins: the earliest-listed firing trigger is the reaso
|
|
|
336
337
|
assert.is((_b = events.find((e) => e.toolCallId === 'm2')) === null || _b === void 0 ? void 0 : _b.trigger, 'turnEnd', 'the supersession survivor still collapses via the next trigger');
|
|
337
338
|
});
|
|
338
339
|
suite.run();
|
|
340
|
+
// ── batched collapse (GENC-1476) ───────────────────────────────────────────
|
|
341
|
+
// Condensation rewrites history in place, so collapsing the instant a trigger fires
|
|
342
|
+
// breaks the prompt cache on nearly every call of a re-read loop. Batching holds the
|
|
343
|
+
// collapse set still between boundaries.
|
|
344
|
+
const batch = createLogicSuite('applyCondensation batching');
|
|
345
|
+
/** A re-read loop: `n` reads of the same path, each registered one model-call apart. */
|
|
346
|
+
const reReadLoop = (n) => {
|
|
347
|
+
const history = [];
|
|
348
|
+
const policies = new Map();
|
|
349
|
+
for (let i = 1; i <= n; i += 1) {
|
|
350
|
+
history.push(asstCall(`r${i}`, 'vfs_read', { path: 'A.tsx' }), toolMsg(`r${i}`, big()));
|
|
351
|
+
policies.set(`r${i}`, reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: i }));
|
|
352
|
+
}
|
|
353
|
+
return { history, policies };
|
|
354
|
+
};
|
|
355
|
+
/** Result content of every tool message, in order. */
|
|
356
|
+
const results = (out) => out.filter((m) => m.role === 'tool').map((m) => { var _a, _b; return (_b = (_a = m.toolResult) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : ''; });
|
|
357
|
+
batch('without batching every superseded read collapses immediately', () => {
|
|
358
|
+
const { history, policies } = reReadLoop(4);
|
|
359
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 4 }), sink().on));
|
|
360
|
+
// Only the newest survives; the other three are already stubs.
|
|
361
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 3);
|
|
362
|
+
assert.is(out[3].length, big().length);
|
|
363
|
+
});
|
|
364
|
+
batch('batching leaves the prefix untouched between boundaries', () => {
|
|
365
|
+
const { history, policies } = reReadLoop(4);
|
|
366
|
+
// Boundary is floor(4/10)*10 = 0, so no call is within the batch yet.
|
|
367
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on));
|
|
368
|
+
assert.equal(out.map((c) => c.length), [big().length, big().length, big().length, big().length]);
|
|
369
|
+
});
|
|
370
|
+
batch('the batch boundary collapses the backlog in one step', () => {
|
|
371
|
+
const { history, policies } = reReadLoop(12);
|
|
372
|
+
const before = results(applyCondensation(history, policies, ctx({ modelCall: 9, batchCalls: 10 }), sink().on));
|
|
373
|
+
const after = results(applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on));
|
|
374
|
+
assert.is(before.filter((c) => c.startsWith('[')).length, 0);
|
|
375
|
+
// At the boundary, reads 1-9 collapse together; 10-12 are past it and stay full.
|
|
376
|
+
assert.is(after.filter((c) => c.startsWith('[')).length, 9);
|
|
377
|
+
});
|
|
378
|
+
batch('a call past the boundary neither collapses nor supersedes an older one', () => {
|
|
379
|
+
// The regression this guards: excluding the new call as a SUPERSEDER but still letting
|
|
380
|
+
// it collapse would elide the very result the agent just fetched.
|
|
381
|
+
const { history, policies } = reReadLoop(2);
|
|
382
|
+
policies.set('r2', reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: 7 }));
|
|
383
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 5 }), sink().on));
|
|
384
|
+
assert.is(out[1].length, big().length, 'the newest read must stay full');
|
|
385
|
+
assert.is(out[0].length, big().length, 'the older read must not collapse early either');
|
|
386
|
+
});
|
|
387
|
+
batch('age is measured against the boundary, not the live clock', () => {
|
|
388
|
+
const history = [asstCall('g1', 'grep', {}), toolMsg('g1', big())];
|
|
389
|
+
const policies = new Map([
|
|
390
|
+
['g1', reg({ on: { kind: 'age', turns: 1 }, response: 'pointer' }, { iteration: 1 })],
|
|
391
|
+
]);
|
|
392
|
+
// Live clock says age 4 > 1 (would collapse); the boundary says 0, so it holds.
|
|
393
|
+
const held = results(applyCondensation(history, policies, ctx({ modelCall: 5, batchCalls: 10 }), sink().on));
|
|
394
|
+
assert.is(held[0].length, big().length);
|
|
395
|
+
const collapsed = results(applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on));
|
|
396
|
+
assert.ok(collapsed[0].startsWith('['));
|
|
397
|
+
});
|
|
398
|
+
batch('batchCalls of 1, 0 or absent all keep the historical behaviour', () => {
|
|
399
|
+
for (const batchCalls of [1, 0, undefined]) {
|
|
400
|
+
const { history, policies } = reReadLoop(3);
|
|
401
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 3, batchCalls }), sink().on));
|
|
402
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 2, `batchCalls=${batchCalls}`);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
batch('agentEnd is exempt from batching — it collapses as it fires', () => {
|
|
406
|
+
// An agent swap changes the system prompt and tool defs, which invalidates the cached
|
|
407
|
+
// prefix anyway, so deferring the collapse would hold a stale payload for nothing.
|
|
408
|
+
const history = [asstCall('a1', 'read_context', {}), toolMsg('a1', big())];
|
|
409
|
+
const policies = new Map([
|
|
410
|
+
['a1', reg({ on: { kind: 'agentEnd' }, response: 'pointer' }, { iteration: 7, activation: 1 })],
|
|
411
|
+
]);
|
|
412
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 10, activationEnded: () => true }), sink().on));
|
|
413
|
+
// Boundary is 0 and the call sits at 7, so a batched trigger would have been deferred.
|
|
414
|
+
assert.ok(out[0].startsWith('['));
|
|
415
|
+
});
|
|
416
|
+
batch('phaseEnd is exempt too', () => {
|
|
417
|
+
const history = [asstCall('p1', 'read_docs', {}), toolMsg('p1', big())];
|
|
418
|
+
const policies = new Map([
|
|
419
|
+
['p1', reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { iteration: 7, phaseEpoch: 1 })],
|
|
420
|
+
]);
|
|
421
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 10, phaseEnded: () => true }), sink().on));
|
|
422
|
+
assert.ok(out[0].startsWith('['));
|
|
423
|
+
});
|
|
424
|
+
batch('a mixed policy batches superseded but still honours agentEnd immediately', () => {
|
|
425
|
+
// The vfs shape: `on: [{ superseded }, { agentEnd }]`. Mid-run the supersession is
|
|
426
|
+
// deferred; once the agent ends the payload collapses regardless of the boundary.
|
|
427
|
+
const { history, policies } = reReadLoop(2);
|
|
428
|
+
for (const id of ['r1', 'r2'])
|
|
429
|
+
policies.set(id, reg({ on: [{ kind: 'superseded', by: 'A.tsx' }, { kind: 'agentEnd' }], response: 'pointer' }, { iteration: id === 'r1' ? 1 : 2, activation: 1 }));
|
|
430
|
+
const midRun = results(applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on));
|
|
431
|
+
assert.is(midRun.filter((c) => c.startsWith('[')).length, 0, 'supersession is deferred');
|
|
432
|
+
const afterSwap = results(applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10, activationEnded: () => true }), sink().on));
|
|
433
|
+
assert.is(afterSwap.filter((c) => c.startsWith('[')).length, 2, 'agentEnd collapses both');
|
|
434
|
+
});
|
|
435
|
+
batch.run();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/ai-assistant",
|
|
3
3
|
"description": "Genesis AI Assistant micro-frontend",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.6.1",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -73,26 +73,26 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@genesislcap/foundation-testing": "15.
|
|
77
|
-
"@genesislcap/genx": "15.
|
|
78
|
-
"@genesislcap/rollup-builder": "15.
|
|
79
|
-
"@genesislcap/ts-builder": "15.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.
|
|
81
|
-
"@genesislcap/vite-builder": "15.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.
|
|
76
|
+
"@genesislcap/foundation-testing": "15.6.1",
|
|
77
|
+
"@genesislcap/genx": "15.6.1",
|
|
78
|
+
"@genesislcap/rollup-builder": "15.6.1",
|
|
79
|
+
"@genesislcap/ts-builder": "15.6.1",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "15.6.1",
|
|
81
|
+
"@genesislcap/vite-builder": "15.6.1",
|
|
82
|
+
"@genesislcap/webpack-builder": "15.6.1",
|
|
83
83
|
"@types/dompurify": "^3.0.5",
|
|
84
84
|
"@types/marked": "^5.0.2",
|
|
85
85
|
"esbuild": "0.25.12"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@genesislcap/foundation-ai": "15.
|
|
89
|
-
"@genesislcap/foundation-logger": "15.
|
|
90
|
-
"@genesislcap/foundation-notifications": "15.
|
|
91
|
-
"@genesislcap/foundation-redux": "15.
|
|
92
|
-
"@genesislcap/foundation-ui": "15.
|
|
93
|
-
"@genesislcap/foundation-utils": "15.
|
|
94
|
-
"@genesislcap/rapid-design-system": "15.
|
|
95
|
-
"@genesislcap/web-core": "15.
|
|
88
|
+
"@genesislcap/foundation-ai": "15.6.1",
|
|
89
|
+
"@genesislcap/foundation-logger": "15.6.1",
|
|
90
|
+
"@genesislcap/foundation-notifications": "15.6.1",
|
|
91
|
+
"@genesislcap/foundation-redux": "15.6.1",
|
|
92
|
+
"@genesislcap/foundation-ui": "15.6.1",
|
|
93
|
+
"@genesislcap/foundation-utils": "15.6.1",
|
|
94
|
+
"@genesislcap/rapid-design-system": "15.6.1",
|
|
95
|
+
"@genesislcap/web-core": "15.6.1",
|
|
96
96
|
"dompurify": "^3.3.1",
|
|
97
97
|
"marked": "^17.0.3"
|
|
98
98
|
},
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"access": "public"
|
|
106
106
|
},
|
|
107
107
|
"customElements": "dist/custom-elements.json",
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "9139f6649cfbd67d5f940b7e02c04f7970ad45a5"
|
|
109
109
|
}
|
|
@@ -218,6 +218,16 @@ export interface ChatDriverConfig {
|
|
|
218
218
|
maxToolIterations?: number;
|
|
219
219
|
/** Hard cap on fold operations. Default `5`. */
|
|
220
220
|
maxFoldOperations?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Collapse `condenseWhen` payloads in batches of this many model-calls rather than as
|
|
223
|
+
* soon as each trigger fires. A positive integer; decimals are floored and anything
|
|
224
|
+
* below `1` becomes `1`. Default `1` — collapse immediately, the historical behaviour.
|
|
225
|
+
*
|
|
226
|
+
* Condensation rewrites history in place, so collapsing continuously breaks the
|
|
227
|
+
* provider's prompt cache on nearly every call of a re-read loop. Raising this trades
|
|
228
|
+
* up to that many calls' worth of extra context for one cache break per batch.
|
|
229
|
+
*/
|
|
230
|
+
condenseBatchCalls?: number;
|
|
221
231
|
/** Ring-buffer size for per-turn snapshots. Default `400`. */
|
|
222
232
|
maxTurnSnapshots?: number;
|
|
223
233
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
@@ -566,6 +576,8 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
566
576
|
|
|
567
577
|
/** Hard cap on tool-loop iterations. */
|
|
568
578
|
private readonly maxToolIterations: number;
|
|
579
|
+
/** Model-calls per condensation batch; `1` collapses as soon as a trigger fires. */
|
|
580
|
+
private readonly condenseBatchCalls: number;
|
|
569
581
|
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
570
582
|
private readonly sessionKey: string;
|
|
571
583
|
/** Injected activity bus; defaults to a no-op off-browser (Node/tests/headless). */
|
|
@@ -583,11 +595,13 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
583
595
|
primerHistory,
|
|
584
596
|
maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS,
|
|
585
597
|
maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS,
|
|
598
|
+
condenseBatchCalls = 1,
|
|
586
599
|
maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
587
600
|
sessionKey = '',
|
|
588
601
|
activityBus = NOOP_ACTIVITY_BUS,
|
|
589
602
|
} = config;
|
|
590
603
|
this.maxToolIterations = maxToolIterations;
|
|
604
|
+
this.condenseBatchCalls = condenseBatchCalls;
|
|
591
605
|
this.sessionKey = sessionKey;
|
|
592
606
|
this.activityBus = activityBus;
|
|
593
607
|
if (typeof toolHandlers === 'function') {
|
|
@@ -2335,6 +2349,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
2335
2349
|
// A call's phase has ended once the app declared a later phase epoch
|
|
2336
2350
|
// current via `endPhase()`.
|
|
2337
2351
|
phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
|
|
2352
|
+
batchCalls: this.condenseBatchCalls,
|
|
2338
2353
|
},
|
|
2339
2354
|
(detail) => recordMetaEvent(this.sessionKey, 'context.condensed', detail),
|
|
2340
2355
|
);
|
|
@@ -44,12 +44,14 @@ const ctx = (
|
|
|
44
44
|
turn?: number;
|
|
45
45
|
activationEnded?: (a: number) => boolean;
|
|
46
46
|
phaseEnded?: (e: number) => boolean;
|
|
47
|
+
batchCalls?: number;
|
|
47
48
|
} = {},
|
|
48
49
|
): CondenseContext => ({
|
|
49
50
|
modelCall: o.modelCall ?? 0,
|
|
50
51
|
turn: o.turn ?? 0,
|
|
51
52
|
activationEnded: o.activationEnded ?? (() => false),
|
|
52
53
|
phaseEnded: o.phaseEnded ?? (() => false),
|
|
54
|
+
batchCalls: o.batchCalls,
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
const sink = () => {
|
|
@@ -434,3 +436,167 @@ suite('multi-trigger first-wins: the earliest-listed firing trigger is the reaso
|
|
|
434
436
|
});
|
|
435
437
|
|
|
436
438
|
suite.run();
|
|
439
|
+
|
|
440
|
+
// ── batched collapse (GENC-1476) ───────────────────────────────────────────
|
|
441
|
+
// Condensation rewrites history in place, so collapsing the instant a trigger fires
|
|
442
|
+
// breaks the prompt cache on nearly every call of a re-read loop. Batching holds the
|
|
443
|
+
// collapse set still between boundaries.
|
|
444
|
+
|
|
445
|
+
const batch = createLogicSuite('applyCondensation batching');
|
|
446
|
+
|
|
447
|
+
/** A re-read loop: `n` reads of the same path, each registered one model-call apart. */
|
|
448
|
+
const reReadLoop = (n: number) => {
|
|
449
|
+
const history: ChatMessage[] = [];
|
|
450
|
+
const policies = new Map<string, RegisteredCondensePolicy>();
|
|
451
|
+
for (let i = 1; i <= n; i += 1) {
|
|
452
|
+
history.push(asstCall(`r${i}`, 'vfs_read', { path: 'A.tsx' }), toolMsg(`r${i}`, big()));
|
|
453
|
+
policies.set(
|
|
454
|
+
`r${i}`,
|
|
455
|
+
reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: i }),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
return { history, policies };
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/** Result content of every tool message, in order. */
|
|
462
|
+
const results = (out: ChatMessage[]): string[] =>
|
|
463
|
+
out.filter((m) => m.role === 'tool').map((m) => m.toolResult?.content ?? '');
|
|
464
|
+
|
|
465
|
+
batch('without batching every superseded read collapses immediately', () => {
|
|
466
|
+
const { history, policies } = reReadLoop(4);
|
|
467
|
+
const out = results(applyCondensation(history, policies, ctx({ modelCall: 4 }), sink().on));
|
|
468
|
+
// Only the newest survives; the other three are already stubs.
|
|
469
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 3);
|
|
470
|
+
assert.is(out[3].length, big().length);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
batch('batching leaves the prefix untouched between boundaries', () => {
|
|
474
|
+
const { history, policies } = reReadLoop(4);
|
|
475
|
+
// Boundary is floor(4/10)*10 = 0, so no call is within the batch yet.
|
|
476
|
+
const out = results(
|
|
477
|
+
applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on),
|
|
478
|
+
);
|
|
479
|
+
assert.equal(
|
|
480
|
+
out.map((c) => c.length),
|
|
481
|
+
[big().length, big().length, big().length, big().length],
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
batch('the batch boundary collapses the backlog in one step', () => {
|
|
486
|
+
const { history, policies } = reReadLoop(12);
|
|
487
|
+
const before = results(
|
|
488
|
+
applyCondensation(history, policies, ctx({ modelCall: 9, batchCalls: 10 }), sink().on),
|
|
489
|
+
);
|
|
490
|
+
const after = results(
|
|
491
|
+
applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on),
|
|
492
|
+
);
|
|
493
|
+
assert.is(before.filter((c) => c.startsWith('[')).length, 0);
|
|
494
|
+
// At the boundary, reads 1-9 collapse together; 10-12 are past it and stay full.
|
|
495
|
+
assert.is(after.filter((c) => c.startsWith('[')).length, 9);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
batch('a call past the boundary neither collapses nor supersedes an older one', () => {
|
|
499
|
+
// The regression this guards: excluding the new call as a SUPERSEDER but still letting
|
|
500
|
+
// it collapse would elide the very result the agent just fetched.
|
|
501
|
+
const { history, policies } = reReadLoop(2);
|
|
502
|
+
policies.set(
|
|
503
|
+
'r2',
|
|
504
|
+
reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: 7 }),
|
|
505
|
+
);
|
|
506
|
+
const out = results(
|
|
507
|
+
applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 5 }), sink().on),
|
|
508
|
+
);
|
|
509
|
+
assert.is(out[1].length, big().length, 'the newest read must stay full');
|
|
510
|
+
assert.is(out[0].length, big().length, 'the older read must not collapse early either');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
batch('age is measured against the boundary, not the live clock', () => {
|
|
514
|
+
const history = [asstCall('g1', 'grep', {}), toolMsg('g1', big())];
|
|
515
|
+
const policies = new Map([
|
|
516
|
+
['g1', reg({ on: { kind: 'age', turns: 1 }, response: 'pointer' }, { iteration: 1 })],
|
|
517
|
+
]);
|
|
518
|
+
// Live clock says age 4 > 1 (would collapse); the boundary says 0, so it holds.
|
|
519
|
+
const held = results(
|
|
520
|
+
applyCondensation(history, policies, ctx({ modelCall: 5, batchCalls: 10 }), sink().on),
|
|
521
|
+
);
|
|
522
|
+
assert.is(held[0].length, big().length);
|
|
523
|
+
const collapsed = results(
|
|
524
|
+
applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on),
|
|
525
|
+
);
|
|
526
|
+
assert.ok(collapsed[0].startsWith('['));
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
batch('batchCalls of 1, 0 or absent all keep the historical behaviour', () => {
|
|
530
|
+
for (const batchCalls of [1, 0, undefined]) {
|
|
531
|
+
const { history, policies } = reReadLoop(3);
|
|
532
|
+
const out = results(
|
|
533
|
+
applyCondensation(history, policies, ctx({ modelCall: 3, batchCalls }), sink().on),
|
|
534
|
+
);
|
|
535
|
+
assert.is(out.filter((c) => c.startsWith('[')).length, 2, `batchCalls=${batchCalls}`);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
batch('agentEnd is exempt from batching — it collapses as it fires', () => {
|
|
540
|
+
// An agent swap changes the system prompt and tool defs, which invalidates the cached
|
|
541
|
+
// prefix anyway, so deferring the collapse would hold a stale payload for nothing.
|
|
542
|
+
const history = [asstCall('a1', 'read_context', {}), toolMsg('a1', big())];
|
|
543
|
+
const policies = new Map([
|
|
544
|
+
['a1', reg({ on: { kind: 'agentEnd' }, response: 'pointer' }, { iteration: 7, activation: 1 })],
|
|
545
|
+
]);
|
|
546
|
+
const out = results(
|
|
547
|
+
applyCondensation(
|
|
548
|
+
history,
|
|
549
|
+
policies,
|
|
550
|
+
ctx({ modelCall: 7, batchCalls: 10, activationEnded: () => true }),
|
|
551
|
+
sink().on,
|
|
552
|
+
),
|
|
553
|
+
);
|
|
554
|
+
// Boundary is 0 and the call sits at 7, so a batched trigger would have been deferred.
|
|
555
|
+
assert.ok(out[0].startsWith('['));
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
batch('phaseEnd is exempt too', () => {
|
|
559
|
+
const history = [asstCall('p1', 'read_docs', {}), toolMsg('p1', big())];
|
|
560
|
+
const policies = new Map([
|
|
561
|
+
['p1', reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { iteration: 7, phaseEpoch: 1 })],
|
|
562
|
+
]);
|
|
563
|
+
const out = results(
|
|
564
|
+
applyCondensation(
|
|
565
|
+
history,
|
|
566
|
+
policies,
|
|
567
|
+
ctx({ modelCall: 7, batchCalls: 10, phaseEnded: () => true }),
|
|
568
|
+
sink().on,
|
|
569
|
+
),
|
|
570
|
+
);
|
|
571
|
+
assert.ok(out[0].startsWith('['));
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
batch('a mixed policy batches superseded but still honours agentEnd immediately', () => {
|
|
575
|
+
// The vfs shape: `on: [{ superseded }, { agentEnd }]`. Mid-run the supersession is
|
|
576
|
+
// deferred; once the agent ends the payload collapses regardless of the boundary.
|
|
577
|
+
const { history, policies } = reReadLoop(2);
|
|
578
|
+
for (const id of ['r1', 'r2'])
|
|
579
|
+
policies.set(
|
|
580
|
+
id,
|
|
581
|
+
reg(
|
|
582
|
+
{ on: [{ kind: 'superseded', by: 'A.tsx' }, { kind: 'agentEnd' }], response: 'pointer' },
|
|
583
|
+
{ iteration: id === 'r1' ? 1 : 2, activation: 1 },
|
|
584
|
+
),
|
|
585
|
+
);
|
|
586
|
+
const midRun = results(
|
|
587
|
+
applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on),
|
|
588
|
+
);
|
|
589
|
+
assert.is(midRun.filter((c) => c.startsWith('[')).length, 0, 'supersession is deferred');
|
|
590
|
+
|
|
591
|
+
const afterSwap = results(
|
|
592
|
+
applyCondensation(
|
|
593
|
+
history,
|
|
594
|
+
policies,
|
|
595
|
+
ctx({ modelCall: 4, batchCalls: 10, activationEnded: () => true }),
|
|
596
|
+
sink().on,
|
|
597
|
+
),
|
|
598
|
+
);
|
|
599
|
+
assert.is(afterSwap.filter((c) => c.startsWith('[')).length, 2, 'agentEnd collapses both');
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
batch.run();
|