@animalabs/agent-framework 0.33.0 → 0.34.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/README.md +136 -8
- package/dist/src/agent.d.ts +25 -4
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +68 -13
- package/dist/src/agent.js.map +1 -1
- package/dist/src/append-all.d.ts +5 -0
- package/dist/src/append-all.d.ts.map +1 -0
- package/dist/src/append-all.js +8 -0
- package/dist/src/append-all.js.map +1 -0
- package/dist/src/context-options.d.ts +9 -6
- package/dist/src/context-options.d.ts.map +1 -1
- package/dist/src/context-options.js +13 -8
- package/dist/src/context-options.js.map +1 -1
- package/dist/src/cut-off-round.js +4 -4
- package/dist/src/dendrite/registry.d.ts.map +1 -1
- package/dist/src/dendrite/registry.js +4 -2
- package/dist/src/dendrite/registry.js.map +1 -1
- package/dist/src/failure-text.d.ts +36 -0
- package/dist/src/failure-text.d.ts.map +1 -0
- package/dist/src/failure-text.js +89 -0
- package/dist/src/failure-text.js.map +1 -0
- package/dist/src/framework.d.ts +94 -30
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +987 -284
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +40 -6
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +66 -25
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/reply-judge.d.ts +4 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -1
- package/dist/src/gate/reply-judge.js +12 -3
- package/dist/src/gate/reply-judge.js.map +1 -1
- package/dist/src/gate/suppression-review.js +2 -2
- package/dist/src/gate/suppression-review.js.map +1 -1
- package/dist/src/gate/types.d.ts +7 -0
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +1 -1
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/inference-bounds.d.ts +2 -1
- package/dist/src/inference-bounds.d.ts.map +1 -1
- package/dist/src/inference-bounds.js +9 -6
- package/dist/src/inference-bounds.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +57 -13
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +136 -24
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +109 -15
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +593 -86
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +15 -9
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +37 -18
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +31 -16
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +10 -6
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +7 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +2 -1
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +25 -4
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/modules/discord/index.js +1 -1
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/health/index.js +2 -2
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/modules/history/semantic.js +3 -3
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +4 -3
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +2 -1
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +3 -2
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +10 -8
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +10 -8
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/presentation.d.ts +2 -1
- package/dist/src/presentation.d.ts.map +1 -1
- package/dist/src/presentation.js +4 -2
- package/dist/src/presentation.js.map +1 -1
- package/dist/src/reasoning-loss.d.ts +5 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +8 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +5 -4
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +2 -1
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/sent-rows.js +1 -1
- package/dist/src/supplied-text.d.ts +1 -1
- package/dist/src/supplied-text.js +3 -3
- package/dist/src/supplied-text.js.map +1 -1
- package/dist/src/thinking-drops.d.ts +2 -0
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +38 -9
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +13 -4
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +33 -16
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +2 -1
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +39 -5
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -8
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/inference-log.d.ts +1 -0
- package/dist/src/types/inference-log.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +64 -4
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +71 -14
- package/src/append-all.ts +6 -0
- package/src/context-options.ts +18 -8
- package/src/cut-off-round.ts +4 -4
- package/src/dendrite/registry.ts +4 -2
- package/src/failure-text.ts +88 -0
- package/src/framework.ts +1018 -293
- package/src/gate/event-gate.ts +90 -26
- package/src/gate/reply-judge.ts +12 -3
- package/src/gate/suppression-review.ts +2 -2
- package/src/gate/types.ts +7 -0
- package/src/heartbeat/heartbeat.ts +1 -1
- package/src/index.ts +1 -0
- package/src/inference-bounds.ts +11 -6
- package/src/mcpl/channel-registry.ts +160 -31
- package/src/mcpl/modern-connection.ts +629 -91
- package/src/mcpl/protocol-family.ts +15 -9
- package/src/mcpl/server-connection.ts +28 -15
- package/src/mcpl/tool-presence.ts +10 -5
- package/src/mcpl/toolset-host.ts +7 -6
- package/src/mcpl/toolsets-module.ts +2 -1
- package/src/mcpl/types.ts +25 -4
- package/src/modules/discord/index.ts +1 -1
- package/src/modules/health/index.ts +2 -2
- package/src/modules/history/semantic.ts +3 -3
- package/src/modules/workspace/index.ts +4 -3
- package/src/modules/workspace/observe.ts +2 -1
- package/src/modules/workspace/reconcile.ts +3 -2
- package/src/notice-tags.ts +11 -8
- package/src/presentation.ts +4 -2
- package/src/reasoning-loss.ts +7 -0
- package/src/recovery/offline-branch.ts +5 -4
- package/src/recovery/recover-cli.ts +2 -1
- package/src/sent-rows.ts +1 -1
- package/src/supplied-text.ts +3 -3
- package/src/thinking-drops.ts +2 -0
- package/src/tool-presentation.ts +34 -9
- package/src/tune-out/coordinator.ts +32 -15
- package/src/tune-out/tools.ts +2 -1
- package/src/types/agent.ts +36 -5
- package/src/types/framework.ts +44 -8
- package/src/types/inference-log.ts +1 -0
- package/src/types/trace.ts +66 -4
package/src/agent.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
truncateForHistory,
|
|
36
36
|
DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS,
|
|
37
37
|
} from './tool-result-history.js';
|
|
38
|
+
import { appendAll } from './append-all.js';
|
|
38
39
|
|
|
39
40
|
export interface ActivationRequestOptions {
|
|
40
41
|
/** The silent heartbeat tick this request answers, if any. */
|
|
@@ -112,6 +113,11 @@ export interface StartStreamResult {
|
|
|
112
113
|
* far in sent order, with this request's head (context-manager's `noteRequestSent`). Present only where the
|
|
113
114
|
* model binds its thinking and the context manager keeps a record it can be told of. */
|
|
114
115
|
noteRequestSent?: (rows: readonly SentRow[]) => boolean;
|
|
116
|
+
/** Tells the context manager which stored bodies' thinking the provider dropped from a call of this stream, by the
|
|
117
|
+
* ids `placeDrops` gives for a `reply` drop, with the compile the stream was built from (context-manager's
|
|
118
|
+
* `noteThinkingDropped`), so, once the context manager takes the note, its record leaves them without their
|
|
119
|
+
* thinking from the next compile on. Present only where `noteRequestSent` is. */
|
|
120
|
+
noteThinkingDropped?: (messageIds: readonly MessageId[]) => boolean;
|
|
115
121
|
/** The stored bodies the request's compile carried, by each body's first message id (its provenance's raw bodies).
|
|
116
122
|
* Absent when the compile reports no provenance. */
|
|
117
123
|
carriedBodies?: ReadonlySet<MessageId>;
|
|
@@ -435,6 +441,7 @@ export class Agent {
|
|
|
435
441
|
* the next change.
|
|
436
442
|
*/
|
|
437
443
|
private liveBudgetEpoch = 0;
|
|
444
|
+
private contextSettingsChangeCount = 0;
|
|
438
445
|
/**
|
|
439
446
|
* Counts changes of the descent (each arm of one, the same target included,
|
|
440
447
|
* a reset, a cancel, a settle). A settle proves its target only to a
|
|
@@ -696,7 +703,19 @@ export class Agent {
|
|
|
696
703
|
if (typeof manager.updateHotContextSettings !== 'function') {
|
|
697
704
|
throw new Error('The active context strategy does not support live context settings');
|
|
698
705
|
}
|
|
699
|
-
|
|
706
|
+
const settings = (s: HotContextSettingsStatus | null) =>
|
|
707
|
+
JSON.stringify([s?.tailTokens, s?.transitionPaceTokens, s?.preparedWindowTokens]);
|
|
708
|
+
const before = settings(this.getHotContextSettings());
|
|
709
|
+
const status = manager.updateHotContextSettings(update);
|
|
710
|
+
if (settings(status) !== before) this.contextSettingsChangeCount++;
|
|
711
|
+
return status;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** How many times the live budget, its descent target or the hot context
|
|
715
|
+
* settings have changed.
|
|
716
|
+
* @internal For the framework's budget holds; not a host API. */
|
|
717
|
+
contextSettingsChanges(): number {
|
|
718
|
+
return this.contextSettingsChangeCount;
|
|
700
719
|
}
|
|
701
720
|
|
|
702
721
|
getRuntimeSettings(): AgentRuntimeSettingsSnapshot {
|
|
@@ -763,6 +782,7 @@ export class Agent {
|
|
|
763
782
|
private setContextBudget(budget: number | undefined, target: number | undefined, armed = false): void {
|
|
764
783
|
if (budget !== this.contextBudgetTokens) this.liveBudgetEpoch++;
|
|
765
784
|
if (armed || target !== this.contextBudgetTargetTokens) this.descentEpoch++;
|
|
785
|
+
if (budget !== this.contextBudgetTokens || target !== this.contextBudgetTargetTokens) this.contextSettingsChangeCount++;
|
|
766
786
|
this.contextBudgetTokens = budget;
|
|
767
787
|
this.contextBudgetTargetTokens = target;
|
|
768
788
|
}
|
|
@@ -802,7 +822,7 @@ export class Agent {
|
|
|
802
822
|
return await this.compileAtLiveBudgetNoting(compile, started);
|
|
803
823
|
} finally {
|
|
804
824
|
this.compilesRunning.delete(started);
|
|
805
|
-
const earliest =
|
|
825
|
+
const earliest = [...this.compilesRunning].reduce((min, start) => Math.min(min, start), Infinity);
|
|
806
826
|
for (const [budget, start] of this.latestEndedStart) {
|
|
807
827
|
if (start < earliest) this.latestEndedStart.delete(budget);
|
|
808
828
|
}
|
|
@@ -1582,8 +1602,8 @@ export class Agent {
|
|
|
1582
1602
|
/**
|
|
1583
1603
|
* A host's hook, called with each committing compile's `thinkingStrip` (never a dry run's) and the key of the
|
|
1584
1604
|
* branch it read (`branchKey`), as the compile returns, whether or not its request is then sent, since the
|
|
1585
|
-
* context manager's binding record takes the strip either way. One that throws is logged.
|
|
1586
|
-
*
|
|
1605
|
+
* context manager's binding record takes the strip either way. One that throws is logged. It is observational only;
|
|
1606
|
+
* the framework does not write agent-facing reasoning-loss notices.
|
|
1587
1607
|
*/
|
|
1588
1608
|
onThinkingStrip?: (strip: ThinkingStrip, branch: string) => void;
|
|
1589
1609
|
|
|
@@ -1611,12 +1631,9 @@ export class Agent {
|
|
|
1611
1631
|
unsentInjection(namespace: string): { contentHash: string; whole: boolean } | undefined {
|
|
1612
1632
|
let newest: { contentHash: string; parts: number; held: Set<number> } | undefined;
|
|
1613
1633
|
try {
|
|
1614
|
-
const
|
|
1615
|
-
const since = this.unsentSince.get(branch);
|
|
1634
|
+
const since = this.unsentSince.get(branchKey(this.contextManager.currentBranchRef()));
|
|
1616
1635
|
if (since === undefined) {
|
|
1617
|
-
|
|
1618
|
-
const top = count > 0 ? this.contextManager.getMessageWindow(count - 1, 1, { resolveBlobs: false }).messages[0]?.sequence ?? 0 : 0;
|
|
1619
|
-
this.unsentSince.set(branch, top);
|
|
1636
|
+
this.markUnsentPoint();
|
|
1620
1637
|
return undefined;
|
|
1621
1638
|
}
|
|
1622
1639
|
scan: for (let end = this.contextManager.getMessageCount(); end > 0;) {
|
|
@@ -1652,6 +1669,42 @@ export class Agent {
|
|
|
1652
1669
|
return newest && { contentHash: newest.contentHash, whole: newest.held.size === newest.parts };
|
|
1653
1670
|
}
|
|
1654
1671
|
|
|
1672
|
+
/** Set the unsent point where it isn't set, as a start's plan does: a row
|
|
1673
|
+
* stored after it counts as in no request until a compile built to send.
|
|
1674
|
+
* @internal For the framework's budget holds; not a host API. */
|
|
1675
|
+
markUnsentPoint(): void {
|
|
1676
|
+
const branch = branchKey(this.contextManager.currentBranchRef());
|
|
1677
|
+
if (this.unsentSince.has(branch)) return;
|
|
1678
|
+
const count = this.contextManager.getMessageCount();
|
|
1679
|
+
const top = count > 0 ? this.contextManager.getMessageWindow(count - 1, 1, { resolveBlobs: false }).messages[0]?.sequence ?? 0 : 0;
|
|
1680
|
+
this.unsentSince.set(branch, top);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/** The ids of this agent's stored injections, of every namespace, stored
|
|
1684
|
+
* on its branch since its last compile built to send and in its window
|
|
1685
|
+
* where it's presented, in the record's order, a sharded body's first
|
|
1686
|
+
* shard first: the rows unsentInjection counts, which no request has
|
|
1687
|
+
* carried. Reads only, so an unset point gives none. */
|
|
1688
|
+
unsentInjectionRows(): MessageId[] {
|
|
1689
|
+
const ids: MessageId[] = [];
|
|
1690
|
+
const since = this.unsentSince.get(branchKey(this.contextManager.currentBranchRef()));
|
|
1691
|
+
if (since === undefined) return ids;
|
|
1692
|
+
const presented = this.contextManager.windowMode?.() === 'presented';
|
|
1693
|
+
scan: for (let end = this.contextManager.getMessageCount(); end > 0;) {
|
|
1694
|
+
const span = Math.min(end, 64);
|
|
1695
|
+
const { messages } = this.contextManager.getMessageWindow(end - span, span, { resolveBlobs: false });
|
|
1696
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1697
|
+
const row = messages[i]!;
|
|
1698
|
+
if (row.sequence <= since) break scan;
|
|
1699
|
+
if (storedInjectionMarkOf(row.metadata)?.agent !== this.name) continue;
|
|
1700
|
+
if (presented && this.contextManager.windowStatusOf(row.id) !== 'in') continue;
|
|
1701
|
+
ids.push(row.id);
|
|
1702
|
+
}
|
|
1703
|
+
end -= span;
|
|
1704
|
+
}
|
|
1705
|
+
return ids.reverse();
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1655
1708
|
/** Record which of this agent's stored injections a compile built to send
|
|
1656
1709
|
* rendered. Another agent's stored rows never reach this agent's compile,
|
|
1657
1710
|
* since every compile of a shared store leaves them out by its view
|
|
@@ -1925,7 +1978,7 @@ export class Agent {
|
|
|
1925
1978
|
// inferring, sends the same ones.
|
|
1926
1979
|
unguardedResults ??= this._state.status === 'ready' && !guardedResults
|
|
1927
1980
|
? this.buildToolResultMessages(this._state.toolResults) : [];
|
|
1928
|
-
messages
|
|
1981
|
+
appendAll(messages, unguardedResults);
|
|
1929
1982
|
return {
|
|
1930
1983
|
messages: this.toolResultGuard.prepareRequest(messages, true),
|
|
1931
1984
|
system: this.buildSystemPrompt(systemInjections),
|
|
@@ -2268,9 +2321,10 @@ export class Agent {
|
|
|
2268
2321
|
return { ...own, blockBinding: { prefixMismatchBehavior: 'drop_block' } };
|
|
2269
2322
|
}
|
|
2270
2323
|
|
|
2271
|
-
/** The stream's way to tell the context manager what its later rounds sent, with the head its compile was given
|
|
2272
|
-
* where the agent's model binds its thinking
|
|
2273
|
-
|
|
2324
|
+
/** The stream's way to tell the context manager what its later rounds sent, with the head its compile was given,
|
|
2325
|
+
* and which stored bodies' thinking the provider dropped: where the agent's model binds its thinking
|
|
2326
|
+
* (context-manager's `noteRequestSent` and `noteThinkingDropped`). */
|
|
2327
|
+
private requestNotes(binding: BindingCompileOptions, compiled: CompiledForRequest): Pick<StartStreamResult, 'noteRequestSent' | 'noteThinkingDropped'> {
|
|
2274
2328
|
const identity = binding.identity;
|
|
2275
2329
|
// Each note names the compile the stream was built from, so a note can't vouch for this stream under a record a
|
|
2276
2330
|
// later committing compile with binds wrote. A compile that reports no provenance gives the stream nothing to name, and the
|
|
@@ -2278,7 +2332,10 @@ export class Agent {
|
|
|
2278
2332
|
const compileId = compiled.provenance?.compileId;
|
|
2279
2333
|
if (!identity || compileId === undefined) return {};
|
|
2280
2334
|
const cm = this.contextManager;
|
|
2281
|
-
return {
|
|
2335
|
+
return {
|
|
2336
|
+
noteRequestSent: (rows) => cm.noteRequestSent(rows, identity, compileId),
|
|
2337
|
+
noteThinkingDropped: (messageIds) => cm.noteThinkingDropped(messageIds, compileId),
|
|
2338
|
+
};
|
|
2282
2339
|
}
|
|
2283
2340
|
|
|
2284
2341
|
/**
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** `into.push(...items)` passes each item as a call argument, and V8 throws a
|
|
2
|
+
* RangeError past about 125,000 arguments, so this pushes them one at a
|
|
3
|
+
* time. */
|
|
4
|
+
export function appendAll<T>(into: T[], items: readonly T[]): void {
|
|
5
|
+
for (let i = 0, n = items.length; i < n; i++) into.push(items[i]!);
|
|
6
|
+
}
|
package/src/context-options.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* (heldFromResidents), what a suppress rule hid (suppressedRow), and the
|
|
20
20
|
* other residents' stored context injections (isAnotherAgentsInjection):
|
|
21
21
|
* their context, which they share the slot with it but never its
|
|
22
|
-
* conversation.
|
|
22
|
+
* conversation. Both views also omit legacy reasoning-loss notices.
|
|
23
23
|
* - The subconscious keeps its rows in a slot of its own,
|
|
24
24
|
* `subconscious/<serves>`, and reads the residents' slot beside it
|
|
25
25
|
* (`auxiliaryMessageViews: [{}]`), held traffic included, which is what it
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* window by its observe edge's rule, and the merged slot is still the
|
|
28
28
|
* record its window is presented from. Its view leaves out only the
|
|
29
29
|
* residents' stored context injections and what a suppress rule hid,
|
|
30
|
-
*
|
|
30
|
+
* in this shared view, along with legacy notices. Its strategy is a windowed
|
|
31
31
|
* passthrough, anchored at the oldest active tune-out's start, or at an
|
|
32
32
|
* earlier start kept in force: a cancelled tune-out's, kept while another
|
|
33
33
|
* stays active and across a restart, until the next enter sets the oldest
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
* a strategy keeps state for the manager it is opened with.
|
|
38
38
|
*/
|
|
39
39
|
import { WindowedPassthroughStrategy, type ContextManagerConfig, type StoredMessage } from '@animalabs/context-manager';
|
|
40
|
+
import { isReasoningLoss } from './reasoning-loss.js';
|
|
40
41
|
import { heldFromResidents, suppressedRow } from './dendrite/audience.js';
|
|
41
42
|
import { persistentReaderName } from './tune-out/tools.js';
|
|
42
43
|
import { isAnotherAgentsInjection } from './stored-injections.js';
|
|
@@ -44,17 +45,26 @@ import { isAnotherAgentsInjection } from './stored-injections.js';
|
|
|
44
45
|
/** ContextManager.open's options over a store the caller gives: everything but the store. */
|
|
45
46
|
export type ContextManagerOpenOptions = Omit<Extract<ContextManagerConfig, { store: unknown }>, 'store'>;
|
|
46
47
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
48
|
+
/** A resident's view: no held traffic, suppressed rows, foreign injections or legacy notices.
|
|
49
|
+
* Supports both a reusable named predicate and a direct metadata-only probe. */
|
|
50
|
+
export function residentViewFilter(message: Pick<StoredMessage, 'metadata'>, name: string): boolean;
|
|
51
|
+
export function residentViewFilter(name: string): (message: Pick<StoredMessage, 'metadata'>) => boolean;
|
|
52
|
+
export function residentViewFilter(messageOrName: Pick<StoredMessage, 'metadata'> | string, name?: string): boolean | ((message: Pick<StoredMessage, 'metadata'>) => boolean) {
|
|
53
|
+
if (typeof messageOrName !== 'string') return residentViewFilter(name!)(messageOrName);
|
|
49
54
|
return (message) => !heldFromResidents(message.metadata as Record<string, unknown> | undefined)
|
|
50
55
|
&& !suppressedRow(message.metadata as Record<string, unknown> | undefined)
|
|
51
|
-
&& !isAnotherAgentsInjection(message.metadata,
|
|
56
|
+
&& !isAnotherAgentsInjection(message.metadata, messageOrName)
|
|
57
|
+
&& !isReasoningLoss(message.metadata);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
/**
|
|
55
|
-
export function subconsciousViewFilter(name: string):
|
|
60
|
+
/** The subconscious includes held traffic, but not suppressed rows, foreign injections or legacy notices. */
|
|
61
|
+
export function subconsciousViewFilter(message: Pick<StoredMessage, 'metadata'>, name: string): boolean;
|
|
62
|
+
export function subconsciousViewFilter(name: string): (message: Pick<StoredMessage, 'metadata'>) => boolean;
|
|
63
|
+
export function subconsciousViewFilter(messageOrName: Pick<StoredMessage, 'metadata'> | string, name?: string): boolean | ((message: Pick<StoredMessage, 'metadata'>) => boolean) {
|
|
64
|
+
if (typeof messageOrName !== 'string') return subconsciousViewFilter(name!)(messageOrName);
|
|
56
65
|
return (message) => !suppressedRow(message.metadata as Record<string, unknown> | undefined)
|
|
57
|
-
&& !isAnotherAgentsInjection(message.metadata,
|
|
66
|
+
&& !isAnotherAgentsInjection(message.metadata, messageOrName)
|
|
67
|
+
&& !isReasoningLoss(message.metadata);
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
/** The options a resident named `agentName` is opened with: the residents' slot, through its view. The caller gives its strategy. */
|
package/src/cut-off-round.ts
CHANGED
|
@@ -45,10 +45,10 @@ export function cutOffRound(response: NormalizedResponse): CutOffRound | undefin
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Membrane 0.8.0's grammar (unchanged from 0.7.2) for the
|
|
49
|
-
* formatter's XML tool mode. Membrane exports two of its
|
|
50
|
-
* hasUnclosedToolBlock and endsWithPartialToolBlock, but not the
|
|
51
|
-
* itself (dist/utils/tool-parser.js, INVOKE_REGEX): a block opens
|
|
48
|
+
* Membrane 0.8.0's and 0.9.0's grammar (unchanged from 0.7.2) for the
|
|
49
|
+
* anthropic-xml formatter's XML tool mode. Membrane exports two of its
|
|
50
|
+
* predicates, hasUnclosedToolBlock and endsWithPartialToolBlock, but not the
|
|
51
|
+
* grammar itself (dist/utils/tool-parser.js, INVOKE_REGEX): a block opens
|
|
52
52
|
* with `<function_calls>` and each call with `<invoke name="…">`, either tag
|
|
53
53
|
* optionally `antml:` prefixed, and a closer ends the innermost block still
|
|
54
54
|
* open. Its parser takes spans out of an unclosed block's text, which leaves
|
package/src/dendrite/registry.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
ObserveEdge,
|
|
24
24
|
RegistrySnapshot,
|
|
25
25
|
} from './types.js';
|
|
26
|
+
import { appendAll } from '../append-all.js';
|
|
26
27
|
|
|
27
28
|
/** Thrown for a spec the registry refuses to load. */
|
|
28
29
|
export class AgentSpecError extends Error {
|
|
@@ -619,8 +620,9 @@ export class AgentRegistry {
|
|
|
619
620
|
if (!this.live.has(child.name)) continue; // ended by an earlier cascade
|
|
620
621
|
if (child.onParentEnd === 'end') {
|
|
621
622
|
const inner = this.endRecursive(child, 'parent-ended', record.name);
|
|
622
|
-
|
|
623
|
-
|
|
623
|
+
appendAll(cascaded, inner.cascaded);
|
|
624
|
+
cascaded.push(inner.ended);
|
|
625
|
+
appendAll(orphaned, inner.orphaned);
|
|
624
626
|
} else {
|
|
625
627
|
const candidates = adopters.filter((candidate) => candidate !== child.name);
|
|
626
628
|
child.orphaned = { since: this.now(), formerParent, reason, candidates };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { MembraneError } from '@animalabs/membrane';
|
|
2
|
+
import { OverBudgetError, UncoveredDropError } from '@animalabs/context-manager';
|
|
3
|
+
import { isDeadlineCut } from './inference-bounds.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Longest failure text the inference-failure pipeline writes to one sink: the
|
|
7
|
+
* reason in the exhaustion stderr line, a field of a logs/failures.log record,
|
|
8
|
+
* an ops alert's message, the inference log's `error`, and a trace event's
|
|
9
|
+
* `error` and `stack`. A failure's text is whatever its source put in
|
|
10
|
+
* `Error.message`, and a provider can echo the entire rejected request there
|
|
11
|
+
* (one OpenAI-compatible 400 echoed ~1.7 MB). The sinks with shorter bounds
|
|
12
|
+
* name their own, and every cut goes through boundFailureText. This bound
|
|
13
|
+
* doesn't depend on membrane bounding its own errors.
|
|
14
|
+
*/
|
|
15
|
+
export const FAILURE_TEXT_MAX_CHARS = 2_000;
|
|
16
|
+
|
|
17
|
+
/** Longest provider error code recorded verbatim; real codes are short tokens.
|
|
18
|
+
* A failure's name and type are bounded the same way. */
|
|
19
|
+
export const PROVIDER_ERROR_CODE_MAX_CHARS = 128;
|
|
20
|
+
|
|
21
|
+
const isHighSurrogate = (text: string, index: number): boolean => {
|
|
22
|
+
const code = text.charCodeAt(index);
|
|
23
|
+
return code >= 0xd800 && code <= 0xdbff;
|
|
24
|
+
};
|
|
25
|
+
const isLowSurrogate = (text: string, index: number): boolean => {
|
|
26
|
+
const code = text.charCodeAt(index);
|
|
27
|
+
return code >= 0xdc00 && code <= 0xdfff;
|
|
28
|
+
};
|
|
29
|
+
/** Whether a cut before `index` would separate the halves of a surrogate pair. */
|
|
30
|
+
const splitsPair = (text: string, index: number): boolean =>
|
|
31
|
+
index > 0 && index < text.length && isHighSurrogate(text, index - 1) && isLowSurrogate(text, index);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `text` when it fits within `max` characters; otherwise its head and tail
|
|
35
|
+
* around a marker stating how many of how many characters were omitted, the
|
|
36
|
+
* whole within `max`. A cut that would separate a surrogate pair moves toward
|
|
37
|
+
* omission, so the result never holds half of a pair the text held whole, and
|
|
38
|
+
* bounding bounded text returns it as is. Each sink cuts the whole text, so
|
|
39
|
+
* its marker states the whole length.
|
|
40
|
+
*/
|
|
41
|
+
export function boundFailureText(text: string, max: number = FAILURE_TEXT_MAX_CHARS): string {
|
|
42
|
+
if (text.length <= max) return text;
|
|
43
|
+
const markerLength = ` …[${text.length} of ${text.length} characters omitted]… `.length;
|
|
44
|
+
const budget = Math.max(0, max - markerLength);
|
|
45
|
+
const tailLength = Math.floor(budget / 4);
|
|
46
|
+
let headEnd = budget - tailLength;
|
|
47
|
+
if (splitsPair(text, headEnd)) headEnd--;
|
|
48
|
+
let tailStart = text.length - tailLength;
|
|
49
|
+
if (splitsPair(text, tailStart)) tailStart++;
|
|
50
|
+
return `${text.slice(0, headEnd)} …[${tailStart - headEnd} of ${text.length} characters omitted]… ${text.slice(tailStart)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A MembraneError, from this framework's copy of membrane or from another: the
|
|
55
|
+
* host supplies the Membrane instance (config.membrane), so with two copies
|
|
56
|
+
* installed its errors are instances of a class this framework's `instanceof`
|
|
57
|
+
* doesn't match. Recognised by name as well, as context-manager's errors are;
|
|
58
|
+
* its fields are the same, so callers still check each one's type.
|
|
59
|
+
*/
|
|
60
|
+
export function isMembraneError(error: unknown): error is MembraneError {
|
|
61
|
+
return error instanceof MembraneError || (error instanceof Error && error.name === 'MembraneError');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function compileRefusalType(err: Error): 'over_budget' | 'context_refusal' | undefined {
|
|
65
|
+
if (err instanceof OverBudgetError || err.name === 'OverBudgetError') return 'over_budget';
|
|
66
|
+
// context-manager's fatal coverage invariant (fix/coverage-invariant-fatal,
|
|
67
|
+
// 2026-07-26): a compile REFUSES rather than shipping a context with
|
|
68
|
+
// silently-dropped messages. Recovery is identical to over_budget — kick
|
|
69
|
+
// the compression drain so summaries cover the un-represented span.
|
|
70
|
+
if (err instanceof UncoveredDropError || err.name === 'UncoveredDropError') return 'context_refusal';
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function houseReason(err: Error): string | undefined {
|
|
75
|
+
return isDeadlineCut(err) || compileRefusalType(err) !== undefined ? String(err.message) : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function failureWords(err: Error, otherwise = 'the turn failed'): string {
|
|
79
|
+
const own = houseReason(err);
|
|
80
|
+
if (own !== undefined) return own;
|
|
81
|
+
if (!isMembraneError(err)) return otherwise;
|
|
82
|
+
if (err.type === 'safety') return "the request couldn't be completed";
|
|
83
|
+
const status = typeof err.httpStatus === 'number' ? `HTTP ${err.httpStatus}` : undefined;
|
|
84
|
+
if (err.type === 'timeout') return `the model call timed out${status ? ` (${status})` : ''}`;
|
|
85
|
+
const type = typeof err.type === 'string' ? boundFailureText(err.type, PROVIDER_ERROR_CODE_MAX_CHARS) : undefined;
|
|
86
|
+
const parts = [type, status].filter((part): part is string => part !== undefined);
|
|
87
|
+
return `the model call failed${parts.length > 0 ? ` (${parts.join(', ')})` : ''}`;
|
|
88
|
+
}
|