@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/tool-presentation.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { constants, fchmodSync, closeSync, fstatSync, lstatSync, openSync, readS
|
|
|
3
3
|
import { closestNames } from './closest-names.js';
|
|
4
4
|
import { boundedErrorMessage, errorMessage } from './error-message.js';
|
|
5
5
|
import type { ToolDefinition, ToolResult } from './types/events.js';
|
|
6
|
+
import { appendAll } from './append-all.js';
|
|
6
7
|
|
|
7
8
|
export interface ToolPresentationConfig {
|
|
8
9
|
/** Absolute path to shared editable JSON. Missing file means no overrides. */
|
|
@@ -36,6 +37,8 @@ const MAX_DESCRIPTION_CHARS = 32768;
|
|
|
36
37
|
const protectedNames = new Set(['set_tool_visibility', 'set_tool_description', 'workspace--read', 'toolsets--open', 'toolsets--act', 'toolsets--look', 'toolsets--close']);
|
|
37
38
|
export const isPresentationTool = (name: string): boolean => name === 'set_tool_visibility' || name === 'set_tool_description';
|
|
38
39
|
const hash = (value: string): string => createHash('sha256').update(value).digest('hex');
|
|
40
|
+
/** The catalogue's line layout, folded into its revision so a line reference saved under an earlier layout reads as stale. Raise it with any change to where the catalogue's lines fall. */
|
|
41
|
+
const CATALOGUE_LAYOUT = 2;
|
|
39
42
|
|
|
40
43
|
/**
|
|
41
44
|
* A presentation or defaults file this host can't use. Each problem is a
|
|
@@ -61,9 +64,9 @@ const characters = (text: string): number => [...text].length;
|
|
|
61
64
|
/** A text's first `n` characters, never splitting a code point. */
|
|
62
65
|
const firstCharacters = (text: string, n: number): string => [...text].slice(0, n).join('');
|
|
63
66
|
|
|
64
|
-
/** A
|
|
65
|
-
function quoted(name: string): string {
|
|
66
|
-
return characters(name) <=
|
|
67
|
+
/** A text as a message quotes it: whole up to `limit` characters, and past that cut, with its length, since a file can hold a key, and a tool a description, of any size. */
|
|
68
|
+
function quoted(name: string, limit = 60): string {
|
|
69
|
+
return characters(name) <= limit ? JSON.stringify(name) : `${JSON.stringify(firstCharacters(name, limit - 3))}... (${count(characters(name))} characters)`;
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/** A file's problem as a message quotes it: its own clauses whole, and anything else bounded, since nobody wrote that text for its reader. */
|
|
@@ -246,7 +249,7 @@ export class ToolPresentation {
|
|
|
246
249
|
return {name:tool.name,source:sources.get(tool.name) ?? 'Unattributed',visible:override?.visible ?? true,originalDescription:tool.description,description,descriptionSource,inputSchema:structuredClone(tool.inputSchema)};
|
|
247
250
|
});
|
|
248
251
|
const available = tools.map((tool,i) => ({...structuredClone(tool),description:entries[i].description}));
|
|
249
|
-
return {revision:hash(JSON.stringify({raw,entries,diagnostics})),source:this.config.path,cataloguePath:this.config.cataloguePath,diagnostics,available,
|
|
252
|
+
return {revision:hash(JSON.stringify({layout:CATALOGUE_LAYOUT,raw,entries,diagnostics})),source:this.config.path,cataloguePath:this.config.cataloguePath,diagnostics,available,
|
|
250
253
|
advertised:available.filter((_,i)=>entries[i].visible),entries};
|
|
251
254
|
}
|
|
252
255
|
edit(tool: string, input: unknown, available: ToolDefinition[]): ToolResult {
|
|
@@ -336,7 +339,7 @@ export class ToolPresentation {
|
|
|
336
339
|
const name = args.name as string;
|
|
337
340
|
if (!available.some((t) => t.name === name)) {
|
|
338
341
|
const near = closestNames(name, available.map((t) => t.name));
|
|
339
|
-
throw new PresentationRefusal(`Nothing was changed: no tool named ${quoted(name)}
|
|
342
|
+
throw new PresentationRefusal(`Nothing was changed: you have no tool named ${quoted(name)} right now, shown or hidden.${near.length ? ` Did you mean ${quotedList(near, 'or', JSON.stringify)}?` : ''}`
|
|
340
343
|
+ ` Copy the exact name from your tool catalogue's index, workspace--read {"path":${JSON.stringify(this.config.cataloguePath)},"limit":140}, reading on with "offset" if the index runs past that.`);
|
|
341
344
|
}
|
|
342
345
|
if (field === 'description' && typeof args.description === 'string' && characters(args.description) > MAX_DESCRIPTION_CHARS)
|
|
@@ -346,6 +349,11 @@ export class ToolPresentation {
|
|
|
346
349
|
return name;
|
|
347
350
|
}
|
|
348
351
|
}
|
|
352
|
+
const SUMMARY_CHARACTERS = 200;
|
|
353
|
+
function summaryOf(description: string): string {
|
|
354
|
+
const first = description.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/).find(line => line.trim() !== '');
|
|
355
|
+
return first === undefined ? '' : `: ${quoted(first, SUMMARY_CHARACTERS)}`;
|
|
356
|
+
}
|
|
349
357
|
export function renderCatalogue(snapshot: PresentationSnapshot): string {
|
|
350
358
|
const groups = new Map<string, PresentationSnapshot['entries']>();
|
|
351
359
|
for (const entry of snapshot.entries) {
|
|
@@ -360,12 +368,22 @@ export function renderCatalogue(snapshot: PresentationSnapshot): string {
|
|
|
360
368
|
'Visible = not hidden by you: in your tool list, unless the host keeps it in its "settings" toolset or a server\'s tab (toolsets--look shows those). Hidden = moved into the "settings" toolset, or its MCPL server\'s tab: toolsets--look shows it, toolsets--act calls it once that toolset is open.',
|
|
361
369
|
'These are your visibility choices, not fixed primary/secondary rankings.',
|
|
362
370
|
'Copy exact names from the index; similar-sounding names may not exist.',
|
|
371
|
+
'Index by source: line N.',
|
|
363
372
|
'Show a hidden tool: set_tool_visibility {"name":"EXACT_NAME","visible":true}.',
|
|
364
373
|
`Read details: workspace--read {"path":${JSON.stringify(snapshot.cataloguePath)},"offset":LINE,"limit":LINES}.`,
|
|
365
374
|
'Line references below belong to this revision; reread the index after changes.', '',
|
|
366
375
|
];
|
|
376
|
+
const indexLineAt = lines.indexOf('Index by source: line N.');
|
|
367
377
|
// One line each: a quoted parse error can hold the file's own line breaks.
|
|
368
378
|
for (const diagnostic of snapshot.diagnostics) lines.push('Diagnostic: '+diagnostic.replace(/\r\n|\r|\n/g, '\\n'));
|
|
379
|
+
const hidden = snapshot.entries.filter(e => !e.visible);
|
|
380
|
+
const hiddenAt = new Map<string, number>();
|
|
381
|
+
if (hidden.length) {
|
|
382
|
+
lines.push(`## Hidden (${hidden.length})`, '');
|
|
383
|
+
for (const entry of hidden) {hiddenAt.set(entry.name, lines.length); lines.push('');}
|
|
384
|
+
lines.push('');
|
|
385
|
+
}
|
|
386
|
+
lines[indexLineAt] = `Index by source: line ${lines.length + 1}.`;
|
|
369
387
|
lines.push('## Index by source', '');
|
|
370
388
|
const index = new Map<string, number>();
|
|
371
389
|
for (const [source, entries] of groups) {
|
|
@@ -378,11 +396,18 @@ export function renderCatalogue(snapshot: PresentationSnapshot): string {
|
|
|
378
396
|
lines.push(`### ${source}`, '');
|
|
379
397
|
for (const entry of entries) {
|
|
380
398
|
const start = lines.length + 1;
|
|
381
|
-
lines.push(`#### ${entry.name} (${entry.visible?'visible':'hidden'})`, ''
|
|
382
|
-
|
|
383
|
-
|
|
399
|
+
lines.push(`#### ${entry.name} (${entry.visible?'visible':'hidden'})`, '');
|
|
400
|
+
appendAll(lines, entry.description.split('\n'));
|
|
401
|
+
lines.push('', `Description from: ${entry.descriptionSource ?? 'installed component'}`, '', `Schema: ${JSON.stringify(entry.inputSchema)}`);
|
|
402
|
+
if(entry.description!==entry.originalDescription) {
|
|
403
|
+
lines.push('', 'Original description:');
|
|
404
|
+
appendAll(lines, entry.originalDescription.split('\n'));
|
|
405
|
+
}
|
|
384
406
|
lines.push('');
|
|
385
|
-
|
|
407
|
+
const details = `details: offset ${start}, limit ${lines.length-start+1}`;
|
|
408
|
+
lines[index.get(entry.name)!] = `- [${entry.visible?'visible':'hidden'}] ${entry.name} — ${details}`;
|
|
409
|
+
const at = hiddenAt.get(entry.name);
|
|
410
|
+
if (at !== undefined) lines[at] = `- ${entry.name} (${entry.source})${summaryOf(entry.description)} — ${details}`;
|
|
386
411
|
}
|
|
387
412
|
}
|
|
388
413
|
return lines.join('\n');
|
|
@@ -1214,8 +1214,9 @@ export class TuneOutCoordinator {
|
|
|
1214
1214
|
* runDeferredCancels runs them sooner.
|
|
1215
1215
|
*/
|
|
1216
1216
|
private deferredRetry: ReturnType<typeof setTimeout> | undefined;
|
|
1217
|
-
/** Timer key for the scope's cadence and expiry
|
|
1218
|
-
|
|
1217
|
+
/** Timer key for the scope's cadence and expiry; never a channel's key,
|
|
1218
|
+
* which starts with `[`. */
|
|
1219
|
+
private static readonly SCOPE_KEY = 'all-except';
|
|
1219
1220
|
|
|
1220
1221
|
constructor(
|
|
1221
1222
|
private readonly channelRegistry: ChannelRegistry,
|
|
@@ -1223,8 +1224,15 @@ export class TuneOutCoordinator {
|
|
|
1223
1224
|
private readonly hooks: TuneOutFrameworkHooks,
|
|
1224
1225
|
) {}
|
|
1225
1226
|
|
|
1227
|
+
/** A channel's key in the coordinator's maps and its stored last looks.
|
|
1228
|
+
* The store refuses an object key holding a NUL, so no separator: the
|
|
1229
|
+
* pair as JSON, which no server or channel id can make ambiguous. */
|
|
1226
1230
|
private key(serverId: string, channelId: string): string {
|
|
1227
|
-
return
|
|
1231
|
+
return JSON.stringify([serverId, channelId]);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
private channelOf(key: string): [string, string] {
|
|
1235
|
+
return JSON.parse(key) as [string, string];
|
|
1228
1236
|
}
|
|
1229
1237
|
|
|
1230
1238
|
/** The readers' hand-off bookkeeping's key: one epoch's reading of one channel. */
|
|
@@ -1367,7 +1375,7 @@ export class TuneOutCoordinator {
|
|
|
1367
1375
|
if (!held.has(key) && this.holdFor(meta.serverId ?? '', meta.channelId)?.epochId === epochId) held.add(key);
|
|
1368
1376
|
}
|
|
1369
1377
|
for (const key of held) {
|
|
1370
|
-
const [serverId, channelId] =
|
|
1378
|
+
const [serverId, channelId] = this.channelOf(key);
|
|
1371
1379
|
this.rebuildTally(serverId, channelId);
|
|
1372
1380
|
}
|
|
1373
1381
|
this.pruneLooks();
|
|
@@ -1481,7 +1489,7 @@ export class TuneOutCoordinator {
|
|
|
1481
1489
|
private anchorSubconscious(occasion: 'enter' | 'resume' = 'enter'): void {
|
|
1482
1490
|
const active = this.activeStarts();
|
|
1483
1491
|
if (active.length === 0) return;
|
|
1484
|
-
const sequence =
|
|
1492
|
+
const sequence = active.reduce((min, e) => Math.min(min, e.sequence), Infinity);
|
|
1485
1493
|
const epochIds = active.filter((e) => e.sequence === sequence).map((e) => e.epochId);
|
|
1486
1494
|
const kept = this.ordinalsKept();
|
|
1487
1495
|
const read = kept ? this.readWindowState() : { starts: {} };
|
|
@@ -3186,10 +3194,16 @@ export class TuneOutCoordinator {
|
|
|
3186
3194
|
* of the record reads them in first (lossRecordUnread), and writes this
|
|
3187
3195
|
* run's kept ones there too; where this run keeps any, that write is
|
|
3188
3196
|
* made here, before the marker, and the last stop's bound is judged by
|
|
3189
|
-
* the destination's record (carriedNeeded).
|
|
3197
|
+
* the destination's record (carriedNeeded). The subconscious's last
|
|
3198
|
+
* looks are read again too, so the next look reads the destination's
|
|
3199
|
+
* record (lastLooks), unless `sameBranch` says the store switched to the
|
|
3200
|
+
* branch it was on, as a refusing manager's recovery does: no branch
|
|
3201
|
+
* moved, and the cache keeps any look a refused write held only there.
|
|
3202
|
+
* Never throws.
|
|
3190
3203
|
*/
|
|
3191
|
-
branchMoved(): void {
|
|
3204
|
+
branchMoved(sameBranch = false): void {
|
|
3192
3205
|
if (this.stopped) return;
|
|
3206
|
+
if (!sameBranch) this.lastLooksCache = undefined;
|
|
3193
3207
|
try {
|
|
3194
3208
|
const branch = this.branchNow();
|
|
3195
3209
|
if (branch !== undefined && this.runStarts.has(branch)) {
|
|
@@ -4512,7 +4526,7 @@ export class TuneOutCoordinator {
|
|
|
4512
4526
|
// One look per held channel with new traffic since the last look.
|
|
4513
4527
|
for (const [channelKey, diverted] of this.divertedSinceInvocation) {
|
|
4514
4528
|
if (diverted <= 0) continue;
|
|
4515
|
-
const [serverId, channelId] =
|
|
4529
|
+
const [serverId, channelId] = this.channelOf(channelKey);
|
|
4516
4530
|
if (this.channelRegistry.getTuneOutState(serverId, channelId)) continue; // a per-channel epoch's own cadence
|
|
4517
4531
|
this.invokeSubconscious(serverId, channelId, 'cadence', diverted);
|
|
4518
4532
|
}
|
|
@@ -4686,7 +4700,7 @@ export class TuneOutCoordinator {
|
|
|
4686
4700
|
this.writeScope(null);
|
|
4687
4701
|
this.clearScopeTimers();
|
|
4688
4702
|
for (const [key, pending] of this.pendingWakes) {
|
|
4689
|
-
const [serverId, channelId] =
|
|
4703
|
+
const [serverId, channelId] = this.channelOf(key);
|
|
4690
4704
|
if (this.channelRegistry.getTuneOutState(serverId, channelId)) continue;
|
|
4691
4705
|
clearTimeout(pending.timer);
|
|
4692
4706
|
this.pendingWakes.delete(key);
|
|
@@ -4730,7 +4744,7 @@ export class TuneOutCoordinator {
|
|
|
4730
4744
|
}
|
|
4731
4745
|
}
|
|
4732
4746
|
for (const key of this.divertedSinceInvocation.keys()) {
|
|
4733
|
-
const [serverId, channelId] =
|
|
4747
|
+
const [serverId, channelId] = this.channelOf(key);
|
|
4734
4748
|
if (!this.channelRegistry.getTuneOutState(serverId, channelId)) this.divertedSinceInvocation.delete(key);
|
|
4735
4749
|
}
|
|
4736
4750
|
this.dropHandOffs(epochId);
|
|
@@ -5149,14 +5163,17 @@ export class TuneOutCoordinator {
|
|
|
5149
5163
|
this.writeLooks(rest);
|
|
5150
5164
|
}
|
|
5151
5165
|
|
|
5152
|
-
/** At boot: the last looks of epochs no longer active go. */
|
|
5153
5166
|
private pruneLooks(): void {
|
|
5154
5167
|
if (this.hooks.subconsciousName() == null) return;
|
|
5155
5168
|
const looks = this.lastLooks();
|
|
5156
5169
|
if (!looks) return;
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5170
|
+
let active: Set<string>;
|
|
5171
|
+
try {
|
|
5172
|
+
active = new Set(this.holdsOnRecord().map((hold) => hold.epochId));
|
|
5173
|
+
} catch (err) {
|
|
5174
|
+
console.error(`[tune-out] the holds on record could not be read, so the subconscious's last looks are all kept: ${thrownReason(err)}`);
|
|
5175
|
+
return;
|
|
5176
|
+
}
|
|
5160
5177
|
const kept = Object.fromEntries(Object.entries(looks).filter(([epochId]) => active.has(epochId)));
|
|
5161
5178
|
if (Object.keys(kept).length !== Object.keys(looks).length) this.writeLooks(kept);
|
|
5162
5179
|
}
|
|
@@ -5181,7 +5198,7 @@ export class TuneOutCoordinator {
|
|
|
5181
5198
|
if (trigger === 'cadence' && backlog.length === 0) return false;
|
|
5182
5199
|
// The cursor only moves forward: a returned row handed again can be
|
|
5183
5200
|
// behind it.
|
|
5184
|
-
const cursor = Math.max(this.readerCursor.get(key) ?? -1
|
|
5201
|
+
const cursor = backlog.reduce((max, m) => Math.max(max, m.sequence), this.readerCursor.get(key) ?? -1);
|
|
5185
5202
|
if (backlog.length > 0) this.readerCursor.set(key, cursor);
|
|
5186
5203
|
const returned = this.readerReturned.get(key);
|
|
5187
5204
|
if (returned) {
|
package/src/tune-out/tools.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
2
2
|
import { authorLine } from '../author-presentation.js';
|
|
3
3
|
import type { StoredMessage } from '@animalabs/context-manager';
|
|
4
|
+
import { appendAll } from '../append-all.js';
|
|
4
5
|
/**
|
|
5
6
|
* The subconscious's tool surface (issue #77).
|
|
6
7
|
*
|
|
@@ -394,7 +395,7 @@ export function defaultReaderFraming(ctx: ReaderFramingContext): ContentBlock[]
|
|
|
394
395
|
const media = message.content.filter((b) => b.type !== 'text');
|
|
395
396
|
if (media.length > 0) {
|
|
396
397
|
flush();
|
|
397
|
-
content
|
|
398
|
+
appendAll(content, media);
|
|
398
399
|
}
|
|
399
400
|
}
|
|
400
401
|
flush();
|
package/src/types/agent.ts
CHANGED
|
@@ -221,11 +221,15 @@ export interface AgentConfig {
|
|
|
221
221
|
* counts it. After a fold, each block after the stretch it rewrote is
|
|
222
222
|
* bound to a conversation the request no longer holds. Where the context
|
|
223
223
|
* strategy still sends such a block, the provider drops it on every call
|
|
224
|
-
* until a later fold takes it out of the context,
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
224
|
+
* until a later fold takes it out of the context, or, with `binds` set
|
|
225
|
+
* too, until the next compile after a note lands: the stream tells the
|
|
226
|
+
* context manager which stored reply's thinking was dropped
|
|
227
|
+
* (`noteThinkingDropped`), and once a note lands, the next compile sends
|
|
228
|
+
* that reply without it. While such blocks are sent,
|
|
229
|
+
* no stream gives a sample and the calibration keeps the value it had. A
|
|
230
|
+
* strategy that leaves that thinking out at the fold sends none to drop.
|
|
231
|
+
* The direct API carries the control too, but reads no report: it traces
|
|
232
|
+
* no usage, feeds no calibration and notes nothing.
|
|
229
233
|
*
|
|
230
234
|
* Default false: nothing is sent, and the account's default applies.
|
|
231
235
|
*/
|
|
@@ -249,6 +253,11 @@ export interface AgentConfig {
|
|
|
249
253
|
* thinking is kept at the next compile. The direct API
|
|
250
254
|
* (`runInference`) notes nothing, so each of its replies' thinking is
|
|
251
255
|
* left out at the compile after it's stored;
|
|
256
|
+
* - with `dropBlock` set too, each framework stream tells the context
|
|
257
|
+
* manager which stored replies' thinking the provider reports dropping
|
|
258
|
+
* (`noteThinkingDropped`), naming the compile it was built from, so
|
|
259
|
+
* once a note lands, the next compile sends those replies without their
|
|
260
|
+
* thinking;
|
|
252
261
|
* - with `physicalWindowTokens` set too, `maxStreamTokens` no longer
|
|
253
262
|
* starts a new stream mid-turn, since the new stream's compile can take
|
|
254
263
|
* the turn's earlier reasoning out of the request;
|
|
@@ -753,6 +762,28 @@ export interface InferenceRequest {
|
|
|
753
762
|
* channel (ambient-only batches leave it unset).
|
|
754
763
|
*/
|
|
755
764
|
wakeChannelId?: string;
|
|
765
|
+
/**
|
|
766
|
+
* Framework-set on a gate wake whose batch held no message that addressed
|
|
767
|
+
* the agent, and which so took no route: the channels its messages came
|
|
768
|
+
* from (a `chat:system` notice names none, unless it's a coalesced
|
|
769
|
+
* render's latest occurrence), each once. A start merged from several
|
|
770
|
+
* requests carries every one's channels, or, when any of them comes from
|
|
771
|
+
* outside the framework, isn't a silent heartbeat tick and brought
|
|
772
|
+
* channels, only those requests' channels. A request a quiesce keeps in
|
|
773
|
+
* another's place, and one a provider hold takes another into, carry both
|
|
774
|
+
* requests' channels.
|
|
775
|
+
* The framework's own wakes that continue a
|
|
776
|
+
* turn (a restart, a re-inference, a continuation, a retry, a bounce, a
|
|
777
|
+
* re-arm), unless its own wake named a channel that resolves to none,
|
|
778
|
+
* carry the channels the turn's starts gathered, whether or not the turn
|
|
779
|
+
* had a locus, so a turn's trigger can name channels from several batches,
|
|
780
|
+
* and a turn they open with no locus can name them too.
|
|
781
|
+
* When such a turn's plain speech, or a sleep's announcement, is routed
|
|
782
|
+
* with no locus, the agent is told these channels, unless the turn's own
|
|
783
|
+
* wake named a channel that resolves to none, or the turn is an ephemeral
|
|
784
|
+
* run's, which is told nothing.
|
|
785
|
+
*/
|
|
786
|
+
wakeChannels?: Array<{ serverId: string; channelId: string }>;
|
|
756
787
|
/** Timestamp of the event the wake provenance was taken from (ms). */
|
|
757
788
|
wakeAt?: number;
|
|
758
789
|
/** Batched-wake lines the gate handed to this wake rather than wrote
|
package/src/types/framework.ts
CHANGED
|
@@ -191,18 +191,23 @@ export interface FrameworkConfig {
|
|
|
191
191
|
* (architecture channel, 2026-10-06: the §3 carve-out accepted by the
|
|
192
192
|
* residents).
|
|
193
193
|
*
|
|
194
|
-
* - `'delivery'` (default): the locus comes
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
194
|
+
* - `'delivery'` (default): the locus comes from the delivery that woke
|
|
195
|
+
* the turn, a conversation fork's home channel, or a channel the agent
|
|
196
|
+
* opened during the turn, and past those only from an open all-except
|
|
197
|
+
* hold's channel or `unpromptedChannel` (below), each for the turns that
|
|
198
|
+
* take it. Incoming traffic never moves it — not between turns, not
|
|
199
|
+
* mid-turn. A turn with no such locus keeps its prose private, and says
|
|
200
|
+
* so in a `[routing]` notice.
|
|
199
201
|
* - `'latest-inbound'`: the previous rule. The most recent delivered
|
|
200
202
|
* inbound message is a global fallback locus for turns without one, and
|
|
201
203
|
* an addressed message injected mid-turn re-pins the turn's locus.
|
|
202
204
|
*
|
|
203
|
-
* Held traffic never moves the locus under either rule.
|
|
204
|
-
*
|
|
205
|
-
*
|
|
205
|
+
* Held traffic never moves the locus under either rule. Under either, a
|
|
206
|
+
* turn the agent's own timer starts takes `unpromptedChannel` where it's
|
|
207
|
+
* set, after an all-except hold's channel and before both rules' own
|
|
208
|
+
* fallbacks. Neither rule applies to an agent under locked prose routing
|
|
209
|
+
* (AgentConfig.proseRouting 'locked'): its turns speak at its lock,
|
|
210
|
+
* whatever arrives.
|
|
206
211
|
*/
|
|
207
212
|
speech?: {
|
|
208
213
|
locus?: 'delivery' | 'latest-inbound';
|
|
@@ -502,6 +507,35 @@ export interface FrameworkConfig {
|
|
|
502
507
|
*/
|
|
503
508
|
inferenceRouting?: InferenceRoutingPolicy;
|
|
504
509
|
|
|
510
|
+
/**
|
|
511
|
+
* The channel for a turn the agent's own timer starts, which no message
|
|
512
|
+
* prompted: the wake `skip_reply` schedules, or the end of a `sleep`. Plain
|
|
513
|
+
* speech in locus or hybrid prose routing goes there, and so does
|
|
514
|
+
* `channel_publish` naming no channel in any prose routing mode but locked,
|
|
515
|
+
* which servers are told as the turn's default outgoing channel. An
|
|
516
|
+
* all-except hold's channel still comes first while one is open, and without
|
|
517
|
+
* the setting such a turn follows `speech.locus`. A turn the framework opens
|
|
518
|
+
* to continue such a turn that had no locus of its own, as in explicit or
|
|
519
|
+
* disabled routing, takes it too: a `!` continuation, autoContinue's next
|
|
520
|
+
* turn, or a bounced reply's wake. No other turn takes it: not one a message
|
|
521
|
+
* prompted, from a channel, a module, a person at a console or another agent;
|
|
522
|
+
* not a heartbeat's or a reminder's (its plain speech is held as drafts); not
|
|
523
|
+
* a conversation fork's or an ephemeral run's; not a locked agent's, which
|
|
524
|
+
* speaks at its lock or holds its speech as drafts; and not a start merged
|
|
525
|
+
* from a timer's wake and any other cause. One value, for every agent on the
|
|
526
|
+
* framework. A registered channel's id, e.g. `discord:{guildId}:{channelId}`,
|
|
527
|
+
* or, to pick one server's where two register that id,
|
|
528
|
+
* `{serverId}:{channelId}`. `create()` refuses a value that is neither a
|
|
529
|
+
* string nor null, or is blank; null reads as not set. A value that names no
|
|
530
|
+
* registered channel, several, or one its server declared inbound-only, when
|
|
531
|
+
* a turn would take it, is logged, and logged again only after a turn has
|
|
532
|
+
* used it since; the turn takes what it would without the setting.
|
|
533
|
+
* `channel_publish`'s description adds a default for such turns only on a
|
|
534
|
+
* framework with an event gate, the only one with such turns; on any other
|
|
535
|
+
* the description is as it is without the setting.
|
|
536
|
+
*/
|
|
537
|
+
unpromptedChannel?: string | null;
|
|
538
|
+
|
|
505
539
|
/** EventGate config. If omitted, all events trigger inference (unchanged default). */
|
|
506
540
|
gate?: GateOptions;
|
|
507
541
|
/**
|
|
@@ -677,6 +711,8 @@ export interface InferenceLogEntry {
|
|
|
677
711
|
success: boolean;
|
|
678
712
|
/** Why it didn't succeed: the failure's message, or what stopped it */
|
|
679
713
|
error?: string;
|
|
714
|
+
/** `error` in the framework's words, with no provider's text: what an agent may be shown of it */
|
|
715
|
+
errorWords?: string;
|
|
680
716
|
/** Request data or blob ID if large. A stream the framework ended at a
|
|
681
717
|
* tool boundary (stopReason 'turn_ended', 'context_budget' or
|
|
682
718
|
* 'physical_window') records a note in place of the body. */
|
|
@@ -54,6 +54,7 @@ export interface InferenceLogSummary {
|
|
|
54
54
|
requestId: string;
|
|
55
55
|
success: boolean;
|
|
56
56
|
error?: string;
|
|
57
|
+
errorWords?: string;
|
|
57
58
|
durationMs: number;
|
|
58
59
|
tokenUsage?: { input: number; output: number; cacheCreation?: number; cacheRead?: number };
|
|
59
60
|
stopReason?: string;
|
package/src/types/trace.ts
CHANGED
|
@@ -142,6 +142,14 @@ export type TraceEvent =
|
|
|
142
142
|
agentName: string;
|
|
143
143
|
/** The failure's message, bounded as every trace event's `error` is (2,000 characters). */
|
|
144
144
|
error: string;
|
|
145
|
+
/**
|
|
146
|
+
* The reason the agent's `[inference-failed]` note may give, before the
|
|
147
|
+
* note's own 600-character cut, and bounded as `error` is. Present only
|
|
148
|
+
* when the failure's message holds no provider's text: a progress
|
|
149
|
+
* bound's cut, a stream's abort, or context-manager's refusal of a
|
|
150
|
+
* compile. Absent otherwise, and a note then gives no reason.
|
|
151
|
+
*/
|
|
152
|
+
shownReason?: string;
|
|
145
153
|
/** Whether membrane classified the failure as retryable (observability). */
|
|
146
154
|
retryable?: boolean;
|
|
147
155
|
/**
|
|
@@ -156,6 +164,55 @@ export type TraceEvent =
|
|
|
156
164
|
/** The provider's own error code, when it sent one (at most 128 characters). */
|
|
157
165
|
providerErrorCode?: string;
|
|
158
166
|
})
|
|
167
|
+
| (TraceEventBase & {
|
|
168
|
+
/**
|
|
169
|
+
* A start whose compile was refused for budget while compression was
|
|
170
|
+
* pending waits in the agent's budget hold, idle, instead of failing
|
|
171
|
+
* (#304). It follows the refused attempt's `inference:failed`. Once
|
|
172
|
+
* something the refused compile depends on has changed (compression's
|
|
173
|
+
* progress, nothing left pending, a surgery or an undo, the agent's
|
|
174
|
+
* budget or live context settings, or a quiesce's end), the hold ends,
|
|
175
|
+
* the held requests are queued, and its note is stored as the agent's
|
|
176
|
+
* own row, entering its window at once or, while a turn of the agent's
|
|
177
|
+
* is alive, at its next presentation point; a failed write stores
|
|
178
|
+
* none. Their start's own compile is the check: refused again while
|
|
179
|
+
* compression is pending, it waits in a new hold, with a new
|
|
180
|
+
* `inference:held`. `inference:hold_ended` follows each, once. An
|
|
181
|
+
* ephemeral run's start, and one before the framework's `start()`,
|
|
182
|
+
* never waits.
|
|
183
|
+
*/
|
|
184
|
+
type: 'inference:held';
|
|
185
|
+
agentName: string;
|
|
186
|
+
/** 'over_budget', or 'context_refusal' when no summary covers a span the compile would drop. */
|
|
187
|
+
errorType: 'over_budget' | 'context_refusal';
|
|
188
|
+
/** The refusal's message, bounded as a trace event's `error` is (2,000 characters). */
|
|
189
|
+
error: string;
|
|
190
|
+
/** The refused compile's budget and size in tokens, when context-manager gave them. */
|
|
191
|
+
budget?: number;
|
|
192
|
+
actual?: number;
|
|
193
|
+
/** When this hold started (epoch ms). A wait held again after its
|
|
194
|
+
* release starts a new hold; its `context-refusal` alert and the note
|
|
195
|
+
* count from the wait's first hold. */
|
|
196
|
+
since: number;
|
|
197
|
+
/** Requests the hold holds as it starts: the refused start's own. */
|
|
198
|
+
heldRequests: number;
|
|
199
|
+
})
|
|
200
|
+
| (TraceEventBase & {
|
|
201
|
+
type: 'inference:hold_ended';
|
|
202
|
+
agentName: string;
|
|
203
|
+
/**
|
|
204
|
+
* 'released': something the refused compile depends on changed, so the
|
|
205
|
+
* held requests were queued and their start's compile is the check.
|
|
206
|
+
* 'stopped': the held turn was ended by `abortInference()`, `stop()` or
|
|
207
|
+
* its agent's end, reported after its `inference:aborted`. 'removed': a
|
|
208
|
+
* surgery or an undo removed what the held turn stood on, reported after
|
|
209
|
+
* its `inference:aborted`, and the wakes that joined it go back to the
|
|
210
|
+
* queue.
|
|
211
|
+
*/
|
|
212
|
+
outcome: 'released' | 'stopped' | 'removed';
|
|
213
|
+
/** Since the hold started (`inference:held`'s `since`). */
|
|
214
|
+
waitedMs: number;
|
|
215
|
+
})
|
|
159
216
|
|
|
160
217
|
// Streaming inference lifecycle
|
|
161
218
|
| (TraceEventBase & {
|
|
@@ -1043,14 +1100,19 @@ export type TraceEvent =
|
|
|
1043
1100
|
// this stream, not a separate system. See connectome docs/observability.md.
|
|
1044
1101
|
| (TraceEventBase & {
|
|
1045
1102
|
type: 'ops:alert';
|
|
1046
|
-
/** Alert kind — open set: 'refusal' | 'hard-down' | 'mcpl-down' | …
|
|
1103
|
+
/** Alert kind — open set: 'refusal' | 'hard-down' | 'mcpl-down' | …
|
|
1104
|
+
* A kind ending in `-clear` ends the alert of the kind before it:
|
|
1105
|
+
* 'context-refusal-clear' is raised when a budget hold whose wait
|
|
1106
|
+
* raised 'context-refusal' ends with its turn stopped or removed, so
|
|
1107
|
+
* the waiting start doesn't run. */
|
|
1047
1108
|
kind: string;
|
|
1048
1109
|
/** Agent name, or a server/component id for non-agent alerts (e.g. mcpl-down). */
|
|
1049
1110
|
agentName: string;
|
|
1050
1111
|
/** At most 2,000 characters. */
|
|
1051
1112
|
message: string;
|
|
1052
|
-
/** Kind-specific structured payload (mirrors the failures.log record
|
|
1053
|
-
*
|
|
1113
|
+
/** Kind-specific structured payload (mirrors the failures.log record,
|
|
1114
|
+
* where the alert writes one: a budget hold's 'context-refusal' and its
|
|
1115
|
+
* clear write none), bounded field by field as that record is. */
|
|
1054
1116
|
data?: Record<string, unknown>;
|
|
1055
1117
|
})
|
|
1056
1118
|
|
|
@@ -1560,7 +1622,7 @@ export type TraceEvent =
|
|
|
1560
1622
|
});
|
|
1561
1623
|
|
|
1562
1624
|
/** Why rows left an agent's window (`presentation:retracted`). */
|
|
1563
|
-
export type PresentationRetractReason = 'hide' | 'suppress' | 'coalescer' | 'module' | 'resume' | 'rewind' | 'shed' | 'boot' | 'elsewhere';
|
|
1625
|
+
export type PresentationRetractReason = 'hide' | 'suppress' | 'coalescer' | 'module' | 'resume' | 'rewind' | 'shed' | 'boot' | 'elsewhere' | 'unsent-injection';
|
|
1564
1626
|
|
|
1565
1627
|
/**
|
|
1566
1628
|
* Listener for trace events.
|