@animalabs/agent-framework 0.6.4 → 0.6.6
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/README.md +5 -0
- package/dist/src/agent.d.ts +18 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +221 -2
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +59 -2
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +687 -229
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +27 -4
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +34 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +299 -67
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +1 -0
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +1 -0
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +3 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +4 -0
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +43 -12
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js +1 -0
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/types/agent.d.ts +15 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +1 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +9 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +2 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/activation-request-sanitize.test.d.ts +2 -0
- package/dist/test/activation-request-sanitize.test.d.ts.map +1 -0
- package/dist/test/activation-request-sanitize.test.js +89 -0
- package/dist/test/activation-request-sanitize.test.js.map +1 -0
- package/dist/test/agent-runtime-settings.test.d.ts +2 -0
- package/dist/test/agent-runtime-settings.test.d.ts.map +1 -0
- package/dist/test/agent-runtime-settings.test.js +120 -0
- package/dist/test/agent-runtime-settings.test.js.map +1 -0
- package/dist/test/channel-registry-routing.test.js +5 -4
- package/dist/test/channel-registry-routing.test.js.map +1 -1
- package/dist/test/conversation-fork-injection-scoping.test.js +12 -1
- package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -1
- package/dist/test/ephemeral-settle.test.d.ts +2 -0
- package/dist/test/ephemeral-settle.test.d.ts.map +1 -0
- package/dist/test/ephemeral-settle.test.js +351 -0
- package/dist/test/ephemeral-settle.test.js.map +1 -0
- package/dist/test/event-gate.test.js +21 -1
- package/dist/test/event-gate.test.js.map +1 -1
- package/dist/test/helpers/mock-membrane.d.ts +13 -1
- package/dist/test/helpers/mock-membrane.d.ts.map +1 -1
- package/dist/test/helpers/mock-membrane.js +8 -1
- package/dist/test/helpers/mock-membrane.js.map +1 -1
- package/dist/test/maintenance-timer.test.js +52 -1
- package/dist/test/maintenance-timer.test.js.map +1 -1
- package/dist/test/mcpl-gate-roundtrip.test.js +138 -6
- package/dist/test/mcpl-gate-roundtrip.test.js.map +1 -1
- package/dist/test/overbudget-drain-breaker.test.d.ts +2 -0
- package/dist/test/overbudget-drain-breaker.test.d.ts.map +1 -0
- package/dist/test/overbudget-drain-breaker.test.js +157 -0
- package/dist/test/overbudget-drain-breaker.test.js.map +1 -0
- package/dist/test/present-while-acting.test.d.ts +18 -0
- package/dist/test/present-while-acting.test.d.ts.map +1 -0
- package/dist/test/present-while-acting.test.js +267 -0
- package/dist/test/present-while-acting.test.js.map +1 -0
- package/dist/test/trunk-channel-routing.test.js +38 -0
- package/dist/test/trunk-channel-routing.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/agent.ts +244 -3
- package/src/framework.ts +763 -235
- package/src/gate/event-gate.ts +38 -4
- package/src/mcpl/channel-registry.ts +372 -76
- package/src/mcpl/push-handler.ts +2 -0
- package/src/mcpl/server-connection.ts +10 -0
- package/src/mcpl/types.ts +49 -12
- package/src/types/agent.ts +18 -0
- package/src/types/events.ts +1 -1
- package/src/types/framework.ts +10 -0
- package/src/types/trace.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animalabs/agent-framework",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Multi-agent framework with pluggable modules, persistent state, and concurrent tool execution",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"license": "MIT",
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@animalabs/chronicle": "^0.2.2",
|
|
47
|
-
"@animalabs/context-manager": "^0.5.
|
|
48
|
-
"@animalabs/membrane": "^0.5.
|
|
47
|
+
"@animalabs/context-manager": "^0.5.11",
|
|
48
|
+
"@animalabs/membrane": "^0.5.73",
|
|
49
49
|
"chokidar": "^4.0.3",
|
|
50
50
|
"discord.js": "^14.25.1",
|
|
51
51
|
"ws": "^8.18.0"
|
package/src/agent.ts
CHANGED
|
@@ -6,7 +6,14 @@ export interface StartStreamResult {
|
|
|
6
6
|
stream: YieldingStream;
|
|
7
7
|
request: NormalizedRequest;
|
|
8
8
|
}
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ContextManager,
|
|
11
|
+
TokenBudget,
|
|
12
|
+
ContextInjection,
|
|
13
|
+
CompileResult,
|
|
14
|
+
HotContextSettingsStatus,
|
|
15
|
+
HotContextSettingsUpdate,
|
|
16
|
+
} from '@animalabs/context-manager';
|
|
10
17
|
import type {
|
|
11
18
|
AgentConfig,
|
|
12
19
|
AgentState,
|
|
@@ -19,8 +26,14 @@ import type {
|
|
|
19
26
|
AgentInfo,
|
|
20
27
|
InferenceResult,
|
|
21
28
|
InferenceOptions,
|
|
29
|
+
AgentRuntimeSettingsPatch,
|
|
30
|
+
AgentRuntimeSettingsSnapshot,
|
|
31
|
+
AgentRuntimeSettingsOverrides,
|
|
22
32
|
} from './types/index.js';
|
|
23
33
|
|
|
34
|
+
const DEFAULT_CONTEXT_BUDGET_TOKENS = 100_000;
|
|
35
|
+
const DEFAULT_TRANSITION_PACE_TOKENS = 16_000;
|
|
36
|
+
|
|
24
37
|
/**
|
|
25
38
|
* An agent wraps a context manager and manages inference state.
|
|
26
39
|
*/
|
|
@@ -60,6 +73,11 @@ export class Agent {
|
|
|
60
73
|
* ContextManager's built-in default applies. reserveForResponse uses
|
|
61
74
|
* this agent's maxTokens. */
|
|
62
75
|
contextBudgetTokens?: number;
|
|
76
|
+
private readonly configuredContextBudgetTokens?: number;
|
|
77
|
+
private readonly configuredTailTokens?: number;
|
|
78
|
+
private readonly configuredTransitionPaceTokens?: number;
|
|
79
|
+
private contextBudgetTargetTokens?: number;
|
|
80
|
+
private runtimeSettingsOverrides: AgentRuntimeSettingsOverrides = {};
|
|
63
81
|
private contextManager: ContextManager;
|
|
64
82
|
private membrane: Membrane;
|
|
65
83
|
|
|
@@ -81,8 +99,12 @@ export class Agent {
|
|
|
81
99
|
this.providerParams = config.providerParams;
|
|
82
100
|
this.maxStreamTokens = config.maxStreamTokens ?? 150_000;
|
|
83
101
|
this.contextBudgetTokens = config.contextBudgetTokens;
|
|
102
|
+
this.configuredContextBudgetTokens = config.contextBudgetTokens;
|
|
84
103
|
this.contextManager = contextManager;
|
|
85
104
|
this.membrane = membrane;
|
|
105
|
+
const hot = this.getHotContextSettings();
|
|
106
|
+
this.configuredTailTokens = hot?.tailTokens;
|
|
107
|
+
this.configuredTransitionPaceTokens = hot?.transitionPaceTokens;
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
/**
|
|
@@ -148,8 +170,203 @@ export class Agent {
|
|
|
148
170
|
return { maxTokens: this.contextBudgetTokens, reserveForResponse: this.maxTokens };
|
|
149
171
|
}
|
|
150
172
|
|
|
173
|
+
/** Structural compatibility keeps lightweight test/host ContextManager
|
|
174
|
+
* doubles working while making the new capability optional at runtime. */
|
|
175
|
+
private getHotContextSettings(): HotContextSettingsStatus | null {
|
|
176
|
+
const manager = this.contextManager as ContextManager & {
|
|
177
|
+
getHotContextSettings?: () => HotContextSettingsStatus | null;
|
|
178
|
+
};
|
|
179
|
+
return typeof manager.getHotContextSettings === 'function'
|
|
180
|
+
? manager.getHotContextSettings()
|
|
181
|
+
: null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private updateHotContextSettings(update: HotContextSettingsUpdate): HotContextSettingsStatus {
|
|
185
|
+
const manager = this.contextManager as ContextManager & {
|
|
186
|
+
updateHotContextSettings?: (value: HotContextSettingsUpdate) => HotContextSettingsStatus;
|
|
187
|
+
};
|
|
188
|
+
if (typeof manager.updateHotContextSettings !== 'function') {
|
|
189
|
+
throw new Error('The active context strategy does not support live context settings');
|
|
190
|
+
}
|
|
191
|
+
return manager.updateHotContextSettings(update);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
getRuntimeSettings(): AgentRuntimeSettingsSnapshot {
|
|
195
|
+
const hot = this.getHotContextSettings();
|
|
196
|
+
return {
|
|
197
|
+
contextBudgetTokens:
|
|
198
|
+
this.contextBudgetTargetTokens ??
|
|
199
|
+
this.contextBudgetTokens ??
|
|
200
|
+
DEFAULT_CONTEXT_BUDGET_TOKENS,
|
|
201
|
+
...(hot ? { tailTokens: hot.tailTokens } : {}),
|
|
202
|
+
...(hot?.transitionPaceTokens !== undefined
|
|
203
|
+
? { transitionPaceTokens: hot.transitionPaceTokens }
|
|
204
|
+
: {}),
|
|
205
|
+
transition: this.contextBudgetTargetTokens === undefined
|
|
206
|
+
? 'stable'
|
|
207
|
+
: hot?.blocked
|
|
208
|
+
? 'blocked'
|
|
209
|
+
: 'converging',
|
|
210
|
+
...(hot?.blocked === 'transition-pace-floor'
|
|
211
|
+
? { transitionReason: 'transition_pace_too_small' as const }
|
|
212
|
+
: hot?.blocked === 'prepared-window-floor'
|
|
213
|
+
? { transitionReason: 'protected_context_exceeds_target' as const }
|
|
214
|
+
: {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getRuntimeSettingsOverrides(): AgentRuntimeSettingsOverrides {
|
|
219
|
+
return { ...this.runtimeSettingsOverrides };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
updateRuntimeSettings(patch: AgentRuntimeSettingsPatch): AgentRuntimeSettingsSnapshot {
|
|
223
|
+
this.validateRuntimeSettingsPatch(patch);
|
|
224
|
+
let nextContextBudget = this.contextBudgetTokens;
|
|
225
|
+
let nextContextTarget = this.contextBudgetTargetTokens;
|
|
226
|
+
let appliedDefaultPace = false;
|
|
227
|
+
const hotPatch: {
|
|
228
|
+
tailTokens?: number;
|
|
229
|
+
transitionPaceTokens?: number | null;
|
|
230
|
+
preparedWindowTokens?: number | null;
|
|
231
|
+
} = {};
|
|
232
|
+
if (patch.tailTokens !== undefined) hotPatch.tailTokens = patch.tailTokens;
|
|
233
|
+
if (patch.transitionPaceTokens !== undefined) {
|
|
234
|
+
hotPatch.transitionPaceTokens = patch.transitionPaceTokens;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (patch.contextBudgetTokens !== undefined) {
|
|
238
|
+
const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
|
|
239
|
+
if (patch.contextBudgetTokens >= live) {
|
|
240
|
+
nextContextBudget = patch.contextBudgetTokens;
|
|
241
|
+
nextContextTarget = undefined;
|
|
242
|
+
if (this.getHotContextSettings()) hotPatch.preparedWindowTokens = null;
|
|
243
|
+
} else {
|
|
244
|
+
const hot = this.getHotContextSettings();
|
|
245
|
+
if (!hot) {
|
|
246
|
+
throw new Error('The active context strategy cannot prepare a smaller window live');
|
|
247
|
+
}
|
|
248
|
+
if (patch.transitionPaceTokens === undefined && hot.transitionPaceTokens === undefined) {
|
|
249
|
+
hotPatch.transitionPaceTokens = DEFAULT_TRANSITION_PACE_TOKENS;
|
|
250
|
+
appliedDefaultPace = true;
|
|
251
|
+
}
|
|
252
|
+
hotPatch.preparedWindowTokens = patch.contextBudgetTokens - this.maxTokens;
|
|
253
|
+
nextContextTarget = patch.contextBudgetTokens;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (Object.keys(hotPatch).length > 0) {
|
|
258
|
+
this.updateHotContextSettings(hotPatch);
|
|
259
|
+
}
|
|
260
|
+
this.contextBudgetTokens = nextContextBudget;
|
|
261
|
+
this.contextBudgetTargetTokens = nextContextTarget;
|
|
262
|
+
if (appliedDefaultPace) {
|
|
263
|
+
this.runtimeSettingsOverrides.transitionPaceTokens = DEFAULT_TRANSITION_PACE_TOKENS;
|
|
264
|
+
}
|
|
265
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
266
|
+
if (value !== undefined) (this.runtimeSettingsOverrides as Record<string, number>)[key] = value;
|
|
267
|
+
}
|
|
268
|
+
return this.getRuntimeSettings();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
resetRuntimeSettings(keys?: Array<keyof AgentRuntimeSettingsPatch>): AgentRuntimeSettingsSnapshot {
|
|
272
|
+
const reset = new Set(keys ?? ['contextBudgetTokens', 'tailTokens', 'transitionPaceTokens']);
|
|
273
|
+
let nextContextBudget = this.contextBudgetTokens;
|
|
274
|
+
let nextContextTarget = this.contextBudgetTargetTokens;
|
|
275
|
+
const hotPatch: {
|
|
276
|
+
tailTokens?: number;
|
|
277
|
+
transitionPaceTokens?: number | null;
|
|
278
|
+
preparedWindowTokens?: number | null;
|
|
279
|
+
} = {};
|
|
280
|
+
|
|
281
|
+
if (reset.has('contextBudgetTokens')) {
|
|
282
|
+
const configured = this.configuredContextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
|
|
283
|
+
const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
|
|
284
|
+
if (configured < live) {
|
|
285
|
+
const hot = this.getHotContextSettings();
|
|
286
|
+
if (!hot) throw new Error('The active context strategy cannot prepare a smaller window live');
|
|
287
|
+
hotPatch.preparedWindowTokens = configured - this.maxTokens;
|
|
288
|
+
if (hot.transitionPaceTokens === undefined) {
|
|
289
|
+
hotPatch.transitionPaceTokens = DEFAULT_TRANSITION_PACE_TOKENS;
|
|
290
|
+
}
|
|
291
|
+
nextContextTarget = configured;
|
|
292
|
+
} else {
|
|
293
|
+
nextContextBudget = this.configuredContextBudgetTokens;
|
|
294
|
+
nextContextTarget = undefined;
|
|
295
|
+
if (this.getHotContextSettings()) hotPatch.preparedWindowTokens = null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (reset.has('tailTokens')) {
|
|
299
|
+
if (this.configuredTailTokens !== undefined) hotPatch.tailTokens = this.configuredTailTokens;
|
|
300
|
+
}
|
|
301
|
+
if (reset.has('transitionPaceTokens') && this.getHotContextSettings()) {
|
|
302
|
+
hotPatch.transitionPaceTokens =
|
|
303
|
+
nextContextTarget !== undefined && this.configuredTransitionPaceTokens === undefined
|
|
304
|
+
? DEFAULT_TRANSITION_PACE_TOKENS
|
|
305
|
+
: this.configuredTransitionPaceTokens ?? null;
|
|
306
|
+
}
|
|
307
|
+
if (Object.keys(hotPatch).length > 0) this.updateHotContextSettings(hotPatch);
|
|
308
|
+
this.contextBudgetTokens = nextContextBudget;
|
|
309
|
+
this.contextBudgetTargetTokens = nextContextTarget;
|
|
310
|
+
if (reset.has('contextBudgetTokens')) delete this.runtimeSettingsOverrides.contextBudgetTokens;
|
|
311
|
+
if (reset.has('tailTokens')) delete this.runtimeSettingsOverrides.tailTokens;
|
|
312
|
+
if (reset.has('transitionPaceTokens')) {
|
|
313
|
+
delete this.runtimeSettingsOverrides.transitionPaceTokens;
|
|
314
|
+
}
|
|
315
|
+
return this.getRuntimeSettings();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
cancelRuntimeSettingsTransition(): AgentRuntimeSettingsSnapshot {
|
|
319
|
+
if (this.contextBudgetTargetTokens !== undefined) {
|
|
320
|
+
this.updateHotContextSettings({ preparedWindowTokens: null });
|
|
321
|
+
this.contextBudgetTargetTokens = undefined;
|
|
322
|
+
const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
|
|
323
|
+
if (live === (this.configuredContextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS)) {
|
|
324
|
+
delete this.runtimeSettingsOverrides.contextBudgetTokens;
|
|
325
|
+
} else {
|
|
326
|
+
this.runtimeSettingsOverrides.contextBudgetTokens = live;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return this.getRuntimeSettings();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
restoreRuntimeSettings(overrides: AgentRuntimeSettingsOverrides): AgentRuntimeSettingsSnapshot {
|
|
333
|
+
return this.updateRuntimeSettings(overrides);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private validateRuntimeSettingsPatch(patch: AgentRuntimeSettingsPatch): void {
|
|
337
|
+
if (Object.keys(patch).length === 0) throw new Error('At least one setting is required');
|
|
338
|
+
if (patch.contextBudgetTokens !== undefined) {
|
|
339
|
+
if (!Number.isSafeInteger(patch.contextBudgetTokens) || patch.contextBudgetTokens <= this.maxTokens) {
|
|
340
|
+
throw new Error(`contextBudgetTokens must be a safe integer greater than max response tokens (${this.maxTokens})`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (patch.tailTokens !== undefined &&
|
|
344
|
+
(!Number.isSafeInteger(patch.tailTokens) || patch.tailTokens < 0)) {
|
|
345
|
+
throw new Error('tailTokens must be a non-negative safe integer');
|
|
346
|
+
}
|
|
347
|
+
if (patch.transitionPaceTokens !== undefined &&
|
|
348
|
+
(!Number.isSafeInteger(patch.transitionPaceTokens) || patch.transitionPaceTokens <= 0)) {
|
|
349
|
+
throw new Error('transitionPaceTokens must be a positive safe integer');
|
|
350
|
+
}
|
|
351
|
+
if ((patch.tailTokens !== undefined || patch.transitionPaceTokens !== undefined) &&
|
|
352
|
+
!this.getHotContextSettings()) {
|
|
353
|
+
throw new Error('The active context strategy does not support live tail/transition settings');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private settleRuntimeSettingsTransition(): void {
|
|
358
|
+
if (this.contextBudgetTargetTokens === undefined) return;
|
|
359
|
+
const hot = this.getHotContextSettings();
|
|
360
|
+
if (!hot?.prepared) return;
|
|
361
|
+
this.contextBudgetTokens = this.contextBudgetTargetTokens;
|
|
362
|
+
this.contextBudgetTargetTokens = undefined;
|
|
363
|
+
this.updateHotContextSettings({ preparedWindowTokens: null });
|
|
364
|
+
}
|
|
365
|
+
|
|
151
366
|
async compileContext(budget?: TokenBudget): Promise<CompileResult> {
|
|
152
|
-
|
|
367
|
+
const result = await this.contextManager.compile(this.resolveBudget(budget));
|
|
368
|
+
if (!budget) this.settleRuntimeSettingsTransition();
|
|
369
|
+
return result;
|
|
153
370
|
}
|
|
154
371
|
|
|
155
372
|
/**
|
|
@@ -161,7 +378,9 @@ export class Agent {
|
|
|
161
378
|
budget?: TokenBudget,
|
|
162
379
|
injections?: ContextInjection[]
|
|
163
380
|
): Promise<CompileResult> {
|
|
164
|
-
|
|
381
|
+
const result = await this.contextManager.compile(this.resolveBudget(budget), injections);
|
|
382
|
+
if (!budget) this.settleRuntimeSettingsTransition();
|
|
383
|
+
return result;
|
|
165
384
|
}
|
|
166
385
|
|
|
167
386
|
// ==========================================================================
|
|
@@ -340,6 +559,28 @@ export class Agent {
|
|
|
340
559
|
|
|
341
560
|
let { messages, systemInjections } = await this.compileWithInjections(budget, injections);
|
|
342
561
|
|
|
562
|
+
// Sanitize: strip empty/whitespace text blocks and drop messages left with
|
|
563
|
+
// no content. The Anthropic API rejects empty text blocks with 400
|
|
564
|
+
// "messages: text content blocks must be non-empty"; when such a block
|
|
565
|
+
// reaches a live activation request the agent cannot complete ANY turn
|
|
566
|
+
// ([inference-failed] on every attempt) until the offending message ages
|
|
567
|
+
// out of the window. Empty text blocks occur naturally: tool-only turns,
|
|
568
|
+
// delivery-failure placeholders, silent/skip turns. Non-text blocks pass
|
|
569
|
+
// through unchanged, so tool pairing is unaffected. (Field-observed
|
|
570
|
+
// 2026-07-10 on a resident agent: one empty block muted the agent's live
|
|
571
|
+
// path entirely; twin of context-manager's stripEmptyTextBlocks on the
|
|
572
|
+
// compression path.)
|
|
573
|
+
messages = messages
|
|
574
|
+
.map((m) => ({
|
|
575
|
+
...m,
|
|
576
|
+
content: m.content.filter(
|
|
577
|
+
// typeof guard is deliberate runtime defense: history loaded from
|
|
578
|
+
// disk can carry a non-string `text` despite what the types claim.
|
|
579
|
+
(b: ContentBlock) => !(b.type === "text" && (typeof b.text !== "string" || b.text.trim() === "")),
|
|
580
|
+
),
|
|
581
|
+
}))
|
|
582
|
+
.filter((m) => m.content.length > 0);
|
|
583
|
+
|
|
343
584
|
// Safety: ensure messages don't end with an assistant message.
|
|
344
585
|
// Some models reject trailing assistant messages ("prefill not supported"),
|
|
345
586
|
// and after context compression a stale assistant turn can end up last.
|